Skip to main content

qa_spec/
expr.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4
5/// Lightweight expression AST used for `visible_if` and decisions.
6#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
7#[serde(tag = "op", rename_all = "snake_case")]
8pub enum Expr {
9    LiteralBool { value: bool },
10    Eq { left: String, right: String },
11    And { expressions: Vec<Expr> },
12    Or { expressions: Vec<Expr> },
13    Not { expression: Box<Expr> },
14    Var { path: String },
15}
16
17impl Expr {
18    fn get_value<'a>(ctx: &'a Value, path: &str) -> Option<&'a Value> {
19        ctx.pointer(path)
20    }
21
22    /// Evaluates the expression to a boolean if possible.
23    pub fn evaluate(&self, ctx: &Value) -> Option<bool> {
24        match self {
25            Expr::LiteralBool { value } => Some(*value),
26            Expr::Eq { left, right } => {
27                let left_val = Self::get_value(ctx, left)?;
28                let right_val = Self::get_value(ctx, right)?;
29                Some(left_val == right_val)
30            }
31            Expr::And { expressions } => {
32                for expr in expressions {
33                    match expr.evaluate(ctx) {
34                        Some(true) => continue,
35                        Some(false) => return Some(false),
36                        None => return None,
37                    }
38                }
39                Some(true)
40            }
41            Expr::Or { expressions } => {
42                for expr in expressions {
43                    if let Some(true) = expr.evaluate(ctx) {
44                        return Some(true);
45                    }
46                }
47                Some(false)
48            }
49            Expr::Not { expression } => expression.evaluate(ctx).map(|value| !value),
50            Expr::Var { path } => Self::get_value(ctx, path).and_then(|v| v.as_bool()),
51        }
52    }
53}