Skip to main content

url_decompose/
url.rs

1use std::ops::{Range, Deref};
2use std::fmt;
3use lazy_static::lazy_static;
4use regex::Regex;
5use crate::regex_generator;
6
7#[derive(Clone)]
8pub struct Url<S: AsRef<str>> {
9    full: S,
10    scheme: Option<Range<usize>>,
11    user: Option<Range<usize>>,
12    password: Option<Range<usize>>,
13    host: Option<Range<usize>>,
14    port: Option<Range<usize>>,
15    path: Option<Range<usize>>,
16    query: Option<Range<usize>>,
17    fragment: Option<Range<usize>>,
18}
19#[derive(Debug, PartialEq, Eq)]
20pub enum Query<'a> {
21    Field(&'a str),
22    Form(&'a str, &'a str),
23}
24impl<S> Url<S> 
25    where S: AsRef<str>
26{
27    /**
28    Create a new Url instance.
29
30    Parse the url given in parameter and store it and ranges of several element present in the url into the instance.
31    If the url is well formed, it returns theUrl instance in an [`Ok`] value. An empty [`Err`] is returns otherwise (meaning parsing doesn't capture the url well)
32
33    **Note** : To parse the url, it uses a very long regular expression. As it works pretty well, it is planned to be improved.
34    */
35    pub fn new(v: S) -> Result<Url<S>, &'static str> {
36        lazy_static! {
37            static ref URL_REGEX: Regex = {
38                Regex::new(regex_generator::url_regex())
39                    .expect(r"/!\ Regex error from internal string. /!\")
40            };
41        }
42        let test = v.as_ref();
43        let captures: regex::Captures = match URL_REGEX.captures(test) {
44            Some(captures) => captures,
45            None => return Err("Url not valid"),
46        };
47        let (scheme, user, password, host, port, path, query, fragment) = {
48            let to_opt_range = |i| (&captures).get(i).map(|m| m.range());
49            (
50                to_opt_range(1),
51                to_opt_range(2),
52                to_opt_range(3),
53                to_opt_range(4),
54                to_opt_range(5),
55                to_opt_range(6),
56                to_opt_range(7),
57                to_opt_range(8),
58            )
59        };
60        
61        Ok(Url {
62            full: v,
63            scheme, user, password, host, port, path, query, fragment
64        })
65    }
66    /**
67    Returns the complete url.
68    */
69    pub fn full(&self) -> &str {
70        self.full.as_ref()
71    }
72    fn range_to_str(&self, range: &Option<Range<usize>>) -> Option<&str> {
73        range.clone().map(|s| &self.full.as_ref()[s])
74    }
75    /**
76    Returns the scheme of the url if present, [`None`] otherwise.
77
78    ## Example
79    ```
80    use url_compose::Url;
81
82    let url = Url::new("https://username:password@www.example.com:8080/path/to/file?field=value#fragment").unwrap();
83    assert_eq!(url.scheme(), Some("https"));
84    let url = Url::new("/path/example").unwrap();
85    assert_eq!(url.scheme(), None);
86    ```
87    */
88    pub fn scheme(&self) -> Option<&str>
89    {
90        self.range_to_str(&self.scheme)
91    }
92    /**
93    Returns the username of the url if present, [`None`] otherwise.
94
95    In a URL (to connect ftp for example), you can pass userinfo as `username:password`.
96
97    ## Example
98    ```
99    use url_compose::Url;
100
101    let url = Url::new("https://username:password@www.example.com:8080/path/to/file?field=value#fragment").unwrap();
102    assert_eq!(url.user(), Some("username"));
103    let url = Url::new("https://www.example.com").unwrap();
104    assert_eq!(url.user(), None);
105    ```
106    */
107    pub fn user(&self) -> Option<&str>
108    {
109        self.range_to_str(&self.user)
110    }
111    /**
112    Returns the password of the url if present, [`None`] otherwise.
113
114    In a URL (to connect ftp for example), you can pass userinfo as `username:password`.
115    Note that the password in the url is deprecated as the data is displayed in clear. 
116
117    ## Example
118    ```
119    use url_compose::Url;
120
121    let url = Url::new("https://username:password@www.example.com:8080/path/to/file?field=value#fragment").unwrap();
122    assert_eq!(url.password(), Some("password"));
123    let url = Url::new("https://www.example.com").unwrap();
124    assert_eq!(url.password(), None);
125    ```
126    */
127    pub fn password(&self) -> Option<&str>
128    {
129        self.range_to_str(&self.password)
130    }
131    /**
132    Returns the host of the url if present, [`None`] otherwise.
133
134    ## Example
135    ```
136    use url_compose::Url;
137
138    let url = Url::new("https://username:password@www.example.com:8080/path/to/file?field=value#fragment").unwrap();
139    assert_eq!(url.host(), Some("www.example.com"));
140    let url = Url::new("http://localhost:8080").unwrap();
141    assert_eq!(url.host(), Some("localhost"));
142    let url = Url::new("/path/example").unwrap();
143    assert_eq!(url.host(), None);
144    ```
145    */
146    pub fn host(&self) -> Option<&str>
147    {
148        self.range_to_str(&self.host)
149    }
150    /**
151    Returns the port of the url if present, [`None`] otherwise.
152    
153    ## Example
154    ```
155    use url_compose::Url;
156
157    let url = Url::new("https://username:password@www.example.com:8080/path/to/file?field=value#fragment").unwrap();
158    assert_eq!(url.port(), Some("8080"));
159    let url = Url::new("/path/example").unwrap();
160    assert_eq!(url.port(), None);
161    ```
162    */
163    pub fn port(&self) -> Option<&str>
164    {
165        self.range_to_str(&self.port)
166    }
167    /**
168    Returns the path of the url.
169
170    Even if the path returns an [`Option`], it will never return [`None`], but can return [`Some("")`]
171    
172    [`Some("")`]: https://doc.rust-lang.org/nightly/core/option/enum.Option.html#variant.Some
173    
174    ## Example
175    ```
176    use url_compose::Url;
177
178    let url = Url::new("https://username:password@www.example.com:8080/path/to/file?field=value#fragment").unwrap();
179    assert_eq!(url.path(), Some("/path/to/file"));
180    let url = Url::new("https://www.example.com").unwrap();
181    assert_eq!(url.path(), Some(""));
182    ```
183    */
184    pub fn path(&self) -> Option<&str>
185    {
186        self.range_to_str(&self.path)
187    }
188    /**
189    Returns the query of the url if present, [`None`] otherwise.
190
191    It returns the full query in a string slice, including the question mark. 
192    To format into a [`Vec`], see [`Self::dispatch_query()`].
193    
194    ## Example
195    ```
196    use url_compose::Url;
197
198    let url = Url::new("https://username:password@www.example.com:8080/path/to/file?field=value#fragment").unwrap();
199    assert_eq!(url.query(), Some("?field=value"));
200    let url = Url::new("/path/example").unwrap();
201    assert_eq!(url.query(), None);
202    ```
203    */
204    pub fn query(&self) -> Option<&str>
205    {
206        self.range_to_str(&self.query)
207    }
208    /**
209    Returns the fragment of the url if present, [`None`] otherwise.
210    
211    ## Example
212    ```
213    use url_compose::Url;
214
215    let url = Url::new("https://username:password@www.example.com:8080/path/to/file?field=value#fragment").unwrap();
216    assert_eq!(url.frament(), Some("#fragment"));
217    let url = Url::new("/path/example").unwrap();
218    assert_eq!(url.frament(), None);
219    ```
220    */
221    pub fn fragment(&self) -> Option<&str>
222    {
223        self.range_to_str(&self.fragment)
224    }
225    /**
226    Split the query into a [`Vec`] of [`Query`].
227    
228    ## Example
229    ```
230    use url_compose::Url;
231    use url_compose::Query::{Form, Field};
232
233    let url = Url::new("/?field1=value1&field2=value2&present").unwrap();
234    assert_eq!(url.dispatch_query(), [Form("field1", "value1"), Form("field2", "value2"), Field("present")]);
235    let url = Url::new("/?field1=value1;field2=value2").unwrap();
236    assert_eq!(url.dispatch_query(), [Form("field1", "value1"), Form("field2", "value2")]);
237    let url = Url::new("/").unwrap();
238    assert_eq!(url.dispatch_query(), []);
239    ```
240    */
241    pub fn dispatch_query(&self) -> Vec<Query> {
242        let queries = match self.query() {
243            Some(v) => v,
244            None => return Vec::new()
245        };
246        let queries = &queries[1..];
247        let mut result = vec![];
248        for query in queries.split(|c| c=='&' || c==';') {
249            result.push(match query.find('=') {
250                Some(pos) => Query::Form(&query[0..pos], &query[pos+1..]),
251                None => Query::Field(query)
252            });
253        }
254        result
255    }
256    /**
257    Split the host domains into a [`Vec`] of `&str`.
258
259    If the host isn't present in the url, the function returns a empty value.
260    
261    ## Example
262    ```
263    use url_compose::Url;
264
265    let url = Url::new("https://www.example.com/").unwrap();
266    assert_eq!(url.dispatch_domains(), ["www","example","com"]);
267    let url = Url::new("/").unwrap();
268    assert_eq!(url.dispatch_domains(), [] as [&str;0]);
269    ```
270    */
271    pub fn dispatch_domains(&self) -> Vec<&str> {
272        match self.host() {
273            Some(domains) => domains.split('.').collect(),
274            None => Vec::new(),
275        }
276    }
277}
278
279impl<S> From<Url<S>> for String 
280    where S: AsRef<str>
281{
282    fn from(v: Url<S>) -> String {
283        String::from(v.full.as_ref())
284    }
285}
286
287impl<S> fmt::Debug for Url<S>
288    where S: AsRef<str>
289{
290    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291        f.debug_struct("Url")
292         .field("full", &self.full.as_ref())
293         .field("scheme", &self.scheme())
294         .field("user", &self.user())
295         .field("host", &self.host())
296         .field("port", &self.port())
297         .field("path", &self.path())
298         .field("query", &self.query())
299         .field("fragment", &self.fragment())
300         .finish()
301    }
302}
303impl<S> fmt::Display for Url<S>
304    where S: AsRef<str>
305{
306    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
307        write!(f, "{}", self.full.as_ref())
308    }
309}
310impl<S> AsRef<str> for Url<S>
311    where S: AsRef<str> 
312{
313    fn as_ref(&self) -> &str {
314        self.full()
315    }
316}
317impl<S> AsRef<[u8]> for Url<S>
318    where S: AsRef<str> 
319{
320    fn as_ref(&self) -> &[u8] {
321        self.full().as_ref()
322    }
323}
324impl<S> Deref for Url<S>
325    where S: AsRef<str> 
326{
327    type Target = str;
328    fn deref(&self) -> &str {
329        self.full()
330    }
331}