1#![warn(missing_docs)]
2use lsp_types::Uri;
5
6fn fallback_uri() -> Uri {
7 for candidate in ["file:///unknown", "file:///", "about:blank", "urn:perl-lsp:unknown"] {
8 if let Ok(uri) = candidate.parse::<Uri>() {
9 return uri;
10 }
11 }
12
13 let mut suffix = 0usize;
15 loop {
16 let candidate = format!("http://localhost/{suffix}");
17 if let Ok(uri) = candidate.parse::<Uri>() {
18 return uri;
19 }
20 suffix = suffix.saturating_add(1);
21 }
22}
23
24#[must_use]
28pub fn parse_uri(s: &str) -> Uri {
29 match s.parse::<Uri>() {
30 Ok(uri) => uri,
31 Err(_) => fallback_uri(),
32 }
33}
34
35#[cfg(test)]
36mod tests {
37 use super::{fallback_uri, parse_uri};
38
39 #[test]
42 fn parse_uri_returns_original_for_valid_uri() {
43 let uri = parse_uri("file:///tmp/test.pl");
44 assert_eq!(uri.as_str(), "file:///tmp/test.pl");
45 }
46
47 #[test]
48 fn parse_uri_unix_absolute_path() {
49 let uri = parse_uri("file:///home/user/project/lib/Module.pm");
50 assert_eq!(uri.as_str(), "file:///home/user/project/lib/Module.pm");
51 }
52
53 #[test]
54 fn parse_uri_deeply_nested_path() {
55 let uri = parse_uri("file:///a/b/c/d/e/f/g.pl");
56 assert_eq!(uri.as_str(), "file:///a/b/c/d/e/f/g.pl");
57 }
58
59 #[test]
60 fn parse_uri_file_root() {
61 let uri = parse_uri("file:///");
62 assert_eq!(uri.as_str(), "file:///");
63 }
64
65 #[test]
68 fn parse_uri_windows_drive_path() {
69 let uri = parse_uri("file:///C:/Users/dev/project/file.pm");
70 assert_eq!(uri.as_str(), "file:///C:/Users/dev/project/file.pm");
71 }
72
73 #[test]
74 fn parse_uri_windows_lowercase_drive() {
75 let uri = parse_uri("file:///c:/perl/lib/Module.pm");
76 assert_eq!(uri.as_str(), "file:///c:/perl/lib/Module.pm");
77 }
78
79 #[test]
80 fn parse_uri_windows_drive_root() {
81 let uri = parse_uri("file:///D:/");
82 assert_eq!(uri.as_str(), "file:///D:/");
83 }
84
85 #[test]
88 fn parse_uri_percent_encoded_space() {
89 let uri = parse_uri("file:///path/to/my%20module/Foo.pm");
90 assert_eq!(uri.as_str(), "file:///path/to/my%20module/Foo.pm");
91 }
92
93 #[test]
94 fn parse_uri_percent_encoded_special_chars() {
95 let uri = parse_uri("file:///tmp/%E2%9C%93check.pl");
96 assert_eq!(uri.as_str(), "file:///tmp/%E2%9C%93check.pl");
97 }
98
99 #[test]
100 fn parse_uri_percent_encoded_hash() {
101 let uri = parse_uri("file:///tmp/file%23name.pl");
103 assert_eq!(uri.as_str(), "file:///tmp/file%23name.pl");
104 }
105
106 #[test]
107 fn parse_uri_percent_encoded_windows_space() {
108 let uri = parse_uri("file:///C:/My%20Documents/script.pl");
109 assert_eq!(uri.as_str(), "file:///C:/My%20Documents/script.pl");
110 }
111
112 #[test]
115 fn parse_uri_falls_back_for_invalid_uri() {
116 let uri = parse_uri("not a uri");
117 assert!(!uri.as_str().is_empty());
118 }
119
120 #[test]
121 fn parse_uri_empty_string_does_not_panic() {
122 let _uri = parse_uri("");
125 }
126
127 #[test]
128 fn parse_uri_fallback_for_bare_path() {
129 let uri = parse_uri("/usr/local/lib/perl5/Foo.pm");
132 assert!(!uri.as_str().is_empty());
133 }
134
135 #[test]
136 fn parse_uri_fallback_for_whitespace_only() {
137 let uri = parse_uri(" ");
138 assert!(!uri.as_str().is_empty());
139 }
140
141 #[test]
142 fn parse_uri_fallback_for_control_chars() {
143 let uri = parse_uri("\x00\x01\x02");
144 assert!(!uri.as_str().is_empty());
145 }
146
147 #[test]
148 fn parse_uri_fallback_returns_valid_uri_string() {
149 let uri = parse_uri("definitely not valid %%% uri");
151 let s = uri.as_str();
152 assert!(
153 s.starts_with("file:")
154 || s.starts_with("about:")
155 || s.starts_with("urn:")
156 || s.starts_with("http:"),
157 "fallback URI should have a recognized scheme, got: {s}"
158 );
159 }
160
161 #[test]
162 fn fallback_uri_returns_known_scheme() {
163 let uri = fallback_uri();
164 let s = uri.as_str();
165 assert!(
166 s.starts_with("file:")
167 || s.starts_with("about:")
168 || s.starts_with("urn:")
169 || s.starts_with("http:"),
170 "fallback_uri should produce a recognized scheme, got: {s}"
171 );
172 }
173
174 #[test]
175 fn fallback_uri_is_deterministic() {
176 let a = fallback_uri();
177 let b = fallback_uri();
178 assert_eq!(a.as_str(), b.as_str());
179 }
180
181 #[test]
184 fn parse_uri_preserves_https_scheme() {
185 let uri = parse_uri("https://example.com/docs/perl");
186 assert_eq!(uri.as_str(), "https://example.com/docs/perl");
187 }
188
189 #[test]
190 fn parse_uri_preserves_untitled_scheme() {
191 let uri = parse_uri("untitled:Untitled-1");
193 assert_eq!(uri.as_str(), "untitled:Untitled-1");
194 }
195
196 #[test]
199 fn parse_uri_round_trip_preserves_string() {
200 let inputs = [
201 "file:///tmp/test.pl",
202 "file:///C:/Users/file.pm",
203 "file:///path/with%20space/lib.pm",
204 "https://example.com/resource",
205 ];
206 for input in inputs {
207 let uri = parse_uri(input);
208 assert_eq!(uri.as_str(), input, "round-trip failed for: {input}");
209 }
210 }
211
212 #[test]
215 fn parse_uri_with_query_and_fragment() {
216 let uri = parse_uri("file:///path/to/file.pm?line=10#L10");
217 assert_eq!(uri.as_str(), "file:///path/to/file.pm?line=10#L10");
218 }
219
220 #[test]
221 fn parse_uri_with_port() {
222 let uri = parse_uri("http://localhost:8080/path");
223 assert_eq!(uri.as_str(), "http://localhost:8080/path");
224 }
225
226 #[test]
227 fn parse_uri_very_long_path() {
228 let long_segment = "a".repeat(200);
229 let input = format!("file:///{long_segment}/{long_segment}.pm");
230 let uri = parse_uri(&input);
231 assert_eq!(uri.as_str(), input);
232 }
233}