icydb-model 0.299.0

IcyDB application-model authoring, validation, and code generation
Documentation
use super::*;
use crate::base::validator::decimal::MaxDecimalPlaces;

struct TestCtx {
    issues: crate::visitor::VisitorIssues,
}

impl TestCtx {
    fn new() -> Self {
        Self {
            issues: crate::visitor::VisitorIssues::new(),
        }
    }
}

impl crate::visitor::VisitorContext for TestCtx {
    fn add_issue(&mut self, issue: crate::visitor::Issue) {
        self.issues.push(String::new(), issue);
    }

    fn add_issue_at(&mut self, _: crate::visitor::PathSegment, issue: crate::visitor::Issue) {
        self.add_issue(issue);
    }
}

fn assert_invalid_numeric_bound<N: NumericValue + Copy>(bound: N) {
    let validators: &[&dyn Validator<u128>] = &[
        &Lt::new(bound),
        &Gt::new(bound),
        &Lte::new(bound),
        &Gte::new(bound),
        &Equal::new(bound),
        &NotEqual::new(bound),
        &Range::new(bound, bound),
        &MultipleOf::new(bound),
    ];
    for validator in validators {
        for value in [0_u128, 1, 42] {
            for _ in 0..2 {
                let mut ctx = TestCtx::new();
                validator.validate(&value, &mut ctx);
                assert_eq!(ctx.issues.get("").map(<[_]>::len), Some(1));
            }
        }
    }
}

#[test]
fn invalid_numeric_validator_configuration_is_never_accepted() {
    assert_invalid_numeric_bound(u128::MAX);
    for bound in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, 1e40, -1e40] {
        assert_invalid_numeric_bound(bound);
    }
    for bound in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY, f32::MAX] {
        assert_invalid_numeric_bound(bound);
    }
    for validator in [Range::new(0_u128, u128::MAX), Range::new(u128::MAX, 0)] {
        let mut ctx = TestCtx::new();
        validator.validate(&0_u128, &mut ctx);
        assert_eq!(ctx.issues.get("").map(<[_]>::len), Some(1));
    }
}

#[test]
fn invalid_numeric_decimal_places_configuration_is_never_accepted() {
    for validator in [MaxDecimalPlaces::new(-1), MaxDecimalPlaces::new(u64::MAX)] {
        for value in [Decimal::ZERO, Decimal::from_i128_with_scale(1235, 3)] {
            let mut ctx = TestCtx::new();
            validator.validate(&value, &mut ctx);
            assert_eq!(ctx.issues.get("").map(<[_]>::len), Some(1));
        }
    }
}

#[test]
fn numeric_validator_boundaries_and_defaults_remain_valid() {
    let maximum = Decimal::from_i128_with_scale(i128::MAX, 0);
    for validator in [
        &Gte::new(i128::MIN) as &dyn Validator<Decimal>,
        &Lte::new(i128::MAX),
        &Equal::new(i128::MAX as u128),
        &Range::new(i128::MIN, i128::MAX),
        &MultipleOf::new(i128::MAX),
    ] {
        let mut ctx = TestCtx::new();
        validator.validate(&maximum, &mut ctx);
        assert!(ctx.issues.is_empty());
    }
    let defaults: &[(&dyn Validator<i32>, &dyn Validator<i32>)] = &[
        (&Lt::default(), &Lt::new(0)),
        (&Gt::default(), &Gt::new(0)),
        (&Lte::default(), &Lte::new(0)),
        (&Gte::default(), &Gte::new(0)),
        (&Equal::default(), &Equal::new(0)),
        (&NotEqual::default(), &NotEqual::new(0)),
        (&Range::default(), &Range::new(0, 0)),
        (&MultipleOf::default(), &MultipleOf::new(0)),
    ];
    for (default, explicit) in defaults {
        for value in [-1, 0, 1] {
            let mut default_ctx = TestCtx::new();
            let mut explicit_ctx = TestCtx::new();
            default.validate(&value, &mut default_ctx);
            explicit.validate(&value, &mut explicit_ctx);
            assert_eq!(
                default_ctx.issues.is_empty(),
                explicit_ctx.issues.is_empty()
            );
        }
    }
    for (validator, value, valid) in [
        (MaxDecimalPlaces::default(), Decimal::ZERO, true),
        (
            MaxDecimalPlaces::new(0),
            Decimal::from_i128_with_scale(1, 1),
            false,
        ),
        (MaxDecimalPlaces::new(u32::MAX), maximum, true),
    ] {
        let mut ctx = TestCtx::new();
        validator.validate(&value, &mut ctx);
        assert_eq!(ctx.issues.is_empty(), valid);
    }
}

#[test]
fn lt() {
    let v = Lt::new(10);
    let mut ctx = TestCtx::new();

    v.validate(&5, &mut ctx);
    assert!(ctx.issues.is_empty());

    v.validate(&10, &mut ctx);
    assert!(!ctx.issues.is_empty());
}

#[test]
fn gte() {
    let v = Gte::new(5);
    let mut ctx = TestCtx::new();

    v.validate(&5, &mut ctx);
    assert!(ctx.issues.is_empty());

    v.validate(&4, &mut ctx);
    assert!(!ctx.issues.is_empty());
}

#[test]
fn range() {
    let r = Range::new(1, 3);
    let mut ctx = TestCtx::new();

    r.validate(&2, &mut ctx);
    assert!(ctx.issues.is_empty());

    r.validate(&0, &mut ctx);
    assert!(!ctx.issues.is_empty());
}

#[test]
fn multiple_of() {
    let m = MultipleOf::new(5);
    let mut ctx = TestCtx::new();

    m.validate(&10, &mut ctx);
    assert!(ctx.issues.is_empty());

    m.validate(&11, &mut ctx);
    assert!(!ctx.issues.is_empty());
}

#[test]
fn multiple_of_decimal_scale_alignment_preserves_validation() {
    for scale in 1..=28 {
        for sign in [-1_i128, 1] {
            let value = Decimal::from_i128_with_scale(sign * (5 * 10_i128.pow(scale) + 1), scale);
            for divisor in [-10_000_000_000_000_i64, 10_000_000_000_000] {
                let mut ctx = TestCtx::new();
                MultipleOf::new(divisor).validate(&value, &mut ctx);
                assert!(
                    !ctx.issues.is_empty(),
                    "{value} is not a multiple of {divisor}"
                );

                let mut ctx = TestCtx::new();
                MultipleOf::new(Decimal::from_i128_with_scale(sign, 28)).validate(&value, &mut ctx);
                assert!(
                    ctx.issues.is_empty(),
                    "every admitted decimal is a multiple of 1e-28"
                );
            }
        }
    }
    let mut ctx = TestCtx::new();
    MultipleOf::new(-1).validate(&Decimal::from_i128_with_scale(i128::MIN, 0), &mut ctx);
    assert!(ctx.issues.is_empty());
    let mut ctx = TestCtx::new();
    MultipleOf::new(0).validate(&Decimal::ZERO, &mut ctx);
    assert!(!ctx.issues.is_empty());
}