pub mod algorithm;
pub mod node_map;
#[cfg(test)]
mod tests;
pub use algorithm::{
clone_node_recursively, flatten_internal, is_list_object, is_node_object, is_value_object,
merge_value,
};
pub use node_map::{
generate_node_map, node_map_to_flat_array, BlankNodeIdMapper, GraphNodeMap, NodeMap, NodeObject,
};
use crate::jsonld::compaction::{compact, CompactionOptions, JsonLdContext};
use indexmap::IndexMap;
use thiserror::Error;
pub use crate::jsonld::compaction::{JsonLdValue, ProcessingMode};
#[derive(Debug, Clone)]
pub struct FlatteningOptions {
pub processing_mode: ProcessingMode,
pub compact_arrays: bool,
pub ordered: bool,
pub base: Option<String>,
pub expand_context: Option<JsonLdValue>,
pub document_loader_enabled: bool,
}
impl Default for FlatteningOptions {
fn default() -> Self {
Self {
processing_mode: ProcessingMode::JsonLd11,
compact_arrays: true,
ordered: false,
base: None,
expand_context: None,
document_loader_enabled: false,
}
}
}
#[derive(Debug, Error)]
pub enum FlatteningError {
#[error("Expansion error: {0}")]
ExpansionError(String),
#[error("Node map error: {0}")]
NodeMapError(String),
#[error("Compaction error: {0}")]
CompactionError(String),
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Cyclic node reference involving subject: {0}")]
CyclicNodeReference(String),
}
pub fn flatten(
input: &JsonLdValue,
context: Option<&JsonLdValue>,
options: &FlatteningOptions,
) -> Result<JsonLdValue, FlatteningError> {
let expanded: Vec<JsonLdValue> = match input {
JsonLdValue::Array(items) => items.clone(),
JsonLdValue::Null => Vec::new(),
other => vec![other.clone()],
};
let flat = flatten_internal(expanded, options)?;
if let Some(ctx_value) = context {
let active_ctx = build_context_from_value(ctx_value);
let compact_opts = CompactionOptions {
compact_arrays: options.compact_arrays,
processing_mode: options.processing_mode,
ordered: options.ordered,
base: options.base.clone(),
};
let compacted = compact(&flat, &active_ctx, &compact_opts)
.map_err(|e| FlatteningError::CompactionError(e.to_string()))?;
let mut result_map: IndexMap<String, JsonLdValue> = IndexMap::new();
result_map.insert("@context".to_string(), ctx_value.clone());
match compacted {
JsonLdValue::Object(m) => {
for (k, v) in m {
if k != "@context" {
result_map.insert(k, v);
}
}
}
other => {
result_map.insert("@graph".to_string(), other);
}
}
return Ok(JsonLdValue::Object(result_map));
}
Ok(flat)
}
fn build_context_from_value(ctx: &JsonLdValue) -> JsonLdContext {
let mut active = JsonLdContext::new();
match ctx {
JsonLdValue::Object(map) => {
for (key, val) in map {
match (key.as_str(), val) {
("@vocab", JsonLdValue::Str(iri)) => {
active.vocab = Some(iri.clone());
}
("@base", JsonLdValue::Str(iri)) => {
active.base = Some(iri.clone());
}
("@language", JsonLdValue::Str(lang)) => {
active.language = Some(lang.clone());
}
(term, JsonLdValue::Str(iri)) if !term.starts_with('@') => {
active.add_prefix(term, iri.as_str());
}
(term, JsonLdValue::Object(term_def_map)) => {
if let Some(JsonLdValue::Str(iri)) = term_def_map.get("@id") {
active.add_prefix(term, iri.as_str());
}
}
_ => {}
}
}
}
JsonLdValue::Str(iri) => {
active.vocab = Some(iri.clone());
}
_ => {}
}
active
}