1use std::collections::VecDeque;
2use std::ops::Range;
3use std::sync::Arc;
4
5use address::{find_more_addresses, Address};
6use serde::de::DeserializeOwned;
7use serde_yaml::Value;
8
9use crate::address::LocationFragment;
10
11mod address;
12pub use gat_lending_iterator::LendingIterator;
13
14#[macro_export]
15macro_rules! step {
16 ("*") => {
17 $crate::Step::All
18 };
19 ($a:expr) => {
20 $crate::Step::from($a)
21 };
22}
23
24#[macro_export]
25macro_rules! query {
26 ($($a:expr $(,)?)+) => {{
27 let mut the_query = $crate::Query::default();
28 $(
29 the_query.steps.push($crate::step!($a));
30 )+
31 the_query
32 }};
33}
34
35#[macro_export]
36macro_rules! r#where {
37 ($field:literal => $body:expr) => {{
38 $crate::Step::filter($field, $body)
39 }};
40}
41
42#[macro_export]
43macro_rules! sub {
44 ($field:literal => $sub_query:expr) => {{
45 $crate::Step::sub_query($field, $sub_query)
46 }};
47}
48
49#[macro_export]
50macro_rules! and {
51 ($($a:expr $(,)?)+) => {{
52 let mut arms: Vec<$crate::Query> = Vec::new();
53 $(
54 arms.push($a.into());
55 )+
56 $crate::Step::And(arms)
57 }};
58}
59
60#[macro_export]
61macro_rules! or {
62 ($($a:expr $(,)?)+) => {{
63 let mut arms: Vec<$crate::Query> = Vec::new();
64 $(
65 arms.push($a.into());
66 )+
67 $crate::Step::Or(arms)
68 }};
69}
70
71#[macro_export]
72macro_rules! branch {
73 ($($a:expr $(,)?)+) => {{
74 #[allow(clippy::vec_init_then_push)]
75 let mut arms: Vec<$crate::Query> = Vec::new();
76 $(
77 arms.push($a.into());
78 )+
79 $crate::Step::Branch(arms)
80 }};
81}
82
83#[macro_export]
84macro_rules! missing {
85 (parent => $field:literal, $sub_query:expr) => {{
86 $crate::Step::Missing {
87 parent: $field.to_string(),
88 sub_query: $sub_query,
89 }
90 }};
91}
92
93#[derive(Clone, Debug, Default)]
94pub struct Query {
95 pub steps: Vec<Step>,
96}
97
98impl Query {
99 fn take_step(&self) -> Option<(Step, Query)> {
100 if self.steps.is_empty() {
101 return None;
102 };
103 let mut others = self.steps.clone();
104 let next = others.remove(0);
105 Some((next, Query { steps: others }))
106 }
107}
108
109#[derive(Clone)]
110pub enum Step {
111 Recursive,
112 Field(String),
113 And(Vec<Query>),
114 Or(Vec<Query>),
115 Branch(Vec<Query>),
116 At(usize),
117 All,
118 Filter(String, Arc<dyn Fn(&serde_yaml::Value) -> bool>),
119 SubQuery(String, Query),
120 Range(Range<usize>),
121 Missing { parent: String, sub_query: Query },
122}
123
124impl std::fmt::Display for Step {
125 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 std::fmt::Debug::fmt(&self, f)
127 }
128}
129
130impl<S: Into<Step>> From<S> for Query {
131 fn from(value: S) -> Self {
132 let step = value.into();
133 Query { steps: vec![step] }
134 }
135}
136
137impl From<Vec<Step>> for Query {
138 fn from(steps: Vec<Step>) -> Self {
139 Query { steps }
140 }
141}
142
143impl From<String> for Step {
144 fn from(value: String) -> Self {
145 match value.as_str() {
146 "*" => Step::All,
147 "..." => Step::Recursive,
148 _ => Step::Field(value),
149 }
150 }
151}
152
153impl From<&str> for Step {
154 fn from(value: &str) -> Self {
155 let val = value.to_string();
156 Step::from(val)
157 }
158}
159
160impl From<usize> for Step {
161 fn from(value: usize) -> Self {
162 Step::At(value)
163 }
164}
165
166impl From<Range<usize>> for Step {
167 fn from(value: Range<usize>) -> Self {
168 Step::Range(value)
169 }
170}
171
172impl Step {
173 fn name(&self) -> &'static str {
174 match self {
175 Step::Recursive => "recursive",
176 Step::Field(_) => "field",
177 Step::And(_) => "and",
178 Step::Or(_) => "or",
179 Step::Branch(_) => "branch",
180 Step::At(_) => "at",
181 Step::Range(_) => "range",
182 Step::All => "all",
183 Step::Filter(_, _) => "filter",
184 Step::SubQuery(_, _) => "sub_query",
185 Step::Missing { .. } => "missing",
186 }
187 }
188
189 pub fn at(value: usize) -> Step {
190 Step::At(value)
191 }
192
193 pub fn range(value: Range<usize>) -> Step {
194 Step::Range(value)
195 }
196
197 pub fn field<S: Into<String>>(value: S) -> Step {
198 Step::Field(value.into())
199 }
200
201 pub fn filter<D: DeserializeOwned, F: Fn(D) -> bool + 'static>(
202 field: impl Into<String>,
203 fun: F,
204 ) -> Step {
205 Step::Filter(
206 field.into(),
207 Arc::new(move |value: &serde_yaml::Value| {
208 let Ok(actual) = serde_yaml::from_value(value.clone()) else {
209 return false;
210 };
211 fun(actual)
212 }),
213 )
214 }
215
216 pub fn sub_query(field: impl Into<String>, query: Query) -> Step {
217 Step::SubQuery(field.into(), query)
218 }
219}
220
221impl std::fmt::Debug for Step {
222 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223 match self {
224 Self::Field(arg0) => f.debug_tuple("Field").field(arg0).finish(),
225 Self::And(arg0) => f.debug_tuple("And").field(arg0).finish(),
226 Self::Branch(arg0) => f.debug_tuple("Branch").field(arg0).finish(),
227 Self::Or(arg0) => f.debug_tuple("Or").field(arg0).finish(),
228 Self::At(arg0) => f.debug_tuple("Index").field(arg0).finish(),
229 Self::All => write!(f, "All"),
230 Self::Range(r) => f.debug_tuple("Range").field(r).finish(),
231 Self::Filter(arg0, _arg1) => f.debug_tuple("Filter").field(arg0).finish(),
232 Self::SubQuery(arg, _arg2) => f.debug_tuple("SubQuery").field(arg).finish(),
233 Self::Recursive => write!(f, "Recursive"),
234 Self::Missing { parent, sub_query } => f
235 .debug_tuple("Missing")
236 .field(parent)
237 .field(sub_query)
238 .finish(),
239 }
240 }
241}
242
243#[derive(Debug)]
244pub(crate) struct Candidate {
245 pub(crate) starting_point: Address,
246 pub(crate) remaining_query: Query,
247 pub(crate) search_kind: SearchKind,
248}
249
250#[derive(Debug, Default, Clone)]
252pub(crate) enum SearchKind {
253 #[default]
255 Normal,
256 Recursive(Query),
258}
259
260pub struct ManyResults<'input> {
261 candidates: VecDeque<Candidate>,
262 root_node: &'input Value,
263}
264
265#[derive(Debug)]
269pub struct Context {
270 pub path: Path,
271}
272
273#[derive(Debug)]
274pub struct Path(Vec<LocationFragment>);
275
276impl Path {
277 pub fn as_jq(&self) -> String {
280 let mut buf = "".to_string();
281 for s in &self.0 {
282 match s {
283 LocationFragment::Field(f) => {
284 buf.push('.');
285 buf.push_str(f);
286 }
287 LocationFragment::Index(at) => {
288 buf.push('[');
289 buf.push_str(&at.to_string());
290 buf.push(']');
291 }
292 }
293 }
294 buf
295 }
296}
297
298impl From<Address> for Path {
299 fn from(value: Address) -> Self {
300 Self(value.0)
301 }
302}
303
304impl<'input> Iterator for ManyResults<'input> {
305 type Item = (Context, &'input Value);
306
307 fn next(&mut self) -> Option<Self::Item> {
308 while let Some(path_to_explore) = self.candidates.pop_front() {
309 tracing::trace!(
310 "Next candidate to explore: '{}' with query: '{:?}'",
311 path_to_explore.starting_point,
312 path_to_explore.remaining_query,
313 );
314 let found = find_more_addresses(path_to_explore, self.root_node);
315 tracing::trace!("Adding to the list of candidates: {:?}", found.branching);
316 self.candidates.extend(found.branching);
317
318 if let Some(address) = found.hit {
319 tracing::trace!("We got a hit: {address}");
320 let node = get(self.root_node, &address);
321 if let Some(v) = node {
322 return Some((
323 Context {
324 path: address.into(),
325 },
326 v,
327 ));
328 }
329 };
330 }
331
332 None
333 }
334}
335
336fn get_mut<'a>(node: &'a mut Value, adr: &Address) -> Option<&'a mut Value> {
337 let mut current_node = Some(node);
338 for fragment in &adr.0 {
339 let actual_node = current_node?;
340 match fragment {
341 LocationFragment::Field(f) if f == "." => current_node = Some(actual_node),
342 LocationFragment::Field(f) => current_node = actual_node.get_mut(f),
343 LocationFragment::Index(i) => current_node = actual_node.get_mut(i),
344 }
345 }
346
347 current_node
348}
349
350fn get<'a>(node: &'a Value, adr: &Address) -> Option<&'a Value> {
351 let mut current_node = Some(node);
352 for fragment in &adr.0 {
353 let actual_node = current_node?;
354 match fragment {
355 LocationFragment::Field(f) if f == "." => current_node = Some(actual_node),
356 LocationFragment::Field(f) => current_node = actual_node.get(f),
357 LocationFragment::Index(i) => current_node = actual_node.get(i),
358 }
359 }
360
361 current_node
362}
363
364pub struct ManyMutResults<'input> {
365 candidates: VecDeque<Candidate>,
366 found_addresses: VecDeque<Address>,
368 root_node: &'input mut Value,
369}
370
371impl<'input> gat_lending_iterator::LendingIterator for ManyMutResults<'input> {
372 type Item<'a> = (Context, &'a mut Value)
373 where
374 Self: 'a;
375
376 fn next(&mut self) -> Option<Self::Item<'_>> {
377 while let Some(path_to_explore) = self.candidates.pop_front() {
378 tracing::trace!("Looking at {}", path_to_explore.starting_point);
379 let found = find_more_addresses(path_to_explore, self.root_node);
380 self.candidates.extend(found.branching);
381
382 if let Some(address) = found.hit {
383 tracing::trace!("We got a hit: {address}");
384 self.found_addresses.push_back(address);
385 }
386 }
387
388 while let Some(address) = self.found_addresses.pop_front() {
389 let self_ = unsafe { &mut *(self as *mut Self) };
391 if let Some(found_node) = get_mut(self_.root_node, &address) {
392 return Some((
393 Context {
394 path: address.into(),
395 },
396 found_node,
397 ));
398 }
399 }
400 None
401 }
402}
403
404fn value_name(value: &Value) -> &'static str {
405 match value {
406 Value::Null => "null",
407 Value::Bool(_) => "bool",
408 Value::Number(_) => "number",
409 Value::String(_) => "string",
410 Value::Sequence(_) => "sequence",
411 Value::Mapping(_) => "mapping",
412 Value::Tagged(_) => "tagged",
413 }
414}
415
416pub fn navigate_iter(root_node: &Value, query: Query) -> ManyResults<'_> {
417 ManyResults {
418 root_node,
419 candidates: VecDeque::from([Candidate {
420 starting_point: Address::default(),
421 remaining_query: query,
422 search_kind: SearchKind::Normal,
423 }]),
424 }
425}
426
427pub fn navigate_iter_mut(input: &mut Value, query: Query) -> ManyMutResults<'_> {
428 ManyMutResults {
429 root_node: input,
430 candidates: VecDeque::from_iter([Candidate {
431 starting_point: Address::default(),
432 remaining_query: query,
433 search_kind: SearchKind::Normal,
434 }]),
435 found_addresses: VecDeque::default(),
436 }
437}
438
439#[cfg(test)]
440mod tests {
441 use gat_lending_iterator::LendingIterator;
442 use serde_yaml::Mapping;
443 use std::assert_eq;
444 use tracing_test::traced_test;
445
446 use indoc::indoc;
447
448 use super::*;
449
450 #[test]
451 fn it_works() {
452 let raw = indoc! {r#"
453 people:
454 - name: Felipe
455 surname: Sere
456 age: 32
457 address:
458 street: Foo
459 postcode: 12345
460 city: Legoland
461 hobbies:
462 - tennis
463 - computer
464 - name: Charlotte
465 surname: Fereday
466 age: 31
467 address:
468 street: Bar
469 postcode: 12345
470 city: Legoland
471 sports:
472 - swimming
473 - yoga
474 "#};
475 let yaml: Value = serde_yaml::from_str(raw).unwrap();
476
477 let first_persons_name = query!["people", 0, "name",];
478
479 let (ctx, felipe) = navigate_iter(&yaml, first_persons_name).next().unwrap();
480 assert_eq!(felipe, &Value::String("Felipe".into()));
481 assert_eq!(&ctx.path.as_jq(), ".people[0].name");
482
483 let yoga = query!("people", "*", "sports", 1,);
484
485 let yoga: Vec<_> = navigate_iter(&yaml, yoga)
486 .map(|(ctx, val)| (ctx.path.as_jq(), val))
487 .collect();
488 assert_eq!(
489 yoga,
490 vec![(
491 ".people[1].sports[1]".to_string(),
492 &Value::String("yoga".to_string())
493 )]
494 );
495 }
496
497 #[test]
498 fn range_of_indizes() {
499 let raw = indoc! {r#"
500 people:
501 - name: Felipe
502 - name: Charlotte
503 - name: Alice
504 - name: Bob
505 - name: Malory
506 "#};
507
508 let query = query!("people", 1..4, "name",);
509
510 let yaml: Value = serde_yaml::from_str(raw).unwrap();
511
512 let names: Vec<_> = navigate_iter(&yaml, query)
513 .filter_map(|(_ctx, v)| v.as_str())
514 .collect();
515
516 assert_eq!(names, vec!["Charlotte", "Alice", "Bob"]);
517 }
518
519 #[test]
520 fn filter_clause() {
521 let raw = indoc! {r#"
522 people:
523 - name: Felipe
524 surname: Sere
525 age: 32
526 address:
527 street: Foo
528 postcode: 12345
529 city: Legoland
530 hobbies:
531 - tennis
532 - computer
533 - name: Charlotte
534 surname: Fereday
535 age: 31
536 address:
537 street: Bar
538 postcode: 12345
539 city: Legoland
540 sports:
541 - swimming
542 - yoga
543 "#};
544 let yaml: Value = serde_yaml::from_str(raw).unwrap();
545
546 let names_of_people_aged_over_31 =
547 query!["people", r#where!("age" => |age: u32| age > 30), "name",];
548
549 let (_, felipe) = navigate_iter(&yaml, names_of_people_aged_over_31)
550 .next()
551 .unwrap();
552 assert_eq!(felipe, &Value::String("Felipe".into()));
553 }
554
555 #[test]
556 fn find_nothing() {
557 let raw = indoc! {r#"
558 people:
559 - name: Felipe
560 surname: Sere
561 age: 32
562 address:
563 street: Foo
564 postcode: 12345
565 city: Legoland
566 hobbies:
567 - tennis
568 - computer
569 - name: Charlotte
570 surname: Fereday
571 age: 31
572 address:
573 street: Bar
574 postcode: 12345
575 city: Legoland
576 sports:
577 - swimming
578 - yoga
579 "#};
580 let yaml: Value = serde_yaml::from_str(raw).unwrap();
581
582 let missing_property = query!["people", "*", "car"];
583
584 let no_one = navigate_iter(&yaml, missing_property).next();
585 assert!(no_one.is_none());
586
587 let filter_does_not_match = query!["people", r#where!("age" => |age: u32| age < 4),];
588
589 let no_one = navigate_iter(&yaml, filter_does_not_match).next();
590 assert!(no_one.is_none());
591 }
592
593 #[test]
594 fn filter_on_mapping() {
595 let raw = indoc! {r#"
596 people:
597 - name: Felipe
598 surname: Sere
599 age: 32
600 address:
601 street: Foo
602 postcode: 12345
603 city: Legoland
604 hobbies:
605 - tennis
606 - computer
607 - name: Charlotte
608 surname: Fereday
609 age: 31
610 address:
611 street: Bar
612 postcode: 12345
613 city: Legoland
614 sports:
615 - swimming
616 - yoga
617 "#};
618 let yaml: Value = serde_yaml::from_str(raw).unwrap();
619
620 let finds_address = query![
621 "people",
622 "*",
623 "address",
624 r#where!("street" => |street: String| street == "Foo")
625 ];
626
627 let felipe: Vec<_> = navigate_iter(&yaml, finds_address).collect();
628 assert_eq!(felipe.len(), 1);
629 }
630
631 #[test]
632 fn sub_query_for_filter() {
633 let raw = indoc! {r#"
634 people:
635 - name: Felipe
636 surname: Sere
637 age: 32
638 address:
639 street: Foo
640 postcode: 12345
641 city: Legoland
642 hobbies:
643 - tennis
644 - computer
645 - name: Charlotte
646 surname: Fereday
647 age: 31
648 address:
649 street: Bar
650 postcode: 12345
651 city: Legoland
652 sports:
653 - swimming
654 - yoga
655 "#};
656 let yaml: Value = serde_yaml::from_str(raw).unwrap();
657
658 let live_on_foo_street = query![
659 "address",
660 r#where!("street" => |street: String| street == "Foo"),
661 ];
662
663 let finds_address = query!["people", "*", sub!("." => live_on_foo_street),];
664
665 let felipe: Vec<_> = navigate_iter(&yaml, finds_address).collect();
666 assert_eq!(felipe.len(), 1);
667 }
668
669 #[test]
670 fn logical_and_connecting_two_subqueries() {
671 let raw = indoc! {r#"
672 people:
673 - name: Felipe
674 surname: Sere
675 age: 32
676 address:
677 street: Foo
678 postcode: 12345
679 city: Legoland
680 hobbies:
681 - tennis
682 - computer
683 - name: Charlotte
684 surname: Fereday
685 age: 31
686 address:
687 street: Bar
688 postcode: 12345
689 city: Legoland
690 sports:
691 - swimming
692 - yoga
693 "#};
694 let yaml: Value = serde_yaml::from_str(raw).unwrap();
695
696 let age_of_people_living_on_foo_street_playing_tennis = query![
697 "people",
698 "*",
699 and![
700 query!(
701 "address",
702 r#where!("street" => |name: String| name == "Foo")
703 ),
704 query!(
705 "hobbies",
706 r#where!("." => |hobby: String| hobby == "tennis")
707 ),
708 ],
709 "age"
710 ];
711
712 let felipe: Vec<_> =
713 navigate_iter(&yaml, age_of_people_living_on_foo_street_playing_tennis)
714 .map(|(_ctx, v)| v)
715 .collect();
716 assert_eq!(felipe, vec![&serde_yaml::Value::Number(32.into())]);
717 }
718
719 #[test]
720 fn logical_or_for_alternatives() {
721 let raw = indoc! {r#"
722 people:
723 - name: Felipe
724 surname: Sere
725 age: 32
726 address:
727 street: Foo
728 postcode: 12345
729 city: Legoland
730 hobbies:
731 - tennis
732 - computer
733 - name: Charlotte
734 surname: Fereday
735 age: 31
736 address:
737 street: Bar
738 postcode: 12345
739 city: Legoland
740 sports:
741 - swimming
742 - yoga
743 "#};
744 let yaml: Value = serde_yaml::from_str(raw).unwrap();
745
746 let computer_or_swimming = query![
747 "people",
748 "*",
749 or![
750 query!(
751 "hobbies",
752 r#where!("." => |name: String| name == "computer")
753 ),
754 query!(
755 "sports",
756 r#where!("." => |sport: String| sport == "swimming")
757 ),
758 ],
759 "age"
760 ];
761
762 let both: Vec<_> = navigate_iter(&yaml, computer_or_swimming)
763 .map(|(_ctx, v)| v)
764 .collect();
765 assert_eq!(
766 both,
767 vec![
768 &serde_yaml::Value::Number(32.into()),
769 &serde_yaml::Value::Number(31.into())
770 ]
771 );
772 }
773
774 #[test]
775 fn modify_found_values() {
776 let raw = indoc! {r#"
777 people:
778 - name: Felipe
779 surname: Sere
780 age: 32
781 address:
782 street: Foo
783 postcode: 12345
784 city: Legoland
785 hobbies:
786 - tennis
787 - computer
788 - name: Charlotte
789 surname: Fereday
790 age: 31
791 address:
792 street: Bar
793 postcode: 12345
794 city: Legoland
795 sports:
796 - swimming
797 - yoga
798 "#};
799 let mut yaml: Value = serde_yaml::from_str(raw).unwrap();
800
801 let felipes_name = query![
802 "people",
803 "*",
804 r#where!("name" => |name: String| name == "Felipe"),
805 ];
806
807 {
808 let mut iter = navigate_iter_mut(&mut yaml, felipes_name);
809
810 while let Some((_, name)) = iter.next() {
811 *name = serde_yaml::Value::from("epileF");
812 }
813 }
814
815 let modified = serde_yaml::to_string(&yaml).unwrap();
816
817 expect_test::expect![[r#"
818 people:
819 - name: epileF
820 surname: Sere
821 age: 32
822 address:
823 street: Foo
824 postcode: 12345
825 city: Legoland
826 hobbies:
827 - tennis
828 - computer
829 - name: Charlotte
830 surname: Fereday
831 age: 31
832 address:
833 street: Bar
834 postcode: 12345
835 city: Legoland
836 sports:
837 - swimming
838 - yoga
839 "#]]
840 .assert_eq(&modified);
841
842 let hobbies_and_sport = query!["people", "*", branch!["hobbies", "sports"], "*"];
843
844 let all: Vec<_> = navigate_iter(&yaml, hobbies_and_sport.clone()).collect();
845 assert_eq!(all.len(), 4);
846
847 {
848 let mut iter = navigate_iter_mut(&mut yaml, hobbies_and_sport.clone());
849 while let Some((_ctx, hobby_or_sport)) = iter.next() {
850 *hobby_or_sport = serde_yaml::Value::from("F1");
851 }
852 }
853
854 let modified = serde_yaml::to_string(&yaml).unwrap();
855
856 expect_test::expect![[r#"
857 people:
858 - name: epileF
859 surname: Sere
860 age: 32
861 address:
862 street: Foo
863 postcode: 12345
864 city: Legoland
865 hobbies:
866 - F1
867 - F1
868 - name: Charlotte
869 surname: Fereday
870 age: 31
871 address:
872 street: Bar
873 postcode: 12345
874 city: Legoland
875 sports:
876 - F1
877 - F1
878 "#]]
879 .assert_eq(&modified);
880 }
881
882 #[test]
883 #[traced_test]
884 fn recursive_search() {
885 let mut yaml: serde_yaml::Value = serde_yaml::from_str(indoc! {r#"
886 kind: Foo
887 apiVersion: v1
888 metadata:
889 labels:
890 alpha: 1
891 annotations:
892 bravo: 2
893 spec:
894 template:
895 metadata:
896 labels:
897 charlie: 3
898 annotations:
899 delta: 4
900 foo:
901 bar:
902 labels:
903 echo: 5
904 annotations:
905 foxtrott: 6
906 "#})
907 .unwrap();
908
909 let annotations_everywhere = query!["...", "annotations"];
910
911 let all: Vec<_> = navigate_iter(&yaml, annotations_everywhere.clone()).collect();
912 assert_eq!(3, all.len());
913
914 {
915 let mut iter = navigate_iter_mut(&mut yaml, annotations_everywhere);
916 while let Some((_ctx, annotations)) = iter.next() {
917 if let Some(m) = annotations.as_mapping_mut() {
918 m.insert("new".into(), 100.into());
919 };
920 }
921 }
922
923 expect_test::expect![[r#"
924 kind: Foo
925 apiVersion: v1
926 metadata:
927 labels:
928 alpha: 1
929 annotations:
930 bravo: 2
931 new: 100
932 spec:
933 template:
934 metadata:
935 labels:
936 charlie: 3
937 annotations:
938 delta: 4
939 new: 100
940 foo:
941 bar:
942 labels:
943 echo: 5
944 annotations:
945 foxtrott: 6
946 new: 100
947 "#]]
948 .assert_eq(&serde_yaml::to_string(&yaml).unwrap());
949
950 let labels: Vec<_> = navigate_iter(&yaml, query!["spec", "...", "labels"]).collect();
951 assert_eq!(2, labels.len());
952 }
953
954 #[test]
955 #[traced_test]
956 fn negative_lookup() {
957 let yaml: serde_yaml::Value = serde_yaml::from_str(indoc! {r#"
958 kind: Foo
959 apiVersion: v1
960 metadata:
961 labels:
962 alpha: 1
963 spec:
964 template:
965 metadata:
966 foo:
967 bar:
968 labels:
969 echo: 5
970 annotations:
971 foxtrott: 6
972 "#})
973 .unwrap();
974
975 let metadata_without_annotations =
976 query!["...", missing![parent => "metadata", query!["annotations"]]];
977
978 let parents: Vec<_> = navigate_iter(&yaml, metadata_without_annotations)
979 .map(|(ctx, _)| ctx.path.as_jq())
980 .collect();
981 assert_eq!(parents, vec![".metadata", ".spec.template.metadata"]);
982
983 let labels_of_metadata_without_annotations = query![
984 "...",
985 missing![parent => "metadata", query!["annotations"]],
986 "labels"
987 ];
988
989 let labels: Vec<_> = navigate_iter(&yaml, labels_of_metadata_without_annotations)
990 .map(|(ctx, val)| (ctx.path.as_jq(), val))
991 .collect();
992 assert_eq!(1, labels.len());
993 assert_eq!(
994 labels[0],
995 (
996 ".metadata.labels".to_string(),
997 &serde_yaml::Value::Mapping(Mapping::from_iter([("alpha".into(), 1.into(),)]))
998 )
999 );
1000 }
1001}