blob: 7b7f9b54e4e4a15715bf9def82c0cab88fdf3c5e [file] [log] [blame]
// Copyright 2019 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 {
anyhow::{format_err, Error},
fidl_fuchsia_diagnostics::{
self, ComponentSelector, PropertySelector, Selector, StringSelector, StringSelectorUnknown,
SubtreeSelector, TreeSelector,
},
lazy_static::lazy_static,
regex::{Regex, RegexSet},
regex_syntax,
std::borrow::Borrow,
std::fs,
std::io::{BufRead, BufReader},
std::path::{Path, PathBuf},
};
// Character used to delimit the different sections of an inspect selector,
// the component selector, the tree selector, and the property selector.
pub static SELECTOR_DELIMITER: char = ':';
// Character used to delimit nodes within a component hierarchy path.
static PATH_NODE_DELIMITER: char = '/';
// Character used to escape interperetation of this parser's "special
// characers"; *, /, :, and \.
static ESCAPE_CHARACTER: char = '\\';
// Pattern used to encode wildcard.
pub(crate) static WILDCARD_SYMBOL_STR: &str = "*";
static WILDCARD_SYMBOL_CHAR: char = '*';
static RECURSIVE_WILDCARD_SYMBOL_STR: &str = "**";
// Globs will match everything along a moniker, but won't match empty strings.
static GLOB_REGEX_EQUIVALENT: &str = ".+";
// Wildcards will match anything except for an unescaped slash, since their match
// only extends to a single moniker "node".
//
// It is OK for a wildcard to match nothing when appearing as a pattern match.
// For example, "hello*" matches both "hello world" and "hello".
static WILDCARD_REGEX_EQUIVALENT: &str = r#"(\\/|[^/])*"#;
// Recursive wildcards will match anything, including an unescaped slash.
//
// It is OK for a recursive wildcard to match nothing when appearing in a pattern match.
static RECURSIVE_WILDCARD_REGEX_EQUIVALENT: &str = ".*";
// Extract moniker from component path.
// For example, for path "/hub/c/archivist.cmx" this function will return "archivist.cmx".
pub fn parse_path_to_moniker(path: &str) -> Result<Vec<String>, Error> {
// First try to parse the paths according to the legacy snapshot "path" format.
let legacy_snapshot_path = Regex::new(r"/[cr]/([^/]*)/\d+")
.unwrap()
.captures_iter(path)
.map(|cap| cap.get(1).unwrap().as_str().to_owned())
.collect::<Vec<String>>();
match legacy_snapshot_path.as_slice() {
[] => {
// If the legacy snapshot regex failed to produce a moniker vector, then
// the path is generated by the platform and already exists as a moniker. Tokenize
// by unescaped path delimiters.
tokenize_string(path, PATH_NODE_DELIMITER).map_err(|e| {
format_err!(
"Failed to parse snapshot path: {} using legacy or new tokenizers. Error: {}",
path,
e
)
})
}
legacy_moniker => Ok(legacy_moniker.to_vec()),
}
}
/// Validates a string pattern used in either a PropertySelector or a
/// PathSelectorNode.
/// string patterns:
/// 1) Require that the string not be empty.
/// 2) Require that the string not contain any
/// glob symbols.
/// 3) Require that any escape characters have a matching character they
/// are escaping.
/// 4) Require that there are no unescaped selector delimiters, `:`,
/// or unescaped path delimiters, `/`.
fn validate_string_pattern(string_pattern: &str) -> Result<(), Error> {
lazy_static! {
static ref STRING_PATTERN_VALIDATOR: RegexSet = RegexSet::new(&[
// No glob expressions allowed.
r#"([^\\]\*\*|^\*\*)"#,
// No unescaped selector delimiters allowed.
r#"([^\\]:|^:)"#,
// No unescaped path delimiters allowed.
r#"([^\\]/|^/)"#,
]).unwrap();
}
if string_pattern.is_empty() {
return Err(format_err!("String patterns cannot be empty."));
}
let validator_matches = STRING_PATTERN_VALIDATOR.matches(string_pattern);
if !validator_matches.matched_any() {
return Ok(());
} else {
let mut error_string =
format!("String pattern {} failed verification: ", string_pattern).to_string();
if validator_matches.matched(0) {
error_string.push_str("\n A string pattern cannot contain unescaped glob patterns.");
}
if validator_matches.matched(1) {
error_string
.push_str("\n A string pattern cannot contain unescaped selector delimiters, `:`.");
}
if validator_matches.matched(2) {
error_string
.push_str("\n A string pattern cannot contain unescaped path delimiters, `/`.");
}
return Err(format_err!("{}", error_string));
}
}
fn validate_string_selector_allow_recursive_glob(
string_selector: &StringSelector,
) -> Result<(), Error> {
match string_selector {
StringSelector::StringPattern(pattern) if pattern == RECURSIVE_WILDCARD_SYMBOL_STR => {
Ok(())
}
_ => validate_string_selector(string_selector),
}
}
fn validate_string_selector(string_selector: &StringSelector) -> Result<(), Error> {
match string_selector {
StringSelector::StringPattern(pattern) => validate_string_pattern(pattern),
//TODO(fxbug.dev/4601): What do we need to validate against exact matches?
StringSelector::ExactMatch(_) => Ok(()),
_ => Err(format_err!("PathSelectionNodes must be string patterns or pattern matches")),
}
}
/// Validates all PathSelectorNodes within `path_selection_vector`.
/// PathSelectorNodes:
/// 1) Require that all elements of the vector are valid per
/// Selectors::validate_string_pattern specification.
/// 2) Require a non-empty vector.
fn validate_tree_path_selection_vector(
path_selection_vector: &[StringSelector],
) -> Result<(), Error> {
for path_selection_node in path_selection_vector {
validate_string_selector(path_selection_node)?;
}
Ok(())
}
fn validate_component_path_selection_vector(
path_selection_vector: &[StringSelector],
) -> Result<(), Error> {
let last_idx = path_selection_vector.len() - 1;
for path_selection_node in path_selection_vector[..last_idx].iter() {
validate_string_selector(path_selection_node)?;
}
validate_string_selector_allow_recursive_glob(&path_selection_vector[last_idx])
}
/// Validates a TreeSelector:
/// TreeSelectors:
/// 1) Require a present node_path selector field.
/// 2) Require that all entries within the node_path are valid per
/// Selectors::validate_node_path specification.
/// 3) Require that the target_properties field, if it is present,
/// is valid per Selectors::validate_string_pattern specification.
fn validate_tree_selector(tree_selector: &TreeSelector) -> Result<(), Error> {
match tree_selector {
TreeSelector::SubtreeSelector(subtree_selector) => {
if subtree_selector.node_path.is_empty() {
return Err(format_err!("Subtree selectors must have non-empty node_path vector."));
}
validate_tree_path_selection_vector(&subtree_selector.node_path)?;
}
TreeSelector::PropertySelector(property_selector) => {
if property_selector.node_path.is_empty() {
return Err(format_err!(
"Property selectors must have non-empty node_path vector."
));
}
validate_tree_path_selection_vector(&property_selector.node_path)?;
match &property_selector.target_properties {
StringSelector::StringPattern(pattern) => match validate_string_pattern(pattern) {
Ok(_) => {}
Err(e) => {
return Err(e);
}
},
StringSelector::ExactMatch(_) => {
// TODO(fxbug.dev/4601): What do we need to validate for exact match strings?
}
_ => {
return Err(format_err!(
"target_properties must be either string patterns or exact matches."
))
}
}
}
_ => return Err(format_err!("TreeSelector only supports property and subtree selection.")),
}
Ok(())
}
/// Validates a ComponentSelector:
/// ComponentSelectors:
/// 1) Require a present component_moniker field.
/// 2) Require that all entries within the component_moniker vector are valid per
/// Selectors::validate_node_path specification.
fn validate_component_selector(component_selector: &ComponentSelector) -> Result<(), Error> {
match &component_selector.moniker_segments {
Some(moniker) => {
if moniker.is_empty() {
return Err(format_err!(
"Component selectors must have non-empty moniker segment vector."
));
}
validate_component_path_selection_vector(moniker)
}
None => Err(format_err!("Component selectors must have a moniker_segment.")),
}
}
/// Returns true iff a component selector uses the recursive glob.
/// Assumes the selector has already been validated.
pub fn contains_recursive_glob(component_selector: &ComponentSelector) -> bool {
// Unwrap as a valid selector must contain these fields.
let last_segment = component_selector.moniker_segments.as_ref().unwrap().last().unwrap();
match last_segment {
StringSelector::StringPattern(pattern) if pattern == RECURSIVE_WILDCARD_SYMBOL_STR => true,
StringSelector::StringPattern(_) => false,
StringSelector::ExactMatch(_) => false,
StringSelectorUnknown!() => false,
}
}
pub fn validate_selector(selector: &Selector) -> Result<(), Error> {
match (&selector.component_selector, &selector.tree_selector) {
(Some(component_selector), Some(tree_selector)) => {
validate_component_selector(component_selector)?;
validate_tree_selector(tree_selector)?;
Ok(())
}
_ => Err(format_err!("Selectors require a component and tree selector.")),
}
}
/// Parse a string into a FIDL StringSelector structure.
fn convert_string_to_string_selector(string_to_convert: &str) -> StringSelector {
// TODO(fxbug.dev/4601): Expose the ability to parse selectors from string into "exact_match" mode.
StringSelector::StringPattern(string_to_convert.to_string())
}
/// Increments the CharIndices iterator and updates the token builder
/// in order to avoid processing characters being escaped by the selector.
fn handle_escaped_char(
token_builder: &mut String,
selection_iter: &mut std::str::CharIndices<'_>,
) -> Result<(), Error> {
token_builder.push(ESCAPE_CHARACTER);
let escaped_char_option: Option<(usize, char)> = selection_iter.next();
match escaped_char_option {
Some((_, escaped_char)) => token_builder.push(escaped_char),
None => {
return Err(format_err!(
"Selecter fails verification due to unmatched escape character",
));
}
}
Ok(())
}
/// Converts a string into a vector of string tokens representing the unparsed
/// string delimited by the provided delimiter, excluded escaped delimiters.
pub fn tokenize_string(untokenized_selector: &str, delimiter: char) -> Result<Vec<String>, Error> {
let mut token_aggregator = Vec::new();
let mut curr_token_builder: String = String::new();
let mut unparsed_selector_iter = untokenized_selector.char_indices();
while let Some((_, selector_char)) = unparsed_selector_iter.next() {
match selector_char {
escape if escape == ESCAPE_CHARACTER => {
handle_escaped_char(&mut curr_token_builder, &mut unparsed_selector_iter)?;
}
selector_delimiter if selector_delimiter == delimiter => {
if curr_token_builder.is_empty() {
return Err(format_err!(
"Cannot have empty strings delimited by {}",
delimiter
));
}
token_aggregator.push(curr_token_builder);
curr_token_builder = String::new();
}
_ => curr_token_builder.push(selector_char),
}
}
// Push the last section of the selector into the aggregator since we don't delimit the
// end of the selector.
if curr_token_builder.is_empty() {
return Err(format_err!(
"Cannot have empty strings delimited by {}: {}",
delimiter,
untokenized_selector
));
}
token_aggregator.push(curr_token_builder);
return Ok(token_aggregator);
}
/// Converts an unparsed component selector string into a ComponentSelector.
pub fn parse_component_selector(
unparsed_component_selector: &str,
) -> Result<ComponentSelector, Error> {
if unparsed_component_selector.is_empty() {
return Err(format_err!("ComponentSelector must have atleast one path node.",));
}
let tokenized_component_selector =
tokenize_string(unparsed_component_selector, PATH_NODE_DELIMITER)?;
let mut component_selector: ComponentSelector = ComponentSelector::EMPTY;
// Convert every token of the component hierarchy into a PathSelectionNode.
let path_node_vector = tokenized_component_selector
.iter()
.map(|node_string| convert_string_to_string_selector(node_string))
.collect::<Vec<_>>();
validate_component_path_selection_vector(&path_node_vector)?;
component_selector.moniker_segments = Some(path_node_vector);
return Ok(component_selector);
}
/// Converts an unparsed node path selector and an unparsed property selector into
/// a TreeSelector.
fn parse_tree_selector(
unparsed_node_path: &str,
unparsed_property_selector: Option<&str>,
) -> Result<TreeSelector, Error> {
let node_path_option = if unparsed_node_path.is_empty() {
None
} else {
Some(
tokenize_string(unparsed_node_path, PATH_NODE_DELIMITER)?
.iter()
.map(|node_string| convert_string_to_string_selector(node_string))
.collect::<Vec<_>>(),
)
};
let property_option = match unparsed_property_selector {
Some(unparsed_string) => Some(convert_string_to_string_selector(unparsed_string)),
None => None,
};
let tree_selector = match (node_path_option, property_option) {
(Some(node_path), Some(property)) => TreeSelector::PropertySelector(PropertySelector {
node_path: node_path,
target_properties: property,
}),
(Some(node_path), None) => {
TreeSelector::SubtreeSelector(SubtreeSelector { node_path: node_path })
}
_ => {
return Err(format_err!(
"The provided selector is neither a subtree selector nor a property selector.",
))
}
};
validate_tree_selector(&tree_selector)?;
return Ok(tree_selector);
}
/// Converts an unparsed Inspect selector into a ComponentSelector and TreeSelector.
pub fn parse_selector(unparsed_selector: &str) -> Result<Selector, Error> {
// Tokenize the selector by `:` char in order to process each subselector separately.
let selector_sections = tokenize_string(unparsed_selector, SELECTOR_DELIMITER)?;
match selector_sections.as_slice() {
[component_selector, inspect_node_selector, property_selector] => Ok(Selector {
component_selector: Some(parse_component_selector(component_selector)?),
tree_selector: Some(parse_tree_selector(
inspect_node_selector,
Some(property_selector),
)?),
..Selector::EMPTY
}),
[component_selector, inspect_node_selector] => Ok(Selector {
component_selector: Some(parse_component_selector(component_selector)?),
tree_selector: Some(parse_tree_selector(inspect_node_selector, None)?),
..Selector::EMPTY
}),
_ => Err(format_err!(
"Selector format requires at least 2 subselectors delimited by a `:`.",
)),
}
}
pub fn parse_selector_file(selector_file: &Path) -> Result<Vec<Selector>, Error> {
let selector_file = match fs::File::open(selector_file) {
Ok(file) => file,
Err(_) => return Err(format_err!("Failed to open selector file at configured path.",)),
};
let mut selector_vec = Vec::new();
let reader = BufReader::new(selector_file);
for line in reader.lines() {
match line {
Ok(line) => {
if line.is_empty() {
continue;
}
selector_vec.push(parse_selector(&line)?);
}
Err(_) => {
return Err(
format_err!("Failed to read line of selector file at configured path.",),
)
}
}
}
Ok(selector_vec)
}
pub fn parse_selectors(selector_path: impl Into<PathBuf>) -> Result<Vec<Selector>, Error> {
let selector_directory_path: PathBuf = selector_path.into();
let mut selector_vec: Vec<Selector> = Vec::new();
for entry in fs::read_dir(selector_directory_path)? {
let entry = entry?;
if entry.path().is_dir() {
return Err(format_err!("Static selector directories are expected to be flat.",));
} else {
selector_vec.append(&mut parse_selector_file(&entry.path())?);
}
}
Ok(selector_vec)
}
/// Helper method for converting ExactMatch StringSelectors to regex. We must
/// escape all special characters on the behalf of the selector author when converting
/// exact matches to regex.
fn is_special_character(character: char) -> bool {
character == ESCAPE_CHARACTER
|| character == PATH_NODE_DELIMITER
|| character == SELECTOR_DELIMITER
|| character == WILDCARD_SYMBOL_CHAR
}
/// Converts a single character from a StringSelector into a format that allows it
/// selected for as a literal character in regular expression. This means that all
/// characters in the selector string, which are also regex meta characters, end up
/// being escaped.
fn convert_single_character_to_regex(token_builder: &mut String, character: char) {
if regex_syntax::is_meta_character(character) {
token_builder.push(ESCAPE_CHARACTER);
}
token_builder.push(character);
}
/// When the regular expression converter encounters a `\` escape character
/// in the selector string, it needs to express that escape in the regular expression.
/// The regular expression needs to match both the literal backslash and whatever character
/// is being `escaped` in the selector string. So this method converts a selector string
/// like `\:` into `\\:`.
// TODO(fxbug.dev/4601): Should we validate that the only characters being "escaped" in our
// selector strings are characters that have special syntax in our selector
// DSL?
fn convert_escaped_char_to_regex(
token_builder: &mut String,
selection_iter: &mut std::str::CharIndices<'_>,
) -> Result<(), Error> {
// We have to push an additional escape for escape characters
// since the `\` has significance in Regex that we need to escape
// in order to have literal matching on the backslash.
token_builder.push(ESCAPE_CHARACTER);
token_builder.push(ESCAPE_CHARACTER);
let escaped_char_option: Option<(usize, char)> = selection_iter.next();
escaped_char_option
.map(|(_, escaped_char)| convert_single_character_to_regex(token_builder, escaped_char))
.ok_or(format_err!("Selecter fails verification due to unmatched escape character"))
}
/// Converts a single StringSelector into a regular expression.
///
/// If the StringSelector is a StringPattern, it interperets `\` characters
/// as escape characters that prevent `*` characters from being evaluated as pattern
/// matchers.
///
/// If the StringSelector is an ExactMatch, it will "sanitize" the exact match to
/// align with the format of sanitized text from the system. The resulting regex will
/// be a literal matcher for escape-characters followed by special characters in the
/// selector lanaguage.
fn convert_string_selector_to_regex(
node: &StringSelector,
wildcard_symbol_replacement: &str,
recursive_wildcard_symbol_replacement: Option<&str>,
) -> Result<String, Error> {
match node {
StringSelector::StringPattern(string_pattern) => {
if string_pattern == WILDCARD_SYMBOL_STR {
Ok(wildcard_symbol_replacement.to_string())
} else if string_pattern == RECURSIVE_WILDCARD_SYMBOL_STR {
match recursive_wildcard_symbol_replacement {
Some(replacement) => Ok(replacement.to_string()),
None => Err(format_err!("Recursive wildcards are not supported")),
}
} else {
let mut node_regex_builder = "(".to_string();
let mut node_iter = string_pattern.as_str().char_indices();
while let Some((_, selector_char)) = node_iter.next() {
if selector_char == ESCAPE_CHARACTER {
convert_escaped_char_to_regex(&mut node_regex_builder, &mut node_iter)?
} else if selector_char == WILDCARD_SYMBOL_CHAR {
node_regex_builder.push_str(wildcard_symbol_replacement);
} else {
convert_single_character_to_regex(&mut node_regex_builder, selector_char);
}
}
node_regex_builder.push_str(")");
Ok(node_regex_builder)
}
}
StringSelector::ExactMatch(string_pattern) => {
let mut node_regex_builder = "(".to_string();
let mut node_iter = string_pattern.as_str().char_indices();
while let Some((_, selector_char)) = node_iter.next() {
if is_special_character(selector_char) {
// In ExactMatch mode, we assume that the client wants
// their series of strings to be a literal match for the
// sanitized strings on the system. The sanitized strings
// are formed by escaping all special characters, so we do
// the same here.
node_regex_builder.push(ESCAPE_CHARACTER);
node_regex_builder.push(ESCAPE_CHARACTER);
}
convert_single_character_to_regex(&mut node_regex_builder, selector_char);
}
node_regex_builder.push_str(")");
Ok(node_regex_builder)
}
_ => unreachable!("no expected alternative variants of the path selection node."),
}
}
/// Converts a vector of StringSelectors into a string capable of constructing a
/// regular expression which matches against strings encoding paths.
///
/// NOTE: The resulting regular expression makes the assumption that all "nodes" in the
/// strings encoding paths that it will match against have been sanitized by the
/// sanitize_string_for_selectors API in this crate.
pub fn convert_path_selector_to_regex(
selector: &[StringSelector],
is_subtree_selector: bool,
) -> Result<String, Error> {
let mut regex_string = "^".to_string();
for path_selector in selector {
// Path selectors replace wildcards with a regex that only extends to the next
// unescaped '/' character, since we want each node to only be applied to one level
// of the path.
let node_regex = convert_string_selector_to_regex(
path_selector,
WILDCARD_REGEX_EQUIVALENT,
Some(RECURSIVE_WILDCARD_REGEX_EQUIVALENT),
)?;
regex_string.push_str(&node_regex);
regex_string.push_str("/");
}
if is_subtree_selector {
regex_string.push_str(".*")
}
regex_string.push_str("$");
Ok(regex_string)
}
/// Converts a single StringSelectors into a string capable of constructing a regular
/// expression which matches strings encoding a property name on a node.
///
/// NOTE: The resulting regular expression makes the assumption that the property names
/// that it will match against have been sanitized by the sanitize_string_for_selectors API in
/// this crate.
pub fn convert_property_selector_to_regex(selector: &StringSelector) -> Result<String, Error> {
let mut regex_string = "^".to_string();
// Property selectors replace wildcards with GLOB like behavior since there is no
// concept of levels/depth to properties.
let property_regex = convert_string_selector_to_regex(selector, GLOB_REGEX_EQUIVALENT, None)?;
regex_string.push_str(&property_regex);
regex_string.push_str("$");
Ok(regex_string)
}
/// Sanitizes raw strings from the system such that they align with the
/// special-character and escaping semantics of the Selector format.
///
/// Sanitization escapes the known special characters in the selector language.
///
/// NOTE: All strings must be sanitized before being evaluated by
/// selectors in regex form.
pub fn sanitize_string_for_selectors(node: &str) -> String {
if node.is_empty() {
return String::new();
}
// Preallocate enough space to store the original string.
let mut sanitized_string = String::with_capacity(node.len());
node.chars().for_each(|node_char| {
if is_special_character(node_char) {
sanitized_string.push(ESCAPE_CHARACTER);
}
sanitized_string.push(node_char);
});
sanitized_string
}
pub fn match_moniker_against_component_selector(
moniker: &[impl AsRef<str> + std::string::ToString],
component_selector: &ComponentSelector,
) -> Result<bool, Error> {
let moniker_selector: &Vec<StringSelector> = match &component_selector.moniker_segments {
Some(path_vec) => &path_vec,
None => return Err(format_err!("Component selectors require moniker segments.")),
};
let mut sanitized_moniker = moniker
.iter()
.map(|s| sanitize_string_for_selectors(s.as_ref()))
.collect::<Vec<String>>()
.join("/");
// We must append a "/" because the regex strings assume that all paths end
// in a slash.
sanitized_moniker.push('/');
let moniker_regex = Regex::new(&convert_path_selector_to_regex(
moniker_selector,
/*is_subtree_selector=*/ false,
)?)?;
Ok(moniker_regex.is_match(&sanitized_moniker))
}
/// Evaluates a component moniker against a single selector, returning
/// True if the selector matches the component, else false.
///
/// Requires: hierarchy_path is not empty.
/// selectors contains valid Selectors.
pub fn match_component_moniker_against_selector<T>(
moniker: &[T],
selector: &Selector,
) -> Result<bool, Error>
where
T: AsRef<str> + std::string::ToString,
{
validate_selector(selector)?;
if moniker.is_empty() {
return Err(format_err!(
"Cannot have empty monikers, at least the component name is required."
));
}
// Unwrap is safe because the validator ensures there is a component selector.
let component_selector = selector.component_selector.as_ref().unwrap();
match_moniker_against_component_selector(moniker, component_selector)
}
/// Evaluates a component moniker against a list of selectors, returning
/// all of the selectors which are matches for that moniker.
///
/// Requires: hierarchy_path is not empty.
/// selectors contains valid Selectors.
pub fn match_component_moniker_against_selectors<'a, T>(
moniker: &[String],
selectors: &'a [T],
) -> Result<Vec<&'a Selector>, Error>
where
T: Borrow<Selector>,
{
if moniker.is_empty() {
return Err(format_err!(
"Cannot have empty monikers, at least the component name is required."
));
}
let selectors = selectors
.iter()
.map(|selector| {
let component_selector = selector.borrow();
validate_selector(component_selector)?;
Ok(component_selector)
})
.collect::<Result<Vec<&Selector>, Error>>();
selectors?
.iter()
.filter_map(|selector| {
match_component_moniker_against_selector(moniker, selector)
.map(|is_match| if is_match { Some(*selector) } else { None })
.transpose()
})
.collect::<Result<Vec<&Selector>, Error>>()
}
/// Evaluates a component moniker against a list of component selectors, returning
/// all of the component selectors which are matches for that moniker.
///
/// Requires: moniker is not empty.
/// component_selectors contains valid ComponentSelectors.
pub fn match_moniker_against_component_selectors<'a, T>(
moniker: &[String],
selectors: &'a [T],
) -> Result<Vec<&'a ComponentSelector>, Error>
where
T: Borrow<ComponentSelector> + 'a,
{
if moniker.is_empty() {
return Err(format_err!(
"Cannot have empty monikers, at least the component name is required."
));
}
let component_selectors = selectors
.iter()
.map(|selector| {
let component_selector = selector.borrow();
validate_component_selector(component_selector)?;
Ok(component_selector)
})
.collect::<Result<Vec<&ComponentSelector>, Error>>();
component_selectors?
.iter()
.filter_map(|selector| {
match_moniker_against_component_selector(moniker, selector)
.map(|is_match| if is_match { Some(selector.clone()) } else { None })
.transpose()
})
.collect::<Result<Vec<&ComponentSelector>, Error>>()
}
/// Format a |Selector| as a string.
///
/// Returns the formatted |Selector|, or an error if the |Selector| is invalid.
///
/// Note that the output will always include both a component and tree selector. If your input is
/// simply "moniker" you will likely see "moniker:root" as many clients implicitly append "root" if
/// it is not present (e.g. iquery).
pub fn selector_to_string(selector: Selector) -> Result<String, Error> {
validate_selector(&selector)?;
let component_selector =
selector.component_selector.ok_or_else(|| format_err!("component selector missing"))?;
let (node_path, maybe_property_selector) = match selector
.tree_selector
.ok_or_else(|| format_err!("tree selector missing"))?
{
TreeSelector::SubtreeSelector(SubtreeSelector { node_path, .. }) => (node_path, None),
TreeSelector::PropertySelector(PropertySelector {
node_path, target_properties, ..
}) => (node_path, Some(target_properties)),
_ => return Err(format_err!("unknown tree selector type")),
};
let mut segments = vec![];
let escape_special_chars = |val: &str| {
let mut ret = String::with_capacity(val.len());
for c in val.chars() {
if is_special_character(c) {
ret.push('\\');
}
ret.push(c);
}
ret
};
let process_string_selector_vector = |v: Vec<StringSelector>| -> Result<String, Error> {
Ok(v.into_iter()
.map(|segment| match segment {
StringSelector::StringPattern(s) => Ok(s),
StringSelector::ExactMatch(s) => Ok(escape_special_chars(&s)),
_ => {
return Err(format_err!("Unknown string selector type"));
}
})
.collect::<Result<Vec<_>, Error>>()?
.join("/"))
};
// Create the component moniker
segments.push(process_string_selector_vector(
component_selector
.moniker_segments
.ok_or_else(|| format_err!("component selector missing moniker"))?,
)?);
// Create the node selector
segments.push(process_string_selector_vector(node_path)?);
if let Some(property_selector) = maybe_property_selector {
segments.push(process_string_selector_vector(vec![property_selector])?);
}
Ok(segments.join(":"))
}
/// Matches a string against a single StringSelector.
/// This will only match against a single "level" and does not support recursive globbing.
pub fn match_selector_against_single_node(
node: &impl AsRef<str>,
selector: &StringSelector,
) -> Result<bool, Error> {
let regex = Regex::new(&format!(
"^{}$",
convert_string_selector_to_regex(selector, WILDCARD_REGEX_EQUIVALENT, None)?
))?;
Ok(regex.is_match(&sanitize_string_for_selectors(node.as_ref())))
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::File;
use std::io::prelude::*;
use tempfile::TempDir;
#[test]
fn canonical_component_selector_test() {
let test_vector = vec![
(
"a/b/c",
StringSelector::StringPattern("a".to_string()),
StringSelector::StringPattern("b".to_string()),
StringSelector::StringPattern("c".to_string()),
),
(
"a/*/c",
StringSelector::StringPattern("a".to_string()),
StringSelector::StringPattern("*".to_string()),
StringSelector::StringPattern("c".to_string()),
),
(
"a/b*/c",
StringSelector::StringPattern("a".to_string()),
StringSelector::StringPattern("b*".to_string()),
StringSelector::StringPattern("c".to_string()),
),
(
r#"a/b\*/c"#,
StringSelector::StringPattern("a".to_string()),
StringSelector::StringPattern(r#"b\*"#.to_string()),
StringSelector::StringPattern("c".to_string()),
),
(
r#"a/\*/c"#,
StringSelector::StringPattern("a".to_string()),
StringSelector::StringPattern(r#"\*"#.to_string()),
StringSelector::StringPattern("c".to_string()),
),
(
"a/b/**",
StringSelector::StringPattern("a".to_string()),
StringSelector::StringPattern("b".to_string()),
StringSelector::StringPattern("**".to_string()),
),
];
for (test_string, first_path_node, second_path_node, target_component) in test_vector {
let component_selector = parse_component_selector(&test_string).unwrap();
match component_selector.moniker_segments.as_ref().unwrap().as_slice() {
[first, second, third] => {
assert_eq!(*first, first_path_node);
assert_eq!(*second, second_path_node);
assert_eq!(*third, target_component);
}
_ => unreachable!(),
}
}
}
#[test]
fn missing_path_component_selector_test() {
let component_selector_string = "c";
let component_selector =
parse_component_selector(&component_selector_string.to_string()).unwrap();
let mut path_vec = component_selector.moniker_segments.unwrap();
assert_eq!(path_vec.pop(), Some(StringSelector::StringPattern("c".to_string())));
assert!(path_vec.is_empty());
}
#[test]
fn path_components_have_spaces_as_names_selector_test() {
let component_selector_string = " ";
let component_selector =
parse_component_selector(&component_selector_string.to_string()).unwrap();
let mut path_vec = component_selector.moniker_segments.unwrap();
assert_eq!(path_vec.pop(), Some(StringSelector::StringPattern(" ".to_string())));
assert!(path_vec.is_empty());
}
#[test]
fn errorful_component_selector_test() {
let test_vector: Vec<String> = vec![
"".to_string(),
"a\\".to_string(),
r#"a/b***/c"#.to_string(),
r#"a/***/c"#.to_string(),
r#"a/**/c"#.to_string(),
// supported? r#"a/*/b/**"#.to_string(),
];
for test_string in test_vector {
let component_selector_result = parse_component_selector(&test_string);
assert!(component_selector_result.is_err());
}
}
#[test]
fn canonical_tree_selector_test() {
let test_vector = vec![
(
"a/b",
Some("c"),
StringSelector::StringPattern("a".to_string()),
StringSelector::StringPattern("b".to_string()),
Some(StringSelector::StringPattern("c".to_string())),
),
(
"a/*",
Some("c"),
StringSelector::StringPattern("a".to_string()),
StringSelector::StringPattern("*".to_string()),
Some(StringSelector::StringPattern("c".to_string())),
),
(
"a/b",
Some("*"),
StringSelector::StringPattern("a".to_string()),
StringSelector::StringPattern("b".to_string()),
Some(StringSelector::StringPattern("*".to_string())),
),
(
"a/b",
None,
StringSelector::StringPattern("a".to_string()),
StringSelector::StringPattern("b".to_string()),
None,
),
];
for (
test_node_path,
test_target_property,
first_path_node,
second_path_node,
parsed_property,
) in test_vector
{
let tree_selector = parse_tree_selector(test_node_path, test_target_property).unwrap();
match tree_selector {
TreeSelector::SubtreeSelector(tree_selector) => {
match tree_selector.node_path.as_slice() {
[first, second] => {
assert_eq!(*first, first_path_node);
assert_eq!(*second, second_path_node);
}
_ => unreachable!(),
}
}
TreeSelector::PropertySelector(tree_selector) => {
assert_eq!(tree_selector.target_properties, parsed_property.unwrap());
match tree_selector.node_path.as_slice() {
[first, second] => {
assert_eq!(*first, first_path_node);
assert_eq!(*second, second_path_node);
}
_ => unreachable!(),
}
}
_ => unreachable!(),
}
}
}
#[test]
fn errorful_tree_selector_test() {
let test_vector = vec![
// Not allowed due to empty property selector.
("a/b", Some("")),
// Not allowed due to glob property selector.
("a/b", Some("**")),
// Not allowed due to escape-char without a thing to escape.
(r#"a/b\"#, Some("c")),
// String literals can't have globs.
(r#"a/b**"#, Some("c")),
// Property selector string literals cant have globs.
(r#"a/b"#, Some("c**")),
("a/b", Some("**")),
// Node path cant have globs.
("a/**", Some("c")),
("", Some("c")),
];
for (test_nodepath, test_target_property) in test_vector {
let tree_selector_result = parse_tree_selector(test_nodepath, test_target_property);
assert!(tree_selector_result.is_err());
}
}
#[test]
fn successful_selector_parsing() {
let tempdir = TempDir::new().expect("failed to create tmp dir");
File::create(tempdir.path().join("a.txt"))
.expect("create file")
.write_all(
b"a:b:c
",
)
.expect("writing test file");
File::create(tempdir.path().join("b.txt"))
.expect("create file")
.write_all(b"a*/b:c/d/*:*")
.expect("writing test file");
assert!(parse_selectors(tempdir.path()).is_ok());
}
#[test]
fn unsuccessful_selector_parsing_bad_selector() {
let tempdir = TempDir::new().expect("failed to create tmp dir");
File::create(tempdir.path().join("a.txt"))
.expect("create file")
.write_all(b"a:b:c")
.expect("writing test file");
File::create(tempdir.path().join("b.txt"))
.expect("create file")
.write_all(b"**:**:**")
.expect("writing test file");
assert!(parse_selectors(tempdir.path()).is_err());
}
#[test]
fn unsuccessful_selector_parsing_line_with_only_whitespace() {
let tempdir = TempDir::new().expect("failed to create tmp dir");
// Hard to tell, but the "empty" line contains whitespace, which is invalid.
File::create(tempdir.path().join("a.txt"))
.expect("create file")
.write_all(
b"a:b:c
",
)
.expect("writing test file");
assert!(parse_selectors(tempdir.path()).is_err());
}
#[test]
fn unsuccessful_selector_parsing_nonflat_dir() {
let tempdir = TempDir::new().expect("failed to create tmp dir");
File::create(tempdir.path().join("a.txt"))
.expect("create file")
.write_all(b"a:b:c")
.expect("writing test file");
File::create(tempdir.path().join("b.txt"))
.expect("create file")
.write_all(b"**:**:**")
.expect("writing test file");
std::fs::create_dir_all(tempdir.path().join("nested")).expect("make nested");
File::create(tempdir.path().join("nested/c.txt"))
.expect("create file")
.write_all(b"**:**:**")
.expect("writing test file");
assert!(parse_selectors(tempdir.path()).is_err());
}
#[test]
fn canonical_path_regex_transpilation_test() {
// Note: We provide the full selector syntax but this test is only transpiling
// the node-path of the selector, and validating against that.
let test_cases = vec![
(r#"echo.cmx:a/*/c:*"#, vec!["a", "b", "c"]),
(r#"echo.cmx:a/*/*/*/*/c:*"#, vec!["a", "b", "g", "e", "d", "c"]),
(r#"echo.cmx:*/*/*/*/*/*/*:*"#, vec!["a", "b", "/c", "d", "e*", "f"]),
(r#"echo.cmx:a/*/*/d/*/*:*"#, vec!["a", "b", "/c", "d", "e*", "f"]),
(r#"echo.cmx:a/*/\/c/d/e\*/*:*"#, vec!["a", "b", "/c", "d", "e*", "f"]),
(r#"echo.cmx:a/b*/c:*"#, vec!["a", "bob", "c"]),
(r#"echo.cmx:a/b/c"#, vec!["a", "b", "c"]),
(r#"echo.cmx:a/b/c"#, vec!["a", "b", "c", "/"]),
(r#"echo.cmx:a/b/c"#, vec!["a", "b", "c", "d"]),
(r#"echo.cmx:a/b/c"#, vec!["a", "b", "c", "d", "e"]),
];
for (selector, string_to_match) in test_cases {
let mut sanitized_node_path = string_to_match
.iter()
.map(|s| sanitize_string_for_selectors(s))
.collect::<Vec<String>>()
.join("/");
// We must append a "/" because the absolute monikers end in slash and
// hierarchy node paths don't, but we want to reuse the regex logic.
sanitized_node_path.push('/');
let parsed_selector = parse_selector(selector).unwrap();
let tree_selector = parsed_selector.tree_selector.unwrap();
match tree_selector {
TreeSelector::SubtreeSelector(tree_selector) => {
let node_path = tree_selector.node_path;
let selector_regex =
Regex::new(&convert_path_selector_to_regex(&node_path, true).unwrap())
.unwrap();
assert!(selector_regex.is_match(&sanitized_node_path));
}
TreeSelector::PropertySelector(tree_selector) => {
let node_path = tree_selector.node_path;
let selector_regex =
Regex::new(&convert_path_selector_to_regex(&node_path, false).unwrap())
.unwrap();
assert!(selector_regex.is_match(&sanitized_node_path));
}
_ => unreachable!(),
}
}
}
#[test]
fn failing_path_regex_transpilation_test() {
// Note: We provide the full selector syntax but this test is only transpiling
// the node-path of the tree selector, and valdating against that.
let test_cases = vec![
// Failing because it's missing a required "d" directory node in the string.
(r#"echo.cmx:a/*/d/*/f:*"#, vec!["a", "b", "c", "e", "f"]),
// Failing because the match string doesnt end at the c node.
(r#"echo.cmx:a/*/*/*/*/*/c:*"#, vec!["a", "b", "g", "e", "d", "f"]),
(r#"echo.cmx:a/b/c"#, vec!["a", "b"]),
(r#"echo.cmx:a/b/c"#, vec!["a", "b", "card"]),
(r#"echo.cmx:a/b/c"#, vec!["a", "b", "c/"]),
];
for (selector, string_to_match) in test_cases {
let mut sanitized_node_path = string_to_match
.iter()
.map(|s| sanitize_string_for_selectors(s))
.collect::<Vec<String>>()
.join("/");
// We must append a "/" because the absolute monikers end in slash and
// hierarchy node paths don't, but we want to reuse the regex logic.
sanitized_node_path.push('/');
let parsed_selector = parse_selector(selector).unwrap();
let tree_selector = parsed_selector.tree_selector.unwrap();
match tree_selector {
TreeSelector::SubtreeSelector(tree_selector) => {
let node_path = tree_selector.node_path;
let selector_regex =
Regex::new(&convert_path_selector_to_regex(&node_path, true).unwrap())
.unwrap();
assert!(!selector_regex.is_match(&sanitized_node_path));
}
TreeSelector::PropertySelector(tree_selector) => {
let node_path = tree_selector.node_path;
let selector_regex =
Regex::new(&convert_path_selector_to_regex(&node_path, false).unwrap())
.unwrap();
assert!(!selector_regex.is_match(&sanitized_node_path));
}
_ => unreachable!(),
}
}
}
#[test]
fn canonical_property_regex_transpilation_test() {
// Note: We provide the full selector syntax but this test is only transpiling
// the property of the selector, and validating against that.
let test_cases = vec![
(r#"echo.cmx:a:*"#, r#"a"#),
(r#"echo.cmx:a:bob"#, r#"bob"#),
(r#"echo.cmx:a:b*"#, r#"bob"#),
(r#"echo.cmx:a:\*"#, r#"*"#),
];
for (selector, string_to_match) in test_cases {
let parsed_selector = parse_selector(selector).unwrap();
let tree_selector = parsed_selector.tree_selector.unwrap();
match tree_selector {
TreeSelector::SubtreeSelector(_) => {
unreachable!("Subtree selectors don't test property selection.")
}
TreeSelector::PropertySelector(tree_selector) => {
let property_selector = tree_selector.target_properties;
let selector_regex = Regex::new(
&convert_property_selector_to_regex(&property_selector).unwrap(),
)
.unwrap();
assert!(
selector_regex.is_match(&sanitize_string_for_selectors(string_to_match))
);
}
_ => unreachable!(),
}
}
}
#[test]
fn failing_property_regex_transpilation_test() {
// Note: We provide the full selector syntax but this test is only transpiling
// the node-path of the tree selector, and valdating against that.
let test_cases = vec![
(r#"echo.cmx:a:c"#, r#"d"#),
(r#"echo.cmx:a:bob"#, r#"thebob"#),
(r#"echo.cmx:a:c"#, r#"cdog"#),
];
for (selector, string_to_match) in test_cases {
let parsed_selector = parse_selector(selector).unwrap();
let tree_selector = parsed_selector.tree_selector.unwrap();
match tree_selector {
TreeSelector::SubtreeSelector(_) => {
unreachable!("Subtree selectors don't test property selection.")
}
TreeSelector::PropertySelector(tree_selector) => {
let target_properties = tree_selector.target_properties;
let selector_regex = Regex::new(
&convert_property_selector_to_regex(&target_properties).unwrap(),
)
.unwrap();
assert!(
!selector_regex.is_match(&sanitize_string_for_selectors(string_to_match))
);
}
_ => unreachable!(),
}
}
}
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"#),
]
};
}
#[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 doesnt look at them.
(vec![r#"a"#], r#"**"#),
(vec![r#"a"#], r#"/"#),
];
fn create_tree_selector(node_path: &Vec<&str>, property: &str) -> TreeSelector {
let node_path = node_path
.iter()
.map(|path_node_str| StringSelector::StringPattern(path_node_str.to_string()))
.collect::<Vec<StringSelector>>();
let target_properties = StringSelector::StringPattern(property.to_string());
TreeSelector::PropertySelector(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!(validate_tree_selector(&tree_selector).is_ok());
}
for (node_path, property) in SHARED_FAILING_TEST_CASES.iter() {
let tree_selector = create_tree_selector(node_path, property);
assert!(
validate_tree_selector(&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!(
validate_tree_selector(&tree_selector).is_err(),
"Failed to validate tree selector: {:?}",
tree_selector
);
}
}
#[test]
fn component_selector_validator_test() {
fn create_component_selector(component_moniker: &Vec<&str>) -> ComponentSelector {
let mut component_selector = ComponentSelector::EMPTY;
component_selector.moniker_segments = Some(
component_moniker
.into_iter()
.map(|path_node_str| StringSelector::StringPattern(path_node_str.to_string()))
.collect::<Vec<StringSelector>>(),
);
component_selector
}
for (component_moniker, _) in SHARED_PASSING_TEST_CASES.iter() {
let component_selector = create_component_selector(component_moniker);
assert!(validate_component_selector(&component_selector).is_ok());
}
for (component_moniker, _) in SHARED_FAILING_TEST_CASES.iter() {
let component_selector = create_component_selector(component_moniker);
assert!(
validate_component_selector(&component_selector).is_err(),
"Failed to validate component selector: {:?}",
component_selector
);
}
}
#[test]
fn component_selector_match_test() {
// Note: We provide the full selector syntax but this test is only validating it
// against the provided moniker
let passing_test_cases = vec![
(r#"echo.cmx:*:*"#, vec!["echo.cmx"]),
(r#"*/echo.cmx:*:*"#, vec!["abc", "echo.cmx"]),
(r#"ab*/echo.cmx:*:*"#, vec!["abc", "echo.cmx"]),
(r#"ab*/echo.cmx:*:*"#, vec!["abcde", "echo.cmx"]),
(r#"*/ab*/echo.cmx:*:*"#, vec!["123", "abcde", "echo.cmx"]),
(r#"a\/\*/echo.cmx:*:*"#, vec!["a/*", "echo.cmx"]),
(r#"echo.cmx*:*:*"#, vec!["echo.cmx"]),
(r#"a/echo*.cmx:*:*"#, vec!["a", "echo1.cmx"]),
(r#"a/echo*.cmx:*:*"#, vec!["a", "echo.cmx"]),
(r#"ab*/echo.cmx:*:*"#, vec!["ab", "echo.cmx"]),
(r#"a/**:*:*"#, vec!["a", "echo.cmx"]),
(r#"a/**:*:*"#, vec!["a", "b", "echo.cmx"]),
];
for (selector, moniker) in passing_test_cases {
let parsed_selector = parse_selector(selector).unwrap();
assert!(
match_component_moniker_against_selector(&moniker, &parsed_selector).unwrap(),
"Selector {:?} failed to match {:?}",
selector,
moniker
);
}
// Note: We provide the full selector syntax but this test is only validating it
// against the provided moniker
let failing_test_cases = vec![
(r#"*:*:*"#, vec!["a", "echo.cmx"]),
(r#"*/echo.cmx:*:*"#, vec!["123", "abc", "echo.cmx"]),
(r#"a/**:*:*"#, vec!["b", "echo.cmx"]),
(r#"e/**:*:*"#, vec!["echo.cmx"]),
];
for (selector, moniker) in failing_test_cases {
let parsed_selector = parse_selector(selector).unwrap();
assert!(
!match_component_moniker_against_selector(&moniker, &parsed_selector).unwrap(),
"Selector {:?} matched {:?}, but was expected to fail",
selector,
moniker
);
}
}
#[test]
fn multiple_component_selectors_match_test() {
let selectors = vec![r#"*/echo.cmx"#, r#"ab*/echo.cmx"#, r#"abc/m*"#];
let moniker = vec!["abc".to_string(), "echo.cmx".to_string()];
let component_selectors = selectors
.into_iter()
.map(|selector| parse_component_selector(&selector.to_string()).unwrap())
.collect::<Vec<_>>();
let match_res =
match_moniker_against_component_selectors(moniker.as_slice(), &component_selectors[..]);
assert!(match_res.is_ok());
assert_eq!(match_res.unwrap().len(), 2);
}
#[test]
fn parse_path_to_moniker_test() {
assert_eq!(
parse_path_to_moniker("/hub/r/sys/1234/c/my.cmx/1/out/diagnostics")
.expect("test moniker is parsable."),
vec!["sys", "my.cmx"]
);
assert_eq!(
parse_path_to_moniker("/hub/r/12345/28464/c/account_handler.cmx/29647/out/diagnostics")
.expect("test moniker is parsable."),
vec!["12345", "account_handler.cmx"]
);
assert_eq!(
parse_path_to_moniker("a/realm1/b/account_handler.cmx")
.expect("test moniker is parsable."),
vec!["a", "realm1", "b", "account_handler.cmx"]
);
}
#[test]
fn selector_to_string_test() {
// Check that parsing and formatting these selectors results in output identical to the
// original selector.
let cases = vec![
r#"moniker:root"#,
r#"my/component:root"#,
r#"my/component:root:a"#,
r#"a/b/c\*ff:root:a"#,
r#"a/child*:root:a"#,
r#"a/child:root/a/b/c"#,
r#"a/child:root/a/b/c:d"#,
r#"a/child:root/a/b/c*/d"#,
r#"a/child:root/a/b/c\*/d"#,
r#"a/child:root/a/b/c:d*"#,
r#"a/child:root/a/b/c:*d*"#,
r#"a/child:root/a/b/c:\*d*"#,
r#"a/child:root/a/b/c:\*d\:\*\\"#,
];
for input in cases {
let selector = parse_selector(input)
.unwrap_or_else(|e| panic!("Failed to parse '{}': {}", input, e));
let output = selector_to_string(selector).unwrap_or_else(|e| {
panic!("Failed to format parsed selector for '{}': {}", input, e)
});
assert_eq!(output, input);
}
}
#[test]
fn exact_match_selector_to_string() {
let selector = Selector {
component_selector: Some(ComponentSelector {
moniker_segments: Some(vec![StringSelector::ExactMatch("a*:".to_string())]),
..ComponentSelector::EMPTY
}),
tree_selector: Some(TreeSelector::SubtreeSelector(SubtreeSelector {
node_path: vec![StringSelector::ExactMatch("a*:".to_string())],
})),
..Selector::EMPTY
};
// Check we generate the expected string with escaping.
let selector_string = selector_to_string(selector).unwrap();
assert_eq!(r#"a\*\::a\*\:"#, selector_string);
// Parse the resultant selector, and check that it matches a moniker it is supposed to.
let parsed = parse_selector(&selector_string).unwrap();
assert!(match_moniker_against_component_selector(
&["a*:"],
parsed.component_selector.as_ref().unwrap()
)
.unwrap());
}
}