Skip to content

Commit 870dc57

Browse files
committed
Fix; Refactor; Update tests
1 parent 0e73458 commit 870dc57

1 file changed

Lines changed: 64 additions & 39 deletions

File tree

lib/asimov-module/src/url.rs

Lines changed: 64 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -20,22 +20,26 @@ pub fn normalize_url(url: &str) -> Result<String, iri_string::types::CreationErr
2020

2121
let path = iri.path_str();
2222

23-
if scheme == "file"
24-
&& let Some(rest) = path.strip_prefix("~/")
25-
{
23+
// TODO: utilize `path.normalize_lexically()` once it stabilizes
24+
// https://github.com/rust-lang/rust/issues/134694
25+
26+
if scheme == "file" && path.starts_with("~/") {
27+
let rest = path.strip_prefix("~/").unwrap(); // safe, the prefix was checked just above
28+
2629
let home_dir = std::env::home_dir().expect("unable to determine home directory");
27-
let path2 = home_dir.join(rest);
28-
let path3 = std::path::absolute(&path2).unwrap_or(path2);
29-
let path4 = path3.to_str().unwrap_or(path);
30-
31-
write!(&mut out, "{}", path4).unwrap();
32-
} else if scheme == "file" && !path.starts_with('/') {
33-
let cur_dir = std::env::current_dir().expect("unable to determine current directory");
34-
let path2 = cur_dir.join(path);
35-
let path3 = std::path::absolute(&path2).unwrap_or(path2);
36-
let path4 = path3.to_str().unwrap_or(path);
37-
38-
write!(&mut out, "{}", path4).unwrap();
30+
31+
let path = home_dir.join(rest);
32+
let path = std::path::absolute(&path).unwrap_or(path);
33+
let path = path.canonicalize().unwrap_or(path);
34+
35+
write!(&mut out, "{}", path.display()).unwrap();
36+
} else if scheme == "file" {
37+
// `std::path::absolute` also changes relative paths to absolute with the current directory
38+
// as base.
39+
let path = std::path::absolute(path).unwrap_or_else(|_| std::path::PathBuf::from(path));
40+
let path = path.canonicalize().unwrap_or(path);
41+
42+
write!(&mut out, "{}", path.display()).unwrap();
3943
} else if iri.authority_str().is_some() && path.is_empty() {
4044
write!(&mut out, "/").unwrap();
4145
} else {
@@ -56,17 +60,20 @@ pub fn normalize_url(url: &str) -> Result<String, iri_string::types::CreationErr
5660
#[cfg(test)]
5761
mod tests {
5862
use super::*;
59-
use std::string::ToString;
63+
use std::{format, string::ToString};
6064

6165
#[test]
6266
fn url_normalization() {
6367
let cases = [
6468
("https://example.org", "https://example.org/"),
6569
("https://example.org/", "https://example.org/"),
6670
("http://example.com/path", "http://example.com/path"),
71+
("https://api.example.com", "https://api.example.com/"),
72+
("http://localhost:3000", "http://localhost:3000/"),
73+
("ftp://fileserver.local", "ftp://fileserver.local/"),
6774
(
68-
"https://user:pass@example.org:8080/path?query=value#fragment",
69-
"https://user:pass@example.org:8080/path?query=value#fragment",
75+
"https://user:pass@example.org:8080/path?foo=bar&query=hello world#fragment",
76+
"https://user:pass@example.org:8080/path?foo=bar&query=hello%20world#fragment",
7077
),
7178
("near://testnet/123456789", "near://testnet/123456789"),
7279
(
@@ -88,23 +95,50 @@ mod tests {
8895
"https://example.org/path%20already%20encoded",
8996
),
9097
(
91-
"https://example.org/?q=test&foo=bar",
92-
"https://example.org/?q=test&foo=bar",
98+
"data:text/plain;base64,SGVsbG8=",
99+
"data:text/plain;base64,SGVsbG8=",
93100
),
101+
("tel:+1-555-123-4567", "tel:+1-555-123-4567"),
102+
("urn:isbn:1234567890", "urn:isbn:1234567890"),
94103
(
95-
"https://example.org/page#section1",
96-
"https://example.org/page#section1",
104+
// Plain strings get `file:` scheme and current directory prepended
105+
"document.txt",
106+
&format!(
107+
"file:{}/document.txt",
108+
std::env::current_dir().unwrap().display()
109+
),
97110
),
98111
(
99-
"https://example.org/search?q=hello world",
100-
"https://example.org/search?q=hello%20world",
112+
// Domain-like strings without scheme get treated as files
113+
"example.org",
114+
&format!(
115+
"file:{}/example.org",
116+
std::env::current_dir().unwrap().display()
117+
),
101118
),
119+
// TODO: should this be inferred?
120+
// ("localhost:8080", "http://localhost:8080".into()),
102121
(
103-
"data:text/plain;base64,SGVsbG8=",
104-
"data:text/plain;base64,SGVsbG8=",
122+
"folder name/file.txt",
123+
&format!(
124+
"file:{}/folder%20name/file.txt",
125+
std::env::current_dir().unwrap().display()
126+
),
127+
),
128+
(
129+
"./subfolder/../file.txt",
130+
&format!(
131+
"file:{}/subfolder/../file.txt",
132+
std::env::current_dir().unwrap().display()
133+
),
134+
),
135+
(
136+
"../parent/file.txt",
137+
&format!(
138+
"file:{}/../parent/file.txt",
139+
std::env::current_dir().unwrap().display()
140+
),
105141
),
106-
("tel:+1-555-123-4567", "tel:+1-555-123-4567"),
107-
("urn:isbn:1234567890", "urn:isbn:1234567890"),
108142
];
109143

110144
for case in cases {
@@ -173,15 +207,6 @@ mod tests {
173207
"non-path-looking input should be treated as a file in current directory, input: {:?}",
174208
input
175209
);
176-
177-
// let input = "hello\\ world!";
178-
// let want = "file:".to_string() + &cur_dir + "/hello%5C%20world!";
179-
// assert_eq!(
180-
// normalize_url(input).unwrap(),
181-
// want,
182-
// "output should be url encoded, input: {:?}",
183-
// input
184-
// );
185210
}
186211

187212
#[cfg(windows)]
@@ -191,11 +216,11 @@ mod tests {
191216
let cases = [
192217
(
193218
"/file with spaces.txt",
194-
format!("file:///{drive}:/file%20with%20spaces.txt"),
219+
format!("file:/{drive}:/file%20with%20spaces.txt"),
195220
),
196221
(
197222
"/file+with+pluses.txt",
198-
format!("file:///{drive}:/file+with+pluses.txt"),
223+
format!("file:/{drive}:/file+with+pluses.txt"),
199224
),
200225
];
201226

0 commit comments

Comments
 (0)