tier 0.1.17

Rust configuration library for layered TOML, env, and CLI settings
Documentation
use std::collections::BTreeSet;

use serde_json::Value;

use super::object::{
    dynamic_object_placeholders_for_schema, pattern_property_placeholder_for_schema,
    trim_object_example_properties,
};
use super::{build_example_value, merge_example_value};
use crate::schema::count::{
    available_slots as count_available_slots, keyword_u64, remaining_required,
};

pub(super) fn merge_object_examples(
    object: &serde_json::Map<String, Value>,
    root: &Value,
    visited_refs: &mut BTreeSet<String>,
    reserved_keys: &BTreeSet<String>,
    merged: &mut Option<Value>,
) {
    merge_fixed_property_examples(object, root, visited_refs, merged);
    merge_pattern_property_examples(object, root, visited_refs, reserved_keys, merged);
}

pub(super) fn merge_additional_property_examples(
    object: &serde_json::Map<String, Value>,
    root: &Value,
    visited_refs: &mut BTreeSet<String>,
    reserved_keys: &BTreeSet<String>,
    merged: &mut Option<Value>,
) {
    let existing_object_len = merged
        .as_ref()
        .and_then(Value::as_object)
        .map_or(0, serde_json::Map::len);
    let required_dynamic = keyword_u64(object, "minProperties").map_or(0, |min_properties| {
        remaining_required(min_properties, existing_object_len)
    });
    let implicit_additional = Value::Bool(true);
    let additional_properties = object.get("additionalProperties").or({
        if required_dynamic > 0 {
            Some(&implicit_additional)
        } else {
            None
        }
    });
    if let Some(additional) = additional_properties
        && let Some(example) = build_example_value(additional, root, visited_refs, None)
    {
        let available_slots =
            count_available_slots(keyword_u64(object, "maxProperties"), existing_object_len);
        let additional_entries = if object.contains_key("additionalProperties") {
            required_dynamic.max(1).min(available_slots)
        } else {
            required_dynamic.min(available_slots)
        };
        if additional_entries > 0 {
            let placeholders = dynamic_object_placeholders_for_schema(
                object,
                root,
                reserved_keys,
                additional_entries,
            );
            let rendered = placeholders
                .into_iter()
                .map(|placeholder| (placeholder, example.clone()))
                .collect::<serde_json::Map<_, _>>();
            merge_example_value(merged, Value::Object(rendered));
        }
    }
}

fn merge_fixed_property_examples(
    object: &serde_json::Map<String, Value>,
    root: &Value,
    visited_refs: &mut BTreeSet<String>,
    merged: &mut Option<Value>,
) {
    let Some(properties) = object.get("properties").and_then(Value::as_object) else {
        return;
    };

    let mut rendered = serde_json::Map::new();
    for (key, child) in properties {
        if let Some(example) = build_example_value(child, root, visited_refs, None) {
            rendered.insert(key.clone(), example);
        }
    }
    trim_object_example_properties(&mut rendered, object);
    merge_example_value(merged, Value::Object(rendered));
}

fn merge_pattern_property_examples(
    object: &serde_json::Map<String, Value>,
    root: &Value,
    visited_refs: &mut BTreeSet<String>,
    reserved_keys: &BTreeSet<String>,
    merged: &mut Option<Value>,
) {
    let Some(pattern_properties) = object.get("patternProperties").and_then(Value::as_object)
    else {
        return;
    };
    let existing_len = merged
        .as_ref()
        .and_then(Value::as_object)
        .map_or(0, serde_json::Map::len);
    let required_dynamic = keyword_u64(object, "minProperties").map_or(0, |min_properties| {
        remaining_required(min_properties, existing_len)
    });
    let available_slots = count_available_slots(keyword_u64(object, "maxProperties"), existing_len);
    if available_slots == 0 {
        return;
    }

    let mut taken = reserved_keys.clone();
    if let Some(existing) = merged.as_ref().and_then(Value::as_object) {
        taken.extend(existing.keys().cloned());
    }

    let mut rendered = serde_json::Map::new();
    let target_entries = available_slots.min(required_dynamic.max(pattern_properties.len()));
    while rendered.len() < target_entries {
        let mut made_progress = false;
        for (pattern, child) in pattern_properties {
            if rendered.len() >= target_entries {
                break;
            }
            if let Some(key) =
                pattern_property_placeholder_for_schema(pattern, object, root, &taken)
                && let Some(example) = build_example_value(child, root, visited_refs, None)
            {
                taken.insert(key.clone());
                rendered.insert(key, example);
                made_progress = true;
            }
        }

        if !made_progress {
            break;
        }
    }
    if !rendered.is_empty() {
        merge_example_value(merged, Value::Object(rendered));
    }
}