Skip to main content

selectors/
validate.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::error::{StringPatternError, ValidationError};
6use crate::ir;
7use fidl_fuchsia_diagnostics_common as fdiagnostics;
8
9// NOTE: if we could use the negative_impls unstable feature, we could have a single ValidateExt
10// trait instead of one for each type of selector we need.
11pub 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
130/// Checks if the `target` string contains the `forbidden` string without an unescaped
131/// preceding backslash.
132fn 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
146/// Checks if the payload of an already-parsed `StringSelector::Pattern` is
147/// indeed valid.
148/// Does not validate correctly for patterns in the selector *language*
149fn 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                // Checking that path nodes ending with offlimits
290                // chars are still identified.
291                (vec![r#"a**"#], r#"a"#),
292                // Checking that path nodes starting with offlimits
293                // chars are still identified.
294                (vec![r#"**a"#], r#"a"#),
295                // Neither moniker segments nor node paths
296                // are allowed to be empty.
297                (vec![], r#"bob"#),
298            ]
299        });
300
301    #[fuchsia::test]
302    fn tree_selector_validator_test() {
303        let unique_failing_test_cases = [
304            // All failing validators due to property selectors are
305            // unique since the component validator doesn't look at them.
306            (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}