rio_api/
model.rs

1//! Data structures for [RDF 1.1](https://www.w3.org/TR/rdf11-concepts/) and [RDF-star](https://w3c.github.io/rdf-star/cg-spec/) concepts like IRI, literal or triples.
2
3#[cfg(feature = "generalized")]
4pub use crate::generalized::model::*;
5use std::fmt;
6use std::fmt::Write;
7
8/// An RDF [IRI](https://www.w3.org/TR/rdf11-concepts/#dfn-iri).
9///
10/// The default string formatter is returning an N-Triples, Turtle and SPARQL compatible representation.
11///
12/// ```
13/// use rio_api::model::NamedNode;
14///
15/// assert_eq!(
16///     "<http://example.com/foo>",
17///     NamedNode { iri: "http://example.com/foo" }.to_string()
18/// )
19/// ```
20#[derive(Eq, PartialEq, Ord, PartialOrd, Debug, Clone, Copy, Hash)]
21pub struct NamedNode<'a> {
22    /// The [IRI](https://www.w3.org/TR/rdf11-concepts/#dfn-iri) itself.
23    pub iri: &'a str,
24}
25
26impl<'a> fmt::Display for NamedNode<'a> {
27    #[inline]
28    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
29        write!(f, "<{}>", self.iri)
30    }
31}
32
33/// An RDF [blank node](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node).
34///
35///
36/// The default string formatter is returning an N-Triples, Turtle and SPARQL compatible representation.
37///
38/// ```
39/// use rio_api::model::BlankNode;
40///
41/// assert_eq!(
42///     "_:a1",
43///     BlankNode { id: "a1" }.to_string()
44/// )
45/// ```
46#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
47pub struct BlankNode<'a> {
48    /// The [blank node identifier](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node-identifier).
49    pub id: &'a str,
50}
51
52impl<'a> fmt::Display for BlankNode<'a> {
53    #[inline]
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        write!(f, "_:{}", self.id)
56    }
57}
58
59/// An RDF [literal](https://www.w3.org/TR/rdf11-concepts/#dfn-literal).
60///
61/// The default string formatter is returning an N-Triples, Turtle and SPARQL compatible representation.
62///
63/// The language tags should be lowercased  [as suggested by the RDF specification](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string).
64///
65/// ```
66/// use rio_api::model::NamedNode;
67/// use rio_api::model::Literal;
68///
69/// assert_eq!(
70///     "\"foo\\nbar\"",
71///     Literal::Simple { value: "foo\nbar" }.to_string()
72/// );
73///
74/// assert_eq!(
75///     "\"1999-01-01\"^^<http://www.w3.org/2001/XMLSchema#date>",
76///     Literal::Typed { value: "1999-01-01", datatype: NamedNode {iri: "http://www.w3.org/2001/XMLSchema#date" }}.to_string()
77/// );
78///
79/// assert_eq!(
80///     "\"foo\"@en",
81///     Literal::LanguageTaggedString { value: "foo", language: "en" }.to_string()
82/// );
83/// ```
84#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
85pub enum Literal<'a> {
86    /// A [simple literal](https://www.w3.org/TR/rdf11-concepts/#dfn-simple-literal) without datatype or language form.
87    Simple {
88        /// The [lexical form](https://www.w3.org/TR/rdf11-concepts/#dfn-lexical-form).
89        value: &'a str,
90    },
91    /// A [language-tagged string](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tagged-string)
92    LanguageTaggedString {
93        /// The [lexical form](https://www.w3.org/TR/rdf11-concepts/#dfn-lexical-form).
94        value: &'a str,
95        /// The [language tag](https://www.w3.org/TR/rdf11-concepts/#dfn-language-tag).
96        language: &'a str,
97    },
98    /// A literal with an explicit datatype
99    Typed {
100        /// The [lexical form](https://www.w3.org/TR/rdf11-concepts/#dfn-lexical-form).
101        value: &'a str,
102        /// The [datatype IRI](https://www.w3.org/TR/rdf11-concepts/#dfn-datatype-iri).
103        datatype: NamedNode<'a>,
104    },
105}
106
107impl<'a> fmt::Display for Literal<'a> {
108    #[inline]
109    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110        match self {
111            Literal::Simple { value } => fmt_quoted_str(value, f),
112            Literal::LanguageTaggedString { value, language } => {
113                fmt_quoted_str(value, f)?;
114                write!(f, "@{}", language)
115            }
116            Literal::Typed { value, datatype } => {
117                fmt_quoted_str(value, f)?;
118                write!(f, "^^{}", datatype)
119            }
120        }
121    }
122}
123
124/// A restriction of [Term] that can be used as the [subject of an RDF triple](https://www.w3.org/TR/rdf11-concepts/#dfn-subject).
125///
126/// The default string formatter is returning an N-Triples, Turtle and SPARQL compatible representation.
127#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
128pub enum Subject<'a> {
129    NamedNode(NamedNode<'a>),
130    BlankNode(BlankNode<'a>),
131    /// Rio does support [RDF-star](https://w3c.github.io/rdf-star/cg-spec/2021-07-01.html#dfn-triple), which allows triples to be the subject of other triples.
132    Triple(&'a Triple<'a>),
133}
134
135impl<'a> fmt::Display for Subject<'a> {
136    #[inline]
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        match self {
139            Subject::NamedNode(node) => node.fmt(f),
140            Subject::BlankNode(node) => node.fmt(f),
141            Subject::Triple(triple) => write!(f, "<< {} >>", triple),
142        }
143    }
144}
145
146impl<'a> From<NamedNode<'a>> for Subject<'a> {
147    #[inline]
148    fn from(node: NamedNode<'a>) -> Self {
149        Subject::NamedNode(node)
150    }
151}
152
153impl<'a> From<BlankNode<'a>> for Subject<'a> {
154    #[inline]
155    fn from(node: BlankNode<'a>) -> Self {
156        Subject::BlankNode(node)
157    }
158}
159
160impl<'a> From<&'a Triple<'a>> for Subject<'a> {
161    #[inline]
162    fn from(triple: &'a Triple<'a>) -> Self {
163        Subject::Triple(triple)
164    }
165}
166
167impl<'a> From<GraphName<'a>> for Subject<'a> {
168    #[inline]
169    fn from(node: GraphName<'a>) -> Self {
170        match node {
171            GraphName::BlankNode(node) => Subject::BlankNode(node),
172            GraphName::NamedNode(node) => Subject::NamedNode(node),
173        }
174    }
175}
176
177/// An RDF [term](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-term).
178///
179/// It is the union of [IRIs](https://www.w3.org/TR/rdf11-concepts/#dfn-iri), [blank nodes](https://www.w3.org/TR/rdf11-concepts/#dfn-blank-node) and [literals](https://www.w3.org/TR/rdf11-concepts/#dfn-literal).
180///
181/// The default string formatter is returning an N-Triples, Turtle and SPARQL compatible representation.
182#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
183pub enum Term<'a> {
184    NamedNode(NamedNode<'a>),
185    BlankNode(BlankNode<'a>),
186    Literal(Literal<'a>),
187    /// Rio does support [RDF-star](https://w3c.github.io/rdf-star/cg-spec/2021-07-01.html#dfn-rdf-star-terms), which allows triples to be terms inside of other triples.
188    Triple(&'a Triple<'a>),
189}
190
191impl<'a> fmt::Display for Term<'a> {
192    #[inline]
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        match self {
195            Term::NamedNode(node) => node.fmt(f),
196            Term::BlankNode(node) => node.fmt(f),
197            Term::Literal(literal) => literal.fmt(f),
198            Term::Triple(triple) => write!(f, "<< {} >>", triple),
199        }
200    }
201}
202
203impl<'a> From<NamedNode<'a>> for Term<'a> {
204    #[inline]
205    fn from(node: NamedNode<'a>) -> Self {
206        Term::NamedNode(node)
207    }
208}
209
210impl<'a> From<BlankNode<'a>> for Term<'a> {
211    #[inline]
212    fn from(node: BlankNode<'a>) -> Self {
213        Term::BlankNode(node)
214    }
215}
216
217impl<'a> From<Literal<'a>> for Term<'a> {
218    #[inline]
219    fn from(literal: Literal<'a>) -> Self {
220        Term::Literal(literal)
221    }
222}
223
224impl<'a> From<&'a Triple<'a>> for Term<'a> {
225    #[inline]
226    fn from(triple: &'a Triple<'a>) -> Self {
227        Term::Triple(triple)
228    }
229}
230
231impl<'a> From<Subject<'a>> for Term<'a> {
232    #[inline]
233    fn from(resource: Subject<'a>) -> Self {
234        match resource {
235            Subject::NamedNode(node) => Term::NamedNode(node),
236            Subject::BlankNode(node) => Term::BlankNode(node),
237            Subject::Triple(triple) => Term::Triple(triple),
238        }
239    }
240}
241
242impl<'a> From<GraphName<'a>> for Term<'a> {
243    #[inline]
244    fn from(resource: GraphName<'a>) -> Self {
245        match resource {
246            GraphName::NamedNode(node) => Term::NamedNode(node),
247            GraphName::BlankNode(node) => Term::BlankNode(node),
248        }
249    }
250}
251
252/// A [RDF triple](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-triple).
253///
254/// The default string formatter is returning a N-Triples, Turtle and SPARQL compatible representation.
255///
256/// ```
257/// use rio_api::model::NamedNode;
258/// use rio_api::model::Triple;
259///
260/// assert_eq!(
261///     "<http://example.com/foo> <http://schema.org/sameAs> <http://example.com/foo>",
262///     Triple {
263///         subject: NamedNode { iri: "http://example.com/foo" }.into(),
264///         predicate: NamedNode { iri: "http://schema.org/sameAs" },
265///         object: NamedNode { iri: "http://example.com/foo" }.into(),
266///     }.to_string()
267/// )
268/// ```
269#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
270pub struct Triple<'a> {
271    pub subject: Subject<'a>,
272    pub predicate: NamedNode<'a>,
273    pub object: Term<'a>,
274}
275
276impl<'a> fmt::Display for Triple<'a> {
277    #[inline]
278    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279        write!(f, "{} {} {}", self.subject, self.predicate, self.object)
280    }
281}
282
283/// A restriction of [Term] that can be used in the graph name position.
284///
285/// The default string formatter is returning an N-Triples, Turtle and SPARQL compatible representation.
286#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
287pub enum GraphName<'a> {
288    NamedNode(NamedNode<'a>),
289    BlankNode(BlankNode<'a>),
290}
291
292impl<'a> fmt::Display for GraphName<'a> {
293    #[inline]
294    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
295        match self {
296            GraphName::NamedNode(node) => node.fmt(f),
297            GraphName::BlankNode(node) => node.fmt(f),
298        }
299    }
300}
301
302impl<'a> From<NamedNode<'a>> for GraphName<'a> {
303    #[inline]
304    fn from(node: NamedNode<'a>) -> Self {
305        GraphName::NamedNode(node)
306    }
307}
308
309impl<'a> From<BlankNode<'a>> for GraphName<'a> {
310    #[inline]
311    fn from(node: BlankNode<'a>) -> Self {
312        GraphName::BlankNode(node)
313    }
314}
315
316/// A [RDF triple](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-triple) in a [RDF dataset](https://www.w3.org/TR/rdf11-concepts/#dfn-rdf-dataset).
317///
318/// The default string formatter is returning a N-Quads representation.
319///
320/// ```
321/// use rio_api::model::NamedNode;
322/// use rio_api::model::Quad;
323///
324/// assert_eq!(
325///     "<http://example.com/foo> <http://schema.org/sameAs> <http://example.com/foo> <http://example.com/>",
326///     Quad {
327///         subject: NamedNode { iri: "http://example.com/foo" }.into(),
328///         predicate: NamedNode { iri: "http://schema.org/sameAs" },
329///         object: NamedNode { iri: "http://example.com/foo" }.into(),
330///         graph_name: Some(NamedNode { iri: "http://example.com/" }.into()),
331///     }.to_string()
332/// )
333/// ```
334#[derive(Eq, PartialEq, Debug, Clone, Copy, Hash)]
335pub struct Quad<'a> {
336    pub subject: Subject<'a>,
337    pub predicate: NamedNode<'a>,
338    pub object: Term<'a>,
339    pub graph_name: Option<GraphName<'a>>,
340}
341
342impl<'a> fmt::Display for Quad<'a> {
343    #[inline]
344    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
345        if let Some(graph_name) = self.graph_name {
346            write!(
347                f,
348                "{} {} {} {}",
349                self.subject, self.predicate, self.object, graph_name
350            )
351        } else {
352            write!(f, "{} {} {}", self.subject, self.predicate, self.object)
353        }
354    }
355}
356
357#[inline]
358fn fmt_quoted_str(string: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359    f.write_char('"')?;
360    for c in string.chars() {
361        match c {
362            '\n' => f.write_str("\\n"),
363            '\r' => f.write_str("\\r"),
364            '"' => f.write_str("\\\""),
365            '\\' => f.write_str("\\\\"),
366            c => f.write_char(c),
367        }?;
368    }
369    f.write_char('"')
370}