ekg_identifier/
namespace.rs

1use {
2    crate::NamespaceIRI,
3    std::{
4        fmt::{Display, Formatter},
5        hash::{Hash, Hasher},
6    },
7};
8
9/// A `Namespace` represents a namespace IRI that can also be shown
10/// in abbreviated format, also known as "prefix".
11///
12/// For instance, the namespace IRI <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
13/// can also be shown (in [RDF Turtle](https://www.w3.org/TR/turtle/#prefixed-name)
14/// or SPARQL for instance) as `rdf:`.
15/// A "local name" such as "type" in such a namespace would look
16/// like <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> or like `rdf:type`.
17#[derive(Debug, Clone)]
18pub struct Namespace {
19    /// assumed to end with ':'
20    pub name: String,
21    /// assumed to end with either '/' or '#'
22    pub iri:  crate::TBoxNamespaceIRI,
23}
24
25impl Eq for Namespace {}
26
27impl PartialEq for Namespace {
28    fn eq(&self, other: &Self) -> bool {
29        self.name == other.name && self.iri.as_str() == other.iri.as_str()
30    }
31}
32
33impl Hash for Namespace {
34    fn hash<H: Hasher>(&self, state: &mut H) { self.name.hash(state); }
35}
36
37impl Display for Namespace {
38    // noinspection SpellCheckingInspection
39    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
40        write!(f, "{} <{}>", self.name.as_str(), self.iri)
41    }
42}
43
44impl Namespace {
45    pub fn declare(name: &str, iri: crate::TBoxNamespaceIRI) -> Result<Self, ekg_error::Error> {
46        match iri.to_string().chars().last() {
47            Some('/') | Some('#') => Ok(Self { name: name.to_string(), iri }),
48            _ => {
49                tracing::error!("{} does not end with either / or #", iri);
50                Err(ekg_error::Error::IncorrectBaseIRI { iri: iri.to_string() })
51            },
52        }
53    }
54
55    /// Variation of [`declare`] that takes an [`iref::Iri`]. We may want to
56    /// move back to iref since iref 3 is now available which seems to have
57    /// everything we need.
58    pub fn declare_iref_iri(name: &str, iri: &iref::Iri) -> Result<Self, ekg_error::Error> {
59        match iri.to_string().chars().last() {
60            Some('/') | Some('#') => Ok(Self { name: name.to_string(), iri: iri.try_into()? }),
61            _ => {
62                tracing::error!("{} does not end with either / or #", iri);
63                Err(ekg_error::Error::IncorrectBaseIRI { iri: iri.to_string() })
64            },
65        }
66    }
67
68    pub fn declare_from_str(name: &str, iri: &str) -> Result<Self, ekg_error::Error> {
69        Self::declare(name, iri.try_into()?)
70    }
71
72    // noinspection SpellCheckingInspection
73    /// Return an identifier based on the current namespace IRI and the given
74    /// local name within that namespace.
75    pub fn with_local_name(
76        &self,
77        name: &str,
78    ) -> Result<iri_string::types::IriReferenceString, ekg_error::Error> {
79        let iri_str = match self.iri.to_string().chars().last().unwrap() {
80            '/' | '#' => format!("{}{name}", self.iri),
81            _ => {
82                panic!("{} does not end with either / or #", self.iri)
83            },
84        };
85
86        Ok(iri_string::types::IriReferenceString::try_from(
87            iri_str,
88        )?)
89    }
90
91    #[inline]
92    pub fn is_in_namespace(&self, iri: &str) -> bool { self.iri.is_in_namespace(iri) }
93
94    #[cfg(all(feature = "rdftk-support", not(target_arch = "wasm32")))]
95    pub fn as_rdftk_iri_ref(&self) -> Result<rdftk_iri::IRIRef, rdftk_iri::error::Error> {
96        Ok(rdftk_iri::IRIRef::new(self.as_rdftk_iri()?))
97    }
98
99    #[cfg(all(feature = "rdftk-support", not(target_arch = "wasm32")))]
100    pub fn as_rdftk_iri(&self) -> Result<rdftk_iri::IRI, rdftk_iri::error::Error> {
101        use std::str::FromStr;
102        rdftk_iri::IRI::from_str(self.iri.as_str())
103    }
104
105    // noinspection SpellCheckingInspection
106    pub fn as_sparql_prefix(&self) -> String { format!("PREFIX {} <{}>", self.name, self.iri) }
107
108    // noinspection SpellCheckingInspection
109    pub fn as_turtle_prefix(&self) -> String { format!("@prefix {} <{}> .", self.name, self.iri) }
110}
111
112#[cfg(test)]
113mod tests {
114    #[test_log::test]
115    fn test_a_prefix() -> Result<(), ekg_error::Error> {
116        let namespace = crate::Namespace::declare(
117            "test:",
118            iri_string::types::IriReferenceString::try_from("http://whatever.kom/test#")
119                .unwrap()
120                .try_into()
121                .unwrap(),
122        )
123        .unwrap();
124        let x = namespace.with_local_name("abc")?;
125
126        assert_eq!(
127            x.to_string().as_str(),
128            "http://whatever.kom/test#abc"
129        );
130        Ok(())
131    }
132
133    #[test_log::test]
134    fn test_b_prefix() -> Result<(), ekg_error::Error> {
135        let namespace = crate::Namespace::declare(
136            "test:",
137            iri_string::types::IriReferenceString::try_from("http://whatever.kom/test/")
138                .unwrap()
139                .try_into()
140                .unwrap(),
141        )
142        .unwrap();
143        let x = namespace.with_local_name("abc")?;
144
145        assert_eq!(
146            x.to_string().as_str(),
147            "http://whatever.kom/test/abc"
148        );
149        Ok(())
150    }
151
152    #[test_log::test]
153    fn test_iri_string() -> Result<(), ekg_error::Error> {
154        let x = iri_string::types::IriReferenceString::try_from("http://whatever.kom/test/abc#")?;
155        assert_eq!(x.as_str(), "http://whatever.kom/test/abc#");
156
157        let x = iri_string::types::IriReferenceString::try_from("http://whatever.kom/test/abc/")?;
158        assert_eq!(x.as_str(), "http://whatever.kom/test/abc/");
159        Ok(())
160    }
161}