use mown::Mown;
use futures::future::{BoxFuture, FutureExt};
use iref::Iri;
use json::JsonValue;
use crate::{
Error,
ErrorCode,
Id,
Lenient,
object::*,
context::{
ContextMut,
ProcessingOptions,
ProcessingStack,
Local,
Loader
},
syntax::{
Keyword,
Term
}
};
use crate::util::as_array;
use super::{
Expanded,
Entry,
Options,
expand_literal,
expand_array,
expand_value,
expand_node,
expand_iri
};
pub fn expand_element<'a, T: Send + Sync + Id, C: Send + Sync + ContextMut<T>, L: Send + Sync + Loader>(active_context: &'a C, active_property: Option<&'a str>, element: &'a JsonValue, base_url: Option<Iri<'a>>, loader: &'a mut L, options: Options) -> BoxFuture<'a, Result<Expanded<T>, Error>> where C::LocalContext: Send + Sync + From<L::Output> + From<JsonValue>, L::Output: Into<JsonValue> {
async move {
if element.is_null() {
return Ok(Expanded::Null)
}
let active_property_definition = active_context.get_opt(active_property);
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 {
JsonValue::Null => unreachable!(),
JsonValue::Array(element) => {
expand_array(active_context, active_property, active_property_definition, element, base_url, loader, options).await
},
JsonValue::Object(element) => {
let mut entries: Vec<Entry<&'a str>> = Vec::with_capacity(element.len());
for (key, value) in element.iter() {
entries.push(Entry(key, value));
}
if options.ordered {
entries.sort()
}
let mut value_entry: Option<&JsonValue> = None;
let mut id_entry = None;
for Entry(key, value) in entries.iter() {
match expand_iri(active_context, key, false, true) {
Lenient::Ok(Term::Keyword(Keyword::Value)) => {
value_entry = Some(value)
},
Lenient::Ok(Term::Keyword(Keyword::Id)) => {
id_entry = Some(value)
},
_ => ()
}
}
let mut active_context = Mown::Borrowed(active_context);
if let Some(previous_context) = active_context.previous_context() {
if value_entry.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(), ProcessingStack::new(), loader, property_scoped_base_url, options.with_override()).await?);
}
if let Some(local_context) = element.get("@context") {
active_context = Mown::Owned(local_context.process_with(active_context.as_ref(), ProcessingStack::new(), loader, base_url, options.into()).await?);
}
let mut type_entries = Vec::new();
for Entry(key, value) in entries.iter() {
let expanded_key = expand_iri(active_context.as_ref(), key, false, true);
match &expanded_key {
Lenient::Ok(Term::Keyword(Keyword::Type)) => {
type_entries.push(Entry(key, value));
},
_ => ()
}
}
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 = as_array(value);
let mut sorted_value = Vec::with_capacity(value.len());
for term in value {
if let Some(term) = term.as_str() {
sorted_value.push(term);
}
}
sorted_value.sort();
for term in sorted_value {
if let Some(term_definition) = type_scoped_context.get(term) {
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(), ProcessingStack::new(), loader, base_url, options.without_propagation()).await?);
}
}
}
}
let input_type = if let Some(Entry(_, value)) = type_entries.first() {
if let Some(input_type) = as_array(value).last() {
if let Some(input_type) = input_type.as_str() {
Some(expand_iri(active_context.as_ref(), input_type, false, true))
} else {
None
}
} else {
None
}
} else {
None
};
let mut expanded_entries = Vec::with_capacity(element.len());
let mut list_entry = None;
let mut set_entry = None;
value_entry = None;
for Entry(key, value) in entries.iter() {
match expand_iri(active_context.as_ref(), key, false, true) {
Lenient::Ok(expanded_key) => {
match &expanded_key {
Term::Keyword(Keyword::Value) => {
value_entry = Some(value)
},
Term::Keyword(Keyword::List) if active_property.is_some() && active_property != Some("@graph") => {
list_entry = Some(value)
},
Term::Keyword(Keyword::Set) => {
set_entry = Some(value)
},
_ => ()
}
expanded_entries.push(Entry((*key, expanded_key), value))
},
Lenient::Unknown(_) => {
warn!("failed to expand key `{}`", key)
}
}
}
if let Some(list_entry) = list_entry {
for Entry((_, expanded_key), _) in expanded_entries {
match expanded_key {
Term::Keyword(Keyword::Index) => {
panic!("TODO list index")
},
Term::Keyword(Keyword::List) => (),
_ => {
return Err(ErrorCode::InvalidSetOrListObject.into())
}
}
}
let mut result = Vec::new();
for item in as_array(list_entry) {
result.extend(expand_element(active_context.as_ref(), active_property, item, base_url, loader, options).await?)
}
Ok(Expanded::Object(Object::List(result).into()))
} else if let Some(set_entry) = set_entry {
for Entry((_, expanded_key), _) in expanded_entries {
match expanded_key {
Term::Keyword(Keyword::Index) => {
panic!("TODO set index")
},
Term::Keyword(Keyword::Set) => (),
_ => {
return Err(ErrorCode::InvalidSetOrListObject.into())
}
}
}
expand_element(active_context.as_ref(), active_property, set_entry, base_url, loader, options).await
} else if let Some(value_entry) = value_entry {
if let Some(value) = expand_value(input_type, type_scoped_context, expanded_entries, value_entry)? {
Ok(Expanded::Object(value.into()))
} 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).await? {
Ok(result.cast::<Object<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 = if let Some(definition) = active_context.get_opt(active_property) {
if let Some(base_url) = &definition.base_url {
Some(base_url.as_iri())
} else {
None
}
} else {
None
};
let result = property_scoped_context.process_with(active_context, ProcessingStack::new(), loader, base_url, options.into()).await?;
Mown::Owned(result)
} else {
Mown::Borrowed(active_context)
};
return Ok(Expanded::Object(expand_literal(active_context.as_ref(), active_property, element)?))
}
}
}.boxed()
}