lemma-engine 0.9.0

A pure, declarative language for business rules.
Documentation
use lemma::{DateTimeValue, Engine};
use std::collections::HashMap;

#[test]
fn necessary_data_include_nested_spec_data_for_local_rule_deps() {
    let code = r#"
spec money
data money: measure
  -> unit eur 1
  -> unit usd 0.84

spec pricing
uses money
data quantity: 10
data is_member: false
data price: money.money
rule discount: 0%
  unless quantity >= 10 then 10%
  unless quantity >= 50 then 20%
  unless is_member then 15%
rule discount_amount: price * discount
rule total: price - discount_amount
  unless price < 50 eur then price

spec cashier
uses calc: pricing
rule total: calc.total
"#;

    let mut engine = Engine::new();
    engine
        .load([(lemma::SourceType::Volatile, code.to_string())])
        .unwrap();
    let now = DateTimeValue::now();

    let show_all = engine.show(None, "cashier", Some(&now)).unwrap();
    assert!(
        show_all.data.contains_key("calc.price"),
        "Expected show data to include calc.price, got: {:?}",
        show_all.data.keys().collect::<Vec<_>>()
    );
    let price_type = &show_all.data.get("calc.price").unwrap().lemma_type;
    assert!(
        price_type.is_measure(),
        "Expected calc.price to be a measure type, got {:?}",
        price_type.name()
    );

    let response = engine
        .run(
            None,
            "cashier",
            Some(&now),
            HashMap::new(),
            Some(&["total".to_string()]),
            false,
        )
        .expect("run must succeed");
    let total = response.results.get("total").expect("total rule");
    assert!(
        total.missing_data.iter().any(|k| k == "calc.price"),
        "unbound calc.price must appear in total.missing_data: {:?}",
        total.missing_data
    );
    let show_price_type = &show_all.data.get("calc.price").unwrap().lemma_type;
    assert_eq!(
        show_price_type.name(),
        price_type.name(),
        "show must preserve nested measure type for calc.price"
    );
}

#[test]
fn run_errors_on_unknown_rule() {
    let mut engine = Engine::new();
    engine
        .load([(
            lemma::SourceType::Volatile,
            "spec test\ndata x: 1\nrule y: x".to_string(),
        )])
        .unwrap();
    let now = DateTimeValue::now();

    let result = engine.run(
        None,
        "test",
        Some(&now),
        HashMap::new(),
        Some(&["nonexistent".to_string()]),
        false,
    );
    assert!(result.is_err(), "Expected error for unknown rule");
    assert!(
        result.unwrap_err().to_string().contains("not found"),
        "Error should mention rule not found"
    );
}