1use crate::document::{
4 DocumentError, DocumentResult, Value,
5 keyed::{Addressing, KeyedList},
6 path::{join_path, parse_path},
7};
8
9fn resolve_named_element(
18 array: &[Value],
19 segment: &str,
20 prefix: &[String],
21 addressing: Addressing<'_>,
22) -> DocumentResult<usize> {
23 if let Some(registration) = addressing
26 .keyed_lists
27 .iter()
28 .find(|list| keyed_prefix_matches(list, prefix))
29 {
30 let hits = array
31 .iter()
32 .enumerate()
33 .filter(|(_, element)| {
34 element.get(registration.slug_field).and_then(Value::as_str) == Some(segment)
35 })
36 .map(|(index, _)| index)
37 .collect();
38 return resolve_unique_hit(hits, segment, &join_path(prefix));
39 }
40
41 let Some(rule) = addressing.array_rule else {
42 return Err(DocumentError::UnregisteredArray {
43 path: join_path(prefix),
44 });
45 };
46
47 let hits: Vec<usize> = array
48 .iter()
49 .enumerate()
50 .filter(|(_, element)| rule.matches(element, segment))
51 .map(|(index, _)| index)
52 .collect();
53 resolve_unique_hit(hits, segment, &join_path(prefix))
54}
55
56fn resolve_unique_hit(hits: Vec<usize>, segment: &str, prefix: &str) -> DocumentResult<usize> {
57 match hits.as_slice() {
58 [] => Err(DocumentError::SlugNotFound {
59 prefix: prefix.to_string(),
60 slug: segment.to_string(),
61 }),
62 [only] => Ok(*only),
63 several => Err(DocumentError::AmbiguousMatch {
64 prefix: prefix.to_string(),
65 segment: segment.to_string(),
66 indices: several.to_vec(),
67 }),
68 }
69}
70
71fn array_index(segment: &str) -> DocumentResult<Option<usize>> {
79 if segment.is_empty() || !segment.bytes().all(|byte| byte.is_ascii_digit()) {
80 return Ok(None);
81 }
82 segment
83 .parse::<usize>()
84 .map(Some)
85 .map_err(|_| DocumentError::PathSyntax {
86 detail: "array index exceeds the platform index range".to_string(),
87 })
88}
89
90pub fn get_path_ref<'a>(
97 root: &'a Value,
98 path: &str,
99 addressing: Addressing<'_>,
100) -> DocumentResult<&'a Value> {
101 if path.is_empty() {
102 return Err(DocumentError::EmptyPath);
103 }
104
105 let segments = parse_path(path)?;
106 let mut current = root;
107 let mut accumulated_prefix: Vec<String> = Vec::new();
108 let mut seg_idx = 0;
109
110 while seg_idx < segments.len() {
111 let current_seg = segments[seg_idx].as_str();
112
113 match current {
114 Value::Object(obj) => {
115 if let Some(next) = obj.get(current_seg) {
117 accumulated_prefix.push(current_seg.to_string());
118 current = next;
119 seg_idx += 1;
120 } else {
121 return Err(DocumentError::UnknownSegment {
122 path: path.to_string(),
123 segment: current_seg.to_string(),
124 });
125 }
126 }
127 Value::Array(arr) => {
128 if let Some(arr_idx) = array_index(current_seg)? {
130 let elem = arr
131 .get(arr_idx)
132 .ok_or_else(|| DocumentError::IndexOutOfBounds {
133 path: join_path(&accumulated_prefix),
134 index: arr_idx,
135 len: arr.len(),
136 })?;
137 accumulated_prefix.push(current_seg.to_string());
138 current = elem;
139 seg_idx += 1;
140 } else {
141 let index =
142 resolve_named_element(arr, current_seg, &accumulated_prefix, addressing)?;
143 current = &arr[index];
144 accumulated_prefix.push(current_seg.to_string());
145 seg_idx += 1;
146 }
147 }
148 _ => {
149 return Err(DocumentError::NotTraversable {
150 path: path.to_string(),
151 got: current.kind_name().to_string(),
152 });
153 }
154 }
155 }
156
157 Ok(current)
158}
159
160pub fn get_path(root: &Value, path: &str, addressing: Addressing<'_>) -> DocumentResult<Value> {
162 Ok(get_path_ref(root, path, addressing)?.clone())
163}
164
165pub fn resolve_path(
187 root: &Value,
188 path: &str,
189 addressing: Addressing<'_>,
190) -> DocumentResult<String> {
191 if path.is_empty() {
192 return Ok(String::new());
193 }
194
195 let segments = parse_path(path)?;
196 let mut current = root;
197 let mut semantic: Vec<String> = Vec::new();
198 let mut canonical: Vec<String> = Vec::with_capacity(segments.len());
199
200 for (idx, segment) in segments.iter().enumerate() {
201 let copy_rest = |canonical: &mut Vec<String>| {
202 canonical.extend(segments[idx..].iter().cloned());
203 };
204 match current {
205 Value::Object(object) => match object.get(segment.as_str()) {
206 Some(next) => {
207 canonical.push(segment.clone());
208 semantic.push(segment.clone());
209 current = next;
210 }
211 None => {
212 copy_rest(&mut canonical);
213 break;
214 }
215 },
216 Value::Array(array) => {
217 let index = match array_index(segment)? {
218 Some(index) => index,
219 None => resolve_named_element(array, segment, &semantic, addressing)?,
220 };
221 let Some(next) = array.get(index) else {
222 copy_rest(&mut canonical);
223 break;
224 };
225 canonical.push(index.to_string());
226 semantic.push(segment.clone());
227 current = next;
228 }
229 _ => {
230 copy_rest(&mut canonical);
231 break;
232 }
233 }
234 }
235
236 Ok(join_path(&canonical))
237}
238
239pub fn set_path(
245 root: &mut Value,
246 path: &str,
247 value: &Value,
248 addressing: Addressing<'_>,
249) -> DocumentResult<()> {
250 if path.is_empty() {
251 return Err(DocumentError::EmptyPath);
252 }
253
254 let segments = parse_path(path)?;
255 set_path_recursive(root, &segments, 0, &mut Vec::new(), addressing, value)
256}
257
258fn set_path_recursive(
259 current: &mut Value,
260 segments: &[String],
261 idx: usize,
262 accumulated_prefix: &mut Vec<String>,
263 addressing: Addressing<'_>,
264 value: &Value,
265) -> DocumentResult<()> {
266 if idx >= segments.len() {
267 return Err(DocumentError::EmptyPath);
268 }
269
270 let current_seg = segments[idx].as_str();
271 let is_last = idx == segments.len() - 1;
272
273 match current {
274 Value::Object(obj) => {
275 let key_to_use = current_seg.to_string();
277
278 let segments_to_consume = 1;
279
280 if is_last {
281 obj.insert(key_to_use, value.clone());
283 Ok(())
284 } else {
285 let next_idx = idx + segments_to_consume;
287 if next_idx >= segments.len() {
288 return Err(DocumentError::EmptyPath);
289 }
290
291 accumulated_prefix.push(key_to_use.clone());
292
293 use std::collections::btree_map::Entry;
295 match obj.entry(key_to_use) {
296 Entry::Occupied(mut ent) => set_path_recursive(
297 ent.get_mut(),
298 segments,
299 next_idx,
300 accumulated_prefix,
301 addressing,
302 value,
303 ),
304 Entry::Vacant(ent) => {
305 let mut new_obj = Value::Object(Default::default());
306 let result = set_path_recursive(
307 &mut new_obj,
308 segments,
309 next_idx,
310 accumulated_prefix,
311 addressing,
312 value,
313 );
314 if result.is_ok() {
315 ent.insert(new_obj);
316 }
317 result
318 }
319 }
320 }
321 }
322 Value::Array(arr) => {
323 if let Some(arr_idx) = array_index(current_seg)? {
325 if arr_idx >= arr.len() {
326 return Err(DocumentError::IndexOutOfBounds {
327 path: join_path(accumulated_prefix),
328 index: arr_idx,
329 len: arr.len(),
330 });
331 }
332 if is_last {
333 arr[arr_idx] = value.clone();
334 Ok(())
335 } else {
336 accumulated_prefix.push(current_seg.to_string());
337 set_path_recursive(
338 &mut arr[arr_idx],
339 segments,
340 idx + 1,
341 accumulated_prefix,
342 addressing,
343 value,
344 )
345 }
346 } else {
347 let elem_idx =
348 resolve_named_element(arr, current_seg, accumulated_prefix, addressing)?;
349 if is_last {
350 Err(DocumentError::UnsupportedOperation {
351 format: "keyed list".to_string(),
352 operation: "set".to_string(),
353 detail:
354 "a keyed-list slug resolves to an element; set a child field instead"
355 .to_string(),
356 })
357 } else {
358 accumulated_prefix.push(current_seg.to_string());
359 set_path_recursive(
360 &mut arr[elem_idx],
361 segments,
362 idx + 1,
363 accumulated_prefix,
364 addressing,
365 value,
366 )
367 }
368 }
369 }
370 _ => Err(DocumentError::NotTraversable {
371 path: join_path(accumulated_prefix),
372 got: current.kind_name().to_string(),
373 }),
374 }
375}
376
377pub(crate) fn keyed_prefix_matches(
378 registration: &KeyedList<'_>,
379 semantic_prefix: &[String],
380) -> bool {
381 if registration.prefix.is_empty() {
382 return semantic_prefix.is_empty();
383 }
384 crate::document::parse_path(registration.prefix)
385 .ok()
386 .is_some_and(|segments| segments == semantic_prefix)
387}
388
389pub fn unset_path(root: &mut Value, path: &str) -> DocumentResult<()> {
394 if path.is_empty() {
395 return Err(DocumentError::EmptyPath);
396 }
397 let segments = parse_path(path)?;
398 unset_path_recursive(root, &segments, 0, &mut Vec::new())
399}
400
401fn unset_path_recursive(
402 current: &mut Value,
403 segments: &[String],
404 idx: usize,
405 accumulated_prefix: &mut Vec<String>,
406) -> DocumentResult<()> {
407 if idx >= segments.len() {
408 return Err(DocumentError::EmptyPath);
409 }
410 let current_seg = segments[idx].as_str();
411 let is_last = idx == segments.len() - 1;
412
413 match current {
414 Value::Object(obj) => {
415 let key_to_use = current_seg.to_string();
416 let segments_to_consume = 1;
417
418 if is_last {
419 if obj.remove(&key_to_use).is_none() {
420 return Err(DocumentError::PathNotFound { path: key_to_use });
421 }
422 Ok(())
423 } else {
424 let next_idx = idx + segments_to_consume;
425 accumulated_prefix.push(key_to_use.clone());
426 if let Some(next) = obj.get_mut(&key_to_use) {
427 unset_path_recursive(next, segments, next_idx, accumulated_prefix)
428 } else {
429 Err(DocumentError::PathNotFound {
430 path: join_path(accumulated_prefix),
431 })
432 }
433 }
434 }
435 Value::Array(arr) => {
436 if let Some(arr_idx) = array_index(current_seg)? {
437 if arr_idx >= arr.len() {
438 return Err(DocumentError::IndexOutOfBounds {
439 path: join_path(accumulated_prefix),
440 index: arr_idx,
441 len: arr.len(),
442 });
443 }
444 if is_last {
445 arr.remove(arr_idx);
446 Ok(())
447 } else {
448 accumulated_prefix.push(current_seg.to_string());
449 unset_path_recursive(&mut arr[arr_idx], segments, idx + 1, accumulated_prefix)
450 }
451 } else {
452 Err(DocumentError::UnregisteredArray {
453 path: join_path(accumulated_prefix),
454 })
455 }
456 }
457 _ => Err(DocumentError::NotTraversable {
458 path: join_path(accumulated_prefix),
459 got: current.kind_name().to_string(),
460 }),
461 }
462}
463
464#[cfg(test)]
465mod tests {
466 #![allow(
467 clippy::unwrap_used,
468 clippy::panic,
469 clippy::expect_used,
470 clippy::bool_assert_comparison
471 )]
472 use super::*;
473
474 fn make_test_object() -> Value {
475 let mut root = Value::Object(Default::default());
476 let mut imap = Value::Object(Default::default());
477 imap.as_object_mut().unwrap().insert(
478 "host".to_string(),
479 Value::String("mail.example.com".to_string()),
480 );
481 imap.as_object_mut()
482 .unwrap()
483 .insert("port".to_string(), Value::Integer(993));
484
485 root.as_object_mut()
486 .unwrap()
487 .insert("imap".to_string(), imap);
488
489 root
490 }
491
492 #[test]
493 fn test_get_path_simple() {
494 let root = make_test_object();
495 let result = get_path(&root, "imap.host", Addressing::INDEX_ONLY).unwrap();
496 assert_eq!(result.as_str().unwrap(), "mail.example.com");
497 }
498
499 #[test]
500 fn test_get_path_integer() {
501 let root = make_test_object();
502 let result = get_path(&root, "imap.port", Addressing::INDEX_ONLY).unwrap();
503 assert_eq!(result.as_integer().unwrap(), 993);
504 }
505
506 #[test]
507 fn test_set_path_new_key() {
508 let mut root = make_test_object();
509 set_path(
510 &mut root,
511 "imap.tls",
512 &Value::Bool(true),
513 Addressing::INDEX_ONLY,
514 )
515 .unwrap();
516
517 let result = get_path(&root, "imap.tls", Addressing::INDEX_ONLY).unwrap();
518 assert_eq!(result.as_bool().unwrap(), true);
519 }
520
521 #[test]
522 fn test_set_path_overwrite() {
523 let mut root = make_test_object();
524 set_path(
525 &mut root,
526 "imap.port",
527 &Value::Integer(587),
528 Addressing::INDEX_ONLY,
529 )
530 .unwrap();
531
532 let result = get_path(&root, "imap.port", Addressing::INDEX_ONLY).unwrap();
533 assert_eq!(result.as_integer().unwrap(), 587);
534 }
535
536 #[test]
537 fn test_set_path_array_value() {
538 let mut root = Value::Object(Default::default());
539 let value = Value::Array(vec![
540 Value::String("dev".to_string()),
541 Value::String("staging".to_string()),
542 ]);
543 set_path(&mut root, "tags", &value, Addressing::INDEX_ONLY).unwrap();
544
545 let result = get_path(&root, "tags", Addressing::INDEX_ONLY).unwrap();
546 let arr = result.as_array().unwrap();
547 assert_eq!(arr.len(), 2);
548 }
549
550 fn make_steps_object() -> Value {
551 let mut root = Value::Object(Default::default());
553 let mut s0 = Value::Object(Default::default());
554 s0.as_object_mut()
555 .unwrap()
556 .insert("name".to_string(), Value::String("a".to_string()));
557 s0.as_object_mut()
558 .unwrap()
559 .insert("port".to_string(), Value::Integer(1));
560 let mut s1 = Value::Object(Default::default());
561 s1.as_object_mut()
562 .unwrap()
563 .insert("name".to_string(), Value::String("b".to_string()));
564 s1.as_object_mut()
565 .unwrap()
566 .insert("port".to_string(), Value::Integer(2));
567 root.as_object_mut()
568 .unwrap()
569 .insert("steps".to_string(), Value::Array(vec![s0, s1]));
570 root
571 }
572
573 #[test]
574 fn test_get_path_numeric_index() {
575 let root = make_steps_object();
576 let name = get_path(&root, "steps.0.name", Addressing::INDEX_ONLY).unwrap();
577 assert_eq!(name.as_str().unwrap(), "a");
578 let port = get_path(&root, "steps.1.port", Addressing::INDEX_ONLY).unwrap();
579 assert_eq!(port.as_integer().unwrap(), 2);
580 }
581
582 #[test]
583 fn test_set_path_numeric_index() {
584 let mut root = make_steps_object();
585 set_path(
586 &mut root,
587 "steps.0.port",
588 &Value::Integer(99),
589 Addressing::INDEX_ONLY,
590 )
591 .unwrap();
592 let result = get_path(&root, "steps.0.port", Addressing::INDEX_ONLY).unwrap();
593 assert_eq!(result.as_integer().unwrap(), 99);
594 let other = get_path(&root, "steps.1.port", Addressing::INDEX_ONLY).unwrap();
596 assert_eq!(other.as_integer().unwrap(), 2);
597 }
598
599 #[test]
600 fn test_get_path_index_out_of_bounds() {
601 let root = make_steps_object();
602 let err = get_path(&root, "steps.5.name", Addressing::INDEX_ONLY).unwrap_err();
603 assert!(matches!(
604 err,
605 DocumentError::IndexOutOfBounds {
606 index: 5,
607 len: 2,
608 ..
609 }
610 ));
611 }
612
613 #[test]
614 fn test_remove_path_numeric_index() {
615 let mut root = make_steps_object();
616 unset_path(&mut root, "steps.0").unwrap();
617 let arr = get_path(&root, "steps", Addressing::INDEX_ONLY).unwrap();
618 let arr = arr.as_array().unwrap();
619 assert_eq!(arr.len(), 1);
620 assert_eq!(arr[0].get("name").unwrap().as_str().unwrap(), "b");
621 }
622
623 #[test]
624 fn named_root_array_keeps_a_semantic_prefix_for_nested_keyed_lists() {
625 let mut child = Value::Object(Default::default());
626 child
627 .as_object_mut()
628 .unwrap()
629 .insert("id".to_string(), Value::String("beta".to_string()));
630 child
631 .as_object_mut()
632 .unwrap()
633 .insert("value".to_string(), Value::Integer(1));
634
635 let mut parent = Value::Object(Default::default());
636 parent
637 .as_object_mut()
638 .unwrap()
639 .insert("id".to_string(), Value::String("alpha".to_string()));
640 parent
641 .as_object_mut()
642 .unwrap()
643 .insert("children".to_string(), Value::Array(vec![child]));
644
645 let mut root = Value::Array(vec![parent]);
646 let keyed = [
647 KeyedList {
648 prefix: "",
649 slug_field: "id",
650 },
651 KeyedList {
652 prefix: "alpha.children",
653 slug_field: "id",
654 },
655 ];
656 let addressing = Addressing::keyed(&keyed);
657
658 assert_eq!(
659 get_path(&root, "alpha.children.beta.value", addressing).unwrap(),
660 Value::Integer(1)
661 );
662 set_path(
663 &mut root,
664 "alpha.children.beta.value",
665 &Value::Integer(2),
666 addressing,
667 )
668 .unwrap();
669 assert_eq!(
670 get_path(&root, "alpha.children.beta.value", addressing).unwrap(),
671 Value::Integer(2)
672 );
673 }
674
675 #[test]
676 fn oversized_decimal_array_segment_never_falls_through_to_a_slug() {
677 let oversized = format!("{}0", usize::MAX);
678 let mut item = Value::Object(Default::default());
679 item.as_object_mut()
680 .unwrap()
681 .insert("id".to_string(), Value::String(oversized.clone()));
682 let root = Value::Array(vec![item]);
683 let keyed = [KeyedList {
684 prefix: "",
685 slug_field: "id",
686 }];
687
688 let error = get_path(&root, &oversized, Addressing::keyed(&keyed)).unwrap_err();
689 assert!(matches!(error, DocumentError::PathSyntax { .. }));
690 }
691
692 #[test]
693 fn dotted_key_and_nested_path_keep_distinct_keyed_registrations() {
694 let item = |field: &str, slug: &str| {
695 let mut value = Value::Object(Default::default());
696 value
697 .as_object_mut()
698 .unwrap()
699 .insert(field.to_string(), Value::String(slug.to_string()));
700 value
701 };
702
703 let mut nested = Value::Object(Default::default());
704 nested.as_object_mut().unwrap().insert(
705 "b".to_string(),
706 Value::Array(vec![item("nested_id", "nested")]),
707 );
708
709 let mut root = Value::Object(Default::default());
710 root.as_object_mut().unwrap().insert(
711 "a.b".to_string(),
712 Value::Array(vec![item("dotted_id", "dotted")]),
713 );
714 root.as_object_mut()
715 .unwrap()
716 .insert("a".to_string(), nested);
717
718 let keyed = [
719 KeyedList {
720 prefix: r"a\.b",
721 slug_field: "dotted_id",
722 },
723 KeyedList {
724 prefix: "a.b",
725 slug_field: "nested_id",
726 },
727 ];
728 let addressing = Addressing::keyed(&keyed);
729
730 assert_eq!(
731 get_path(&root, r"a\.b.dotted.dotted_id", addressing).unwrap(),
732 Value::String("dotted".to_string())
733 );
734 assert_eq!(
735 get_path(&root, "a.b.nested.nested_id", addressing).unwrap(),
736 Value::String("nested".to_string())
737 );
738 }
739}