1mod anchor;
2mod defaults;
3mod domain;
4mod login;
5mod path;
6mod port;
7mod query;
8mod scheme;
9pub mod scheme_separator;
10
11pub mod global;
12use crate::core::defaults::default_port_mappings;
13use crate::error::ParseError;
14use crate::url::Url;
15
16use std::collections::HashMap;
17
18pub struct Parser {
19 port_mappings: HashMap<&'static str, (u32, &'static str)>,
20}
21
22impl Parser {
23 pub fn new(port_mappings: Option<HashMap<&'static str, (u32, &'static str)>>) -> Self {
32 Parser {
33 port_mappings: port_mappings.unwrap_or_else(default_port_mappings),
34 }
35 }
36
37 pub fn parse(&self, url: &str) -> Result<Url, ParseError> {
66 let scheme = self.scheme(url).map(|s| s.0.to_string());
67 let user_pass = self.login(url);
68 let user_pass = (
69 user_pass.0.map(|s| s.to_string()),
70 user_pass.1.map(|s| s.to_string()),
71 );
72 let domain_fields = self.domain(url);
73 let port = self.port(url);
74 let path = self
75 .path(url)
76 .map(|x| x.iter().map(|s| s.to_string()).collect());
77 let query = self.query(url).map(|s| s.to_string());
78 let anchor = self.anchor(url).map(|s| s.to_string());
79 Ok(Url {
80 scheme,
81 user_pass,
82 subdomain: domain_fields.subdomain.map(|s| s.to_string()),
83 domain: domain_fields.domain.map(|s| s.to_string()),
84 top_level_domain: domain_fields.top_level_domain.map(|s| s.to_string()),
85 port,
86 path,
87 query,
88 anchor,
89 })
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn test_parse_works_when_typical() {
99 for (protocol, _) in default_port_mappings().iter() {
100 let address = &format!("{}{}", protocol, "foo.bar");
101 let url = Parser::new(None).parse(address);
102 assert!(url.is_ok());
103 }
104 }
105
106 #[test]
107 fn test_parse_scheme_works_when_typical() {
108 for (protocol, _) in default_port_mappings().iter() {
109 let address = &format!("{}://{}", protocol, "foo.bar");
110 let url = Parser::new(None).parse(address).unwrap();
111 assert_eq!(&url.scheme.as_ref().unwrap(), protocol);
112 }
113 }
114
115 #[test]
116 fn test_parse_scheme_works_when_no_scheme_in_url() {
117 for (protocol, _) in default_port_mappings().iter() {
118 let address = &format!("{}{}", protocol, "foo.bar");
119 let url = Parser::new(None).parse(address);
120 assert!(url.is_ok());
121 }
122 }
123
124 #[test]
125 fn test_parse_works_when_full_url() {
126 let input = "https://www.example.co.uk:443/blog/article/search?docid=720&hl=en#dayone";
127 let result = Parser::new(None).parse(input).unwrap();
128 assert_eq!(
129 result,
130 Url {
131 scheme: Some("https".to_string()),
132 user_pass: (None, None),
133 subdomain: Some("www".to_string()),
134 domain: Some("example.co".to_string()),
135 top_level_domain: Some("uk".to_string()),
136 port: Some(443),
137 path: Some(vec![
138 "blog".to_string(),
139 "article".to_string(),
140 "search".to_string(),
141 ]),
142 query: Some("docid=720&hl=en".to_string()),
143 anchor: Some("dayone".to_string()),
144 }
145 );
146 }
147
148 #[test]
149 fn test_parse_works_when_full_url_with_login() {
150 let input =
151 "https://user:pass@www.example.co.uk:443/blog/article/search?docid=720&hl=en#dayone";
152 let result = Parser::new(None).parse(input).unwrap();
153 assert_eq!(
154 result,
155 Url {
156 scheme: Some("https".to_string()),
157 user_pass: (Some("user".to_string()), Some("pass".to_string())),
158 subdomain: Some("www".to_string()),
159 domain: Some("example.co".to_string()),
160 top_level_domain: Some("uk".to_string()),
161 port: Some(443),
162 path: Some(vec![
163 "blog".to_string(),
164 "article".to_string(),
165 "search".to_string(),
166 ]),
167 query: Some("docid=720&hl=en".to_string()),
168 anchor: Some("dayone".to_string()),
169 }
170 );
171 }
172
173 #[test]
174 fn test_parse_works_when_user_login() {
175 let input = "scp://user@example.co.uk:22/path/to/file.txt";
176 let result = Parser::new(None).parse(input).unwrap();
177 assert_eq!(
178 result,
179 Url {
180 scheme: Some("scp".to_string()),
181 user_pass: (Some("user".to_string()), None),
182 subdomain: Some("example".to_string()),
183 domain: Some("co".to_string()),
184 top_level_domain: Some("uk".to_string()),
185 port: Some(22),
186 path: Some(vec![
187 "path".to_string(),
188 "to".to_string(),
189 "file.txt".to_string(),
190 ]),
191 query: None,
192 anchor: None,
193 }
194 );
195 }
196
197 #[test]
198 fn test_parse_works_when_user_login_no_port() {
199 let input = "scp://user@example.co.uk/path/to/file.txt";
200 let result = Parser::new(None).parse(input).unwrap();
201 assert_eq!(
202 result,
203 Url {
204 scheme: Some("scp".to_string()),
205 user_pass: (Some("user".to_string()), None),
206 subdomain: Some("example".to_string()),
207 domain: Some("co".to_string()),
208 top_level_domain: Some("uk".to_string()),
209 port: Some(22),
210 path: Some(vec![
211 "path".to_string(),
212 "to".to_string(),
213 "file.txt".to_string(),
214 ]),
215 query: None,
216 anchor: None,
217 }
218 );
219 }
220
221 #[test]
222 fn test_parse_works_when_custom_port_mappings_full_login() {
223 let input = "myschema://user:pass@example.co.uk/path/to/file.txt";
224 let mut myport_mappings = HashMap::new();
225 myport_mappings.insert("myschema", (8888, "My custom schema"));
226 let result = Parser::new(Some(myport_mappings)).parse(input).unwrap();
227 assert_eq!(
228 result,
229 Url {
230 scheme: Some("myschema".to_string()),
231 user_pass: (Some("user".to_string()), Some("pass".to_string())),
232 subdomain: Some("example".to_string()),
233 domain: Some("co".to_string()),
234 top_level_domain: Some("uk".to_string()),
235 port: Some(8888),
236 path: Some(vec![
237 "path".to_string(),
238 "to".to_string(),
239 "file.txt".to_string(),
240 ]),
241 query: None,
242 anchor: None,
243 }
244 );
245 }
246
247 #[test]
248 fn test_parse_works_when_ip() {
249 let input = "ftp://192.168.178.242/dir";
250 let result = Parser::new(None).parse(input).unwrap();
251 assert_eq!(
252 result,
253 Url {
254 scheme: Some("ftp".to_string()),
255 user_pass: (None, None),
256 subdomain: None,
257 domain: Some("192.168.178.242".to_string()),
258 top_level_domain: None,
259 port: Some(21),
260 path: Some(vec![
261 "dir".to_string(),
262 ]),
263 query: None,
264 anchor: None,
265 }
266 );
267 }
268}