sophia_iri/
resolve.rs

1//! Implementation of IRI resolution as per
2//! [\[RFC 3987\]](https://tools.ietf.org/html/rfc3987).
3//!
4//! This module is based on <https://docs.rs/oxiri/>.
5//!
6//! NB: compared to [`Iri`] and [`IriRef`],
7//! [`BaseIri`] and [`BaseIriRef`] are slower to build,
8//! because they analyse the internal structure of the IRI,
9//! in order to allow for efficient resolution of relative IRIs.
10
11use super::{Iri, IriRef, IsIri, IsIriRef};
12use std::borrow::Borrow;
13use std::ops::Deref;
14
15pub use oxiri::IriParseError;
16pub use oxiri::{Iri as Oxiri, IriRef as OxiriRef};
17
18/// A `BaseIri` is an absolute IRI against which relative IRIs can be resolved.
19/// It stores the internal structure of the IRI,
20/// to allow for efficient resolution of relative IRIs against itself.
21#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
22pub struct BaseIri<T>(Oxiri<T>);
23
24impl<T: Deref<Target = str>> IsIriRef for BaseIri<T> {}
25impl<T: Deref<Target = str>> IsIri for BaseIri<T> {}
26
27impl<T: Deref<Target = str>> BaseIri<T> {
28    /// Creates a new `BaseIri` if `iri` is a valid IRI,
29    /// otherwise returns an [`IriParseError`].
30    pub fn new(iri: T) -> Result<Self, IriParseError> {
31        Oxiri::parse(iri).map(BaseIri)
32    }
33
34    /// Resolves `iri` against this `BaseIri`.
35    pub fn resolve<R: Resolvable<String>>(&self, iri: R) -> R::OutputAbs {
36        R::output_abs(self.0.resolve(iri.borrow()).map(Oxiri::into_inner))
37    }
38
39    /// Resolves `iri` against this `BaseIri`, using `buf` to store the result.
40    pub fn resolve_into<'a, R: Resolvable<&'a str>>(
41        &self,
42        iri: R,
43        buf: &'a mut String,
44    ) -> R::OutputAbs {
45        R::output_abs(self.0.resolve_into(iri.borrow(), buf).map(|_| &buf[..]))
46    }
47}
48
49impl<T: Deref<Target = str>> Borrow<str> for BaseIri<T> {
50    fn borrow(&self) -> &str {
51        &self.0
52    }
53}
54
55impl<T: Deref<Target = str>> Deref for BaseIri<T> {
56    type Target = Oxiri<T>;
57    fn deref(&self) -> &Oxiri<T> {
58        &self.0
59    }
60}
61
62//
63
64/// A `BaseIriRef` is an absolute or relative IRI reference,
65/// against which relative IRIs can be resolved.
66/// It stores the internal structure of the IRI,
67/// to allow for efficient resolution of relative IRIs against itself.
68#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
69pub struct BaseIriRef<T>(OxiriRef<T>);
70
71impl<T: Deref<Target = str>> IsIriRef for BaseIriRef<T> {}
72
73impl<T: Deref<Target = str>> BaseIriRef<T> {
74    /// Creates a new `BaseIriRef` if `iri` is a valid IRI,
75    /// otherwise returns an [`IriParseError`].
76    pub fn new(iri: T) -> Result<Self, IriParseError> {
77        OxiriRef::parse(iri).map(BaseIriRef)
78    }
79
80    /// Resolves `iri` against this `BaseIriRef`.
81    pub fn resolve<R: Resolvable<String>>(&self, iri: R) -> R::OutputRel {
82        R::output_rel(self.0.resolve(iri.borrow()).map(OxiriRef::into_inner))
83    }
84
85    /// Resolves `iri` against this `BaseIriRef`, using `buf` to store the result.
86    pub fn resolve_into<'a, R: Resolvable<&'a str>>(
87        &self,
88        iri: R,
89        buf: &'a mut String,
90    ) -> R::OutputRel {
91        R::output_rel(self.0.resolve_into(iri.borrow(), buf).map(|_| &buf[..]))
92    }
93
94    /// Convert this to a [`BaseIri`].
95    ///
96    /// # Precondition
97    /// This [`BaseIriRef`] must be [absolute](OxiriRef::is_absolute)
98    pub fn to_base_iri(self) -> BaseIri<T> {
99        assert!(self.is_absolute());
100        BaseIri(Oxiri::try_from(self.0).unwrap())
101    }
102}
103
104impl<T: Deref<Target = str>> Borrow<str> for BaseIriRef<T> {
105    fn borrow(&self) -> &str {
106        &self.0
107    }
108}
109
110impl<T: Deref<Target = str>> Deref for BaseIriRef<T> {
111    type Target = OxiriRef<T>;
112    fn deref(&self) -> &OxiriRef<T> {
113        &self.0
114    }
115}
116
117//
118
119/// A trait for anything that can be resolved against a
120/// [`BaseIri`](BaseIri::resolve) or a [`BaseIriRef`](BaseIriRef::resolve).
121pub trait Resolvable<T: Borrow<str>>: Borrow<str> {
122    /// The output type when joining to an absolute base.
123    type OutputAbs;
124    /// The output type when joining to an relative base.
125    type OutputRel;
126    /// Method for producing the `Self::OutputAbs` from a raw result.
127    fn output_abs(res: Result<T, IriParseError>) -> Self::OutputAbs;
128    /// Method for producing the `Self::OutputRel` from a raw result.
129    fn output_rel(res: Result<T, IriParseError>) -> Self::OutputRel;
130}
131
132impl<T: Borrow<str>> Resolvable<T> for &str {
133    type OutputAbs = Result<Iri<T>, IriParseError>;
134    type OutputRel = Result<IriRef<T>, IriParseError>;
135    fn output_abs(res: Result<T, IriParseError>) -> Self::OutputAbs {
136        res.map(|iri| Iri::new_unchecked(iri))
137    }
138    fn output_rel(res: Result<T, IriParseError>) -> Self::OutputRel {
139        res.map(|iri| IriRef::new_unchecked(iri))
140    }
141}
142
143impl<T: Borrow<str>, U: IsIriRef> Resolvable<T> for U {
144    type OutputAbs = Iri<T>;
145    type OutputRel = IriRef<T>;
146    fn output_abs(res: Result<T, IriParseError>) -> Self::OutputAbs {
147        Iri::new_unchecked(res.unwrap())
148    }
149    fn output_rel(res: Result<T, IriParseError>) -> Self::OutputRel {
150        IriRef::new_unchecked(res.unwrap())
151    }
152}
153
154//
155
156#[cfg(test)]
157mod test {
158    use super::*;
159    use crate::test::*;
160    use crate::AsIriRef;
161
162    #[test]
163    fn positive() {
164        for (txt, parsed) in POSITIVE_IRIS {
165            let bir = BaseIriRef::new(*txt).unwrap();
166            assert_eq!(bir.is_absolute(), parsed.0);
167            assert_eq!(bir.scheme(), parsed.1);
168            assert_eq!(bir.authority(), parsed.2);
169            assert_eq!(bir.path(), parsed.3);
170            assert_eq!(bir.query(), parsed.4);
171            assert_eq!(bir.fragment(), parsed.5);
172            assert_eq!(bir.to_string(), *txt);
173
174            assert_eq!(bir, IriRef::new(*txt).unwrap().to_base());
175            assert_eq!(bir, IriRef::new(*txt).unwrap().as_base());
176
177            let rbi = BaseIri::new(*txt);
178            if parsed.0 {
179                assert!(rbi.is_ok(), "<{}> → {:?}", txt, rbi);
180                let bi = rbi.unwrap();
181                assert_eq!(bi.scheme(), parsed.1.unwrap());
182                assert_eq!(bi.authority(), parsed.2);
183                assert_eq!(bi.path(), parsed.3);
184                assert_eq!(bi.query(), parsed.4);
185                assert_eq!(bi.fragment(), parsed.5);
186                assert_eq!(bi.to_string(), *txt);
187
188                assert_eq!(bi.as_iri_ref(), bir.as_iri_ref());
189                assert_eq!(bi, Iri::new(*txt).unwrap().to_base());
190                assert_eq!(bi, Iri::new(*txt).unwrap().as_base());
191            } else {
192                assert!(rbi.is_err(), "<{}> → {:?}", txt, rbi);
193            }
194        }
195    }
196
197    #[test]
198    fn negative() {
199        for txt in NEGATIVE_IRIS {
200            let rpir = BaseIriRef::new(*txt);
201            assert!(rpir.is_err(), "<{}> → {:?}", txt, rpir);
202            let rpi = BaseIri::new(*txt);
203            assert!(rpi.is_err(), "<{}> → {:?}", txt, rpi);
204        }
205    }
206
207    #[test]
208    fn relative() {
209        for (rel, abs) in RELATIVE_IRIS {
210            let rbir = BaseIriRef::new(*rel);
211            assert!(rbir.is_ok(), "<{}> → {:?}", rel, rbir);
212
213            let rbi = BaseIri::new(*rel);
214            if rel != abs {
215                assert!(rbi.is_err(), "<{}> → {:?}", rel, rbi);
216            } else {
217                assert!(rbi.is_ok(), "<{}> → {:?}", rel, rbi);
218                assert_eq!(rbir.unwrap().as_iri_ref(), rbi.unwrap().as_iri_ref());
219            }
220        }
221    }
222
223    #[test]
224    fn resolve_iri_parsed() {
225        let base1 = BaseIriRef::new("http://a/b/c/d;p?q").unwrap();
226        let base2: BaseIri<_> = base1.clone().to_base_iri();
227        let mut buf = String::new();
228        for (rel, abs) in RELATIVE_IRIS {
229            let rel = IriRef::new(*rel).unwrap();
230            let got1a = base1.resolve(rel);
231            assert_eq!(&got1a, *abs);
232            buf.clear();
233            let got1b = base1.resolve_into(rel, &mut buf);
234            assert_eq!(&got1b, *abs);
235            let got2 = base2.resolve(rel);
236            assert_eq!(&got2, *abs);
237            buf.clear();
238            let got2b = base2.resolve_into(rel, &mut buf);
239            assert_eq!(&got2b, *abs);
240        }
241    }
242
243    #[test]
244    fn resolve_str() {
245        let base1 = BaseIriRef::new("http://a/b/c/d;p?q").unwrap();
246        let base2: BaseIri<_> = base1.clone().to_base_iri();
247        let mut buf = String::new();
248        for (rel, abs) in RELATIVE_IRIS {
249            let got1a = base1.resolve(*rel).unwrap();
250            assert_eq!(&got1a, *abs);
251            buf.clear();
252            let got1b = base1.resolve_into(*rel, &mut buf).unwrap();
253            assert_eq!(&got1b, *abs);
254            let got2a = base2.resolve(*rel).unwrap();
255            assert_eq!(&got2a, *abs);
256            buf.clear();
257            let got2b = base2.resolve_into(*rel, &mut buf).unwrap();
258            assert_eq!(&got2b, *abs);
259        }
260    }
261
262    #[test]
263    fn resolve_bad_str() {
264        let base1 = BaseIriRef::new("http://a/b/c/d;p?q").unwrap();
265        let base2: BaseIri<_> = base1.clone().to_base_iri();
266        let mut buf = String::new();
267        for txt in NEGATIVE_IRIS {
268            println!("{}", *txt);
269            assert!(base1.resolve(*txt).is_err());
270            assert!(base2.resolve(*txt).is_err());
271            assert!(base1.resolve_into(*txt, &mut buf).is_err());
272            assert!(base2.resolve_into(*txt, &mut buf).is_err());
273        }
274    }
275}