dcbor_pattern/pattern/structure/
map_pattern.rs

1use std::ops::RangeBounds;
2
3use dcbor::prelude::*;
4
5use crate::{
6    Interval,
7    pattern::{Matcher, Path, Pattern, vm::Instr},
8};
9
10/// Pattern for matching CBOR map structures.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum MapPattern {
13    /// Matches any map.
14    Any,
15    /// Matches maps with multiple key-value constraints that must all be
16    /// satisfied.
17    Constraints(Vec<(Pattern, Pattern)>),
18    /// Matches maps with number of key-value pairs in the given interval.
19    Length(Interval),
20}
21
22impl MapPattern {
23    /// Creates a new `MapPattern` that matches any map.
24    pub fn any() -> Self { MapPattern::Any }
25
26    /// Creates a new `MapPattern` that matches maps with multiple key-value
27    /// constraints that must all be satisfied.
28    pub fn with_key_value_constraints(
29        constraints: Vec<(Pattern, Pattern)>,
30    ) -> Self {
31        MapPattern::Constraints(constraints)
32    }
33
34    /// Creates a new `MapPattern` that matches maps with a specific number of
35    /// key-value pairs.
36    pub fn with_length(length: usize) -> Self {
37        MapPattern::Length(Interval::new(length..=length))
38    }
39
40    /// Creates a new `MapPattern` that matches maps with number of key-value
41    /// pairs in the given range.
42    pub fn with_length_range<R: RangeBounds<usize>>(range: R) -> Self {
43        MapPattern::Length(Interval::new(range))
44    }
45
46    /// Creates a new `MapPattern` that matches maps with number of key-value
47    /// pairs in the given range.
48    pub fn with_length_interval(interval: Interval) -> Self {
49        MapPattern::Length(interval)
50    }
51}
52
53impl Matcher for MapPattern {
54    fn paths(&self, haystack: &CBOR) -> Vec<Path> {
55        // First check if this is a map
56        match haystack.as_case() {
57            CBORCase::Map(map) => {
58                match self {
59                    MapPattern::Any => {
60                        // Match any map - return the map itself
61                        vec![vec![haystack.clone()]]
62                    }
63                    MapPattern::Constraints(constraints) => {
64                        // All constraints must be satisfied
65                        for (key_pattern, value_pattern) in constraints {
66                            let mut found_match = false;
67                            for (key, value) in map.iter() {
68                                if key_pattern.matches(key)
69                                    && value_pattern.matches(value)
70                                {
71                                    found_match = true;
72                                    break;
73                                }
74                            }
75                            if !found_match {
76                                return vec![];
77                            }
78                        }
79                        vec![vec![haystack.clone()]]
80                    }
81                    MapPattern::Length(interval) => {
82                        if interval.contains(map.len()) {
83                            vec![vec![haystack.clone()]]
84                        } else {
85                            vec![]
86                        }
87                    }
88                }
89            }
90            _ => {
91                // Not a map, no match
92                vec![]
93            }
94        }
95    }
96
97    fn compile(
98        &self,
99        code: &mut Vec<Instr>,
100        literals: &mut Vec<Pattern>,
101        captures: &mut Vec<String>,
102    ) {
103        // Collect capture names from inner patterns
104        self.collect_capture_names(captures);
105
106        let idx = literals.len();
107        literals.push(Pattern::Structure(
108            crate::pattern::StructurePattern::Map(self.clone()),
109        ));
110        code.push(Instr::MatchStructure(idx));
111    }
112
113    fn collect_capture_names(&self, names: &mut Vec<String>) {
114        match self {
115            MapPattern::Any => {
116                // No captures in a simple any pattern
117            }
118            MapPattern::Constraints(constraints) => {
119                // Collect captures from all key and value patterns
120                for (key_pattern, value_pattern) in constraints {
121                    key_pattern.collect_capture_names(names);
122                    value_pattern.collect_capture_names(names);
123                }
124            }
125            MapPattern::Length(_) => {
126                // No captures in length interval patterns
127            }
128        }
129    }
130
131    fn paths_with_captures(
132        &self,
133        haystack: &CBOR,
134    ) -> (Vec<Path>, std::collections::HashMap<String, Vec<Path>>) {
135        // Check if this CBOR value is a map
136        let CBORCase::Map(map) = haystack.as_case() else {
137            return (vec![], std::collections::HashMap::new());
138        };
139
140        match self {
141            MapPattern::Any => {
142                // Matches any map, no captures
143                (
144                    vec![vec![haystack.clone()]],
145                    std::collections::HashMap::new(),
146                )
147            }
148            MapPattern::Constraints(constraints) => {
149                // Match if all key-value constraints are satisfied
150                let mut all_captures = std::collections::HashMap::new();
151                let mut all_constraints_satisfied = true;
152
153                for (key_pattern, value_pattern) in constraints {
154                    let mut constraint_satisfied = false;
155
156                    for (key, value) in map.iter() {
157                        let (key_paths, key_captures) =
158                            key_pattern.paths_with_captures(key);
159                        let (value_paths, value_captures) =
160                            value_pattern.paths_with_captures(value);
161
162                        if !key_paths.is_empty() && !value_paths.is_empty() {
163                            constraint_satisfied = true;
164
165                            // Merge key captures
166                            for (name, capture_paths) in key_captures {
167                                let updated_paths: Vec<Path> = capture_paths
168                                    .iter()
169                                    .map(|_capture_path| {
170                                        vec![haystack.clone(), key.clone()]
171                                    })
172                                    .collect();
173                                all_captures
174                                    .entry(name)
175                                    .or_insert_with(Vec::new)
176                                    .extend(updated_paths);
177                            }
178
179                            // Merge value captures
180                            for (name, capture_paths) in value_captures {
181                                let updated_paths: Vec<Path> = capture_paths
182                                    .iter()
183                                    .map(|_capture_path| {
184                                        vec![haystack.clone(), value.clone()]
185                                    })
186                                    .collect();
187                                all_captures
188                                    .entry(name)
189                                    .or_insert_with(Vec::new)
190                                    .extend(updated_paths);
191                            }
192                            break; // Found a matching key-value pair for this constraint
193                        }
194                    }
195
196                    if !constraint_satisfied {
197                        all_constraints_satisfied = false;
198                        break;
199                    }
200                }
201
202                if all_constraints_satisfied {
203                    (vec![vec![haystack.clone()]], all_captures)
204                } else {
205                    (vec![], all_captures)
206                }
207            }
208            _ => {
209                // For other variants, fall back to basic paths without captures
210                (self.paths(haystack), std::collections::HashMap::new())
211            }
212        }
213    }
214}
215
216impl std::fmt::Display for MapPattern {
217    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218        match self {
219            MapPattern::Any => write!(f, "map"),
220            MapPattern::Constraints(constraints) => {
221                write!(f, "{{")?;
222                for (i, (key_pattern, value_pattern)) in
223                    constraints.iter().enumerate()
224                {
225                    if i > 0 {
226                        write!(f, ", ")?;
227                    }
228                    write!(f, "{}: {}", key_pattern, value_pattern)?;
229                }
230                write!(f, "}}")
231            }
232            MapPattern::Length(interval) => {
233                write!(f, "{{{}}}", interval)
234            }
235        }
236    }
237}