use jsonld_context_processing::{Options as ProcessingOptions, Process};
use jsonld_core::{
Context,
ContextRef,
IndexSet,
Indexed,
Loader,
ProcessingMode,
Term,
Value,
context::inverse::{LangSelection, TypeSelection},
object::Any,
};
use jsonld_syntax::{ContainerKind, ErrorCode, Keyword};
use rdfx::vocabulary::{self, VocabularyMut};
use std::hash::Hash;
mod document;
mod iri;
mod node;
mod property;
mod value;
pub use document::*;
pub use iri::IriConfusedWithPrefix;
pub(crate) use iri::{compact_iri, compact_iri_with, compact_iri_with_memo};
use node::compact_indexed_node_with;
use property::compact_property;
use value::compact_indexed_value_with;
#[derive(Debug, thiserror::Error)]
pub enum Error<E = std::convert::Infallible> {
#[error("IRI confused with prefix")]
IriConfusedWithPrefix,
#[error("Invalid `@nest` value")]
InvalidNestValue,
#[error("Colliding compacted entry")]
CollidingEntry,
#[error("Context processing failed: {0}")]
ContextProcessing(jsonld_context_processing::Error<E>),
}
impl<E> Error<E> {
pub fn code(&self) -> ErrorCode {
match self {
Self::IriConfusedWithPrefix => ErrorCode::IriConfusedWithPrefix,
Self::InvalidNestValue => ErrorCode::InvalidNestValue,
Self::CollidingEntry => ErrorCode::ConflictingIndexes,
Self::ContextProcessing(e) => e.code(),
}
}
}
impl<E> From<jsonld_context_processing::Error<E>> for Error<E> {
fn from(e: jsonld_context_processing::Error<E>) -> Self {
Self::ContextProcessing(e)
}
}
impl<E> From<IriConfusedWithPrefix> for Error<E> {
fn from(_: IriConfusedWithPrefix) -> Self {
Self::IriConfusedWithPrefix
}
}
pub type CompactFragmentResult<E> = Result<jstrict::Value, Error<E>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Options {
pub processing_mode: ProcessingMode,
pub compact_to_relative: bool,
pub compact_arrays: bool,
pub ordered: bool,
}
impl From<Options> for jsonld_context_processing::Options {
fn from(options: Options) -> jsonld_context_processing::Options {
jsonld_context_processing::Options {
processing_mode: options.processing_mode,
..Default::default()
}
}
}
impl From<jsonld_expansion::Options> for Options {
fn from(options: jsonld_expansion::Options) -> Options {
Options {
processing_mode: options.processing_mode,
ordered: options.ordered,
..Options::default()
}
}
}
impl Default for Options {
fn default() -> Options {
Options {
processing_mode: ProcessingMode::default(),
compact_to_relative: true,
compact_arrays: true,
ordered: false,
}
}
}
pub trait CompactFragment<I, B> {
async fn compact_fragment_full<'a, N, L>(
&'a self,
vocabulary: &'a mut N,
active_context: &'a Context<I, B>,
type_scoped_context: &'a Context<I, B>,
active_property: Option<&'a str>,
loader: &'a L,
options: Options,
) -> CompactFragmentResult<L::Error>
where
N: VocabularyMut<Iri = I, BlankId = B>,
I: Clone + Hash + Eq,
B: Clone + Hash + Eq,
L: Loader;
#[inline(always)]
async fn compact_fragment_with<'a, N, L>(
&'a self,
vocabulary: &'a mut N,
active_context: &'a Context<I, B>,
loader: &'a mut L,
) -> CompactFragmentResult<L::Error>
where
N: VocabularyMut<Iri = I, BlankId = B>,
I: Clone + Hash + Eq,
B: Clone + Hash + Eq,
L: Loader,
{
self.compact_fragment_full(vocabulary, active_context, active_context, None, loader, Options::default())
.await
}
#[inline(always)]
async fn compact_fragment<'a, L>(&'a self, active_context: &'a Context<I, B>, loader: &'a mut L) -> CompactFragmentResult<L::Error>
where
(): VocabularyMut<Iri = I, BlankId = B>,
I: Clone + Hash + Eq,
B: Clone + Hash + Eq,
L: Loader,
{
self.compact_fragment_full(
vocabulary::no_vocabulary_mut(),
active_context,
active_context,
None,
loader,
Options::default(),
)
.await
}
}
#[derive(PartialEq)]
enum TypeLangValue<'a, I> {
Type(TypeSelection<I>),
Lang(LangSelection<'a>),
}
pub trait CompactIndexedFragment<I, B> {
async fn compact_indexed_fragment<'a, N, L>(
&'a self,
vocabulary: &'a mut N,
index: Option<&'a str>,
active_context: &'a Context<I, B>,
type_scoped_context: &'a Context<I, B>,
active_property: Option<&'a str>,
loader: &'a L,
options: Options,
) -> CompactFragmentResult<L::Error>
where
N: VocabularyMut<Iri = I, BlankId = B>,
I: Clone + Hash + Eq,
B: Clone + Hash + Eq,
L: Loader;
}
impl<I, B, T: CompactIndexedFragment<I, B>> CompactFragment<I, B> for Indexed<T> {
async fn compact_fragment_full<'a, N, L>(
&'a self,
vocabulary: &'a mut N,
active_context: &'a Context<I, B>,
type_scoped_context: &'a Context<I, B>,
active_property: Option<&'a str>,
loader: &'a L,
options: Options,
) -> CompactFragmentResult<L::Error>
where
N: VocabularyMut<Iri = I, BlankId = B>,
I: Clone + Hash + Eq,
B: Clone + Hash + Eq,
L: Loader,
{
self.inner()
.compact_indexed_fragment(vocabulary, self.index(), active_context, type_scoped_context, active_property, loader, options)
.await
}
}
impl<I, B, T: Any<I, B>> CompactIndexedFragment<I, B> for T {
async fn compact_indexed_fragment<'a, N, L>(
&'a self,
vocabulary: &'a mut N,
index: Option<&'a str>,
active_context: &'a Context<I, B>,
type_scoped_context: &'a Context<I, B>,
active_property: Option<&'a str>,
loader: &'a L,
options: Options,
) -> CompactFragmentResult<L::Error>
where
N: VocabularyMut<Iri = I, BlankId = B>,
I: Clone + Hash + Eq,
B: Clone + Hash + Eq,
L: Loader,
{
use jsonld_core::object::Ref;
match self.as_ref() {
Ref::Value(value) => compact_indexed_value_with(vocabulary, value, index, active_context, active_property, loader, options).await,
Ref::Node(node) => compact_indexed_node_with(vocabulary, node, index, active_context, type_scoped_context, active_property, loader, options).await,
Ref::List(list) => {
let mut active_context = active_context;
if let Some(previous_context) = active_context.previous_context() {
active_context = previous_context;
}
let mut active_context = ContextRef::Borrowed(active_context);
let mut list_container = false;
if let Some(active_property) = active_property
&& let Some(active_property_definition) = type_scoped_context.get(active_property)
{
if let Some(local_context) = active_property_definition.context() {
active_context = ContextRef::owned(
local_context
.process_with(
vocabulary,
active_context.as_ref(),
loader,
active_property_definition.base_url().cloned(),
ProcessingOptions::from(options).with_override(),
)
.await?
.into_processed(),
);
}
list_container = active_property_definition.container().contains(ContainerKind::List);
}
if list_container {
compact_collection_with(
vocabulary,
list.iter(),
active_context.as_ref(),
active_context.as_ref(),
active_property,
loader,
options,
)
.await
} else {
let mut result = jstrict::Object::default();
compact_property(
vocabulary,
&mut result,
Term::Keyword(Keyword::List),
list,
active_context.as_ref(),
loader,
false,
options,
)
.await?;
if let Some(index) = index {
let mut index_container = false;
if let Some(active_property) = active_property
&& let Some(active_property_definition) = active_context.get(active_property)
&& active_property_definition.container().contains(ContainerKind::Index)
{
index_container = true;
}
if !index_container {
let alias = crate::iri::keyword_alias(vocabulary, active_context.as_ref(), options, Keyword::Index);
result.insert(alias.into(), jstrict::Value::String(index.into()));
}
}
Ok(jstrict::Value::Object(result))
}
}
}
}
}
fn add_value(map: &mut jstrict::Object, key: &str, value: jstrict::Value, as_array: bool) {
match value {
jstrict::Value::Array(values) => {
match map.get_unique_mut(key).ok().flatten() {
Some(existing) if !existing.is_array() => {
let prev = std::mem::replace(existing, jstrict::Value::Array(Vec::new()));
if let jstrict::Value::Array(arr) = existing {
arr.push(prev);
}
}
None if as_array => {
map.insert(key.into(), jstrict::Value::Array(Vec::new()));
}
_ => {}
}
for v in values {
add_value(map, key, v, false);
}
}
scalar => match map.get_unique_mut(key).ok().flatten() {
Some(jstrict::Value::Array(arr)) => {
arr.push(scalar);
}
Some(existing) => {
let prev = std::mem::replace(existing, jstrict::Value::Array(Vec::new()));
if let jstrict::Value::Array(arr) = existing {
arr.push(prev);
arr.push(scalar);
}
}
None if as_array => {
map.insert(key.into(), jstrict::Value::Array(vec![scalar]));
}
None => {
map.insert(key.into(), scalar);
}
},
}
}
fn value_value<I>(value: &Value<I>) -> jstrict::Value {
use jsonld_core::object::Literal;
match value {
Value::Literal(lit, _ty) => match lit {
Literal::Null => jstrict::Value::Null,
Literal::Boolean(b) => jstrict::Value::Boolean(*b),
Literal::Number(n) => jstrict::Value::Number(n.clone()),
Literal::String(s) => jstrict::Value::String(s.as_str().into()),
},
Value::LangString(s) => jstrict::Value::String(s.as_str().into()),
Value::Json(json) => json.clone(),
}
}
async fn compact_collection_with<'a, N, L, O, T>(
vocabulary: &'a mut N,
items: O,
active_context: &'a Context<N::Iri, N::BlankId>,
type_scoped_context: &'a Context<N::Iri, N::BlankId>,
active_property: Option<&'a str>,
loader: &'a L,
options: Options,
) -> CompactFragmentResult<L::Error>
where
N: VocabularyMut,
N::Iri: Clone + Hash + Eq,
N::BlankId: Clone + Hash + Eq,
T: 'a + CompactFragment<N::Iri, N::BlankId>,
O: 'a + Iterator<Item = &'a T>,
L: Loader,
{
let mut result = Vec::new();
for item in items {
let compacted_item = Box::pin(item.compact_fragment_full(vocabulary, active_context, type_scoped_context, active_property, loader, options)).await?;
if !compacted_item.is_null() {
result.push(compacted_item);
}
}
let mut list_or_set = false;
if let Some(active_property) = active_property
&& let Some(active_property_definition) = active_context.get(active_property)
{
list_or_set =
active_property_definition.container().contains(ContainerKind::List) || active_property_definition.container().contains(ContainerKind::Set);
}
if result.len() != 1 || !options.compact_arrays || active_property == Some("@graph") || active_property == Some("@set") || list_or_set {
return Ok(jstrict::Value::Array(result.into_iter().collect()));
}
Ok(result.into_iter().next().unwrap_or(jstrict::Value::Null))
}
impl<T: CompactFragment<I, B>, I, B> CompactFragment<I, B> for IndexSet<T> {
async fn compact_fragment_full<'a, N, L>(
&'a self,
vocabulary: &'a mut N,
active_context: &'a Context<I, B>,
type_scoped_context: &'a Context<I, B>,
active_property: Option<&'a str>,
loader: &'a L,
options: Options,
) -> CompactFragmentResult<L::Error>
where
N: VocabularyMut<Iri = I, BlankId = B>,
I: Clone + Hash + Eq,
B: Clone + Hash + Eq,
L: Loader,
{
compact_collection_with(vocabulary, self.iter(), active_context, type_scoped_context, active_property, loader, options).await
}
}
impl<T: CompactFragment<I, B>, I, B> CompactFragment<I, B> for Vec<T> {
async fn compact_fragment_full<'a, N, L>(
&'a self,
vocabulary: &'a mut N,
active_context: &'a Context<I, B>,
type_scoped_context: &'a Context<I, B>,
active_property: Option<&'a str>,
loader: &'a L,
options: Options,
) -> CompactFragmentResult<L::Error>
where
N: VocabularyMut<Iri = I, BlankId = B>,
I: Clone + Hash + Eq,
B: Clone + Hash + Eq,
L: Loader,
{
compact_collection_with(vocabulary, self.iter(), active_context, type_scoped_context, active_property, loader, options).await
}
}
impl<T: CompactFragment<I, B>, I, B> CompactFragment<I, B> for [T] {
async fn compact_fragment_full<'a, N, L>(
&'a self,
vocabulary: &'a mut N,
active_context: &'a Context<I, B>,
type_scoped_context: &'a Context<I, B>,
active_property: Option<&'a str>,
loader: &'a L,
options: Options,
) -> CompactFragmentResult<L::Error>
where
N: VocabularyMut<Iri = I, BlankId = B>,
I: Clone + Hash + Eq,
B: Clone + Hash + Eq,
L: Loader,
{
compact_collection_with(vocabulary, self.iter(), active_context, type_scoped_context, active_property, loader, options).await
}
}