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}