url_parse/core/
mod.rs

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    /// Create a new parser object. Optionally pass in a hash map of default port mappings.
24    /// Its fields are then directly accessible.
25    ///
26    /// # Example
27    /// ```rust,no_run
28    /// use url_parse::core::Parser;
29    /// let parser = Parser::new(None);
30    /// ```
31    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    /// Create a new parser object with `Parser::new()`. You can then use `parser.parse(url)` which will return a public `Url` parsed structure back.
38    /// Its fields are then directly accessible.
39    ///
40    /// # Example
41    /// ```rust,no_run
42    /// use url_parse::core::Parser;
43    /// use url_parse::url::Url;
44    /// let input = "https://user:pass@www.example.co.uk:443/blog/article/search?docid=720&hl=en#dayone";
45    /// let result = Parser::new(None).parse(input).unwrap();
46    /// assert_eq!(
47    ///     result,
48    ///     Url {
49    ///         scheme: Some("https".to_string()),
50    ///         user_pass: (Some("user".to_string()), Some("pass".to_string())),
51    ///         subdomain: Some("www".to_string()),
52    ///         domain: Some("example.co".to_string()),
53    ///         top_level_domain: Some("uk".to_string()),
54    ///         port: Some(443),
55    ///         path: Some(vec![
56    ///             "blog".to_string(),
57    ///             "article".to_string(),
58    ///             "search".to_string(),
59    ///         ]),
60    ///         query: Some("docid=720&hl=en#dayone".to_string()),
61    ///         anchor: Some("dayone".to_string()),
62    ///     }
63    /// )
64    /// ```
65    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}