polyspec_crudui_validator/expr/condition_map.rs
1//! Condition-map resolver (expressions.md §8). A thin wrapper over the
2//! expression engine — NOT a separate parser.
3//!
4//! A condition map is an ordered `{expr: value, …}`. Keys (each an §2 expression)
5//! are evaluated in declaration order; the first truthy key's value wins. On a
6//! miss the literal `true` key's value is used if present, else null. Declaration
7//! order must be preserved by the caller's map type (`serde_json::Map` with
8//! `preserve_order`, or an ordered key list).
9//!
10//! The `true` default key is the literal expression `true` (always truthy); R4
11//! forbids convention sigils such as `_`. The map's value is returned verbatim
12//! (boolean | string | number | null) — the call site decides the expected type.
13
14use serde_json::Value;
15
16use super::expression::Expression;
17
18/// The default (else) key: the literal expression `true`.
19pub const DEFAULT_KEY: &str = "true";
20
21/// Resolve a condition map against form data.
22///
23/// `entries` is the ordered `(expr, value)` list; the default key is matched by
24/// literal text (never by evaluation), so it never short-circuits an earlier real
25/// condition. Returns `None` only when no key matches and there is no `true` key.
26pub fn resolve(
27 entries: &[(String, Value)],
28 form_data: &Value,
29 current_path: &[String],
30 row_keys: &[usize],
31) -> Option<Value> {
32 for (expr, value) in entries {
33 if expr == DEFAULT_KEY {
34 continue;
35 }
36 if key_holds(expr, form_data, current_path, row_keys) {
37 return Some(value.clone());
38 }
39 }
40
41 for (expr, value) in entries {
42 if expr == DEFAULT_KEY {
43 return Some(value.clone());
44 }
45 }
46
47 None
48}
49
50/// Whether a non-default key holds. A key that does not parse is a literal, not a
51/// condition, so it does not hold: the JS reference `evaluateCondition` catches the
52/// parse error and returns false (packages/validator/ts/src/validate/validator.ts).
53#[allow(clippy::disallowed_methods, reason = "a key that does not parse is a literal")]
54fn key_holds(expr: &str, form_data: &Value, current_path: &[String], row_keys: &[usize]) -> bool {
55 Expression::evaluate(expr, form_data, current_path, row_keys).unwrap_or(false)
56}