jsonld_core/loader/dereference.rs
1//! Invoking the `LoadDocumentCallback` from inside an algorithm.
2//!
3//! The algorithms recurse once per nesting level of their input, and keep that
4//! recursion off the caller's native stack by checking, at every recursion
5//! point, that at least [`RED_ZONE`] of it is left
6//! ([`native_stack`](jsonld_syntax::native_stack)). Dereferencing a document
7//! leaves the algorithm for the embedder's [`Loader`]: an HTTP client behind a
8//! cache, say, whose native stack per poll the crate cannot bound. Polled
9//! without a check of its own, a loader runs on whatever the last recursion
10//! point left, which may be only a little more than the red zone.
11//!
12//! [`Dereference::dereference`] is therefore the one way an algorithm invokes a
13//! loader: every poll of the loader runs as a recursion point does, so it
14//! always starts with at least [`RED_ZONE`] of native stack. The workspace
15//! `clippy.toml` disallows calling [`Loader::load`] anywhere else. It is also
16//! where a loaded document's IRIs enter the caller's vocabulary.
17//!
18//! [`RED_ZONE`]: jsonld_syntax::native_stack::RED_ZONE
19
20use crate::loader::{Loader, LoadingResult};
21use jsonld_syntax::native_stack::Recursion;
22use rdfx::vocabulary::IriVocabularyMut;
23use std::{future::Future, hash::Hash};
24
25/// Dereferencing a document through a [`Loader`] on sufficient native stack.
26///
27/// Implemented for every loader; an algorithm calls [`Self::dereference`]
28/// where the JSON-LD 1.1 API invokes the `LoadDocumentCallback`.
29pub trait Dereference: Loader {
30 /// Loads the document behind `url`, using `vocabulary`, polling the loader
31 /// on at least [`RED_ZONE`](jsonld_syntax::native_stack::RED_ZONE) of
32 /// native stack.
33 ///
34 /// # Errors
35 ///
36 /// Returns the loader's error when the document cannot be fetched or
37 /// parsed.
38 ///
39 /// # Panics
40 ///
41 /// Panics if `url` does not resolve in `vocabulary`, i.e. if it was
42 /// obtained from a different vocabulary.
43 fn dereference<V>(&self, vocabulary: &mut V, url: V::Iri) -> impl Future<Output = LoadingResult<V::Iri, Self::Error>>
44 where
45 V: IriVocabularyMut,
46 V::Iri: Clone + Eq + Hash;
47}
48
49impl<L: Loader + ?Sized> Dereference for L {
50 fn dereference<V>(&self, vocabulary: &mut V, url: V::Iri) -> impl Future<Output = LoadingResult<V::Iri, Self::Error>>
51 where
52 V: IriVocabularyMut,
53 V::Iri: Clone + Eq + Hash,
54 {
55 Recursion::new(async move {
56 #[allow(clippy::expect_used)]
57 let lexical_url = vocabulary.iri(&url).expect("`url` does not resolve in the given vocabulary");
58 #[expect(clippy::disallowed_methods, reason = "the one guarded invocation of a loader every algorithm goes through")]
59 let document = self.load(lexical_url).await?;
60 Ok(document.map_iris(|i| vocabulary.insert_owned(i)))
61 })
62 }
63}