use super::{
expand_array, expand_iri, expand_literal, expand_node, expand_value, ActiveProperty, Entry,
Expanded, ExpandedEntry, JsonExpand, LiteralValue, Options,
};
use crate::util::as_array;
use crate::{
context::{ContextMut, Loader, Local, ProcessingOptions},
object::*,
syntax::{Keyword, Term},
Error, ErrorCode, Id, Indexed, Loc, Reference, Warning,
};
use cc_traits::{CollectionRef, Get, KeyedRef, Len, MapIter};
use futures::future::{BoxFuture, FutureExt};
use generic_json::{Json, Key, ValueRef};
use iref::Iri;
use mown::Mown;
pub type ElementExpansionResult<T, J> = Result<Expanded<J, T>, Loc<Error, <J as Json>::MetaData>>;
pub fn expand_element<
'a,
J: JsonExpand,
T: 'a + Id + Send + Sync,
C: ContextMut<T> + Send + Sync,
L: Loader + Send + Sync,
>(
active_context: &'a C,
active_property: ActiveProperty<'a, J>,
element: &'a J,
base_url: Option<Iri<'a>>,
loader: &'a mut L,
options: Options,
from_map: bool,
warnings: &'a mut Vec<Loc<Warning, J::MetaData>>,
) -> BoxFuture<'a, ElementExpansionResult<T, J>>
where
C::LocalContext: From<L::Output> + From<J> + Send + Sync,
L::Output: Into<J>,
{
let source = loader.id_opt(base_url);
async move {
if element.is_null() {
return Ok(Expanded::Null);
}
let active_property_definition = active_context.get_opt(active_property.id());
let mut property_scoped_base_url = None;
let property_scoped_context = if let Some(definition) = active_property_definition {
if let Some(base_url) = &definition.base_url {
property_scoped_base_url = Some(base_url.as_iri());
}
definition.context.as_ref()
} else {
None
};
match element.as_value_ref() {
ValueRef::Null => unreachable!(),
ValueRef::Array(element) => {
expand_array(
active_context,
active_property,
active_property_definition,
element,
base_url,
loader,
options,
from_map,
warnings,
)
.await
}
ValueRef::Object(element) => {
let mut entries: Vec<Entry<J>> = Vec::with_capacity(element.len());
for (key, value) in element.iter() {
entries.push(Entry(key, value));
}
if options.ordered {
entries.sort()
}
let mut value_entry1 = None;
let mut id_entry = None;
for Entry(key, value) in entries.iter() {
match expand_iri(
source,
active_context,
key.as_ref(),
key.metadata(),
false,
true,
warnings,
) {
Term::Keyword(Keyword::Value) => value_entry1 = Some(value.clone()),
Term::Keyword(Keyword::Id) => id_entry = Some(value.clone()),
_ => (),
}
}
let mut active_context = Mown::Borrowed(active_context);
if let Some(previous_context) = active_context.previous_context() {
if !from_map
&& value_entry1.is_none()
&& !(element.len() == 1 && id_entry.is_some())
{
active_context = Mown::Owned(previous_context.clone())
}
}
if let Some(property_scoped_context) = property_scoped_context {
let options: ProcessingOptions = options.into();
active_context = Mown::Owned(
property_scoped_context
.process_with(
active_context.as_ref(),
loader,
property_scoped_base_url,
options.with_override(),
)
.await
.map_err(|e| {
e.with_metadata(active_property.metadata().unwrap().clone())
})?
.into_inner(),
);
}
if let Some(local_context) = element.get("@context") {
active_context = Mown::Owned(
local_context
.process_with(active_context.as_ref(), loader, base_url, options.into())
.await?
.into_inner(),
);
}
let mut type_entries: Vec<Entry<J>> = Vec::new();
for entry @ Entry(key, _) in entries.iter() {
let expanded_key = expand_iri(
source,
active_context.as_ref(),
key.as_ref(),
key.metadata(),
false,
true,
warnings,
);
if let Term::Keyword(Keyword::Type) = expanded_key {
type_entries.push(entry.clone());
}
}
type_entries.sort();
let type_scoped_context = active_context.as_ref();
let mut active_context = Mown::Borrowed(active_context.as_ref());
for Entry(_, value) in &type_entries {
let (value, len) = as_array(&**value);
let mut sorted_value = Vec::with_capacity(len);
for term in value {
if term.is_string() {
sorted_value.push(term);
}
}
sorted_value.sort_unstable_by(|a, b| a.as_str().cmp(&b.as_str()));
for term in sorted_value {
let term_str = term.as_str().unwrap();
if let Some(term_definition) = type_scoped_context.get(term_str) {
if let Some(local_context) = &term_definition.context {
let base_url =
term_definition.base_url.as_ref().map(|url| url.as_iri());
let options: ProcessingOptions = options.into();
active_context = Mown::Owned(
local_context
.process_with(
active_context.as_ref(),
loader,
base_url,
options.without_propagation(),
)
.await
.map_err(|e| e.with_metadata(term.metadata().clone()))?
.into_inner(),
);
}
}
}
}
let input_type = if let Some(Entry(_, value)) = type_entries.first() {
let (value, _) = as_array(&**value);
if let Some(input_type) = value.last() {
input_type.as_str().map(|input_type_str| {
expand_iri(
source,
active_context.as_ref(),
input_type_str,
input_type.metadata(),
false,
true,
warnings,
)
})
} else {
None
}
} else {
None
};
let mut expanded_entries: Vec<ExpandedEntry<J, Term<T>>> =
Vec::with_capacity(element.len());
let mut list_entry = None;
let mut set_entry = None;
let mut value_entry = None;
for Entry(key, value) in entries {
if key.is_empty() {
warnings.push(Loc::new(Warning::EmptyTerm, source, key.metadata().clone()));
}
let expanded_key = expand_iri(
source,
active_context.as_ref(),
key.as_ref(),
key.metadata(),
false,
true,
warnings,
);
match &expanded_key {
Term::Keyword(Keyword::Value) => value_entry = Some(value.clone()),
Term::Keyword(Keyword::List)
if active_property.is_some() && active_property != Some("@graph") =>
{
list_entry = Some(value.clone())
}
Term::Keyword(Keyword::Set) => set_entry = Some(value.clone()),
Term::Ref(Reference::Blank(id)) => {
warnings.push(Loc::new(
Warning::BlankNodeIdProperty(id.clone()),
source,
key.metadata().clone(),
));
}
_ => (),
}
expanded_entries.push(ExpandedEntry(
J::Object::upcast_key_ref(key),
expanded_key,
J::Object::upcast_item_ref(value),
))
}
if let Some(list_entry) = list_entry {
let mut index = None;
for ExpandedEntry(key, expanded_key, value) in expanded_entries {
match expanded_key {
Term::Keyword(Keyword::Index) => match value.as_str() {
Some(value) => index = Some(value.to_string()),
None => {
return Err(ErrorCode::InvalidIndexValue
.located(source, value.metadata().clone()))
}
},
Term::Keyword(Keyword::List) => (),
_ => {
return Err(ErrorCode::InvalidSetOrListObject
.located(source, key.metadata().clone()))
}
}
}
let mut result = Vec::new();
let (list_entry, _) = as_array(&*list_entry);
for item in list_entry {
result.extend(
expand_element(
active_context.as_ref(),
active_property,
&*item,
base_url,
loader,
options,
false,
warnings,
)
.await?,
)
}
Ok(Expanded::Object(Indexed::new(Object::List(result), index)))
} else if let Some(set_entry) = set_entry {
for ExpandedEntry(key, expanded_key, _) in expanded_entries {
match expanded_key {
Term::Keyword(Keyword::Index) => {
}
Term::Keyword(Keyword::Set) => (),
_ => {
return Err(ErrorCode::InvalidSetOrListObject
.located(source, key.metadata().clone()))
}
}
}
expand_element(
active_context.as_ref(),
active_property,
&*set_entry,
base_url,
loader,
options,
false,
warnings,
)
.await
} else if let Some(value_entry) = value_entry {
if let Some(value) = expand_value(
source,
input_type,
type_scoped_context,
expanded_entries,
&*value_entry,
warnings,
)
.map_err(|e| e.located(source, value_entry.metadata().clone()))?
{
Ok(Expanded::Object(value))
} else {
Ok(Expanded::Null)
}
} else {
if let Some(result) = expand_node(
active_context.as_ref(),
type_scoped_context,
active_property,
expanded_entries,
base_url,
loader,
options,
warnings,
)
.await?
{
Ok(result.cast::<Object<J, T>>().into())
} else {
Ok(Expanded::Null)
}
}
}
_ => {
if active_property.is_none() || active_property == Some("@graph") {
return Ok(Expanded::Null);
}
let active_context = if let Some(property_scoped_context) = property_scoped_context
{
let base_url =
active_context
.get_opt(active_property.id())
.and_then(|definition| {
definition
.base_url
.as_ref()
.map(|base_url| base_url.as_iri())
});
let result = property_scoped_context
.process_with(active_context, loader, base_url, options.into())
.await
.map_err(|e| e.with_metadata(active_property.metadata().unwrap().clone()))?
.into_inner();
Mown::Owned(result)
} else {
Mown::Borrowed(active_context)
};
return Ok(Expanded::Object(
expand_literal(
source,
active_context.as_ref(),
active_property,
LiteralValue::Given(element),
warnings,
)
.map_err(|e| e.located(source, element.metadata().clone()))?,
));
}
}
}
.boxed()
}