tier 0.1.17

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

use serde_json::Value;

use crate::number::json_number_from_i128;
use crate::value::values_contain;

use super::super::super::matches::example_matches_schema;
use super::super::super::numeric::{
    first_integer_multiple_at_or_above, integer_multiple_step_constraint,
    last_integer_multiple_at_or_below,
};

pub(super) fn uniquify_number_example(
    number: serde_json::Number,
    schema: &Value,
    root: &Value,
    existing: &[Value],
) -> Option<Value> {
    if let Some(integer) = number.as_i64() {
        return uniquify_integer_number_example(i128::from(integer), schema, root, existing);
    }

    if let Some(integer) = number.as_u64() {
        return uniquify_integer_number_example(i128::from(integer), schema, root, existing);
    }

    let base = number.as_f64()?;
    let step = schema
        .get("multipleOf")
        .and_then(Value::as_f64)
        .filter(|step| step.is_normal() && *step > 0.0)
        .unwrap_or(1.0);
    for attempt in 1..=1024u32 {
        for direction in [1.0, -1.0] {
            let Some(candidate) =
                unique_real_number_candidate(base, step, attempt, direction).map(Value::Number)
            else {
                continue;
            };
            if !values_contain(existing, &candidate)
                && example_matches_schema(&candidate, schema, root, &mut BTreeSet::new())
            {
                return Some(candidate);
            }
        }
    }

    None
}

fn unique_real_number_candidate(
    base: f64,
    step: f64,
    attempt: u32,
    direction: f64,
) -> Option<serde_json::Number> {
    let candidate = base + step * f64::from(attempt) * direction;
    serde_json::Number::from_f64(candidate)
}

fn uniquify_integer_number_example(
    integer: i128,
    schema: &Value,
    root: &Value,
    existing: &[Value],
) -> Option<Value> {
    if let Some(object) = schema.as_object()
        && let Some(step) = integer_multiple_step_constraint(object)
    {
        return uniquify_stepped_integer_number_example(integer, step, schema, root, existing);
    }

    uniquify_adjacent_integer_number_example(integer, schema, root, existing)
}

fn uniquify_stepped_integer_number_example(
    integer: i128,
    step: i128,
    schema: &Value,
    root: &Value,
    existing: &[Value],
) -> Option<Value> {
    let mut next = integer
        .checked_add(1)
        .and_then(|lower| first_integer_multiple_at_or_above(lower, step));
    let mut previous = integer
        .checked_sub(1)
        .and_then(|upper| last_integer_multiple_at_or_below(upper, step));

    for _ in 0..1024 {
        if let Some(value) = next
            .and_then(json_number_from_i128)
            .map(Value::Number)
            .filter(|value| {
                !values_contain(existing, value)
                    && example_matches_schema(value, schema, root, &mut BTreeSet::new())
            })
        {
            return Some(value);
        }
        if let Some(value) = previous
            .and_then(json_number_from_i128)
            .map(Value::Number)
            .filter(|value| {
                !values_contain(existing, value)
                    && example_matches_schema(value, schema, root, &mut BTreeSet::new())
            })
        {
            return Some(value);
        }

        next = next.and_then(|candidate| candidate.checked_add(step));
        previous = previous.and_then(|candidate| candidate.checked_sub(step));
    }
    None
}

fn uniquify_adjacent_integer_number_example(
    integer: i128,
    schema: &Value,
    root: &Value,
    existing: &[Value],
) -> Option<Value> {
    let mut candidate = integer;
    for _ in 0..1024 {
        let Some(next) = candidate.checked_add(1) else {
            break;
        };
        candidate = next;
        if let Some(value) = json_number_from_i128(candidate).map(Value::Number)
            && !values_contain(existing, &value)
            && example_matches_schema(&value, schema, root, &mut BTreeSet::new())
        {
            return Some(value);
        }
    }

    let mut candidate = integer;
    for _ in 0..1024 {
        let Some(previous) = candidate.checked_sub(1) else {
            break;
        };
        candidate = previous;
        if let Some(value) = json_number_from_i128(candidate).map(Value::Number)
            && !values_contain(existing, &value)
            && example_matches_schema(&value, schema, root, &mut BTreeSet::new())
        {
            return Some(value);
        }
    }

    None
}