1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
// Copyright (c) 2018-2023, agnos.ai UK Ltd, all rights reserved.
//---------------------------------------------------------------
use iref::{Iri, IriBuf};

/// A `Namespace` represents a namespace IRI that can also be shown
/// in abbreviated format, also known as "prefix".
///
/// For instance, the namespace IRI <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
/// can also be shown (in [RDF Turtle](https://www.w3.org/TR/turtle/#prefixed-name)
/// or SPARQL for instance) as `rdf:`.
/// A "local name" such as "type" in such a namespace would look
/// like <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> or like `rdf:type`.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Namespace {
    /// assumed to end with ':'
    pub name: String,
    /// assumed to end with either '/' or '#'
    pub iri:  IriBuf,
}

impl std::fmt::Display for Namespace {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{} <{}>",
            self.name.as_str(),
            self.iri.as_str()
        )
    }
}

impl Namespace {
    pub fn declare<'a, Base: Into<Iri<'a>>>(name: &str, iri: Base) -> Self {
        let iri = iri.into();
        match iri.as_str().chars().last() {
            Some('/') | Some('#') => Self { name: name.to_string(), iri: IriBuf::from(iri) },
            _ => {
                Self {
                    name: name.to_string(),
                    iri:  IriBuf::from_string(format!("{}/", iri)).unwrap(),
                }
            },
        }
    }

    pub fn declare_from_str(name: &str, iri: &str) -> Self {
        Self::declare(name, Iri::from_str(iri).unwrap())
    }

    /// Return an identifier based on the current namespace IRI and the given
    /// local name within that namespace.
    pub fn with_local_name(&self, name: &str) -> Result<IriBuf, iref::Error> {
        use std::str::FromStr;
        let iri_str = match *self.iri.as_bytes().last().unwrap() as char {
            '/' | '#' => format!("{}{name}", self.iri.as_str()),
            _ => {
                panic!(
                    "{} does not end with either / or #",
                    self.iri.as_str()
                )
            },
        };

        IriBuf::from_str(iri_str.as_str())
    }

    #[cfg(all(feature = "rdftk-support", not(target_arch = "wasm32")))]
    pub fn as_rdftk_iri_ref(&self) -> Result<rdftk_iri::IRIRef, rdftk_iri::error::Error> {
        Ok(rdftk_iri::IRIRef::new(self.as_rdftk_iri()?))
    }

    #[cfg(all(feature = "rdftk-support", not(target_arch = "wasm32")))]
    pub fn as_rdftk_iri(&self) -> Result<rdftk_iri::IRI, rdftk_iri::error::Error> {
        use std::str::FromStr;
        rdftk_iri::IRI::from_str(self.iri.as_str())
    }
}

#[cfg(test)]
mod tests {
    use {super::Namespace, iref::Iri};

    #[test_log::test]
    fn test_a_prefix() -> Result<(), iref::Error> {
        let namespace = Namespace::declare(
            "test:",
            Iri::new("http://whatever.kom/test#").unwrap(),
        );
        let x = namespace.with_local_name("abc")?;

        assert_eq!(x.as_str(), "http://whatever.kom/test#abc");
        Ok(())
    }

    #[test_log::test]
    fn test_b_prefix() -> Result<(), iref::Error> {
        let namespace = Namespace::declare(
            "test:",
            Iri::new("http://whatever.kom/test/").unwrap(),
        );
        let x = namespace.with_local_name("abc")?;

        assert_eq!(x.as_str(), "http://whatever.kom/test/abc");
        Ok(())
    }
}