Skip to main content

hidane_rules/
eval.rs

1//! Evaluating a ruleset for one request: what the evaluator takes and what it decides.
2//!
3//! [`authorize`] finds the `allow` statements that apply to the request and evaluates their
4//! conditions. What the official emulator does, as measured with
5//! `tools/oracle/rules_conditions.py` (`results/rules-conditions-official-v1.22.0.json`) and
6//! `tools/oracle/rules_eval.py` (`tests/fixtures/rules_eval.json`):
7//!
8//! - Types are checked when a condition runs. Each operator, function and method takes the
9//!   types its error lists (`Unsupported operation error. Received: list + list. Expected:
10//!   int + int, …`); a method a type does not have is a `Function not found error`.
11//! - Errors are absorbed the CEL way: `error || true` and `true || error` are true, `error &&
12//!   false` and `false && error` are false, anything else with an error is the error. An error
13//!   that reaches the top of a condition denies, like `false`.
14//! - `==` between values of different types is false; an int equals a float of the same value,
15//!   but not inside lists and maps. Sets and map diffs compare numbers by value at any depth.
16//! - Strings are Java strings: sizes, indexes and slices count UTF-16 code units, `lower()` and
17//!   `upper()` change ASCII letters only, and regular expressions are RE2.
18//! - The operands of an operator, a call or a list or map literal are all evaluated, the last
19//!   first (as `debug()` shows), and the first error in order wins (for `x in y`, `y`'s; for a
20//!   method, a null receiver or a method it does not have comes before its arguments' errors).
21//! - The arguments of a function (the last first), then its `let`s in order, are evaluated
22//!   before its result, but an error in one only matters if the function uses it.
23//! - A decision evaluates at most 1,000 expressions ([`MAX_EXPRESSIONS`], measured with
24//!   `tools/oracle/rules_expressions.py`). Each expression evaluated counts once, parentheses
25//!   not at all, and some count more: `&&` and `||` once more when they evaluate their right
26//!   side, a conditional once more once its condition is evaluated (whatever it came to) and two
27//!   more again when it is false, `is` and each `let` once more, a path once more for each
28//!   literal segment. What a short circuit or a conditional skips does not count. An expression
29//!   counts once what is in it is evaluated, a call before its function runs: `debug()` prints
30//!   only within the limit (`tools/oracle/rules_expressions_report.py`). The expression that goes
31//!   past the limit is an error ("Unable to evaluate the expression as the maximum of 1000
32//!   expressions to evaluate has been reached."), whatever it came to, and nothing is evaluated
33//!   after it, so nothing absorbs it and the coverage report holds no value of it or of what
34//!   holds it. The official emulator's second evaluation of a condition (below) counts from
35//!   zero, so evaluating once comes to the same decisions.
36//!
37//! Each condition is evaluated once, so a denial lists each statement once; the official
38//! emulator evaluates again after an error and lists everything twice
39//! (`docs/parity-exceptions.md`).
40//!
41//! [`authorize_traced`] also reports what the official emulator logs and counts for its
42//! coverage report (`tools/oracle/rules_coverage.py`, [`Trace`]): each `debug()` value, and
43//! what each expression evaluated to. Which statements it evaluates shows there: those of
44//! every `match` covering the path, each `match`'s until one allows.
45
46mod constraint;
47mod interp;
48mod library;
49mod list;
50mod time;
51mod trace;
52mod value;
53
54pub use constraint::Comparator;
55pub use trace::{Evaluation, KeyStatus, Observed, Outcome, Segment, Trace, Verdict};
56pub(crate) use trace::{NodeKey, Traced, hash_val, observe, shows};
57pub use value::java_double;
58
59use std::rc::Rc;
60
61use indexmap::IndexMap;
62
63use crate::{
64    Ruleset, ast,
65    paths::{Binding, Covering, Part},
66};
67use interp::{Frame, Interp, Level};
68pub(crate) use value::Val;
69
70/// A value as rules see it. Firestore values convert to these (`timestamp`, `bytes`, `latlng`,
71/// a reference as a `path`, maps and lists); the others only come from rules.
72#[derive(Debug, Clone, PartialEq)]
73pub enum Value {
74    Null,
75    Bool(bool),
76    Int(i64),
77    Float(f64),
78    String(String),
79    Bytes(Vec<u8>),
80    Timestamp(Timestamp),
81    /// Seconds and nanoseconds.
82    Duration(i64, i32),
83    /// Latitude and longitude, in degrees.
84    LatLng(f64, f64),
85    /// The segments of a path. A reference holds its document's path from `databases`, like
86    /// [`Request::path`] and [`Document::path`].
87    Path(Vec<String>),
88    List(Vec<Value>),
89    Map(Map),
90}
91
92/// A map's entries, in the order the document or the literal holds them. Equality ignores the
93/// order; where rules list keys in another order, the evaluator sorts.
94pub type Map = IndexMap<String, Value>;
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
97pub struct Timestamp {
98    pub seconds: i64,
99    pub nanos: i32,
100}
101
102/// The method a request is checked against. `read` covers `get` and `list`, `write` covers
103/// `create`, `update` and `delete`.
104#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub enum Method {
106    Get,
107    List,
108    Create,
109    Update,
110    Delete,
111}
112
113impl Method {
114    /// The name rules see in `request.method` and denials print.
115    pub fn name(self) -> &'static str {
116        match self {
117            Self::Get => "get",
118            Self::List => "list",
119            Self::Create => "create",
120            Self::Update => "update",
121            Self::Delete => "delete",
122        }
123    }
124
125    fn statement(self) -> ast::Method {
126        match self {
127            Self::Get => ast::Method::Get,
128            Self::List => ast::Method::List,
129            Self::Create => ast::Method::Create,
130            Self::Update => ast::Method::Update,
131            Self::Delete => ast::Method::Delete,
132        }
133    }
134}
135
136/// A document as `resource`, `request.resource` and `get()` give it.
137#[derive(Debug, Clone, PartialEq)]
138pub struct Document {
139    /// The path from `databases`: `databases`, the database ID, `documents`, then the
140    /// document's segments.
141    pub path: Vec<String>,
142    pub data: Map,
143}
144
145/// `request.auth` for a caller with a token.
146#[derive(Debug, Clone, PartialEq)]
147pub struct Auth {
148    /// The token's `sub`; without one, `request.auth.uid` is null.
149    pub uid: Option<String>,
150    /// Every claim of the token.
151    pub token: Map,
152}
153
154/// `request.query` for a list, and the filter that decides it.
155///
156/// `request.query` holds `limit`, `offset` and `orderBy` from here, and what the official
157/// emulator derives from the path (`tools/oracle/rules_request_fields.py`): `kind`, the
158/// collection ID (null for a query of every collection); `parent`, the document the collection
159/// is in (null at the root), named `ancestor` for a collection group; `allDescendants`; and
160/// `distinct` and `selectOnlyKeys`, always false, and `groupBy`, always empty, which Firestore
161/// queries cannot set.
162#[derive(Debug, Clone, PartialEq, Default)]
163pub struct Query {
164    pub limit: Option<i64>,
165    pub offset: Option<i64>,
166    /// The orderings the query gives (not those completing it), field path to `ASC` or
167    /// `DESC`, in the order `request.query.orderBy` lists them.
168    pub order_by: Vec<(String, String)>,
169    pub filter: Option<Filter>,
170    /// For a collection group, its collection ID: the list covers the collections with that ID
171    /// at any depth below [`Request::path`] (an empty ID: every collection below it).
172    pub group: Option<String>,
173    /// With an empty [`Query::group`]: only the collections directly below [`Request::path`]
174    /// (a query whose `from` has no collection ID and is not `allDescendants`).
175    pub children: bool,
176    /// `request.fields`: the fields the query selects, as the official emulator lists them
177    /// (`None`, null, without a projection).
178    pub fields: Option<Vec<String>>,
179}
180
181/// A query's filter.
182#[derive(Debug, Clone, PartialEq)]
183pub enum Filter {
184    And(Vec<Filter>),
185    Or(Vec<Filter>),
186    /// A field, by its segments (`a.b` is `["a", "b"]`), and the value it is compared with
187    /// (null for the unary operators; a list for `In`, `NotIn` and `ArrayContainsAny`).
188    Field {
189        field: Vec<String>,
190        op: FilterOp,
191        value: Value,
192    },
193    /// The document's name, and a reference ([`Value::Path`]) or a list of them.
194    Name {
195        op: FilterOp,
196        value: Value,
197    },
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq)]
201pub enum FilterOp {
202    Lt,
203    Le,
204    Gt,
205    Ge,
206    Eq,
207    Ne,
208    ArrayContains,
209    In,
210    ArrayContainsAny,
211    NotIn,
212    IsNan,
213    IsNull,
214    IsNotNan,
215    IsNotNull,
216}
217
218/// One request, as rules see it.
219#[derive(Debug, Clone, PartialEq)]
220pub struct Request {
221    pub method: Method,
222    /// A create or an update without an existence precondition (a set, a patch, a transform):
223    /// whether the document exists chose the method. A denial then also lists the statements of
224    /// the other of the two, which cannot allow the write (`tools/oracle/rules_writes.py`,
225    /// `rules_denials.py`).
226    pub upsert: bool,
227    /// The path checked, from `databases` (a document, or for a list its collection).
228    pub path: Vec<String>,
229    pub auth: Option<Auth>,
230    pub time: Timestamp,
231    /// The document as it would be after a create or an update, or for a verify the stored
232    /// one; `None` otherwise.
233    pub resource: Option<Document>,
234    pub query: Option<Query>,
235    /// `request.inTransaction`: true for every write, which the official emulator commits in
236    /// a transaction of its own, and for a read of documents in a transaction begun before it;
237    /// false for a read that begins one, and for every list, in a transaction or not
238    /// (`tools/oracle/rules_request_fields.py`).
239    pub in_transaction: bool,
240    /// For a write, `request.writeFields`: the paths of its transforms, then those of its
241    /// update mask (`None`, null, for a write with neither).
242    pub write_fields: Option<Vec<String>>,
243    /// For a write, `request.transforms`: the paths its field transforms change, in order
244    /// (`None`, null, without any).
245    pub transforms: Option<Vec<String>>,
246    /// A create whose document ID the server chooses (a `CreateDocument` without one). The
247    /// official emulator decides it without the ID: [`Request::path`] is the collection's,
248    /// the statements are those of the collection's documents, with the document's wildcard
249    /// unbound, and `request.resource` holds only `data`. `request.autoId` says so for a
250    /// create that is not an upsert; other writes do not have it.
251    pub auto_id: bool,
252    /// A verify of a commit (`Write.verify`), checked as a `get` of its document. `request` has
253    /// the keys of a write's, `request.resource` the stored document (null when there is none),
254    /// as in the official emulator's second pass, where its first has the keys of a read's
255    /// (`tools/oracle/rules_verify.py`, `docs/parity-exceptions.md`).
256    pub verify: bool,
257}
258
259/// The documents `get()` / `exists()` read (as they were before the request's writes) and
260/// `getAfter()` / `existsAfter()` read (as they would be after all of them).
261pub trait Documents {
262    fn get(&self, path: &[String]) -> Option<Document>;
263    fn get_after(&self, path: &[String]) -> Option<Document>;
264}
265
266/// The lookups a request may still make (`tools/oracle/rules_lookups.py`): a single read or
267/// write may look up 10 distinct documents, a query 20, and a batch 10 per write and 20 in all.
268/// A document looked up again costs nothing, across the writes of a batch too, and so does the
269/// document a request reads or writes and every document its batch writes
270/// (`tools/oracle/rules_eval.py`).
271///
272/// The verifies of a commit count as its writes (`tools/oracle/rules_verify.py`). The documents
273/// they verify cost nothing through `getAfter()` and `existsAfter()` only, and in a verify's
274/// condition so do the documents the batch writes.
275///
276/// A decision also evaluates at most [`MAX_EXPRESSIONS`] expressions
277/// (`tools/oracle/rules_expressions.py`): the statements of the method that applies, in order,
278/// count together; each write and verify of a batch, the other method of an upsert and each way
279/// of a list count from zero.
280#[derive(Debug, Clone)]
281pub struct Budget {
282    /// Distinct documents looked up so far, by the whole request or batch.
283    pub looked_up: Vec<Vec<String>>,
284    /// How many of them the current write or read looked up first.
285    pub this_part: usize,
286    pub part_limit: usize,
287    pub total_limit: usize,
288    /// The documents the batch writes, from `databases` (see [`Budget::add_written`]).
289    pub written: Vec<Vec<String>>,
290    /// The documents the batch verifies, from `databases` (see [`Budget::add_verified`]).
291    pub verified: Vec<Vec<String>>,
292    /// Whether a verify is being decided (see [`Budget::next_verify`]).
293    pub verifying: bool,
294    /// The expressions the decision has evaluated so far.
295    pub expressions: usize,
296}
297
298/// The most expressions a decision may evaluate.
299pub const MAX_EXPRESSIONS: usize = 1_000;
300
301impl Budget {
302    fn with(part_limit: usize, total_limit: usize) -> Self {
303        Self {
304            looked_up: Vec::new(),
305            this_part: 0,
306            part_limit,
307            total_limit,
308            written: Vec::new(),
309            verified: Vec::new(),
310            verifying: false,
311            expressions: 0,
312        }
313    }
314
315    /// The budget of another decision of the same request: its lookups so far, but none of
316    /// the expressions this one evaluated.
317    fn another(&self) -> Self {
318        Self {
319            expressions: 0,
320            ..self.clone()
321        }
322    }
323
324    /// Records a document the batch writes (or deletes): any write of the batch may look it up
325    /// for free. The document of the request being decided is free without this.
326    pub fn add_written(&mut self, path: Vec<String>) {
327        if !self.written.contains(&path) {
328            self.written.push(path);
329        }
330    }
331
332    /// Records a document the batch verifies: any write or verify of the batch may look it up
333    /// with `getAfter()` or `existsAfter()` for free.
334    pub fn add_verified(&mut self, path: Vec<String>) {
335        if !self.verified.contains(&path) {
336            self.verified.push(path);
337        }
338    }
339
340    /// Whether `path` costs nothing to look up, `after` the writes or before them, besides the
341    /// document of the request being decided and those looked up already.
342    pub fn free(&self, path: &[String], after: bool) -> bool {
343        let has = |list: &[Vec<String>]| list.iter().any(|p| p.as_slice() == path);
344        if after {
345            has(&self.written) || has(&self.verified)
346        } else {
347            !self.verifying && has(&self.written)
348        }
349    }
350
351    /// A single read or write.
352    pub fn single() -> Self {
353        Self::with(10, 10)
354    }
355
356    /// A query.
357    pub fn query() -> Self {
358        Self::with(20, 20)
359    }
360
361    /// A batch of writes; call [`Budget::next_write`] before each one.
362    pub fn batch() -> Self {
363        Self::with(10, 20)
364    }
365
366    pub fn next_write(&mut self) {
367        self.this_part = 0;
368        self.verifying = false;
369        self.expressions = 0;
370    }
371
372    /// Like [`Budget::next_write`], before a verify of the batch.
373    pub fn next_verify(&mut self) {
374        self.this_part = 0;
375        self.verifying = true;
376        self.expressions = 0;
377    }
378}
379
380/// What the rules decided.
381#[derive(Debug, Clone, PartialEq, Eq)]
382pub enum Decision {
383    Allow,
384    /// `PERMISSION_DENIED`, with the official emulator's message.
385    Deny(String),
386}
387
388/// Decides one request against `ruleset` (the `cloud.firestore` service). `resource` is the
389/// stored document (`None` if it does not exist, or for a list). The rules' version decides
390/// how recursive wildcards match.
391///
392/// The `allow` statements of every `match` that covers the path and names the method apply, in
393/// source order; one whose condition is true allows. A denial lists each one's result:
394/// `false for 'get' @ L5` or the error instead of `false`, with the line of the condition, or
395/// says `No matching allow statements` when none applies. For an upsert the statements of the
396/// other method are listed too, `create`'s first: each is `false`, or its condition's error.
397/// Their lookups do not count against `budget`.
398///
399/// A list is decided from its query's filter, not from the documents (`list`).
400pub fn authorize(
401    ruleset: &Ruleset,
402    request: &Request,
403    resource: Option<&Document>,
404    documents: &dyn Documents,
405    budget: &mut Budget,
406) -> Decision {
407    authorize_traced(
408        ruleset,
409        request,
410        resource,
411        documents,
412        budget,
413        &mut Trace::new(),
414    )
415}
416
417/// [`authorize`], reporting into `trace` what the conditions evaluated.
418///
419/// An upsert evaluates the statements of both methods, whichever applies, as the official
420/// emulator does: `create`'s, then `update`'s, as [`authorize`] evaluates those of a method
421/// (`tools/oracle/rules_coverage.py`). Only those of the method that applies decide.
422pub fn authorize_traced(
423    ruleset: &Ruleset,
424    request: &Request,
425    resource: Option<&Document>,
426    documents: &dyn Documents,
427    budget: &mut Budget,
428    trace: &mut Trace,
429) -> Decision {
430    let Some(service) = ruleset.service("cloud.firestore") else {
431        return unmatched(request, resource.map_or(Val::Null, value::resource), trace);
432    };
433    if request.method == Method::List {
434        return list::authorize(ruleset, service, request, documents, budget, trace);
435    }
436    let mut parts: Vec<Part<'_>> = request.path.iter().map(|s| Part::Segment(s)).collect();
437    if request.auto_id {
438        parts.push(Part::UnknownId);
439    }
440    let coverings = crate::paths::covering_parts(ruleset.version, service, &parts);
441    let other = match request.method {
442        Method::Create if request.upsert => Some(Method::Update),
443        Method::Update if request.upsert => Some(Method::Create),
444        _ => None,
445    };
446    let applying = statements(&coverings, request.method);
447    let others = other.map(|other| (other, statements(&coverings, other)));
448    if applying.is_empty() && others.as_ref().is_none_or(|(_, s)| s.is_empty()) {
449        return unmatched(request, resource.map_or(Val::Null, value::resource), trace);
450    }
451
452    // The statements of each method see `request.method` as theirs.
453    let context_for = |method| Context {
454        ruleset,
455        service,
456        request: request_value(request, method),
457        resource: resource.map_or(Val::Null, value::resource),
458        target: &request.path,
459        documents,
460    };
461    let context = context_for(request.method);
462    let Some((other, others)) = others else {
463        return match context.decide(&applying, request.method, budget, trace) {
464            Ok(()) => Decision::Allow,
465            Err(entries) => denied(&entries),
466        };
467    };
468    let other_context = context_for(other);
469    // `create`'s statements come first: before the decision when `update` applies. The other
470    // method's lookups count against a copy of the budget, and its expressions from zero.
471    if other == Method::Create {
472        other_context.each(&others, other, &mut budget.another(), trace);
473    }
474    let decided = context.decide(&applying, request.method, budget, trace);
475    let Err(entries) = decided else {
476        if other == Method::Update {
477            other_context.each(&others, other, &mut budget.another(), trace);
478        }
479        return Decision::Allow;
480    };
481    // A denial lists every statement of the other method; `create`'s, evaluated already, are
482    // evaluated again without being traced.
483    let listed = match other {
484        Method::Create => {
485            other_context.listed(&others, other, &mut budget.another(), &mut Trace::new())
486        }
487        _ => other_context.listed(&others, other, &mut budget.another(), trace),
488    };
489    let entries: Vec<String> = match other {
490        Method::Create => listed.into_iter().chain(entries).collect(),
491        _ => entries.into_iter().chain(listed).collect(),
492    };
493    denied(&entries)
494}
495
496fn no_match() -> Decision {
497    Decision::Deny("No matching allow statements".to_owned())
498}
499
500/// The denial of a request no statement applies to, reported as an evaluation of none.
501fn unmatched(request: &Request, resource: Val, trace: &mut Trace) -> Decision {
502    if trace.evaluations.is_some() {
503        trace.evaluating(
504            request.method,
505            &request_value(request, request.method),
506            &resource,
507        );
508    }
509    no_match()
510}
511
512/// A denial listing `entries`.
513fn denied(entries: &[String]) -> Decision {
514    Decision::Deny(format!("\n{}", entries.join(", ")))
515}
516
517/// The `allow` statements for `method` of every `match` in `coverings`, in source order.
518fn statements<'c, 'a>(
519    coverings: &'c [Covering<'a>],
520    method: Method,
521) -> Vec<(&'c ast::Allow, &'c Covering<'a>)> {
522    let method = method.statement();
523    let mut statements = Vec::new();
524    for covering in coverings {
525        for allow in &covering.target().body.allows {
526            if allow
527                .methods
528                .iter()
529                .any(|m| m.method.is_some_and(|m| m.covers(method)))
530            {
531                statements.push((allow, covering));
532            }
533        }
534    }
535    statements.sort_by_key(|(allow, _)| allow.span.start);
536    statements
537}
538
539/// What a statement's condition came to.
540fn verdict(result: &Result<bool, String>) -> Verdict {
541    match result {
542        Ok(true) => Verdict::Allow,
543        Ok(false) => Verdict::False,
544        Err(_) => Verdict::Error,
545    }
546}
547
548fn entry(text: &str, method: Method, line: u32) -> String {
549    format!("{text} for '{}' @ L{line}", method.name())
550}
551
552/// What every condition of one request is evaluated with.
553struct Context<'a> {
554    ruleset: &'a Ruleset,
555    service: &'a ast::Service,
556    request: Val,
557    resource: Val,
558    /// The path whose document is free to look up, in the database `get()` may read.
559    target: &'a [String],
560    documents: &'a dyn Documents,
561}
562
563impl Context<'_> {
564    /// Evaluates `statements` in order; if none allows, each one's entry. As the official
565    /// emulator does, the statements of a `match` stop at the first that allows, but those of
566    /// every other `match` are evaluated too (`tools/oracle/rules_coverage.py`).
567    fn decide(
568        &self,
569        statements: &[(&ast::Allow, &Covering<'_>)],
570        method: Method,
571        budget: &mut Budget,
572        trace: &mut Trace,
573    ) -> Result<(), Vec<String>> {
574        let (entries, allowed) = self.each(statements, method, budget, trace);
575        if allowed { Ok(()) } else { Err(entries) }
576    }
577
578    /// The statements of an upsert's method that does not apply, for a denial: each one's
579    /// entry, `false` or its condition's error.
580    fn listed(
581        &self,
582        statements: &[(&ast::Allow, &Covering<'_>)],
583        method: Method,
584        budget: &mut Budget,
585        trace: &mut Trace,
586    ) -> Vec<String> {
587        let mut entries = Vec::with_capacity(statements.len());
588        let mut marks = Vec::with_capacity(statements.len());
589        let at = trace.evaluating(method, &self.request, &self.resource);
590        for (allow, covering) in statements {
591            marks.push(trace.mark());
592            let (result, line) = match &allow.condition {
593                Some(condition) => (
594                    self.evaluate(covering, condition, budget, trace),
595                    condition.span.line,
596                ),
597                None => (Ok(true), allow.span.line),
598            };
599            trace.verdict(at, line, verdict(&result));
600            let text = result.err();
601            entries.push(entry(text.as_deref().unwrap_or("false"), method, line));
602        }
603        trace.repeat(&marks);
604        entries
605    }
606
607    /// Evaluates `statements` but those after one that allowed in the same `match`. Returns the
608    /// entries of those that did not allow, and whether one did.
609    fn each(
610        &self,
611        statements: &[(&ast::Allow, &Covering<'_>)],
612        method: Method,
613        budget: &mut Budget,
614        trace: &mut Trace,
615    ) -> (Vec<String>, bool) {
616        let mut entries = Vec::with_capacity(statements.len());
617        let mut marks = Vec::with_capacity(statements.len());
618        let mut allowed: Vec<*const ast::Match> = Vec::new();
619        let at = trace.evaluating(method, &self.request, &self.resource);
620        for (allow, covering) in statements {
621            let target = std::ptr::from_ref(covering.target());
622            if allowed.contains(&target) {
623                continue;
624            }
625            marks.push(trace.mark());
626            let Some(condition) = &allow.condition else {
627                trace.verdict(at, allow.span.line, Verdict::Allow);
628                allowed.push(target);
629                continue;
630            };
631            let result = self.evaluate(covering, condition, budget, trace);
632            trace.verdict(at, condition.span.line, verdict(&result));
633            let text = match result {
634                Ok(true) => {
635                    allowed.push(target);
636                    continue;
637                }
638                Ok(false) => "false".to_owned(),
639                Err(e) => e,
640            };
641            entries.push(entry(&text, method, condition.span.line));
642        }
643        trace.repeat(&marks);
644        (entries, !allowed.is_empty())
645    }
646
647    /// The condition of a statement at the end of `covering`: true, false or the error's text.
648    fn evaluate(
649        &self,
650        covering: &Covering<'_>,
651        condition: &ast::Expr,
652        budget: &mut Budget,
653        trace: &mut Trace,
654    ) -> Result<bool, String> {
655        let mut interp = Interp::new(
656            levels(self.ruleset, self.service, covering),
657            self.request.clone(),
658            self.resource.clone(),
659            self.target,
660            self.documents,
661            budget,
662            trace,
663            &self.ruleset.parens,
664        );
665        let frame = Frame::condition(covering.chain.len() + 1);
666        interp
667            .eval(condition, frame.scope())
668            .and_then(interp::boolean)
669            .map_err(|e| {
670                if e.message.is_empty() {
671                    // Undecided: the official emulator names the condition's start.
672                    format!(
673                        "evaluation error at L{}:{}",
674                        condition.span.line, condition.span.column
675                    )
676                } else {
677                    e.message
678                }
679            })
680    }
681}
682
683/// The names a condition at the end of `covering` sees: the functions of the file, of the
684/// service and of each `match`, and the variables each `match` binds.
685fn levels<'a>(
686    ruleset: &'a Ruleset,
687    service: &'a ast::Service,
688    covering: &'a Covering<'a>,
689) -> Vec<Level<'a>> {
690    let mut levels = vec![
691        Level {
692            functions: &ruleset.functions,
693            variables: Vec::new(),
694        },
695        Level {
696            functions: &service.body.functions,
697            variables: Vec::new(),
698        },
699    ];
700    let mut bindings = covering.bindings.iter();
701    for m in &covering.chain {
702        let count = m
703            .path
704            .iter()
705            .filter(|s| s.kind.variable().is_some())
706            .count();
707        let variables = bindings
708            .by_ref()
709            .take(count)
710            // A wildcard that took a list's unknown document ID is not bound: using it is a
711            // `Null value error`, as for any unknown name.
712            .filter_map(|(name, binding)| {
713                let value = match binding {
714                    Binding::Segment(s) => Val::str(s),
715                    Binding::Path(p) => Val::path(p.clone()),
716                    Binding::Unbound => return None,
717                };
718                Some((name.as_str(), value))
719            })
720            .collect();
721        levels.push(Level {
722            functions: &m.body.functions,
723            variables,
724        });
725    }
726    levels
727}
728
729/// `request` for the statements of `method`, with the keys the official emulator gives it, in
730/// the order its coverage report lists them (`tools/oracle/rules_request_fields.py`):
731///
732/// - for a `get`: `time`, `auth`, `inTransaction`, `path`, `fields` (null), `method`;
733/// - for a `list`: `time`, `auth`, `inTransaction`, `fields`, `query`, `path`, `method`;
734/// - for a write: `time`, `auth`, `inTransaction`, `path`, `transforms`, `writeFields`,
735///   `readFields` (null), `autoId` (for a create that is not an upsert), `resource` (null for a
736///   delete; only `data` when the ID is the server's), `method`; for a verify, a `get`, the
737///   same but `autoId` (`tools/oracle/rules_verify.py`).
738///
739/// `auth` is null without a token, else `uid` and `token`.
740fn request_value(request: &Request, method: Method) -> Val {
741    let strings = |list: &Option<Vec<String>>| {
742        list.as_ref().map_or(Val::Null, |items| {
743            Val::list(items.iter().map(|s| Val::str(s)).collect())
744        })
745    };
746    let mut map = IndexMap::new();
747    map.insert(
748        "time".to_owned(),
749        Val::from(&Value::Timestamp(request.time)),
750    );
751    map.insert("auth".to_owned(), auth_value(request));
752    map.insert(
753        "inTransaction".to_owned(),
754        Val::Bool(request.in_transaction),
755    );
756    let path = Val::path(request.path.clone());
757    match method {
758        Method::Get if !request.verify => {
759            map.insert("path".to_owned(), path);
760            map.insert("fields".to_owned(), Val::Null);
761        }
762        Method::List => {
763            let query = request.query.clone().unwrap_or_default();
764            map.insert("fields".to_owned(), strings(&query.fields));
765            map.insert("query".to_owned(), query_value(request, &query));
766            map.insert("path".to_owned(), path);
767        }
768        Method::Get | Method::Create | Method::Update | Method::Delete => {
769            map.insert("path".to_owned(), path);
770            map.insert("transforms".to_owned(), strings(&request.transforms));
771            map.insert("writeFields".to_owned(), strings(&request.write_fields));
772            map.insert("readFields".to_owned(), Val::Null);
773            if method == Method::Create && !request.upsert {
774                map.insert("autoId".to_owned(), Val::Bool(request.auto_id));
775            }
776            let resource = match &request.resource {
777                None => Val::Null,
778                // The document has no name yet.
779                Some(doc) if request.auto_id => match value::resource(doc) {
780                    Val::Map(map) => {
781                        let data = map.get("data").cloned().unwrap_or(Val::Null);
782                        Val::Map(Rc::new(IndexMap::from([("data".to_owned(), data)])))
783                    }
784                    other => other,
785                },
786                Some(doc) => value::resource(doc),
787            };
788            map.insert("resource".to_owned(), resource);
789        }
790    }
791    map.insert("method".to_owned(), Val::str(method.name()));
792    Val::Map(Rc::new(map))
793}
794
795/// `request.query`, its keys in the order of the official emulator's coverage report.
796fn query_value(request: &Request, query: &Query) -> Val {
797    let all_descendants = query.group.is_some() && !query.children;
798    // The collection ID, and the document holding the collection (or the collections).
799    let (kind, parent) = match &query.group {
800        None => match request.path.split_last() {
801            Some((id, parent)) => (Val::str(id), parent),
802            None => (Val::Null, &[][..]),
803        },
804        Some(id) if id.is_empty() => (Val::Null, request.path.as_slice()),
805        Some(id) => (Val::str(id), request.path.as_slice()),
806    };
807    // `databases/{id}/documents`, the root, is no document.
808    let parent = if parent.len() > 3 {
809        Val::path(parent.to_vec())
810    } else {
811        Val::Null
812    };
813    let order_by = query
814        .order_by
815        .iter()
816        .map(|(field, direction)| (field.clone(), Val::str(direction)))
817        .collect();
818    let mut q = IndexMap::new();
819    q.insert("distinct".to_owned(), Val::Bool(false));
820    q.insert("selectOnlyKeys".to_owned(), Val::Bool(false));
821    q.insert("groupBy".to_owned(), Val::Map(Rc::default()));
822    q.insert("kind".to_owned(), kind);
823    let parent_key = if all_descendants {
824        "ancestor"
825    } else {
826        "parent"
827    };
828    q.insert(parent_key.to_owned(), parent);
829    q.insert("orderBy".to_owned(), Val::Map(Rc::new(order_by)));
830    q.insert("limit".to_owned(), query.limit.map_or(Val::Null, Val::Int));
831    q.insert("offset".to_owned(), Val::Int(query.offset.unwrap_or(0)));
832    q.insert("allDescendants".to_owned(), Val::Bool(all_descendants));
833    Val::Map(Rc::new(q))
834}
835
836/// `request.auth`: null without a token, else `uid` and `token`.
837fn auth_value(request: &Request) -> Val {
838    match &request.auth {
839        None => Val::Null,
840        Some(auth) => {
841            let mut a = IndexMap::new();
842            a.insert(
843                "uid".to_owned(),
844                auth.uid.as_deref().map_or(Val::Null, Val::str),
845            );
846            a.insert(
847                "token".to_owned(),
848                Val::from(&Value::Map(auth.token.clone())),
849            );
850            Val::Map(Rc::new(a))
851        }
852    }
853}