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}