cedar_policy_core/ast/
pattern.rs1use std::sync::Arc;
18
19use serde::{Deserialize, Serialize};
20
21#[derive(Deserialize, Serialize, Hash, Debug, Clone, Copy, PartialEq, Eq)]
23#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
24pub enum PatternElem {
25 Char(char),
27 Wildcard,
29}
30
31#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
34#[serde(transparent)]
35pub struct Pattern {
36 elems: Arc<Vec<PatternElem>>,
38}
39
40impl Pattern {
41 pub fn new(elems: impl IntoIterator<Item = PatternElem>) -> Self {
43 Self {
44 elems: Arc::new(elems.into_iter().collect()),
45 }
46 }
47
48 pub fn get_elems(&self) -> &[PatternElem] {
50 &self.elems
51 }
52
53 pub fn iter(&self) -> impl Iterator<Item = &PatternElem> {
55 self.elems.iter()
56 }
57}
58
59impl std::fmt::Display for Pattern {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 for pc in self.elems.as_ref() {
62 match pc {
63 PatternElem::Char('*') => write!(f, r#"\*"#)?,
64 PatternElem::Char(c) => write!(f, "{}", c.escape_debug())?,
65 PatternElem::Wildcard => write!(f, r#"*"#)?,
66 }
67 }
68 Ok(())
69 }
70}
71
72impl PatternElem {
73 fn match_char(&self, text_char: &char) -> bool {
74 match self {
75 PatternElem::Char(c) => text_char == c,
76 PatternElem::Wildcard => true,
77 }
78 }
79 fn is_wildcard(&self) -> bool {
80 matches!(self, PatternElem::Wildcard)
81 }
82}
83
84impl Pattern {
85 pub fn wildcard_match(&self, text: &str) -> bool {
87 let pattern = self.get_elems();
88 if pattern.is_empty() {
89 return text.is_empty();
90 }
91
92 let text: Vec<char> = text.chars().collect();
101
102 let mut i: usize = 0; let mut j: usize = 0; let mut star_idx: usize = 0; let mut tmp_idx: usize = 0; let mut contains_star: bool = false; let text_len = text.len();
109 let pattern_len = pattern.len();
110
111 while i < text_len && (!contains_star || star_idx != pattern_len - 1) {
112 #[allow(clippy::indexing_slicing)]
114 if j < pattern_len && pattern[j].is_wildcard() {
115 contains_star = true;
116 star_idx = j;
117 tmp_idx = i;
118 j += 1;
119 } else if j < pattern_len && pattern[j].match_char(&text[i]) {
120 i += 1;
121 j += 1;
122 } else if contains_star {
123 j = star_idx + 1;
124 i = tmp_idx + 1;
125 tmp_idx = i;
126 } else {
127 return false;
128 }
129 }
130
131 #[allow(clippy::indexing_slicing)]
133 while j < pattern_len && pattern[j].is_wildcard() {
134 j += 1;
135 }
136
137 j == pattern_len
138 }
139}
140
141#[cfg(test)]
142pub mod test {
143 use super::*;
144
145 impl std::ops::Add for Pattern {
146 type Output = Pattern;
147 fn add(self, rhs: Self) -> Self::Output {
148 let elems = [self.get_elems(), rhs.get_elems()].concat();
149 Pattern::new(elems)
150 }
151 }
152
153 fn string_map(text: &str) -> Pattern {
155 Pattern::new(text.chars().map(PatternElem::Char))
156 }
157
158 fn star() -> Pattern {
160 Pattern::new(vec![PatternElem::Wildcard])
161 }
162
163 fn empty() -> Pattern {
165 Pattern::new(vec![])
166 }
167
168 #[test]
169 fn test_wildcard_match_basic() {
170 assert!((string_map("foo") + star()).wildcard_match("foo bar"));
172 assert!((star() + string_map("bar")).wildcard_match("foo bar"));
173 assert!((star() + string_map("o b") + star()).wildcard_match("foo bar"));
174 assert!((string_map("f") + star() + string_map(" bar")).wildcard_match("foo bar"));
175 assert!((string_map("f") + star() + star() + string_map("r")).wildcard_match("foo bar"));
176 assert!((star() + string_map("f") + star() + star() + star()).wildcard_match("foo bar"));
177
178 assert!(!(star() + string_map("foo")).wildcard_match("foo bar"));
180 assert!(!(string_map("bar") + star()).wildcard_match("foo bar"));
181 assert!(!(star() + string_map("bo") + star()).wildcard_match("foo bar"));
182 assert!(!(string_map("f") + star() + string_map("br")).wildcard_match("foo bar"));
183 assert!(!(star() + string_map("x") + star() + star() + star()).wildcard_match("foo bar"));
184 assert!(!empty().wildcard_match("foo bar"));
185
186 assert!(empty().wildcard_match(""));
188 assert!(star().wildcard_match(""));
189
190 assert!(!string_map("foo bar").wildcard_match(""));
192
193 assert!(string_map("*").wildcard_match("*"));
195 assert!(star().wildcard_match("*"));
196
197 assert!(!string_map("\u{0000}").wildcard_match("*"));
199 assert!(!string_map(r"\u{0000}").wildcard_match("*"));
200 }
201
202 #[test]
203 fn test_wildcard_match_unicode() {
204 assert!((string_map("y") + star()).wildcard_match("y̆"));
206 assert!(string_map("y̆").wildcard_match("y̆"));
207
208 assert!(!(star() + string_map("p") + star()).wildcard_match("y̆"));
210
211 assert!((star() + string_map("p") + star()).wildcard_match("ḛ̶͑͝x̶͔͛a̵̰̯͛m̴͉̋́p̷̠͂l̵͇̍̔ȩ̶̣͝"));
213 assert!((star() + string_map("a̵̰̯͛m̴͉̋́") + star()).wildcard_match("ḛ̶͑͝x̶͔͛a̵̰̯͛m̴͉̋́p̷̠͂l̵͇̍̔ȩ̶̣͝"));
214
215 assert!(!(string_map("y") + star()).wildcard_match("ḛ̶͑͝x̶͔͛a̵̰̯͛m̴͉̋́p̷̠͂l̵͇̍̔ȩ̶̣͝"));
217 }
218}