1use crate::{imp::RiMaybeRef, Iri, IriRef, Uri, UriRef};
2use borrow_or_share::Bos;
3use core::str;
4
5#[cfg(feature = "alloc")]
6use crate::{
7 imp::{HostMeta, Meta, RmrRef},
8 pct_enc,
9};
10#[cfg(feature = "alloc")]
11use alloc::string::String;
12#[cfg(feature = "alloc")]
13use core::num::NonZeroUsize;
14
15macro_rules! impl_from {
16 ($($x:ident => $($y:ident),+)*) => {
17 $($(
18 impl<T: Bos<str>> From<$x<T>> for $y<T> {
19 #[doc = concat!("Consumes the `", stringify!($x), "` and creates a new [`", stringify!($y), "`] with the same contents.")]
20 fn from(value: $x<T>) -> Self {
21 RiMaybeRef::new(value.val, value.meta)
22 }
23 }
24 )+)*
25 };
26}
27
28impl_from! {
29 Uri => UriRef, Iri, IriRef
30 UriRef => IriRef
31 Iri => IriRef
32}
33
34#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36pub enum ConvertError {
37 NotAscii {
39 index: usize,
41 },
42 NoScheme,
44}
45
46#[cfg(feature = "impl-error")]
47impl crate::Error for ConvertError {}
48
49macro_rules! impl_try_from {
50 ($(#[$doc:meta] $x:ident if $($cond:ident)&&+ => $y:ident)*) => {
51 $(
52 impl<'a> TryFrom<$x<&'a str>> for $y<&'a str> {
53 type Error = ConvertError;
54
55 #[$doc]
56 fn try_from(value: $x<&'a str>) -> Result<Self, Self::Error> {
57 let r = value.make_ref();
58 $(r.$cond()?;)+
59 Ok(RiMaybeRef::new(value.val, value.meta))
60 }
61 }
62
63 #[cfg(feature = "alloc")]
64 impl TryFrom<$x<String>> for $y<String> {
65 type Error = (ConvertError, $x<String>);
66
67 #[$doc]
68 fn try_from(value: $x<String>) -> Result<Self, Self::Error> {
69 let r = value.make_ref();
70 $(
71 if let Err(e) = r.$cond() {
72 return Err((e, value));
73 }
74 )+
75 Ok(RiMaybeRef::new(value.val, value.meta))
76 }
77 }
78 )*
79 };
80}
81
82impl_try_from! {
83 UriRef if ensure_has_scheme => Uri
85 Iri if ensure_ascii => Uri
87 IriRef if ensure_has_scheme && ensure_ascii => Uri
89 IriRef if ensure_ascii => UriRef
91 IriRef if ensure_has_scheme => Iri
93}
94
95#[cfg(feature = "alloc")]
96impl<T: Bos<str>> Iri<T> {
97 pub fn to_uri(&self) -> Uri<String> {
113 RiMaybeRef::from_pair(encode_non_ascii(self.make_ref()))
114 }
115}
116
117#[cfg(feature = "alloc")]
118impl<T: Bos<str>> IriRef<T> {
119 pub fn to_uri_ref(&self) -> UriRef<String> {
135 RiMaybeRef::from_pair(encode_non_ascii(self.make_ref()))
136 }
137}
138
139#[cfg(feature = "alloc")]
140fn encode_non_ascii(r: RmrRef<'_, '_>) -> (String, Meta) {
141 let len = r
142 .as_str()
143 .chars()
144 .map(|c| if c.is_ascii() { 1 } else { c.len_utf8() * 3 })
145 .sum();
146
147 let mut buf = String::with_capacity(len);
148 let mut meta = Meta::default();
149
150 if let Some(scheme) = r.scheme_opt() {
151 buf.push_str(scheme.as_str());
152 meta.scheme_end = NonZeroUsize::new(buf.len());
153 buf.push(':');
154 }
155
156 if let Some(auth) = r.authority() {
157 buf.push_str("//");
158
159 if let Some(userinfo) = auth.userinfo() {
160 encode_non_ascii_str(&mut buf, userinfo.as_str());
161 buf.push('@');
162 }
163
164 let mut auth_meta = auth.meta();
165 auth_meta.host_bounds.0 = buf.len();
166 match auth_meta.host_meta {
167 HostMeta::RegName => encode_non_ascii_str(&mut buf, auth.host()),
168 _ => buf.push_str(auth.host()),
169 }
170 auth_meta.host_bounds.1 = buf.len();
171 meta.auth_meta = Some(auth_meta);
172
173 if let Some(port) = auth.port() {
174 buf.push(':');
175 buf.push_str(port.as_str());
176 }
177 }
178
179 meta.path_bounds.0 = buf.len();
180 encode_non_ascii_str(&mut buf, r.path().as_str());
181 meta.path_bounds.1 = buf.len();
182
183 if let Some(query) = r.query() {
184 buf.push('?');
185 encode_non_ascii_str(&mut buf, query.as_str());
186 meta.query_end = NonZeroUsize::new(buf.len());
187 }
188
189 if let Some(fragment) = r.fragment() {
190 buf.push('#');
191 encode_non_ascii_str(&mut buf, fragment.as_str());
192 }
193
194 debug_assert_eq!(buf.len(), len);
195
196 (buf, meta)
197}
198
199#[cfg(feature = "alloc")]
200fn encode_non_ascii_str(buf: &mut String, s: &str) {
201 if s.is_ascii() {
202 buf.push_str(s);
203 } else {
204 let mut iter = s.char_indices();
205 while let Some((start, ch)) = iter.next() {
206 if ch.is_ascii() {
207 buf.push(ch);
208 } else {
209 let end = iter.as_str().as_ptr() as usize - s.as_ptr() as usize;
212 for &x in &s.as_bytes()[start..end] {
213 buf.push_str(pct_enc::encode_byte(x));
214 }
215 }
216 }
217 }
218}