url_parse/
url.rs

1#[derive(Debug)]
2pub struct Url {
3    pub scheme: Option<String>,
4    pub user_pass: (Option<String>, Option<String>),
5    pub subdomain: Option<String>,
6    pub domain: Option<String>,
7    pub top_level_domain: Option<String>,
8    pub port: Option<u32>,
9    pub path: Option<Vec<String>>,
10    pub query: Option<String>,
11    pub anchor: Option<String>,
12}
13
14impl Url {
15    /// Extract the representation of the host for this URL.
16    ///
17    /// # Example
18    /// ```rust
19    /// use url_parse::core::Parser;
20    /// use url_parse::core::global::Domain;
21    /// let input = "https://user:pass@www.example.com:443/blog/article/search?docid=720&hl=en#dayone";
22    /// let expected = "example.com";
23    /// let parsed = Parser::new(None).parse(input).unwrap();
24    /// let result = parsed.host_str().unwrap();
25    /// assert_eq!(result, expected);
26    /// ```
27    pub fn host_str(&self) -> Option<String> {
28        match &self.top_level_domain {
29            Some(v) => Some(self.domain.as_ref().unwrap().to_owned() + "." + v),
30            None => Some(self.domain.as_ref().unwrap().to_owned()),
31        }
32    }
33
34    /// Extract the username from the url.
35    ///
36    /// # Example
37    /// ```rust
38    /// use url_parse::core::Parser;
39    /// use url_parse::core::global::Domain;
40    /// let input = "https://user:pass@www.example.com:443/blog/article/search?docid=720&hl=en#dayone";
41    /// let expected = 443;
42    /// let parsed = Parser::new(None).parse(input).unwrap();
43    /// let result = parsed.port_or_known_default().unwrap();
44    /// assert_eq!(result, expected);
45    /// ```
46    pub fn port_or_known_default(&self) -> Option<u32> {
47        self.port
48    }
49
50    /// Extract the username from the url.
51    ///
52    /// # Example
53    /// ```rust
54    /// use url_parse::core::Parser;
55    /// use url_parse::core::global::Domain;
56    /// let input = "https://user:pass@www.example.com:443/blog/article/search?docid=720&hl=en#dayone";
57    /// let expected = "user";
58    /// let parsed = Parser::new(None).parse(input).unwrap();
59    /// let result = parsed.username().unwrap();
60    /// assert_eq!(result, expected);
61    /// ```
62    pub fn username(&self) -> Option<String> {
63        match &self.user_pass {
64            (Some(user), Some(_)) | (Some(user), None) => Some(user.to_owned()),
65            (None, None) => None,
66            (None, Some(_)) => None,
67        }
68    }
69
70    /// Extract the password from the url.
71    ///
72    /// # Example
73    /// ```rust
74    /// use url_parse::core::Parser;
75    /// use url_parse::core::global::Domain;
76    /// let input = "https://user:pass@www.example.com:443/blog/article/search?docid=720&hl=en#dayone";
77    /// let expected = "pass";
78    /// let parsed = Parser::new(None).parse(input).unwrap();
79    /// let result = parsed.password().unwrap();
80    /// assert_eq!(result, expected);
81    /// ```
82    pub fn password(&self) -> Option<String> {
83        match &self.user_pass {
84            (Some(_), Some(pass)) => Some(pass.to_owned()),
85            (None, None) => None,
86            (None, Some(_)) | (Some(_), None) => None,
87        }
88    }
89
90    /// Extract the path segments from the path.
91    ///
92    /// # Example
93    /// ```rust
94    /// use url_parse::core::Parser;
95    /// use url_parse::core::global::Domain;
96    /// let input = "https://www.example.co.uk:443/blog/article/search?docid=720&hl=en#dayone";
97    /// let result = Parser::new(None).path(input).unwrap();
98    /// let expected = vec!["blog", "article", "search"];
99    /// assert_eq!(result, expected);
100    /// ```
101    pub fn path_segments(&self) -> Option<Vec<String>> {
102        self.path.clone()
103    }
104
105    /// Serialize an URL struct to a String.
106    ///
107    /// # Example
108    /// ```rust
109    /// use url_parse::core::Parser;
110    /// use url_parse::core::global::Domain;
111    /// use url_parse::url::Url;
112    ///
113    ///let input = Url {
114    ///    scheme: Some("https".to_string()),
115    ///    user_pass: (Some("user".to_string()), Some("pass".to_string())),
116    ///    subdomain: Some("www".to_string()),
117    ///    domain: Some("example.co".to_string()),
118    ///    top_level_domain: Some("uk".to_string()),
119    ///    port: Some(443),
120    ///    path: Some(vec![
121    ///        "blog".to_string(),
122    ///        "article".to_string(),
123    ///        "search".to_string(),
124    ///    ]),
125    ///    query: Some("docid=720&hl=en".to_string()),
126    ///    anchor: Some("dayone".to_string()),
127    ///};
128    ///let expected =
129    ///    "https://user:pass@www.example.co.uk:443/blog/article/search?docid=720&hl=en#dayone";
130    ///
131    ///let result = input.serialize();
132    /// assert_eq!(result, expected);
133    /// ```
134    pub fn serialize(&self) -> String {
135        let mut result: String = "".to_string();
136        if self.scheme.is_some() {
137            result += self.scheme.as_ref().unwrap();
138            result += "://";
139        }
140        let (user, pass) = &self.user_pass;
141        if user.is_some() {
142            result += user.as_ref().unwrap();
143        }
144        if pass.is_some() {
145            result += ":";
146            result += pass.as_ref().unwrap();
147            result += "@";
148        }
149        if self.subdomain.is_some() {
150            result += self.subdomain.as_ref().unwrap();
151            result += ".";
152        }
153        if self.domain.is_some() {
154            result += self.domain.as_ref().unwrap();
155            result += ".";
156        }
157        if self.top_level_domain.is_some() {
158            result += self.top_level_domain.as_ref().unwrap();
159        }
160        if self.port.is_some() {
161            result += ":";
162            result += &self.port.unwrap().to_string();
163        }
164
165        if self.path.is_some() {
166            for segment in self.path_segments().unwrap().iter() {
167                result += "/";
168                result += segment;
169            }
170        }
171        if self.query.is_some() {
172            result += "?";
173            result += self.query.as_ref().unwrap();
174        }
175        if self.anchor.is_some() {
176            result += "#";
177            result += self.anchor.as_ref().unwrap();
178        }
179        result
180    }
181    /// Create a new empty instance with all fields set to none.
182    pub fn empty() -> Self {
183        Self {
184            scheme: None,
185            user_pass: (None, None),
186            subdomain: None,
187            domain: None,
188            top_level_domain: None,
189            port: None,
190            path: None,
191            query: None,
192            anchor: None,
193        }
194    }
195}
196
197/// Compare two objects of this type.
198impl PartialEq for Url {
199    fn eq(&self, other: &Self) -> bool {
200        self.scheme == other.scheme
201            && self.user_pass == other.user_pass
202            && self.subdomain == other.subdomain
203            && self.domain == other.domain
204            && self.top_level_domain == other.top_level_domain
205            && self.port == other.port
206            && self.path == other.path
207            && self.query == other.query
208            && self.anchor == other.anchor
209    }
210}
211
212/// Display the serialization of this URL.
213impl std::fmt::Display for Url {
214    #[inline]
215    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
216        write!(fmt, "{:?}", self)
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use super::*;
223
224    #[test]
225    fn test_empty_works_when_typical() {
226        let expected = Url {
227            scheme: None,
228            user_pass: (None, None),
229            subdomain: None,
230            domain: None,
231            top_level_domain: None,
232            port: None,
233            path: None,
234            query: None,
235            anchor: None,
236        };
237        let result = Url::empty();
238        assert_eq!(result, expected);
239    }
240
241    #[test]
242    fn test_extract_host_works_when_typical() {
243        let mut input = Url::empty();
244        input.subdomain = Some("abc".to_owned());
245        input.domain = Some("def".to_owned());
246        input.top_level_domain = Some("xyz".to_owned());
247
248        let result = input.host_str().unwrap();
249
250        assert_eq!(result, "def.xyz".to_owned());
251    }
252
253    #[test]
254    fn test_extract_host_works_when_no_top_level_domain() {
255        let mut input = Url::empty();
256        input.subdomain = Some("abc".to_owned());
257        input.domain = Some("def".to_owned());
258        input.top_level_domain = None;
259
260        let result = input.host_str().unwrap();
261
262        assert_eq!(result, "def".to_owned());
263    }
264
265    #[test]
266    fn test_port_or_known_default_when_typical() {
267        let mut input = Url::empty();
268        input.port = Some(1234);
269
270        let result = input.port_or_known_default().unwrap();
271
272        assert_eq!(result, 1234);
273    }
274
275    #[test]
276    fn test_username_works_when_typical() {
277        let mut input = Url::empty();
278        input.user_pass = (Some("user".to_string()), Some("pass".to_string()));
279
280        let result = input.username().unwrap();
281
282        assert_eq!(result, "user".to_owned());
283    }
284
285    #[test]
286    fn test_username_works_when_no_password() {
287        let mut input = Url::empty();
288        input.user_pass = (Some("user".to_string()), None);
289
290        let result = input.username().unwrap();
291
292        assert_eq!(result, "user".to_owned());
293    }
294
295    #[test]
296    fn test_username_is_none_when_no_credentials() {
297        let input = Url::empty();
298
299        let result = input.username();
300
301        assert!(result.is_none());
302    }
303
304    #[test]
305    fn test_username_is_none_when_no_username_but_impossible_password() {
306        let mut input = Url::empty();
307        input.user_pass = (None, Some("pass".to_string()));
308
309        let result = input.username();
310
311        assert!(result.is_none());
312    }
313
314    #[test]
315    fn test_password_works_when_typical() {
316        let mut input = Url::empty();
317        input.user_pass = (Some("user".to_string()), Some("pass".to_string()));
318
319        let result = input.password().unwrap();
320
321        assert_eq!(result, "pass".to_owned());
322    }
323
324    #[test]
325    fn test_password_none_when_no_credentials() {
326        let mut input = Url::empty();
327        input.user_pass = (None, None);
328
329        let result = input.password();
330
331        assert!(result.is_none());
332    }
333
334    #[test]
335    fn test_password_none_when_no_password() {
336        let mut input = Url::empty();
337        input.user_pass = (Some("user".to_string()), None);
338
339        let result = input.password();
340
341        assert!(result.is_none());
342    }
343    #[test]
344    fn test_print_url_when_typical() {
345        let input = Url::empty();
346        println!("{}", input);
347    }
348
349    #[test]
350    fn test_path_works_when_partial_url() {
351        let mut input = Url::empty();
352        let expected = vec![
353            "blog".to_string(),
354            "article".to_string(),
355            "search".to_string(),
356        ];
357        input.path = Some(expected.clone());
358
359        let result = input.path_segments().unwrap();
360        assert_eq!(result, expected);
361    }
362
363    #[test]
364    fn test_serialize_to_string() {
365        let input = Url {
366            scheme: Some("https".to_string()),
367            user_pass: (Some("user".to_string()), Some("pass".to_string())),
368            subdomain: Some("www".to_string()),
369            domain: Some("example.co".to_string()),
370            top_level_domain: Some("uk".to_string()),
371            port: Some(443),
372            path: Some(vec![
373                "blog".to_string(),
374                "article".to_string(),
375                "search".to_string(),
376            ]),
377            query: Some("docid=720&hl=en".to_string()),
378            anchor: Some("dayone".to_string()),
379        };
380        let expected =
381            "https://user:pass@www.example.co.uk:443/blog/article/search?docid=720&hl=en#dayone";
382
383        let result = input.serialize();
384
385        assert_eq!(result, expected);
386    }
387
388    #[test]
389    fn test_no_regression_when_serializing() {
390        use crate::core::Parser;
391        let url = Parser::new(None).parse("google.com").unwrap();
392        assert_eq!("google.com/", url.serialize())
393    }
394}