ekg_namespace/
namespace.rs

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