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 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 pub fn port_or_known_default(&self) -> Option<u32> {
47 self.port
48 }
49
50 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 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 pub fn path_segments(&self) -> Option<Vec<String>> {
102 self.path.clone()
103 }
104
105 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 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
197impl 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
212impl 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}