use std::borrow::Cow;
use rdf_syntax::Iri;
mod define;
mod iri;
mod merged;
mod options;
mod stack;
use define::*;
use iri::*;
use merged::Merged;
pub use options::JsonLdContextProcessingOptions;
use stack::ProcessingStack;
use crate::{
AsyncJsonLdLoader, JsonLdBacktraceBuilder, JsonLdContextDocument, JsonLdError,
JsonLdLocatedError, JsonLdNullable, JsonLdProcessedContext, JsonLdProcessingMode,
JsonLdSourceFileRef, JsonLdTerm,
algorithms::AsyncJsonLdEnvironment,
context::RawJsonLdProcessedContext,
syntax::{JsonLdContext, JsonLdContextEntry, JsonLdKeyword, context::JsonLdKeyOrKeywordRef},
};
struct TargetProcessedContext<'a> {
pub value: &'a mut RawJsonLdProcessedContext,
pub defined: DefinedTerms,
}
struct ContextProcessor<'a> {
pub remote_contexts: ProcessingStack,
pub active_context: &'a RawJsonLdProcessedContext,
pub base_url: Option<&'a Iri>,
pub options: JsonLdContextProcessingOptions,
}
impl<'a> ContextProcessor<'a> {
fn for_recursive_definition<'b>(&'b self) -> ContextProcessor<'b> {
ContextProcessor {
remote_contexts: self.remote_contexts.clone(),
active_context: self.active_context,
base_url: None,
options: self.options.with_no_override(),
}
}
fn for_sub_context<'b>(
&'b self,
active_context: &'b RawJsonLdProcessedContext,
base_url: Option<&'b Iri>,
options: JsonLdContextProcessingOptions,
) -> ContextProcessor<'b> {
ContextProcessor {
remote_contexts: self.remote_contexts.clone(),
active_context,
base_url,
options,
}
}
}
impl JsonLdContextDocument {
pub async fn process(
&self,
env: impl AsyncJsonLdEnvironment,
) -> Result<JsonLdProcessedContext<'_>, JsonLdLocatedError> {
let loc = JsonLdBacktraceBuilder::new().file(JsonLdSourceFileRef::original(self.url()));
self.document
.context
.process_with(
env,
self.url(),
&RawJsonLdProcessedContext::new(None),
JsonLdContextProcessingOptions::default(),
loc,
)
.await
}
}
impl JsonLdContext {
pub async fn process(
&self,
env: impl AsyncJsonLdEnvironment,
base_url: Option<&Iri>,
) -> Result<JsonLdProcessedContext<'_>, JsonLdLocatedError> {
let active_context = RawJsonLdProcessedContext::new(None);
self.process_with(
env,
base_url,
&active_context,
JsonLdContextProcessingOptions::default(),
JsonLdBacktraceBuilder::new().file(JsonLdSourceFileRef::original(None)),
)
.await
}
pub async fn process_with(
&self,
env: impl AsyncJsonLdEnvironment,
base_url: Option<&Iri>,
active_context: &RawJsonLdProcessedContext,
options: JsonLdContextProcessingOptions,
location: JsonLdBacktraceBuilder<'_>,
) -> Result<JsonLdProcessedContext<'_>, JsonLdLocatedError> {
ContextProcessor {
options,
remote_contexts: ProcessingStack::new(),
base_url,
active_context,
}
.process(&env, self, location)
.await
.map(|raw| JsonLdProcessedContext::new(Cow::Borrowed(self), raw))
}
}
impl<'a> ContextProcessor<'a> {
async fn process(
mut self,
env: &impl AsyncJsonLdEnvironment,
local_context: &JsonLdContext,
location: JsonLdBacktraceBuilder<'_>,
) -> Result<RawJsonLdProcessedContext, JsonLdLocatedError> {
let mut result = self.active_context.clone();
if let JsonLdContext::One(JsonLdContextEntry::Definition(def)) = local_context
&& let Some(propagate) = def.propagate
{
if self.options.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
return Err(JsonLdError::InvalidContextEntry.at(location));
}
self.options.propagate = propagate
}
if !self.options.propagate && result.previous_context().is_none() {
result.set_previous_context(self.active_context.clone());
}
for (i, context) in local_context.iter().enumerate() {
let entry_loc = location.array_index(i);
match context {
JsonLdContextEntry::Null => {
if !self.options.override_protected && result.has_protected_items() {
return Err(JsonLdError::InvalidContextNullification.at(entry_loc));
} else {
let previous_result = result;
result = RawJsonLdProcessedContext::new(
self.active_context
.original_base_url()
.map(ToOwned::to_owned),
);
if !self.options.propagate {
result.set_previous_context(previous_result);
}
}
}
JsonLdContextEntry::IriRef(iri_ref) => {
let context_iri = resolve_iri(iri_ref, self.base_url)
.ok_or_else(|| JsonLdError::LoadingDocumentFailed.at(entry_loc))?;
if self.remote_contexts.push(context_iri.clone()) {
let loaded_context = env
.loader()
.async_load(&context_iri)
.await
.map_err(|e| JsonLdError::ContextLoadingFailed(e).at(entry_loc))?
.try_into_context_document()
.map_err(|e| {
JsonLdError::RemoteContextSyntax(e.to_string()).at(entry_loc)
})?
.into_document()
.context;
let new_options = JsonLdContextProcessingOptions {
processing_mode: self.options.processing_mode,
override_protected: false,
propagate: true,
};
let remote_loc =
entry_loc.file(JsonLdSourceFileRef::original(Some(&context_iri)));
result = Box::pin(
self.for_sub_context(&result, Some(&context_iri), new_options)
.process(env, &loaded_context, remote_loc),
)
.await?;
}
}
JsonLdContextEntry::Definition(context) => {
if context.version.is_some() {
if self.options.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
return Err(JsonLdError::ProcessingModeConflict.at(entry_loc));
}
}
let mut import_url = None;
let import_context = match &context.import {
Some(import_value) => {
let import_loc = entry_loc.object_value(JsonLdKeyword::Import);
if self.options.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
return Err(JsonLdError::InvalidContextEntry.at(import_loc));
}
let import = resolve_iri(import_value, self.base_url)
.ok_or_else(|| JsonLdError::InvalidImportValue.at(import_loc))?;
let import_context = env
.loader()
.async_load(&import)
.await
.map_err(|e| JsonLdError::ContextLoadingFailed(e).at(import_loc))?
.try_into_context_document()
.map_err(|e| {
JsonLdError::RemoteContextSyntax(e.to_string()).at(import_loc)
})?
.into_document()
.context;
match &import_context {
JsonLdContext::One(JsonLdContextEntry::Definition(
import_context_def,
)) => {
if import_context_def.import.is_some() {
return Err(JsonLdError::InvalidContextEntry.at(import_loc));
}
}
_ => {
return Err(JsonLdError::InvalidRemoteContext.at(import_loc));
}
}
import_url = Some(import);
Some(import_context)
}
None => None,
};
let context = Merged::new(context, import_context);
if self.remote_contexts.is_empty() {
if let Some(value) = context.base() {
match value {
JsonLdNullable::Null => {
result.set_base_iri(None);
}
JsonLdNullable::Some(iri_ref) => match iri_ref.as_iri() {
Some(iri) => result.set_base_iri(Some(iri.to_owned())),
None => {
let resolved = resolve_iri(iri_ref, result.base_iri())
.ok_or_else(|| {
JsonLdError::InvalidBaseIri
.at(entry_loc.object_value(JsonLdKeyword::Base))
})?;
result.set_base_iri(Some(resolved))
}
},
}
}
}
if let Some(value) = context.vocab() {
match value {
JsonLdNullable::Null => {
result.set_vocabulary(None);
}
JsonLdNullable::Some(value) => {
match result.expand_iri_with(
JsonLdNullable::Some(value.into()),
true,
true,
|w| env.warn(w),
) {
JsonLdTerm::Id(vocab) => {
result.set_vocabulary(Some(JsonLdTerm::Id(vocab)))
}
_ => {
return Err(JsonLdError::InvalidVocabMapping
.at(entry_loc.object_value(JsonLdKeyword::Vocab)));
}
}
}
}
}
if let Some(value) = context.language() {
match value {
JsonLdNullable::Null => {
result.set_default_language(None);
}
JsonLdNullable::Some(tag) => {
result.set_default_language(Some(tag.to_owned()));
}
}
}
if let Some(value) = context.direction() {
if self.options.processing_mode == JsonLdProcessingMode::JsonLd1_0 {
return Err(JsonLdError::InvalidContextEntry
.at(entry_loc.object_value(JsonLdKeyword::Direction)));
}
match value {
JsonLdNullable::Null => {
result.set_default_base_direction(None);
}
JsonLdNullable::Some(dir) => {
result.set_default_base_direction(Some(dir));
}
}
}
let mut result = TargetProcessedContext {
value: &mut result,
defined: DefinedTerms::new(),
};
let protected = context.protected().unwrap_or(false);
if context.type_().is_some() {
self.define(
env,
&mut result,
&context,
JsonLdKeyOrKeywordRef::Keyword(JsonLdKeyword::Type),
protected,
entry_loc,
)
.await?
}
for (key, _binding) in context.bindings() {
let binding_loc = if context.key_is_imported(key) {
entry_loc.file(JsonLdSourceFileRef::original(import_url.as_deref()))
} else {
entry_loc
};
self.define(
env,
&mut result,
&context,
key.into(),
protected,
binding_loc,
)
.await?
}
}
}
}
Ok(result)
}
}