icydb-model-macros 0.303.0

Procedural macros for IcyDB application models
Documentation
use darling::{FromMeta, ast::NestedMeta};
use quote::quote;

use super::{SourceRule, SourceRuleOperation, Type};

fn parse_rule(tokens: proc_macro2::TokenStream) -> Result<SourceRule, darling::Error> {
    let args = NestedMeta::parse_meta_list(tokens)?;
    SourceRule::from_list(args.as_slice())
}

fn parse_type(tokens: proc_macro2::TokenStream) -> Result<Type, darling::Error> {
    let args = NestedMeta::parse_meta_list(tokens)?;
    Type::from_list(args.as_slice())
}

#[test]
fn parses_each_closed_typed_rule_operation() {
    assert!(matches!(
        parse_rule(quote!(
            name = "length",
            length_range_inclusive(min = 1, max = 40)
        ))
        .expect("length range should parse")
        .operation,
        SourceRuleOperation::LengthRangeInclusive { .. }
    ));
    assert!(matches!(
        parse_rule(quote!(name = "step", multiple_of(divisor = 5)))
            .expect("multiple-of should parse")
            .operation,
        SourceRuleOperation::MultipleOf { .. }
    ));
    assert!(matches!(
        parse_rule(quote!(
            name = "maximum",
            numeric_maximum_inclusive(value = 100)
        ))
        .expect("numeric maximum should parse")
        .operation,
        SourceRuleOperation::NumericMaximumInclusive { .. }
    ));
    assert!(matches!(
        parse_rule(quote!(
            name = "minimum",
            numeric_minimum_inclusive(value = 0)
        ))
        .expect("numeric minimum should parse")
        .operation,
        SourceRuleOperation::NumericMinimumInclusive { .. }
    ));
    assert!(matches!(
        parse_rule(quote!(
            name = "range",
            numeric_range_inclusive(min = 0, max = 100)
        ))
        .expect("numeric range should parse")
        .operation,
        SourceRuleOperation::NumericRangeInclusive { .. }
    ));
}

#[test]
fn rejects_missing_multiple_and_malformed_typed_operations() {
    assert!(parse_rule(quote!(name = "missing")).is_err());
    assert!(parse_rule(quote!(name = "unknown", unsupported_operation(value = 0))).is_err());
    assert!(
        parse_rule(quote!(
            name = "multiple",
            numeric_minimum_inclusive(value = 0),
            numeric_maximum_inclusive(value = 100)
        ))
        .is_err()
    );
    assert!(
        parse_rule(quote!(
            name = "missing_operand",
            numeric_range_inclusive(min = 0)
        ))
        .is_err()
    );
    assert!(
        parse_rule(quote!(
            name = "wrong_operand",
            numeric_minimum_inclusive(min = 0)
        ))
        .is_err()
    );
    assert!(
        parse_rule(quote!(
            name = "repeated_operand",
            numeric_minimum_inclusive(value = 0, value = 1)
        ))
        .is_err()
    );
    assert!(
        parse_rule(quote!(
            name = "reversed",
            length_range_inclusive(min = 2, max = 1)
        ))
        .is_err()
    );
    assert!(parse_rule(quote!(name = "zero", multiple_of(divisor = 0))).is_err());
}

#[test]
fn rejects_invalid_and_duplicate_local_rule_names() {
    assert!(parse_rule(quote!(name = "", numeric_minimum_inclusive(value = 0))).is_err());
    assert!(
        parse_rule(quote!(
            name = "invalid name",
            numeric_minimum_inclusive(value = 0)
        ))
        .is_err()
    );
    assert!(
        parse_type(quote!(
            rule(name = "range", numeric_minimum_inclusive(value = 0)),
            rule(name = "range", numeric_maximum_inclusive(value = 100))
        ))
        .is_err()
    );
}