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