yaml_navigator/
lib.rs

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/// The kind of search process we are in
251#[derive(Debug, Default, Clone)]
252pub(crate) enum SearchKind {
253    /// Just searching from the root of the tree downwards
254    #[default]
255    Normal,
256    /// A recursive search where the `Query` can match from any point downwards
257    Recursive(Query),
258}
259
260pub struct ManyResults<'input> {
261    candidates: VecDeque<Candidate>,
262    root_node: &'input Value,
263}
264
265/// A bit `Context` that goes along with
266/// the `serde_yaml::Value` that are yielded
267/// when iterating over a document.
268#[derive(Debug)]
269pub struct Context {
270    pub path: Path,
271}
272
273#[derive(Debug)]
274pub struct Path(Vec<LocationFragment>);
275
276impl Path {
277    /// Represents the path as a jq-ish
278    /// string like `.foo[12].bar`
279    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    // the addresses here have to be relative to the root
367    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            // SAFETY: see https://docs.rs/polonius-the-crab/0.3.1/polonius_the_crab/#the-arcanemagic
390            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}