use std::{hash::Hash, sync::Arc};
use crate::{Error, Options, Process, Processed, ProcessingCache, ProcessingResult, ProcessingStack, WarningHandler, cache::cache_key};
use iri_rs::{Iri, IriBuf};
use jsonld_core::{Context, Environment, ExtractContext, Loader, ProcessingMode, Term};
use jsonld_syntax::{self as syntax, Nullable, native_stack::Recursion};
use rdfx::vocabulary::VocabularyMut;
mod define;
mod iri;
mod merged;
mod sync;
pub use define::*;
pub use iri::*;
pub use merged::*;
use sync::{process_context_sync, requires_loader};
use syntax::context::definition::KeyOrKeywordRef;
pub(crate) fn is_valid_vocab_json_ld_1_0(value: &syntax::context::definition::Vocab) -> bool {
value.as_str().starts_with("_:") || Iri::new(value.as_str()).is_ok()
}
impl Process for syntax::context::Context {
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,
mut 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>,
{
if !requires_loader(self) {
return process_context_sync(
Environment {
vocabulary,
loader,
warnings: &mut warnings,
},
active_context,
self,
ProcessingStack::default(),
base_url,
options,
0,
);
}
process_context(
Environment {
vocabulary,
loader,
warnings: &mut warnings,
},
active_context,
self,
ProcessingStack::default(),
base_url,
options,
true,
)
.await
}
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,
mut 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>,
{
let key = cache_key(active_context, self, base_url.as_ref(), options);
if let Some(cached) = cache.get(key, active_context, self, base_url.as_ref(), options) {
return Ok(Processed::new(self, (*cached).clone()));
}
let entry_active = active_context.clone();
let entry_base_url = base_url.clone();
let processed = if requires_loader(self) {
process_context(
Environment {
vocabulary,
loader,
warnings: &mut warnings,
},
active_context,
self,
ProcessingStack::default(),
base_url,
options,
true,
)
.await?
} else {
process_context_sync(
Environment {
vocabulary,
loader,
warnings: &mut warnings,
},
active_context,
self,
ProcessingStack::default(),
base_url,
options,
0,
)?
};
cache.insert(key, entry_active, self.clone(), entry_base_url, options, Arc::new(processed.processed.clone()));
Ok(processed)
}
}
fn resolve_iri<V, I>(vocabulary: &mut V, iri_ref: iri_rs::IriRef<&str>, base_iri: Option<&I>) -> Option<I>
where
V: rdfx::vocabulary::IriVocabularyMut<Iri = I>,
{
if let Some(base_iri) = base_iri {
let base = vocabulary.iri(base_iri)?;
let resolved = iri_ref.resolved(&base).ok()?;
let iri_buf = IriBuf::try_from(resolved).ok()?;
Some(vocabulary.insert_owned(iri_buf))
} else {
let iri = Iri::try_from(iri_ref).ok()?;
Some(vocabulary.insert(iri))
}
}
async fn process_context<'l: 'a, 'a, N, L, W>(
mut env: Environment<'a, N, L, W>,
active_context: &'a Context<N::Iri, N::BlankId>,
local_context: &'l syntax::context::Context,
mut remote_contexts: ProcessingStack<N::Iri>,
base_url: Option<N::Iri>,
mut options: Options,
validate_scoped_context: bool,
) -> ProcessingResult<'l, N::Iri, N::BlankId, L::Error>
where
N: VocabularyMut,
N::Iri: Clone + Eq + Hash,
N::BlankId: Clone + PartialEq,
L: Loader,
W: WarningHandler<N>,
{
let mut result = active_context.clone();
result.set_processing_mode(options.processing_mode);
if let syntax::context::Context::One(syntax::ContextEntry::Definition(def)) = local_context
&& let Some(propagate) = def.propagate
{
if options.processing_mode == ProcessingMode::JsonLd1_0 {
return Err(Error::InvalidContextEntry);
}
options.propagate = propagate;
}
if !options.propagate && result.previous_context().is_none() {
result.set_previous_context(active_context.clone());
}
for context in local_context {
match context {
syntax::ContextEntry::Null => {
if !options.override_protected && result.has_protected_items() {
return Err(Error::InvalidContextNullification);
}
let previous_result = result;
result = Context::new(active_context.original_base_url().cloned());
result.set_processing_mode(options.processing_mode);
if !options.propagate {
result.set_previous_context(previous_result);
}
}
syntax::ContextEntry::IriRef(iri_ref) => {
let context_iri = resolve_iri(env.vocabulary, iri_ref.as_ref(), base_url.as_ref()).ok_or(Error::LoadingDocumentFailed)?;
if remote_contexts.cycle(&context_iri) {
if options.processing_mode == ProcessingMode::JsonLd1_0 {
return Err(Error::RecursiveContextInclusion);
}
if !validate_scoped_context {
continue;
}
}
if remote_contexts.len() >= crate::MAX_REMOTE_CONTEXTS {
return Err(Error::ContextOverflow);
}
remote_contexts.enter(context_iri.clone());
let loaded_context = if let Some(previously) = remote_contexts.dereferenced(&context_iri) {
previously
} else {
let loaded = Arc::new(
env.loader
.load_with(env.vocabulary, context_iri.clone())
.await?
.into_document()
.into_ld_context()
.map_err(Error::ContextExtractionFailed)?,
);
remote_contexts.remember_dereferenced(context_iri.clone(), Arc::clone(&loaded));
loaded
};
let new_options = Options {
processing_mode: options.processing_mode,
override_protected: false,
propagate: true,
vocab: options.vocab,
};
let r = Recursion::new(process_context(
Environment {
vocabulary: env.vocabulary,
loader: env.loader,
warnings: env.warnings,
},
&result,
&loaded_context,
remote_contexts.clone(),
Some(context_iri),
new_options,
validate_scoped_context,
))
.await?;
result = r.into_processed();
}
syntax::ContextEntry::Definition(context) => {
if context.version.is_some() {
if options.processing_mode == ProcessingMode::JsonLd1_0 {
return Err(Error::ProcessingModeConflict);
}
}
let import_context = match &context.import {
Some(import_value) => {
if options.processing_mode == ProcessingMode::JsonLd1_0 {
return Err(Error::InvalidContextEntry);
}
let import = resolve_iri(env.vocabulary, import_value.as_ref(), base_url.as_ref()).ok_or(Error::InvalidImportValue)?;
let import_context = env
.loader
.load_with(env.vocabulary, import.clone())
.await?
.into_document()
.into_ld_context()
.map_err(Error::ContextExtractionFailed)?;
match &import_context {
syntax::context::Context::One(syntax::ContextEntry::Definition(import_context_def)) => {
if import_context_def.import.is_some() {
return Err(Error::InvalidContextEntry);
}
}
_ => {
return Err(Error::InvalidRemoteContext);
}
}
Some(import_context)
}
None => None,
};
let context = Merged::new(context, import_context);
if remote_contexts.is_empty() {
if let Some(value) = context.base() {
match value {
syntax::Nullable::Null => {
result.set_base_iri(None);
}
syntax::Nullable::Some(iri_ref) => {
if let Ok(iri) = Iri::try_from(iri_ref.as_ref()) {
result.set_base_iri(Some(env.vocabulary.insert(iri)));
} else {
let resolved = resolve_iri(env.vocabulary, iri_ref.as_ref(), result.base_iri()).ok_or(Error::InvalidBaseIri)?;
result.set_base_iri(Some(resolved));
}
}
}
}
}
if let Some(value) = context.vocab() {
match value {
syntax::Nullable::Null => {
result.set_vocabulary(None);
}
syntax::Nullable::Some(value) => {
let json_ld_1_0 = options.processing_mode == ProcessingMode::JsonLd1_0;
if json_ld_1_0 && !is_valid_vocab_json_ld_1_0(value) {
return Err(Error::InvalidVocabMapping);
}
match expand_iri_simple(&mut env, &result, Nullable::Some(value.into()), !json_ld_1_0, Some(options.vocab))? {
Some(arc) if matches!(arc.as_ref(), Term::Id(_)) => {
let term = std::sync::Arc::try_unwrap(arc).unwrap_or_else(|a| (*a).clone());
result.set_vocabulary(Some(term));
}
_ => return Err(Error::InvalidVocabMapping),
}
}
}
}
if let Some(value) = context.language() {
match value {
Nullable::Null => {
result.set_default_language(None);
}
Nullable::Some(tag) => {
result.set_default_language(Some(tag.to_owned()));
}
}
}
if let Some(value) = context.direction() {
if options.processing_mode == ProcessingMode::JsonLd1_0 {
return Err(Error::InvalidContextEntry);
}
match value {
Nullable::Null => {
result.set_default_base_direction(None);
}
Nullable::Some(dir) => {
result.set_default_base_direction(Some(dir));
}
}
}
let mut defined = DefinedTerms::new();
let protected = context.protected().unwrap_or(false);
if context.type_().is_some() {
define(
Environment {
vocabulary: env.vocabulary,
loader: env.loader,
warnings: env.warnings,
},
&mut result,
&context,
KeyOrKeywordRef::Keyword(syntax::Keyword::Type),
&mut defined,
remote_contexts.clone(),
base_url.clone(),
protected,
options,
)
.await?;
}
for (key, _binding) in context.bindings() {
define(
Environment {
vocabulary: env.vocabulary,
loader: env.loader,
warnings: env.warnings,
},
&mut result,
&context,
key.into(),
&mut defined,
remote_contexts.clone(),
base_url.clone(),
protected,
options,
)
.await?;
}
}
}
}
Ok(Processed::new(local_context, result))
}