Skip to main content

ld_core/
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//! A value is described as a set of RDF quads: [`LinkedDataSubject`] says
6//! which properties a subject carries, [`LinkedDataPredicateObjects`] which
7//! objects a predicate points at, and [`LinkedDataGraph`] which graph they
8//! belong to. The derive macros implement all of them from `#[ld(...)]`
9//! attributes.
10//!
11//! # Example
12//!
13//! ```
14//! use iri_rs::IriBuf;
15//! use ld_core::rdfx::{RdfDisplay, generator};
16//! use ld_core::{Deserialize, Serialize, to_quads};
17//!
18//! #[derive(Serialize, Deserialize)]
19//! #[ld(prefix("ex" = "http://example.org/"))]
20//! struct Foo {
21//!     #[ld(id)]
22//!     id: IriBuf,
23//!
24//!     #[ld("ex:name")]
25//!     name: String,
26//!
27//!     #[ld("ex:email")]
28//!     email: String,
29//! }
30//!
31//! let value = Foo {
32//!     id: IriBuf::new("http://example.org/JohnSmith".to_owned()).unwrap(),
33//!     name: "John Smith".to_owned(),
34//!     email: "john.smith@example.org".to_owned(),
35//! };
36//!
37//! let quads = to_quads(generator::Blank::new(), &value).unwrap();
38//! let output: Vec<String> = quads.iter().map(|q| format!("{} .", q.rdf_display())).collect();
39//!
40//! assert!(output.contains(
41//!     &r#"<http://example.org/JohnSmith> <http://example.org/name> "John Smith" ."#.to_owned()
42//! ));
43//! ```
44//!
45//! # Feature flags
46//!
47//! - `derive` (default) — the [`Serialize`] and [`Deserialize`] derive macros.
48//! - `serde` (default) — `serde` interop, including the [`json_literal!`]
49//!   macro that maps a `serde` type onto an `rdf:JSON` literal.
50use derive_more::Debug;
51use derive_where::derive_where;
52use iri_rs::{Iri, IriBuf};
53#[cfg(feature = "derive")]
54pub use ld_core_derive::{Deserialize, Serialize};
55use rdfx::{
56	Interpretation, dataset::PatternMatchingDataset, interpretation::ReverseInterpretation,
57};
58
59#[doc(hidden)]
60pub use iri_rs;
61
62#[doc(hidden)]
63pub use rdfx;
64
65#[doc(hidden)]
66pub use xsd_rs;
67
68#[doc(hidden)]
69pub use jstrict;
70
71mod anonymous;
72mod datatypes;
73mod graph;
74mod r#impl;
75mod macros;
76mod predicate;
77mod quads;
78mod rdf;
79mod reference;
80mod resource;
81mod subject;
82
83pub use anonymous::*;
84pub use graph::*;
85pub use predicate::*;
86pub use quads::{
87	IntoQuadsError, to_interpreted_graph_quads, to_interpreted_quads, to_interpreted_subject_quads,
88	to_lexical_quads, to_lexical_quads_with, to_lexical_subject_quads,
89	to_lexical_subject_quads_with, to_quads, to_quads_with,
90};
91pub use rdf::*;
92pub use reference::*;
93pub use resource::*;
94pub use subject::*;
95
96#[derive(Debug, thiserror::Error)]
97/// Error raised while deserializing a value from Linked Data.
98pub enum FromLinkedDataError {
99	/// Resource has no IRI representation.
100	#[error("expected IRI")]
101	ExpectedIri(ContextIris),
102
103	#[error("unsupported IRI `{found}`")]
104	/// Resource is identified by an IRI the type does not accept.
105	UnsupportedIri {
106		/// Error context.
107		context: ContextIris,
108
109		/// Unsupported IRI.
110		found: IriBuf,
111
112		/// Optional hint listing the supported IRIs.
113		supported: Option<Vec<IriBuf>>,
114	},
115
116	/// Resource has no literal representation.
117	#[error("expected literal")]
118	ExpectedLiteral(ContextIris),
119
120	/// Resource has literal representations, but none of the expected type.
121	///
122	/// The type IRIs are boxed: this is the only variant carrying two of them,
123	/// and inline they would make it the largest variant by a wide margin, which
124	/// sets the size of the whole enum.
125	#[error("literal type mismatch")]
126	LiteralTypeMismatch {
127		/// Error context.
128		context: ContextIris,
129		/// Expected literal type, if the type accepts only one.
130		expected: Option<Box<IriBuf>>,
131		/// Literal type actually found.
132		found: Box<IriBuf>,
133	},
134
135	/// Resource has a literal representation of the correct type, but the
136	/// lexical value could not be successfully parsed.
137	#[error("invalid literal")]
138	InvalidLiteral(ContextIris),
139
140	/// Missing required value.
141	#[error("missing required value")]
142	MissingRequiredValue(ContextIris),
143
144	/// Too many values.
145	#[error("too many values")]
146	TooManyValues(ContextIris),
147
148	/// Generic error for invalid subjects.
149	#[error("invalid subject")]
150	InvalidSubject {
151		/// Error context.
152		context: ContextIris,
153		/// Subject IRI, if it has one.
154		subject: Option<IriBuf>,
155	},
156}
157
158impl FromLinkedDataError {
159	/// Returns the context in which this error was raised.
160	pub fn context(&self) -> &ContextIris {
161		match self {
162			Self::ExpectedIri(c) => c,
163			Self::UnsupportedIri { context, .. } => context,
164			Self::ExpectedLiteral(c) => c,
165			Self::LiteralTypeMismatch { context, .. } => context,
166			Self::InvalidLiteral(c) => c,
167			Self::MissingRequiredValue(c) => c,
168			Self::TooManyValues(c) => c,
169			Self::InvalidSubject { context, .. } => context,
170		}
171	}
172}
173
174/// Linked-Data type.
175///
176/// A Linked-Data type represents an RDF dataset which can be visited using the
177/// [`visit`](Self::visit) method.
178pub trait LinkedData<I: Interpretation = ()> {
179	/// Visit the RDF dataset represented by this type.
180	fn visit<S>(&self, visitor: S) -> Result<S::Ok, S::Error>
181	where
182		S: Visitor<I>;
183}
184
185impl<I: Interpretation, T: ?Sized + LinkedData<I>> LinkedData<I> for &T {
186	fn visit<S>(&self, visitor: S) -> Result<S::Ok, S::Error>
187	where
188		S: Visitor<I>,
189	{
190		T::visit(self, visitor)
191	}
192}
193
194impl<I: Interpretation, T: ?Sized + LinkedData<I>> LinkedData<I> for Box<T> {
195	fn visit<S>(&self, visitor: S) -> Result<S::Ok, S::Error>
196	where
197		S: Visitor<I>,
198	{
199		T::visit(self, visitor)
200	}
201}
202
203impl<I: Interpretation> LinkedData<I> for IriBuf {
204	fn visit<S>(&self, visitor: S) -> Result<S::Ok, S::Error>
205	where
206		S: Visitor<I>,
207	{
208		visitor.end()
209	}
210}
211
212/// RDF dataset visitor.
213pub trait Visitor<I: Interpretation = ()> {
214	/// Type of the value returned by the visitor when the dataset has been
215	/// entirely visited.
216	type Ok;
217
218	/// Error type.
219	type Error;
220
221	/// Visits the default graph of the dataset.
222	fn default_graph<T>(&mut self, value: &T) -> Result<(), Self::Error>
223	where
224		T: ?Sized + LinkedDataGraph<I>;
225
226	/// Visits a named graph of the dataset.
227	fn named_graph<T>(&mut self, value: &T) -> Result<(), Self::Error>
228	where
229		T: ?Sized + LinkedDataResource<I> + LinkedDataGraph<I>;
230
231	/// Ends the dataset visit.
232	fn end(self) -> Result<Self::Ok, Self::Error>;
233}
234
235/// Any mutable reference to a visitor is itself a visitor.
236impl<I: Interpretation, S: Visitor<I>> Visitor<I> for &mut S {
237	type Ok = ();
238	type Error = S::Error;
239
240	fn default_graph<T>(&mut self, value: &T) -> Result<(), Self::Error>
241	where
242		T: ?Sized + LinkedDataGraph<I>,
243	{
244		S::default_graph(self, value)
245	}
246
247	fn named_graph<T>(&mut self, value: &T) -> Result<(), Self::Error>
248	where
249		T: ?Sized + LinkedDataResource<I> + LinkedDataGraph<I>,
250	{
251		S::named_graph(self, value)
252	}
253
254	fn end(self) -> Result<Self::Ok, Self::Error> {
255		Ok(())
256	}
257}
258
259#[derive(Debug)]
260#[debug(bound(I::Resource: core::fmt::Debug))]
261#[derive_where(Clone, Copy)]
262/// Reference to a resource, either interpreted or lexical.
263pub enum ResourceOrIriRef<'a, I: Interpretation> {
264	/// A resource of the interpretation.
265	Resource(&'a I::Resource),
266	/// An IRI, outside of any interpretation.
267	Iri(Iri<&'a str>),
268	/// An anonymous resource, with no identifier.
269	Anonymous,
270}
271
272impl<'a, I: Interpretation> ResourceOrIriRef<'a, I> {
273	/// Resolves this reference into an IRI, if it has one.
274	pub fn into_iri(self, interpretation: &I) -> Option<IriBuf>
275	where
276		I: ReverseInterpretation,
277	{
278		match self {
279			Self::Resource(r) => interpretation.iris_of(r).next().map(|i| i.into_owned()),
280			Self::Iri(i) => Some(i.into()),
281			Self::Anonymous => None,
282		}
283	}
284}
285
286#[derive(Debug)]
287#[debug(bound(I::Resource: core::fmt::Debug))]
288#[derive_where(Clone, Copy)]
289/// Position a value occupies while being visited, used to report errors.
290#[derive(Default)]
291pub enum Context<'a, I: Interpretation> {
292	/// The value is a subject.
293	#[default]
294	Subject,
295	/// The value is a predicate of the given subject.
296	Predicate {
297		/// Subject the predicate belongs to.
298		subject: ResourceOrIriRef<'a, I>,
299	},
300	/// The value is an object of the given subject and predicate.
301	Object {
302		/// Subject the object belongs to.
303		subject: ResourceOrIriRef<'a, I>,
304		/// Predicate the object belongs to.
305		predicate: ResourceOrIriRef<'a, I>,
306	},
307}
308
309impl<'a, I: Interpretation> Context<'a, I> {
310	/// Moves this context into the predicate position of `subject`.
311	pub fn with_subject(self, subject: &'a I::Resource) -> Self {
312		Self::Predicate {
313			subject: ResourceOrIriRef::Resource(subject),
314		}
315	}
316
317	/// Moves this context into the object position of `predicate`.
318	pub fn with_predicate(self, predicate: &'a I::Resource) -> Self {
319		match self {
320			Self::Predicate { subject } => Self::Object {
321				subject,
322				predicate: ResourceOrIriRef::Resource(predicate),
323			},
324			_ => Self::Subject,
325		}
326	}
327
328	/// Moves this context into the object position of the `predicate` IRI.
329	pub fn with_predicate_iri(self, predicate: Iri<&'a str>) -> Self {
330		match self {
331			Self::Predicate { subject } => Self::Object {
332				subject,
333				predicate: ResourceOrIriRef::Iri(predicate),
334			},
335			_ => Self::Subject,
336		}
337	}
338
339	/// Moves this context into the object position of an anonymous predicate.
340	pub fn with_anonymous_predicate(self) -> Self {
341		match self {
342			Self::Predicate { subject } => Self::Object {
343				subject,
344				predicate: ResourceOrIriRef::Anonymous,
345			},
346			_ => Self::Subject,
347		}
348	}
349
350	/// Resolves the resources of this context into IRIs.
351	pub fn into_iris(self, interpretation: &I) -> ContextIris
352	where
353		I: ReverseInterpretation,
354	{
355		match self {
356			Self::Subject => ContextIris::Subject,
357			Self::Predicate { subject } => ContextIris::Predicate {
358				subject: subject.into_iri(interpretation).map(Box::new),
359			},
360			Self::Object { subject, predicate } => ContextIris::Object {
361				subject: subject.into_iri(interpretation).map(Box::new),
362				predicate: predicate.into_iri(interpretation).map(Box::new),
363			},
364		}
365	}
366}
367
368#[derive(Debug, Clone)]
369/// Error context with its resources resolved into IRIs.
370///
371/// The IRIs are boxed because this type is carried by every
372/// [`FromLinkedDataError`] variant, and therefore sits in the `Err` slot of
373/// every deserialization `Result`. An inline [`IriBuf`] is 56 bytes, which
374/// would make the error — and so every `Result` returned on the success path
375/// too — several times larger than the values being deserialized.
376pub enum ContextIris {
377	/// The value is a subject.
378	Subject,
379	/// The value is a predicate of the given subject.
380	Predicate {
381		/// Subject IRI, if it has one.
382		subject: Option<Box<IriBuf>>,
383	},
384	/// The value is an object of the given subject and predicate.
385	Object {
386		/// Subject IRI, if it has one.
387		subject: Option<Box<IriBuf>>,
388		/// Predicate IRI, if it has one.
389		predicate: Option<Box<IriBuf>>,
390	},
391}
392
393/// Type that can be deserialized from an RDF dataset.
394pub trait LinkedDataDeserialize<I: Interpretation>: Sized
395where
396	I::Resource: rdfx::Resource,
397{
398	/// Deserializes a value from `dataset`, reporting errors against
399	/// `context`.
400	fn deserialize_dataset_in(
401		interpretation: &I,
402		dataset: &(
403		     impl rdfx::dataset::TraversableDataset<Subject = I::Resource>
404		     + PatternMatchingDataset<Subject = I::Resource>
405		 ),
406		context: Context<I>,
407	) -> Result<Self, FromLinkedDataError>;
408
409	/// Deserializes a value from `dataset`.
410	fn deserialize_dataset(
411		interpretation: &I,
412		dataset: &(
413		     impl rdfx::dataset::TraversableDataset<Subject = I::Resource>
414		     + PatternMatchingDataset<Subject = I::Resource>
415		 ),
416	) -> Result<Self, FromLinkedDataError> {
417		Self::deserialize_dataset_in(interpretation, dataset, Context::default())
418	}
419}