Skip to main content

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    // noinspection SpellCheckingInspection
95    pub fn as_sparql_prefix(&self) -> String { format!("PREFIX {} <{}>", self.name, self.iri) }
96
97    // noinspection SpellCheckingInspection
98    pub fn as_turtle_prefix(&self) -> String { format!("@prefix {} <{}> .", self.name, self.iri) }
99}
100
101#[cfg(test)]
102mod tests {
103    #[test_log::test]
104    fn test_a_prefix() -> Result<(), ekg_error::Error> {
105        let namespace = crate::Namespace::declare(
106            "test:",
107            iri_string::types::IriReferenceString::try_from("http://whatever.kom/test#")
108                .unwrap()
109                .into(),
110        )
111        .unwrap();
112        let x = namespace.with_local_name("abc")?;
113
114        assert_eq!(
115            x.to_string().as_str(),
116            "http://whatever.kom/test#abc"
117        );
118        Ok(())
119    }
120
121    #[test_log::test]
122    fn test_b_prefix() -> Result<(), ekg_error::Error> {
123        let namespace = crate::Namespace::declare(
124            "test:",
125            iri_string::types::IriReferenceString::try_from("http://whatever.kom/test/")
126                .unwrap()
127                .into(),
128        )
129        .unwrap();
130        let x = namespace.with_local_name("abc")?;
131
132        assert_eq!(
133            x.to_string().as_str(),
134            "http://whatever.kom/test/abc"
135        );
136        Ok(())
137    }
138
139    #[test_log::test]
140    fn test_iri_string() -> Result<(), ekg_error::Error> {
141        let x = iri_string::types::IriReferenceString::try_from("http://whatever.kom/test/abc#")?;
142        assert_eq!(x.as_str(), "http://whatever.kom/test/abc#");
143
144        let x = iri_string::types::IriReferenceString::try_from("http://whatever.kom/test/abc/")?;
145        assert_eq!(x.as_str(), "http://whatever.kom/test/abc/");
146        Ok(())
147    }
148}