json_ld_context_processing/
lib.rs

1//! JSON-LD context processing types and algorithms.
2use algorithm::{Action, RejectVocab};
3pub use json_ld_core::{warning, Context, ProcessingMode};
4use json_ld_core::{ExtractContextError, LoadError, Loader};
5use json_ld_syntax::ErrorCode;
6use rdf_types::VocabularyMut;
7use std::{fmt, hash::Hash};
8
9pub mod algorithm;
10mod processed;
11mod stack;
12
13pub use processed::*;
14pub use stack::ProcessingStack;
15
16/// Warnings that can be raised during context processing.
17pub enum Warning {
18	KeywordLikeTerm(String),
19	KeywordLikeValue(String),
20	MalformedIri(String),
21}
22
23impl fmt::Display for Warning {
24	fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25		match self {
26			Self::KeywordLikeTerm(s) => write!(f, "keyword-like term `{s}`"),
27			Self::KeywordLikeValue(s) => write!(f, "keyword-like value `{s}`"),
28			Self::MalformedIri(s) => write!(f, "malformed IRI `{s}`"),
29		}
30	}
31}
32
33impl<N> contextual::DisplayWithContext<N> for Warning {
34	fn fmt_with(&self, _: &N, f: &mut fmt::Formatter) -> fmt::Result {
35		fmt::Display::fmt(self, f)
36	}
37}
38
39pub trait WarningHandler<N>: json_ld_core::warning::Handler<N, Warning> {}
40
41impl<N, H> WarningHandler<N> for H where H: json_ld_core::warning::Handler<N, Warning> {}
42
43/// Errors that can happen during context processing.
44#[derive(Debug, thiserror::Error)]
45pub enum Error {
46	#[error("Invalid context nullification")]
47	InvalidContextNullification,
48
49	#[error("Remote document loading failed")]
50	LoadingDocumentFailed,
51
52	#[error("Processing mode conflict")]
53	ProcessingModeConflict,
54
55	#[error("Invalid `@context` entry")]
56	InvalidContextEntry,
57
58	#[error("Invalid `@import` value")]
59	InvalidImportValue,
60
61	#[error("Invalid remote context")]
62	InvalidRemoteContext,
63
64	#[error("Invalid base IRI")]
65	InvalidBaseIri,
66
67	#[error("Invalid vocabulary mapping")]
68	InvalidVocabMapping,
69
70	#[error("Cyclic IRI mapping")]
71	CyclicIriMapping,
72
73	#[error("Invalid term definition")]
74	InvalidTermDefinition,
75
76	#[error("Keyword redefinition")]
77	KeywordRedefinition,
78
79	#[error("Invalid `@protected` value")]
80	InvalidProtectedValue,
81
82	#[error("Invalid type mapping")]
83	InvalidTypeMapping,
84
85	#[error("Invalid reverse property")]
86	InvalidReverseProperty,
87
88	#[error("Invalid IRI mapping")]
89	InvalidIriMapping,
90
91	#[error("Invalid keyword alias")]
92	InvalidKeywordAlias,
93
94	#[error("Invalid container mapping")]
95	InvalidContainerMapping,
96
97	#[error("Invalid scoped context")]
98	InvalidScopedContext,
99
100	#[error("Protected term redefinition")]
101	ProtectedTermRedefinition,
102
103	#[error(transparent)]
104	ContextLoadingFailed(#[from] LoadError),
105
106	#[error("Unable to extract JSON-LD context: {0}")]
107	ContextExtractionFailed(ExtractContextError),
108
109	#[error("Use of forbidden `@vocab`")]
110	ForbiddenVocab,
111}
112
113impl From<RejectVocab> for Error {
114	fn from(_value: RejectVocab) -> Self {
115		Self::ForbiddenVocab
116	}
117}
118
119impl Error {
120	pub fn code(&self) -> ErrorCode {
121		match self {
122			Self::InvalidContextNullification => ErrorCode::InvalidContextNullification,
123			Self::LoadingDocumentFailed => ErrorCode::LoadingDocumentFailed,
124			Self::ProcessingModeConflict => ErrorCode::ProcessingModeConflict,
125			Self::InvalidContextEntry => ErrorCode::InvalidContextEntry,
126			Self::InvalidImportValue => ErrorCode::InvalidImportValue,
127			Self::InvalidRemoteContext => ErrorCode::InvalidRemoteContext,
128			Self::InvalidBaseIri => ErrorCode::InvalidBaseIri,
129			Self::InvalidVocabMapping => ErrorCode::InvalidVocabMapping,
130			Self::CyclicIriMapping => ErrorCode::CyclicIriMapping,
131			Self::InvalidTermDefinition => ErrorCode::InvalidTermDefinition,
132			Self::KeywordRedefinition => ErrorCode::KeywordRedefinition,
133			Self::InvalidProtectedValue => ErrorCode::InvalidPropagateValue,
134			Self::InvalidTypeMapping => ErrorCode::InvalidTypeMapping,
135			Self::InvalidReverseProperty => ErrorCode::InvalidReverseProperty,
136			Self::InvalidIriMapping => ErrorCode::InvalidIriMapping,
137			Self::InvalidKeywordAlias => ErrorCode::InvalidKeywordAlias,
138			Self::InvalidContainerMapping => ErrorCode::InvalidContainerMapping,
139			Self::InvalidScopedContext => ErrorCode::InvalidScopedContext,
140			Self::ProtectedTermRedefinition => ErrorCode::ProtectedTermRedefinition,
141			Self::ContextLoadingFailed(_) => ErrorCode::LoadingRemoteContextFailed,
142			Self::ContextExtractionFailed(_) => ErrorCode::LoadingRemoteContextFailed,
143			Self::ForbiddenVocab => ErrorCode::InvalidVocabMapping,
144		}
145	}
146}
147
148/// Result of context processing functions.
149pub type ProcessingResult<'a, T, B> = Result<Processed<'a, T, B>, Error>;
150
151pub trait Process {
152	/// Process the local context with specific options.
153	#[allow(async_fn_in_trait)]
154	async fn process_full<N, L, W>(
155		&self,
156		vocabulary: &mut N,
157		active_context: &Context<N::Iri, N::BlankId>,
158		loader: &L,
159		base_url: Option<N::Iri>,
160		options: Options,
161		warnings: W,
162	) -> Result<Processed<N::Iri, N::BlankId>, Error>
163	where
164		N: VocabularyMut,
165		N::Iri: Clone + Eq + Hash,
166		N::BlankId: Clone + PartialEq,
167		L: Loader,
168		W: WarningHandler<N>;
169
170	/// Process the local context with specific options.
171	#[allow(clippy::type_complexity)]
172	#[allow(async_fn_in_trait)]
173	async fn process_with<N, L>(
174		&self,
175		vocabulary: &mut N,
176		active_context: &Context<N::Iri, N::BlankId>,
177		loader: &L,
178		base_url: Option<N::Iri>,
179		options: Options,
180	) -> Result<Processed<N::Iri, N::BlankId>, Error>
181	where
182		N: VocabularyMut,
183		N::Iri: Clone + Eq + Hash,
184		N::BlankId: Clone + PartialEq,
185		L: Loader,
186	{
187		self.process_full(
188			vocabulary,
189			active_context,
190			loader,
191			base_url,
192			options,
193			warning::Print,
194		)
195		.await
196	}
197
198	/// Process the local context with the given initial active context with the default options:
199	/// `is_remote` is `false`, `override_protected` is `false` and `propagate` is `true`.
200	#[allow(async_fn_in_trait)]
201	async fn process<N, L>(
202		&self,
203		vocabulary: &mut N,
204		loader: &L,
205		base_url: Option<N::Iri>,
206	) -> Result<Processed<N::Iri, N::BlankId>, Error>
207	where
208		N: VocabularyMut,
209		N::Iri: Clone + Eq + Hash,
210		N::BlankId: Clone + PartialEq,
211		L: Loader,
212	{
213		let active_context = Context::default();
214		self.process_full(
215			vocabulary,
216			&active_context,
217			loader,
218			base_url,
219			Options::default(),
220			warning::Print,
221		)
222		.await
223	}
224}
225
226/// Options of the Context Processing Algorithm.
227#[derive(Clone, Copy, PartialEq, Eq)]
228pub struct Options {
229	/// The processing mode
230	pub processing_mode: ProcessingMode,
231
232	/// Override protected definitions.
233	pub override_protected: bool,
234
235	/// Propagate the processed context.
236	pub propagate: bool,
237
238	/// Forbid the use of `@vocab` to expand terms.
239	pub vocab: Action,
240}
241
242impl Options {
243	/// Return the same set of options, but with `override_protected` set to `true`.
244	#[must_use]
245	pub fn with_override(&self) -> Options {
246		let mut opt = *self;
247		opt.override_protected = true;
248		opt
249	}
250
251	/// Return the same set of options, but with `override_protected` set to `false`.
252	#[must_use]
253	pub fn with_no_override(&self) -> Options {
254		let mut opt = *self;
255		opt.override_protected = false;
256		opt
257	}
258
259	/// Return the same set of options, but with `propagate` set to `false`.
260	#[must_use]
261	pub fn without_propagation(&self) -> Options {
262		let mut opt = *self;
263		opt.propagate = false;
264		opt
265	}
266}
267
268impl Default for Options {
269	fn default() -> Options {
270		Options {
271			processing_mode: ProcessingMode::default(),
272			override_protected: false,
273			propagate: true,
274			vocab: Action::Keep,
275		}
276	}
277}