mod definition;
pub mod inverse;
mod memo;
use crate::{Direction, LenientLangTag, LenientLangTagBuf, ProcessingMode, Term, ValidId as Id};
use contextual::WithContext;
use iri_rs::IriBuf;
use jsonld_syntax::{Keyword, KeywordType, Nullable};
use once_cell::sync::OnceCell;
use rdfx::{BlankIdBuf, vocabulary::Vocabulary};
use std::{borrow::Borrow, hash::Hash, sync::Arc};
pub use jsonld_syntax::context::{
definition::{Key, KeyOrType, Type},
term_definition::Nest,
};
pub use definition::*;
pub use inverse::InverseContext;
pub use memo::ShardedMemo;
pub enum ContextRef<'a, T = IriBuf, B = BlankIdBuf> {
Borrowed(&'a Context<T, B>),
Owned(Box<Context<T, B>>),
}
impl<T, B> ContextRef<'_, T, B> {
#[inline(always)]
pub fn owned(context: Context<T, B>) -> Self {
Self::Owned(Box::new(context))
}
#[allow(clippy::should_implement_trait)]
#[inline(always)]
#[must_use]
pub fn as_ref(&self) -> &Context<T, B> {
match self {
Self::Borrowed(context) => context,
Self::Owned(context) => context,
}
}
#[inline(always)]
#[must_use]
pub fn is_owned(&self) -> bool {
matches!(self, Self::Owned(_))
}
}
impl<T, B> std::ops::Deref for ContextRef<'_, T, B> {
type Target = Context<T, B>;
#[inline(always)]
fn deref(&self) -> &Self::Target {
self.as_ref()
}
}
#[derive(Debug, Clone, Copy, thiserror::Error)]
pub enum InvalidVocab {
#[error("vocabulary cannot be a keyword")]
Keyword,
}
#[derive(Debug, Clone, Copy, thiserror::Error)]
#[error("invalid context key")]
pub struct InvalidContextKey;
#[derive(Debug, Clone, Copy, thiserror::Error)]
pub enum InvalidContextError {
#[error(transparent)]
InvalidVocab(#[from] InvalidVocab),
#[error(transparent)]
InvalidContextKey(#[from] InvalidContextKey),
#[error("IRI handle does not resolve in the given vocabulary")]
UnresolvedIri,
}
pub type CompactIriKey<T, B> = (Term<T, B>, bool, bool, ProcessingMode);
pub struct CompactIriKeyRef<'a, T, B>(pub &'a Term<T, B>, pub bool, pub bool, pub ProcessingMode);
impl<T: std::hash::Hash, B: std::hash::Hash> std::hash::Hash for CompactIriKeyRef<'_, T, B> {
#[inline]
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
self.1.hash(state);
self.2.hash(state);
self.3.hash(state);
}
}
impl<T: PartialEq, B: PartialEq> hashbrown::Equivalent<CompactIriKey<T, B>> for CompactIriKeyRef<'_, T, B> {
#[inline]
fn equivalent(&self, key: &CompactIriKey<T, B>) -> bool {
self.0 == &key.0 && self.1 == key.1 && self.2 == key.2 && self.3 == key.3
}
}
pub struct KeywordAliases {
aliases: [Box<str>; 13],
}
impl KeywordAliases {
#[must_use]
pub fn new(aliases: [Box<str>; 13]) -> Self {
Self { aliases }
}
#[inline]
#[must_use]
pub fn get(&self, k: Keyword) -> &str {
match keyword_alias_index(k) {
Some(i) => self.aliases[i].as_ref(),
None => k.into_str(),
}
}
}
pub const CACHED_KEYWORDS: [Keyword; 13] = [
Keyword::Id,
Keyword::Type,
Keyword::Value,
Keyword::List,
Keyword::Set,
Keyword::Graph,
Keyword::Index,
Keyword::Language,
Keyword::Direction,
Keyword::Reverse,
Keyword::None,
Keyword::Included,
Keyword::Json,
];
#[inline]
fn keyword_aliases_slot(mode: ProcessingMode) -> usize {
match mode {
ProcessingMode::JsonLd1_0 => 0,
ProcessingMode::JsonLd1_1 => 1,
}
}
#[inline]
fn keyword_alias_index(k: Keyword) -> Option<usize> {
Some(match k {
Keyword::Id => 0,
Keyword::Type => 1,
Keyword::Value => 2,
Keyword::List => 3,
Keyword::Set => 4,
Keyword::Graph => 5,
Keyword::Index => 6,
Keyword::Language => 7,
Keyword::Direction => 8,
Keyword::Reverse => 9,
Keyword::None => 10,
Keyword::Included => 11,
Keyword::Json => 12,
_ => return None,
})
}
type CompactIriCache<T, B> = ShardedMemo<CompactIriKey<T, B>, Option<Arc<str>>>;
type TermResolutionCache<T, B> = ShardedMemo<Box<str>, Arc<Term<T, B>>>;
pub type PrefixTerm<T, B> = (Key, Arc<Term<T, B>>);
pub struct Context<T = IriBuf, B = BlankIdBuf> {
original_base_url: Option<T>,
base_iri: Option<T>,
vocabulary: Option<Term<T, B>>,
default_language: Option<LenientLangTagBuf>,
default_base_direction: Option<Direction>,
previous_context: Option<Arc<Self>>,
definitions: Arc<Definitions<T, B>>,
processing_mode: ProcessingMode,
inverse: Arc<OnceCell<InverseContext<T, B>>>,
prefix_terms: Arc<OnceCell<Vec<PrefixTerm<T, B>>>>,
compact_iri_cache: Arc<OnceCell<CompactIriCache<T, B>>>,
term_resolution_cache: Arc<OnceCell<TermResolutionCache<T, B>>>,
keyword_aliases: Arc<[OnceCell<KeywordAliases>; 2]>,
}
impl<T, B> Default for Context<T, B> {
fn default() -> Self {
Self {
original_base_url: None,
base_iri: None,
vocabulary: None,
default_language: None,
default_base_direction: None,
previous_context: None,
definitions: Arc::new(Definitions::default()),
processing_mode: ProcessingMode::default(),
inverse: Arc::new(OnceCell::new()),
prefix_terms: Arc::new(OnceCell::new()),
compact_iri_cache: Arc::new(OnceCell::new()),
term_resolution_cache: Arc::new(OnceCell::new()),
keyword_aliases: Arc::new([OnceCell::new(), OnceCell::new()]),
}
}
}
pub type DefinitionEntryRef<'a, T = IriBuf, B = BlankIdBuf> = (&'a Key, &'a TermDefinition<T, B>);
impl<T, B> Context<T, B> {
pub fn new(base_iri: Option<T>) -> Self
where
T: Clone,
{
Self {
original_base_url: base_iri.clone(),
base_iri,
vocabulary: None,
default_language: None,
default_base_direction: None,
previous_context: None,
definitions: Arc::new(Definitions::default()),
processing_mode: ProcessingMode::default(),
inverse: Arc::new(OnceCell::new()),
prefix_terms: Arc::new(OnceCell::new()),
compact_iri_cache: Arc::new(OnceCell::new()),
term_resolution_cache: Arc::new(OnceCell::new()),
keyword_aliases: Arc::new([OnceCell::new(), OnceCell::new()]),
}
}
#[must_use]
pub fn with_private_caches(&self) -> Context<T, B>
where
T: Clone,
B: Clone,
{
Context {
original_base_url: self.original_base_url.clone(),
base_iri: self.base_iri.clone(),
vocabulary: self.vocabulary.clone(),
default_language: self.default_language.clone(),
default_base_direction: self.default_base_direction,
previous_context: self.previous_context.as_ref().map(|previous| Arc::new(previous.with_private_caches())),
definitions: Arc::clone(&self.definitions),
processing_mode: self.processing_mode,
inverse: Arc::clone(&self.inverse),
prefix_terms: Arc::clone(&self.prefix_terms),
compact_iri_cache: Arc::new(OnceCell::new()),
term_resolution_cache: Arc::new(OnceCell::new()),
keyword_aliases: Arc::clone(&self.keyword_aliases),
}
}
#[inline(always)]
pub fn processing_mode(&self) -> ProcessingMode {
self.processing_mode
}
#[inline(always)]
pub fn set_processing_mode(&mut self, mode: ProcessingMode) {
self.processing_mode = mode;
}
pub fn get<Q>(&self, term: &Q) -> Option<TermDefinitionRef<'_, T, B>>
where
Key: Borrow<Q>,
KeywordType: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
self.definitions.get(term)
}
pub fn get_normal<Q>(&self, term: &Q) -> Option<&NormalTermDefinition<T, B>>
where
Key: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
self.definitions.get_normal(term)
}
pub fn get_type(&self) -> Option<&TypeTermDefinition> {
self.definitions.get_type()
}
pub fn contains_term<Q>(&self, term: &Q) -> bool
where
Key: Borrow<Q>,
KeywordType: Borrow<Q>,
Q: ?Sized + Hash + Eq,
{
self.definitions.contains_term(term)
}
pub fn original_base_url(&self) -> Option<&T> {
self.original_base_url.as_ref()
}
pub fn base_iri(&self) -> Option<&T> {
self.base_iri.as_ref()
}
pub fn vocabulary(&self) -> Option<&Term<T, B>> {
match &self.vocabulary {
Some(v) => Some(v),
None => None,
}
}
pub fn default_language(&self) -> Option<&LenientLangTag> {
self.default_language.as_ref().map(jsonld_syntax::LenientLangTagBuf::as_lenient_lang_tag_ref)
}
pub fn default_base_direction(&self) -> Option<Direction> {
self.default_base_direction
}
pub fn previous_context(&self) -> Option<&Self> {
match &self.previous_context {
Some(c) => Some(c),
None => None,
}
}
pub fn definitions_arc_ptr(&self) -> *const Definitions<T, B> {
Arc::as_ptr(&self.definitions)
}
pub fn previous_context_arc_ptr(&self) -> *const Self {
self.previous_context.as_ref().map_or(std::ptr::null(), Arc::as_ptr)
}
pub fn len(&self) -> usize {
self.definitions.len()
}
pub fn is_empty(&self) -> bool {
self.definitions.is_empty()
}
pub fn definitions(&self) -> &Definitions<T, B> {
&self.definitions
}
pub fn has_protected_items(&self) -> bool {
for binding in self.definitions() {
if binding.definition().protected() {
return true;
}
}
false
}
pub fn inverse(&self) -> &InverseContext<T, B>
where
T: Clone + Hash + Eq,
B: Clone + Hash + Eq,
{
self.inverse.get_or_init(|| self.into())
}
pub fn prefix_terms(&self) -> &[PrefixTerm<T, B>] {
self.prefix_terms.get_or_init(|| {
self.definitions
.iter()
.filter_map(|binding| match binding {
BindingRef::Normal(key, def) if def.prefix => def.value_arc().map(|value| (*key, value.clone())),
_ => None,
})
.collect()
})
}
pub fn compact_iri_cache(&self) -> &CompactIriCache<T, B> {
self.compact_iri_cache.get_or_init(ShardedMemo::default)
}
pub fn term_resolution_cache(&self) -> &TermResolutionCache<T, B> {
self.term_resolution_cache.get_or_init(ShardedMemo::default)
}
pub fn keyword_aliases_or_init<F: FnOnce() -> KeywordAliases>(&self, mode: ProcessingMode, init: F) -> &KeywordAliases {
self.keyword_aliases[keyword_aliases_slot(mode)].get_or_init(init)
}
#[inline]
fn invalidate_iri_caches(&mut self) {
if self.inverse.get().is_some() {
self.inverse = Arc::new(OnceCell::new());
}
if self.compact_iri_cache.get().is_some() {
self.compact_iri_cache = Arc::new(OnceCell::new());
}
if self.term_resolution_cache.get().is_some() {
self.term_resolution_cache = Arc::new(OnceCell::new());
}
if self.keyword_aliases.iter().any(|slot| slot.get().is_some()) {
self.keyword_aliases = Arc::new([OnceCell::new(), OnceCell::new()]);
}
}
pub fn set_normal(&mut self, key: Key, definition: Option<NormalTermDefinition<T, B>>) -> Option<NormalTermDefinition<T, B>>
where
T: Clone,
B: Clone,
{
self.invalidate_iri_caches();
if self.prefix_terms.get().is_some() {
self.prefix_terms = Arc::new(OnceCell::new());
}
Arc::make_mut(&mut self.definitions).set_normal(key, definition)
}
pub fn set_type(&mut self, type_: Option<TypeTermDefinition>) -> Option<TypeTermDefinition>
where
T: Clone,
B: Clone,
{
if self.compact_iri_cache.get().is_some() {
self.compact_iri_cache = Arc::new(OnceCell::new());
}
if self.term_resolution_cache.get().is_some() {
self.term_resolution_cache = Arc::new(OnceCell::new());
}
if self.keyword_aliases.iter().any(|slot| slot.get().is_some()) {
self.keyword_aliases = Arc::new([OnceCell::new(), OnceCell::new()]);
}
Arc::make_mut(&mut self.definitions).set_type(type_)
}
pub fn set_base_iri(&mut self, iri: Option<T>) {
self.invalidate_iri_caches();
self.base_iri = iri;
}
pub fn set_vocabulary(&mut self, vocab: Option<Term<T, B>>) {
self.invalidate_iri_caches();
self.vocabulary = vocab;
}
pub fn set_default_language(&mut self, lang: Option<LenientLangTagBuf>) {
self.invalidate_iri_caches();
self.default_language = lang;
}
pub fn set_default_base_direction(&mut self, dir: Option<Direction>) {
self.invalidate_iri_caches();
self.default_base_direction = dir;
}
pub fn set_previous_context(&mut self, previous: Self) {
self.invalidate_iri_caches();
self.previous_context = Some(Arc::new(previous));
}
pub fn into_syntax_definition(self, vocabulary: &impl Vocabulary<Iri = T, BlankId = B>) -> Result<jsonld_syntax::context::Definition, InvalidContextError>
where
T: Clone,
B: Clone,
{
let (bindings, type_) = Arc::unwrap_or_clone(self.definitions).into_parts();
let vocab = match self.vocabulary {
Some(Term::Null) => Some(Nullable::Null),
Some(Term::Id(r)) => Some(Nullable::Some(r.with(vocabulary).to_string().into())),
Some(Term::Keyword(_)) => return Err(InvalidContextError::InvalidVocab(InvalidVocab::Keyword)),
None => None,
};
let base = match self.base_iri {
Some(i) => {
let iri: iri_rs::IriBuf = vocabulary.iri(&i).ok_or(InvalidContextError::UnresolvedIri)?.into();
Some(Nullable::Some(iri.into()))
}
None => None,
};
Ok(jsonld_syntax::context::Definition {
base,
import: None,
language: self.default_language.map(Nullable::Some),
direction: self.default_base_direction.map(Nullable::Some),
propagate: None,
protected: None,
type_: type_.map(TypeTermDefinition::into_syntax_definition),
version: None,
vocab,
bindings: {
let mut entries: Vec<_> = bindings.into_iter().collect();
entries.sort_unstable_by(|(a, _), (b, _)| a.as_str().cmp(b.as_str()));
entries
.into_iter()
.map(|(key, definition)| definition.into_syntax_definition(vocabulary).map(|d| (key, d)))
.collect::<Result<_, _>>()?
},
})
}
pub fn map_ids<U, C>(self, mut map_iri: impl FnMut(T) -> U, mut map_id: impl FnMut(Id<T, B>) -> Id<U, C>) -> Context<U, C>
where
T: Clone,
B: Clone,
{
self.map_ids_with(&mut map_iri, &mut map_id)
}
fn map_ids_with<U, C>(self, map_iri: &mut impl FnMut(T) -> U, map_id: &mut impl FnMut(Id<T, B>) -> Id<U, C>) -> Context<U, C>
where
T: Clone,
B: Clone,
{
Context {
original_base_url: self.original_base_url.map(&mut *map_iri),
base_iri: self.base_iri.map(&mut *map_iri),
vocabulary: self.vocabulary.map(|v| v.map_id(&mut *map_id)),
default_language: self.default_language,
default_base_direction: self.default_base_direction,
previous_context: self.previous_context.map(|c| Arc::new(Arc::unwrap_or_clone(c).map_ids_with(map_iri, map_id))),
definitions: Arc::new(Arc::unwrap_or_clone(self.definitions).map_ids(map_iri, map_id)),
processing_mode: self.processing_mode,
inverse: Arc::new(OnceCell::new()),
prefix_terms: Arc::new(OnceCell::new()),
compact_iri_cache: Arc::new(OnceCell::new()),
term_resolution_cache: Arc::new(OnceCell::new()),
keyword_aliases: Arc::new([OnceCell::new(), OnceCell::new()]),
}
}
}
pub trait IntoSyntax<T = IriBuf, B = BlankIdBuf> {
fn into_syntax(self, vocabulary: &impl Vocabulary<Iri = T, BlankId = B>) -> Result<jsonld_syntax::context::Context, InvalidContextError>;
}
impl<T, B> IntoSyntax<T, B> for jsonld_syntax::context::Context {
fn into_syntax(self, _namespace: &impl Vocabulary<Iri = T, BlankId = B>) -> Result<jsonld_syntax::context::Context, InvalidContextError> {
Ok(self)
}
}
impl<T: Clone, B: Clone> IntoSyntax<T, B> for Context<T, B> {
fn into_syntax(self, vocabulary: &impl Vocabulary<Iri = T, BlankId = B>) -> Result<jsonld_syntax::context::Context, InvalidContextError> {
Ok(jsonld_syntax::context::Context::One(jsonld_syntax::ContextEntry::Definition(
self.into_syntax_definition(vocabulary)?,
)))
}
}
impl<T: Clone, B: Clone> Clone for Context<T, B> {
fn clone(&self) -> Self {
Self {
original_base_url: self.original_base_url.clone(),
base_iri: self.base_iri.clone(),
vocabulary: self.vocabulary.clone(),
default_language: self.default_language.clone(),
default_base_direction: self.default_base_direction,
previous_context: self.previous_context.clone(),
definitions: Arc::clone(&self.definitions),
processing_mode: self.processing_mode,
inverse: Arc::clone(&self.inverse),
prefix_terms: Arc::clone(&self.prefix_terms),
compact_iri_cache: Arc::clone(&self.compact_iri_cache),
term_resolution_cache: Arc::clone(&self.term_resolution_cache),
keyword_aliases: Arc::clone(&self.keyword_aliases),
}
}
}
impl<T: PartialEq, B: PartialEq> PartialEq for Context<T, B> {
fn eq(&self, other: &Self) -> bool {
self.original_base_url == other.original_base_url
&& self.base_iri == other.base_iri
&& self.vocabulary == other.vocabulary
&& self.default_language == other.default_language
&& self.default_base_direction == other.default_base_direction
&& self.processing_mode == other.processing_mode
&& (Arc::ptr_eq(&self.definitions, &other.definitions) || self.definitions == other.definitions)
&& self.previous_context == other.previous_context
}
}