Skip to main content

polyspec_crudui_validator/validate/
visibility.rs

1//! Visibility (validation rules, "Evaluation"): `design.show` resolved against
2//! the data in the field's row context.
3
4use serde_json::Value;
5
6use super::rules::is_condition_expression;
7use super::validator::has_ternary_regex;
8use crate::expr::{condition_map, Evaluator, Expression, Node};
9
10/// Whether a `design.show` value shows its field. The value resolves like a
11/// conditional parameter: a condition map selects its value, a ternary its branch
12/// and a condition expression that parses completely its value; any other string
13/// and any other value is a literal. Only a value that resolves to `false` hides
14/// the field, so a missing value, `null`, a condition map that selects nothing and
15/// a literal string show it. `path` is the field's data path, row keys included,
16/// and `row_keys` the positions of the row keys in it.
17pub fn show(value: Option<&Value>, data: &Value, path: &[String], row_keys: &[usize]) -> bool {
18    let resolved = match value {
19        None => return true,
20        Some(Value::Object(map)) => {
21            let entries: Vec<(String, Value)> =
22                map.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
23            condition_map::resolve(&entries, data, path, row_keys).unwrap_or(Value::Null)
24        }
25        Some(Value::String(expression)) => match Expression::parse(expression) {
26            Ok(node @ Node::Ternary { .. }) => {
27                Evaluator::new(data, path, row_keys).evaluate_value(&node)
28            }
29            Ok(node) if is_condition_expression(expression) && !has_ternary_regex(expression) => {
30                Evaluator::new(data, path, row_keys).evaluate_value(&node)
31            }
32            _ => return true,
33        },
34        Some(literal) => literal.clone(),
35    };
36    resolved != Value::Bool(false)
37}
38
39/// Whether a field is visible: its `design` object's `show` shows it. A field
40/// without `design.show` is visible.
41pub fn is_visible(field: &Value, data: &Value, path: &[String], row_keys: &[usize]) -> bool {
42    match field.get("design") {
43        Some(Value::Object(design)) => show(design.get("show"), data, path, row_keys),
44        _ => true,
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51    use serde_json::json;
52
53    #[test]
54    fn show_values() {
55        let data = json!({ "on": 1, "rows": { "r": { "kind": "a" } } });
56        let path = |p: &str| p.split('.').map(String::from).collect::<Vec<_>>();
57        assert!(show(None, &data, &path("x"), &[]));
58        assert!(show(Some(&json!(null)), &data, &path("x"), &[]));
59        assert!(show(
60            Some(&json!("not an (expression")),
61            &data,
62            &path("x"),
63            &[]
64        ));
65        assert!(show(Some(&json!(true)), &data, &path("x"), &[]));
66        assert!(!show(Some(&json!(false)), &data, &path("x"), &[]));
67        assert!(show(Some(&json!(".on")), &data, &path("x"), &[]));
68        assert!(!show(Some(&json!(".off")), &data, &path("x"), &[]));
69        assert!(show(
70            Some(&json!(".kind == 'a'")),
71            &data,
72            &path("rows.r.note"),
73            &[]
74        ));
75        assert!(!show(
76            Some(&json!({ ".on": false, "true": true })),
77            &data,
78            &path("x"),
79            &[]
80        ));
81        assert!(show(
82            Some(&json!({ ".off": false, "true": true })),
83            &data,
84            &path("x"),
85            &[]
86        ));
87        assert!(show(
88            Some(&json!({ ".off": false })),
89            &data,
90            &path("x"),
91            &[]
92        ));
93        assert!(show(Some(&json!({ ".on": null })), &data, &path("x"), &[]));
94        assert!(is_visible(
95            &json!({ "design": false }),
96            &data,
97            &path("x"),
98            &[]
99        ));
100        assert!(!is_visible(
101            &json!({ "design": { "show": false } }),
102            &data,
103            &path("x"),
104            &[]
105        ));
106    }
107}