use indexmap::IndexSet;
use json_ld_context_processing::{Options as ProcessingOptions, Process};
use json_ld_core::{
context::inverse::{LangSelection, TypeSelection},
object::Any,
Context, Indexed, Loader, ProcessingMode, Term, Value,
};
use json_ld_syntax::{ContainerKind, ErrorCode, Keyword};
use json_syntax::object::Entry;
use mown::Mown;
use rdf_types::{vocabulary, VocabularyMut};
use std::hash::Hash;
mod document;
mod iri;
mod node;
mod property;
mod value;
pub use document::*;
pub(crate) use iri::*;
use node::*;
use property::*;
use value::*;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("IRI confused with prefix")]
IriConfusedWithPrefix,
#[error("Invalid `@nest` value")]
InvalidNestValue,
#[error("Context processing failed: {0}")]
ContextProcessing(json_ld_context_processing::Error),
}
impl Error {
pub fn code(&self) -> ErrorCode {
match self {
Self::IriConfusedWithPrefix => ErrorCode::IriConfusedWithPrefix,
Self::InvalidNestValue => ErrorCode::InvalidNestValue,
Self::ContextProcessing(e) => e.code(),
}
}
}
impl From<json_ld_context_processing::Error> for Error {
fn from(e: json_ld_context_processing::Error) -> Self {
Self::ContextProcessing(e)
}
}
impl From<IriConfusedWithPrefix> for Error {
fn from(_: IriConfusedWithPrefix) -> Self {
Self::IriConfusedWithPrefix
}
}
pub type CompactFragmentResult = Result<json_syntax::Value, Error>;
#[derive(Clone, Copy)]
pub struct Options {
pub processing_mode: ProcessingMode,
pub compact_to_relative: bool,
pub compact_arrays: bool,
pub ordered: bool,
}
impl Options {
pub fn unordered(self) -> Self {
Self {
ordered: false,
..self
}
}
}
impl From<Options> for json_ld_context_processing::Options {
fn from(options: Options) -> json_ld_context_processing::Options {
json_ld_context_processing::Options {
processing_mode: options.processing_mode,
..Default::default()
}
}
}
impl From<json_ld_expansion::Options> for Options {
fn from(options: json_ld_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> {
#[allow(async_fn_in_trait)]
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
where
N: VocabularyMut<Iri = I, BlankId = B>,
I: Clone + Hash + Eq,
B: Clone + Hash + Eq,
L: Loader;
#[allow(async_fn_in_trait)]
#[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
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
}
#[allow(async_fn_in_trait)]
#[inline(always)]
async fn compact_fragment<'a, L>(
&'a self,
active_context: &'a Context<I, B>,
loader: &'a mut L,
) -> CompactFragmentResult
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
}
}
enum TypeLangValue<'a, I> {
Type(TypeSelection<I>),
Lang(LangSelection<'a>),
}
pub trait CompactIndexedFragment<I, B> {
#[allow(async_fn_in_trait)]
#[allow(clippy::too_many_arguments)]
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
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
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
where
N: VocabularyMut<Iri = I, BlankId = B>,
I: Clone + Hash + Eq,
B: Clone + Hash + Eq,
L: Loader,
{
use json_ld_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 = Mown::Borrowed(active_context);
let mut list_container = false;
if let Some(active_property) = active_property {
if let Some(active_property_definition) =
type_scoped_context.get(active_property)
{
if let Some(local_context) = active_property_definition.context() {
active_context = Mown::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 = json_syntax::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 {
if let Some(active_property_definition) =
active_context.get(active_property)
{
if active_property_definition
.container()
.contains(ContainerKind::Index)
{
index_container = true;
}
}
}
if !index_container {
let alias = compact_key(
vocabulary,
active_context.as_ref(),
&Term::Keyword(Keyword::Index),
true,
false,
options,
)?;
result.insert(alias.unwrap(), json_syntax::Value::String(index.into()));
}
}
Ok(json_syntax::Value::Object(result))
}
}
}
}
}
fn add_value(map: &mut json_syntax::Object, key: &str, value: json_syntax::Value, as_array: bool) {
match map
.get_unique(key)
.ok()
.unwrap()
.map(|entry| entry.is_array())
{
Some(false) => {
let Entry { key, value } = map.remove_unique(key).ok().unwrap().unwrap();
map.insert(key, json_syntax::Value::Array(vec![value]));
}
None if as_array => {
map.insert(key.into(), json_syntax::Value::Array(Vec::new()));
}
_ => (),
}
match value {
json_syntax::Value::Array(values) => {
for value in values {
add_value(map, key, value, false)
}
}
value => {
if let Some(array) = map.get_unique_mut(key).ok().unwrap() {
array.as_array_mut().unwrap().push(value);
return;
}
map.insert(key.into(), value);
}
}
}
fn value_value<I>(value: &Value<I>) -> json_syntax::Value {
use json_ld_core::object::Literal;
match value {
Value::Literal(lit, _ty) => match lit {
Literal::Null => json_syntax::Value::Null,
Literal::Boolean(b) => json_syntax::Value::Boolean(*b),
Literal::Number(n) => json_syntax::Value::Number(n.clone()),
Literal::String(s) => json_syntax::Value::String(s.as_str().into()),
},
Value::LangString(s) => json_syntax::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
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 {
if 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.is_empty()
|| result.len() > 1
|| !options.compact_arrays
|| active_property == Some("@graph")
|| active_property == Some("@set")
|| list_or_set
{
return Ok(json_syntax::Value::Array(result.into_iter().collect()));
}
Ok(result.into_iter().next().unwrap())
}
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
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
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> + Send + Sync, 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
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
}
}