use std::collections::{BTreeMap, BTreeSet};
use rskit_codec::value::{ArrayStrategy, merge_with};
use rskit_errors::{AppError, AppResult};
use rskit_util::collections::ensure_unique_by;
use serde_json::Value;
use super::MergeIdentity;
#[derive(Default)]
pub struct IncludeMerge {
identity_sections: BTreeMap<String, Box<dyn MergeIdentity>>,
unique_key_sections: BTreeSet<String>,
}
impl std::fmt::Debug for IncludeMerge {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("IncludeMerge")
.field("identity_sections", &self.identity_sections.keys())
.field("unique_key_sections", &self.unique_key_sections)
.finish()
}
}
impl IncludeMerge {
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_identity(
mut self,
section: impl Into<String>,
identity: impl MergeIdentity + 'static,
) -> Self {
self.identity_sections
.insert(section.into(), Box::new(identity));
self
}
#[must_use]
pub fn with_unique_keys(mut self, section: impl Into<String>) -> Self {
self.unique_key_sections.insert(section.into());
self
}
pub fn merge(&self, base: Value, overlay: Value) -> AppResult<Value> {
if !self.unique_key_sections.is_empty() {
self.check_unique_keys(&base, &overlay)?;
}
let identity_sections = &self.identity_sections;
Ok(merge_with(base, overlay, |key| {
if identity_sections.contains_key(key) {
ArrayStrategy::Concat
} else {
ArrayStrategy::Replace
}
}))
}
pub(crate) fn validate(&self, value: &Value) -> AppResult<()> {
match value {
Value::Object(table) => {
for (key, child) in table {
if let (Some(identity), Value::Array(elements)) =
(self.identity_sections.get(key), child)
{
check_unique_identities(key, identity.as_ref(), elements)?;
}
self.validate(child)?;
}
}
Value::Array(elements) => {
for element in elements {
self.validate(element)?;
}
}
_ => {}
}
Ok(())
}
fn check_unique_keys(&self, base: &Value, overlay: &Value) -> AppResult<()> {
let (Value::Object(base), Value::Object(overlay)) = (base, overlay) else {
return Ok(());
};
for (key, overlay_child) in overlay {
let Some(base_child) = base.get(key) else {
continue;
};
if self.unique_key_sections.contains(key)
&& let (Value::Object(base_section), Value::Object(overlay_section)) =
(base_child, overlay_child)
{
for member in overlay_section.keys() {
if base_section.contains_key(member) {
return Err(AppError::invalid_input(
key,
format!(
"duplicate '{member}' in section '{key}' across merged documents"
),
));
}
}
}
self.check_unique_keys(base_child, overlay_child)?;
}
Ok(())
}
}
fn check_unique_identities(
section: &str,
identity: &dyn MergeIdentity,
elements: &[Value],
) -> AppResult<()> {
let identities = elements
.iter()
.filter_map(|element| identity.identity_of(element));
ensure_unique_by(identities, Clone::clone).map_err(|duplicate| {
AppError::invalid_input(
section,
format!(
"duplicate {} identity '{duplicate}' in section '{section}'",
identity.label()
),
)
})
}