iri_string/types/iri.rs
1//! IRI-specific implementations.
2
3#[cfg(feature = "alloc")]
4use alloc::collections::TryReserveError;
5#[cfg(all(feature = "alloc", not(feature = "std")))]
6use alloc::string::String;
7
8#[cfg(feature = "alloc")]
9use crate::convert::try_percent_encode_iri_inline;
10use crate::convert::MappedToUri;
11use crate::spec::IriSpec;
12use crate::types::{
13 RiAbsoluteStr, RiFragmentStr, RiQueryStr, RiReferenceStr, RiRelativeStr, RiStr,
14};
15#[cfg(feature = "alloc")]
16use crate::types::{
17 RiAbsoluteString, RiFragmentString, RiQueryString, RiReferenceString, RiRelativeString,
18 RiString,
19};
20use crate::types::{
21 UriAbsoluteStr, UriFragmentStr, UriQueryStr, UriReferenceStr, UriRelativeStr, UriStr,
22};
23#[cfg(feature = "alloc")]
24use crate::types::{
25 UriAbsoluteString, UriFragmentString, UriQueryString, UriReferenceString, UriRelativeString,
26 UriString,
27};
28
29/// A type alias for [`RiAbsoluteStr`]`<`[`IriSpec`]`>`.
30pub type IriAbsoluteStr = RiAbsoluteStr<IriSpec>;
31
32/// A type alias for [`RiAbsoluteString`]`<`[`IriSpec`]`>`.
33#[cfg(feature = "alloc")]
34pub type IriAbsoluteString = RiAbsoluteString<IriSpec>;
35
36/// A type alias for [`RiFragmentStr`]`<`[`IriSpec`]`>`.
37pub type IriFragmentStr = RiFragmentStr<IriSpec>;
38
39/// A type alias for [`RiFragmentString`]`<`[`IriSpec`]`>`.
40#[cfg(feature = "alloc")]
41pub type IriFragmentString = RiFragmentString<IriSpec>;
42
43/// A type alias for [`RiStr`]`<`[`IriSpec`]`>`.
44pub type IriStr = RiStr<IriSpec>;
45
46/// A type alias for [`RiString`]`<`[`IriSpec`]`>`.
47#[cfg(feature = "alloc")]
48pub type IriString = RiString<IriSpec>;
49
50/// A type alias for [`RiReferenceStr`]`<`[`IriSpec`]`>`.
51pub type IriReferenceStr = RiReferenceStr<IriSpec>;
52
53/// A type alias for [`RiReferenceString`]`<`[`IriSpec`]`>`.
54#[cfg(feature = "alloc")]
55pub type IriReferenceString = RiReferenceString<IriSpec>;
56
57/// A type alias for [`RiRelativeStr`]`<`[`IriSpec`]`>`.
58pub type IriRelativeStr = RiRelativeStr<IriSpec>;
59
60/// A type alias for [`RiRelativeString`]`<`[`IriSpec`]`>`.
61#[cfg(feature = "alloc")]
62pub type IriRelativeString = RiRelativeString<IriSpec>;
63
64/// A type alias for [`RiQueryStr`]`<`[`IriSpec`]`>`.
65pub type IriQueryStr = RiQueryStr<IriSpec>;
66
67/// A type alias for [`RiQueryString`]`<`[`IriSpec`]`>`.
68#[cfg(feature = "alloc")]
69pub type IriQueryString = RiQueryString<IriSpec>;
70
71/// Implements the conversion from an IRI into a URI.
72macro_rules! impl_conversion_between_uri {
73 (
74 $ty_owned_iri:ident,
75 $ty_owned_uri:ident,
76 $ty_borrowed_iri:ident,
77 $ty_borrowed_uri:ident,
78 $example_iri:expr,
79 $example_uri:expr
80 ) => {
81 /// Conversion from an IRI into a URI.
82 impl $ty_borrowed_iri {
83 /// Percent-encodes the IRI into a valid URI that identifies the equivalent resource.
84 ///
85 /// If you need more precise control over memory allocation and buffer
86 /// handling, use [`MappedToUri`][`crate::convert::MappedToUri`] type.
87 ///
88 /// # Examples
89 ///
90 /// ```
91 /// # use iri_string::validate::Error;
92 /// # #[cfg(feature = "alloc")] {
93 #[doc = concat!("use iri_string::format::ToDedicatedString;")]
94 #[doc = concat!("use iri_string::types::{", stringify!($ty_borrowed_iri), ", ", stringify!($ty_owned_uri), "};")]
95 ///
96 #[doc = concat!("let iri = ", stringify!($ty_borrowed_iri), "::new(", stringify!($example_iri), ")?;")]
97 /// // Type annotation here is not necessary.
98 #[doc = concat!("let uri: ", stringify!($ty_owned_uri), " = iri.encode_to_uri().to_dedicated_string();")]
99 #[doc = concat!("assert_eq!(uri, ", stringify!($example_uri), ");")]
100 /// # }
101 /// # Ok::<_, Error>(())
102 /// ```
103 #[inline]
104 #[must_use]
105 pub fn encode_to_uri(&self) -> MappedToUri<'_, Self> {
106 MappedToUri::from(self)
107 }
108
109 /// Converts an IRI into a URI without modification, if possible.
110 ///
111 /// This is semantically equivalent to
112 #[doc = concat!("`", stringify!($ty_borrowed_uri), "::new(self.as_str()).ok()`.")]
113 ///
114 /// # Examples
115 ///
116 /// ```
117 /// # use iri_string::validate::Error;
118 #[doc = concat!("use iri_string::types::{", stringify!($ty_borrowed_iri), ", ", stringify!($ty_borrowed_uri), "};")]
119 ///
120 #[doc = concat!("let ascii_iri = ", stringify!($ty_borrowed_iri), "::new(", stringify!($example_uri), ")?;")]
121 /// assert_eq!(
122 /// ascii_iri.as_uri().map(AsRef::as_ref),
123 #[doc = concat!(" Some(", stringify!($example_uri), ")")]
124 /// );
125 ///
126 #[doc = concat!("let nonascii_iri = ", stringify!($ty_borrowed_iri), "::new(", stringify!($example_iri), ")?;")]
127 /// assert_eq!(nonascii_iri.as_uri(), None);
128 /// # Ok::<_, Error>(())
129 /// ```
130 #[must_use]
131 pub fn as_uri(&self) -> Option<&$ty_borrowed_uri> {
132 if !self.as_str().is_ascii() {
133 return None;
134 }
135 debug_assert!(
136 <$ty_borrowed_uri>::new(self.as_str()).is_ok(),
137 "[consistency] the ASCII-only IRI must also be a valid URI"
138 );
139 // SAFETY: An ASCII-only IRI is a URI.
140 // URI (by `UriSpec`) is a subset of IRI (by `IriSpec`),
141 // and the difference is that URIs can only have ASCII characters.
142 let uri = unsafe { <$ty_borrowed_uri>::new_maybe_unchecked(self.as_str()) };
143 Some(uri)
144 }
145 }
146
147 /// Conversion from an IRI into a URI.
148 #[cfg(feature = "alloc")]
149 impl $ty_owned_iri {
150 /// Percent-encodes the IRI into a valid URI that identifies the equivalent resource.
151 ///
152 /// After the encode, the IRI is also a valid URI.
153 ///
154 /// If you want a new URI string rather than modifying the IRI
155 /// string, or if you need more precise control over memory
156 /// allocation and buffer handling, use
157 #[doc = concat!("[`encode_to_uri`][`", stringify!($ty_borrowed_iri), "::encode_to_uri`]")]
158 /// method.
159 ///
160 /// # Panics
161 ///
162 /// Panics if the memory allocation failed.
163 ///
164 /// # Examples
165 ///
166 /// ```
167 /// # use iri_string::validate::Error;
168 /// #[cfg(feature = "alloc")] {
169 #[doc = concat!("use iri_string::types::", stringify!($ty_owned_iri), ";")]
170 ///
171 #[doc = concat!("let mut iri = ", stringify!($ty_owned_iri), "::try_from(", stringify!($example_iri), ")?;")]
172 /// iri.encode_to_uri_inline();
173 #[doc = concat!("assert_eq!(iri, ", stringify!($example_uri), ");")]
174 /// # }
175 /// # Ok::<_, Error>(())
176 /// ```
177 #[inline]
178 pub fn encode_to_uri_inline(&mut self) {
179 self.try_encode_to_uri_inline()
180 .expect("failed to allocate memory");
181 }
182
183 /// Percent-encodes the IRI into a valid URI that identifies the equivalent resource.
184 ///
185 /// After the encode, the IRI is also a valid URI.
186 ///
187 /// If you want a new URI string rather than modifying the IRI
188 /// string, or if you need more precise control over memory
189 /// allocation and buffer handling, use
190 #[doc = concat!("[`encode_to_uri`][`", stringify!($ty_borrowed_iri), "::encode_to_uri`]")]
191 /// method.
192 ///
193 // TODO: This seems true as of this writing, but is this guaranteed? See
194 // <https://users.rust-lang.org/t/does-try-reserve-guarantees-that-the-content-is-preserved-on-allocation-failure/77446>.
195 // /// If the memory allocation failed, the content is preserved without modification.
196 // ///
197 /// # Examples
198 ///
199 /// ```
200 /// # use iri_string::validate::Error;
201 /// #[cfg(feature = "alloc")] {
202 #[doc = concat!("use iri_string::types::", stringify!($ty_owned_iri), ";")]
203 ///
204 #[doc = concat!("let mut iri = ", stringify!($ty_owned_iri), "::try_from(", stringify!($example_iri), ")?;")]
205 /// iri.try_encode_to_uri_inline()
206 /// .expect("failed to allocate memory");
207 #[doc = concat!("assert_eq!(iri, ", stringify!($example_uri), ");")]
208 /// # }
209 /// # Ok::<_, Error>(())
210 /// ```
211 #[inline]
212 pub fn try_encode_to_uri_inline(&mut self) -> Result<(), TryReserveError> {
213 // SAFETY: IRI is valid after it is encoded to URI (by percent encoding).
214 unsafe {
215 let buf = self.as_inner_mut();
216 try_percent_encode_iri_inline(buf)?;
217 }
218 debug_assert!(
219 <$ty_borrowed_iri>::new(self.as_str()).is_ok(),
220 "[consistency] the content must be valid at any time"
221 );
222 Ok(())
223 }
224
225 /// Percent-encodes the IRI into a valid URI that identifies the equivalent resource.
226 ///
227 /// If you want a new URI string rather than modifying the IRI
228 /// string, or if you need more precise control over memory
229 /// allocation and buffer handling, use
230 #[doc = concat!("[`encode_to_uri`][`", stringify!($ty_borrowed_iri), "::encode_to_uri`]")]
231 /// method.
232 ///
233 /// # Examples
234 ///
235 /// ```
236 /// # use iri_string::validate::Error;
237 /// #[cfg(feature = "alloc")] {
238 #[doc = concat!("use iri_string::types::{", stringify!($ty_owned_iri), ", ", stringify!($ty_owned_uri), "};")]
239 ///
240 #[doc = concat!("let iri = ", stringify!($ty_owned_iri), "::try_from(", stringify!($example_iri), ")?;")]
241 /// // Type annotation here is not necessary.
242 #[doc = concat!("let uri: ", stringify!($ty_owned_uri), " = iri.encode_into_uri();")]
243 #[doc = concat!("assert_eq!(uri, ", stringify!($example_uri), ");")]
244 /// # }
245 /// # Ok::<_, Error>(())
246 /// ```
247 #[inline]
248 #[must_use]
249 pub fn encode_into_uri(self) -> $ty_owned_uri {
250 self.try_encode_into_uri()
251 .expect("failed to allocate memory")
252 }
253
254 /// Percent-encodes the IRI into a valid URI that identifies the equivalent resource.
255 ///
256 /// If you want a new URI string rather than modifying the IRI
257 /// string, or if you need more precise control over memory
258 /// allocation and buffer handling, use
259 #[doc = concat!("[`encode_to_uri`][`", stringify!($ty_borrowed_iri), "::encode_to_uri`]")]
260 /// method.
261 ///
262 // TODO: This seems true as of this writing, but is this guaranteed? See
263 // <https://users.rust-lang.org/t/does-try-reserve-guarantees-that-the-content-is-preserved-on-allocation-failure/77446>.
264 // /// If the memory allocation failed, the content is preserved without modification.
265 // ///
266 /// # Examples
267 ///
268 /// ```
269 /// # use iri_string::validate::Error;
270 /// #[cfg(feature = "alloc")] {
271 #[doc = concat!("use iri_string::types::{", stringify!($ty_owned_iri), ", ", stringify!($ty_owned_uri), "};")]
272 ///
273 #[doc = concat!("let iri = ", stringify!($ty_owned_iri), "::try_from(", stringify!($example_iri), ")?;")]
274 /// // Type annotation here is not necessary.
275 #[doc = concat!("let uri: ", stringify!($ty_owned_uri), " = iri.try_encode_into_uri()")]
276 /// .expect("failed to allocate memory");
277 #[doc = concat!("assert_eq!(uri, ", stringify!($example_uri), ");")]
278 /// # }
279 /// # Ok::<_, Error>(())
280 /// ```
281 pub fn try_encode_into_uri(mut self) -> Result<$ty_owned_uri, TryReserveError> {
282 self.try_encode_to_uri_inline()?;
283 let s: String = self.into();
284 debug_assert!(
285 <$ty_borrowed_uri>::new(s.as_str()).is_ok(),
286 "[consistency] the encoded IRI must also be a valid URI"
287 );
288 // SAFETY: An ASCII-only IRI is a URI.
289 // URI (by `UriSpec`) is a subset of IRI (by `IriSpec`),
290 // and the difference is that URIs can only have ASCII characters.
291 let uri = unsafe { <$ty_owned_uri>::new_maybe_unchecked(s) };
292 Ok(uri)
293 }
294
295 /// Converts an IRI into a URI without modification, if possible.
296 ///
297 /// # Examples
298 ///
299 /// ```
300 /// # use iri_string::validate::Error;
301 #[doc = concat!("use iri_string::types::{", stringify!($ty_owned_iri), ", ", stringify!($ty_owned_uri), "};")]
302 ///
303 #[doc = concat!("let ascii_iri = ", stringify!($ty_owned_iri), "::try_from(", stringify!($example_uri), ")?;")]
304 /// assert_eq!(
305 /// ascii_iri.try_into_uri().map(|uri| uri.to_string()),
306 #[doc = concat!(" Ok(", stringify!($example_uri), ".to_string())")]
307 /// );
308 ///
309 #[doc = concat!("let nonascii_iri = ", stringify!($ty_owned_iri), "::try_from(", stringify!($example_iri), ")?;")]
310 /// assert_eq!(
311 /// nonascii_iri.try_into_uri().map_err(|iri| iri.to_string()),
312 #[doc = concat!(" Err(", stringify!($example_iri), ".to_string())")]
313 /// );
314 /// # Ok::<_, Error>(())
315 /// ```
316 pub fn try_into_uri(self) -> Result<$ty_owned_uri, $ty_owned_iri> {
317 if !self.as_str().is_ascii() {
318 return Err(self);
319 }
320 let s: String = self.into();
321 debug_assert!(
322 <$ty_borrowed_uri>::new(s.as_str()).is_ok(),
323 "[consistency] the ASCII-only IRI must also be a valid URI"
324 );
325 // SAFETY: An ASCII-only IRI is a URI.
326 // URI (by `UriSpec`) is a subset of IRI (by `IriSpec`),
327 // and the difference is that URIs can only have ASCII characters.
328 let uri = unsafe { <$ty_owned_uri>::new_maybe_unchecked(s) };
329 Ok(uri)
330 }
331 }
332 };
333}
334
335impl_conversion_between_uri!(
336 IriAbsoluteString,
337 UriAbsoluteString,
338 IriAbsoluteStr,
339 UriAbsoluteStr,
340 "http://example.com/?alpha=\u{03B1}",
341 "http://example.com/?alpha=%CE%B1"
342);
343impl_conversion_between_uri!(
344 IriReferenceString,
345 UriReferenceString,
346 IriReferenceStr,
347 UriReferenceStr,
348 "http://example.com/?alpha=\u{03B1}",
349 "http://example.com/?alpha=%CE%B1"
350);
351impl_conversion_between_uri!(
352 IriRelativeString,
353 UriRelativeString,
354 IriRelativeStr,
355 UriRelativeStr,
356 "../?alpha=\u{03B1}",
357 "../?alpha=%CE%B1"
358);
359impl_conversion_between_uri!(
360 IriString,
361 UriString,
362 IriStr,
363 UriStr,
364 "http://example.com/?alpha=\u{03B1}",
365 "http://example.com/?alpha=%CE%B1"
366);
367impl_conversion_between_uri!(
368 IriQueryString,
369 UriQueryString,
370 IriQueryStr,
371 UriQueryStr,
372 "alpha-is-\u{03B1}",
373 "alpha-is-%CE%B1"
374);
375impl_conversion_between_uri!(
376 IriFragmentString,
377 UriFragmentString,
378 IriFragmentStr,
379 UriFragmentStr,
380 "alpha-is-\u{03B1}",
381 "alpha-is-%CE%B1"
382);