use crate::compaction::{self, Compact};
use crate::context_processing::{self, Process};
use crate::expansion;
use crate::syntax::ErrorCode;
use crate::{flattening::ConflictingIndexes, Context, ExpandedDocument, Loader, ProcessingMode};
use iref::IriBuf;
use json_ld_core::rdf::RdfDirection;
use json_ld_core::{ContextLoadError, LoadError};
use json_ld_core::{Document, RdfQuads, RemoteContextReference};
use rdf_types::{vocabulary, BlankIdBuf, Generator, Vocabulary, VocabularyMut};
use std::hash::Hash;
mod remote_document;
#[derive(Clone)]
pub struct Options<I = IriBuf> {
pub base: Option<I>,
pub compact_arrays: bool,
pub compact_to_relative: bool,
pub expand_context: Option<RemoteContextReference<I>>,
pub ordered: bool,
pub processing_mode: ProcessingMode,
pub rdf_direction: Option<RdfDirection>,
pub produce_generalized_rdf: bool,
pub expansion_policy: expansion::Policy,
}
impl<I> Options<I> {
pub fn unordered(self) -> Self {
Self {
ordered: false,
..self
}
}
pub fn with_expand_context(self, context: RemoteContextReference<I>) -> Self {
Self {
expand_context: Some(context),
..self
}
}
pub fn context_processing_options(&self) -> context_processing::Options {
context_processing::Options {
processing_mode: self.processing_mode,
..Default::default()
}
}
pub fn expansion_options(&self) -> expansion::Options {
expansion::Options {
processing_mode: self.processing_mode,
ordered: self.ordered,
policy: self.expansion_policy,
}
}
pub fn compaction_options(&self) -> compaction::Options {
compaction::Options {
processing_mode: self.processing_mode,
compact_to_relative: self.compact_to_relative,
compact_arrays: self.compact_arrays,
ordered: self.ordered,
}
}
}
impl<I> Default for Options<I> {
fn default() -> Self {
Self {
base: None,
compact_arrays: true,
compact_to_relative: true,
expand_context: None,
ordered: false,
processing_mode: ProcessingMode::JsonLd1_1,
rdf_direction: None,
produce_generalized_rdf: false,
expansion_policy: expansion::Policy::default(),
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum ExpandError {
#[error("Expansion failed: {0}")]
Expansion(expansion::Error),
#[error("Context processing failed: {0}")]
ContextProcessing(context_processing::Error),
#[error(transparent)]
Loading(#[from] LoadError),
#[error(transparent)]
ContextLoading(ContextLoadError),
}
impl ExpandError {
pub fn code(&self) -> ErrorCode {
match self {
Self::Expansion(e) => e.code(),
Self::ContextProcessing(e) => e.code(),
Self::Loading(_) => ErrorCode::LoadingDocumentFailed,
Self::ContextLoading(_) => ErrorCode::LoadingRemoteContextFailed,
}
}
}
pub type ExpandResult<I, B> = Result<ExpandedDocument<I, B>, ExpandError>;
pub type IntoDocumentResult<I, B> = Result<Document<I, B>, ExpandError>;
#[derive(Debug, thiserror::Error)]
pub enum CompactError {
#[error("Expansion failed: {0}")]
Expand(ExpandError),
#[error("Context processing failed: {0}")]
ContextProcessing(context_processing::Error),
#[error("Compaction failed: {0}")]
Compaction(compaction::Error),
#[error(transparent)]
Loading(#[from] LoadError),
#[error(transparent)]
ContextLoading(ContextLoadError),
}
impl CompactError {
pub fn code(&self) -> ErrorCode {
match self {
Self::Expand(e) => e.code(),
Self::ContextProcessing(e) => e.code(),
Self::Compaction(e) => e.code(),
Self::Loading(_) => ErrorCode::LoadingDocumentFailed,
Self::ContextLoading(_) => ErrorCode::LoadingRemoteContextFailed,
}
}
}
pub type CompactResult = Result<json_syntax::Value, CompactError>;
#[derive(Debug, thiserror::Error)]
pub enum FlattenError<I, B> {
#[error("Expansion failed: {0}")]
Expand(ExpandError),
#[error("Compaction failed: {0}")]
Compact(CompactError),
#[error("Conflicting indexes: {0}")]
ConflictingIndexes(ConflictingIndexes<I, B>),
#[error(transparent)]
Loading(#[from] LoadError),
#[error(transparent)]
ContextLoading(ContextLoadError),
}
impl<I, B> FlattenError<I, B> {
pub fn code(&self) -> ErrorCode {
match self {
Self::Expand(e) => e.code(),
Self::Compact(e) => e.code(),
Self::ConflictingIndexes(_) => ErrorCode::ConflictingIndexes,
Self::Loading(_) => ErrorCode::LoadingDocumentFailed,
Self::ContextLoading(_) => ErrorCode::LoadingRemoteContextFailed,
}
}
}
pub type FlattenResult<I, B> = Result<json_syntax::Value, FlattenError<I, B>>;
#[derive(Debug, thiserror::Error)]
pub enum ToRdfError {
#[error("Expansion failed: {0}")]
Expand(ExpandError),
}
impl ToRdfError {
pub fn code(&self) -> ErrorCode {
match self {
Self::Expand(e) => e.code(),
}
}
}
pub type ToRdfResult<V, G> = Result<ToRdf<V, G>, ToRdfError>;
pub type CompareResult = Result<bool, ExpandError>;
pub trait JsonLdProcessor<Iri>: Sized {
#[allow(async_fn_in_trait)]
async fn compare_full<N>(
&self,
other: &Self,
vocabulary: &mut N,
loader: &impl Loader,
options: Options<Iri>,
warnings: impl context_processing::WarningHandler<N> + expansion::WarningHandler<N>,
) -> CompareResult
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
N::BlankId: Clone + Eq + Hash;
#[allow(async_fn_in_trait)]
async fn compare_with_using<'a, N>(
&'a self,
other: &'a Self,
vocabulary: &'a mut N,
loader: &'a impl Loader,
options: Options<Iri>,
) -> CompareResult
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
N::BlankId: 'a + Clone + Eq + Hash,
{
self.compare_full(other, vocabulary, loader, options, ())
.await
}
#[allow(async_fn_in_trait)]
async fn compare_with<'a, N>(
&'a self,
other: &'a Self,
vocabulary: &'a mut N,
loader: &'a impl Loader,
) -> CompareResult
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
N::BlankId: 'a + Clone + Eq + Hash,
{
self.compare_with_using(other, vocabulary, loader, Options::default())
.await
}
#[allow(async_fn_in_trait)]
async fn compare_using<'a>(
&'a self,
other: &'a Self,
loader: &'a impl Loader,
options: Options<Iri>,
) -> CompareResult
where
(): VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
{
self.compare_with_using(
other,
rdf_types::vocabulary::no_vocabulary_mut(),
loader,
options,
)
.await
}
#[allow(async_fn_in_trait)]
async fn compare<'a>(&'a self, other: &'a Self, loader: &'a impl Loader) -> CompareResult
where
(): VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
{
self.compare_with(other, rdf_types::vocabulary::no_vocabulary_mut(), loader)
.await
}
#[allow(async_fn_in_trait)]
async fn expand_full<N>(
&self,
vocabulary: &mut N,
loader: &impl Loader,
options: Options<Iri>,
warnings: impl context_processing::WarningHandler<N> + expansion::WarningHandler<N>,
) -> ExpandResult<Iri, N::BlankId>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
N::BlankId: Clone + Eq + Hash;
#[allow(async_fn_in_trait)]
async fn expand_with_using<'a, N>(
&'a self,
vocabulary: &'a mut N,
loader: &'a impl Loader,
options: Options<Iri>,
) -> ExpandResult<Iri, N::BlankId>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
N::BlankId: 'a + Clone + Eq + Hash,
{
self.expand_full(vocabulary, loader, options, ()).await
}
#[allow(async_fn_in_trait)]
async fn expand_with<'a, N>(
&'a self,
vocabulary: &'a mut N,
loader: &'a impl Loader,
) -> ExpandResult<Iri, N::BlankId>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
N::BlankId: 'a + Clone + Eq + Hash,
{
self.expand_with_using(vocabulary, loader, Options::default())
.await
}
#[allow(async_fn_in_trait)]
async fn expand_using<'a>(
&'a self,
loader: &'a impl Loader,
options: Options<Iri>,
) -> ExpandResult<Iri, BlankIdBuf>
where
(): VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
{
self.expand_with_using(vocabulary::no_vocabulary_mut(), loader, options)
.await
}
#[allow(async_fn_in_trait)]
async fn expand<'a>(&'a self, loader: &'a impl Loader) -> ExpandResult<Iri, BlankIdBuf>
where
(): VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
{
self.expand_with(vocabulary::no_vocabulary_mut(), loader)
.await
}
#[allow(async_fn_in_trait)]
async fn into_document_full<'a, N>(
self,
vocabulary: &'a mut N,
loader: &'a impl Loader,
options: Options<Iri>,
warnings: impl 'a + context_processing::WarningHandler<N> + expansion::WarningHandler<N>,
) -> IntoDocumentResult<Iri, N::BlankId>
where
N: VocabularyMut<Iri = Iri>,
Iri: 'a + Clone + Eq + Hash,
N::BlankId: 'a + Clone + Eq + Hash;
#[allow(async_fn_in_trait)]
async fn into_document_with_using<'a, N>(
self,
vocabulary: &'a mut N,
loader: &'a impl Loader,
options: Options<Iri>,
) -> IntoDocumentResult<Iri, N::BlankId>
where
N: VocabularyMut<Iri = Iri>,
Iri: 'a + Clone + Eq + Hash,
N::BlankId: 'a + Clone + Eq + Hash,
{
self.into_document_full(vocabulary, loader, options, ())
.await
}
#[allow(async_fn_in_trait)]
async fn into_document_with<'a, N>(
self,
vocabulary: &'a mut N,
loader: &'a impl Loader,
) -> IntoDocumentResult<Iri, N::BlankId>
where
N: VocabularyMut<Iri = Iri>,
Iri: 'a + Clone + Eq + Hash,
N::BlankId: 'a + Clone + Eq + Hash,
{
self.into_document_with_using(vocabulary, loader, Options::default())
.await
}
#[allow(async_fn_in_trait)]
async fn into_document<'a>(self, loader: &'a impl Loader) -> IntoDocumentResult<Iri, BlankIdBuf>
where
(): VocabularyMut<Iri = Iri>,
Iri: 'a + Clone + Eq + Hash,
{
self.into_document_with(vocabulary::no_vocabulary_mut(), loader)
.await
}
#[allow(async_fn_in_trait)]
async fn compact_full<'a, N>(
&'a self,
vocabulary: &'a mut N,
context: RemoteContextReference<Iri>,
loader: &'a impl Loader,
options: Options<Iri>,
warnings: impl 'a + context_processing::WarningHandler<N> + expansion::WarningHandler<N>,
) -> CompactResult
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
N::BlankId: 'a + Clone + Eq + Hash;
#[allow(async_fn_in_trait)]
async fn compact_with_using<'a, N>(
&'a self,
vocabulary: &'a mut N,
context: RemoteContextReference<Iri>,
loader: &'a impl Loader,
options: Options<Iri>,
) -> CompactResult
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
N::BlankId: 'a + Clone + Eq + Hash,
{
self.compact_full(vocabulary, context, loader, options, ())
.await
}
#[allow(async_fn_in_trait)]
async fn compact_with<'a, N>(
&'a self,
vocabulary: &'a mut N,
context: RemoteContextReference<Iri>,
loader: &'a impl Loader,
) -> CompactResult
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
N::BlankId: 'a + Clone + Eq + Hash,
{
self.compact_with_using(vocabulary, context, loader, Options::default())
.await
}
#[allow(async_fn_in_trait)]
async fn compact_using<'a>(
&'a self,
context: RemoteContextReference<Iri>,
loader: &'a impl Loader,
options: Options<Iri>,
) -> CompactResult
where
(): VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
{
self.compact_with_using(vocabulary::no_vocabulary_mut(), context, loader, options)
.await
}
#[allow(async_fn_in_trait)]
async fn compact<'a>(
&'a self,
context: RemoteContextReference<Iri>,
loader: &'a impl Loader,
) -> CompactResult
where
(): VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
{
self.compact_with(vocabulary::no_vocabulary_mut(), context, loader)
.await
}
#[allow(async_fn_in_trait)]
async fn flatten_full<'a, N>(
&'a self,
vocabulary: &'a mut N,
generator: &'a mut impl Generator<N>,
context: Option<RemoteContextReference<Iri>>,
loader: &'a impl Loader,
options: Options<Iri>,
warnings: impl 'a + context_processing::WarningHandler<N> + expansion::WarningHandler<N>,
) -> FlattenResult<Iri, N::BlankId>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
N::BlankId: 'a + Clone + Eq + Hash;
#[allow(async_fn_in_trait)]
async fn flatten_with_using<'a, N>(
&'a self,
vocabulary: &'a mut N,
generator: &'a mut impl Generator<N>,
loader: &'a impl Loader,
options: Options<Iri>,
) -> FlattenResult<Iri, N::BlankId>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
N::BlankId: 'a + Clone + Eq + Hash,
{
self.flatten_full(vocabulary, generator, None, loader, options, ())
.await
}
#[allow(async_fn_in_trait)]
async fn flatten_with<'a, N>(
&'a self,
vocabulary: &'a mut N,
generator: &'a mut impl Generator<N>,
loader: &'a impl Loader,
) -> FlattenResult<Iri, N::BlankId>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
N::BlankId: 'a + Clone + Eq + Hash,
{
self.flatten_with_using(vocabulary, generator, loader, Options::default())
.await
}
#[allow(async_fn_in_trait)]
async fn flatten_using<'a>(
&'a self,
generator: &'a mut impl Generator,
loader: &'a impl Loader,
options: Options<Iri>,
) -> FlattenResult<Iri, BlankIdBuf>
where
(): VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
{
self.flatten_with_using(vocabulary::no_vocabulary_mut(), generator, loader, options)
.await
}
#[allow(async_fn_in_trait)]
async fn flatten<'a>(
&'a self,
generator: &'a mut impl Generator,
loader: &'a impl Loader,
) -> FlattenResult<Iri, BlankIdBuf>
where
(): VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
{
self.flatten_with(vocabulary::no_vocabulary_mut(), generator, loader)
.await
}
#[allow(async_fn_in_trait)]
async fn to_rdf_full<N, G>(
&self,
mut vocabulary: N,
generator: G,
loader: &impl Loader,
options: Options<Iri>,
warnings: impl context_processing::WarningHandler<N> + expansion::WarningHandler<N>,
) -> ToRdfResult<N, G>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
N::BlankId: Clone + Eq + Hash,
G: Generator<N>,
{
let rdf_direction = options.rdf_direction;
let produce_generalized_rdf = options.produce_generalized_rdf;
let expanded_input = self
.expand_full(&mut vocabulary, loader, options.unordered(), warnings)
.await
.map_err(ToRdfError::Expand)?;
Ok(ToRdf::new(
vocabulary,
generator,
expanded_input,
rdf_direction,
produce_generalized_rdf,
))
}
#[allow(async_fn_in_trait)]
async fn to_rdf_with_using<N, G>(
&self,
vocabulary: N,
generator: G,
loader: &impl Loader,
options: Options<Iri>,
) -> ToRdfResult<N, G>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
N::BlankId: Clone + Eq + Hash,
G: Generator<N>,
{
self.to_rdf_full(vocabulary, generator, loader, options, ())
.await
}
#[allow(async_fn_in_trait)]
async fn to_rdf_with<N, G>(
&self,
vocabulary: N,
generator: G,
loader: &impl Loader,
) -> ToRdfResult<N, G>
where
N: VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
N::BlankId: Clone + Eq + Hash,
G: Generator<N>,
{
self.to_rdf_full(vocabulary, generator, loader, Options::default(), ())
.await
}
#[allow(async_fn_in_trait)]
async fn to_rdf_using<G>(
&self,
generator: G,
loader: &impl Loader,
options: Options<Iri>,
) -> ToRdfResult<(), G>
where
(): VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
G: Generator,
{
self.to_rdf_with_using((), generator, loader, options).await
}
#[allow(async_fn_in_trait)]
async fn to_rdf<G>(&self, generator: G, loader: &impl Loader) -> ToRdfResult<(), G>
where
(): VocabularyMut<Iri = Iri>,
Iri: Clone + Eq + Hash,
G: Generator,
{
self.to_rdf_using(generator, loader, Options::default())
.await
}
}
pub struct ToRdf<V: Vocabulary, G> {
vocabulary: V,
generator: G,
doc: ExpandedDocument<V::Iri, V::BlankId>,
rdf_direction: Option<RdfDirection>,
produce_generalized_rdf: bool,
}
impl<V: Vocabulary, G: rdf_types::Generator<V>> ToRdf<V, G> {
fn new(
mut vocabulary: V,
mut generator: G,
mut doc: ExpandedDocument<V::Iri, V::BlankId>,
rdf_direction: Option<RdfDirection>,
produce_generalized_rdf: bool,
) -> Self
where
V::Iri: Clone + Eq + Hash,
V::BlankId: Clone + Eq + Hash,
{
doc.relabel_and_canonicalize_with(&mut vocabulary, &mut generator);
Self {
vocabulary,
generator,
doc,
rdf_direction,
produce_generalized_rdf,
}
}
pub fn quads(&mut self) -> json_ld_core::rdf::Quads<'_, V, G> {
self.doc.rdf_quads_full(
&mut self.vocabulary,
&mut self.generator,
self.rdf_direction,
self.produce_generalized_rdf,
)
}
#[inline(always)]
pub fn cloned_quads(&mut self) -> json_ld_core::rdf::ClonedQuads<'_, V, G> {
self.quads().cloned()
}
pub fn vocabulary(&self) -> &V {
&self.vocabulary
}
pub fn vocabulary_mut(&mut self) -> &mut V {
&mut self.vocabulary
}
pub fn into_vocabulary(self) -> V {
self.vocabulary
}
pub fn generator(&self) -> &G {
&self.generator
}
pub fn generator_mut(&mut self) -> &mut G {
&mut self.generator
}
pub fn into_generator(self) -> G {
self.generator
}
pub fn document(&self) -> &ExpandedDocument<V::Iri, V::BlankId> {
&self.doc
}
pub fn document_mut(&mut self) -> &mut ExpandedDocument<V::Iri, V::BlankId> {
&mut self.doc
}
pub fn into_document(self) -> ExpandedDocument<V::Iri, V::BlankId> {
self.doc
}
}
async fn compact_expanded_full<'a, T, N, L>(
expanded_input: &'a T,
url: Option<&'a N::Iri>,
vocabulary: &'a mut N,
context: RemoteContextReference<N::Iri>,
loader: &'a L,
options: Options<N::Iri>,
warnings: impl context_processing::WarningHandler<N>,
) -> Result<json_syntax::Value, CompactError>
where
N: VocabularyMut,
N::Iri: Clone + Eq + Hash,
N::BlankId: 'a + Clone + Eq + Hash,
T: Compact<N::Iri, N::BlankId>,
L: Loader,
{
let context_base = url.or(options.base.as_ref());
let context = context
.load_context_with(vocabulary, loader)
.await
.map_err(CompactError::ContextLoading)?
.into_document();
let mut active_context = context
.process_full(
vocabulary,
&Context::new(None),
loader,
context_base.cloned(),
options.context_processing_options(),
warnings,
)
.await
.map_err(CompactError::ContextProcessing)?;
match options.base.as_ref() {
Some(base) => active_context.set_base_iri(Some(base.clone())),
None => {
if options.compact_to_relative && active_context.base_iri().is_none() {
active_context.set_base_iri(url.cloned());
}
}
}
expanded_input
.compact_full(
vocabulary,
active_context.as_ref(),
loader,
options.compaction_options(),
)
.await
.map_err(CompactError::Compaction)
}
#[cfg(test)]
mod tests {
use futures::Future;
use json_ld_core::{NoLoader, RemoteDocument};
use json_syntax::Value;
use rdf_types::generator;
use crate::JsonLdProcessor;
async fn assert_send<F: Future + Send>(f: F) -> F::Output {
f.await
}
#[async_std::test]
async fn to_rdf_is_send() {
let generator = generator::Blank::new();
let document = RemoteDocument::new(None, None, Value::Null);
let f = document.to_rdf(generator, &NoLoader);
let _ = assert_send(f).await;
}
}