Skip to main content

hidane_rules/eval/
value.rs

1//! The values rules compute with, their type names, equality and the official error texts.
2
3use std::rc::Rc;
4
5use indexmap::IndexMap;
6
7use super::{Document, Timestamp, Value};
8
9/// A value while a condition runs. Containers are shared, so reading `resource.data` does not
10/// copy the document.
11#[derive(Debug, Clone)]
12pub(crate) enum Val {
13    Null,
14    Bool(bool),
15    Int(i64),
16    Float(f64),
17    Str(Rc<str>),
18    Bytes(Rc<[u8]>),
19    List(Rc<[Val]>),
20    Map(Rc<IndexMap<String, Val>>),
21    /// Distinct items (by [`loose_eq`]), in the order they were first added.
22    Set(Rc<[Val]>),
23    MapDiff(Rc<MapDiff>),
24    /// Seconds since the epoch and nanoseconds in `0..1e9`.
25    Timestamp(i64, u32),
26    /// Nanoseconds.
27    Duration(i128),
28    LatLng(f64, f64),
29    Path(Rc<[String]>),
30    /// What a list knows of a field its query filters on (`super::constraint`).
31    Constraint(Rc<[super::constraint::Simple]>),
32    /// What the official emulator cannot evaluate, such as a collection group's `request.path`:
33    /// reading it from its map is undecided.
34    Unknown,
35}
36
37/// The keys `a.diff(b)` sorts out: in `a` only, in `b` only, in both with other values, in
38/// both with equal values; and every key with its status in the order the official emulator
39/// shows them, `b`'s keys first.
40#[derive(Debug)]
41pub(crate) struct MapDiff {
42    pub added: Vec<String>,
43    pub removed: Vec<String>,
44    pub changed: Vec<String>,
45    pub unchanged: Vec<String>,
46    pub statuses: Vec<(String, super::trace::KeyStatus)>,
47}
48
49/// An evaluation error, with the official emulator's text and, once known, the byte offset of
50/// the token the coverage report points it at (`super::interp`).
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub(crate) struct Error {
53    pub message: String,
54    pub at: Option<usize>,
55}
56
57pub(crate) type R = Result<Val, Error>;
58
59pub(crate) const NANOS: i128 = 1_000_000_000;
60pub(crate) const MIN_SECONDS: i64 = -62_135_596_800;
61pub(crate) const MAX_SECONDS: i64 = 253_402_300_799;
62const MAX_DURATION_SECONDS: i128 = 315_576_000_000;
63
64impl Error {
65    pub fn new(message: impl Into<String>) -> Self {
66        Self {
67            message: message.into(),
68            at: None,
69        }
70    }
71
72    /// The error, placed at the token starting at byte `at` unless it already has a place. An
73    /// error without a message has none.
74    pub fn at(mut self, at: usize) -> Self {
75        if self.at.is_none() && !self.message.is_empty() {
76            self.at = Some(at);
77        }
78        self
79    }
80
81    /// An error without a message: a comparison with a `constraint` that its filters do not
82    /// imply. A denial shows it as `evaluation error at L5:22` (the condition's start).
83    pub fn undecided() -> Self {
84        Self::new(String::new())
85    }
86
87    pub fn null() -> Self {
88        Self::new("Null value error.".to_owned())
89    }
90
91    /// Indexing a path that holds a list's unknown document ID (`paths::UNKNOWN_ID`).
92    pub fn unbound_template() -> Self {
93        Self::new("Variable  is not bound in path template.".to_owned())
94    }
95
96    pub fn property(name: &str) -> Self {
97        Self::new(format!("Property {name} is undefined on object."))
98    }
99
100    pub fn type_error(received: &str, expected: &str) -> Self {
101        Self::new(format!(
102            "Type error. Received: [{received}] Expected: [{expected}]."
103        ))
104    }
105
106    pub fn unsupported(received: &str, expected: &[String]) -> Self {
107        Self::new(format!(
108            "Unsupported operation error. Received: {received}. Expected: {}.",
109            expected.join(", ")
110        ))
111    }
112
113    pub fn not_found(name: &str) -> Self {
114        Self::new(format!("Function not found error: Name: [{name}]."))
115    }
116
117    pub fn arity(received: usize, expected: &[String]) -> Self {
118        Self::new(format!(
119            "Incorrect number of arguments. Received: {received}. Expected: {}.",
120            expected.join(", ")
121        ))
122    }
123
124    pub fn index(index: i64, size: usize) -> Self {
125        Self::new(format!(
126            "Index out of bound error. Index: [{index}] , size: [{size}]."
127        ))
128    }
129
130    pub fn range(from: i64, to: i64) -> Self {
131        Self::new(format!(
132            "Illegal range error. From index: [{from}] , To index: [{to}]."
133        ))
134    }
135
136    pub fn overflow() -> Self {
137        Self::new(
138            "Integer value is out of the [-9223372036854775808, 9223372036854775807] range."
139                .to_owned(),
140        )
141    }
142
143    pub fn divide_by_zero() -> Self {
144        Self::new("Divide by zero error.".to_owned())
145    }
146
147    pub fn conversion(argument: &str, from: &str, to: &str) -> Self {
148        Self::new(format!(
149            "Type conversion error. Argument: [{argument}], From type: [{from}], To type: [{to}]."
150        ))
151    }
152
153    pub fn regex(pattern: &str) -> Self {
154        Self::new(format!(
155            "Invalid regular expression pattern. Pattern: {pattern}."
156        ))
157    }
158
159    pub fn timestamp() -> Self {
160        Self::new(
161            "Timestamp is not valid. Seconds must be in range [-62,135,596,800, \
162             +253,402,300,799]. Nanos must be in range [0, +999,999,999]."
163                .to_owned(),
164        )
165    }
166
167    pub fn duration() -> Self {
168        Self::new(
169            "Duration is not valid. Seconds must be in range [-315,576,000,000, \
170             +315,576,000,000]. Nanos must be in range [-999,999,999, +999,999,999]. Nanos must \
171             have the same sign as seconds."
172                .to_owned(),
173        )
174    }
175
176    /// A lookup the rules may not make: past the budget, or of a path that is not a document
177    /// of this database. The path is printed as a protobuf text message.
178    pub fn service(function: &str, path: &[String]) -> Self {
179        let mut argument = "path_value {\n".to_owned();
180        for segment in path {
181            argument.push_str("  segments {\n    simple: \"");
182            argument.push_str(&text_format_escape(segment));
183            argument.push_str("\"\n  }\n");
184        }
185        argument.push_str("}\n");
186        Self::new(format!(
187            "Service call error. Function: [{function}], Argument: [{argument}]."
188        ))
189    }
190}
191
192/// A string as protobuf's text format quotes it: C escapes, and octal for other bytes outside
193/// printable ASCII.
194fn text_format_escape(s: &str) -> String {
195    let mut out = String::with_capacity(s.len());
196    for &b in s.as_bytes() {
197        match b {
198            b'\x07' => out.push_str("\\a"),
199            b'\x08' => out.push_str("\\b"),
200            b'\x0c' => out.push_str("\\f"),
201            b'\n' => out.push_str("\\n"),
202            b'\r' => out.push_str("\\r"),
203            b'\t' => out.push_str("\\t"),
204            b'\x0b' => out.push_str("\\v"),
205            b'\\' => out.push_str("\\\\"),
206            b'\'' => out.push_str("\\'"),
207            b'"' => out.push_str("\\\""),
208            0x20..0x7f => out.push(char::from(b)),
209            _ => out.push_str(&format!("\\{b:03o}")),
210        }
211    }
212    out
213}
214
215impl Val {
216    pub fn str(s: &str) -> Self {
217        Self::Str(Rc::from(s))
218    }
219
220    pub fn string(s: String) -> Self {
221        Self::Str(Rc::from(s))
222    }
223
224    pub fn list(items: Vec<Val>) -> Self {
225        Self::List(Rc::from(items))
226    }
227
228    pub fn path(segments: Vec<String>) -> Self {
229        Self::Path(Rc::from(segments))
230    }
231
232    /// The type's name in the official messages and for `is`.
233    pub fn type_name(&self) -> &'static str {
234        match self {
235            Self::Null => "null",
236            Self::Bool(_) => "bool",
237            Self::Int(_) => "int",
238            Self::Float(_) => "float",
239            Self::Str(_) => "string",
240            Self::Bytes(_) => "bytes",
241            Self::List(_) => "list",
242            Self::Map(_) => "map",
243            Self::Set(_) => "set",
244            Self::MapDiff(_) => "map_diff",
245            Self::Timestamp(..) => "timestamp",
246            Self::Duration(_) => "duration",
247            Self::LatLng(..) => "latlng",
248            Self::Path(_) => "path",
249            Self::Constraint(_) => "constraint",
250            Self::Unknown => "unknown",
251        }
252    }
253
254    /// `value is type`. A set is never `set` (as measured), and nothing is a `vector`.
255    pub fn is(&self, type_name: &str) -> bool {
256        match type_name {
257            "number" => matches!(self, Self::Int(_) | Self::Float(_)),
258            "set" | "vector" => false,
259            name => self.type_name() == name,
260        }
261    }
262
263    /// A timestamp, or the official error outside years 1 to 9999.
264    pub fn timestamp(seconds: i64, nanos: u32) -> R {
265        if (MIN_SECONDS..=MAX_SECONDS).contains(&seconds) {
266            Ok(Self::Timestamp(seconds, nanos))
267        } else {
268            Err(Error::timestamp())
269        }
270    }
271
272    /// A timestamp from nanoseconds since the epoch.
273    pub fn timestamp_nanos(nanos: i128) -> R {
274        let seconds = nanos.div_euclid(NANOS);
275        let seconds = i64::try_from(seconds).map_err(|_| Error::timestamp())?;
276        Self::timestamp(seconds, nanos.rem_euclid(NANOS) as u32)
277    }
278
279    /// A duration, or the official error beyond 10,000 years either way.
280    pub fn duration(nanos: i128) -> R {
281        if (nanos / NANOS).abs() <= MAX_DURATION_SECONDS {
282            Ok(Self::Duration(nanos))
283        } else {
284            Err(Error::duration())
285        }
286    }
287}
288
289/// `==` in a condition: an int equals a float of the same value, but inside lists and maps the
290/// types must match (`[1] == [1.0]` is false). Values of different types are not equal.
291pub(crate) fn eq(a: &Val, b: &Val) -> bool {
292    match (a, b) {
293        (Val::Int(x), Val::Float(y)) | (Val::Float(y), Val::Int(x)) => (*x as f64) == *y,
294        _ => strict_eq(a, b),
295    }
296}
297
298/// Equality without numeric conversion, as lists, maps, `in` and the list methods use it.
299pub(crate) fn strict_eq(a: &Val, b: &Val) -> bool {
300    same(a, b, strict_eq)
301}
302
303/// Equality where numbers compare by value at any depth, as sets and map diffs use it
304/// (`[1].toSet() == [1.0].toSet()`).
305pub(crate) fn loose_eq(a: &Val, b: &Val) -> bool {
306    match (a, b) {
307        (Val::Int(x), Val::Float(y)) | (Val::Float(y), Val::Int(x)) => (*x as f64) == *y,
308        _ => same(a, b, loose_eq),
309    }
310}
311
312fn same(a: &Val, b: &Val, inner: fn(&Val, &Val) -> bool) -> bool {
313    match (a, b) {
314        (Val::Null, Val::Null) => true,
315        (Val::Bool(x), Val::Bool(y)) => x == y,
316        (Val::Int(x), Val::Int(y)) => x == y,
317        (Val::Float(x), Val::Float(y)) => x == y,
318        (Val::Str(x), Val::Str(y)) => x == y,
319        (Val::Bytes(x), Val::Bytes(y)) => x == y,
320        (Val::List(x), Val::List(y)) => {
321            x.len() == y.len() && x.iter().zip(y.iter()).all(|(a, b)| inner(a, b))
322        }
323        (Val::Map(x), Val::Map(y)) => {
324            x.len() == y.len() && x.iter().all(|(k, v)| y.get(k).is_some_and(|w| inner(v, w)))
325        }
326        (Val::Set(x), Val::Set(y)) => {
327            x.len() == y.len() && x.iter().all(|a| y.iter().any(|b| loose_eq(a, b)))
328        }
329        (Val::Timestamp(s, n), Val::Timestamp(t, m)) => s == t && n == m,
330        (Val::Duration(x), Val::Duration(y)) => x == y,
331        (Val::LatLng(a, b), Val::LatLng(c, d)) => a == c && b == d,
332        (Val::Path(x), Val::Path(y)) => x == y,
333        // Two map diffs are never equal.
334        _ => false,
335    }
336}
337
338/// Whether `items` holds `v`, by `eq`.
339pub(crate) fn contains(items: &[Val], v: &Val, eq: fn(&Val, &Val) -> bool) -> bool {
340    items.iter().any(|x| eq(x, v))
341}
342
343/// The distinct items of `items`, the first of equal ones kept.
344pub(crate) fn to_set(items: impl IntoIterator<Item = Val>) -> Val {
345    let mut out: Vec<Val> = Vec::new();
346    for item in items {
347        if !contains(&out, &item, loose_eq) {
348            out.push(item);
349        }
350    }
351    Val::Set(Rc::from(out))
352}
353
354impl From<&Value> for Val {
355    fn from(v: &Value) -> Self {
356        match v {
357            Value::Null => Self::Null,
358            Value::Bool(b) => Self::Bool(*b),
359            Value::Int(i) => Self::Int(*i),
360            Value::Float(f) => Self::Float(*f),
361            Value::String(s) => Self::str(s),
362            Value::Bytes(b) => Self::Bytes(Rc::from(b.as_slice())),
363            Value::Timestamp(Timestamp { seconds, nanos }) => {
364                let total = i128::from(*seconds) * NANOS + i128::from(*nanos);
365                Self::Timestamp(
366                    total.div_euclid(NANOS) as i64,
367                    total.rem_euclid(NANOS) as u32,
368                )
369            }
370            Value::Duration(seconds, nanos) => {
371                Self::Duration(i128::from(*seconds) * NANOS + i128::from(*nanos))
372            }
373            Value::LatLng(lat, lng) => Self::LatLng(*lat, *lng),
374            Value::Path(segments) => Self::Path(Rc::from(segments.as_slice())),
375            Value::List(items) => Self::List(items.iter().map(Self::from).collect()),
376            Value::Map(map) => Self::Map(Rc::new(
377                map.iter()
378                    .map(|(k, v)| (k.clone(), Self::from(v)))
379                    .collect(),
380            )),
381        }
382    }
383}
384
385/// A document as `resource` and `get()` give it: `data`, `__name__` and `id`, in the order
386/// of the official emulator's coverage report (`tools/oracle/rules_request_fields.py`).
387pub(crate) fn resource(doc: &Document) -> Val {
388    let mut map = IndexMap::with_capacity(3);
389    map.insert(
390        "data".to_owned(),
391        Val::Map(Rc::new(
392            doc.data
393                .iter()
394                .map(|(k, v)| (k.clone(), Val::from(v)))
395                .collect(),
396        )),
397    );
398    map.insert("__name__".to_owned(), Val::path(doc.path.clone()));
399    map.insert(
400        "id".to_owned(),
401        Val::str(doc.path.last().map_or("", String::as_str)),
402    );
403    Val::Map(Rc::new(map))
404}
405
406/// A float as Java's `Double.toString` writes it: `1.0`, `0.001`, `1.0E7`, `1.0E-4`,
407/// `Infinity`, `NaN`, with the shortest digits that read back the same.
408pub fn java_double(f: f64) -> String {
409    if f.is_nan() {
410        return "NaN".to_owned();
411    }
412    if f.is_infinite() {
413        return if f > 0.0 { "Infinity" } else { "-Infinity" }.to_owned();
414    }
415    if f == 0.0 {
416        return if f.is_sign_negative() { "-0.0" } else { "0.0" }.to_owned();
417    }
418    let sign = if f < 0.0 { "-" } else { "" };
419    // `{:e}` gives the shortest digits: `1.2345e-7`.
420    let sci = format!("{:e}", f.abs());
421    let (mantissa, exponent) = sci.split_once('e').unwrap_or((&sci, "0"));
422    let exponent: i32 = exponent.parse().unwrap_or(0);
423    let digits: String = mantissa.chars().filter(char::is_ascii_digit).collect();
424    let abs = f.abs();
425    if (1e-3..1e7).contains(&abs) {
426        let point = exponent + 1;
427        let (int, frac) = if point <= 0 {
428            (
429                "0".to_owned(),
430                format!("{}{digits}", "0".repeat(point.unsigned_abs() as usize)),
431            )
432        } else {
433            let point = point as usize;
434            let padded = format!("{digits:0<point$}");
435            (padded[..point].to_owned(), padded[point..].to_owned())
436        };
437        let frac = if frac.is_empty() {
438            "0".to_owned()
439        } else {
440            frac
441        };
442        format!("{sign}{int}.{frac}")
443    } else {
444        let (first, rest) = digits.split_at(1);
445        let rest = if rest.is_empty() { "0" } else { rest };
446        format!("{sign}{first}.{rest}E{exponent}")
447    }
448}
449
450/// Java's `Double.parseDouble`: surrounding control characters and spaces are ignored, and
451/// `NaN`, `Infinity` and a trailing `d` or `f` are accepted.
452pub(crate) fn parse_java_double(s: &str) -> Option<f64> {
453    let t = s.trim_matches(|c: char| c <= ' ');
454    let (sign, body) = match t.as_bytes().first() {
455        Some(b'-') => (-1.0, &t[1..]),
456        Some(b'+') => (1.0, &t[1..]),
457        _ => (1.0, t),
458    };
459    match body {
460        "NaN" => return Some(f64::NAN),
461        "Infinity" => return Some(sign * f64::INFINITY),
462        _ => {}
463    }
464    let body = body.strip_suffix(['d', 'D', 'f', 'F']).unwrap_or(body);
465    let bytes = body.as_bytes();
466    let mut i = 0;
467    let digits = |i: &mut usize| {
468        let start = *i;
469        while *i < bytes.len() && bytes[*i].is_ascii_digit() {
470            *i += 1;
471        }
472        *i - start
473    };
474    let mut count = digits(&mut i);
475    if i < bytes.len() && bytes[i] == b'.' {
476        i += 1;
477        count += digits(&mut i);
478    }
479    if count == 0 {
480        return None;
481    }
482    if i < bytes.len() && matches!(bytes[i], b'e' | b'E') {
483        i += 1;
484        if i < bytes.len() && matches!(bytes[i], b'+' | b'-') {
485            i += 1;
486        }
487        if digits(&mut i) == 0 {
488            return None;
489        }
490    }
491    if i != bytes.len() {
492        return None;
493    }
494    body.parse::<f64>().ok().map(|f| sign * f)
495}
496
497/// Java's `Long.parseLong`: an optional sign and decimal digits.
498pub(crate) fn parse_java_long(s: &str) -> Option<i64> {
499    let digits = s.strip_prefix(['+', '-']).unwrap_or(s);
500    if digits.is_empty() || !digits.bytes().all(|b| b.is_ascii_digit()) {
501        return None;
502    }
503    s.strip_prefix('+').unwrap_or(s).parse().ok()
504}
505
506/// A string's length in UTF-16 code units, as Java counts it.
507pub(crate) fn utf16_len(s: &str) -> usize {
508    s.chars().map(char::len_utf16).sum()
509}
510
511/// Code units `from..to` of `s`; a surrogate pair cut in half becomes U+FFFD.
512pub(crate) fn utf16_slice(s: &str, from: usize, to: usize) -> String {
513    let units: Vec<u16> = s.encode_utf16().collect();
514    String::from_utf16_lossy(&units[from..to])
515}
516
517/// Java's `String.compareTo`: UTF-16 code unit order.
518pub(crate) fn utf16_cmp(a: &str, b: &str) -> std::cmp::Ordering {
519    a.encode_utf16().cmp(b.encode_utf16())
520}
521
522/// Whether a path holds a list's unknown parts (`crate::paths::UNKNOWN_ID`, `UNKNOWN_RUN`).
523pub(crate) fn has_unknown(segments: &[String]) -> bool {
524    segments
525        .iter()
526        .any(|s| s == crate::paths::UNKNOWN_ID || s == crate::paths::UNKNOWN_RUN)
527}
528
529/// A map's value as read: [`Val::Unknown`] is undecided.
530pub(crate) fn read(v: Option<&Val>) -> Option<R> {
531    v.map(|v| match v {
532        Val::Unknown => Err(Error::undecided()),
533        v => Ok(v.clone()),
534    })
535}
536
537#[cfg(test)]
538mod tests {
539    use super::*;
540
541    #[test]
542    fn java_doubles() {
543        for (f, s) in [
544            (1.0, "1.0"),
545            (1.5, "1.5"),
546            (-2.25, "-2.25"),
547            (1_000_000.0, "1000000.0"),
548            (10_000_000.0, "1.0E7"),
549            (100_000_000.0, "1.0E8"),
550            (123_456_789.0, "1.23456789E8"),
551            (0.001, "0.001"),
552            (0.0001, "1.0E-4"),
553            (0.000_123, "1.23E-4"),
554            (-0.0, "-0.0"),
555            (0.1 + 0.2, "0.30000000000000004"),
556            (1e300, "1.0E300"),
557            (f64::NAN, "NaN"),
558            (f64::NEG_INFINITY, "-Infinity"),
559            (9_223_372_036_854_775_807.0, "9.223372036854776E18"),
560        ] {
561            assert_eq!(java_double(f), s, "{f}");
562        }
563    }
564
565    #[test]
566    fn java_parsing() {
567        assert_eq!(parse_java_double(" 1.5 "), Some(1.5));
568        assert_eq!(parse_java_double("1e3"), Some(1000.0));
569        assert_eq!(parse_java_double("-.5"), Some(-0.5));
570        assert_eq!(parse_java_double("2."), Some(2.0));
571        assert_eq!(parse_java_double("1.5d"), Some(1.5));
572        assert_eq!(parse_java_double("-Infinity"), Some(f64::NEG_INFINITY));
573        assert!(parse_java_double("NaN").unwrap().is_nan());
574        for bad in ["inf", "abc", "", ".", "1e", "1.5x", "0x10", "nan"] {
575            assert_eq!(parse_java_double(bad), None, "{bad}");
576        }
577        assert_eq!(parse_java_long("+12"), Some(12));
578        assert_eq!(parse_java_long("-12"), Some(-12));
579        for bad in [" 12", "1.5", "", "+", "9223372036854775808", "0x10"] {
580            assert_eq!(parse_java_long(bad), None, "{bad}");
581        }
582    }
583
584    #[test]
585    fn equalities() {
586        let list = |items: Vec<Val>| Val::list(items);
587        assert!(eq(&Val::Int(1), &Val::Float(1.0)));
588        assert!(!eq(&list(vec![Val::Int(1)]), &list(vec![Val::Float(1.0)])));
589        assert!(loose_eq(
590            &list(vec![Val::Int(1)]),
591            &list(vec![Val::Float(1.0)])
592        ));
593        assert!(!eq(&Val::str("1"), &Val::Int(1)));
594        assert!(!eq(&Val::Null, &Val::Bool(false)));
595        assert!(!eq(&Val::Float(f64::NAN), &Val::Float(f64::NAN)));
596    }
597
598    #[test]
599    fn service_errors_print_the_path_as_text() {
600        let path: Vec<String> = ["databases", "a\"b", "é"].map(String::from).to_vec();
601        assert_eq!(
602            Error::service("get", &path).message,
603            "Service call error. Function: [get], Argument: [path_value {\n  segments {\n    \
604             simple: \"databases\"\n  }\n  segments {\n    simple: \"a\\\"b\"\n  }\n  segments {\n    \
605             simple: \"\\303\\251\"\n  }\n}\n]."
606        );
607    }
608}