json_ld_expansion/
lib.rs

1//! This library implements the [JSON-LD expansion algorithm](https://www.w3.org/TR/json-ld-api/#expansion-algorithms)
2//! for the [`json-ld` crate](https://crates.io/crates/json-ld).
3//!
4//! # Usage
5//!
6//! The expansion algorithm is provided by the [`Expand`] trait.
7use std::hash::Hash;
8
9use json_ld_context_processing::Context;
10use json_ld_core::{Environment, ExpandedDocument, Loader, RemoteDocument};
11use json_syntax::Value;
12use rdf_types::{vocabulary, vocabulary::BlankIdVocabulary, BlankIdBuf, VocabularyMut};
13
14mod array;
15mod document;
16mod element;
17mod error;
18mod expanded;
19mod literal;
20mod node;
21mod options;
22mod value;
23mod warning;
24
25pub use error::*;
26pub use expanded::*;
27pub use options::*;
28pub use warning::*;
29
30pub(crate) use array::*;
31pub(crate) use document::filter_top_level_item;
32pub(crate) use element::*;
33pub(crate) use json_ld_context_processing::algorithm::expand_iri_simple as expand_iri;
34pub(crate) use literal::*;
35pub(crate) use node::*;
36pub(crate) use value::*;
37
38/// Result of the document expansion.
39pub type ExpansionResult<T, B> = Result<ExpandedDocument<T, B>, Error>;
40
41/// Handler for the possible warnings emitted during the expansion
42/// of a JSON-LD document.
43pub trait WarningHandler<N: BlankIdVocabulary>:
44	json_ld_core::warning::Handler<N, Warning<N::BlankId>>
45{
46}
47
48impl<N: BlankIdVocabulary, H> WarningHandler<N> for H where
49	H: json_ld_core::warning::Handler<N, Warning<N::BlankId>>
50{
51}
52
53/// Document expansion.
54///
55/// This trait provides the functions necessary to expand
56/// a JSON-LD document into an [`ExpandedDocument`].
57/// It is implemented by [`json_syntax::Value`] representing
58/// a JSON object and [`RemoteDocument`].
59///
60/// # Example
61///
62/// ```
63/// # mod json_ld { pub use json_ld_syntax as syntax; pub use json_ld_core::{RemoteDocument, ExpandedDocument, NoLoader}; pub use json_ld_expansion::Expand; };
64///
65/// use iref::IriBuf;
66/// use rdf_types::BlankIdBuf;
67/// use static_iref::iri;
68/// use json_ld::{syntax::Parse, RemoteDocument, Expand};
69///
70/// # #[async_std::test]
71/// # async fn example() {
72/// // Parse the input JSON(-LD) document.
73/// let (json, _) = json_ld::syntax::Value::parse_str(
74///   r##"
75///   {
76///     "@graph": [
77///       {
78///         "http://example.org/vocab#a": {
79///           "@graph": [
80///             {
81///               "http://example.org/vocab#b": "Chapter One"
82///             }
83///           ]
84///         }
85///       }
86///     ]
87///   }
88///   "##)
89/// .unwrap();
90///
91/// // Prepare a dummy document loader using [`json_ld::NoLoader`],
92/// // since we won't need to load any remote document while expanding this one.
93/// let mut loader = json_ld::NoLoader;
94///
95/// // The `expand` method returns an [`json_ld::ExpandedDocument`].
96/// json
97///     .expand(&mut loader)
98///     .await
99///     .unwrap();
100/// # }
101/// ```
102pub trait Expand<Iri> {
103	/// Returns the default base URL passed to the expansion algorithm
104	/// and used to initialize the default empty context when calling
105	/// [`Expand::expand`] or [`Expand::expand_with`].
106	fn default_base_url(&self) -> Option<&Iri>;
107
108	/// Expand the document with full options.
109	///
110	/// The `vocabulary` is used to interpret identifiers.
111	/// The `context` is used as initial context.
112	/// The `base_url` is the initial base URL used to resolve relative IRI references.
113	/// The given `loader` is used to load remote documents (such as contexts)
114	/// imported by the input and required during expansion.
115	/// The `options` are used to tweak the expansion algorithm.
116	/// The `warning_handler` is called each time a warning is emitted during expansion.
117	#[allow(async_fn_in_trait)]
118	async fn expand_full<N, L, W>(
119		&self,
120		vocabulary: &mut N,
121		context: Context<Iri, N::BlankId>,
122		base_url: Option<&N::Iri>,
123		loader: &L,
124		options: Options,
125		warnings_handler: W,
126	) -> ExpansionResult<N::Iri, N::BlankId>
127	where
128		N: VocabularyMut<Iri = Iri>,
129		Iri: Clone + Eq + Hash,
130		N::BlankId: Clone + Eq + Hash,
131		L: Loader,
132		W: WarningHandler<N>;
133
134	/// Expand the input JSON-LD document with the given `vocabulary`
135	/// to interpret identifiers.
136	///
137	/// The given `loader` is used to load remote documents (such as contexts)
138	/// imported by the input and required during expansion.
139	/// The expansion algorithm is called with an empty initial context with
140	/// a base URL given by [`Expand::default_base_url`].
141	#[allow(async_fn_in_trait)]
142	async fn expand_with<'a, N, L>(
143		&'a self,
144		vocabulary: &'a mut N,
145		loader: &'a L,
146	) -> ExpansionResult<Iri, N::BlankId>
147	where
148		N: VocabularyMut<Iri = Iri>,
149		Iri: 'a + Clone + Eq + Hash,
150		N::BlankId: 'a + Clone + Eq + Hash,
151		L: Loader,
152	{
153		self.expand_full(
154			vocabulary,
155			Context::<N::Iri, N::BlankId>::new(self.default_base_url().cloned()),
156			self.default_base_url(),
157			loader,
158			Options::default(),
159			(),
160		)
161		.await
162	}
163
164	/// Expand the input JSON-LD document.
165	///
166	/// The given `loader` is used to load remote documents (such as contexts)
167	/// imported by the input and required during expansion.
168	/// The expansion algorithm is called with an empty initial context with
169	/// a base URL given by [`Expand::default_base_url`].
170	#[allow(async_fn_in_trait)]
171	async fn expand<'a, L>(&'a self, loader: &'a L) -> ExpansionResult<Iri, BlankIdBuf>
172	where
173		(): VocabularyMut<Iri = Iri>,
174		Iri: 'a + Clone + Eq + Hash,
175		L: Loader,
176	{
177		self.expand_with(vocabulary::no_vocabulary_mut(), loader)
178			.await
179	}
180}
181
182/// Value expansion without base URL.
183impl<Iri> Expand<Iri> for Value {
184	fn default_base_url(&self) -> Option<&Iri> {
185		None
186	}
187
188	async fn expand_full<N, L, W>(
189		&self,
190		vocabulary: &mut N,
191		context: Context<Iri, N::BlankId>,
192		base_url: Option<&Iri>,
193		loader: &L,
194		options: Options,
195		mut warnings_handler: W,
196	) -> ExpansionResult<Iri, N::BlankId>
197	where
198		N: VocabularyMut<Iri = Iri>,
199		Iri: Clone + Eq + Hash,
200		N::BlankId: Clone + Eq + Hash,
201		L: Loader,
202		W: WarningHandler<N>,
203	{
204		document::expand(
205			Environment {
206				vocabulary,
207				loader,
208				warnings: &mut warnings_handler,
209			},
210			self,
211			context,
212			base_url,
213			options,
214		)
215		.await
216	}
217}
218
219/// Remote document expansion.
220///
221/// The default base URL given to the expansion algorithm is the URL of
222/// the remote document.
223impl<Iri> Expand<Iri> for RemoteDocument<Iri> {
224	fn default_base_url(&self) -> Option<&Iri> {
225		self.url()
226	}
227
228	async fn expand_full<N, L, W>(
229		&self,
230		vocabulary: &mut N,
231		context: Context<Iri, N::BlankId>,
232		base_url: Option<&Iri>,
233		loader: &L,
234		options: Options,
235		warnings_handler: W,
236	) -> ExpansionResult<Iri, N::BlankId>
237	where
238		N: VocabularyMut<Iri = Iri>,
239		Iri: Clone + Eq + Hash,
240		N::BlankId: Clone + Eq + Hash,
241		L: Loader,
242		W: WarningHandler<N>,
243	{
244		self.document()
245			.expand_full(
246				vocabulary,
247				context,
248				base_url,
249				loader,
250				options,
251				warnings_handler,
252			)
253			.await
254	}
255}