ekg_metadata/
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: &iri_string::types::IriReferenceStr) -> 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(&iri_string::types::IriReferenceString::try_from(
36            iri_str,
37        )?)
38    }
39
40    pub fn new_str(str: &str) -> Result<Self, ekg_error::Error> {
41        Ok(Term::Literal(Literal::from_str(str)?))
42    }
43
44    pub fn new_blank_node(str: &str) -> Result<Self, ekg_error::Error> {
45        Ok(Term::BlankNode(
46            Literal::from_type_and_buffer(DataType::BlankNode, str, None)?.unwrap(),
47        ))
48    }
49
50    /// Display a [`Term`] in human-readable format.
51    ///
52    /// ```no_run
53    /// use ekg_metadata::Term;
54    ///
55    /// let term = Term::new_iri(
56    ///     &iri_string::types::IriReferenceString::try_from("https://whatever.url").unwrap(),
57    /// )
58    /// .unwrap();
59    /// let turtle = format!("{}", term.display_turtle());
60    ///
61    /// assert_eq!(turtle, "<https://whatever.url>");
62    /// ```
63    pub fn display_turtle<'a, 'b>(&'a self) -> impl std::fmt::Display + 'a + 'b
64    where 'a: 'b {
65        struct TurtleTerm<'b>(&'b Term);
66        impl<'b> std::fmt::Display for TurtleTerm<'b> {
67            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
68                let value = match self.0 {
69                    Term::Iri(value) => value,
70                    Term::Literal(value) => value,
71                    Term::BlankNode(value) => value,
72                };
73                value.display_turtle().fmt(f)
74            }
75        }
76        TurtleTerm(self)
77    }
78}
79
80impl FromStr for Term {
81    type Err = ekg_error::Error;
82
83    fn from_str(str: &str) -> Result<Self, Self::Err> { Term::new_str(str) }
84}
85
86impl From<Literal> for Term {
87    fn from(value: Literal) -> Self { value.as_term() }
88}
89
90#[cfg(test)]
91mod tests {
92    #[test_log::test]
93    fn test_term_01() {
94        let uri = iri_string::types::IriReferenceString::try_from("https://whatever.url/").unwrap();
95        assert_eq!(uri.to_string(), "https://whatever.url/");
96        assert_eq!(uri.path_str(), "/");
97        let term = crate::Term::new_iri(&uri).unwrap();
98        let turtle = term.display_turtle().to_string();
99
100        assert_eq!(turtle, "<https://whatever.url/>");
101    }
102
103    #[test_log::test]
104    fn test_term_02() {
105        let term = crate::Term::new_iri(
106            &iri_string::types::IriReferenceString::try_from("unknown-protocol://whatever.url")
107                .unwrap(),
108        );
109        assert!(term.is_err());
110        assert!(matches!(
111            term.unwrap_err(),
112            ekg_error::Error::InvalidIri(_)
113        ));
114    }
115
116    #[test_log::test]
117    fn test_term_03() {
118        // We are not accepting wrongly formatted IRIs
119        let term = crate::Term::from_static("https:/x/whatever.url");
120        assert!(term.is_err());
121        assert!(matches!(
122            term.unwrap_err(),
123            ekg_error::Error::InvalidIri(_)
124        ));
125    }
126
127    #[test_log::test]
128    fn test_term_04() {
129        let term = crate::Term::new_str("some string").unwrap();
130
131        let turtle = format!("{}", term.display_turtle());
132
133        assert_eq!(turtle, "\"some string\"");
134    }
135
136    #[test_log::test]
137    fn test_term_05() -> Result<(), ekg_error::Error> {
138        let term: crate::Term = "some string".parse()?;
139
140        let turtle = format!("{}", term.display_turtle());
141
142        assert_eq!(turtle, "\"some string\"");
143
144        Ok(())
145    }
146
147    #[test_log::test]
148    fn test_term_06() -> Result<(), ekg_error::Error> {
149        let term: crate::Term = "\"some string\"^^xsd:string".parse()?;
150
151        let turtle = format!("{}", term.display_turtle());
152
153        assert_eq!(turtle, "\"\"some string\"^^xsd:string\""); // TODO: This is incorrect, recognise the XSD data type suffix and process it
154
155        Ok(())
156    }
157
158    #[test_log::test]
159    fn test_fluent_uri_01() -> Result<(), ekg_error::Error> {
160        let uri = iri_string::types::IriReferenceString::try_from(
161            "https://placeholder.kg/ontology/abc#xyz",
162        )
163        .map_err(|e| {
164            println!("{}", e);
165            ekg_error::Error::Unknown
166        })?;
167
168        assert_eq!(
169            uri.to_string().as_str(),
170            "https://placeholder.kg/ontology/abc#xyz"
171        );
172        Ok(())
173    }
174
175    #[test_log::test]
176    fn test_fluent_uri_02() -> Result<(), ekg_error::Error> {
177        let uri =
178            iri_string::types::IriReferenceString::try_from("https://placeholder.kg/ontology/abc#")
179                .map_err(|e| {
180                    println!("{}", e);
181                    ekg_error::Error::Unknown
182                })?;
183
184        assert_eq!(
185            uri.to_string().as_str(),
186            "https://placeholder.kg/ontology/abc#"
187        );
188        Ok(())
189    }
190
191    #[test_log::test]
192    fn test_fluent_uri_03() -> Result<(), ekg_error::Error> {
193        let uri =
194            iri_string::types::IriReferenceString::try_from("https://placeholder.kg/ontology/abc/")
195                .map_err(|e| {
196                    println!("{}", e);
197                    ekg_error::Error::Unknown
198                })?;
199
200        assert_eq!(
201            uri.to_string().as_str(),
202            "https://placeholder.kg/ontology/abc/"
203        );
204        Ok(())
205    }
206}