url_build_parse/
lib.rs

1//! # url build parse
2//!
3//! `url-build-parse` provides the ability to parse URL from string as well as construct URL from parts.
4//!
5//! See [URL on Wikipedia](https://en.wikipedia.org/wiki/URL) and [RFC 3986](https://www.rfc-editor.org/rfc/rfc3986) for more information.
6//!
7//! Some supported URLs as an example (but not limited to):
8//! - ftp://ftp.is.co.za/rfc/rfc1808.txt
9//! - http://www.ietf.org/rfc/rfc2396.txt
10//! - ldap://[2001:db8::7]/c=GB?objectClass?one
11//! - mailto:John.Doe@example.com
12//! - news:comp.infosystems.www.servers.unix
13//! - tel:+1-816-555-1212
14//! - telnet://192.0.2.16:80/
15//! - urn:oasis:names:specification:docbook:dtd:xml:4.1.2
16//!
17//! Take a look at tests to get information on how to use it.
18//!
19
20
21use std::collections::HashMap;
22use url_search_params;
23use url_search_params::{build_url_search_params, parse_url_search_params};
24
25#[derive(PartialEq, Eq, Clone, Debug)]
26pub struct UrlComponents {
27    pub scheme: String,
28    pub authority: Option<UrlAuthority>,
29    pub path: String,
30    pub query: Option<HashMap<String, String>>,
31    pub fragment: Option<String>
32}
33#[derive(PartialEq, Eq, Clone, Debug)]
34pub struct UrlAuthority {
35    pub user_info: Option<UrlUserInfo>,
36    pub host: String,
37    pub port: Option<usize>
38}
39#[derive(PartialEq, Eq, Clone, Debug)]
40pub struct UrlUserInfo {
41    pub username: String,
42    pub password: Option<String>
43}
44
45
46impl UrlComponents {
47    pub fn new() -> UrlComponents {
48        let url_components = UrlComponents {
49            scheme: "".to_string(),
50            authority: None,
51            path: "".to_string(),
52            query: None,
53            fragment: None };
54        url_components
55    }
56}
57
58
59/// Convert given string into a UrlComponents struct
60///
61/// # Examples
62///
63/// ```
64/// use std::collections::HashMap;
65/// use url_build_parse::{build_url, parse_url, UrlAuthority, UrlComponents, UrlUserInfo};
66///
67/// let authority = UrlAuthority {
68///     user_info: Option::from(
69///                     UrlUserInfo
70///                         {
71///                             username: "usr".to_string(),
72///                             password: Option::from("pwd".to_string())
73///                         }),
74///     host: "somehost".to_string(),
75///     port: Option::from(80)
76/// };
77///
78/// let mut q = HashMap::new();
79/// q.insert("q".to_string(), "123".to_string());
80///
81///
82/// let url_components = UrlComponents {
83///     scheme: "https".to_string(),
84///     authority: Option::from(authority),
85///     path: "/".to_string(),
86///     query: Option::from(q),
87///     fragment: Option::from("fragment".to_string())
88/// };
89///
90/// let url = build_url(url_components.clone()).unwrap();
91/// assert_eq!("https://usr:pwd@somehost:80/?q=123#fragment", url.as_str());
92///
93/// let parsed_url_components = parse_url(url.as_str()).unwrap();
94/// assert_eq!(url_components, parsed_url_components);
95/// ```
96pub fn parse_url(url: &str) -> Result<UrlComponents, String> {
97    let mut url_components = UrlComponents::new();
98
99    let boxed_scheme = extract_scheme(url);
100    if boxed_scheme.is_err() {
101        return Err(boxed_scheme.err().unwrap());
102    }
103
104    let (scheme, _remaining_url) = boxed_scheme.unwrap();
105    url_components.scheme = scheme;
106    let mut remaining_url = _remaining_url;
107
108
109    let boxed_authority = extract_authority(remaining_url.as_str());
110    if boxed_authority.is_err() {
111        return Err(boxed_authority.err().unwrap());
112    }
113
114    let (authority_string, boxed_remaining_url) = boxed_authority.unwrap();
115
116    if authority_string.is_some() {
117        let boxed_authority = parse_authority(authority_string.unwrap().as_str());
118        if boxed_authority.is_err() {
119            return Err(boxed_authority.err().unwrap());
120        }
121
122        let (boxed_username, boxed_password, host, boxed_port) = boxed_authority.unwrap();
123
124
125        let mut authority = UrlAuthority {
126            user_info: None,
127            host,
128            port: None
129        };
130
131        let mut user_info : Option<UrlUserInfo> = None;
132
133        if boxed_username.is_some() {
134            let username = boxed_username.unwrap();
135            if user_info.is_none() {
136                let mut password : Option<String> = None;
137                if boxed_password.is_some() {
138                    password = boxed_password;
139                }
140                user_info = Some(UrlUserInfo { username: username.to_string(), password });
141            }
142
143            authority.user_info = user_info;
144        }
145
146
147        if boxed_port.is_some() {
148            let port = boxed_port;
149            authority.port = port;
150        }
151
152        url_components.authority = Option::from(authority);
153    }
154
155
156
157    if boxed_remaining_url.is_none() {
158        return Ok(url_components)
159    }
160    remaining_url = boxed_remaining_url.unwrap();
161
162    let boxed_path = extract_path(remaining_url.as_str());
163    if boxed_path.is_err() {
164        return Err(boxed_path.err().unwrap());
165    }
166    let (_path, _remaining_url) = boxed_path.unwrap();
167
168    url_components.path = _path;
169    if _remaining_url.is_none() {
170        return Ok(url_components)
171    }
172    remaining_url = _remaining_url.unwrap();
173
174
175    let (boxed_query, _remaining_url) = extract_query(remaining_url.as_str());
176    if boxed_query.is_some() {
177        let query  = boxed_query.unwrap();
178        let parsed_query = parse_query(query.as_str()).unwrap();
179        let params: HashMap<String, String> = parse_url_search_params(parsed_query.as_str());
180        url_components.query = Some(params);
181        if _remaining_url.is_none() {
182            return Ok(url_components)
183        }
184        remaining_url = _remaining_url.unwrap();
185    }
186
187    let boxed_fragment = extract_fragment(remaining_url.as_str());
188    if boxed_fragment.is_err() {
189        return Err(boxed_fragment.err().unwrap());
190    }
191
192    let fragment = parse_fragment(boxed_fragment.unwrap().as_str()).unwrap();
193    url_components.fragment = Option::from(fragment);
194
195    Ok(url_components)
196}
197
198/// Convert given UrlComponents struct into URL string
199///
200/// # Examples
201///
202/// ```
203/// use url_build_parse::parse_url;
204/// let url = "https://usr:pwd@somehost:80/path?param=value&anotherParam#fragment";
205/// let url_components = parse_url(url).unwrap();
206///
207///
208/// assert_eq!(url_components.scheme, "https");
209/// assert_eq!(url_components.authority.as_ref().unwrap().user_info.as_ref().unwrap().username, "usr");
210/// assert_eq!(url_components.authority.as_ref().unwrap().user_info.as_ref().unwrap().password.as_ref().unwrap(), "pwd");
211/// assert_eq!(url_components.authority.as_ref().unwrap().host, "somehost");
212/// assert_eq!(*url_components.authority.as_ref().unwrap().port.as_ref().unwrap() as u8, 80 as u8);
213/// assert_eq!(url_components.path, "/path");
214/// assert_eq!(url_components.query.as_ref().unwrap().get("param").unwrap(), "value");
215/// assert!(url_components.query.as_ref().unwrap().contains_key("anotherParam"));
216/// assert_eq!("", url_components.query.as_ref().unwrap().get("anotherParam").unwrap());
217/// ```
218pub fn build_url(url_components: UrlComponents) -> Result<String, String> {
219    let mut url = "".to_string();
220
221    if url_components.fragment.is_some() {
222        url = ["#".to_string(),  url_components.fragment.unwrap(), url].join("");
223    }
224
225    if url_components.query.is_some() {
226        let query = build_url_search_params(url_components.query.unwrap());
227        url = ["?".to_string(), query, url].join("");
228    }
229
230    url = [url_components.path, url].join("");
231
232    if url_components.authority.is_some() {
233        let authority = build_authority(url_components.authority.unwrap());
234        url = ["//".to_string(), authority, url].join("");
235    }
236
237    url = [url_components.scheme, ":".to_string(), url].join("");
238
239    Ok(url)
240}
241
242pub(crate) fn build_authority(url_authority: UrlAuthority) -> String {
243    let mut authority = "".to_string();
244
245    if url_authority.user_info.is_some() {
246        let url_user_info = url_authority.user_info.unwrap();
247        authority = url_user_info.username;
248        if url_user_info.password.is_some() {
249            authority = [authority, ":".to_string(), url_user_info.password.unwrap()].join("");
250        }
251    }
252
253    if authority.chars().count() != 0 {
254        authority = [authority, "@".to_string(), url_authority.host].join("");
255    } else {
256        authority = [authority,  url_authority.host].join("");
257    }
258
259
260
261    if url_authority.port.is_some() {
262        authority = [authority, ":".to_string(), url_authority.port.unwrap().to_string()].join("");
263    }
264
265    authority
266}
267
268pub(crate) fn extract_scheme(url: &str) -> Result<(String, String), String> {
269    let boxed_split_at_path = url.split_once(":");
270    if boxed_split_at_path.is_some() {
271        let (scheme, remaining_url) = boxed_split_at_path.unwrap();
272        Ok((scheme.to_string(), remaining_url.to_string()))
273    } else {
274        Err("unable to identify scheme".to_string())
275    }
276}
277
278pub(crate) fn extract_authority(mut url: &str) -> Result<(Option<String>, Option<String>), String> {
279    if url.chars().count() == 0 {
280        let error_message = "error: remaining url is empty";
281        return Err(error_message.to_string())
282    }
283
284    if !url.contains("//") {
285        return Ok((None, Option::from(url.to_string())))
286    }
287
288    let (_, _remaining_url) = url.split_once("//").unwrap();
289    url = _remaining_url;
290
291    let  is_there_a_slash = url.contains("/");
292    let  is_there_a_question_mark = url.contains("?");
293    let  is_there_a_hash = url.contains("#");
294
295    if !is_there_a_slash && !is_there_a_question_mark && !is_there_a_hash {
296        return Ok((Option::from(url.to_string()), None))
297    }
298
299    if is_there_a_slash {
300        let boxed_split = url.split_once("/");
301        if boxed_split.is_some() {
302            let (authority, remaining_url) = boxed_split.unwrap();
303            let remaining_url = ["/", remaining_url].join("");
304            let authority_option = Option::from(authority.to_string());
305            let remaining_url = Option::from(remaining_url.to_string());
306            return Ok((authority_option, remaining_url))
307        }
308    }
309
310    if !is_there_a_slash && is_there_a_question_mark {
311        let boxed_split = url.split_once("?");
312        if boxed_split.is_some() {
313            let (authority, remaining_url) = boxed_split.unwrap();
314            let authority_option = Option::from(authority.to_string());
315            let remaining_url = ["?", remaining_url].join("");
316            let remaining_url = Option::from(remaining_url.to_string());
317            return Ok((authority_option, remaining_url))
318        }
319    }
320
321    if !is_there_a_slash && !is_there_a_question_mark && is_there_a_hash {
322        let boxed_split = url.split_once("#");
323        if boxed_split.is_some() {
324            let (authority, remaining_url) = boxed_split.unwrap();
325            let remaining_url = ["#", remaining_url].join("");
326            let authority_option = Option::from(authority.to_string());
327            let remaining_url = Option::from(remaining_url.to_string());
328            return Ok((authority_option, remaining_url))
329        }
330    }
331
332    let error_message = ["error: something went wrong with remaining url ", url].join("");
333    Err(error_message.to_string())
334
335}
336
337pub(crate) fn extract_path(url: &str) -> Result<(String, Option<String>), String> {
338    if url.chars().count() == 0 {
339        let error_message = "error: remaining url is empty";
340        return Err(error_message.to_string())
341    }
342
343    let is_there_a_question_mark = url.contains("?");
344    let is_there_a_hash = url.contains("#");
345
346    if !is_there_a_question_mark && !is_there_a_hash {
347        return Ok((url.to_string(), None));
348    }
349
350    let mut delimiter = "?";
351    if !is_there_a_question_mark && is_there_a_hash {
352        delimiter = "#";
353    }
354
355    let boxed_split = url.split_once(&delimiter);
356    if boxed_split.is_some() {
357        let (_path, _rest) = boxed_split.unwrap();
358        let path = _path.to_string();
359        let remaining_url: String =
360            [delimiter.to_string(), _rest.to_string()].join("");
361
362        return Ok((path.to_string(), Option::from(remaining_url)));
363    }
364
365
366    let error_message = ["error: something went wrong with remaining url ", url].join("");
367    Err(error_message.to_string())
368
369}
370
371pub(crate) fn extract_query(url: &str) ->
372       (Option<String>, Option<String>) {
373    if url.chars().count() == 0 {
374        return (None, None);
375    }
376
377    let is_there_a_hash = url.contains("#");
378
379    if is_there_a_hash {
380        let (query, rest) = url.split_once("#").unwrap();
381        let rest = ["#".to_string(), rest.to_string()].join("");
382        let mut query_option : Option<String> = None;
383
384        if query.chars().count() != 0 {
385            query_option = Some(query.to_string());
386        }
387
388        (query_option, Option::from(rest.to_string()))
389    } else {
390        (Option::from(url.to_string()), None)
391    }
392
393}
394
395pub(crate) fn extract_fragment(url: &str) -> Result<String, String> {
396    if url.chars().count() == 0 {
397        let error_message = "error: remaining url is empty";
398        return Err(error_message.to_string())
399    }
400
401    let is_there_a_hash = url.contains("#");
402
403    if !is_there_a_hash {
404        let error_message = ["error: fragment is not defined url: ", url].join("");
405        return Err(error_message.to_string())
406    }
407
408    let (_, fragment) = url.split_once("#").unwrap();
409
410    let fragment = ["#".to_string(), fragment.to_string()].join("");
411    Ok(fragment.to_string())
412
413}
414
415pub(crate) fn parse_query(query_with_question_mark: &str) -> Result<String, String> {
416    let (_, query) = query_with_question_mark.split_once("?").unwrap();
417
418    Ok(query.to_string())
419}
420
421pub(crate) fn parse_fragment(url: &str) -> Result<String, String> {
422    let (_, fragment) = url.split_once("#").unwrap();
423
424    Ok(fragment.to_string())
425}
426
427pub(crate) fn parse_authority(authority: &str)
428    -> Result<
429        (
430            Option<String>,
431            Option<String>,
432            String,
433            Option<usize>
434        ), String> {
435    let mut port : Option<usize> = None;
436
437    let mut remaining_authority = authority.to_string();
438
439    let boxed_userinfo = extract_userinfo(remaining_authority.as_str());
440    let (username, password, _remaining_authority) = boxed_userinfo.unwrap();
441    remaining_authority = _remaining_authority;
442
443    let boxed_host = extract_host(remaining_authority.as_str());
444    let (host, _remaining_authority) = boxed_host.unwrap();
445
446    if _remaining_authority.is_some() {
447        let boxed_port = extract_port(_remaining_authority.unwrap().as_str());
448        port = boxed_port.unwrap();
449    }
450
451    Ok((username, password, host, port))
452}
453
454pub(crate) fn extract_userinfo(authority: &str) -> Result<(Option<String>, Option<String>, String), String> {
455    let mut username : Option<String> = None;
456    let mut password : Option<String> = None;
457
458
459    let mut remaining_authority = authority.to_string();
460
461    let is_there_an_at_symbol = authority.contains("@");
462    if is_there_an_at_symbol {
463        let (userinfo, _remaining_authority) = authority.split_once("@").unwrap();
464        remaining_authority = _remaining_authority.to_string();
465        let is_there_a_colon = userinfo.contains(":");
466        if is_there_a_colon {
467            let (_username, _password) = userinfo.split_once(":").unwrap();
468            username = Some(_username.to_string());
469            password = Some(_password.to_string());
470        } else {
471            let _username = userinfo.to_string();
472            username = Some(_username);
473        }
474    }
475
476    Ok((username, password, remaining_authority))
477}
478
479pub(crate) fn extract_host(authority: &str) -> Result<(String, Option<String>), String> {
480    let mut host : String = authority.to_string();
481    let mut remaining_authority: Option<String> = None;
482
483    let is_it_an_ip_v6_url = authority.contains("]");
484    if is_it_an_ip_v6_url {
485        let (_host, _remaining_authority) = authority.split_once("]").unwrap();
486        host = [_host, "]"].join("");
487        let it_contains_port = _remaining_authority.contains(":");
488        if it_contains_port {
489            remaining_authority = Option::from(_remaining_authority.to_string());
490        }
491    } else {
492        let it_contains_port = authority.contains(":");
493        if it_contains_port {
494            let (_host, _remaining_authority) = authority.split_once(":").unwrap();
495            host = _host.to_string();
496            remaining_authority = Option::from([":", _remaining_authority].join(""));
497        }
498    }
499
500    Ok((host, remaining_authority))
501}
502
503pub(crate) fn extract_port(authority: &str) -> Result<Option<usize>, String> {
504    let mut port: Option<usize> = None;
505
506    let is_there_a_colon = authority.contains(":");
507    if is_there_a_colon {
508        let (_, port_as_string) = authority.split_once(":").unwrap();
509
510        let boxed_port = port_as_string.parse::<usize>();
511        if boxed_port.is_err() {
512            let msg = [
513                "unable to parse port from remaining authority ".to_string(),
514                " | ".to_string(),
515                boxed_port.err().unwrap().to_string(),
516                " | ".to_string(),
517                port_as_string.to_string()].join("");
518            return Err(msg)
519        }
520
521        port = Some(boxed_port.unwrap());
522    }
523
524    Ok(port)
525}
526
527
528
529
530#[cfg(test)]
531mod tests {
532    use std::collections::HashMap;
533    use crate::{build_authority, build_url, extract_authority, extract_fragment, extract_host, extract_path, extract_port, extract_query, extract_scheme, extract_userinfo, parse_authority, parse_url, UrlAuthority, UrlComponents, UrlUserInfo};
534
535    #[test]
536    fn extract_scheme_test_no_delimiter() {
537        let url = "schemewithoutdelimiter";
538        let boxed_result = extract_scheme(url);
539
540        assert!(boxed_result.is_err());
541        assert_eq!("unable to identify scheme", boxed_result.err().unwrap());
542    }
543
544    #[test]
545    fn extract_scheme_test() {
546        let url = "https://example.com";
547        let boxed_result = extract_scheme(url);
548        let (scheme, remaining_url) = boxed_result.unwrap();
549
550        assert_eq!("https", scheme);
551        assert_eq!("//example.com", remaining_url);
552    }
553
554    #[test]
555    fn extract_authority_test_no_authority() {
556        let remaining_url = "/path?q=qwerty";
557        let boxed_result = extract_authority(remaining_url);
558        let (authority, remaining_url) = boxed_result.unwrap();
559
560        assert_eq!(None, authority);
561        assert_eq!("/path?q=qwerty", remaining_url.unwrap());
562    }
563
564    #[test]
565    fn extract_authority_test_no_authority_no_slash() {
566        let remaining_url = "path?q=qwerty";
567        let boxed_result = extract_authority(remaining_url);
568        let (authority, remaining_url) = boxed_result.unwrap();
569
570        assert_eq!(None, authority);
571        assert_eq!("path?q=qwerty", remaining_url.unwrap());
572    }
573
574    #[test]
575    fn extract_authority_test() {
576        let remaining_url = "//example.com";
577        let boxed_result = extract_authority(remaining_url);
578        let (authority, remaining_url) = boxed_result.unwrap();
579
580        assert_eq!("example.com", authority.unwrap());
581        assert_eq!(None, remaining_url);
582    }
583
584    #[test]
585    fn extract_authority_path_defined_query_defined_fragment_defined() {
586        let remaining_url = "//example.com/some-path?q=test#123";
587        let boxed_result = extract_authority(remaining_url);
588        let (authority, remaining_url) = boxed_result.unwrap();
589
590        assert_eq!("example.com", authority.unwrap());
591        assert_eq!("/some-path?q=test#123", remaining_url.unwrap());
592    }
593
594    #[test]
595    fn extract_authority_path_defined_as_slash_query_defined_fragment_defined() {
596        let remaining_url = "//user:passwd@example.com:443/?q=test#123";
597        let boxed_result = extract_authority(remaining_url);
598        let (authority, remaining_url) = boxed_result.unwrap();
599
600        assert_eq!("user:passwd@example.com:443", authority.unwrap());
601        assert_eq!("/?q=test#123", remaining_url.unwrap());
602    }
603
604    #[test]
605    fn extract_authority_path_undefined_query_defined_fragment_defined() {
606        let remaining_url = "//user:passwd@example.com?q=test#123";
607        let boxed_result = extract_authority(remaining_url);
608        let (authority, remaining_url) = boxed_result.unwrap();
609
610        assert_eq!("user:passwd@example.com", authority.unwrap());
611        assert_eq!("?q=test#123", remaining_url.unwrap());
612    }
613
614    #[test]
615    fn extract_authority_path_undefined_query_undefined_fragment_defined() {
616        let remaining_url = "//example.com:80#123";
617        let boxed_result = extract_authority(remaining_url);
618        let (authority, remaining_url) = boxed_result.unwrap();
619
620        assert_eq!("example.com:80", authority.unwrap());
621        assert_eq!("#123", remaining_url.unwrap());
622    }
623
624    #[test]
625    fn extract_authority_path_defined_query_undefined_fragment_defined() {
626        let remaining_url = "//example.com/some-path#123";
627        let boxed_result = extract_authority(remaining_url);
628        let (authority, remaining_url) = boxed_result.unwrap();
629
630        assert_eq!("example.com", authority.unwrap());
631        assert_eq!("/some-path#123", remaining_url.unwrap());
632    }
633
634    #[test]
635    fn extract_authority_undefined_path_zero_length_query_undefined_fragment_undefined() {
636        let remaining_url = "";
637        let boxed_result = extract_authority(remaining_url);
638        assert!(boxed_result.is_err());
639        assert_eq!("error: remaining url is empty", boxed_result.err().unwrap());
640    }
641
642    #[test]
643    fn extract_authority_defined_path_zero_length_query_undefined_fragment_undefined() {
644        let remaining_url = "//usr:pwd@host:443";
645        let boxed_result = extract_authority(remaining_url);
646
647        let (authority, remaining_url) = boxed_result.unwrap();
648
649        assert_eq!("usr:pwd@host:443", authority.unwrap());
650        assert_eq!(None, remaining_url);
651    }
652
653    #[test]
654    fn extract_path_path_defined_query_undefined_fragment_defined() {
655        let remaining_url = "/some-path#123";
656        let boxed_result = extract_path(remaining_url);
657        let (path, remaining_url) = boxed_result.unwrap();
658
659        assert_eq!("/some-path", path);
660        assert_eq!("#123", remaining_url.unwrap());
661    }
662
663    #[test]
664    fn extract_path_path_defined_query_defined_fragment_defined() {
665        let remaining_url = "/some-path?q=query#123";
666        let boxed_result = extract_path(remaining_url);
667        let (path, remaining_url) = boxed_result.unwrap();
668
669        assert_eq!("/some-path", path);
670        assert_eq!("?q=query#123", remaining_url.unwrap());
671    }
672
673    #[test]
674    fn extract_path_path_defined_query_defined_fragment_undefined() {
675        let remaining_url = "/some-path?q=query";
676        let boxed_result = extract_path(remaining_url);
677        let (path, remaining_url) = boxed_result.unwrap();
678
679        assert_eq!("/some-path", path);
680        assert_eq!("?q=query", remaining_url.unwrap());
681    }
682
683    #[test]
684    fn extract_path_path_defined_as_slash_query_defined_fragment_undefined() {
685        let remaining_url = "/?q=query";
686        let boxed_result = extract_path(remaining_url);
687        let (path, remaining_url) = boxed_result.unwrap();
688
689        assert_eq!("/", path);
690        assert_eq!("?q=query", remaining_url.unwrap());
691    }
692
693    #[test]
694    fn extract_path_path_zero_length_query_defined_fragment_defined() {
695        let remaining_url = "?q=query#fragment";
696        let boxed_result = extract_path(remaining_url);
697        let (path, remaining_url) = boxed_result.unwrap();
698
699        assert_eq!("", path);
700        assert_eq!("?q=query#fragment", remaining_url.unwrap());
701    }
702
703    #[test]
704    fn extract_path_path_zero_length_query_undefined_fragment_defined() {
705        let remaining_url = "#fragment";
706        let boxed_result = extract_path(remaining_url);
707        let (path, remaining_url) = boxed_result.unwrap();
708
709        assert_eq!("", path);
710        assert_eq!("#fragment", remaining_url.unwrap());
711    }
712
713    #[test]
714    fn extract_path_path_zero_length_query_defined_fragment_undefined() {
715        let remaining_url = "?q=query";
716        let boxed_result = extract_path(remaining_url);
717        let (path, remaining_url) = boxed_result.unwrap();
718
719        assert_eq!("", path);
720        assert_eq!("?q=query", remaining_url.unwrap());
721    }
722
723    #[test]
724    fn extract_path_path_zero_length_query_undefined_fragment_undefined() {
725        let remaining_url = "";
726        let boxed_result = extract_path(remaining_url);
727        assert!(boxed_result.is_err());
728        assert_eq!("error: remaining url is empty", boxed_result.err().unwrap());
729    }
730
731    #[test]
732    fn extract_query_empty_remaining_url() {
733        let remaining_url = "";
734        let (boxed_query, _remaining_url) = extract_query(remaining_url);
735        assert!(boxed_query.is_none());
736    }
737
738    #[test]
739    fn extract_query_query_undefined() {
740        let remaining_url = "#qweqwe";
741        let (query, remaining_url) = extract_query(remaining_url);
742
743        assert!(query.is_none());
744        assert_eq!("#qweqwe", remaining_url.unwrap());
745    }
746
747    #[test]
748    fn extract_query_query_defined_fragment_undefined() {
749        let remaining_url = "?q=query";
750        let (query, _remaining_url) = extract_query(remaining_url);
751
752        assert_eq!("?q=query", query.unwrap());
753        assert_eq!(None, _remaining_url);
754    }
755
756    #[test]
757    fn extract_query_query_defined_fragment_defined() {
758        let remaining_url = "?q=query#fragment1";
759        let (query, remaining_url) = extract_query(remaining_url);
760
761        assert_eq!("?q=query", query.unwrap());
762        assert_eq!("#fragment1", remaining_url.unwrap());
763    }
764
765    #[test]
766    fn extract_fragment_undefined() {
767        let remaining_url = "gment1";
768        let boxed_result = extract_fragment(remaining_url);
769        assert!(boxed_result.is_err());
770        assert_eq!("error: fragment is not defined url: gment1", boxed_result.err().unwrap());
771    }
772
773    #[test]
774    fn extract_fragment_undefined_empty() {
775        let remaining_url = "";
776        let boxed_result = extract_fragment(remaining_url);
777        assert!(boxed_result.is_err());
778        assert_eq!("error: remaining url is empty", boxed_result.err().unwrap());
779    }
780
781    #[test]
782    fn extract_fragment_defined() {
783        let remaining_url = "#test";
784        let boxed_result = extract_fragment(remaining_url);
785        assert!(boxed_result.is_ok());
786        assert_eq!("#test", boxed_result.unwrap());
787    }
788
789    #[test]
790    fn parse_authority_parts() {
791        let authority = "usr:pwd@somehost:80";
792        let boxed_result = parse_authority(authority);
793
794
795        assert!(boxed_result.is_ok());
796        let (boxed_username, boxed_password, host, boxed_port) = boxed_result.unwrap();
797
798        assert!(boxed_username.is_some());
799        assert_eq!("usr", boxed_username.unwrap());
800
801        assert!(boxed_password.is_some());
802        assert_eq!("pwd", boxed_password.unwrap());
803
804        assert_eq!("somehost", host);
805
806        assert!(boxed_port.is_some());
807        assert_eq!(80, boxed_port.unwrap());
808    }
809
810    #[test]
811    fn parse_authority_parts_no_password() {
812        let authority = "usr@somehost:80";
813        let boxed_result = parse_authority(authority);
814
815
816        assert!(boxed_result.is_ok());
817        let (boxed_username, boxed_password, host, boxed_port) = boxed_result.unwrap();
818
819        assert!(boxed_username.is_some());
820        assert_eq!("usr", boxed_username.unwrap());
821
822        assert!(boxed_password.is_none());
823
824        assert_eq!("somehost", host);
825
826        assert!(boxed_port.is_some());
827        assert_eq!(80, boxed_port.unwrap());
828    }
829
830    #[test]
831    fn parse_authority_parts_no_user_no_password() {
832        let authority = "somehost:80";
833        let boxed_result = parse_authority(authority);
834
835
836        assert!(boxed_result.is_ok());
837        let (boxed_username, boxed_password, host, boxed_port) = boxed_result.unwrap();
838
839        assert!(boxed_username.is_none());
840        assert!(boxed_password.is_none());
841
842        assert_eq!("somehost", host);
843
844        assert!(boxed_port.is_some());
845        assert_eq!(80, boxed_port.unwrap());
846    }
847
848    #[test]
849    fn parse_authority_parts_no_user_no_password_no_port() {
850        let authority = "somehost";
851        let boxed_result = parse_authority(authority);
852
853
854        assert!(boxed_result.is_ok());
855        let (boxed_username, boxed_password, host, boxed_port) = boxed_result.unwrap();
856
857        assert!(boxed_username.is_none());
858        assert!(boxed_password.is_none());
859
860        assert_eq!("somehost", host);
861
862        assert!(boxed_port.is_none());
863    }
864
865    #[test]
866    fn parse_authority_parts_no_password_no_port() {
867        let authority = "usr@somehost";
868        let boxed_result = parse_authority(authority);
869
870
871        assert!(boxed_result.is_ok());
872        let (boxed_username, boxed_password, host, boxed_port) = boxed_result.unwrap();
873
874        assert!(boxed_username.is_some());
875        assert_eq!("usr", boxed_username.unwrap());
876        assert!(boxed_password.is_none());
877
878        assert_eq!("somehost", host);
879
880        assert!(boxed_port.is_none());
881    }
882
883
884    #[test]
885    fn parse_authority_parts_no_port() {
886        let authority = "usr:pwd@somehost";
887        let boxed_result = parse_authority(authority);
888
889
890        assert!(boxed_result.is_ok());
891        let (boxed_username, boxed_password, host, boxed_port) = boxed_result.unwrap();
892
893        assert!(boxed_username.is_some());
894        assert_eq!("usr", boxed_username.unwrap());
895
896
897        assert!(boxed_password.is_some());
898        assert_eq!("pwd", boxed_password.unwrap());
899
900        assert_eq!("somehost", host);
901
902        assert!(boxed_port.is_none());
903    }
904
905    #[test]
906    fn parse_extract_userinfo() {
907        let boxed_userinfo =
908            extract_userinfo(
909                "usr:pwd@[2001:0db8:85a3:0000:0000:8a2e:0370:7334]");
910        assert!(boxed_userinfo.is_ok());
911
912        let (username, password, remaining_authority) = boxed_userinfo.unwrap();
913
914        assert_eq!("usr", username.unwrap());
915        assert_eq!("pwd", password.unwrap());
916        assert_eq!("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", remaining_authority);
917    }
918
919
920    #[test]
921    fn parse_extract_userinfo_no_passwd() {
922        let boxed_userinfo =
923            extract_userinfo(
924                "usr@192.168.0.1");
925        assert!(boxed_userinfo.is_ok());
926
927        let (username, password, remaining_authority) = boxed_userinfo.unwrap();
928
929        assert_eq!("usr", username.unwrap());
930        assert_eq!(None, password);
931        assert_eq!("192.168.0.1", remaining_authority);
932    }
933
934    #[test]
935    fn parse_extract_userinfo_no_passwd_no_user() {
936        let boxed_userinfo =
937            extract_userinfo(
938                "somehost.com");
939        assert!(boxed_userinfo.is_ok());
940
941        let (username, password, remaining_authority) = boxed_userinfo.unwrap();
942
943        assert_eq!(None, username);
944        assert_eq!(None, password);
945        assert_eq!("somehost.com", remaining_authority);
946    }
947
948    #[test]
949    fn parse_extract_host_ip_v4() {
950        let (host, remaining_authority) =
951            extract_host("somehost.com:80".as_ref()).unwrap();
952
953        assert_eq!("somehost.com", host);
954        assert_eq!(":80", remaining_authority.unwrap());
955    }
956
957    #[test]
958    fn parse_extract_host_ip_v4_no_port() {
959        let (host, remaining_authority) =
960            extract_host("somehost.com".as_ref()).unwrap();
961
962        assert_eq!("somehost.com", host);
963        assert_eq!(None, remaining_authority);
964    }
965
966
967    #[test]
968    fn parse_extract_host_ip_v6() {
969        let (host, remaining_authority) =
970            extract_host("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80".as_ref()).unwrap();
971
972        assert_eq!("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", host);
973        assert_eq!(":80", remaining_authority.unwrap());
974    }
975
976    #[test]
977    fn parse_extract_host_ip_v6_no_port() {
978        let (host, remaining_authority) =
979            extract_host("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]".as_ref()).unwrap();
980
981        assert_eq!("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", host);
982        assert_eq!(None, remaining_authority);
983    }
984
985    #[test]
986    fn parse_authority_parts_ip_v6() {
987        let authority = "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]";
988        let boxed_result = parse_authority(authority);
989
990
991        assert!(boxed_result.is_ok());
992
993        let (boxed_username, boxed_password, host, boxed_port) = boxed_result.unwrap();
994
995        assert!(boxed_username.is_none());
996
997
998        assert!(boxed_password.is_none());
999
1000        assert_eq!("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", host);
1001
1002        assert!(boxed_port.is_none());
1003    }
1004
1005    #[test]
1006    fn parse_authority_parts_usr_pwd_ip_v6_port() {
1007        let authority = "usr:pwd@[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:80";
1008        let boxed_result = parse_authority(authority);
1009
1010
1011        assert!(boxed_result.is_ok());
1012        let (boxed_username, boxed_password, host, boxed_port) = boxed_result.unwrap();
1013
1014        assert!(boxed_username.is_some());
1015        assert_eq!("usr", boxed_username.unwrap());
1016
1017        assert!(boxed_password.is_some());
1018        assert_eq!("pwd", boxed_password.unwrap());
1019
1020        assert_eq!("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]", host);
1021
1022        assert!(boxed_port.is_some());
1023        assert_eq!(80, boxed_port.unwrap());
1024    }
1025
1026    #[test]
1027    fn parse_simple_url_no_authority() {
1028        let url = "mailto:user@host,user2@host";
1029
1030        let url_components = parse_url(url).unwrap();
1031
1032        assert_eq!(url_components.scheme, "mailto");
1033        assert!(url_components.authority.is_none());
1034        assert_eq!(url_components.path, "user@host,user2@host");
1035
1036    }
1037
1038    #[test]
1039    fn parse_simple_url_no_authority_with_query() {
1040        let url = "mailto:user@host?subject=test#fragment";
1041
1042        let url_components = parse_url(url).unwrap();
1043
1044        assert_eq!(url_components.scheme, "mailto");
1045        assert!(url_components.authority.is_none());
1046        assert_eq!(url_components.path, "user@host");
1047        assert_eq!(url_components.fragment.unwrap(), "fragment");
1048
1049    }
1050
1051    #[test]
1052    fn parse_simple_url_no_authority_with_fragment() {
1053        let url = "mailto:user@host#fragment";
1054
1055        let url_components = parse_url(url).unwrap();
1056
1057        assert_eq!(url_components.scheme, "mailto");
1058        assert!(url_components.authority.is_none());
1059        assert_eq!(url_components.path, "user@host");
1060        assert_eq!(url_components.fragment.unwrap(), "fragment");
1061
1062    }
1063
1064    #[test]
1065    fn parse_simple_url_no_authority_with_query_with_fragment() {
1066        let url = "mailto:user@host?q=123#fragment";
1067
1068        let url_components = parse_url(url).unwrap();
1069
1070        assert_eq!(url_components.scheme, "mailto");
1071        assert!(url_components.authority.is_none());
1072        assert_eq!(url_components.path, "user@host");
1073
1074    }
1075
1076    #[test]
1077    fn parse_simple_url_no_authority_no_path_with_query_with_fragment() {
1078        let url = "mailto:?to=&subject=mailto%20with%20examples&body=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FMailto";
1079
1080        let url_components = parse_url(url).unwrap();
1081
1082        assert_eq!(url_components.scheme, "mailto");
1083        assert!(url_components.authority.is_none());
1084        assert_eq!(url_components.path, "");
1085        assert_eq!(url_components.query.as_ref().unwrap().get("subject").unwrap(), "mailto with examples");
1086        assert_eq!(url_components.query.as_ref().unwrap().get("to").unwrap(), "");
1087        assert_eq!(url_components.query.as_ref().unwrap().get("body").unwrap(), "https://en.wikipedia.org/wiki/Mailto");
1088
1089    }
1090
1091    #[test]
1092    fn extract_port_test() {
1093        let boxed_port = extract_port(":80");
1094        assert!(boxed_port.is_ok());
1095        assert_eq!(80, boxed_port.unwrap().unwrap());
1096    }
1097
1098    #[test]
1099    fn extract_port_test_fail() {
1100        let boxed_port = extract_port(":someport");
1101        assert!(boxed_port.is_err());
1102        assert_eq!("unable to parse port from remaining authority  | invalid digit found in string | someport", boxed_port.err().unwrap());
1103    }
1104
1105
1106    #[test]
1107    fn build_authority_host_empty() {
1108        let authority = UrlAuthority{
1109            user_info: None,
1110            host: "".to_string(),
1111            port: None
1112        };
1113
1114        let url_authority = build_authority(authority);
1115
1116        assert_eq!(url_authority, "");
1117    }
1118
1119    #[test]
1120    fn build_authority_host_empty_usrname() {
1121        let authority = UrlAuthority{
1122            user_info: Option::from(UrlUserInfo { username: "usr".to_string(), password: None }),
1123            host: "".to_string(),
1124            port: None
1125        };
1126
1127        let url_authority = build_authority(authority);
1128
1129        assert_eq!(url_authority, "usr@");
1130    }
1131
1132    #[test]
1133    fn build_authority_host_usrname_passwd() {
1134        let authority = UrlAuthority{
1135            user_info: Option::from(UrlUserInfo { username: "usr".to_string(), password: Option::from("pwd".to_string()) }),
1136            host: "somehost".to_string(),
1137            port: None
1138        };
1139
1140        let url_authority = build_authority(authority);
1141
1142        assert_eq!(url_authority, "usr:pwd@somehost");
1143    }
1144
1145    #[test]
1146    fn build_url_all_specified() {
1147        let authority = UrlAuthority{
1148            user_info: Option::from(UrlUserInfo { username: "usr".to_string(), password: Option::from("pwd".to_string()) }),
1149            host: "somehost".to_string(),
1150            port: Option::from(80)
1151        };
1152
1153        let mut q = HashMap::new();
1154        q.insert("q".to_string(), "123".to_string());
1155        q.insert("w".to_string(), "456".to_string());
1156
1157
1158        let url_components = UrlComponents{
1159            scheme: "https".to_string(),
1160            authority: Option::from(authority),
1161            path: "/".to_string(),
1162            query: Option::from(q),
1163            fragment: Option::from("fragment".to_string())
1164        };
1165
1166        let url = build_url(url_components.clone()).unwrap();
1167
1168        let parsed_url_components = parse_url(url.as_str()).unwrap();
1169
1170        assert_eq!(url_components, parsed_url_components);
1171    }
1172
1173    #[test]
1174    fn build_url_only_required_specified() {
1175        let url_components = UrlComponents{
1176            scheme: "https".to_string(),
1177            authority: None,
1178            path: "/".to_string(),
1179            query: None,
1180            fragment: None
1181        };
1182
1183        let url = build_url(url_components.clone()).unwrap();
1184        let parsed_url_components = parse_url(url.as_str()).unwrap();
1185        assert_eq!(url_components, parsed_url_components);
1186    }
1187
1188    #[test]
1189    fn build_authority_host_usrname_passwd_port() {
1190        let authority = UrlAuthority{
1191            user_info: Option::from(UrlUserInfo { username: "usr".to_string(), password: Option::from("pwd".to_string()) }),
1192            host: "somehost".to_string(),
1193            port: Option::from(80)
1194        };
1195
1196        let url_authority = build_authority(authority);
1197
1198        assert_eq!(url_authority, "usr:pwd@somehost:80");
1199    }
1200
1201    #[test]
1202    fn simple_build_steam_api() {
1203        let params_map = HashMap::new();
1204
1205        let url_builder = UrlComponents{
1206            scheme: "https".to_string(),
1207            authority: Some(UrlAuthority{
1208                user_info: None,
1209                host: "api.steampowered.com".to_string(),
1210                port: None
1211            }),
1212            query: Some(params_map),
1213            fragment: None,
1214            path: "/path".to_string()
1215        };
1216
1217        let url = build_url(url_builder).unwrap();
1218
1219        assert_eq!("https://api.steampowered.com/path?", url)
1220    }
1221
1222    #[test]
1223    fn parse_simple_url_no_path_no_query_no_fragment() {
1224        let url = "https://usr:pwd@somehost:80";
1225        let url_components = parse_url(url).unwrap();
1226
1227
1228        assert_eq!(url_components.scheme, "https");
1229        assert_eq!(url_components.authority.as_ref().unwrap().user_info.as_ref().unwrap().username, "usr");
1230        assert_eq!(url_components.authority.as_ref().unwrap().user_info.as_ref().unwrap().password.as_ref().unwrap(), "pwd");
1231        assert_eq!(url_components.authority.as_ref().unwrap().host, "somehost");
1232        assert_eq!(*url_components.authority.as_ref().unwrap().port.as_ref().unwrap() as u8, 80 as u8);
1233        assert_eq!(url_components.path, "");
1234
1235    }
1236
1237    #[test]
1238    fn parse_simple_url_no_usr_no_pwd_no_path_no_query_no_fragment() {
1239        let url = "https://somehost";
1240        let url_components = parse_url(url).unwrap();
1241
1242
1243        assert_eq!(url_components.scheme, "https");
1244        assert!(url_components.authority.as_ref().unwrap().user_info.is_none());
1245        assert!(url_components.authority.as_ref().unwrap().port.is_none());
1246        assert_eq!(url_components.authority.as_ref().unwrap().host, "somehost");
1247        assert_eq!(url_components.path, "");
1248        assert_eq!(url_components.query, None);
1249        assert_eq!(url_components.fragment, None);
1250
1251    }
1252
1253    #[test]
1254    fn parse_simple_url() {
1255        let url = "https://usr:pwd@somehost:80/path?param=value&anotherParam#fragment";
1256        let url_components = parse_url(url).unwrap();
1257
1258
1259        assert_eq!(url_components.scheme, "https");
1260        assert_eq!(url_components.authority.as_ref().unwrap().user_info.as_ref().unwrap()
1261                       .username, "usr");
1262        assert_eq!(url_components.authority.as_ref().unwrap().user_info.as_ref().unwrap()
1263                       .password.as_ref().unwrap(), "pwd");
1264        assert_eq!(url_components.authority.as_ref().unwrap()
1265                       .host, "somehost");
1266        assert_eq!(*url_components.authority.as_ref().unwrap()
1267                        .port.as_ref().unwrap() as u8, 80 as u8);
1268        assert_eq!(url_components.path, "/path");
1269        assert_eq!(url_components.query.as_ref().unwrap()
1270                       .get("param").unwrap(), "value");
1271        assert!(url_components.query.as_ref().unwrap()
1272                        .contains_key("anotherParam"));
1273        assert_eq!("", url_components.query.as_ref().unwrap()
1274                        .get("anotherParam").unwrap());
1275
1276    }
1277
1278    #[test]
1279    fn parse_simple_url_ftp() {
1280        let url = "ftp://ftp.is.co.za/rfc/rfc1808.txt";
1281        let url_components = parse_url(url).unwrap();
1282
1283
1284        assert_eq!(url_components.scheme, "ftp");
1285        assert_eq!(url_components.authority.as_ref().unwrap().user_info, None);
1286        assert_eq!(url_components.authority.as_ref().unwrap()
1287                       .host, "ftp.is.co.za");
1288        assert_eq!(url_components.authority.as_ref().unwrap()
1289            .port, None);
1290        assert_eq!(url_components.path, "/rfc/rfc1808.txt");
1291        assert_eq!(url_components.query, None);
1292        assert_eq!(url_components.fragment, None);
1293    }
1294
1295    #[test]
1296    fn parse_simple_url_ldap() {
1297        let url = "ldap://[2001:db8::7]/c=GB?objectClass?one";
1298        let url_components = parse_url(url).unwrap();
1299
1300
1301        assert_eq!(url_components.scheme, "ldap");
1302        assert_eq!(url_components.authority.as_ref().unwrap().user_info, None);
1303        assert_eq!(url_components.authority.as_ref().unwrap()
1304                       .host, "[2001:db8::7]");
1305        assert_eq!(url_components.authority.as_ref().unwrap()
1306                       .port, None);
1307        assert_eq!(url_components.path, "/c=GB");
1308        assert!(url_components.query.unwrap().contains_key("objectClass?one"));
1309        assert_eq!(url_components.fragment, None);
1310    }
1311
1312    #[test]
1313    fn parse_simple_url_news() {
1314        let url = "news:comp.infosystems.www.servers.unix";
1315        let url_components = parse_url(url).unwrap();
1316
1317
1318        assert_eq!(url_components.scheme, "news");
1319        assert_eq!(url_components.authority, None);
1320        assert_eq!(url_components.path, "comp.infosystems.www.servers.unix");
1321        assert_eq!(url_components.query, None);
1322        assert_eq!(url_components.fragment, None);
1323    }
1324
1325    #[test]
1326    fn parse_simple_url_tel() {
1327        let url = "tel:+1-816-555-1212";
1328        let url_components = parse_url(url).unwrap();
1329
1330
1331        assert_eq!(url_components.scheme, "tel");
1332        assert_eq!(url_components.authority, None);
1333        assert_eq!(url_components.path, "+1-816-555-1212");
1334        assert_eq!(url_components.query, None);
1335        assert_eq!(url_components.fragment, None);
1336    }
1337
1338    #[test]
1339    fn parse_simple_url_telnet() {
1340        let url = "telnet://192.0.2.16:80/";
1341        let url_components = parse_url(url).unwrap();
1342
1343
1344        assert_eq!(url_components.scheme, "telnet");
1345        assert_eq!(url_components.authority.as_ref().unwrap().user_info, None);
1346        assert_eq!(url_components.authority.as_ref().unwrap().host, "192.0.2.16");
1347        assert_eq!(url_components.authority.as_ref().unwrap().port.unwrap(), 80);
1348        assert_eq!(url_components.path, "/");
1349        assert_eq!(url_components.query, None);
1350        assert_eq!(url_components.fragment, None);
1351    }
1352
1353    #[test]
1354    fn parse_simple_url_mailto() {
1355        let url = "mailto:John.Doe@example.com";
1356        let url_components = parse_url(url).unwrap();
1357
1358
1359        assert_eq!(url_components.scheme, "mailto");
1360        assert_eq!(url_components.authority, None);
1361        assert_eq!(url_components.path, "John.Doe@example.com");
1362        assert_eq!(url_components.query, None);
1363        assert_eq!(url_components.fragment, None);
1364    }
1365
1366    #[test]
1367    fn parse_simple_url_urn() {
1368        let url = "urn:oasis:names:specification:docbook:dtd:xml:4.1.2";
1369        let url_components = parse_url(url).unwrap();
1370
1371
1372        assert_eq!(url_components.scheme, "urn");
1373        assert_eq!(url_components.authority, None);
1374        assert_eq!(url_components.path, "oasis:names:specification:docbook:dtd:xml:4.1.2");
1375        assert_eq!(url_components.query, None);
1376        assert_eq!(url_components.fragment, None);
1377    }
1378}