ekg_namespace/
term.rs

1use {
2    crate::{DataType, Literal},
3    std::str::FromStr,
4};
5
6/// An RDF Term is either an IRI, a literal or a blank node.
7///
8/// See <https://www.w3.org/TR/rdf11-concepts/#section-triples>
9#[derive(Debug)]
10pub enum Term {
11    Iri(Literal),
12    Literal(Literal),
13    BlankNode(Literal),
14}
15
16const ACCEPTABLE_IRI_PROTOCOLS: [&str; 3] = ["http://", "https://", "s3://"];
17
18impl Term {
19    pub fn from_static(iri_str: &'static str) -> Result<Self, ekg_error::Error> {
20        Self::new_iri_from_str(iri_str)
21    }
22
23    pub fn new_iri(iri: &fluent_uri::Uri<&str>) -> Result<Self, ekg_error::Error> {
24        for acceptable_protocol in ACCEPTABLE_IRI_PROTOCOLS.iter() {
25            if iri.as_str().starts_with(acceptable_protocol) {
26                return Ok(Term::Iri(Literal::from_iri(iri)?));
27            }
28        }
29        Err(ekg_error::Error::InvalidIri(
30            iri.as_str().to_string(),
31        ))
32    }
33
34    pub fn new_iri_from_str(iri_str: &str) -> Result<Self, ekg_error::Error> {
35        Term::new_iri(&fluent_uri::Uri::parse(iri_str)?)
36    }
37
38    pub fn new_str(str: &str) -> Result<Self, ekg_error::Error> {
39        Ok(Term::Literal(Literal::from_str(str)?))
40    }
41
42    pub fn new_blank_node(str: &str) -> Result<Self, ekg_error::Error> {
43        Ok(Term::BlankNode(
44            Literal::from_type_and_buffer(DataType::BlankNode, str, None)?.unwrap(),
45        ))
46    }
47
48    /// Display a [`Term`] in human-readable format.
49    ///
50    /// ```no_run
51    /// use ekg_namespace::Term;
52    ///
53    /// let term = Term::new_iri(&fluent_uri::Uri::parse("https://whatever.url").unwrap()).unwrap();
54    /// let turtle = format!("{}", term.display_turtle());
55    ///
56    /// assert_eq!(turtle, "<https://whatever.url>");
57    /// ```
58    pub fn display_turtle<'a, 'b>(&'a self) -> impl std::fmt::Display + 'a + 'b
59    where 'a: 'b {
60        struct TurtleTerm<'b>(&'b Term);
61        impl<'b> std::fmt::Display for TurtleTerm<'b> {
62            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
63                let value = match self.0 {
64                    Term::Iri(value) => value,
65                    Term::Literal(value) => value,
66                    Term::BlankNode(value) => value,
67                };
68                value.display_turtle().fmt(f)
69            }
70        }
71        TurtleTerm(self)
72    }
73}
74
75impl FromStr for Term {
76    type Err = ekg_error::Error;
77
78    fn from_str(str: &str) -> Result<Self, Self::Err> { Term::new_str(str) }
79}
80
81impl From<Literal> for Term {
82    fn from(value: Literal) -> Self { value.as_term() }
83}
84
85#[cfg(test)]
86mod tests {
87    #[test_log::test]
88    fn test_term_01() {
89        let uri = fluent_uri::Uri::parse("https://whatever.url/").unwrap();
90        assert_eq!(uri.to_string(), "https://whatever.url/");
91        assert_eq!(uri.path().as_str(), "/");
92        let term = crate::Term::new_iri(&uri).unwrap();
93        let turtle = term.display_turtle().to_string();
94
95        assert_eq!(turtle, "<https://whatever.url/>");
96    }
97
98    #[test_log::test]
99    fn test_term_02() {
100        let term = crate::Term::new_iri(
101            &fluent_uri::Uri::parse("unknown-protocol://whatever.url").unwrap(),
102        );
103        assert!(term.is_err());
104        assert!(matches!(
105            term.unwrap_err(),
106            ekg_error::Error::InvalidIri(_)
107        ));
108    }
109
110    #[test_log::test]
111    fn test_term_03() {
112        // We are not accepting wrongly formatted IRIs
113        let term = crate::Term::from_static("https:/x/whatever.url");
114        assert!(term.is_err());
115        assert!(matches!(
116            term.unwrap_err(),
117            ekg_error::Error::InvalidIri(_)
118        ));
119    }
120
121    #[test_log::test]
122    fn test_term_04() {
123        let term = crate::Term::new_str("some string").unwrap();
124
125        let turtle = format!("{}", term.display_turtle());
126
127        assert_eq!(turtle, "\"some string\"");
128    }
129
130    #[test_log::test]
131    fn test_term_05() -> Result<(), ekg_error::Error> {
132        let term: crate::Term = "some string".parse()?;
133
134        let turtle = format!("{}", term.display_turtle());
135
136        assert_eq!(turtle, "\"some string\"");
137
138        Ok(())
139    }
140
141    #[test_log::test]
142    fn test_term_06() -> Result<(), ekg_error::Error> {
143        let term: crate::Term = "\"some string\"^^xsd:string".parse()?;
144
145        let turtle = format!("{}", term.display_turtle());
146
147        assert_eq!(turtle, "\"\"some string\"^^xsd:string\""); // TODO: This is incorrect, recognise the XSD data type suffix and process it
148
149        Ok(())
150    }
151
152    #[test_log::test]
153    fn test_fluent_uri_01() -> Result<(), ekg_error::Error> {
154        let uri =
155            fluent_uri::Uri::parse("https://placeholder.kg/ontology/abc#xyz").map_err(|e| {
156                println!("{}", e);
157                ekg_error::Error::Unknown
158            })?;
159
160        assert_eq!(
161            uri.to_string().as_str(),
162            "https://placeholder.kg/ontology/abc#xyz"
163        );
164        Ok(())
165    }
166
167    #[test_log::test]
168    fn test_fluent_uri_02() -> Result<(), ekg_error::Error> {
169        let uri = fluent_uri::Uri::parse("https://placeholder.kg/ontology/abc#").map_err(|e| {
170            println!("{}", e);
171            ekg_error::Error::Unknown
172        })?;
173
174        assert_eq!(
175            uri.to_string().as_str(),
176            "https://placeholder.kg/ontology/abc#"
177        );
178        Ok(())
179    }
180
181    #[test_log::test]
182    fn test_fluent_uri_03() -> Result<(), ekg_error::Error> {
183        let uri = fluent_uri::Uri::parse("https://placeholder.kg/ontology/abc/").map_err(|e| {
184            println!("{}", e);
185            ekg_error::Error::Unknown
186        })?;
187
188        assert_eq!(
189            uri.to_string().as_str(),
190            "https://placeholder.kg/ontology/abc/"
191        );
192        Ok(())
193    }
194}