Skip to main content

json_ld/core/loader/
mod.rs

1use json_syntax::JsonValue;
2use rdf_syntax::{Iri, IriBuf};
3use std::{borrow::Cow, sync::LazyLock};
4
5use crate::{JsonLdDocument, syntax::JsonLdContextDocumentValue};
6
7pub mod chain;
8pub mod fs;
9pub mod map;
10pub mod none;
11#[cfg(feature = "reqwest")]
12pub mod reqwest;
13#[cfg(feature = "tokio")]
14pub mod tokio_fs;
15
16#[cfg(feature = "reqwest")]
17pub use self::reqwest::JsonLdReqwestLoader;
18pub use chain::JsonLdChainLoader;
19pub use fs::JsonLdFsLoader;
20pub use map::StaticJsonLdLoader;
21pub use none::NoJsonLdLoader;
22#[cfg(feature = "tokio")]
23pub use tokio_fs::JsonLdTokioFsLoader;
24
25/// Remote JSON-LD context.
26pub type RemoteJsonLdContext = RemoteJsonLdDocument<JsonLdContextDocumentValue>;
27
28/// Remote document, loaded or not.
29///
30/// Either an IRI or the actual document content.
31#[derive(Clone)]
32pub enum RemoteJsonLdDocument<T = JsonValue> {
33	/// IRI to the remote document.
34	Iri(IriBuf),
35
36	/// Remote document content.
37	Loaded(JsonLdDocument<T>),
38}
39
40impl<T> RemoteJsonLdDocument<T> {
41	/// Creates an IRI to a `JsonValue` JSON document.
42	///
43	/// This method can replace `RemoteJsonLdDocument::Iri` to help the type
44	/// inference in the case where `T = JsonValue`.
45	pub fn iri(iri: IriBuf) -> Self {
46		Self::Iri(iri)
47	}
48}
49
50impl<T> RemoteJsonLdDocument<T> {
51	pub fn url(&self) -> Option<&Iri> {
52		match self {
53			Self::Iri(iri) => Some(iri),
54			Self::Loaded(t) => t.url(),
55		}
56	}
57}
58
59impl<T> RemoteJsonLdDocument<T>
60where
61	T: TryFrom<JsonValue>,
62	T::Error: Into<anyhow::Error>,
63{
64	/// Loads the remote document with the given `loader`.
65	///
66	/// If the document is already [`Self::Loaded`], simply returns the inner
67	/// [`JsonLdDocument`].
68	pub async fn load(
69		self,
70		loader: &impl AsyncJsonLdLoader,
71	) -> Result<JsonLdDocument<T>, JsonLdLoadError> {
72		match self {
73			Self::Iri(r) => loader
74				.async_load(&r)
75				.await?
76				.try_map(TryInto::try_into)
77				.map_err(|e| JsonLdLoadError::new(r, e)),
78			Self::Loaded(doc) => Ok(doc),
79		}
80	}
81
82	/// Loads the remote document with the given `loader`.
83	///
84	/// For [`Self::Iri`] returns an owned [`JsonLdDocument`] with
85	/// [`Cow::Owned`].
86	/// For [`Self::Loaded`] returns a reference to the inner [`JsonLdDocument`]
87	/// with [`Cow::Borrowed`].
88	pub async fn loaded(
89		&self,
90		loader: &impl AsyncJsonLdLoader,
91	) -> Result<Cow<'_, JsonLdDocument<T>>, JsonLdLoadError>
92	where
93		T: Clone,
94	{
95		match self {
96			Self::Iri(r) => loader
97				.async_load(r)
98				.await?
99				.try_map(TryInto::try_into)
100				.map_err(|e| JsonLdLoadError::new(r.clone(), e))
101				.map(Cow::Owned),
102			Self::Loaded(doc) => Ok(Cow::Borrowed(doc)),
103		}
104	}
105}
106
107/// Loading error.
108#[derive(Debug, thiserror::Error)]
109#[error("loading document `{target}` failed: {cause}")]
110pub struct JsonLdLoadError {
111	pub target: IriBuf,
112	pub cause: anyhow::Error,
113}
114
115impl JsonLdLoadError {
116	pub fn new(target: IriBuf, cause: impl Into<anyhow::Error>) -> Self {
117		Self {
118			target,
119			cause: cause.into(),
120		}
121	}
122}
123
124/// Async Document loader.
125///
126/// A document loader is required by most processing functions to fetch remote
127/// documents identified by an IRI. In particular, the loader is in charge of
128/// fetching all the remote contexts imported in a `@context` entry.
129///
130/// This library provides a few default loader implementations:
131///   - [`NoJsonLdLoader`] dummy loader that always fail. Perfect if you are
132///     certain that the processing will not require any loading.
133///   - Standard [`HashMap`](std::collections::HashMap) and
134///     [`BTreeMap`](std::collections::BTreeMap) mapping IRIs to pre-loaded
135///     documents. This way no network calls are performed and the loaded
136///     content can be trusted.
137///   - [`JsonLdFsLoader`] that redirecting registered IRI prefixes to a local
138///     directory on the file system. This also avoids network calls. The loaded
139///     content can be trusted as long as the file system is trusted.
140///   - `JsonLdReqwestLoader` actually downloading the remote documents using the
141///     [`reqwest`](https://crates.io/crates/reqwest) library.
142///     This requires the `reqwest` feature to be enabled.
143pub trait AsyncJsonLdLoader {
144	/// Loads the document behind the given IRI.
145	#[allow(async_fn_in_trait)]
146	async fn async_load(&self, url: &Iri) -> Result<JsonLdDocument, JsonLdLoadError>;
147}
148
149impl<L: AsyncJsonLdLoader> AsyncJsonLdLoader for &L {
150	async fn async_load(&self, url: &Iri) -> Result<JsonLdDocument, JsonLdLoadError> {
151		L::async_load(self, url).await
152	}
153}
154
155impl<L: AsyncJsonLdLoader> AsyncJsonLdLoader for &mut L {
156	async fn async_load(&self, url: &Iri) -> Result<JsonLdDocument, JsonLdLoadError> {
157		L::async_load(self, url).await
158	}
159}
160
161/// Lazily initialized loader, e.g. a `static` map of pre-loaded documents.
162impl<L: AsyncJsonLdLoader, F: FnOnce() -> L> AsyncJsonLdLoader for LazyLock<L, F> {
163	async fn async_load(&self, url: &Iri) -> Result<JsonLdDocument, JsonLdLoadError> {
164		L::async_load(self, url).await
165	}
166}
167
168pub trait JsonLdLoader {
169	/// Loads the document behind the given IRI.
170	fn load(&self, url: &Iri) -> Result<JsonLdDocument, JsonLdLoadError>;
171
172	/// Returns this loader as an [`AsyncJsonLdLoader`].
173	fn as_async_loader(&self) -> &ToAsyncJsonLdLoader<Self> {
174		ToAsyncJsonLdLoader::from_ref(self)
175	}
176
177	/// Turns this loader into an async loader.
178	fn into_async_loader(self) -> ToAsyncJsonLdLoader<Self>
179	where
180		Self: Sized,
181	{
182		ToAsyncJsonLdLoader(self)
183	}
184}
185
186impl<L: JsonLdLoader> JsonLdLoader for &L {
187	fn load(&self, url: &Iri) -> Result<JsonLdDocument, JsonLdLoadError> {
188		L::load(self, url)
189	}
190}
191
192impl<L: JsonLdLoader> JsonLdLoader for &mut L {
193	fn load(&self, url: &Iri) -> Result<JsonLdDocument, JsonLdLoadError> {
194		L::load(self, url)
195	}
196}
197
198/// Lazily initialized loader, e.g. a `static` map of pre-loaded documents.
199impl<L: JsonLdLoader, F: FnOnce() -> L> JsonLdLoader for LazyLock<L, F> {
200	fn load(&self, url: &Iri) -> Result<JsonLdDocument, JsonLdLoadError> {
201		L::load(self, url)
202	}
203}
204
205#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
206#[repr(transparent)]
207pub struct ToAsyncJsonLdLoader<T: ?Sized>(pub T);
208
209impl<T: ?Sized> ToAsyncJsonLdLoader<T> {
210	pub fn from_ref(loader: &T) -> &Self {
211		unsafe {
212			// SAFETY: `ToAsyncJsonLdLoader` uses the `transparent` repr.
213			std::mem::transmute(loader)
214		}
215	}
216}
217
218impl<T: JsonLdLoader> AsyncJsonLdLoader for ToAsyncJsonLdLoader<T> {
219	async fn async_load(&self, url: &Iri) -> Result<JsonLdDocument, JsonLdLoadError> {
220		self.0.load(url)
221	}
222}