From 43c0477608b60021019a10e93ad68e12e512cb7d Mon Sep 17 00:00:00 2001 From: damooo2 <127201808+damooo2@users.noreply.github.com> Date: Thu, 7 Dec 2023 17:00:03 +0000 Subject: [PATCH] refactor JsonLdOptions to take document loader factory instead of loader --- jsonld/src/lib.rs | 1 + jsonld/src/loader_factory.rs | 111 ++++++++++++++++++++++++++++++++++ jsonld/src/options.rs | 36 +++++++---- jsonld/src/parser.rs | 48 +++++---------- jsonld/src/parser/test.rs | 33 +++++----- resource/src/loader/_trait.rs | 23 ++++--- sophia/examples/parse.rs | 16 +++-- 7 files changed, 192 insertions(+), 76 deletions(-) create mode 100644 jsonld/src/loader_factory.rs diff --git a/jsonld/src/lib.rs b/jsonld/src/lib.rs index a4386cf5..e478a2f7 100644 --- a/jsonld/src/lib.rs +++ b/jsonld/src/lib.rs @@ -16,6 +16,7 @@ pub use options::*; pub mod error; pub use error::*; pub mod loader; +pub mod loader_factory; pub mod parser; pub use parser::*; pub mod serializer; diff --git a/jsonld/src/loader_factory.rs b/jsonld/src/loader_factory.rs new file mode 100644 index 00000000..0d30b2b7 --- /dev/null +++ b/jsonld/src/loader_factory.rs @@ -0,0 +1,111 @@ +//! I define trait for loader factories. +//! + +use std::{fmt::Display, marker::PhantomData, sync::Arc}; + +use json_ld::Loader; +use json_syntax::Value; +use locspan::Location; +use sophia_iri::Iri; + +/// A trait for factory of document loaders. +pub trait LoaderFactory { + /// Type of loaders this factory yields. + type Loader<'l>: Loader< + Iri>, + Location>>, + Output = Value>>>, + Error = Self::LoaderError, + > + Send + + Sync + + 'l + where + Self: 'l; + + /// Type of loader error. + type LoaderError: Display + Send; + + /// Yield a new loader. + fn yield_loader(&self) -> Self::Loader<'_>; +} + +/// A loader factory that yields default loaders. +#[derive(Debug, Clone, Default)] +pub struct DefaultLoaderFactory { + _phantom: PhantomData L>, +} + +impl DefaultLoaderFactory { + /// Create a new [`DefaultLoaderFactory`]. + #[inline] + pub fn new() -> Self { + Self::default() + } +} + +impl LoaderFactory for DefaultLoaderFactory +where + L: Loader>, Location>>, Output = Value>>>> + + Default + + Send + + Sync, + L::Error: Display + Send, +{ + type Loader<'l> = L + where + Self: 'l; + + type LoaderError = L::Error; + + #[inline] + fn yield_loader(&self) -> Self::Loader<'_> { + L::default() + } +} + +/// A loader factory that delegates to an underlying closure. +#[derive(Debug, Clone)] +pub struct ClosureLoaderFactory { + closure: F, + _phantom: PhantomData L>, +} + +impl ClosureLoaderFactory { + /// Create a new [`ClosureLoaderFactory`] with given closure. + #[inline] + pub fn new(closure: F) -> Self { + Self { + closure, + _phantom: PhantomData, + } + } + + /// Create a new [`ClosureLoaderFactory`] that yields loaders by cloning a template loader. + #[inline] + pub fn new_cloning(template_loader: L) -> ClosureLoaderFactory L> + where + L: Clone, + { + ClosureLoaderFactory::new(move || template_loader.clone()) + } +} + +impl LoaderFactory for ClosureLoaderFactory +where + L: Loader>, Location>>, Output = Value>>>> + + Send + + Sync, + L::Error: Display + Send, + F: Fn() -> L, +{ + type Loader<'l> = L + where + Self: 'l; + + type LoaderError = L::Error; + + #[inline] + fn yield_loader(&self) -> Self::Loader<'_> { + (self.closure)() + } +} diff --git a/jsonld/src/options.rs b/jsonld/src/options.rs index ce9bbc05..702c4e67 100644 --- a/jsonld/src/options.rs +++ b/jsonld/src/options.rs @@ -1,17 +1,17 @@ //! Defines types for configuring JSON-LD processing. -use std::sync::{LockResult, Mutex, MutexGuard}; - use json_ld::expansion::Policy; pub use json_ld::rdf::RdfDirection; use json_ld::syntax::context::Value; +use json_ld::NoLoader; pub use json_ld::Options; pub use json_ld::ProcessingMode; use locspan::Location; use locspan::Span; use sophia_iri::Iri; -use crate::loader::NoLoader; +use crate::loader_factory::DefaultLoaderFactory; +use crate::loader_factory::LoaderFactory; use crate::vocabulary::ArcIri; /// JSON-LD option, as defined by . @@ -24,9 +24,9 @@ use crate::vocabulary::ArcIri; /// /// * the generic parameter `L` is the type of the [document loader](`json_ld::Loader`) #[derive(Default)] -pub struct JsonLdOptions { +pub struct JsonLdOptions { inner: InnerOptions, - loader: Mutex, + loader_factory: LF, use_native_types: bool, use_rdf_type: bool, // non standard: @@ -34,14 +34,14 @@ pub struct JsonLdOptions { compact_context: Option, } -impl JsonLdOptions { +impl JsonLdOptions> { /// Build a new JSON-LD options. pub fn new() -> Self { Self::default() } } -impl JsonLdOptions { +impl JsonLdOptions { /// The [`base`] IRI against which to resolve relative IRIs. /// /// [`base`]: https://www.w3.org/TR/json-ld11-api/#dom-jsonldoptions-base @@ -66,10 +66,11 @@ impl JsonLdOptions { } /// The [`documentLoader`] is used to retrieve remote documents and contexts. + /// The returned factory can yield a [`documentLoader`]. /// /// [`documentLoader`]: https://www.w3.org/TR/json-ld11-api/#dom-jsonldoptions-documentloader - pub fn document_loader(&self) -> LockResult> { - self.loader.lock() + pub fn document_loader_factory(&self) -> &LF { + &self.loader_factory } /// [`expandContext`] is a context that is used to initialize the active context when expanding a document. @@ -191,10 +192,13 @@ impl JsonLdOptions { } /// Change the [`document_loader`](Self::document_loader) - pub fn with_document_loader(self, document_loader: L2) -> JsonLdOptions { + pub fn with_document_loader_factory( + self, + document_loader_factory: LF2, + ) -> JsonLdOptions { JsonLdOptions { inner: self.inner, - loader: Mutex::new(document_loader), + loader_factory: document_loader_factory, use_native_types: self.use_native_types, use_rdf_type: self.use_native_types, spaces: self.spaces, @@ -293,7 +297,15 @@ impl JsonLdOptions { } } -impl std::ops::Deref for JsonLdOptions { +impl JsonLdOptions { + /// The [`documentLoader`] is used to retrieve remote documents and contexts. + /// [`documentLoader`]: https://www.w3.org/TR/json-ld11-api/#dom-jsonldoptions-documentloader + pub fn document_loader(&self) -> LF::Loader<'_> { + self.loader_factory.yield_loader() + } +} + +impl std::ops::Deref for JsonLdOptions { type Target = InnerOptions; fn deref(&self) -> &Self::Target { diff --git a/jsonld/src/parser.rs b/jsonld/src/parser.rs index 09d17bba..97405170 100644 --- a/jsonld/src/parser.rs +++ b/jsonld/src/parser.rs @@ -1,8 +1,8 @@ //! A JSON-LD parser based on Thimothée Haudebourg's [`json_ld`] crate. -use std::{fmt::Display, io::BufRead, ops::DerefMut, sync::Arc}; +use std::{io::BufRead, sync::Arc}; -use json_ld::{JsonLdProcessor, Loader, RemoteDocument, ToRdfError}; +use json_ld::{JsonLdProcessor, RemoteDocument, ToRdfError}; use json_syntax::{Parse, Value}; use locspan::{Location, Span}; use sophia_api::{prelude::QuadParser, quad::Spog}; @@ -10,6 +10,7 @@ use sophia_iri::Iri; use crate::{ loader::NoLoader, + loader_factory::{DefaultLoaderFactory, LoaderFactory}, vocabulary::{ArcIri, ArcVoc}, JsonLdOptions, }; @@ -30,17 +31,17 @@ mod test; /// /// * the generic parameter `L` is the type of the [document loader](`json_ld::Loader`) /// (determined by the `options` parameters) -pub struct JsonLdParser { - options: JsonLdOptions, +pub struct JsonLdParser> { + options: JsonLdOptions, } -impl Default for JsonLdParser { +impl Default for JsonLdParser> { fn default() -> Self { Self::new() } } -impl JsonLdParser { +impl JsonLdParser> { /// Make a new [`JsonLdParser`] with the default options pub fn new() -> Self { JsonLdParser { @@ -49,46 +50,36 @@ impl JsonLdParser { } } -impl JsonLdParser { +impl JsonLdParser { /// Make a new [`JsonLdParser`] with the given options - pub fn new_with_options(options: JsonLdOptions) -> Self { + pub fn new_with_options(options: JsonLdOptions) -> Self { JsonLdParser { options } } /// Borrow the options of this parser - pub fn options(&self) -> &JsonLdOptions { + pub fn options(&self) -> &JsonLdOptions { &self.options } /// Parse (as RDF) a pre-parsed (as JSON) document pub fn parse_json(&self, data: &RemoteDocument) -> JsonLdQuadSource where - L: Loader> - + json_ld::ContextLoader> - + Send - + Sync, - L::Output: Into>>, - L::Error: Display + Send, - L::Context: Into>>, - L::ContextError: Display + Send, + LF: LoaderFactory, { let gen_loc = Location::new( Iri::new_unchecked(Arc::from("x-bnode-gen://")), Span::default(), ); let mut generator = rdf_types::generator::Blank::new().with_metadata(gen_loc); - let mut g_loader = match self.options.document_loader() { - Ok(g) => g, - Err(err) => return JsonLdQuadSource::from_err(err), - }; - let loader = g_loader.deref_mut(); + let mut loader = self.options.document_loader(); let mut vocab = ArcVoc {}; let options = self.options.inner().clone(); let rt = tokio::runtime::Builder::new_current_thread() .enable_all() .build() .expect("Could not build tokio runtime"); - match rt.block_on(data.to_rdf_with_using(&mut vocab, &mut generator, loader, options)) { + match rt.block_on(data.to_rdf_with_using(&mut vocab, &mut generator, &mut loader, options)) + { Err(ToRdfError::Expand(err)) => JsonLdQuadSource::from_err(err), Ok(mut to_rdf) => JsonLdQuadSource::Quads( to_rdf @@ -101,16 +92,9 @@ impl JsonLdParser { } } -impl QuadParser for JsonLdParser +impl QuadParser for JsonLdParser where - L: Loader> - + json_ld::ContextLoader> - + Send - + Sync, - L::Output: Into>>, - L::Error: Display + Send, - L::Context: Into>>, - L::ContextError: Display + Send, + LF: LoaderFactory, { type Source = JsonLdQuadSource; diff --git a/jsonld/src/parser/test.rs b/jsonld/src/parser/test.rs index d43cab36..d2cb5fa6 100644 --- a/jsonld/src/parser/test.rs +++ b/jsonld/src/parser/test.rs @@ -2,15 +2,17 @@ use sophia_api::{prelude::QuadParser, quad::Spog, source::QuadSource}; use sophia_term::ArcTerm; use sophia_turtle::parser::nq; -use crate::{JsonLdOptions, JsonLdParser}; +use crate::{ + loader_factory::{ClosureLoaderFactory, DefaultLoaderFactory}, + JsonLdOptions, JsonLdParser, +}; // Check whether JsonLdParser correctly implements QuadParser // (i.e. it has the correct trait bounds). // NB: the goal is NOT to check the loader itself -- we actually don't use it. #[test] fn check_no_loader() { - let loader = crate::loader::NoLoader::default(); - let options = JsonLdOptions::new().with_document_loader(loader); + let options = JsonLdOptions::>::default(); let p = JsonLdParser::new_with_options(options); let got: TestDataset = p .parse_str(r#"{"@id": "tag:foo", "tag:bar": "BAZ"}"#) @@ -27,8 +29,7 @@ fn check_no_loader() { // NB: the goal is NOT to check the loader itself -- we actually don't use it. #[test] fn check_fs_loader() { - let loader = crate::loader::FsLoader::default(); - let options = JsonLdOptions::new().with_document_loader(loader); + let options = JsonLdOptions::>::default(); let p = JsonLdParser::new_with_options(options); let got: TestDataset = p .parse_str(r#"{"@id": "tag:foo", "tag:bar": "BAZ"}"#) @@ -45,8 +46,8 @@ fn check_fs_loader() { // NB: the goal is NOT to check the loader itself -- we actually don't use it. #[test] fn check_static_loader() { - let loader = crate::loader::StaticLoader::default(); - let options = JsonLdOptions::new().with_document_loader(loader); + let options = + JsonLdOptions::>>::default(); let p = JsonLdParser::new_with_options(options); let got: TestDataset = p .parse_str(r#"{"@id": "tag:foo", "tag:bar": "BAZ"}"#) @@ -64,8 +65,7 @@ fn check_static_loader() { #[cfg(feature = "http_client")] #[test] fn check_http_loader() { - let loader = crate::loader::HttpLoader::default(); - let options = JsonLdOptions::new().with_document_loader(loader); + let options = JsonLdOptions::>::default(); let p = JsonLdParser::new_with_options(options); let got: TestDataset = p .parse_str(r#"{"@id": "tag:foo", "tag:bar": "BAZ"}"#) @@ -83,8 +83,7 @@ fn check_http_loader() { // NB: the goal is NOT to check the loader itself -- we actually don't use it. #[test] fn check_file_url_loader() { - let loader = crate::loader::FileUrlLoader::default(); - let options = JsonLdOptions::new().with_document_loader(loader); + let options = JsonLdOptions::>::default(); let p = JsonLdParser::new_with_options(options); let got: TestDataset = p .parse_str(r#"{"@id": "tag:foo", "tag:bar": "BAZ"}"#) @@ -101,11 +100,13 @@ fn check_file_url_loader() { // NB: the goal is NOT to check the loader itself -- we actually don't use it. #[test] fn check_chain_loader() { - let loader = crate::loader::ChainLoader::new( - crate::loader::StaticLoader::default(), - crate::loader::FsLoader::default(), - ); - let options = JsonLdOptions::new().with_document_loader(loader); + let options = + JsonLdOptions::new().with_document_loader_factory(ClosureLoaderFactory::new(|| { + crate::loader::ChainLoader::new( + crate::loader::StaticLoader::default(), + crate::loader::FsLoader::default(), + ) + })); let p = JsonLdParser::new_with_options(options); let got: TestDataset = p .parse_str(r#"{"@id": "tag:foo", "tag:bar": "BAZ"}"#) diff --git a/resource/src/loader/_trait.rs b/resource/src/loader/_trait.rs index 3044e9fd..9a8cebfe 100644 --- a/resource/src/loader/_trait.rs +++ b/resource/src/loader/_trait.rs @@ -7,6 +7,7 @@ use sophia_api::parser::TripleParser; use sophia_api::source::TripleSource; use sophia_api::term::Term; use sophia_iri::Iri; +use sophia_jsonld::loader_factory::ClosureLoaderFactory; use sophia_turtle::parser::{nt, turtle}; use std::borrow::Borrow; use std::fmt::Debug; @@ -54,17 +55,19 @@ pub trait Loader: Sync + Sized { use sophia_jsonld::{loader::ClosureLoader, JsonLdOptions, JsonLdParser}; let options = JsonLdOptions::new() .with_base(iri.as_ref().map_unchecked(|t| t.into())) - .with_document_loader(ClosureLoader::new(|url| { - async move { - let (content, ctype) = - self.get(url.as_ref()).map_err(|e| e.to_string())?; - if ctype == "application/ld+json" { - String::from_utf8(content).map_err(|e| e.to_string()) - } else { - Err(format!("{url} is not JSON-LD: {ctype}")) + .with_document_loader_factory(ClosureLoaderFactory::new(|| { + ClosureLoader::new(|url| { + async move { + let (content, ctype) = + self.get(url.as_ref()).map_err(|e| e.to_string())?; + if ctype == "application/ld+json" { + String::from_utf8(content).map_err(|e| e.to_string()) + } else { + Err(format!("{url} is not JSON-LD: {ctype}")) + } } - } - .boxed() + .boxed() + }) })); JsonLdParser::new_with_options(options) .parse(bufread) diff --git a/sophia/examples/parse.rs b/sophia/examples/parse.rs index 70f7faaf..ccfd59cd 100644 --- a/sophia/examples/parse.rs +++ b/sophia/examples/parse.rs @@ -34,6 +34,7 @@ use sophia::turtle::parser::{ use sophia::turtle::serializer::{nq::NqSerializer, nt::NtSerializer}; #[cfg(feature = "xml")] use sophia::xml::parser::RdfXmlParser; +use sophia_jsonld::loader_factory::{ClosureLoaderFactory, DefaultLoaderFactory, LoaderFactory}; fn main() { let format = std::env::args() @@ -64,13 +65,16 @@ fn main() { "json-ld" | "jsonld" => { let options = JsonLdOptions::new() .with_base(base.clone().unwrap().map_unchecked(std::sync::Arc::from)); - let loader: sophia::jsonld::loader::FileUrlLoader = Default::default(); + let loader_factory = + DefaultLoaderFactory::::default(); #[cfg(feature = "http_client")] - let loader = sophia::jsonld::loader::ChainLoader::new( - loader, - sophia::jsonld::loader::HttpLoader::default(), - ); - let options = options.with_document_loader(loader); + let loader_factory = ClosureLoaderFactory::new(|| { + sophia::jsonld::loader::ChainLoader::new( + loader_factory.yield_loader(), + sophia::jsonld::loader::HttpLoader::default(), + ) + }); + let options = options.with_document_loader_factory(loader_factory); dump_quads(input, JsonLdParser::new_with_options(options)) } #[cfg(feature = "xml")]