Skip to content

Commit

Permalink
improve ergonomy of sophia_jsonld
Browse files Browse the repository at this point in the history
expand_context and compact_context can now be added
as Iri or as JSON string.
  • Loading branch information
pchampin committed Feb 28, 2024
1 parent 661f7f6 commit fed585a
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 44 deletions.
56 changes: 56 additions & 0 deletions jsonld/src/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//! Utility trait to ease the loading of JSON-LD Contexts
use std::{borrow::Borrow, sync::Arc};

use json_ld::{
syntax::{context::Value as ContextValue, Value},
ExtractContext, RemoteDocument, RemoteDocumentReference,
};
use json_syntax::Parse;
use locspan::{Location, Span};
use sophia_iri::Iri;

use crate::vocabulary::ArcIri;

/// Type alias for the context references used by the JSON-LD options.
pub type ContextRef =
RemoteDocumentReference<ArcIri, Location<ArcIri, Span>, ContextValue<Location<ArcIri, Span>>>;

/// Anything that can be turned into a [`ContextRef`]
pub trait IntoContextRef {
/// Turn it into a [`ContextRef`]
fn into_context_ref(self) -> ContextRef;
}

impl IntoContextRef for ContextRef {
fn into_context_ref(self) -> ContextRef {
self
}
}

impl<T: Borrow<str>> IntoContextRef for Iri<T> {
fn into_context_ref(self) -> ContextRef {
RemoteDocumentReference::Iri(self.map_unchecked(|t| Arc::from(t.borrow())))
}
}

/// Anything that can be turned into a [`ContextRef`] but may fail
pub trait TryIntoContextRef {
/// Raised when the conversion to [`ContextRef`] fails
type Error;
/// Try to turn it into a [`ContextRef`]
fn try_into_context_ref(self) -> Result<ContextRef, Self::Error>;
}

impl TryIntoContextRef for &str {
type Error = Box<dyn std::error::Error>;

fn try_into_context_ref(self) -> Result<ContextRef, Self::Error> {
let iri = ArcIri::new_unchecked("x-string://".into());
let doc = Value::parse_str(self, |span| locspan::Location::new(iri.clone(), span))?;
let context = Value::extract_context(doc)
.map_err(|e| format!("Could not extract @context: {}", e))?;
let rdoc = RemoteDocument::new(None, None, context);
Ok(RemoteDocumentReference::Loaded(rdoc))
}
}
2 changes: 2 additions & 0 deletions jsonld/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#![deny(missing_docs)]

pub mod context;
pub use context::ContextRef;
pub mod options;
pub use options::*;
pub mod error;
Expand Down
61 changes: 43 additions & 18 deletions jsonld/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,18 @@
use std::fmt::Display;
use std::sync::Arc;

use json_ld::expansion::Policy;
pub use json_ld::expansion::Policy;
pub use json_ld::rdf::RdfDirection;
use json_ld::syntax::context::Value;
use json_ld::syntax::context::Value as ContextValue;
use json_ld::Loader;
pub use json_ld::Options;
pub use json_ld::ProcessingMode;
use json_syntax::Value;
use locspan::Location;
use locspan::Span;
use sophia_iri::Iri;

use crate::context::*;
use crate::loader::NoLoader;
use crate::loader_factory::ClosureLoaderFactory;
use crate::loader_factory::DefaultLoaderFactory;
Expand Down Expand Up @@ -228,9 +230,7 @@ impl<LF> JsonLdOptions<LF> {
f: F,
) -> JsonLdOptions<ClosureLoaderFactory<L, F>>
where
L: Loader<ArcIri, Location<Iri<Arc<str>>>, Output = json_syntax::Value<Location<ArcIri>>>
+ Send
+ Sync,
L: Loader<ArcIri, Location<Iri<Arc<str>>>, Output = Value<Location<ArcIri>>> + Send + Sync,
L::Error: Display + Send,
F: Fn() -> L,
{
Expand All @@ -253,7 +253,7 @@ impl<LF> JsonLdOptions<LF> {
/// [`JsonLdOptions::with_document_loader`],
pub fn with_default_document_loader<L>(self) -> JsonLdOptions<DefaultLoaderFactory<L>>
where
L: Loader<ArcIri, Location<Iri<Arc<str>>>, Output = json_syntax::Value<Location<ArcIri>>>
L: Loader<ArcIri, Location<Iri<Arc<str>>>, Output = Value<Location<ArcIri>>>
+ Default
+ Send
+ Sync,
Expand Down Expand Up @@ -281,7 +281,7 @@ impl<LF> JsonLdOptions<LF> {
document_loader: L,
) -> JsonLdOptions<ClosureLoaderFactory<L, impl Fn() -> L>>
where
L: Loader<ArcIri, Location<Iri<Arc<str>>>, Output = json_syntax::Value<Location<ArcIri>>>
L: Loader<ArcIri, Location<Iri<Arc<str>>>, Output = Value<Location<ArcIri>>>
+ Clone
+ Send
+ Sync,
Expand All @@ -299,20 +299,34 @@ impl<LF> JsonLdOptions<LF> {

/// Change the [`expand_context`](Self::expand_context)
///
/// See also [`with_no_expand_context`](Self::with_no_expand_context)
pub fn with_expand_context<SE2>(mut self, expand_context: ContextRef) -> Self {
self.inner.expand_context = Some(expand_context);
/// See also [`with_no_expand_context`](Self::with_no_expand_context),
/// [`try_with_expand_context`](Self::try_with_expand_context)
pub fn with_expand_context<C: IntoContextRef>(mut self, expand_context: C) -> Self {
self.inner.expand_context = Some(expand_context.into_context_ref());
self
}

/// Change the [`expand_context`](Self::expand_context)
///
/// See also [`with_expand_context`](Self::with_expand_context)
/// [`try_with_expand_context`](Self::try_with_expand_context)
pub fn with_no_expand_context(mut self) -> Self {
self.inner.expand_context = None;
self
}

/// Change the [`expand_context`](Self::expand_context)
///
/// See also [`with_expand_context`](Self::with_expand_context),
/// [`with_no_expand_context`](Self::with_no_expand_context)
pub fn try_with_expand_context<C: TryIntoContextRef>(
mut self,
expand_context: C,
) -> Result<Self, C::Error> {
self.inner.expand_context = Some(expand_context.try_into_context_ref()?);
Ok(self)
}

/// Change the [`ordered`](Self::ordered) flag
pub fn with_ordered(mut self, ordered: bool) -> Self {
self.inner.ordered = ordered;
Expand Down Expand Up @@ -373,19 +387,33 @@ impl<LF> JsonLdOptions<LF> {

/// Change the [`compact_context`](Self::compact_context)
///
/// See also [`with_no_compact_context`](Self::with_no_compact_context)
pub fn with_compact_context<SE2>(mut self, compact_context: ContextRef) -> Self {
self.compact_context = Some(compact_context);
/// See also [`with_no_compact_context`](Self::with_no_compact_context),
/// [`try_with_compact_context`](Self::try_with_compact_context)
pub fn with_compact_context<C: IntoContextRef>(mut self, compact_context: C) -> Self {
self.compact_context = Some(compact_context.into_context_ref());
self
}

/// Change the [`compact_context`](Self::compact_context)
///
/// See also [`with_compact_context`](Self::with_compact_context)
/// [`try_with_compact_context`](Self::try_with_compact_context)
pub fn with_no_compact_context(mut self) -> Self {
self.compact_context = None;
self
}

/// Change the [`compact_context`](Self::compact_context)
///
/// See also [`with_compact_context`](Self::with_compact_context),
/// [`with_no_compact_context`](Self::with_no_compact_context)
pub fn try_with_compact_context<C: TryIntoContextRef>(
mut self,
compact_context: C,
) -> Result<Self, C::Error> {
self.compact_context = Some(compact_context.try_into_context_ref()?);
Ok(self)
}
}

impl<LF: LoaderFactory> JsonLdOptions<LF> {
Expand All @@ -405,9 +433,6 @@ impl<LF> std::ops::Deref for JsonLdOptions<LF> {
}
}

/// Type alias for the context references used by the JSON-LD options.
pub type ContextRef =
json_ld::RemoteDocumentReference<ArcIri, Location<ArcIri, Span>, Value<Location<ArcIri, Span>>>;

/// Type alias for [`json_ld::Options`] as used in this crate.
type InnerOptions = json_ld::Options<ArcIri, Location<ArcIri, Span>, Value<Location<ArcIri, Span>>>;
type InnerOptions =
json_ld::Options<ArcIri, Location<ArcIri, Span>, ContextValue<Location<ArcIri, Span>>>;
3 changes: 0 additions & 3 deletions sophia/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@ sophia_xml = { workspace = true, optional = true }

[dev-dependencies]
url.workspace = true
# dependencies for the jsonld-context example
json-ld = "0.15.1"
locspan = "0.7"

[[example]]
name = "jsonld-context"
Expand Down
28 changes: 5 additions & 23 deletions sophia/examples/jsonld-context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,15 @@
//!
//! Thanks to Jos van den Oever for this example.
use json_ld::{expansion::Policy, syntax::Value, RemoteDocument, RemoteDocumentReference};
use sophia::{
api::{
parser::QuadParser,
serializer::{Stringifier, QuadSerializer},
serializer::{QuadSerializer, Stringifier},
source::QuadSource,
},
jsonld::{
vocabulary::ArcIri, ContextRef, JsonLdOptions, JsonLdParser,
},
jsonld::{JsonLdOptions, JsonLdParser, Policy},
turtle::serializer::trig::{TrigConfig, TrigSerializer},
};
use std::sync::Arc;

fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut args = std::env::args();
Expand All @@ -35,8 +31,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {

let mut options = JsonLdOptions::new().with_expansion_policy(Policy::Standard);
if let Some(context_path) = context_path {
let context = parse_context(&context_path)?;
options = options.with_expand_context::<()>(context);
let context_str = std::fs::read_to_string(&context_path)
.map_err(|e| format!("Could not read file {}: {}", &context_path, e))?;
options = options.try_with_expand_context(context_str.as_str())?;
}
let parser = JsonLdParser::new_with_options(options);
let quads = parser.parse_str(&json_str);
Expand All @@ -45,21 +42,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
Ok(())
}

fn parse_context(context_path: &str) -> Result<ContextRef, Box<dyn std::error::Error>> {
let context_str = std::fs::read_to_string(context_path)
.map_err(|e| format!("Could not read file {}: {}", context_path, e))?;
let iri = ArcIri::new(Arc::from(format!("file:{}", &context_path)))?;
use json_ld::syntax::Parse;
let doc = Value::parse_str(&context_str, |span| {
locspan::Location::new(iri.clone(), span)
})?;
use json_ld::ExtractContext;
let context =
Value::extract_context(doc).map_err(|e| format!("Could not extract @context: {}", e))?;
let rdoc = RemoteDocument::new(Some(iri), None, context);
Ok(RemoteDocumentReference::Loaded(rdoc))
}

fn to_trig(quads: impl QuadSource) -> Result<String, Box<dyn std::error::Error>> {
let mut stringifier =
TrigSerializer::new_stringifier_with_config(TrigConfig::new().with_pretty(true));
Expand Down

0 comments on commit fed585a

Please sign in to comment.