1use crate::error::{StringPatternError, ValidationError};
6use crate::ir;
7use fidl_fuchsia_diagnostics_common as fdiagnostics;
8
9pub trait ValidateExt {
12 fn validate(&self) -> Result<(), ValidationError>;
13}
14
15pub trait ValidateComponentSelectorExt {
16 fn validate(&self) -> Result<(), ValidationError>;
17}
18
19pub trait ValidateTreeSelectorExt {
20 fn validate(&self) -> Result<(), ValidationError>;
21}
22
23pub trait Selector {
24 type Component: ComponentSelector;
25 type Tree: TreeSelector;
26
27 fn component(&self) -> Option<&Self::Component>;
28 fn tree(&self) -> Option<&Self::Tree>;
29}
30
31pub trait ComponentSelector {
32 type Segment: StringSelector;
33 fn segments(&self) -> Option<&[Self::Segment]>;
34}
35
36pub trait TreeSelector {
37 type Segment: StringSelector;
38
39 fn node_path(&self) -> Option<&[Self::Segment]>;
40 fn property(&self) -> Option<&Self::Segment>;
41}
42
43pub trait StringSelector {
44 fn exact_match(&self) -> Option<&str>;
45 fn pattern(&self) -> Option<&str>;
46}
47
48impl<T: Selector> ValidateExt for T {
49 fn validate(&self) -> Result<(), ValidationError> {
50 match (self.component(), self.tree()) {
51 (Some(component), Some(tree)) => {
52 component.validate()?;
53 tree.validate()?;
54 }
55 (None, _) => return Err(ValidationError::MissingComponentSelector),
56 (_, None) => return Err(ValidationError::MissingTreeSelector),
57 }
58 Ok(())
59 }
60}
61
62impl<T: ComponentSelector> ValidateComponentSelectorExt for T {
63 fn validate(&self) -> Result<(), ValidationError> {
64 let segments = self.segments().unwrap_or(&[]);
65 if segments.is_empty() {
66 return Err(ValidationError::EmptyComponentSelector);
67 }
68 let last_idx = segments.len() - 1;
69 for segment in &segments[..last_idx] {
70 segment.validate(StringSelectorValidationOpts {
71 allow_empty: false,
72 ..Default::default()
73 })?;
74 }
75 segments[last_idx].validate(StringSelectorValidationOpts {
76 allow_recursive_glob: true,
77 ..Default::default()
78 })?;
79 Ok(())
80 }
81}
82
83impl<T: TreeSelector> ValidateTreeSelectorExt for T {
84 fn validate(&self) -> Result<(), ValidationError> {
85 let node_path = self.node_path().unwrap_or(&[]);
86 if node_path.is_empty() {
87 return Err(ValidationError::EmptySubtreeSelector);
88 }
89 for segment in node_path {
90 segment.validate(StringSelectorValidationOpts::default())?;
91 }
92 if let Some(segment) = self.property() {
93 segment.validate(StringSelectorValidationOpts::default())?;
94 }
95 Ok(())
96 }
97}
98
99#[derive(Default)]
100struct StringSelectorValidationOpts {
101 allow_recursive_glob: bool,
102 allow_empty: bool,
103}
104
105trait ValidateStringSelectorExt {
106 fn validate(&self, opts: StringSelectorValidationOpts) -> Result<(), ValidationError>;
107}
108
109impl<T: StringSelector> ValidateStringSelectorExt for T {
110 fn validate(&self, opts: StringSelectorValidationOpts) -> Result<(), ValidationError> {
111 match (self.exact_match(), self.pattern()) {
112 (None, None) | (Some(_), Some(_)) => Err(ValidationError::InvalidStringSelector),
113 (Some(exact_match), None) => {
114 if !opts.allow_empty && exact_match.is_empty() {
115 return Err(ValidationError::InvalidStringSelector);
116 }
117 Ok(())
118 }
119 (None, Some(pattern)) => {
120 if opts.allow_recursive_glob && pattern == "**" {
121 Ok(())
122 } else {
123 validate_pattern(pattern)
124 }
125 }
126 }
127 }
128}
129
130fn contains_unescaped(target: &str, forbidden: &str) -> bool {
133 if forbidden.is_empty() {
134 return true;
135 }
136 for (idx, _) in target.match_indices(forbidden) {
137 let preceding = &target[..idx];
138 let backslash_count = preceding.chars().rev().take_while(|&c| c == '\\').count();
139 if backslash_count % 2 == 0 {
140 return true;
141 }
142 }
143 false
144}
145
146fn validate_pattern(pattern: &str) -> Result<(), ValidationError> {
150 if pattern.is_empty() {
151 return Err(ValidationError::EmptyStringPattern);
152 }
153
154 let mut errors = vec![];
155 if contains_unescaped(pattern, "**") {
156 errors.push(StringPatternError::UnescapedGlob);
157 }
158 if !errors.is_empty() {
159 return Err(ValidationError::InvalidStringPattern(pattern.to_string(), errors));
160 }
161 Ok(())
162}
163
164impl Selector for fdiagnostics::Selector {
165 type Component = fdiagnostics::ComponentSelector;
166 type Tree = fdiagnostics::TreeSelector;
167
168 fn component(&self) -> Option<&Self::Component> {
169 self.component_selector.as_ref()
170 }
171
172 fn tree(&self) -> Option<&Self::Tree> {
173 self.tree_selector.as_ref()
174 }
175}
176
177impl ComponentSelector for fdiagnostics::ComponentSelector {
178 type Segment = fdiagnostics::StringSelector;
179
180 fn segments(&self) -> Option<&[Self::Segment]> {
181 self.moniker_segments.as_deref()
182 }
183}
184
185impl TreeSelector for fdiagnostics::TreeSelector {
186 type Segment = fdiagnostics::StringSelector;
187
188 fn node_path(&self) -> Option<&[Self::Segment]> {
189 match self {
190 Self::SubtreeSelector(t) => Some(&t.node_path[..]),
191 Self::PropertySelector(p) => Some(&p.node_path[..]),
192 fdiagnostics::TreeSelectorUnknown!() => None,
193 }
194 }
195
196 fn property(&self) -> Option<&Self::Segment> {
197 match self {
198 Self::SubtreeSelector(_) => None,
199 Self::PropertySelector(p) => Some(&p.target_properties),
200 fdiagnostics::TreeSelectorUnknown!() => None,
201 }
202 }
203}
204
205impl StringSelector for fdiagnostics::StringSelector {
206 fn exact_match(&self) -> Option<&str> {
207 match self {
208 Self::ExactMatch(s) => Some(s),
209 _ => None,
210 }
211 }
212
213 fn pattern(&self) -> Option<&str> {
214 match self {
215 Self::StringPattern(s) => Some(s),
216 _ => None,
217 }
218 }
219}
220
221impl<'a> Selector for ir::Selector<'a> {
222 type Component = ir::ComponentSelector<'a>;
223 type Tree = ir::TreeSelector<'a>;
224
225 fn component(&self) -> Option<&Self::Component> {
226 Some(&self.component)
227 }
228
229 fn tree(&self) -> Option<&Self::Tree> {
230 Some(&self.tree)
231 }
232}
233
234impl<'a> ComponentSelector for ir::ComponentSelector<'a> {
235 type Segment = ir::Segment<'a>;
236
237 fn segments(&self) -> Option<&[Self::Segment]> {
238 Some(&self.segments[..])
239 }
240}
241
242impl<'a> TreeSelector for ir::TreeSelector<'a> {
243 type Segment = ir::Segment<'a>;
244
245 fn node_path(&self) -> Option<&[Self::Segment]> {
246 Some(&self.node)
247 }
248
249 fn property(&self) -> Option<&Self::Segment> {
250 self.property.as_ref()
251 }
252}
253
254impl StringSelector for ir::Segment<'_> {
255 fn exact_match(&self) -> Option<&str> {
256 match self {
257 Self::ExactMatch(s) => Some(s),
258 _ => None,
259 }
260 }
261
262 fn pattern(&self) -> Option<&str> {
263 match self {
264 Self::Pattern(s) => Some(s),
265 _ => None,
266 }
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use super::*;
273 use std::sync::LazyLock;
274
275 static SHARED_PASSING_TEST_CASES: LazyLock<Vec<(Vec<&'static str>, &'static str)>> =
276 LazyLock::new(|| {
277 vec![
278 (vec![r#"abc"#, r#"def"#, r#"g"#], r#"bob"#),
279 (vec![r#"\**"#], r#"\**"#),
280 (vec![r#"\/"#], r#"\/"#),
281 (vec![r#"\:"#], r#"\:"#),
282 (vec![r#"asda\\\:"#], r#"a"#),
283 (vec![r#"asda*"#], r#"a"#),
284 ]
285 });
286 static SHARED_FAILING_TEST_CASES: LazyLock<Vec<(Vec<&'static str>, &'static str)>> =
287 LazyLock::new(|| {
288 vec![
289 (vec![r#"a**"#], r#"a"#),
292 (vec![r#"**a"#], r#"a"#),
295 (vec![], r#"bob"#),
298 ]
299 });
300
301 #[fuchsia::test]
302 fn tree_selector_validator_test() {
303 let unique_failing_test_cases = [
304 (vec![r#"a"#], r#"**"#),
307 ];
308
309 fn create_tree_selector(node_path: &[&str], property: &str) -> fdiagnostics::TreeSelector {
310 let node_path = node_path
311 .iter()
312 .map(|path_node_str| {
313 fdiagnostics::StringSelector::StringPattern(path_node_str.to_string())
314 })
315 .collect::<Vec<fdiagnostics::StringSelector>>();
316 let target_properties =
317 fdiagnostics::StringSelector::StringPattern(property.to_string());
318 fdiagnostics::TreeSelector::PropertySelector(fdiagnostics::PropertySelector {
319 node_path,
320 target_properties,
321 })
322 }
323
324 for (node_path, property) in SHARED_PASSING_TEST_CASES.iter() {
325 let tree_selector = create_tree_selector(node_path, property);
326 assert!(tree_selector.validate().is_ok());
327 }
328
329 for (node_path, property) in SHARED_FAILING_TEST_CASES.iter() {
330 let tree_selector = create_tree_selector(node_path, property);
331 assert!(
332 ValidateTreeSelectorExt::validate(&tree_selector).is_err(),
333 "Failed to validate tree selector: {tree_selector:?}"
334 );
335 }
336
337 for (node_path, property) in unique_failing_test_cases.iter() {
338 let tree_selector = create_tree_selector(node_path, property);
339 assert!(
340 ValidateTreeSelectorExt::validate(&tree_selector).is_err(),
341 "Failed to validate tree selector: {tree_selector:?}"
342 );
343 }
344 }
345
346 #[fuchsia::test]
347 fn component_selector_validator_test() {
348 fn create_component_selector(
349 component_moniker: &[&str],
350 ) -> fdiagnostics::ComponentSelector {
351 fdiagnostics::ComponentSelector {
352 moniker_segments: Some(
353 component_moniker
354 .iter()
355 .map(|path_node_str| {
356 fdiagnostics::StringSelector::StringPattern(path_node_str.to_string())
357 })
358 .collect::<Vec<fdiagnostics::StringSelector>>(),
359 ),
360 ..fdiagnostics::ComponentSelector::default()
361 }
362 }
363
364 for (component_moniker, _) in SHARED_PASSING_TEST_CASES.iter() {
365 let component_selector = create_component_selector(component_moniker);
366
367 assert!(component_selector.validate().is_ok());
368 }
369
370 for (component_moniker, _) in SHARED_FAILING_TEST_CASES.iter() {
371 let component_selector = create_component_selector(component_moniker);
372
373 assert!(
374 component_selector.validate().is_err(),
375 "Failed to validate component selector: {component_selector:?}"
376 );
377 }
378 }
379
380 #[fuchsia::test]
381 fn test_contains_unescaped() {
382 assert!(contains_unescaped("foo**bar", "**"));
383 assert!(!contains_unescaped("foo\\**bar", "**"));
384 assert!(contains_unescaped("foo\\\\**bar", "**"));
385 assert!(!contains_unescaped("foo\\\\\\**bar", "**"));
386 assert!(contains_unescaped("🦀**", "**"));
387 assert!(!contains_unescaped("🦀\\**", "**"));
388 }
389}