1use 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, store::identical};
13
14#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
15pub struct FieldPath(Vec<String>);
16
17impl FieldPath {
18 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
86impl 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
116pub fn get<'a>(fields: &'a Fields, path: &FieldPath) -> Option<&'a Value> {
118 let (last, parents) = path.0.split_last()?;
119 let mut current = fields;
120 for segment in parents {
121 match ¤t.get(segment)?.value_type {
122 Some(ValueType::MapValue(map)) => current = &map.fields,
123 _ => return None,
124 }
125 }
126 current.get(last)
127}
128
129pub fn set(fields: &mut Fields, path: &FieldPath, value: Value) {
131 let Some((last, parents)) = path.0.split_last() else {
132 return;
133 };
134 let mut current = fields;
135 for segment in parents {
136 let entry = current.entry(segment.clone()).or_insert_with(empty_map);
137 if !matches!(entry.value_type, Some(ValueType::MapValue(_))) {
138 *entry = empty_map();
139 }
140 let Some(ValueType::MapValue(map)) = &mut entry.value_type else {
141 unreachable!("just made it a map");
142 };
143 current = &mut map.fields;
144 }
145 current.insert(last.clone(), value);
146}
147
148pub fn delete(fields: &mut Fields, path: &FieldPath) {
150 let Some((last, parents)) = path.0.split_last() else {
151 return;
152 };
153 let mut current = fields;
154 for segment in parents {
155 match current.get_mut(segment).and_then(|v| v.value_type.as_mut()) {
156 Some(ValueType::MapValue(map)) => current = &mut map.fields,
157 _ => return,
158 }
159 }
160 current.remove(last);
161}
162
163#[derive(Default)]
166struct MaskTree {
167 whole: bool,
169 children: Vec<(String, MaskTree)>,
170}
171
172impl MaskTree {
173 fn new(paths: &[FieldPath]) -> Self {
174 let mut root = Self::default();
175 for path in paths {
176 let mut node = &mut root;
177 for segment in path.segments() {
178 let at = match node.children.iter().position(|(name, _)| name == segment) {
179 Some(at) => at,
180 None => {
181 node.children.push((segment.clone(), Self::default()));
182 node.children.len() - 1
183 }
184 };
185 node = &mut node.children[at].1;
186 }
187 node.whole = true;
188 }
189 root
190 }
191
192 fn ordered(&self) -> impl Iterator<Item = &(String, MaskTree)> {
195 let names: Vec<&str> = self
196 .children
197 .iter()
198 .map(|(name, _)| name.as_str())
199 .collect();
200 hash_order(&names).into_iter().map(|i| &self.children[i])
201 }
202
203 fn flatten(&self, prefix: &mut Vec<String>, out: &mut Vec<FieldPath>) {
207 let (whole, below): (Vec<_>, Vec<_>) =
208 self.children.iter().partition(|(_, node)| node.whole);
209 let children: Vec<&(String, MaskTree)> = whole.into_iter().chain(below).collect();
210 let names: Vec<&str> = children.iter().map(|(name, _)| name.as_str()).collect();
211 for (name, below) in hash_order(&names).into_iter().map(|i| children[i]) {
212 prefix.push(name.clone());
213 if below.whole {
214 out.push(FieldPath(prefix.clone()));
215 } else {
216 below.flatten(prefix, out);
217 }
218 prefix.pop();
219 }
220 }
221}
222
223pub fn mask_paths(mask: &[FieldPath]) -> Vec<FieldPath> {
228 let mut out = Vec::new();
229 MaskTree::new(mask).flatten(&mut Vec::new(), &mut out);
230 out
231}
232
233fn in_hash_order<T>(mut entries: Vec<(String, T)>) -> impl Iterator<Item = (String, T)> {
235 let order = hash_order(&entries.iter().map(|(name, _)| name).collect::<Vec<_>>());
236 let mut taken: Vec<Option<(String, T)>> = entries.drain(..).map(Some).collect();
237 order.into_iter().filter_map(move |i| taken[i].take())
238}
239
240fn map_fields(value: &Value) -> Option<&Fields> {
241 match &value.value_type {
242 Some(ValueType::MapValue(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
253pub fn project(fields: &Fields, paths: &[FieldPath]) -> Fields {
257 project_level(fields, &MaskTree::new(paths))
258}
259
260fn project_level(fields: &Fields, mask: &MaskTree) -> Fields {
261 let mut found = Vec::new();
262 for (name, below) in mask.ordered() {
263 let Some(value) = fields.get(name) else {
264 continue;
265 };
266 if below.whole {
267 found.push((name.clone(), value.clone()));
268 } else if let Some(map) = map_fields(value) {
269 let inner = project_level(map, below);
270 if !inner.is_empty() {
271 found.push((name.clone(), map_of(inner)));
272 }
273 }
274 }
275 in_hash_order(found).collect()
276}
277
278pub fn apply_mask(existing: &mut Fields, input: &Fields, mask: &[FieldPath]) {
287 merge_level(existing, Some(input), &MaskTree::new(mask));
288}
289
290fn merge_level(existing: &mut Fields, input: Option<&Fields>, mask: &MaskTree) {
291 let mut changes = Vec::new();
292 for (name, below) in mask.ordered() {
293 let old = existing.get(name);
294 let given = input.and_then(|fields| fields.get(name));
295 let new = if below.whole {
296 given.cloned()
297 } else {
298 let mut inner = old.and_then(map_fields).cloned().unwrap_or_default();
299 merge_level(&mut inner, given.and_then(map_fields), below);
300 match old {
301 Some(old) if map_fields(old).is_some() || !inner.is_empty() => Some(map_of(inner)),
303 Some(old) => Some(old.clone()),
304 None if inner.is_empty() => None,
305 None => Some(map_of(inner)),
306 }
307 };
308 let changed = match (&new, old) {
310 (Some(new), Some(old)) => !identical(new, old),
311 (new, old) => new.is_some() || old.is_some(),
312 };
313 if changed {
314 changes.push((name.clone(), new));
315 }
316 }
317 for (name, new) in in_hash_order(changes) {
318 existing.remove(&name);
319 if let Some(value) = new {
320 existing.insert(name, value);
321 }
322 }
323}
324
325fn empty_map() -> Value {
326 Value {
327 value_type: Some(ValueType::MapValue(MapValue::default())),
328 }
329}
330
331#[cfg(test)]
332mod tests {
333 use super::*;
334
335 #[test]
336 fn mask_paths_as_rules_see_them() {
337 let listed = |mask: &[&str]| -> Vec<String> {
338 let mask: Vec<FieldPath> = mask.iter().map(|p| FieldPath::parse(p).unwrap()).collect();
339 mask_paths(&mask).iter().map(ToString::to_string).collect()
340 };
341 assert_eq!(listed(&["c", "b", "a"]), ["a", "b", "c"]);
343 assert_eq!(listed(&["n", "m.b", "z"]), ["z", "m.b", "n"]);
344 assert_eq!(listed(&["b", "a", "b"]), ["a", "b"]);
345 assert_eq!(listed(&["m.b", "m"]), ["m"]);
346 assert_eq!(listed(&["m.b.c", "m.a", "z"]), ["z", "m.a", "m.b.c"]);
347 assert_eq!(
348 listed(&["x.y", "a", "`q r`", "b.c.d"]),
349 ["a", "`q r`", "b.c.d", "x.y"]
350 );
351 assert_eq!(listed(&["b.c", "r", "B.x", "R"]), ["r", "R", "b.c", "B.x"]);
353 assert_eq!(listed(&["m.b.c", "m.r"]), ["m.r", "m.b.c"]);
354 assert!(listed(&[]).is_empty());
355 }
356
357 fn int(i: i64) -> Value {
358 Value {
359 value_type: Some(ValueType::IntegerValue(i)),
360 }
361 }
362
363 fn map(entries: &[(&str, Value)]) -> Value {
364 Value {
365 value_type: Some(ValueType::MapValue(MapValue {
366 fields: entries
367 .iter()
368 .map(|(k, v)| ((*k).to_owned(), v.clone()))
369 .collect(),
370 })),
371 }
372 }
373
374 fn fields(entries: &[(&str, Value)]) -> Fields {
375 entries
376 .iter()
377 .map(|(k, v)| ((*k).to_owned(), v.clone()))
378 .collect()
379 }
380
381 fn fp(s: &str) -> FieldPath {
382 FieldPath::parse(s).unwrap()
383 }
384
385 #[test]
386 fn display_is_the_canonical_form() {
387 for canonical in [
388 "a", "a.b", "_x9", "`a b`.c", "`1a`", "`x\\`y`", "`a\\\\b`", "`é`",
389 ] {
390 assert_eq!(fp(canonical).to_string(), canonical);
391 }
392 assert_eq!(fp("`a`.`b`").to_string(), "a.b");
393 }
394
395 #[test]
396 fn parses_unquoted_and_quoted_segments() {
397 assert_eq!(fp("a").segments(), ["a"]);
398 assert_eq!(fp("a.b_1._c").segments(), ["a", "b_1", "_c"]);
399 assert_eq!(fp("`a.b`.c").segments(), ["a.b", "c"]);
400 assert_eq!(fp("`x\\`y`").segments(), ["x`y"]);
401 assert_eq!(fp("`\\\\`").segments(), ["\\"]);
402 assert_eq!(fp("`1st`").segments(), ["1st"]);
403 }
404
405 #[test]
406 fn rejects_like_the_official_emulator() {
407 for bad in [
408 "", "a..b", ".a", "a.", "1a", "a-b", "``", "`a", "`a`b", "a b",
409 ] {
410 let err = FieldPath::parse(bad).unwrap_err();
411 assert!(
412 err.starts_with(&format!("Invalid property path \"{bad}\". Unquoted")),
413 "{bad}: {err}"
414 );
415 }
416 assert_eq!(
418 FieldPath::parse("a..b").unwrap_err(),
419 "Invalid property path \"a..b\". Unquoted property paths must match regex \
420 ([a-zA-Z_][a-zA-Z_0-9]*), and quoted property paths must match regex \
421 (`(?:[^`\\\\]|(?:\\\\.))+`)"
422 );
423 }
424
425 #[test]
426 fn update_mask_matches_the_official_emulator() {
427 let mut doc = fields(&[
430 ("a", int(1)),
431 ("b", int(2)),
432 ("c", map(&[("x", int(1)), ("y", int(2))])),
433 ]);
434 let input = fields(&[("a", int(10)), ("c", map(&[("x", int(5))])), ("e", int(9))]);
435 apply_mask(&mut doc, &input, &[fp("a"), fp("c.x"), fp("d")]);
436 assert_eq!(
437 doc,
438 fields(&[
439 ("a", int(10)),
440 ("b", int(2)),
441 ("c", map(&[("x", int(5)), ("y", int(2))])),
442 ])
443 );
444 }
445
446 #[test]
447 fn masks_on_missing_documents_create_only_the_masked_fields() {
448 let mut doc = Fields::new();
449 let input = fields(&[("a", int(1)), ("b", map(&[("x", int(1))]))]);
450 apply_mask(&mut doc, &input, &[fp("b.x")]);
451 assert_eq!(doc, fields(&[("b", map(&[("x", int(1))]))]));
452 }
453
454 #[test]
455 fn read_masks_project_nested_fields() {
456 let doc = fields(&[
457 ("a", int(1)),
458 ("b", map(&[("x", int(1)), ("y", int(2))])),
459 ("c", int(3)),
460 ]);
461 assert_eq!(
462 project(
463 &doc,
464 &[fp("b.x"), fp("c"), fp("missing"), fp("a.not_a_map")]
465 ),
466 fields(&[("b", map(&[("x", int(1))])), ("c", int(3))])
467 );
468 }
469
470 fn layout(fields: &Fields) -> String {
472 fields
473 .iter()
474 .map(|(name, value)| match &value.value_type {
475 Some(ValueType::MapValue(map)) => format!("{name}{{{}}}", layout(&map.fields)),
476 _ => name.clone(),
477 })
478 .collect::<Vec<_>>()
479 .join(",")
480 }
481
482 #[test]
486 fn masked_fields_that_change_move_to_the_end_in_hash_order() {
487 let base = || {
488 fields(&[
489 ("zeta", int(1)),
490 ("alpha", int(2)),
491 ("mid", map(&[("z", int(3)), ("a", int(4))])),
492 ])
493 };
494 let mut doc = base();
495 apply_mask(
496 &mut doc,
497 &fields(&[("beta", int(9)), ("alpha", int(10))]),
498 &[fp("beta"), fp("alpha")],
499 );
500 assert_eq!(layout(&doc), "zeta,mid{z,a},alpha,beta");
501
502 let mut doc = base();
504 apply_mask(
505 &mut doc,
506 &fields(&[
507 ("deep", map(&[("x", map(&[("y", int(1))]))])),
508 ("alpha", int(2)),
509 ]),
510 &[fp("deep.x.y"), fp("alpha")],
511 );
512 assert_eq!(layout(&doc), "zeta,alpha,mid{z,a},deep{x{y}}");
513 }
514
515 #[test]
516 fn a_map_changed_inside_moves_to_the_end() {
517 let base = || {
518 fields(&[
519 ("mid", map(&[("z", int(3)), ("a", int(4))])),
520 ("zeta", int(1)),
521 ("alpha", int(2)),
522 ])
523 };
524 let mut doc = base();
525 apply_mask(
526 &mut doc,
527 &fields(&[("mid", map(&[("b", int(5))]))]),
528 &[fp("mid.b")],
529 );
530 assert_eq!(layout(&doc), "zeta,alpha,mid{z,a,b}");
531
532 let mut doc = base();
533 apply_mask(&mut doc, &Fields::new(), &[fp("mid.z")]);
534 assert_eq!(layout(&doc), "zeta,alpha,mid{a}");
535
536 let mut doc = fields(&[
537 ("p", map(&[("x", int(1)), ("y", int(2))])),
538 ("alpha", int(1)),
539 ("zeta", int(2)),
540 ("mid", int(3)),
541 ]);
542 apply_mask(
543 &mut doc,
544 &fields(&[("alpha", int(9))]),
545 &[fp("p.y"), fp("alpha")],
546 );
547 assert_eq!(layout(&doc), "zeta,mid,p{x},alpha");
548 }
549
550 #[test]
551 fn the_changed_fields_have_a_hash_map_of_their_own() {
552 let present = [
553 "str", "int", "dbl", "zeta", "alpha", "a", "b", "c", "x1", "x2", "x3",
554 ];
555 let doc_with = |extra: &[(&str, Value)]| {
556 let mut doc = fields(&[("keep", int(0))]);
557 for (n, name) in present.iter().enumerate() {
558 doc.insert((*name).to_owned(), int(n as i64));
559 }
560 doc.extend(extra.iter().map(|(k, v)| ((*k).to_owned(), v.clone())));
561 doc
562 };
563 let input: Fields = present
564 .iter()
565 .enumerate()
566 .map(|(n, name)| ((*name).to_owned(), int(n as i64 + 100)))
567 .collect();
568 let mask: Vec<FieldPath> = present
569 .iter()
570 .chain(&["gone1", "gone2"])
571 .map(|p| fp(p))
572 .collect();
573
574 let mut doc = doc_with(&[]);
576 apply_mask(&mut doc, &input, &mask);
577 assert_eq!(layout(&doc), "keep,zeta,str,a,b,c,alpha,x1,x2,x3,int,dbl");
578
579 let mut doc = doc_with(&[("gone1", int(1)), ("gone2", int(2))]);
581 apply_mask(&mut doc, &input, &mask);
582 assert_eq!(layout(&doc), "keep,zeta,a,b,c,int,dbl,str,alpha,x1,x2,x3");
583 }
584
585 #[test]
586 fn read_masks_return_fields_in_hash_order() {
587 let doc = fields(&[
588 ("str", int(1)),
589 ("mid", map(&[("z", int(1)), ("a", int(2))])),
590 ("zeta", int(3)),
591 ("alpha", int(4)),
592 ("a", int(5)),
593 ]);
594 let masked = |mask: &[&str]| {
595 layout(&project(
596 &doc,
597 &mask.iter().map(|p| fp(p)).collect::<Vec<_>>(),
598 ))
599 };
600 assert_eq!(masked(&["a", "alpha", "zeta"]), "zeta,a,alpha");
601 assert_eq!(masked(&["mid.a", "mid.z", "str"]), "str,mid{a,z}");
602 assert_eq!(masked(&["mid", "str"]), "str,mid{z,a}");
604 }
605
606 #[test]
607 fn set_replaces_non_map_intermediates() {
608 let mut doc = fields(&[("a", int(1))]);
609 set(&mut doc, &fp("a.b"), int(2));
610 assert_eq!(doc, fields(&[("a", map(&[("b", int(2))]))]));
611 }
612}