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::super::super::matches::example_matches_schema;
use crate::value::values_contain;

pub(super) fn uniquify_string_example(
    text: String,
    schema: &Value,
    root: &Value,
    existing: &[Value],
) -> Option<Value> {
    if let Some(values) = schema.get("enum").and_then(Value::as_array) {
        for value in values {
            let candidate = value.clone();
            if !values_contain(existing, &candidate)
                && example_matches_schema(&candidate, schema, root, &mut BTreeSet::new())
            {
                return Some(candidate);
            }
        }
    }

    for candidate in constrained_string_unique_candidates(&text, schema) {
        if !values_contain(existing, &candidate)
            && example_matches_schema(&candidate, schema, root, &mut BTreeSet::new())
        {
            return Some(candidate);
        }
    }

    for attempt in 1..=1024 {
        for candidate in [
            Value::String(format!("{text}-{attempt}")),
            Value::String(format!("{text}_{attempt}")),
            Value::String(format!("item{attempt}")),
            Value::String(format!("value{attempt}")),
        ] {
            if !values_contain(existing, &candidate)
                && example_matches_schema(&candidate, schema, root, &mut BTreeSet::new())
            {
                return Some(candidate);
            }
        }
    }

    None
}

fn constrained_string_unique_candidates(text: &str, schema: &Value) -> Vec<Value> {
    let min_length = schema
        .get("minLength")
        .and_then(Value::as_u64)
        .and_then(|length| usize::try_from(length).ok())
        .unwrap_or(0);
    let max_length = schema
        .get("maxLength")
        .and_then(Value::as_u64)
        .and_then(|length| usize::try_from(length).ok());

    let mut lengths = BTreeSet::new();
    let text_length = text.chars().count();
    if text_length > 0 {
        lengths.insert(text_length);
    }
    if min_length > 0 {
        lengths.insert(min_length);
    }
    if let Some(max_length) = max_length
        && max_length > 0
    {
        lengths.insert(max_length);
    }

    let mut candidates = Vec::new();
    for length in lengths.into_iter().take(8) {
        if max_length.is_some_and(|max_length| length > max_length) || length < min_length {
            continue;
        }
        for seed in ['a', 'b', 'c', 'x', 'y', 'z', '0', '1', '2', 'A', 'B', 'C'] {
            let candidate = std::iter::repeat_n(seed, length).collect::<String>();
            if candidate != text {
                candidates.push(Value::String(candidate));
            }
        }
    }

    candidates
}