json-ld 0.22.0

JSON-LD implementation
Documentation
use json_syntax::{JsonObject, JsonValue};
use mown::Mown;

use crate::{
	JsonLdBacktraceBuilder, JsonLdLenient, JsonLdLocatedError, JsonLdTerm, JsonLdType, JsonLdValue,
	algorithms::{
		AsyncJsonLdEnvironment, AsyncProcessingEnvironmentRef,
		context_processing::JsonLdContextProcessingOptions,
	},
	context::JsonLdContainer,
	object::value::JsonLdLiteralType,
	syntax::{JsonLdContainerItem, JsonLdKeyword},
};

use super::Compactor;

impl<'a> Compactor<'a> {
	/// Compact the given indexed value.
	pub async fn compact_indexed_value_with(
		&self,
		env: &impl AsyncJsonLdEnvironment,
		value: &JsonLdValue,
		index: Option<&str>,
		location: JsonLdBacktraceBuilder<'_>,
	) -> Result<JsonValue, JsonLdLocatedError> {
		// If the term definition for active property in active context has a local context:
		let mut active_context = Mown::Borrowed(self.active_context);
		if let Some(active_property) = self.active_property
			&& let Some(active_property_definition) = active_context.get(active_property)
			&& let Some(local_context) = active_property_definition.context()
		{
			let def_loc =
				location.push_backtrace(active_property_definition.location().unwrap_or_default());
			let scoped_context_loc = def_loc.object_value(JsonLdKeyword::Context);

			active_context = Mown::Owned(
				local_context
					.process_with(
						AsyncProcessingEnvironmentRef(env),
						active_property_definition.base_url(),
						active_context.as_ref(),
						JsonLdContextProcessingOptions::from(self.options).with_override(),
						scoped_context_loc,
					)
					.await?
					.into_raw(),
			)
		}

		// If element has an @value or @id entry and the result of using the Value Compaction algorithm,
		// passing active context, active property, and element as value is a scalar,
		// or the term definition for active property has a type mapping of @json,
		// return that result.

		// Here starts the Value Compaction Algorithm.

		// Initialize result to a copy of value.
		let mut result = JsonObject::default();

		// If the active context has a null inverse context,
		// set inverse context in active context to the result of calling the
		// Inverse Context Creation algorithm using active context.
		// NOTE never null here (FIXME is that true?)

		// Initialize inverse context to the value of inverse context in active context.
		// DONE

		let active_property_definition = match self.active_property {
			Some(active_property) => active_context.get(active_property),
			None => None,
		};

		// Initialize language to the language mapping for active property in active context,
		// if any, otherwise to the default language of active context.
		let language = match active_property_definition {
			Some(def) => match def.language() {
				Some(lang) => lang.as_ref().map(|l| l.as_deref()).option(),
				None => active_context.default_language(),
			},
			None => active_context.default_language(),
		};

		// Initialize direction to the direction mapping for active property in active context,
		// if any, otherwise to the default base direction of active context.
		let direction = match active_property_definition {
			Some(def) => match def.direction() {
				Some(dir) => dir.option(),
				None => active_context.default_base_direction(),
			},
			None => active_context.default_base_direction(),
		};

		// If value has an @id entry and has no other entries other than @index:
		// NOTE not possible here

		// Otherwise, if value has an @type entry whose value matches the type mapping of
		// active property, set result to the value associated with the @value entry of value.
		let type_mapping: Option<JsonLdType> = match active_property_definition {
			Some(def) => def.typ().cloned(),
			None => None,
		};

		let container_mapping = match active_property_definition {
			Some(def) => def.container(),
			None => JsonLdContainer::Null,
		};

		let remove_index = (index.is_some()
			&& container_mapping.contains(JsonLdContainerItem::Index))
			|| index.is_none();

		match value {
			JsonLdValue::Literal(lit) => {
				if lit.is_json() {
					// JSON literal
					if type_mapping == Some(JsonLdType::Json) && remove_index {
						return Ok(lit.value.clone());
					} else {
						let compact_key = self.with_active_context(&active_context).compact_key(
							&JsonLdTerm::Keyword(JsonLdKeyword::Value),
							true,
							false,
							location,
						)?;
						result.insert(compact_key.unwrap(), lit.value.clone());

						let compact_key = self.with_active_context(&active_context).compact_key(
							&JsonLdTerm::Keyword(JsonLdKeyword::Type),
							true,
							false,
							location,
						)?;

						let compact_ty = self.with_active_context(&active_context).compact_iri(
							&JsonLdTerm::Keyword(JsonLdKeyword::Json),
							true,
							false,
							location,
						)?;
						result.insert(
							compact_key.unwrap(),
							match compact_ty {
								Some(s) => JsonValue::String(s.into()),
								None => JsonValue::Null,
							},
						);
					}
				} else {
					let ty = match &lit.type_ {
						Some(JsonLdLiteralType::Iri(iri)) => Some(iri),
						_ => None,
					};

					if ty.cloned().map(JsonLdType::Iri) == type_mapping && remove_index {
						match &lit.value {
							JsonValue::Null => return Ok(JsonValue::Null),
							JsonValue::Boolean(b) => return Ok(JsonValue::Boolean(*b)),
							JsonValue::Number(n) => return Ok(JsonValue::Number(n.clone())),
							JsonValue::String(s) => {
								if ty.is_some() || (language.is_none() && direction.is_none()) {
									return Ok(JsonValue::String(s.as_str().into()));
								} else {
									let compact_key =
										self.with_active_context(&active_context).compact_key(
											&JsonLdTerm::Keyword(JsonLdKeyword::Value),
											true,
											false,
											location,
										)?;
									result.insert(
										compact_key.unwrap(),
										JsonValue::String(s.as_str().into()),
									);
								}
							}
							_ => {
								let compact_key =
									self.with_active_context(&active_context).compact_key(
										&JsonLdTerm::Keyword(JsonLdKeyword::Value),
										true,
										false,
										location,
									)?;
								result.insert(compact_key.unwrap(), lit.value.clone());
							}
						}
					} else {
						let compact_key = self.with_active_context(&active_context).compact_key(
							&JsonLdTerm::Keyword(JsonLdKeyword::Value),
							true,
							false,
							location,
						)?;
						result.insert(compact_key.unwrap(), lit.value.clone());

						if let Some(ty) = ty {
							let compact_key =
								self.with_active_context(&active_context).compact_key(
									&JsonLdTerm::Keyword(JsonLdKeyword::Type),
									true,
									false,
									location,
								)?;
							let compact_ty =
								self.with_active_context(&active_context).compact_iri(
									&JsonLdTerm::Id(JsonLdLenient::iri(ty.clone())),
									true,
									false,
									location,
								)?;
							result.insert(
								compact_key.unwrap(),
								match compact_ty {
									Some(s) => JsonValue::String(s.into()),
									None => JsonValue::Null,
								},
							);
						}
					}
				}
			}
			JsonLdValue::LangString(ls) => {
				let ls_language = ls.language(); //.map(|l| Nullable::Some(l));
				let ls_direction = ls.direction(); //.map(|d| Nullable::Some(d));

				if remove_index
				&& (ls_language.is_none() || language == ls_language) // || (ls.language().is_none() && language.is_none()))
				&& (ls_direction.is_none() || direction == ls_direction)
				{
					// || (ls.direction().is_none() && direction.is_none())) {
					return Ok(JsonValue::String(ls.as_str().into()));
				} else {
					let compact_key = self.with_active_context(&active_context).compact_key(
						&JsonLdTerm::Keyword(JsonLdKeyword::Value),
						true,
						false,
						location,
					)?;
					result.insert(compact_key.unwrap(), JsonValue::String(ls.as_str().into()));

					if let Some(language) = ls.language() {
						let compact_key = self.with_active_context(&active_context).compact_key(
							&JsonLdTerm::Keyword(JsonLdKeyword::Language),
							true,
							false,
							location,
						)?;
						result.insert(
							compact_key.unwrap(),
							JsonValue::String(language.as_str().into()),
						);
					}

					if let Some(direction) = ls.direction() {
						let compact_key = self.with_active_context(&active_context).compact_key(
							&JsonLdTerm::Keyword(JsonLdKeyword::Direction),
							true,
							false,
							location,
						)?;
						result.insert(
							compact_key.unwrap(),
							JsonValue::String(direction.as_str().into()),
						);
					}
				}
			}
		}

		if !remove_index && let Some(index) = index {
			let compact_key = self.with_active_context(&active_context).compact_key(
				&JsonLdTerm::Keyword(JsonLdKeyword::Index),
				true,
				false,
				location,
			)?;
			result.insert(compact_key.unwrap(), JsonValue::String(index.into()));
		}

		Ok(JsonValue::Object(result))
	}
}

/// Default value of `as_array` is false.
pub fn add_value(map: &mut JsonObject, key: &str, value: JsonValue, as_array: bool) {
	match map
		.get_unique(key)
		.ok()
		.unwrap()
		.map(|entry| entry.is_array())
	{
		Some(false) => {
			let (key, value) = map.remove_unique(key).ok().unwrap().unwrap();
			map.insert(key, JsonValue::Array(vec![value]));
		}
		None if as_array => {
			map.insert(key, JsonValue::Array(Vec::new()));
		}
		_ => (),
	}

	match value {
		JsonValue::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, value);
		}
	}
}

/// Get the `@value` field of a value object.
pub fn value_value(value: &JsonLdValue) -> JsonValue {
	match value {
		JsonLdValue::Literal(lit) => lit.value.clone(),
		JsonLdValue::LangString(s) => JsonValue::String(s.as_str().into()),
	}
}