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