sophia_api/ns/
_term.rs

1use super::*;
2use crate::term::{Term, TermKind};
3
4/// A [`Term`] produced by a [`Namespace`].
5///
6/// The raison d'ĂȘtre of this type, compared to [`IriRef<&str>`],
7/// is that it stored the IRI in two parts (namespace and suffix),
8/// so that the namespace can be reused by multiple distinct terms.
9///
10/// It makes sense for [`Namespace`]s, whose terms all have the same prefix.
11#[derive(Clone, Copy, Debug)]
12pub struct NsTerm<'a> {
13    pub(crate) ns: IriRef<&'a str>,
14    /// NB: suffix must satisfy that ns+suffix is still a valid IRI reference
15    pub(crate) suffix: &'a str,
16}
17
18impl fmt::Display for NsTerm<'_> {
19    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
20        write!(f, "{}{}", self.ns.as_str(), self.suffix)
21    }
22}
23
24impl<'a> NsTerm<'a> {
25    /// Make an NsTerm without checking that it produces a valid IRI.
26    pub const fn new_unchecked(ns: IriRef<&'a str>, suffix: &'a str) -> Self {
27        NsTerm { ns, suffix }
28    }
29
30    /// Return an [`IriRef`] representing this term.
31    pub fn iriref(&self) -> IriRef<MownStr> {
32        IriRef::new_unchecked(if self.suffix.is_empty() {
33            self.ns.as_str().into()
34        } else {
35            self.to_string().into()
36        })
37    }
38
39    /// Return an [`IriRef`] representing this term.
40    pub fn to_iriref(self) -> IriRef<MownStr<'a>> {
41        if self.suffix.is_empty() {
42            self.ns.map_unchecked(MownStr::from)
43        } else {
44            IriRef::new_unchecked(self.to_string().into())
45        }
46    }
47}
48
49impl<'a> Term for NsTerm<'a> {
50    type BorrowTerm<'x> = &'x Self where 'a: 'x;
51
52    fn kind(&self) -> TermKind {
53        TermKind::Iri
54    }
55    fn iri(&self) -> Option<IriRef<MownStr<'_>>> {
56        Some(self.iriref())
57    }
58    fn borrow_term(&self) -> Self::BorrowTerm<'_> {
59        self
60    }
61    fn eq<T: Term>(&self, other: T) -> bool {
62        match other.iri() {
63            Some(iri) => {
64                let ns = self.ns.as_str();
65                iri.as_str().starts_with(ns) && &iri[ns.len()..] == self.suffix
66            }
67            None => false,
68        }
69    }
70}
71
72impl<'a, T: Term> PartialEq<T> for NsTerm<'a> {
73    fn eq(&self, other: &T) -> bool {
74        Term::eq(self, other.borrow_term())
75    }
76}
77
78impl<'a> Eq for NsTerm<'a> {}
79
80#[cfg(test)]
81mod test {
82    use super::*;
83
84    #[test]
85    fn ns_term_eq() {
86        let ns = IriRef::new_unchecked("http://example.org/");
87        let t1a = NsTerm { ns, suffix: "foo" };
88        let t2a = NsTerm {
89            ns,
90            suffix: "foo/bar",
91        };
92        let t3a = NsTerm { ns, suffix: "bar" };
93        let t1b = IriRef::new_unchecked("http://example.org/foo");
94        let t2b = IriRef::new_unchecked("http://example.org/foo/bar");
95        let t3b = IriRef::new_unchecked("http://example.org/bar");
96
97        assert!(t1a == t1b);
98        assert!(t2a == t2b);
99        assert!(t3a == t3b);
100        assert!(t1a != t2b);
101        assert!(t2a != t3b);
102        assert!(t3a != t1b);
103    }
104}
105
106impl<'a> std::ops::Mul<NsTerm<'a>> for &'a str {
107    type Output = crate::term::SimpleTerm<'a>;
108
109    fn mul(self, rhs: NsTerm<'a>) -> Self::Output {
110        crate::term::SimpleTerm::LiteralDatatype(self.into(), rhs.to_iriref())
111    }
112}