tier 0.1.17

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

use serde_json::Value;

mod generic;
mod number;
mod string;

use self::generic::uniquify_generic_example_value;
use self::number::uniquify_number_example;
use self::string::uniquify_string_example;
use super::super::matches::example_matches_schema;
use super::constraints::{array_requires_unique_items, legacy_additional_items_for_schema};
use crate::value::values_contain;

pub(in crate::schema::example) fn uniquify_merged_array_example(
    merged: &mut Option<Value>,
    object: &serde_json::Map<String, Value>,
    root: &Value,
) {
    if let Some(value) = merged {
        uniquify_example_value_in_place(value, object, root);
    }
}

pub(in crate::schema::example) fn uniquify_example_value_in_place(
    value: &mut Value,
    object: &serde_json::Map<String, Value>,
    root: &Value,
) {
    if let Value::Array(items) = value
        && array_requires_unique_items(object)
    {
        uniquify_array_example_items(items, object, root);
    }
}

fn array_item_schema(object: &serde_json::Map<String, Value>, index: usize) -> Option<&Value> {
    if let Some(prefix_items) = object.get("prefixItems").and_then(Value::as_array)
        && let Some(schema) = prefix_items.get(index)
    {
        return Some(schema);
    }

    if let Some(item_schemas) = object.get("items").and_then(Value::as_array)
        && let Some(schema) = item_schemas.get(index)
    {
        return Some(schema);
    }

    object
        .get("items")
        .filter(|value| !value.is_array() && !matches!(value, Value::Bool(false)))
        .or_else(|| {
            legacy_additional_items_for_schema(object)
                .filter(|value| !value.is_array() && !matches!(value, Value::Bool(false)))
        })
}

pub(super) fn uniquify_array_example_items(
    items: &mut [Value],
    object: &serde_json::Map<String, Value>,
    root: &Value,
) {
    let mut seen = Vec::<Value>::new();
    for index in 0..items.len() {
        let Some((item, future)) = items[index..].split_first_mut() else {
            break;
        };
        if values_contain(&seen, item)
            && let Some(schema) = array_item_schema(object, index)
        {
            let mut reserved = seen.clone();
            reserved.extend(future.iter().cloned());
            let unique = uniquify_example_value(item.clone(), schema, root, &reserved)
                .or_else(|| uniquify_example_value(item.clone(), schema, root, &seen));
            if let Some(unique) = unique {
                *item = unique;
            }
        }
        seen.push(item.clone());
    }
}

pub(in crate::schema::example) fn build_repeated_example_values(
    example: Value,
    schema: &Value,
    root: &Value,
    count: usize,
    unique_items: bool,
    existing: &[Value],
) -> Vec<Value> {
    let mut taken = if unique_items {
        existing.to_vec()
    } else {
        Vec::new()
    };
    let mut rendered = Vec::with_capacity(count);
    for _ in 0..count {
        let value = if unique_items {
            uniquify_example_value(example.clone(), schema, root, &taken)
                .unwrap_or_else(|| example.clone())
        } else {
            example.clone()
        };
        if unique_items {
            taken.push(value.clone());
        }
        rendered.push(value);
    }
    rendered
}

fn uniquify_example_value(
    value: Value,
    schema: &Value,
    root: &Value,
    existing: &[Value],
) -> Option<Value> {
    if !values_contain(existing, &value) {
        return Some(value);
    }

    let specialized = match value {
        Value::String(text) => uniquify_string_example(text, schema, root, existing),
        Value::Number(number) => uniquify_number_example(number, schema, root, existing),
        Value::Bool(flag) => {
            let candidate = Value::Bool(!flag);
            (!values_contain(existing, &candidate)
                && example_matches_schema(&candidate, schema, root, &mut BTreeSet::new()))
            .then_some(candidate)
        }
        _ => None,
    };

    specialized.or_else(|| uniquify_generic_example_value(schema, root, existing))
}