trillium_client/
into_url.rs1use crate::{Error, Method, Result};
2use std::{
3 borrow::Cow,
4 net::{IpAddr, SocketAddr},
5 str::FromStr,
6};
7use trillium_server_common::url::{ParseError, Url};
8
9pub trait IntoUrl {
11 fn into_url(self, base: Option<&Url>) -> Result<Url>;
13
14 fn request_target(&self, _method: Method) -> Option<Cow<'static, str>> {
17 None
18 }
19}
20
21impl IntoUrl for Url {
22 fn into_url(self, base: Option<&Url>) -> Result<Url> {
23 if self.cannot_be_a_base() {
24 return Err(Error::UnexpectedUriFormat);
25 }
26
27 if base.is_some_and(|base| !self.as_str().starts_with(base.as_str())) {
28 Err(Error::UnexpectedUriFormat)
29 } else {
30 Ok(self)
31 }
32 }
33}
34
35impl IntoUrl for &str {
36 fn into_url(self, base: Option<&Url>) -> Result<Url> {
37 match (Url::from_str(self), base) {
38 (Ok(url), base) => url.into_url(base),
39 (Err(ParseError::RelativeUrlWithoutBase), Some(base)) => base
40 .join(self.trim_start_matches('/'))
41 .map_err(|_| Error::UnexpectedUriFormat),
42 _ => Err(Error::UnexpectedUriFormat),
43 }
44 }
45
46 fn request_target(&self, method: Method) -> Option<Cow<'static, str>> {
47 match method {
48 Method::Connect if !self.contains('/') => Url::from_str(&format!("http://{self}"))
49 .ok()
50 .map(|_| Cow::Owned(self.to_string())),
51
52 Method::Options if *self == "*" => Some(Cow::Borrowed("*")),
53 _ => None,
54 }
55 }
56}
57
58impl IntoUrl for String {
59 #[inline(always)]
60 fn into_url(self, base: Option<&Url>) -> Result<Url> {
61 self.as_str().into_url(base)
62 }
63
64 fn request_target(&self, method: Method) -> Option<Cow<'static, str>> {
65 self.as_str().request_target(method)
66 }
67}
68
69impl<S: AsRef<str>> IntoUrl for &[S] {
70 fn into_url(self, base: Option<&Url>) -> Result<Url> {
71 let Some(mut url) = base.cloned() else {
72 return Err(Error::UnexpectedUriFormat);
73 };
74 url.path_segments_mut()
75 .map_err(|_| Error::UnexpectedUriFormat)?
76 .pop_if_empty()
77 .extend(self);
78 Ok(url)
79 }
80}
81
82impl<S: AsRef<str>, const N: usize> IntoUrl for [S; N] {
83 fn into_url(self, base: Option<&Url>) -> Result<Url> {
84 self.as_slice().into_url(base)
85 }
86}
87
88impl<S: AsRef<str>> IntoUrl for Vec<S> {
89 fn into_url(self, base: Option<&Url>) -> Result<Url> {
90 self.as_slice().into_url(base)
91 }
92}
93
94impl IntoUrl for SocketAddr {
95 fn into_url(self, base: Option<&Url>) -> Result<Url> {
96 let scheme = if self.port() == 443 { "https" } else { "http" };
97 format!("{scheme}://{self}").into_url(base)
98 }
99
100 fn request_target(&self, method: Method) -> Option<Cow<'static, str>> {
101 (method == Method::Connect).then(|| self.to_string().into())
102 }
103}
104
105impl IntoUrl for IpAddr {
106 fn into_url(self, base: Option<&Url>) -> Result<Url> {
108 match self {
109 IpAddr::V4(v4) => format!("http://{v4}"),
110 IpAddr::V6(v6) => format!("http://[{v6}]"),
111 }
112 .into_url(base)
113 }
114}