lemma-engine 0.8.14

A language that means business.
Documentation
use lemma::evaluation::OperationResult;
use lemma::parsing::ast::DateTimeValue;
use lemma::Engine;
use rust_decimal::Decimal;
use std::collections::HashMap;

#[test]
fn rule_vetoes_when_result_exceeds_decimal_wire_limit() {
    let max_decimal = Decimal::MAX.normalize().to_string();
    let code = format!(
        r#"
spec decimal_limit
data max_val: {max_decimal}
data two: 2
rule over_limit: max_val * two
"#
    );

    let mut engine = Engine::new();
    engine.load(&code, lemma::SourceType::Volatile).unwrap();

    let now = DateTimeValue::now();
    let response = engine
        .run(
            None,
            "decimal_limit",
            Some(&now),
            HashMap::new(),
            false,
            lemma::EvaluationRequest::default(),
        )
        .expect("evaluation must complete");

    let rule = response
        .results
        .get("over_limit")
        .expect("over_limit rule must be present");

    match &rule.result {
        OperationResult::Veto(veto) => {
            assert_eq!(
                veto.to_string(),
                "Calculated result exceeds decimal value limit"
            );
        }
        OperationResult::Value(value) => {
            panic!("expected Veto for uncommittable result, got {value:?}");
        }
    }
}

#[test]
fn response_number_json_never_uses_fraction_notation() {
    use indexmap::IndexMap;
    use lemma::evaluation::response::{EvaluatedRule, Response, RuleResult};
    use lemma::planning::semantics::{
        Expression, ExpressionKind, LiteralValue, RulePath, Source, Span,
    };

    fn dummy_source() -> Source {
        Source::new(
            lemma::SourceType::Volatile,
            Span {
                start: 0,
                end: 0,
                line: 1,
                col: 1,
            },
        )
    }

    let mut results = IndexMap::new();
    results.insert(
        "third".to_string(),
        RuleResult {
            rule: EvaluatedRule {
                name: "third".to_string(),
                path: RulePath::new(vec![], "third".to_string()),
                default_expression: Expression::new(
                    ExpressionKind::Literal(Box::new(LiteralValue::number_from_decimal(
                        Decimal::new(1, 1) / Decimal::new(3, 1),
                    ))),
                    dummy_source(),
                ),
                unless_branches: vec![],
                source_location: dummy_source(),
                rule_type: lemma::planning::semantics::primitive_number().clone(),
            },
            result: OperationResult::Value(Box::new(LiteralValue::number_from_decimal(
                Decimal::new(1, 1) / Decimal::new(3, 1),
            ))),
            data: vec![],
            operations: vec![],
            explanation: None,
            rule_type: lemma::planning::semantics::primitive_number().clone(),
        },
    );

    let response = Response {
        spec_name: "test".to_string(),
        spec_hash: None,
        spec_effective_from: None,
        spec_effective_to: None,
        data: vec![],
        results,
    };

    let json: serde_json::Value =
        serde_json::from_str(&serde_json::to_string(&response).unwrap()).unwrap();
    let number = json["results"]["third"]["result"]["value"]["value"]["number"]
        .as_str()
        .expect("number must be a JSON string");
    assert!(
        !number.contains('/'),
        "wire number must not use fraction notation, got {number}"
    );
}