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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// Copyright (c) 2018-2023, agnos.ai UK Ltd, all rights reserved.
//---------------------------------------------------------------
use {
    crate::{DataType, Literal, RDFStoreError},
    iref::Iri,
    std::str::FromStr,
};

/// An RDF Term is either an IRI, a literal or a blank node.
///
/// See <https://www.w3.org/TR/rdf11-concepts/#section-triples>
#[derive(Debug)]
pub enum Term {
    Iri(Literal),
    Literal(Literal),
    BlankNode(Literal),
}

impl Term {
    pub fn new_iri(iri: &Iri) -> Result<Self, RDFStoreError> {
        Ok(Term::Iri(Literal::from_iri(iri)?))
    }

    pub fn new_iri_from_str(iri_str: &str) -> Result<Self, RDFStoreError> {
        Term::new_iri(&Iri::new(iri_str)?)
    }

    pub fn new_str(str: &str) -> Result<Self, RDFStoreError> {
        Ok(Term::Literal(Literal::from_str(str)?))
    }

    pub fn new_blank_node(str: &str) -> Result<Self, RDFStoreError> {
        Ok(Term::BlankNode(
            Literal::from_type_and_buffer(DataType::BlankNode, str, None)?.unwrap(),
        ))
    }

    /// Display a [`Term`] in human readable format.
    ///
    /// ```rust
    /// use {iref::Iri, rdf_store_rs::Term};
    ///
    /// let term = Term::new_iri(&Iri::new("https://whatever.url").unwrap()).unwrap();
    /// let turtle = format!("{}", term.display_turtle());
    ///
    /// assert_eq!(turtle, "<https://whatever.url>");
    /// ```
    pub fn display_turtle<'a, 'b>(&'a self) -> impl std::fmt::Display + 'a + 'b
    where 'a: 'b {
        struct TurtleTerm<'b>(&'b Term);
        impl<'b> std::fmt::Display for TurtleTerm<'b> {
            fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
                let value = match self.0 {
                    Term::Iri(value) => value,
                    Term::Literal(value) => value,
                    Term::BlankNode(value) => value,
                };
                value.display_turtle().fmt(f)
            }
        }
        TurtleTerm(self)
    }
}

impl FromStr for Term {
    type Err = RDFStoreError;

    fn from_str(str: &str) -> Result<Self, Self::Err> { Term::new_str(str) }
}

impl From<Literal> for Term {
    fn from(value: Literal) -> Self { value.as_term() }
}

#[cfg(test)]
mod tests {
    use {
        crate::{RDFStoreError, Term},
        iref::Iri,
    };

    #[test_log::test]
    fn test_term_01() {
        let term = Term::new_iri(&Iri::new("https://whatever.url").unwrap()).unwrap();

        let turtle = format!("{}", term.display_turtle());

        assert_eq!(turtle, "<https://whatever.url>");
    }

    #[test_log::test]
    fn test_term_02() {
        let term = Term::new_iri(&Iri::new("unknown-protocol://whatever.url").unwrap()).unwrap();

        let turtle = format!("{}", term.display_turtle());

        assert_eq!(turtle, "<unknown-protocol://whatever.url>");
    }

    #[test_log::test]
    fn test_term_03() {
        // At the moment, we're even accepting wrongly formatted IRIs, we may want to
        // validate each IRI
        let term = Term::new_iri(&Iri::new("https:/x/whatever.url").unwrap()).unwrap();

        let turtle = format!("{}", term.display_turtle());

        assert_eq!(turtle, "<https:/x/whatever.url>");
    }

    #[test_log::test]
    fn test_term_04() {
        let term = Term::new_str("some string").unwrap();

        let turtle = format!("{}", term.display_turtle());

        assert_eq!(turtle, "\"some string\"");
    }

    #[test_log::test]
    fn test_term_05() -> Result<(), RDFStoreError> {
        let term: Term = "some string".parse()?;

        let turtle = format!("{}", term.display_turtle());

        assert_eq!(turtle, "\"some string\"");

        Ok(())
    }

    #[test_log::test]
    fn test_term_06() -> Result<(), RDFStoreError> {
        let term: Term = "\"some string\"^^xsd:string".parse()?;

        let turtle = format!("{}", term.display_turtle());

        assert_eq!(turtle, "\"\"some string\"^^xsd:string\""); // TODO: This is incorrect, recognise the XSD data type suffix and process it

        Ok(())
    }
}