use algorithm::{Action, RejectVocab};
pub use jsonld_core::{Context, ProcessingMode, warning};
use jsonld_core::{ExtractContextError, LoadError, Loader};
use jsonld_syntax::ErrorCode;
use rdfx::vocabulary::VocabularyMut;
use std::{fmt, hash::Hash};
pub mod algorithm;
mod cache;
mod processed;
mod stack;
pub use cache::ProcessingCache;
pub use processed::*;
pub use stack::{MAX_REMOTE_CONTEXTS, ProcessingStack};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Warning {
KeywordLikeTerm(String),
KeywordLikeValue(String),
MalformedIri(String),
}
impl fmt::Display for Warning {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::KeywordLikeTerm(s) => write!(f, "keyword-like term `{s}`"),
Self::KeywordLikeValue(s) => write!(f, "keyword-like value `{s}`"),
Self::MalformedIri(s) => write!(f, "malformed IRI `{s}`"),
}
}
}
impl<N> contextual::DisplayWithContext<N> for Warning {
fn fmt_with(&self, _: &N, f: &mut fmt::Formatter) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
pub trait WarningHandler<N>: jsonld_core::warning::Handler<N, Warning> {}
impl<N, H> WarningHandler<N> for H where H: jsonld_core::warning::Handler<N, Warning> {}
#[derive(Debug, thiserror::Error)]
pub enum Error<E = std::convert::Infallible> {
#[error("Invalid context nullification")]
InvalidContextNullification,
#[error("Remote document loading failed")]
LoadingDocumentFailed,
#[error("Processing mode conflict")]
ProcessingModeConflict,
#[error("Invalid `@context` entry")]
InvalidContextEntry,
#[error("Invalid `@import` value")]
InvalidImportValue,
#[error("Invalid remote context")]
InvalidRemoteContext,
#[error("Invalid base IRI")]
InvalidBaseIri,
#[error("Invalid vocabulary mapping")]
InvalidVocabMapping,
#[error("Cyclic IRI mapping")]
CyclicIriMapping,
#[error("Invalid term definition")]
InvalidTermDefinition,
#[error("Keyword redefinition")]
KeywordRedefinition,
#[error("Invalid `@protected` value")]
InvalidProtectedValue,
#[error("Invalid type mapping")]
InvalidTypeMapping,
#[error("Invalid reverse property")]
InvalidReverseProperty,
#[error("Invalid IRI mapping")]
InvalidIriMapping,
#[error("Invalid keyword alias")]
InvalidKeywordAlias,
#[error("Invalid container mapping")]
InvalidContainerMapping,
#[error("Invalid scoped context")]
InvalidScopedContext,
#[error("Protected term redefinition")]
ProtectedTermRedefinition,
#[error(transparent)]
ContextLoadingFailed(#[from] LoadError<E>),
#[error("Unable to extract JSON-LD context: {0}")]
ContextExtractionFailed(ExtractContextError),
#[error("Recursive context inclusion")]
RecursiveContextInclusion,
#[error("Context overflow")]
ContextOverflow,
#[error("Use of forbidden `@vocab`")]
ForbiddenVocab,
}
impl<E> From<RejectVocab> for Error<E> {
fn from(_value: RejectVocab) -> Self {
Self::ForbiddenVocab
}
}
impl<E> Error<E> {
pub fn code(&self) -> ErrorCode {
match self {
Self::InvalidContextNullification => ErrorCode::InvalidContextNullification,
Self::LoadingDocumentFailed => ErrorCode::LoadingDocumentFailed,
Self::ProcessingModeConflict => ErrorCode::ProcessingModeConflict,
Self::InvalidContextEntry => ErrorCode::InvalidContextEntry,
Self::InvalidImportValue => ErrorCode::InvalidImportValue,
Self::InvalidRemoteContext => ErrorCode::InvalidRemoteContext,
Self::InvalidBaseIri => ErrorCode::InvalidBaseIri,
Self::InvalidVocabMapping => ErrorCode::InvalidVocabMapping,
Self::CyclicIriMapping => ErrorCode::CyclicIriMapping,
Self::InvalidTermDefinition => ErrorCode::InvalidTermDefinition,
Self::KeywordRedefinition => ErrorCode::KeywordRedefinition,
Self::InvalidProtectedValue => ErrorCode::InvalidProtectedValue,
Self::InvalidTypeMapping => ErrorCode::InvalidTypeMapping,
Self::InvalidReverseProperty => ErrorCode::InvalidReverseProperty,
Self::InvalidIriMapping => ErrorCode::InvalidIriMapping,
Self::InvalidKeywordAlias => ErrorCode::InvalidKeywordAlias,
Self::InvalidContainerMapping => ErrorCode::InvalidContainerMapping,
Self::InvalidScopedContext => ErrorCode::InvalidScopedContext,
Self::ProtectedTermRedefinition => ErrorCode::ProtectedTermRedefinition,
Self::ContextLoadingFailed(_) => ErrorCode::LoadingRemoteContextFailed,
Self::ContextExtractionFailed(_) => ErrorCode::LoadingRemoteContextFailed,
Self::RecursiveContextInclusion => ErrorCode::RecursiveContextInclusion,
Self::ContextOverflow => ErrorCode::ContextOverflow,
Self::ForbiddenVocab => ErrorCode::InvalidVocabMapping,
}
}
}
pub type ProcessingResult<'a, T, B, E> = Result<Processed<'a, T, B>, Error<E>>;
pub trait Process {
async fn process_full<N, L, W>(
&self,
vocabulary: &mut N,
active_context: &Context<N::Iri, N::BlankId>,
loader: &L,
base_url: Option<N::Iri>,
options: Options,
warnings: W,
) -> Result<Processed<'_, N::Iri, N::BlankId>, Error<L::Error>>
where
N: VocabularyMut,
N::Iri: Clone + Eq + Hash,
N::BlankId: Clone + PartialEq,
L: Loader,
W: WarningHandler<N>;
async fn process_full_with_cache<N, L, W>(
&self,
vocabulary: &mut N,
active_context: &Context<N::Iri, N::BlankId>,
loader: &L,
base_url: Option<N::Iri>,
options: Options,
warnings: W,
cache: &ProcessingCache<N::Iri, N::BlankId>,
) -> Result<Processed<'_, N::Iri, N::BlankId>, Error<L::Error>>
where
N: VocabularyMut,
N::Iri: Clone + Eq + Hash,
N::BlankId: Clone + Eq + Hash,
L: Loader,
W: WarningHandler<N>;
async fn process_with_cache<N, L>(
&self,
vocabulary: &mut N,
active_context: &Context<N::Iri, N::BlankId>,
loader: &L,
base_url: Option<N::Iri>,
options: Options,
cache: &ProcessingCache<N::Iri, N::BlankId>,
) -> Result<Processed<'_, N::Iri, N::BlankId>, Error<L::Error>>
where
N: VocabularyMut,
N::Iri: Clone + Eq + Hash,
N::BlankId: Clone + Eq + Hash,
L: Loader,
{
self.process_full_with_cache(vocabulary, active_context, loader, base_url, options, warning::Print, cache)
.await
}
async fn process_with<N, L>(
&self,
vocabulary: &mut N,
active_context: &Context<N::Iri, N::BlankId>,
loader: &L,
base_url: Option<N::Iri>,
options: Options,
) -> ProcessingResult<'_, N::Iri, N::BlankId, L::Error>
where
N: VocabularyMut,
N::Iri: Clone + Eq + Hash,
N::BlankId: Clone + PartialEq,
L: Loader,
{
self.process_full(vocabulary, active_context, loader, base_url, options, warning::Print).await
}
async fn process<N, L>(&self, vocabulary: &mut N, loader: &L, base_url: Option<N::Iri>) -> Result<Processed<'_, N::Iri, N::BlankId>, Error<L::Error>>
where
N: VocabularyMut,
N::Iri: Clone + Eq + Hash,
N::BlankId: Clone + PartialEq,
L: Loader,
{
let active_context = Context::default();
self.process_full(vocabulary, &active_context, loader, base_url, Options::default(), warning::Print)
.await
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Options {
pub processing_mode: ProcessingMode,
pub override_protected: bool,
pub propagate: bool,
pub vocab: Action,
}
impl Options {
#[must_use]
pub fn with_override(&self) -> Options {
let mut opt = *self;
opt.override_protected = true;
opt
}
#[must_use]
pub fn with_no_override(&self) -> Options {
let mut opt = *self;
opt.override_protected = false;
opt
}
#[must_use]
pub fn without_propagation(&self) -> Options {
let mut opt = *self;
opt.propagate = false;
opt
}
}
impl Default for Options {
fn default() -> Options {
Options {
processing_mode: ProcessingMode::default(),
override_protected: false,
propagate: true,
vocab: Action::Keep,
}
}
}