1use url::Url;
2
3use crate::core::error::{Error, Kind, Result};
4
5mod sealed {
6 pub trait Sealed {}
7}
8
9pub trait IntoUrl: sealed::Sealed {
10 fn into_url(self) -> Result<Url>;
11
12 #[doc(hidden)]
13 fn into_url_with_base(self, base: Option<&Url>) -> Result<Url>
14 where
15 Self: Sized,
16 {
17 let _ = base;
18 self.into_url()
19 }
20}
21
22fn parse_with_base(raw: &str, base: Option<&Url>) -> Result<Url> {
23 match (Url::parse(raw), base) {
24 (Err(url::ParseError::RelativeUrlWithoutBase), Some(base))
25 if !raw.chars().any(|c| c.is_ascii_control()) =>
26 {
27 base.join(raw).map_err(Error::from_url_parse)
28 }
29 _ => raw.into_url(),
30 }
31}
32
33impl sealed::Sealed for &str {}
34impl IntoUrl for &str {
35 fn into_url(self) -> Result<Url> {
36 if self.chars().any(|c| c.is_ascii_control()) {
37 return Err(Error::new(Kind::Url).with_message("URL contains a control character"));
38 }
39 Url::parse(self).map_err(Error::from_url_parse)
40 }
41
42 fn into_url_with_base(self, base: Option<&Url>) -> Result<Url> {
43 parse_with_base(self, base)
44 }
45}
46
47impl sealed::Sealed for String {}
48impl IntoUrl for String {
49 fn into_url(self) -> Result<Url> {
50 self.as_str().into_url()
51 }
52
53 fn into_url_with_base(self, base: Option<&Url>) -> Result<Url> {
54 parse_with_base(&self, base)
55 }
56}
57
58impl sealed::Sealed for &String {}
59impl IntoUrl for &String {
60 fn into_url(self) -> Result<Url> {
61 self.as_str().into_url()
62 }
63
64 fn into_url_with_base(self, base: Option<&Url>) -> Result<Url> {
65 parse_with_base(self, base)
66 }
67}
68
69impl sealed::Sealed for Url {}
70impl IntoUrl for Url {
71 fn into_url(self) -> Result<Url> {
72 Ok(self)
73 }
74}
75
76impl sealed::Sealed for &Url {}
77impl IntoUrl for &Url {
78 fn into_url(self) -> Result<Url> {
79 Ok(self.clone())
80 }
81}