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);