iref_core/iri/
mod.rs

1use std::{
2	borrow::{Borrow, Cow},
3	hash::{self, Hash},
4};
5
6use static_regular_grammar::RegularGrammar;
7
8mod authority;
9mod authority_mut;
10mod fragment;
11mod path;
12mod path_mut;
13mod query;
14mod reference;
15
16pub use authority::*;
17pub use authority_mut::*;
18pub use fragment::*;
19pub use path::*;
20pub use path_mut::*;
21pub use query::*;
22pub use reference::*;
23
24use crate::{
25	common::{parse, str_eq, RiBufImpl, RiImpl, RiRefBufImpl, RiRefImpl},
26	uri::InvalidUriRef,
27	InvalidUri, Uri, UriBuf, UriRef, UriRefBuf,
28};
29
30macro_rules! iri_error {
31	($($(#[$meta:meta])* $variant:ident : $ident:ident),*) => {
32		#[derive(Debug, thiserror::Error)]
33		pub enum IriError<T> {
34			$(
35				$(#[$meta])*
36				$variant(#[from] $ident<T>)
37			),*
38		}
39
40		$(
41			impl<'a> From<$ident<String>> for IriError<Cow<'a, str>> {
42				fn from($ident(value): $ident<String>) -> Self {
43					Self::$variant($ident(Cow::Owned(value)))
44				}
45			}
46
47			impl<'a> From<$ident<&'a str>> for IriError<Cow<'a, str>> {
48				fn from($ident(value): $ident<&'a str>) -> Self {
49					Self::$variant($ident(Cow::Borrowed(value)))
50				}
51			}
52
53			impl<'a> From<$ident<Vec<u8>>> for IriError<Cow<'a, [u8]>> {
54				fn from($ident(value): $ident<Vec<u8>>) -> Self {
55					Self::$variant($ident(Cow::Owned(value)))
56				}
57			}
58
59			impl<'a> From<$ident<&'a [u8]>> for IriError<Cow<'a, [u8]>> {
60				fn from($ident(value): $ident<&'a [u8]>) -> Self {
61					Self::$variant($ident(Cow::Borrowed(value)))
62				}
63			}
64		)*
65	};
66}
67
68iri_error! {
69	#[error("invalid IRI: {0}")]
70	Iri: InvalidIri,
71
72	#[error("invalid IRI reference: {0}")]
73	Reference: InvalidIriRef,
74
75	#[error("invalid IRI scheme: {0}")]
76	Scheme: InvalidScheme,
77
78	#[error("invalid IRI authority: {0}")]
79	Authority: InvalidAuthority,
80
81	#[error("invalid IRI authority user info: {0}")]
82	UserInfo: InvalidUserInfo,
83
84	#[error("invalid IRI authority host: {0}")]
85	Host: InvalidHost,
86
87	#[error("invalid IRI authority port: {0}")]
88	Port: InvalidPort,
89
90	#[error("invalid IRI path: {0}")]
91	Path: InvalidPath,
92
93	#[error("invalid IRI path segment: {0}")]
94	PathSegment: InvalidSegment,
95
96	#[error("invalid IRI query: {0}")]
97	Query: InvalidQuery,
98
99	#[error("invalid IRI fragment: {0}")]
100	Fragment: InvalidFragment
101}
102
103/// Internationalized Resource Identifier (IRI).
104///
105/// # Example
106///
107/// ```rust
108/// # use iref_core as iref;
109/// use iref::iri::{Iri, Scheme, Authority, Path, Query, Fragment};
110/// # fn main() -> Result<(), iref::iri::InvalidIri<&'static str>> {
111/// let iri = Iri::new("https://www.rust-lang.org/foo/bar?query#fragment")?;
112///
113/// assert_eq!(iri.scheme(), Scheme::new(b"https").unwrap());
114/// assert_eq!(iri.authority(), Some(Authority::new("www.rust-lang.org").unwrap()));
115/// assert_eq!(iri.path(), Path::new("/foo/bar").unwrap());
116/// assert_eq!(iri.query(), Some(Query::new("query").unwrap()));
117/// assert_eq!(iri.fragment(), Some(Fragment::new("fragment").unwrap()));
118/// #
119/// # Ok(())
120/// # }
121/// ```
122#[derive(RegularGrammar)]
123#[grammar(
124	file = "src/iri/grammar.abnf",
125	entry_point = "IRI",
126	cache = "automata/iri.aut.cbor"
127)]
128#[grammar(sized(IriBuf, derive(Debug, Display, PartialEq, Eq, PartialOrd, Ord, Hash)))]
129#[cfg_attr(feature = "serde", grammar(serde))]
130#[cfg_attr(feature = "ignore-grammars", grammar(disable))]
131pub struct Iri(str);
132
133#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
134pub struct IriParts<'a> {
135	pub scheme: &'a Scheme,
136	pub authority: Option<&'a Authority>,
137	pub path: &'a Path,
138	pub query: Option<&'a Query>,
139	pub fragment: Option<&'a Fragment>,
140}
141
142impl RiRefImpl for Iri {
143	type Authority = Authority;
144	type Path = Path;
145	type Query = Query;
146	type Fragment = Fragment;
147
148	type RiRefBuf = IriRefBuf;
149
150	fn as_bytes(&self) -> &[u8] {
151		self.0.as_bytes()
152	}
153}
154
155impl RiImpl for Iri {}
156
157impl Iri {
158	pub fn parts(&self) -> IriParts {
159		let bytes = self.as_bytes();
160		let ranges = parse::parts(bytes, 0);
161
162		IriParts {
163			scheme: unsafe { Scheme::new_unchecked(&bytes[ranges.scheme]) },
164			authority: ranges
165				.authority
166				.map(|r| unsafe { Authority::new_unchecked(&self.0[r]) }),
167			path: unsafe { Path::new_unchecked(&self.0[ranges.path]) },
168			query: ranges
169				.query
170				.map(|r| unsafe { Query::new_unchecked(&self.0[r]) }),
171			fragment: ranges
172				.fragment
173				.map(|r| unsafe { Fragment::new_unchecked(&self.0[r]) }),
174		}
175	}
176
177	/// Converts this IRI into an IRI reference.
178	///
179	/// All IRI are valid IRI references.
180	pub fn as_iri_ref(&self) -> &IriRef {
181		unsafe { IriRef::new_unchecked(&self.0) }
182	}
183
184	/// Converts this IRI into an URI, if possible.
185	pub fn as_uri(&self) -> Option<&Uri> {
186		Uri::new(self.as_bytes()).ok()
187	}
188
189	/// Converts this IRI into an URI reference, if possible.
190	pub fn as_uri_ref(&self) -> Option<&UriRef> {
191		UriRef::new(self.as_bytes()).ok()
192	}
193
194	/// Returns the scheme of the IRI.
195	#[inline]
196	pub fn scheme(&self) -> &Scheme {
197		RiImpl::scheme(self)
198	}
199
200	/// Returns the authority part of the IRI reference, if any.
201	pub fn authority(&self) -> Option<&Authority> {
202		RiRefImpl::authority(self)
203	}
204
205	/// Returns the path of the IRI reference.
206	pub fn path(&self) -> &Path {
207		RiRefImpl::path(self)
208	}
209
210	pub fn query(&self) -> Option<&Query> {
211		RiRefImpl::query(self)
212	}
213
214	pub fn fragment(&self) -> Option<&Fragment> {
215		RiRefImpl::fragment(self)
216	}
217
218	/// Get this IRI relatively to the given IRI reference.
219	///
220	/// # Example
221	/// ```
222	/// # use iref_core::Iri;
223	/// let a = Iri::new("https://crates.io/").unwrap();
224	/// let b = Iri::new("https://crates.io/crates/iref").unwrap();
225	/// let c = Iri::new("https://crates.io/crates/json-ld").unwrap();
226	/// assert_eq!(b.relative_to(a.as_iri_ref()), "crates/iref");
227	/// assert_eq!(a.relative_to(b.as_iri_ref()), "..");
228	/// assert_eq!(b.relative_to(c.as_iri_ref()), "iref");
229	/// assert_eq!(c.relative_to(b.as_iri_ref()), "json-ld");
230	/// ```
231	pub fn relative_to(&self, other: &(impl ?Sized + AsRef<IriRef>)) -> IriRefBuf {
232		self.as_iri_ref().relative_to(other)
233	}
234
235	/// Get the suffix of this IRI, if any, with regard to the given prefix IRI.
236	///
237	/// Returns `Some((suffix, query, fragment))` if this IRI is of the form
238	/// `prefix/suffix?query#fragment` where `prefix` is given as parameter.
239	/// Returns `None` otherwise.
240	/// If the `suffix` scheme or authority is different from this path, it will return `None`.
241	///
242	/// See [`Path::suffix`] for more details.
243	#[inline]
244	pub fn suffix(
245		&self,
246		prefix: &(impl ?Sized + AsRef<Iri>),
247	) -> Option<(PathBuf, Option<&Query>, Option<&Fragment>)> {
248		RiRefImpl::suffix(self, prefix.as_ref())
249	}
250
251	/// The IRI without the file name, query and fragment.
252	///
253	/// # Example
254	/// ```
255	/// # use iref_core::Iri;
256	/// let a = Iri::new("https://crates.io/crates/iref?query#fragment").unwrap();
257	/// let b = Iri::new("https://crates.io/crates/iref/?query#fragment").unwrap();
258	/// assert_eq!(a.base(), "https://crates.io/crates/");
259	/// assert_eq!(b.base(), "https://crates.io/crates/iref/")
260	/// ```
261	#[inline]
262	pub fn base(&self) -> &Self {
263		unsafe { Self::new_unchecked(std::str::from_utf8_unchecked(RiRefImpl::base(self))) }
264	}
265}
266
267impl AsRef<IriRef> for Iri {
268	fn as_ref(&self) -> &IriRef {
269		self.as_iri_ref()
270	}
271}
272
273impl Borrow<IriRef> for Iri {
274	fn borrow(&self) -> &IriRef {
275		self.as_iri_ref()
276	}
277}
278
279impl<'a> From<&'a Iri> for &'a IriRef {
280	fn from(value: &'a Iri) -> Self {
281		value.as_iri_ref()
282	}
283}
284
285impl<'a> TryFrom<&'a Iri> for &'a Uri {
286	type Error = InvalidUri<&'a Iri>;
287
288	fn try_from(value: &'a Iri) -> Result<Self, Self::Error> {
289		value.as_uri().ok_or(InvalidUri(value))
290	}
291}
292
293impl<'a> TryFrom<&'a Iri> for &'a UriRef {
294	type Error = InvalidUriRef<&'a Iri>;
295
296	fn try_from(value: &'a Iri) -> Result<Self, Self::Error> {
297		value.as_uri_ref().ok_or(InvalidUriRef(value))
298	}
299}
300
301str_eq!(Iri);
302
303impl PartialEq for Iri {
304	fn eq(&self, other: &Self) -> bool {
305		self.parts() == other.parts()
306	}
307}
308
309impl<'a> PartialEq<&'a Iri> for Iri {
310	fn eq(&self, other: &&'a Self) -> bool {
311		*self == **other
312	}
313}
314
315impl PartialEq<IriBuf> for Iri {
316	fn eq(&self, other: &IriBuf) -> bool {
317		*self == *other.as_iri()
318	}
319}
320
321impl PartialEq<IriRef> for Iri {
322	fn eq(&self, other: &IriRef) -> bool {
323		*self.as_iri_ref() == *other
324	}
325}
326
327impl<'a> PartialEq<&'a IriRef> for Iri {
328	fn eq(&self, other: &&'a IriRef) -> bool {
329		*self.as_iri_ref() == **other
330	}
331}
332
333impl PartialEq<IriRefBuf> for Iri {
334	fn eq(&self, other: &IriRefBuf) -> bool {
335		*self.as_iri_ref() == *other.as_iri_ref()
336	}
337}
338
339impl Eq for Iri {}
340
341impl PartialOrd for Iri {
342	fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
343		Some(self.cmp(other))
344	}
345}
346
347impl<'a> PartialOrd<&'a Iri> for Iri {
348	fn partial_cmp(&self, other: &&'a Self) -> Option<std::cmp::Ordering> {
349		self.partial_cmp(*other)
350	}
351}
352
353impl PartialOrd<IriBuf> for Iri {
354	fn partial_cmp(&self, other: &IriBuf) -> Option<std::cmp::Ordering> {
355		self.partial_cmp(other.as_iri())
356	}
357}
358
359impl PartialOrd<IriRef> for Iri {
360	fn partial_cmp(&self, other: &IriRef) -> Option<std::cmp::Ordering> {
361		self.as_iri_ref().partial_cmp(other)
362	}
363}
364
365impl<'a> PartialOrd<&'a IriRef> for Iri {
366	fn partial_cmp(&self, other: &&'a IriRef) -> Option<std::cmp::Ordering> {
367		self.as_iri_ref().partial_cmp(*other)
368	}
369}
370
371impl PartialOrd<IriRefBuf> for Iri {
372	fn partial_cmp(&self, other: &IriRefBuf) -> Option<std::cmp::Ordering> {
373		self.partial_cmp(other.as_iri_ref())
374	}
375}
376
377impl Ord for Iri {
378	fn cmp(&self, other: &Self) -> std::cmp::Ordering {
379		self.parts().cmp(&other.parts())
380	}
381}
382
383impl Hash for Iri {
384	fn hash<H: hash::Hasher>(&self, state: &mut H) {
385		self.parts().hash(state)
386	}
387}
388
389impl RiRefImpl for IriBuf {
390	type Authority = Authority;
391	type Path = Path;
392	type Query = Query;
393	type Fragment = Fragment;
394
395	type RiRefBuf = IriRefBuf;
396
397	fn as_bytes(&self) -> &[u8] {
398		self.0.as_bytes()
399	}
400}
401
402impl RiImpl for IriBuf {}
403
404impl RiRefBufImpl for IriBuf {
405	type Ri = Iri;
406	type RiBuf = Self;
407
408	unsafe fn new_unchecked(bytes: Vec<u8>) -> Self {
409		Self::new_unchecked(String::from_utf8_unchecked(bytes))
410	}
411
412	unsafe fn as_mut_vec(&mut self) -> &mut Vec<u8> {
413		self.0.as_mut_vec()
414	}
415
416	fn into_bytes(self) -> Vec<u8> {
417		self.0.into_bytes()
418	}
419}
420
421impl RiBufImpl for IriBuf {}
422
423impl IriBuf {
424	/// Creates a new IRI from a byte string.
425	#[inline]
426	pub fn from_vec(buffer: Vec<u8>) -> Result<Self, InvalidIri<Vec<u8>>> {
427		match String::from_utf8(buffer) {
428			Ok(string) => Self::new(string).map_err(|InvalidIri(s)| InvalidIri(s.into_bytes())),
429			Err(e) => Err(InvalidIri(e.into_bytes())),
430		}
431	}
432
433	/// Creates a new IRI from its scheme.
434	pub fn from_scheme(scheme: SchemeBuf) -> Self {
435		RiBufImpl::from_scheme(scheme)
436	}
437
438	/// Converts this IRI into an IRI reference.
439	pub fn into_iri_ref(self) -> IriRefBuf {
440		unsafe { IriRefBuf::new_unchecked(self.0) }
441	}
442
443	/// Converts this IRI into an URI, if possible.
444	pub fn try_into_uri(self) -> Result<UriBuf, InvalidUri<IriBuf>> {
445		UriBuf::new(self.into_bytes()).map_err(|InvalidUri(bytes)| unsafe {
446			InvalidUri(Self::new_unchecked(String::from_utf8_unchecked(bytes)))
447		})
448	}
449
450	/// Converts this IRI into an URI reference, if possible.
451	pub fn try_into_uri_ref(self) -> Result<UriRefBuf, InvalidUriRef<IriBuf>> {
452		UriRefBuf::new(self.into_bytes()).map_err(|InvalidUriRef(bytes)| unsafe {
453			InvalidUriRef(Self::new_unchecked(String::from_utf8_unchecked(bytes)))
454		})
455	}
456
457	/// Returns a mutable reference to the underlying `Vec<u8>` buffer
458	/// representing the IRI.
459	///
460	/// # Safety
461	///
462	/// The caller must ensure that once the mutable reference is dropped, its
463	/// content is still a valid IRI.
464	pub unsafe fn as_mut_vec(&mut self) -> &mut Vec<u8> {
465		self.0.as_mut_vec()
466	}
467
468	pub fn path_mut(&mut self) -> PathMut {
469		PathMut::from_impl(RiRefBufImpl::path_mut(self))
470	}
471
472	pub fn authority_mut(&mut self) -> Option<AuthorityMut> {
473		RiRefBufImpl::authority_mut(self).map(AuthorityMut::from_impl)
474	}
475
476	/// Sets the scheme part.
477	///
478	/// # Example
479	///
480	/// ```
481	/// # use iref_core as iref;
482	/// use iref::{IriBuf, iri::Scheme};
483	///
484	/// let mut a = IriBuf::new("http://example.org/path".to_string()).unwrap();
485	/// a.set_scheme(Scheme::new(b"https").unwrap());
486	/// assert_eq!(a, "https://example.org/path");
487	/// ```
488	pub fn set_scheme(&mut self, new_scheme: &Scheme) {
489		RiBufImpl::set_scheme(self, new_scheme)
490	}
491
492	/// Sets the authority part.
493	///
494	/// If the path is relative, this also turns it into an absolute path,
495	/// since an authority cannot be followed by a relative path.
496	///
497	/// To avoid any ambiguity, if `authority` is `None` and the path starts
498	/// with `//`, it will be changed into `/.//` as to not be interpreted as
499	/// an authority part.
500	///
501	/// # Example
502	///
503	/// ```
504	/// # use iref_core as iref;
505	/// use iref::{IriBuf, iri::Authority};
506	///
507	/// let mut a = IriBuf::new("scheme:/path".to_string()).unwrap();
508	/// a.set_authority(Some(Authority::new("example.org").unwrap()));
509	/// assert_eq!(a, "scheme://example.org/path");
510	///
511	/// // When an authority is added before a relative path,
512	/// // the path becomes absolute.
513	/// let mut b = IriBuf::new("scheme:path".to_string()).unwrap();
514	/// b.set_authority(Some(Authority::new("example.org").unwrap()));
515	/// assert_eq!(b, "scheme://example.org/path");
516	///
517	/// // When an authority is removed and the path starts with `//`,
518	/// // a `/.` prefix is added to the path to avoid any ambiguity.
519	/// let mut c = IriBuf::new("scheme://example.org//path".to_string()).unwrap();
520	/// c.set_authority(None);
521	/// assert_eq!(c, "scheme:/.//path");
522	/// ```
523	pub fn set_authority(&mut self, authority: Option<&Authority>) {
524		RiRefBufImpl::set_authority(self, authority)
525	}
526
527	/// Sets the path part.
528	///
529	/// If there is an authority and the path is relative, this also turns it
530	/// into an absolute path, since an authority cannot be followed by a
531	/// relative path.
532	///
533	/// To avoid any ambiguity, if there is no authority and the path starts
534	/// with `//`, it will be changed into `/.//` as to not be interpreted as
535	/// an authority part. Similarly if there is no scheme nor authority and the
536	/// beginning of the new path looks like a scheme, it is prefixed with `./`
537	/// to not be confused with a scheme.
538	///
539	/// # Example
540	///
541	/// ```
542	/// # use iref_core as iref;
543	/// use iref::{IriBuf, iri::Path};
544	///
545	/// let mut a = IriBuf::new("http://example.org/old/path".to_string()).unwrap();
546	/// a.set_path(Path::new("/foo/bar").unwrap());
547	/// assert_eq!(a, "http://example.org/foo/bar");
548	///
549	/// // If there is an authority and the new path is relative,
550	/// // it is turned into an absolute path.
551	/// let mut b = IriBuf::new("http://example.org/old/path".to_string()).unwrap();
552	/// b.set_path(Path::new("relative/path").unwrap());
553	/// assert_eq!(b, "http://example.org/relative/path");
554	///
555	/// // If there is no authority and the path starts with `//`,
556	/// // it is prefixed with `/.` to avoid being confused with an authority.
557	/// let mut c = IriBuf::new("http:old/path".to_string()).unwrap();
558	/// c.set_path(Path::new("//foo/bar").unwrap());
559	/// assert_eq!(c, "http:/.//foo/bar");
560	/// ```
561	pub fn set_path(&mut self, path: &Path) {
562		RiRefBufImpl::set_path(self, path)
563	}
564
565	/// Sets the query part.
566	pub fn set_query(&mut self, query: Option<&Query>) {
567		RiRefBufImpl::set_query(self, query)
568	}
569
570	/// Sets the fragment part.
571	pub fn set_fragment(&mut self, fragment: Option<&Fragment>) {
572		RiRefBufImpl::set_fragment(self, fragment)
573	}
574}
575
576impl AsRef<IriRef> for IriBuf {
577	fn as_ref(&self) -> &IriRef {
578		self.as_iri_ref()
579	}
580}
581
582impl Borrow<IriRef> for IriBuf {
583	fn borrow(&self) -> &IriRef {
584		self.as_iri_ref()
585	}
586}
587
588impl From<IriBuf> for IriRefBuf {
589	fn from(value: IriBuf) -> Self {
590		value.into_iri_ref()
591	}
592}
593
594impl TryFrom<IriBuf> for UriBuf {
595	type Error = InvalidUri<IriBuf>;
596
597	fn try_from(value: IriBuf) -> Result<Self, Self::Error> {
598		value.try_into_uri()
599	}
600}
601
602impl TryFrom<IriBuf> for UriRefBuf {
603	type Error = InvalidUriRef<IriBuf>;
604
605	fn try_from(value: IriBuf) -> Result<Self, Self::Error> {
606		value.try_into_uri_ref()
607	}
608}
609
610str_eq!(IriBuf);
611
612impl PartialEq<IriRef> for IriBuf {
613	fn eq(&self, other: &IriRef) -> bool {
614		*self.as_iri_ref() == *other
615	}
616}
617
618impl<'a> PartialEq<&'a IriRef> for IriBuf {
619	fn eq(&self, other: &&'a IriRef) -> bool {
620		*self.as_iri_ref() == **other
621	}
622}
623
624impl PartialEq<IriRefBuf> for IriBuf {
625	fn eq(&self, other: &IriRefBuf) -> bool {
626		*self.as_iri_ref() == *other.as_iri_ref()
627	}
628}
629
630impl PartialOrd<IriRef> for IriBuf {
631	fn partial_cmp(&self, other: &IriRef) -> Option<std::cmp::Ordering> {
632		self.as_iri_ref().partial_cmp(other)
633	}
634}
635
636impl<'a> PartialOrd<&'a IriRef> for IriBuf {
637	fn partial_cmp(&self, other: &&'a IriRef) -> Option<std::cmp::Ordering> {
638		self.as_iri_ref().partial_cmp(*other)
639	}
640}
641
642impl PartialOrd<IriRefBuf> for IriBuf {
643	fn partial_cmp(&self, other: &IriRefBuf) -> Option<std::cmp::Ordering> {
644		self.as_iri_ref().partial_cmp(other.as_iri_ref())
645	}
646}