tier 0.1.17

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

use serde_json::{Map, Value};

use crate::schema::count::{keyword_u64, usize_saturating};
use crate::schema::{dynamic_object_placeholder, dynamic_object_placeholder_for_schema};

pub(super) fn required_properties(object: &Map<String, Value>) -> BTreeSet<String> {
    object
        .get("required")
        .and_then(Value::as_array)
        .map(|required| {
            required
                .iter()
                .filter_map(Value::as_str)
                .map(ToOwned::to_owned)
                .collect()
        })
        .unwrap_or_default()
}

pub(super) fn min_properties(object: &Map<String, Value>) -> usize {
    keyword_u64(object, "minProperties").map_or(0, usize_saturating)
}

pub(super) fn max_properties(object: &Map<String, Value>) -> Option<usize> {
    keyword_u64(object, "maxProperties").map(usize_saturating)
}

pub(super) fn all_known_properties_required(
    object: &Map<String, Value>,
    properties: &Map<String, Value>,
    min_properties: usize,
) -> bool {
    object
        .get("additionalProperties")
        .and_then(Value::as_bool)
        .is_some_and(|allowed| !allowed)
        && !properties.is_empty()
        && min_properties >= properties.len()
}

pub(super) fn allows_optional_properties(
    max_properties: Option<usize>,
    required_property_count: usize,
) -> bool {
    max_properties.is_none_or(|max_properties| max_properties > required_property_count)
}

pub(super) fn required_dynamic_properties(
    required: bool,
    min_properties: usize,
    known_property_count: usize,
) -> bool {
    required && min_properties > known_property_count
}

pub(super) fn allows_dynamic_properties(
    max_properties: Option<usize>,
    required_fixed_property_count: usize,
) -> bool {
    max_properties.is_none_or(|max_properties| max_properties > required_fixed_property_count)
}

pub(super) fn dynamic_property_segment(
    object: &Map<String, Value>,
    root: &Value,
    reserved_keys: &BTreeSet<String>,
) -> Option<String> {
    let dynamic_keys_allowed = object.get("propertyNames").is_none_or(|_| {
        dynamic_object_placeholder_for_schema(object, root, reserved_keys).is_some()
    });
    if !dynamic_keys_allowed {
        return None;
    }

    let placeholder = dynamic_object_placeholder(reserved_keys);
    Some(if placeholder == "{item}" {
        "*".to_owned()
    } else {
        placeholder
    })
}

pub(super) fn additional_properties_schema<'a>(
    object: &'a Map<String, Value>,
    required_dynamic_properties: bool,
    implicit_additional: &'a Value,
) -> Option<&'a Value> {
    object
        .get("additionalProperties")
        .filter(|value| !matches!(value, Value::Bool(false)))
        .or_else(|| {
            if !object.contains_key("additionalProperties") && required_dynamic_properties {
                Some(implicit_additional)
            } else {
                None
            }
        })
}