Skip to main content

linked_data_next/
lib.rs

1//! This library provides primitive traits to serialize and deserialize
2//! Linked-Data types. It is shipped with derive macros (using the `derive`
3//! feature) that can automatically implement those primitives for you.
4//!
5//! # Example
6//!
7//! ```
8//! use iref::IriBuf;
9//! use static_iref::iri;
10//!
11//! #[derive(linked_data_next::Serialize, linked_data_next::Deserialize)]
12//! #[ld(prefix("ex" = "http://example.org/"))]
13//! struct Foo {
14//!   #[ld(id)]
15//!   id: IriBuf,
16//!
17//!   #[ld("ex:name")]
18//!   name: String,
19//!
20//!   #[ld("ex:email")]
21//!   email: String
22//! }
23//!
24//! let value = Foo {
25//!   id: iri!("http://example.org/JohnSmith").to_owned(),
26//!   name: "John Smith".to_owned(),
27//!   email: "john.smith@example.org".to_owned()
28//! };
29//!
30//! let quads = linked_data_next::to_quads(rdf_types::generator::Blank::new(), &value)
31//!   .expect("RDF serialization failed");
32//!
33//! for quad in quads {
34//!   use rdf_types::RdfDisplay;
35//!   println!("{} .", quad.rdf_display())
36//! }
37//! ```
38//!
39//! This should print the following:
40//! ```text
41//! <http://example.org/JohnSmith> <http://example.org/name> "John Smith" .
42//! <http://example.org/JohnSmith> <http://example.org/email> "john.smith@example.org" .
43//! ```
44use educe::Educe;
45use iref::{Iri, IriBuf};
46#[cfg(feature = "derive")]
47pub use linked_data_next_derive::{Deserialize, Serialize};
48use rdf_types::{
49	Interpretation, Vocabulary,
50	dataset::{PatternMatchingDataset, TraversableDataset},
51	interpretation::ReverseIriInterpretation,
52	vocabulary::IriVocabulary,
53};
54
55#[doc(hidden)]
56pub use iref;
57
58#[doc(hidden)]
59pub use rdf_types;
60
61#[doc(hidden)]
62pub use xsd_types;
63
64#[doc(hidden)]
65pub use json_syntax;
66
67mod anonymous;
68mod datatypes;
69mod graph;
70mod r#impl;
71mod macros;
72mod predicate;
73mod quads;
74mod rdf;
75mod rdf_list;
76mod reference;
77mod resource;
78mod subject;
79
80pub use anonymous::*;
81pub use graph::*;
82pub use predicate::*;
83pub use quads::{
84	IntoQuadsError, to_interpreted_graph_quads, to_interpreted_quads, to_interpreted_subject_quads,
85	to_lexical_quads, to_lexical_quads_with, to_lexical_subject_quads,
86	to_lexical_subject_quads_with, to_quads, to_quads_with,
87};
88pub use rdf::*;
89pub use reference::*;
90pub use resource::*;
91pub use subject::*;
92
93#[derive(Debug, thiserror::Error)]
94pub enum FromLinkedDataError {
95	/// Resource has no IRI representation.
96	#[error("expected IRI")]
97	ExpectedIri(ContextIris),
98
99	#[error("unsupported IRI `{found}`")]
100	UnsupportedIri {
101		/// Error context.
102		context: ContextIris,
103
104		/// Unsupported IRI.
105		found: IriBuf,
106
107		/// Optional hint listing the supported IRIs.
108		supported: Option<Vec<IriBuf>>,
109	},
110
111	/// Resource has no literal representation.
112	#[error("expected literal")]
113	ExpectedLiteral(ContextIris),
114
115	/// Resource has literal representations, but none of the expected type.
116	#[error("literal type mismatch")]
117	LiteralTypeMismatch {
118		context: ContextIris,
119		expected: Option<IriBuf>,
120		found: IriBuf,
121	},
122
123	/// Resource has a literal representation of the correct type, but the
124	/// lexical value could not be successfully parsed.
125	#[error("invalid literal")]
126	InvalidLiteral(ContextIris),
127
128	/// Missing required value.
129	#[error("missing required value")]
130	MissingRequiredValue(ContextIris),
131
132	/// Too many values.
133	#[error("too many values")]
134	TooManyValues(ContextIris),
135
136	/// Generic error for invalid subjects.
137	#[error("invalid subject")]
138	InvalidSubject {
139		context: ContextIris,
140		subject: Option<IriBuf>,
141	},
142}
143
144impl FromLinkedDataError {
145	pub fn context(&self) -> &ContextIris {
146		match self {
147			Self::ExpectedIri(c) => c,
148			Self::UnsupportedIri { context, .. } => context,
149			Self::ExpectedLiteral(c) => c,
150			Self::LiteralTypeMismatch { context, .. } => context,
151			Self::InvalidLiteral(c) => c,
152			Self::MissingRequiredValue(c) => c,
153			Self::TooManyValues(c) => c,
154			Self::InvalidSubject { context, .. } => context,
155		}
156	}
157}
158
159/// Linked-Data type.
160///
161/// A Linked-Data type represents an RDF dataset which can be visited using the
162/// [`visit`](Self::visit) method.
163pub trait LinkedData<I: Interpretation = (), V: Vocabulary = ()> {
164	/// Visit the RDF dataset represented by this type.
165	fn visit<S>(&self, visitor: S) -> Result<S::Ok, S::Error>
166	where
167		S: Visitor<I, V>;
168}
169
170impl<I: Interpretation, V: Vocabulary, T: ?Sized + LinkedData<I, V>> LinkedData<I, V> for &T {
171	fn visit<S>(&self, visitor: S) -> Result<S::Ok, S::Error>
172	where
173		S: Visitor<I, V>,
174	{
175		T::visit(self, visitor)
176	}
177}
178
179impl<I: Interpretation, V: Vocabulary, T: ?Sized + LinkedData<I, V>> LinkedData<I, V> for Box<T> {
180	fn visit<S>(&self, visitor: S) -> Result<S::Ok, S::Error>
181	where
182		S: Visitor<I, V>,
183	{
184		T::visit(self, visitor)
185	}
186}
187
188impl<I: Interpretation, V: Vocabulary> LinkedData<I, V> for Iri {
189	fn visit<S>(&self, visitor: S) -> Result<S::Ok, S::Error>
190	where
191		S: Visitor<I, V>,
192	{
193		visitor.end()
194	}
195}
196
197/// RDF dataset visitor.
198pub trait Visitor<I: Interpretation = (), V: Vocabulary = ()> {
199	/// Type of the value returned by the visitor when the dataset has been
200	/// entirely visited.
201	type Ok;
202
203	/// Error type.
204	type Error;
205
206	/// Visits the default graph of the dataset.
207	fn default_graph<T>(&mut self, value: &T) -> Result<(), Self::Error>
208	where
209		T: ?Sized + LinkedDataGraph<I, V>;
210
211	/// Visits a named graph of the dataset.
212	fn named_graph<T>(&mut self, value: &T) -> Result<(), Self::Error>
213	where
214		T: ?Sized + LinkedDataResource<I, V> + LinkedDataGraph<I, V>;
215
216	/// Ends the dataset visit.
217	fn end(self) -> Result<Self::Ok, Self::Error>;
218}
219
220/// Any mutable reference to a visitor is itself a visitor.
221impl<I: Interpretation, V: Vocabulary, S: Visitor<I, V>> Visitor<I, V> for &mut S {
222	type Ok = ();
223	type Error = S::Error;
224
225	fn default_graph<T>(&mut self, value: &T) -> Result<(), Self::Error>
226	where
227		T: ?Sized + LinkedDataGraph<I, V>,
228	{
229		S::default_graph(self, value)
230	}
231
232	fn named_graph<T>(&mut self, value: &T) -> Result<(), Self::Error>
233	where
234		T: ?Sized + LinkedDataResource<I, V> + LinkedDataGraph<I, V>,
235	{
236		S::named_graph(self, value)
237	}
238
239	fn end(self) -> Result<Self::Ok, Self::Error> {
240		Ok(())
241	}
242}
243
244#[derive(Educe)]
245#[educe(Debug(bound = "I::Resource: core::fmt::Debug"), Clone, Copy)]
246pub enum ResourceOrIriRef<'a, I: Interpretation> {
247	Resource(&'a I::Resource),
248	Iri(&'a Iri),
249	Anonymous,
250}
251
252impl<I: Interpretation> ResourceOrIriRef<'_, I> {
253	pub fn into_iri<V>(self, vocabulary: &V, interpretation: &I) -> Option<IriBuf>
254	where
255		V: IriVocabulary,
256		I: ReverseIriInterpretation<Iri = V::Iri>,
257	{
258		match self {
259			Self::Resource(r) => interpretation
260				.iris_of(r)
261				.next()
262				.map(|i| vocabulary.iri(i).unwrap().to_owned()),
263			Self::Iri(i) => Some(i.to_owned()),
264			Self::Anonymous => None,
265		}
266	}
267}
268
269#[derive(Default, Educe)]
270#[educe(Debug(bound = "I::Resource: core::fmt::Debug"), Clone, Copy)]
271pub enum Context<'a, I: Interpretation> {
272	#[default]
273	Subject,
274	Predicate {
275		subject: ResourceOrIriRef<'a, I>,
276	},
277	Object {
278		subject: ResourceOrIriRef<'a, I>,
279		predicate: ResourceOrIriRef<'a, I>,
280	},
281}
282
283impl<'a, I: Interpretation> Context<'a, I> {
284	pub fn with_subject(self, subject: &'a I::Resource) -> Self {
285		Self::Predicate {
286			subject: ResourceOrIriRef::Resource(subject),
287		}
288	}
289
290	pub fn with_predicate(self, predicate: &'a I::Resource) -> Self {
291		match self {
292			Self::Predicate { subject } => Self::Object {
293				subject,
294				predicate: ResourceOrIriRef::Resource(predicate),
295			},
296			_ => Self::Subject,
297		}
298	}
299
300	pub fn with_predicate_iri(self, predicate: &'a Iri) -> Self {
301		match self {
302			Self::Predicate { subject } => Self::Object {
303				subject,
304				predicate: ResourceOrIriRef::Iri(predicate),
305			},
306			_ => Self::Subject,
307		}
308	}
309
310	pub fn with_anonymous_predicate(self) -> Self {
311		match self {
312			Self::Predicate { subject } => Self::Object {
313				subject,
314				predicate: ResourceOrIriRef::Anonymous,
315			},
316			_ => Self::Subject,
317		}
318	}
319
320	pub fn into_iris<V>(self, vocabulary: &V, interpretation: &I) -> ContextIris
321	where
322		V: IriVocabulary,
323		I: ReverseIriInterpretation<Iri = V::Iri>,
324	{
325		match self {
326			Self::Subject => ContextIris::Subject,
327			Self::Predicate { subject } => ContextIris::Predicate {
328				subject: subject.into_iri(vocabulary, interpretation),
329			},
330			Self::Object { subject, predicate } => ContextIris::Object {
331				subject: subject.into_iri(vocabulary, interpretation),
332				predicate: predicate.into_iri(vocabulary, interpretation),
333			},
334		}
335	}
336}
337
338#[derive(Debug, Clone)]
339pub enum ContextIris {
340	Subject,
341	Predicate {
342		subject: Option<IriBuf>,
343	},
344	Object {
345		subject: Option<IriBuf>,
346		predicate: Option<IriBuf>,
347	},
348}
349
350pub trait LinkedDataDeserialize<V: Vocabulary = (), I: Interpretation = ()>: Sized {
351	fn deserialize_dataset_in(
352		vocabulary: &V,
353		interpretation: &I,
354		dataset: &(impl TraversableDataset<Resource = I::Resource> + PatternMatchingDataset),
355		context: Context<I>,
356	) -> Result<Self, FromLinkedDataError>;
357
358	fn deserialize_dataset(
359		vocabulary: &V,
360		interpretation: &I,
361		dataset: &(impl TraversableDataset<Resource = I::Resource> + PatternMatchingDataset),
362	) -> Result<Self, FromLinkedDataError> {
363		Self::deserialize_dataset_in(vocabulary, interpretation, dataset, Context::default())
364	}
365}