Skip to main content

lemma/api/
response.rs

1//! Response / RuleResult JSON shapes.
2
3use crate::api::value::RuleResultValue;
4use crate::evaluation::evaluate_data::EvaluateDataValue;
5use crate::evaluation::explanations::Explanation;
6use crate::evaluation::response::{Response as DomainResponse, RuleResult as DomainRuleResult};
7use crate::parsing::ast::DateTimeValue;
8use crate::result_value::RowResult;
9use indexmap::IndexMap;
10use serde::Serialize;
11
12fn evaluate_data_json(value: &EvaluateDataValue) -> serde_json::Value {
13    enum Step<'a> {
14        Descend(&'a EvaluateDataValue),
15        NextChild,
16        Deliver(serde_json::Value),
17    }
18    enum Frame<'a> {
19        List {
20            out: Vec<serde_json::Value>,
21            children: std::slice::Iter<'a, EvaluateDataValue>,
22        },
23        Map {
24            out: serde_json::Map<String, serde_json::Value>,
25            pending_key: Option<String>,
26            children: indexmap::map::Iter<'a, String, EvaluateDataValue>,
27        },
28    }
29
30    let mut frames: Vec<Frame<'_>> = Vec::new();
31    let mut step = Step::Descend(value);
32    loop {
33        step = match step {
34            Step::Descend(node) => match node {
35                EvaluateDataValue::Literal(text) => {
36                    Step::Deliver(serde_json::Value::String(text.clone()))
37                }
38                EvaluateDataValue::Boolean(bit) => Step::Deliver(serde_json::Value::Bool(*bit)),
39                EvaluateDataValue::List(items) => {
40                    frames.push(Frame::List {
41                        out: Vec::with_capacity(items.len()),
42                        children: items.iter(),
43                    });
44                    Step::NextChild
45                }
46                EvaluateDataValue::Map(entries) => {
47                    frames.push(Frame::Map {
48                        out: serde_json::Map::with_capacity(entries.len()),
49                        pending_key: None,
50                        children: entries.iter(),
51                    });
52                    Step::NextChild
53                }
54            },
55            Step::NextChild => {
56                let child = match frames.last_mut() {
57                    Some(Frame::List { children, .. }) => children.next(),
58                    Some(Frame::Map {
59                        pending_key,
60                        children,
61                        ..
62                    }) => children.next().map(|(key, child)| {
63                        *pending_key = Some(key.clone());
64                        child
65                    }),
66                    None => {
67                        panic!("BUG: evaluate data json frame stack empty while reading a child")
68                    }
69                };
70                match child {
71                    Some(child) => Step::Descend(child),
72                    None => {
73                        let finished = frames
74                            .pop()
75                            .expect("BUG: evaluate data json frame stack empty on completion");
76                        let done = match finished {
77                            Frame::List { out, .. } => serde_json::Value::Array(out),
78                            Frame::Map { out, .. } => serde_json::Value::Object(out),
79                        };
80                        Step::Deliver(done)
81                    }
82                }
83            }
84            Step::Deliver(done) => match frames.last_mut() {
85                None => return done,
86                Some(Frame::List { out, .. }) => {
87                    out.push(done);
88                    Step::NextChild
89                }
90                Some(Frame::Map {
91                    out, pending_key, ..
92                }) => {
93                    let key = pending_key
94                        .take()
95                        .expect("BUG: nested evaluate data json value has no field name");
96                    out.insert(key, done);
97                    Step::NextChild
98                }
99            },
100        };
101    }
102}
103
104#[derive(Debug, Clone, Serialize)]
105pub struct Response {
106    #[serde(rename = "spec")]
107    pub spec_name: String,
108    pub effective: String,
109    #[serde(skip_serializing_if = "Option::is_none")]
110    pub spec_effective_from: Option<DateTimeValue>,
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub spec_effective_to: Option<DateTimeValue>,
113    pub results: IndexMap<String, RuleResult>,
114    #[serde(skip_serializing_if = "Vec::is_empty")]
115    pub rows: Vec<RowResult>,
116}
117
118impl From<&DomainResponse> for Response {
119    fn from(response: &DomainResponse) -> Self {
120        Self {
121            spec_name: response.spec_name.clone(),
122            effective: response.effective.clone(),
123            spec_effective_from: response.spec_effective_from.clone(),
124            spec_effective_to: response.spec_effective_to.clone(),
125            results: response
126                .results
127                .iter()
128                .map(|(name, result)| (name.clone(), RuleResult::from(result)))
129                .collect(),
130            rows: response.rows.clone(),
131        }
132    }
133}
134
135/// Rule result JSON: flattened value fields, no rule/veto_detail.
136#[derive(Debug, Clone, Serialize)]
137pub struct RuleResult {
138    pub vetoed: bool,
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub veto_reason: Option<String>,
141    pub rule_type: String,
142    #[serde(flatten)]
143    pub result: Option<RuleResultValue>,
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub explanation: Option<Explanation>,
146    #[serde(skip_serializing_if = "Vec::is_empty")]
147    pub missing_data: Vec<String>,
148    /// Closed options for keys in [`Self::missing_data`] on this run.
149    #[serde(skip_serializing_if = "IndexMap::is_empty")]
150    pub missing_data_options: IndexMap<String, Vec<serde_json::Value>>,
151}
152
153impl From<&DomainRuleResult> for RuleResult {
154    fn from(result: &DomainRuleResult) -> Self {
155        Self {
156            vetoed: result.vetoed,
157            veto_reason: result.veto_reason.clone(),
158            rule_type: result.rule_type.clone(),
159            result: result.result.as_ref().map(RuleResultValue::from),
160            explanation: result.explanation.clone(),
161            missing_data: result.missing_data().to_vec(),
162            missing_data_options: result
163                .missing_data_options()
164                .iter()
165                .map(|(key, options)| {
166                    (
167                        key.clone(),
168                        options.iter().map(evaluate_data_json).collect(),
169                    )
170                })
171                .collect(),
172        }
173    }
174}