Skip to main content

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                // SAFETY: An ASCII-only IRI is a URI.
136                // URI (by `UriSpec`) is a subset of IRI (by `IriSpec`),
137                // and the difference is that URIs can only have ASCII characters.
138                let uri = unsafe {
139                    <$ty_borrowed_uri>::new_unchecked_justified(
140                        self.as_str(),
141                        "an ASCII-only IRI is also a valid URI",
142                    )
143                };
144                Some(uri)
145            }
146        }
147
148        /// Conversion from an IRI into a URI.
149        #[cfg(feature = "alloc")]
150        impl $ty_owned_iri {
151            /// Percent-encodes the IRI into a valid URI that identifies the equivalent resource.
152            ///
153            /// After the encode, the IRI is also a valid URI.
154            ///
155            /// If you want a new URI string rather than modifying the IRI
156            /// string, or if you need more precise control over memory
157            /// allocation and buffer handling, use
158            #[doc = concat!("[`encode_to_uri`][`", stringify!($ty_borrowed_iri), "::encode_to_uri`]")]
159            /// method.
160            ///
161            /// # Panics
162            ///
163            /// Panics if the memory allocation failed.
164            ///
165            /// # Examples
166            ///
167            /// ```
168            /// # use iri_string::validate::Error;
169            /// #[cfg(feature = "alloc")] {
170            #[doc = concat!("use iri_string::types::", stringify!($ty_owned_iri), ";")]
171            ///
172            #[doc = concat!("let mut iri = ", stringify!($ty_owned_iri), "::try_from(", stringify!($example_iri), ")?;")]
173            /// iri.encode_to_uri_inline();
174            #[doc = concat!("assert_eq!(iri, ", stringify!($example_uri), ");")]
175            /// # }
176            /// # Ok::<_, Error>(())
177            /// ```
178            #[inline]
179            pub fn encode_to_uri_inline(&mut self) {
180                self.try_encode_to_uri_inline()
181                    .expect("failed to allocate memory");
182            }
183
184            /// Percent-encodes the IRI into a valid URI that identifies the equivalent resource.
185            ///
186            /// After the encode, the IRI is also a valid URI.
187            ///
188            /// If you want a new URI string rather than modifying the IRI
189            /// string, or if you need more precise control over memory
190            /// allocation and buffer handling, use
191            #[doc = concat!("[`encode_to_uri`][`", stringify!($ty_borrowed_iri), "::encode_to_uri`]")]
192            /// method.
193            ///
194            // TODO: This seems true as of this writing, but is this guaranteed? See
195            // <https://users.rust-lang.org/t/does-try-reserve-guarantees-that-the-content-is-preserved-on-allocation-failure/77446>.
196            // /// If the memory allocation failed, the content is preserved without modification.
197            // ///
198            /// # Examples
199            ///
200            /// ```
201            /// # use iri_string::validate::Error;
202            /// #[cfg(feature = "alloc")] {
203            #[doc = concat!("use iri_string::types::", stringify!($ty_owned_iri), ";")]
204            ///
205            #[doc = concat!("let mut iri = ", stringify!($ty_owned_iri), "::try_from(", stringify!($example_iri), ")?;")]
206            /// iri.try_encode_to_uri_inline()
207            ///     .expect("failed to allocate memory");
208            #[doc = concat!("assert_eq!(iri, ", stringify!($example_uri), ");")]
209            /// # }
210            /// # Ok::<_, Error>(())
211            /// ```
212            #[inline]
213            pub fn try_encode_to_uri_inline(&mut self) -> Result<(), TryReserveError> {
214                // SAFETY: IRI is valid after it is encoded to URI (by percent encoding).
215                unsafe {
216                    let buf = self.as_inner_mut();
217                    try_percent_encode_iri_inline(buf)?;
218                }
219                debug_assert_eq!(
220                    <$ty_borrowed_iri>::validate(self.as_str()),
221                    Ok(()),
222                    "the content must be valid at any time"
223                );
224                Ok(())
225            }
226
227            /// Percent-encodes the IRI into a valid URI that identifies the equivalent resource.
228            ///
229            /// If you want a new URI string rather than modifying the IRI
230            /// string, or if you need more precise control over memory
231            /// allocation and buffer handling, use
232            #[doc = concat!("[`encode_to_uri`][`", stringify!($ty_borrowed_iri), "::encode_to_uri`]")]
233            /// method.
234            ///
235            /// # Examples
236            ///
237            /// ```
238            /// # use iri_string::validate::Error;
239            /// #[cfg(feature = "alloc")] {
240            #[doc = concat!("use iri_string::types::{", stringify!($ty_owned_iri), ", ", stringify!($ty_owned_uri), "};")]
241            ///
242            #[doc = concat!("let iri = ", stringify!($ty_owned_iri), "::try_from(", stringify!($example_iri), ")?;")]
243            /// // Type annotation here is not necessary.
244            #[doc = concat!("let uri: ", stringify!($ty_owned_uri), " = iri.encode_into_uri();")]
245            #[doc = concat!("assert_eq!(uri, ", stringify!($example_uri), ");")]
246            /// # }
247            /// # Ok::<_, Error>(())
248            /// ```
249            #[inline]
250            #[must_use]
251            pub fn encode_into_uri(self) -> $ty_owned_uri {
252                self.try_encode_into_uri()
253                    .expect("failed to allocate memory")
254            }
255
256            /// Percent-encodes the IRI into a valid URI that identifies the equivalent resource.
257            ///
258            /// If you want a new URI string rather than modifying the IRI
259            /// string, or if you need more precise control over memory
260            /// allocation and buffer handling, use
261            #[doc = concat!("[`encode_to_uri`][`", stringify!($ty_borrowed_iri), "::encode_to_uri`]")]
262            /// method.
263            ///
264            // TODO: This seems true as of this writing, but is this guaranteed? See
265            // <https://users.rust-lang.org/t/does-try-reserve-guarantees-that-the-content-is-preserved-on-allocation-failure/77446>.
266            // /// If the memory allocation failed, the content is preserved without modification.
267            // ///
268            /// # Examples
269            ///
270            /// ```
271            /// # use iri_string::validate::Error;
272            /// #[cfg(feature = "alloc")] {
273            #[doc = concat!("use iri_string::types::{", stringify!($ty_owned_iri), ", ", stringify!($ty_owned_uri), "};")]
274            ///
275            #[doc = concat!("let iri = ", stringify!($ty_owned_iri), "::try_from(", stringify!($example_iri), ")?;")]
276            /// // Type annotation here is not necessary.
277            #[doc = concat!("let uri: ", stringify!($ty_owned_uri), " = iri.try_encode_into_uri()")]
278            ///     .expect("failed to allocate memory");
279            #[doc = concat!("assert_eq!(uri, ", stringify!($example_uri), ");")]
280            /// # }
281            /// # Ok::<_, Error>(())
282            /// ```
283            pub fn try_encode_into_uri(mut self) -> Result<$ty_owned_uri, TryReserveError> {
284                self.try_encode_to_uri_inline()?;
285                let s: String = self.into();
286                // SAFETY: The resulting ASCII-only IRI is a URI.
287                // URI (by `UriSpec`) is a subset of IRI (by `IriSpec`),
288                // and the difference is that URIs can only have ASCII characters.
289                let uri = unsafe {
290                    <$ty_owned_uri>::new_unchecked_justified(
291                        s,
292                        "an IRI encoded into ASCII-only string is also a valid URI",
293                    )
294                };
295                Ok(uri)
296            }
297
298            /// Converts an IRI into a URI without modification, if possible.
299            ///
300            /// # Examples
301            ///
302            /// ```
303            /// # use iri_string::validate::Error;
304            #[doc = concat!("use iri_string::types::{", stringify!($ty_owned_iri), ", ", stringify!($ty_owned_uri), "};")]
305            ///
306            #[doc = concat!("let ascii_iri = ", stringify!($ty_owned_iri), "::try_from(", stringify!($example_uri), ")?;")]
307            /// assert_eq!(
308            ///     ascii_iri.try_into_uri().map(|uri| uri.to_string()),
309            #[doc = concat!("    Ok(", stringify!($example_uri), ".to_string())")]
310            /// );
311            ///
312            #[doc = concat!("let nonascii_iri = ", stringify!($ty_owned_iri), "::try_from(", stringify!($example_iri), ")?;")]
313            /// assert_eq!(
314            ///     nonascii_iri.try_into_uri().map_err(|iri| iri.to_string()),
315            #[doc = concat!("    Err(", stringify!($example_iri), ".to_string())")]
316            /// );
317            /// # Ok::<_, Error>(())
318            /// ```
319            pub fn try_into_uri(self) -> Result<$ty_owned_uri, $ty_owned_iri> {
320                if !self.as_str().is_ascii() {
321                    return Err(self);
322                }
323                let s: String = self.into();
324                // SAFETY: An ASCII-only IRI is a URI.
325                // URI (by `UriSpec`) is a subset of IRI (by `IriSpec`),
326                // and the difference is that URIs can only have ASCII characters.
327                let uri = unsafe {
328                    <$ty_owned_uri>::new_unchecked_justified(
329                        s,
330                        "an ASCII-only IRI is also a valid URI",
331                    )
332                };
333                Ok(uri)
334            }
335        }
336    };
337}
338
339impl_conversion_between_uri!(
340    IriAbsoluteString,
341    UriAbsoluteString,
342    IriAbsoluteStr,
343    UriAbsoluteStr,
344    "http://example.com/?alpha=\u{03B1}",
345    "http://example.com/?alpha=%CE%B1"
346);
347impl_conversion_between_uri!(
348    IriReferenceString,
349    UriReferenceString,
350    IriReferenceStr,
351    UriReferenceStr,
352    "http://example.com/?alpha=\u{03B1}",
353    "http://example.com/?alpha=%CE%B1"
354);
355impl_conversion_between_uri!(
356    IriRelativeString,
357    UriRelativeString,
358    IriRelativeStr,
359    UriRelativeStr,
360    "../?alpha=\u{03B1}",
361    "../?alpha=%CE%B1"
362);
363impl_conversion_between_uri!(
364    IriString,
365    UriString,
366    IriStr,
367    UriStr,
368    "http://example.com/?alpha=\u{03B1}",
369    "http://example.com/?alpha=%CE%B1"
370);
371impl_conversion_between_uri!(
372    IriQueryString,
373    UriQueryString,
374    IriQueryStr,
375    UriQueryStr,
376    "alpha-is-\u{03B1}",
377    "alpha-is-%CE%B1"
378);
379impl_conversion_between_uri!(
380    IriFragmentString,
381    UriFragmentString,
382    IriFragmentStr,
383    UriFragmentStr,
384    "alpha-is-\u{03B1}",
385    "alpha-is-%CE%B1"
386);