Skip to main content

hidane_core/
field_path.rs

1//! Field paths (`a.b`, `` `x.y`.z ``) and the operations masks need on document fields.
2//!
3//! Syntax, as the official emulator enforces it: segments are separated by `.`; an unquoted
4//! segment matches `[a-zA-Z_][a-zA-Z_0-9]*`; any other segment is wrapped in backticks, where
5//! `` \` `` and `\\` are escapes.
6
7use std::fmt;
8
9use hidane_proto::google::firestore::v1::{MapValue, Value, value::ValueType};
10pub use hidane_proto::ordered::Fields;
11
12use crate::{hash_order::hash_order, order::is_vector, store::identical};
13
14#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct FieldPath(Vec<String>);
16
17impl FieldPath {
18    /// Parses a field path; the error is the official emulator's message.
19    pub fn parse(path: &str) -> Result<Self, String> {
20        let invalid = || {
21            format!(
22                "Invalid property path \"{path}\". Unquoted property paths must match regex \
23                 ([a-zA-Z_][a-zA-Z_0-9]*), and quoted property paths must match regex \
24                 (`(?:[^`\\\\]|(?:\\\\.))+`)"
25            )
26        };
27        let mut segments = Vec::new();
28        let mut chars = path.chars().peekable();
29        loop {
30            let mut segment = String::new();
31            if chars.peek() == Some(&'`') {
32                chars.next();
33                loop {
34                    match chars.next() {
35                        Some('`') => break,
36                        Some('\\') => segment.push(chars.next().ok_or_else(invalid)?),
37                        Some(c) => segment.push(c),
38                        None => return Err(invalid()),
39                    }
40                }
41                if segment.is_empty() {
42                    return Err(invalid());
43                }
44            } else {
45                while let Some(&c) = chars.peek() {
46                    if c == '.' {
47                        break;
48                    }
49                    let valid = if segment.is_empty() {
50                        c.is_ascii_alphabetic() || c == '_'
51                    } else {
52                        c.is_ascii_alphanumeric() || c == '_'
53                    };
54                    if !valid {
55                        return Err(invalid());
56                    }
57                    segment.push(c);
58                    chars.next();
59                }
60                if segment.is_empty() {
61                    return Err(invalid());
62                }
63            }
64            segments.push(segment);
65            match chars.next() {
66                None => return Ok(Self(segments)),
67                Some('.') => {}
68                Some(_) => return Err(invalid()),
69            }
70        }
71    }
72
73    pub fn from_segments<I, S>(segments: I) -> Self
74    where
75        I: IntoIterator<Item = S>,
76        S: Into<String>,
77    {
78        Self(segments.into_iter().map(Into::into).collect())
79    }
80
81    pub fn segments(&self) -> &[String] {
82        &self.0
83    }
84}
85
86/// The canonical form: simple segments as they are, others in backticks with `` ` `` and `\`
87/// escaped, joined by `.`.
88impl fmt::Display for FieldPath {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        for (i, segment) in self.0.iter().enumerate() {
91            if i > 0 {
92                f.write_str(".")?;
93            }
94            let mut chars = segment.chars();
95            let simple = chars
96                .next()
97                .is_some_and(|c| c.is_ascii_alphabetic() || c == '_')
98                && chars.all(|c| c.is_ascii_alphanumeric() || c == '_');
99            if simple {
100                f.write_str(segment)?;
101            } else {
102                f.write_str("`")?;
103                for c in segment.chars() {
104                    if c == '`' || c == '\\' {
105                        f.write_str("\\")?;
106                    }
107                    write!(f, "{c}")?;
108                }
109                f.write_str("`")?;
110            }
111        }
112        Ok(())
113    }
114}
115
116/// The value at `path`, if every segment but the last names a map. A vector is one value, not
117/// a map: no path goes into it, as on the official emulator (#262).
118pub fn get<'a>(fields: &'a Fields, path: &FieldPath) -> Option<&'a Value> {
119    let (last, parents) = path.0.split_last()?;
120    let mut current = fields;
121    for segment in parents {
122        current = map_fields(current.get(segment)?)?;
123    }
124    current.get(last)
125}
126
127/// Sets `path` to `value`, creating (or replacing non-map values, vectors among them, with)
128/// intermediate maps.
129pub fn set(fields: &mut Fields, path: &FieldPath, value: Value) {
130    let Some((last, parents)) = path.0.split_last() else {
131        return;
132    };
133    let mut current = fields;
134    for segment in parents {
135        let entry = current.entry(segment.clone()).or_insert_with(empty_map);
136        if map_fields(entry).is_none() {
137            *entry = empty_map();
138        }
139        let Some(ValueType::MapValue(map)) = &mut entry.value_type else {
140            unreachable!("just made it a map");
141        };
142        current = &mut map.fields;
143    }
144    current.insert(last.clone(), value);
145}
146
147/// Removes `path` if it exists. Intermediate maps are kept even if they become empty.
148pub fn delete(fields: &mut Fields, path: &FieldPath) {
149    let Some((last, parents)) = path.0.split_last() else {
150        return;
151    };
152    let mut current = fields;
153    for segment in parents {
154        match current.get_mut(segment).and_then(|v| v.value_type.as_mut()) {
155            Some(ValueType::MapValue(map)) if !is_vector(map) => current = &mut map.fields,
156            _ => return,
157        }
158    }
159    current.remove(last);
160}
161
162/// Masked paths as a tree of segments. Each level lists its names in the order they first
163/// appear in the mask.
164#[derive(Default)]
165struct MaskTree {
166    /// The mask names this exact path: its whole value is taken.
167    whole: bool,
168    children: Vec<(String, MaskTree)>,
169}
170
171impl MaskTree {
172    fn new(paths: &[FieldPath]) -> Self {
173        let mut root = Self::default();
174        for path in paths {
175            let mut node = &mut root;
176            for segment in path.segments() {
177                let at = match node.children.iter().position(|(name, _)| name == segment) {
178                    Some(at) => at,
179                    None => {
180                        node.children.push((segment.clone(), Self::default()));
181                        node.children.len() - 1
182                    }
183                };
184                node = &mut node.children[at].1;
185            }
186            node.whole = true;
187        }
188        root
189    }
190
191    /// The names of this level in the order the official emulator visits them: the order of
192    /// a Java `HashMap` that received them in mask order.
193    fn ordered(&self) -> impl Iterator<Item = &(String, MaskTree)> {
194        let names: Vec<&str> = self
195            .children
196            .iter()
197            .map(|(name, _)| name.as_str())
198            .collect();
199        hash_order(&names).into_iter().map(|i| &self.children[i])
200    }
201
202    /// The paths this level takes whole, below `prefix`, in the order rules list them: that
203    /// of a Java `HashMap` that received the names taken whole first, then the others, each
204    /// in mask order.
205    fn flatten(&self, prefix: &mut Vec<String>, out: &mut Vec<FieldPath>) {
206        let (whole, below): (Vec<_>, Vec<_>) =
207            self.children.iter().partition(|(_, node)| node.whole);
208        let children: Vec<&(String, MaskTree)> = whole.into_iter().chain(below).collect();
209        let names: Vec<&str> = children.iter().map(|(name, _)| name.as_str()).collect();
210        for (name, below) in hash_order(&names).into_iter().map(|i| children[i]) {
211            prefix.push(name.clone());
212            if below.whole {
213                out.push(FieldPath(prefix.clone()));
214            } else {
215                below.flatten(prefix, out);
216            }
217            prefix.pop();
218        }
219    }
220}
221
222/// The paths of a mask as the official emulator lists them to Security Rules
223/// (`request.writeFields`, `request.fields`): each once, without those below another, and at
224/// each level in the order of a Java `HashMap` that received the names the mask takes whole,
225/// then the others (`tools/oracle/rules_request_fields.py`).
226pub fn mask_paths(mask: &[FieldPath]) -> Vec<FieldPath> {
227    let mut out = Vec::new();
228    MaskTree::new(mask).flatten(&mut Vec::new(), &mut out);
229    out
230}
231
232/// `entries` in the order of a Java `HashMap` that received them in turn.
233fn in_hash_order<T>(mut entries: Vec<(String, T)>) -> impl Iterator<Item = (String, T)> {
234    let order = hash_order(&entries.iter().map(|(name, _)| name).collect::<Vec<_>>());
235    let mut taken: Vec<Option<(String, T)>> = entries.drain(..).map(Some).collect();
236    order.into_iter().filter_map(move |i| taken[i].take())
237}
238
239/// The fields of a map a path can go into: not those of a vector, which is one value.
240fn map_fields(value: &Value) -> Option<&Fields> {
241    match &value.value_type {
242        Some(ValueType::MapValue(map)) if !is_vector(map) => Some(&map.fields),
243        _ => None,
244    }
245}
246
247fn map_of(fields: Fields) -> Value {
248    Value {
249        value_type: Some(ValueType::MapValue(MapValue { fields })),
250    }
251}
252
253/// Keeps only the fields named by `paths` (a read mask). As on the official emulator, each map
254/// lists what it returns in the order of a Java `HashMap`: the masked names are visited in the
255/// order of one, and the values found are put into another (#108).
256pub fn project(fields: &Fields, paths: &[FieldPath]) -> Fields {
257    project_level(fields, &MaskTree::new(paths), false)
258}
259
260/// [`project`] as an enterprise database projects a query's documents: every map on the way
261/// to a selected path is there, empty where the document has nothing below it, whatever the
262/// document holds there (nothing, a value that is not a map, a vector, whose inside no path
263/// reaches).
264pub fn project_keeping_maps(fields: &Fields, paths: &[FieldPath]) -> Fields {
265    project_level(fields, &MaskTree::new(paths), true)
266}
267
268fn project_level(fields: &Fields, mask: &MaskTree, keep_maps: bool) -> Fields {
269    let mut found = Vec::new();
270    for (name, below) in mask.ordered() {
271        let value = fields.get(name);
272        if below.whole {
273            if let Some(value) = value {
274                found.push((name.clone(), value.clone()));
275            }
276        } else if let Some(map) = value.and_then(map_fields) {
277            let inner = project_level(map, below, keep_maps);
278            if keep_maps || !inner.is_empty() {
279                found.push((name.clone(), map_of(inner)));
280            }
281        } else if keep_maps {
282            let inner = project_level(&Fields::default(), below, keep_maps);
283            found.push((name.clone(), map_of(inner)));
284        }
285    }
286    in_hash_order(found).collect()
287}
288
289/// Applies an update mask: every masked path takes its value from `input`, or is deleted when
290/// `input` has no value there. Fields outside the mask are untouched, including fields of
291/// `input` that the mask does not name.
292///
293/// Where fields land follows the official emulator (#108): at each level, the masked names are
294/// visited in the order of a Java `HashMap`, those whose value changes are put into another
295/// one, and in that map's order each is removed and, unless deleted, added again at the end.
296/// The names the mask takes whole go into that map before those it masks a path below, which
297/// decides the order of two names in one bucket (`fo-bucket` in `tools/oracle/field_order.py`).
298/// A map changed inside moves the same way; fields that keep their value stay where they are.
299pub fn apply_mask(existing: &mut Fields, input: &Fields, mask: &[FieldPath]) {
300    merge_level(existing, Some(input), &MaskTree::new(mask));
301}
302
303fn merge_level(existing: &mut Fields, input: Option<&Fields>, mask: &MaskTree) {
304    let mut changes = Vec::new();
305    let (whole, nested): (Vec<_>, Vec<_>) = mask.ordered().partition(|(_, below)| below.whole);
306    for (name, below) in whole.into_iter().chain(nested) {
307        let old = existing.get(name);
308        let given = input.and_then(|fields| fields.get(name));
309        let new = if below.whole {
310            given.cloned()
311        } else {
312            let mut inner = old.and_then(map_fields).cloned().unwrap_or_default();
313            merge_level(&mut inner, given.and_then(map_fields), below);
314            match old {
315                // A map, or something the masked paths below turn into one.
316                Some(old) if map_fields(old).is_some() || !inner.is_empty() => Some(map_of(inner)),
317                Some(old) => Some(old.clone()),
318                None if inner.is_empty() => None,
319                None => Some(map_of(inner)),
320            }
321        };
322        // Bit for bit: `-0.0` over `0.0` is a change, NaN over NaN is not.
323        let changed = match (&new, old) {
324            (Some(new), Some(old)) => !identical(new, old),
325            (new, old) => new.is_some() || old.is_some(),
326        };
327        if changed {
328            changes.push((name.clone(), new));
329        }
330    }
331    for (name, new) in in_hash_order(changes) {
332        existing.remove(&name);
333        if let Some(value) = new {
334            existing.insert(name, value);
335        }
336    }
337}
338
339fn empty_map() -> Value {
340    Value {
341        value_type: Some(ValueType::MapValue(MapValue::default())),
342    }
343}
344
345#[cfg(test)]
346mod tests {
347    use super::*;
348
349    #[test]
350    fn mask_paths_as_rules_see_them() {
351        let listed = |mask: &[&str]| -> Vec<String> {
352            let mask: Vec<FieldPath> = mask.iter().map(|p| FieldPath::parse(p).unwrap()).collect();
353            mask_paths(&mask).iter().map(ToString::to_string).collect()
354        };
355        // Recorded in crates/hidane/tests/fixtures/rules_request_fields.json.
356        assert_eq!(listed(&["c", "b", "a"]), ["a", "b", "c"]);
357        assert_eq!(listed(&["n", "m.b", "z"]), ["z", "m.b", "n"]);
358        assert_eq!(listed(&["b", "a", "b"]), ["a", "b"]);
359        assert_eq!(listed(&["m.b", "m"]), ["m"]);
360        assert_eq!(listed(&["m.b.c", "m.a", "z"]), ["z", "m.a", "m.b.c"]);
361        assert_eq!(
362            listed(&["x.y", "a", "`q r`", "b.c.d"]),
363            ["a", "`q r`", "b.c.d", "x.y"]
364        );
365        // `b`, `r`, `B` and `R` share a bucket: names taken whole come first.
366        assert_eq!(listed(&["b.c", "r", "B.x", "R"]), ["r", "R", "b.c", "B.x"]);
367        assert_eq!(listed(&["m.b.c", "m.r"]), ["m.r", "m.b.c"]);
368        assert!(listed(&[]).is_empty());
369    }
370
371    fn int(i: i64) -> Value {
372        Value {
373            value_type: Some(ValueType::IntegerValue(i)),
374        }
375    }
376
377    fn map(entries: &[(&str, Value)]) -> Value {
378        Value {
379            value_type: Some(ValueType::MapValue(MapValue {
380                fields: entries
381                    .iter()
382                    .map(|(k, v)| ((*k).to_owned(), v.clone()))
383                    .collect(),
384            })),
385        }
386    }
387
388    fn fields(entries: &[(&str, Value)]) -> Fields {
389        entries
390            .iter()
391            .map(|(k, v)| ((*k).to_owned(), v.clone()))
392            .collect()
393    }
394
395    fn fp(s: &str) -> FieldPath {
396        FieldPath::parse(s).unwrap()
397    }
398
399    #[test]
400    fn display_is_the_canonical_form() {
401        for canonical in [
402            "a", "a.b", "_x9", "`a b`.c", "`1a`", "`x\\`y`", "`a\\\\b`", "`é`",
403        ] {
404            assert_eq!(fp(canonical).to_string(), canonical);
405        }
406        assert_eq!(fp("`a`.`b`").to_string(), "a.b");
407    }
408
409    #[test]
410    fn parses_unquoted_and_quoted_segments() {
411        assert_eq!(fp("a").segments(), ["a"]);
412        assert_eq!(fp("a.b_1._c").segments(), ["a", "b_1", "_c"]);
413        assert_eq!(fp("`a.b`.c").segments(), ["a.b", "c"]);
414        assert_eq!(fp("`x\\`y`").segments(), ["x`y"]);
415        assert_eq!(fp("`\\\\`").segments(), ["\\"]);
416        assert_eq!(fp("`1st`").segments(), ["1st"]);
417    }
418
419    #[test]
420    fn rejects_like_the_official_emulator() {
421        for bad in [
422            "", "a..b", ".a", "a.", "1a", "a-b", "``", "`a", "`a`b", "a b",
423        ] {
424            let err = FieldPath::parse(bad).unwrap_err();
425            assert!(
426                err.starts_with(&format!("Invalid property path \"{bad}\". Unquoted")),
427                "{bad}: {err}"
428            );
429        }
430        // The exact text the official emulator returns (tests/fixtures/document_writes.json).
431        assert_eq!(
432            FieldPath::parse("a..b").unwrap_err(),
433            "Invalid property path \"a..b\". Unquoted property paths must match regex \
434             ([a-zA-Z_][a-zA-Z_0-9]*), and quoted property paths must match regex \
435             (`(?:[^`\\\\]|(?:\\\\.))+`)"
436        );
437    }
438
439    #[test]
440    fn update_mask_matches_the_official_emulator() {
441        // Observed: {a:1, b:2, c:{x:1, y:2}} patched with mask [a, c.x, d] and input
442        // {a:10, c:{x:5}, e:9} becomes {a:10, b:2, c:{x:5, y:2}}.
443        let mut doc = fields(&[
444            ("a", int(1)),
445            ("b", int(2)),
446            ("c", map(&[("x", int(1)), ("y", int(2))])),
447        ]);
448        let input = fields(&[("a", int(10)), ("c", map(&[("x", int(5))])), ("e", int(9))]);
449        apply_mask(&mut doc, &input, &[fp("a"), fp("c.x"), fp("d")]);
450        assert_eq!(
451            doc,
452            fields(&[
453                ("a", int(10)),
454                ("b", int(2)),
455                ("c", map(&[("x", int(5)), ("y", int(2))])),
456            ])
457        );
458    }
459
460    #[test]
461    fn masks_on_missing_documents_create_only_the_masked_fields() {
462        let mut doc = Fields::new();
463        let input = fields(&[("a", int(1)), ("b", map(&[("x", int(1))]))]);
464        apply_mask(&mut doc, &input, &[fp("b.x")]);
465        assert_eq!(doc, fields(&[("b", map(&[("x", int(1))]))]));
466    }
467
468    #[test]
469    fn read_masks_project_nested_fields() {
470        let doc = fields(&[
471            ("a", int(1)),
472            ("b", map(&[("x", int(1)), ("y", int(2))])),
473            ("c", int(3)),
474        ]);
475        assert_eq!(
476            project(
477                &doc,
478                &[fp("b.x"), fp("c"), fp("missing"), fp("a.not_a_map")]
479            ),
480            fields(&[("b", map(&[("x", int(1))])), ("c", int(3))])
481        );
482    }
483
484    #[test]
485    fn enterprise_projections_keep_the_maps_on_the_way() {
486        let doc = fields(&[("a", int(1)), ("b", map(&[("x", int(1)), ("y", int(2))]))]);
487        let empty = || map(&[]);
488        // Recorded in crates/hidane/tests/fixtures/queries_enterprise.json.
489        assert_eq!(
490            project_keeping_maps(
491                &doc,
492                &[fp("b.x"), fp("missing"), fp("a.not_a_map"), fp("m.x.y")]
493            ),
494            project_keeping_maps(
495                &fields(&[
496                    ("b", map(&[("x", int(1))])),
497                    ("a", empty()),
498                    ("m", map(&[("x", empty())])),
499                ]),
500                &[fp("b"), fp("a"), fp("m")]
501            )
502        );
503    }
504
505    /// The names of `fields`, nested maps as `name{...}`, in the order they are held.
506    fn layout(fields: &Fields) -> String {
507        fields
508            .iter()
509            .map(|(name, value)| match &value.value_type {
510                Some(ValueType::MapValue(map)) => format!("{name}{{{}}}", layout(&map.fields)),
511                _ => name.clone(),
512            })
513            .collect::<Vec<_>>()
514            .join(",")
515    }
516
517    // The cases below are recorded from the official emulator in
518    // crates/hidane/tests/fixtures/field_order.json (#108).
519
520    #[test]
521    fn masked_fields_that_change_move_to_the_end_in_hash_order() {
522        let base = || {
523            fields(&[
524                ("zeta", int(1)),
525                ("alpha", int(2)),
526                ("mid", map(&[("z", int(3)), ("a", int(4))])),
527            ])
528        };
529        let mut doc = base();
530        apply_mask(
531            &mut doc,
532            &fields(&[("beta", int(9)), ("alpha", int(10))]),
533            &[fp("beta"), fp("alpha")],
534        );
535        assert_eq!(layout(&doc), "zeta,mid{z,a},alpha,beta");
536
537        // alpha keeps its value, so it stays.
538        let mut doc = base();
539        apply_mask(
540            &mut doc,
541            &fields(&[
542                ("deep", map(&[("x", map(&[("y", int(1))]))])),
543                ("alpha", int(2)),
544            ]),
545            &[fp("deep.x.y"), fp("alpha")],
546        );
547        assert_eq!(layout(&doc), "zeta,alpha,mid{z,a},deep{x{y}}");
548    }
549
550    #[test]
551    fn a_map_changed_inside_moves_to_the_end() {
552        let base = || {
553            fields(&[
554                ("mid", map(&[("z", int(3)), ("a", int(4))])),
555                ("zeta", int(1)),
556                ("alpha", int(2)),
557            ])
558        };
559        let mut doc = base();
560        apply_mask(
561            &mut doc,
562            &fields(&[("mid", map(&[("b", int(5))]))]),
563            &[fp("mid.b")],
564        );
565        assert_eq!(layout(&doc), "zeta,alpha,mid{z,a,b}");
566
567        let mut doc = base();
568        apply_mask(&mut doc, &Fields::new(), &[fp("mid.z")]);
569        assert_eq!(layout(&doc), "zeta,alpha,mid{a}");
570
571        let mut doc = fields(&[
572            ("p", map(&[("x", int(1)), ("y", int(2))])),
573            ("alpha", int(1)),
574            ("zeta", int(2)),
575            ("mid", int(3)),
576        ]);
577        apply_mask(
578            &mut doc,
579            &fields(&[("alpha", int(9))]),
580            &[fp("p.y"), fp("alpha")],
581        );
582        assert_eq!(layout(&doc), "zeta,mid,p{x},alpha");
583    }
584
585    #[test]
586    fn the_changed_fields_have_a_hash_map_of_their_own() {
587        let present = [
588            "str", "int", "dbl", "zeta", "alpha", "a", "b", "c", "x1", "x2", "x3",
589        ];
590        let doc_with = |extra: &[(&str, Value)]| {
591            let mut doc = fields(&[("keep", int(0))]);
592            for (n, name) in present.iter().enumerate() {
593                doc.insert((*name).to_owned(), int(n as i64));
594            }
595            doc.extend(extra.iter().map(|(k, v)| ((*k).to_owned(), v.clone())));
596            doc
597        };
598        let input: Fields = present
599            .iter()
600            .enumerate()
601            .map(|(n, name)| ((*name).to_owned(), int(n as i64 + 100)))
602            .collect();
603        let mask: Vec<FieldPath> = present
604            .iter()
605            .chain(&["gone1", "gone2"])
606            .map(|p| fp(p))
607            .collect();
608
609        // Thirteen masked names, eleven changes: the changes fit 16 buckets.
610        let mut doc = doc_with(&[]);
611        apply_mask(&mut doc, &input, &mask);
612        assert_eq!(layout(&doc), "keep,zeta,str,a,b,c,alpha,x1,x2,x3,int,dbl");
613
614        // Deleting two fields makes thirteen changes: 32 buckets.
615        let mut doc = doc_with(&[("gone1", int(1)), ("gone2", int(2))]);
616        apply_mask(&mut doc, &input, &mask);
617        assert_eq!(layout(&doc), "keep,zeta,a,b,c,int,dbl,str,alpha,x1,x2,x3");
618    }
619
620    #[test]
621    fn read_masks_return_fields_in_hash_order() {
622        let doc = fields(&[
623            ("str", int(1)),
624            ("mid", map(&[("z", int(1)), ("a", int(2))])),
625            ("zeta", int(3)),
626            ("alpha", int(4)),
627            ("a", int(5)),
628        ]);
629        let masked = |mask: &[&str]| {
630            layout(&project(
631                &doc,
632                &mask.iter().map(|p| fp(p)).collect::<Vec<_>>(),
633            ))
634        };
635        assert_eq!(masked(&["a", "alpha", "zeta"]), "zeta,a,alpha");
636        assert_eq!(masked(&["mid.a", "mid.z", "str"]), "str,mid{a,z}");
637        // A masked map keeps its own order.
638        assert_eq!(masked(&["mid", "str"]), "str,mid{z,a}");
639    }
640
641    #[test]
642    fn set_replaces_non_map_intermediates() {
643        let mut doc = fields(&[("a", int(1))]);
644        set(&mut doc, &fp("a.b"), int(2));
645        assert_eq!(doc, fields(&[("a", map(&[("b", int(2))]))]));
646    }
647}