Skip to main content

hidane_rules/eval/
trace.rs

1//! What a decision reports besides the decision: the values `debug()` received and, for the
2//! coverage report (`crate::coverage`), what each expression evaluated to.
3
4use std::hash::{Hash, Hasher};
5
6use super::{
7    Method, Timestamp,
8    constraint::Comparator,
9    value::{Error, NANOS, Val},
10};
11use crate::{
12    ast::Expr,
13    diag::Position,
14    paths::{UNKNOWN_ID, UNKNOWN_RUN},
15};
16
17/// A value as `debug()` logs it and the coverage report counts it: the official emulator's
18/// value message, which also holds what only rules compute (sets, map diffs, constraints) and
19/// the path segments a list does not know.
20#[derive(Debug, Clone)]
21pub enum Observed {
22    Null,
23    /// What the official emulator does not know, such as a collection group's `request.path`
24    /// (`undefined`).
25    Undefined,
26    Bool(bool),
27    Int(i64),
28    Float(f64),
29    String(String),
30    Bytes(Vec<u8>),
31    Timestamp(Timestamp),
32    /// Seconds and nanoseconds, both of the duration's sign.
33    Duration(i64, i32),
34    /// Latitude and longitude.
35    LatLng(f64, f64),
36    Path(Vec<Segment>),
37    /// In the map's order.
38    Map(Vec<(String, Observed)>),
39    List(Vec<Observed>),
40    Set(Vec<Observed>),
41    /// Every key of the two maps: the other map's, in its order, then those only in this one.
42    MapDiff(Vec<(String, KeyStatus)>),
43    /// What a list knows of a field: each filter's comparator and value.
44    Constraint(Vec<(Comparator, Observed)>),
45}
46
47/// A segment of a path: a name, or what a list does not know, one document ID (`capture`) or
48/// any number of segments (`glob_capture`).
49#[derive(Debug, Clone, PartialEq, Eq, Hash)]
50pub enum Segment {
51    Simple(String),
52    Capture,
53    GlobCapture,
54}
55
56/// What `a.diff(b)` says of a key.
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
58pub enum KeyStatus {
59    /// Only in `a`.
60    Added,
61    /// Only in `b`.
62    Removed,
63    Changed,
64    Unchanged,
65}
66
67impl KeyStatus {
68    /// The official name: `ADDED`, `REMOVED`, `CHANGED` or `UNCHANGED`.
69    pub fn name(self) -> &'static str {
70        match self {
71            Self::Added => "ADDED",
72            Self::Removed => "REMOVED",
73            Self::Changed => "CHANGED",
74            Self::Unchanged => "UNCHANGED",
75        }
76    }
77}
78
79/// Values are the same as the official emulator's messages are: floats by their bits (every
80/// NaN alike, `-0.0` apart from `0.0`), maps and lists in their order.
81impl PartialEq for Observed {
82    fn eq(&self, other: &Self) -> bool {
83        match (self, other) {
84            (Self::Null, Self::Null) | (Self::Undefined, Self::Undefined) => true,
85            (Self::Bool(a), Self::Bool(b)) => a == b,
86            (Self::Int(a), Self::Int(b)) => a == b,
87            (Self::Float(a), Self::Float(b)) => bits(*a) == bits(*b),
88            (Self::String(a), Self::String(b)) => a == b,
89            (Self::Bytes(a), Self::Bytes(b)) => a == b,
90            (Self::Timestamp(a), Self::Timestamp(b)) => a == b,
91            (Self::Duration(a, b), Self::Duration(c, d)) => a == c && b == d,
92            (Self::LatLng(a, b), Self::LatLng(c, d)) => {
93                bits(*a) == bits(*c) && bits(*b) == bits(*d)
94            }
95            (Self::Path(a), Self::Path(b)) => a == b,
96            (Self::Map(a), Self::Map(b)) => a == b,
97            (Self::List(a), Self::List(b)) | (Self::Set(a), Self::Set(b)) => a == b,
98            (Self::MapDiff(a), Self::MapDiff(b)) => a == b,
99            (Self::Constraint(a), Self::Constraint(b)) => a == b,
100            _ => false,
101        }
102    }
103}
104
105impl Eq for Observed {}
106
107fn bits(f: f64) -> u64 {
108    if f.is_nan() {
109        f64::NAN.to_bits()
110    } else {
111        f.to_bits()
112    }
113}
114
115/// Hashes `v` as what it shows ([`observe`]), without building that.
116pub(crate) fn hash_val<H: Hasher>(v: &Val, state: &mut H) {
117    match v {
118        Val::Null => 0u8.hash(state),
119        Val::Unknown => 15u8.hash(state),
120        Val::Bool(b) => (1u8, b).hash(state),
121        Val::Int(i) => (2u8, i).hash(state),
122        Val::Float(f) => (3u8, bits(*f)).hash(state),
123        Val::Str(s) => (4u8, &**s).hash(state),
124        Val::Bytes(b) => (5u8, &**b).hash(state),
125        Val::Timestamp(seconds, nanos) => (6u8, seconds, nanos).hash(state),
126        Val::Duration(nanos) => (7u8, nanos).hash(state),
127        Val::LatLng(lat, lng) => (8u8, bits(*lat), bits(*lng)).hash(state),
128        Val::Path(segments) => (9u8, &**segments).hash(state),
129        Val::Map(map) => {
130            (10u8, map.len()).hash(state);
131            for (k, v) in map.iter() {
132                k.hash(state);
133                hash_val(v, state);
134            }
135        }
136        Val::List(items) | Val::Set(items) => {
137            (
138                if matches!(v, Val::Set(_)) { 12u8 } else { 11u8 },
139                items.len(),
140            )
141                .hash(state);
142            for item in items.iter() {
143                hash_val(item, state);
144            }
145        }
146        Val::MapDiff(diff) => (13u8, &diff.statuses).hash(state),
147        Val::Constraint(simple) => {
148            (14u8, simple.len()).hash(state);
149            for s in simple.iter() {
150                s.comparator.hash(state);
151                hash_val(&s.value, state);
152            }
153        }
154    }
155}
156
157/// Whether `o` is what `v` shows ([`observe`]).
158pub(crate) fn shows(o: &Observed, v: &Val) -> bool {
159    match (o, v) {
160        (Observed::Null, Val::Null) | (Observed::Undefined, Val::Unknown) => true,
161        (Observed::Bool(a), Val::Bool(b)) => a == b,
162        (Observed::Int(a), Val::Int(b)) => a == b,
163        (Observed::Float(a), Val::Float(b)) => bits(*a) == bits(*b),
164        (Observed::String(a), Val::Str(b)) => a.as_str() == &**b,
165        (Observed::Bytes(a), Val::Bytes(b)) => a.as_slice() == &**b,
166        (Observed::Timestamp(t), Val::Timestamp(seconds, nanos)) => {
167            t.seconds == *seconds && i64::from(t.nanos) == i64::from(*nanos)
168        }
169        (Observed::Duration(..), Val::Duration(_)) | (Observed::Path(_), Val::Path(_)) => {
170            *o == observe(v)
171        }
172        (Observed::LatLng(a, b), Val::LatLng(c, d)) => bits(*a) == bits(*c) && bits(*b) == bits(*d),
173        (Observed::Map(a), Val::Map(b)) => {
174            a.len() == b.len()
175                && a.iter()
176                    .zip(b.iter())
177                    .all(|((k, x), (l, y))| k == l && shows(x, y))
178        }
179        (Observed::List(a), Val::List(b)) | (Observed::Set(a), Val::Set(b)) => {
180            a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| shows(x, y))
181        }
182        (Observed::MapDiff(a), Val::MapDiff(b)) => *a == b.statuses,
183        (Observed::Constraint(a), Val::Constraint(b)) => {
184            a.len() == b.len()
185                && a.iter()
186                    .zip(b.iter())
187                    .all(|((c, x), s)| *c == s.comparator && shows(x, &s.value))
188        }
189        _ => false,
190    }
191}
192
193/// What an expression of the coverage report evaluated to once.
194#[derive(Debug, Clone, PartialEq, Eq)]
195pub enum Outcome {
196    Value(Observed),
197    /// An evaluation error: where the official emulator places it, and its text. A comparison
198    /// a list's filter does not decide has neither.
199    Error {
200        position: Option<Position>,
201        message: String,
202    },
203    /// Not evaluated: the other side of `&&` or `||` decided, or `?:` took the other branch.
204    Skipped,
205}
206
207/// Which expression of the coverage report a value belongs to: an expression by its byte range,
208/// or the body of a function from one of its `let`s (by the keyword's offset).
209#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
210pub(crate) enum NodeKey {
211    Expr(usize, usize),
212    Body(usize),
213}
214
215impl NodeKey {
216    pub fn of(e: &Expr) -> Self {
217        Self::Expr(e.span.start, e.span.end)
218    }
219}
220
221/// An expression's value as the evaluator had it.
222#[derive(Clone)]
223pub(crate) enum Traced {
224    Value(Val),
225    Error(Error),
226    Skipped,
227}
228
229/// What an `allow` statement's condition came to.
230#[derive(Debug, Clone, Copy, PartialEq, Eq)]
231pub enum Verdict {
232    Allow,
233    False,
234    Error,
235}
236
237/// One evaluation of the statements of a method, as the Emulator UI's request monitor shows it
238/// ([`Trace::with_evaluations`]): what `request` and `resource` held, and what each statement
239/// evaluated came to, in order, by the line of its condition (or of the statement, without
240/// one). A request evaluates the statements of one method, of both for an upsert (`create`'s
241/// first), and of each way a list's filter may be satisfied; none when no statement applies.
242#[derive(Debug, Clone)]
243pub struct Evaluation {
244    pub method: Method,
245    pub request: Observed,
246    pub resource: Observed,
247    pub statements: Vec<(u32, Verdict)>,
248}
249
250/// What one decision reports besides the decision: the value of each `debug()` call, in order;
251/// when values are traced ([`Trace::with_values`]), what each expression of the coverage report
252/// evaluated to, for [`crate::coverage::Coverage::record`]; and when evaluations are
253/// ([`Trace::with_evaluations`]), each [`Evaluation`].
254#[derive(Default)]
255pub struct Trace {
256    pub(crate) debug: Vec<Observed>,
257    pub(crate) values: Option<Vec<(NodeKey, Traced)>>,
258    pub(crate) evaluations: Option<Vec<Evaluation>>,
259}
260
261impl Trace {
262    /// A trace of `debug()` alone.
263    pub fn new() -> Self {
264        Self::default()
265    }
266
267    /// A trace of `debug()` and of every expression's value.
268    pub fn with_values() -> Self {
269        Self {
270            values: Some(Vec::new()),
271            ..Self::default()
272        }
273    }
274
275    /// This trace, also reporting each [`Evaluation`].
276    #[must_use]
277    pub fn with_evaluations(mut self) -> Self {
278        self.evaluations = Some(Vec::new());
279        self
280    }
281
282    /// The values `debug()` received, in order.
283    pub fn debug(&self) -> &[Observed] {
284        &self.debug
285    }
286
287    /// The evaluations, in order, if they are reported.
288    pub fn evaluations(&self) -> &[Evaluation] {
289        self.evaluations.as_deref().unwrap_or_default()
290    }
291
292    /// Starts an evaluation of `method`'s statements with `request` and `resource`; returns
293    /// where its verdicts go.
294    pub(crate) fn evaluating(
295        &mut self,
296        method: Method,
297        request: &Val,
298        resource: &Val,
299    ) -> Option<usize> {
300        let evaluations = self.evaluations.as_mut()?;
301        evaluations.push(Evaluation {
302            method,
303            request: observe(request),
304            resource: observe(resource),
305            statements: Vec::new(),
306        });
307        Some(evaluations.len() - 1)
308    }
309
310    /// Records what the statement whose condition is at `line` came to, in evaluation `at`.
311    pub(crate) fn verdict(&mut self, at: Option<usize>, line: u32, verdict: Verdict) {
312        if let (Some(at), Some(evaluations)) = (at, &mut self.evaluations) {
313            evaluations[at].statements.push((line, verdict));
314        }
315    }
316
317    pub(crate) fn tracing(&self) -> bool {
318        self.values.is_some()
319    }
320
321    pub(crate) fn record(&mut self, key: NodeKey, result: &Result<Val, Error>) {
322        if let Some(values) = &mut self.values {
323            values.push((
324                key,
325                match result {
326                    Ok(v) => Traced::Value(v.clone()),
327                    Err(e) => Traced::Error(e.clone()),
328                },
329            ));
330        }
331    }
332
333    pub(crate) fn skip(&mut self, key: NodeKey) {
334        if let Some(values) = &mut self.values {
335            values.push((key, Traced::Skipped));
336        }
337    }
338
339    /// Where the values traced next start.
340    pub(crate) fn mark(&self) -> usize {
341        self.values.as_ref().map_or(0, Vec::len)
342    }
343
344    /// Counts what each statement traced again for each statement evaluated after it, as the
345    /// official emulator's report counts them: as if it evaluated a growing `||` of the
346    /// statements, the first alone, then the first two, and so on (but it runs each `debug()`
347    /// once). `marks` holds where each statement's values start, in order.
348    pub(crate) fn repeat(&mut self, marks: &[usize]) {
349        let Some(values) = &mut self.values else {
350            return;
351        };
352        let end = values.len();
353        for (i, &start) in marks.iter().enumerate() {
354            let stop = marks.get(i + 1).copied().unwrap_or(end);
355            for _ in i + 1..marks.len() {
356                for j in start..stop {
357                    let again = values[j].clone();
358                    values.push(again);
359                }
360            }
361        }
362    }
363}
364
365/// A value as `debug()` and the coverage report show it.
366pub(crate) fn observe(v: &Val) -> Observed {
367    match v {
368        Val::Null => Observed::Null,
369        // What reading it would leave undecided only shows inside a map.
370        Val::Unknown => Observed::Undefined,
371        Val::Bool(b) => Observed::Bool(*b),
372        Val::Int(i) => Observed::Int(*i),
373        Val::Float(f) => Observed::Float(*f),
374        Val::Str(s) => Observed::String(s.to_string()),
375        Val::Bytes(b) => Observed::Bytes(b.to_vec()),
376        Val::List(items) => Observed::List(items.iter().map(observe).collect()),
377        Val::Set(items) => Observed::Set(items.iter().map(observe).collect()),
378        Val::Map(map) => Observed::Map(map.iter().map(|(k, v)| (k.clone(), observe(v))).collect()),
379        Val::MapDiff(diff) => Observed::MapDiff(diff.statuses.clone()),
380        Val::Timestamp(seconds, nanos) => Observed::Timestamp(Timestamp {
381            seconds: *seconds,
382            nanos: i32::try_from(*nanos).unwrap_or(0),
383        }),
384        Val::Duration(nanos) => Observed::Duration(
385            i64::try_from(nanos / NANOS).unwrap_or(0),
386            i32::try_from(nanos % NANOS).unwrap_or(0),
387        ),
388        Val::LatLng(lat, lng) => Observed::LatLng(*lat, *lng),
389        Val::Path(segments) => Observed::Path(
390            segments
391                .iter()
392                .map(|s| match s.as_str() {
393                    UNKNOWN_ID => Segment::Capture,
394                    UNKNOWN_RUN => Segment::GlobCapture,
395                    s => Segment::Simple(s.to_owned()),
396                })
397                .collect(),
398        ),
399        Val::Constraint(simple) => Observed::Constraint(
400            simple
401                .iter()
402                .map(|s| (s.comparator, observe(&s.value)))
403                .collect(),
404        ),
405    }
406}