blob: 13cd68a52b3fddb2688cdd6348c141be31892401 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::error::{StringPatternError, ValidationError};
use crate::types;
use fidl_fuchsia_diagnostics as fdiagnostics;
// NOTE: if we could use the negative_impls unstable feature, we could have a single ValidateExt
// trait instead of one for each type of selector we need.
pub trait ValidateExt {
fn validate(&self) -> Result<(), ValidationError>;
}
pub trait ValidateComponentSelectorExt {
fn validate(&self) -> Result<(), ValidationError>;
}
pub trait ValidateTreeSelectorExt {
fn validate(&self) -> Result<(), ValidationError>;
}
pub trait Selector {
type Component: ComponentSelector;
type Tree: TreeSelector;
fn component(&self) -> Option<&Self::Component>;
fn tree(&self) -> Option<&Self::Tree>;
}
pub trait ComponentSelector {
type Segment: StringSelector;
fn segments(&self) -> Option<&[Self::Segment]>;
}
pub trait TreeSelector {
type Segment: StringSelector;
fn node_path(&self) -> Option<&[Self::Segment]>;
fn property(&self) -> Option<&Self::Segment>;
}
pub trait StringSelector {
fn exact_match(&self) -> Option<&str>;
fn pattern(&self) -> Option<&str>;
}
impl<T: Selector> ValidateExt for T {
fn validate(&self) -> Result<(), ValidationError> {
match (self.component(), self.tree()) {
(Some(component), Some(tree)) => {
component.validate()?;
tree.validate()?;
}
(None, _) => return Err(ValidationError::MissingComponentSelector),
(_, None) => return Err(ValidationError::MissingTreeSelector),
}
Ok(())
}
}
impl<T: ComponentSelector> ValidateComponentSelectorExt for T {
fn validate(&self) -> Result<(), ValidationError> {
let segments = self.segments().unwrap_or(&[]);
if segments.is_empty() {
return Err(ValidationError::EmptyComponentSelector);
}
let last_idx = segments.len() - 1;
for segment in &segments[..last_idx] {
segment.validate(StringSelectorValidationOpts::default())?;
}
segments[last_idx].validate(StringSelectorValidationOpts { allow_recursive_glob: true })?;
Ok(())
}
}
impl<T: TreeSelector> ValidateTreeSelectorExt for T {
fn validate(&self) -> Result<(), ValidationError> {
let node_path = self.node_path().unwrap_or(&[]);
if node_path.is_empty() {
return Err(ValidationError::EmptySubtreeSelector);
}
for segment in node_path {
segment.validate(StringSelectorValidationOpts::default())?;
}
if let Some(segment) = self.property() {
segment.validate(StringSelectorValidationOpts::default())?;
}
Ok(())
}
}
#[derive(Default)]
struct StringSelectorValidationOpts {
allow_recursive_glob: bool,
}
trait ValidateStringSelectorExt {
fn validate(&self, opts: StringSelectorValidationOpts) -> Result<(), ValidationError>;
}
impl<T: StringSelector> ValidateStringSelectorExt for T {
fn validate(&self, opts: StringSelectorValidationOpts) -> Result<(), ValidationError> {
match (self.exact_match(), self.pattern()) {
(None, None) | (Some(_), Some(_)) => {
return Err(ValidationError::InvalidStringSelector)
}
(Some(_), None) => {
// A exact match is always valid.
return Ok(());
}
(None, Some(pattern)) => {
if opts.allow_recursive_glob && pattern == "**" {
return Ok(());
} else {
return validate_pattern(pattern);
}
}
}
}
}
/// Checks if the `target` string contains the `forbidden` string without the
/// character `/` preceding the `forbidden` string.
fn contains_unescaped(target: &str, forbidden: &str) -> bool {
if target.len() < forbidden.len() {
return false;
}
if target.starts_with(forbidden) {
return true;
}
let flen = forbidden.len();
for i in 1..(target.len() - flen + 1) {
if &target[i..i + flen] == forbidden && !target[i - 1..i + flen].starts_with("\\") {
return true;
}
}
false
}
fn validate_pattern(pattern: &str) -> Result<(), ValidationError> {
if pattern.is_empty() {
return Err(ValidationError::EmptyStringPattern);
}
let mut errors = vec![];
if contains_unescaped(pattern, "**") {
errors.push(StringPatternError::UnescapedGlob);
}
if contains_unescaped(pattern, ":") {
errors.push(StringPatternError::UnescapedColon);
}
if contains_unescaped(pattern, "/") {
errors.push(StringPatternError::UnescapedForwardSlash);
}
if !errors.is_empty() {
return Err(ValidationError::InvalidStringPattern(pattern.to_string(), errors));
}
Ok(())
}
impl Selector for fdiagnostics::Selector {
type Component = fdiagnostics::ComponentSelector;
type Tree = fdiagnostics::TreeSelector;
fn component(&self) -> Option<&Self::Component> {
self.component_selector.as_ref()
}
fn tree(&self) -> Option<&Self::Tree> {
self.tree_selector.as_ref()
}
}
impl ComponentSelector for fdiagnostics::ComponentSelector {
type Segment = fdiagnostics::StringSelector;
fn segments(&self) -> Option<&[Self::Segment]> {
self.moniker_segments.as_ref().map(|s| s.as_slice())
}
}
impl TreeSelector for fdiagnostics::TreeSelector {
type Segment = fdiagnostics::StringSelector;
fn node_path(&self) -> Option<&[Self::Segment]> {
match self {
Self::SubtreeSelector(t) => Some(&t.node_path[..]),
Self::PropertySelector(p) => Some(&p.node_path[..]),
fdiagnostics::TreeSelectorUnknown!() => None,
}
}
fn property(&self) -> Option<&Self::Segment> {
match self {
Self::SubtreeSelector(_) => None,
Self::PropertySelector(p) => Some(&p.target_properties),
fdiagnostics::TreeSelectorUnknown!() => None,
}
}
}
impl StringSelector for fdiagnostics::StringSelector {
fn exact_match(&self) -> Option<&str> {
match self {
Self::ExactMatch(s) => Some(s),
_ => None,
}
}
fn pattern(&self) -> Option<&str> {
match self {
Self::StringPattern(s) => Some(s),
_ => None,
}
}
}
impl<'a> Selector for types::Selector<'a> {
type Component = types::ComponentSelector<'a>;
type Tree = types::TreeSelector<'a>;
fn component(&self) -> Option<&Self::Component> {
Some(&self.component)
}
fn tree(&self) -> Option<&Self::Tree> {
Some(&self.tree)
}
}
impl<'a> ComponentSelector for types::ComponentSelector<'a> {
type Segment = types::Segment<'a>;
fn segments(&self) -> Option<&[Self::Segment]> {
Some(&self.segments[..])
}
}
impl<'a> TreeSelector for types::TreeSelector<'a> {
type Segment = types::Segment<'a>;
fn node_path(&self) -> Option<&[Self::Segment]> {
Some(&self.node)
}
fn property(&self) -> Option<&Self::Segment> {
self.property.as_ref()
}
}
impl<'a> StringSelector for types::Segment<'a> {
fn exact_match(&self) -> Option<&str> {
match self {
Self::ExactMatch(s) => Some(s),
_ => None,
}
}
fn pattern(&self) -> Option<&str> {
match self {
Self::Pattern(s) => Some(s),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use lazy_static::lazy_static;
lazy_static! {
static ref SHARED_PASSING_TEST_CASES: Vec<(Vec<&'static str>, &'static str)> = {
vec![
(vec![r#"abc"#, r#"def"#, r#"g"#], r#"bob"#),
(vec![r#"\**"#], r#"\**"#),
(vec![r#"\/"#], r#"\/"#),
(vec![r#"\:"#], r#"\:"#),
(vec![r#"asda\\\:"#], r#"a"#),
(vec![r#"asda*"#], r#"a"#),
]
};
static ref SHARED_FAILING_TEST_CASES: Vec<(Vec<&'static str>, &'static str)> = {
vec![
// Slashes aren't allowed in path nodes.
(vec![r#"/"#], r#"a"#),
// Colons aren't allowed in path nodes.
(vec![r#":"#], r#"a"#),
// Checking that path nodes ending with offlimits
// chars are still identified.
(vec![r#"asdasd:"#], r#"a"#),
(vec![r#"a**"#], r#"a"#),
// Checking that path nodes starting with offlimits
// chars are still identified.
(vec![r#":asdasd"#], r#"a"#),
(vec![r#"**a"#], r#"a"#),
// Neither moniker segments nor node paths
// are allowed to be empty.
(vec![], r#"bob"#),
]
};
}
#[fuchsia::test]
fn tree_selector_validator_test() {
let unique_failing_test_cases = vec![
// All failing validators due to property selectors are
// unique since the component validator doesn't look at them.
(vec![r#"a"#], r#"**"#),
(vec![r#"a"#], r#"/"#),
];
fn create_tree_selector(
node_path: &Vec<&str>,
property: &str,
) -> fdiagnostics::TreeSelector {
let node_path = node_path
.iter()
.map(|path_node_str| {
fdiagnostics::StringSelector::StringPattern(path_node_str.to_string())
})
.collect::<Vec<fdiagnostics::StringSelector>>();
let target_properties =
fdiagnostics::StringSelector::StringPattern(property.to_string());
fdiagnostics::TreeSelector::PropertySelector(fdiagnostics::PropertySelector {
node_path,
target_properties,
})
}
for (node_path, property) in SHARED_PASSING_TEST_CASES.iter() {
let tree_selector = create_tree_selector(node_path, property);
assert!(tree_selector.validate().is_ok());
}
for (node_path, property) in SHARED_FAILING_TEST_CASES.iter() {
let tree_selector = create_tree_selector(node_path, property);
assert!(
ValidateTreeSelectorExt::validate(&tree_selector).is_err(),
"Failed to validate tree selector: {:?}",
tree_selector
);
}
for (node_path, property) in unique_failing_test_cases.iter() {
let tree_selector = create_tree_selector(node_path, property);
assert!(
ValidateTreeSelectorExt::validate(&tree_selector).is_err(),
"Failed to validate tree selector: {:?}",
tree_selector
);
}
}
#[fuchsia::test]
fn component_selector_validator_test() {
fn create_component_selector(
component_moniker: &Vec<&str>,
) -> fdiagnostics::ComponentSelector {
let mut component_selector = fdiagnostics::ComponentSelector::default();
component_selector.moniker_segments = Some(
component_moniker
.into_iter()
.map(|path_node_str| {
fdiagnostics::StringSelector::StringPattern(path_node_str.to_string())
})
.collect::<Vec<fdiagnostics::StringSelector>>(),
);
component_selector
}
for (component_moniker, _) in SHARED_PASSING_TEST_CASES.iter() {
let component_selector = create_component_selector(component_moniker);
assert!(component_selector.validate().is_ok());
}
for (component_moniker, _) in SHARED_FAILING_TEST_CASES.iter() {
let component_selector = create_component_selector(component_moniker);
assert!(
component_selector.validate().is_err(),
"Failed to validate component selector: {:?}",
component_selector
);
}
}
}