cedar_policy_core/ast/
pattern.rsuse std::sync::Arc;
#[cfg(feature = "protobufs")]
use crate::ast::proto;
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize, Hash, Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum PatternElem {
Char(char),
Wildcard,
}
#[cfg(feature = "protobufs")]
impl From<&proto::expr::like::PatternElem> for PatternElem {
#[allow(clippy::expect_used)]
fn from(v: &proto::expr::like::PatternElem) -> Self {
match v
.data
.as_ref()
.expect("`as_ref()` for field that should exist")
{
proto::expr::like::pattern_elem::Data::C(c) => {
PatternElem::Char(c.chars().next().expect("c is non-empty"))
}
proto::expr::like::pattern_elem::Data::Ty(ty) => {
match proto::expr::like::pattern_elem::Ty::try_from(ty.to_owned())
.expect("decode should succeed")
{
proto::expr::like::pattern_elem::Ty::Wildcard => PatternElem::Wildcard,
}
}
}
}
}
#[cfg(feature = "protobufs")]
impl From<&PatternElem> for proto::expr::like::PatternElem {
fn from(v: &PatternElem) -> Self {
match v {
PatternElem::Char(c) => Self {
data: Some(proto::expr::like::pattern_elem::Data::C(c.to_string())),
},
PatternElem::Wildcard => Self {
data: Some(proto::expr::like::pattern_elem::Data::Ty(
proto::expr::like::pattern_elem::Ty::Wildcard.into(),
)),
},
}
}
}
#[derive(Debug, Clone, Hash, Eq, PartialEq, Serialize, Deserialize)]
#[serde(transparent)]
pub struct Pattern {
elems: Arc<Vec<PatternElem>>,
}
impl Pattern {
fn new(elems: Arc<Vec<PatternElem>>) -> Self {
Self { elems }
}
pub fn get_elems(&self) -> &[PatternElem] {
&self.elems
}
pub fn iter(&self) -> impl Iterator<Item = &PatternElem> {
self.elems.iter()
}
pub fn len(&self) -> usize {
self.elems.len()
}
}
impl From<Arc<Vec<PatternElem>>> for Pattern {
fn from(value: Arc<Vec<PatternElem>>) -> Self {
Self::new(value)
}
}
impl From<Vec<PatternElem>> for Pattern {
fn from(value: Vec<PatternElem>) -> Self {
Self::new(Arc::new(value))
}
}
impl FromIterator<PatternElem> for Pattern {
fn from_iter<T: IntoIterator<Item = PatternElem>>(iter: T) -> Self {
Self::new(Arc::new(iter.into_iter().collect()))
}
}
impl std::fmt::Display for Pattern {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for pc in self.elems.as_ref() {
match pc {
PatternElem::Char('*') => write!(f, r#"\*"#)?,
PatternElem::Char(c) => write!(f, "{}", c.escape_debug())?,
PatternElem::Wildcard => write!(f, r#"*"#)?,
}
}
Ok(())
}
}
impl PatternElem {
fn match_char(&self, text_char: &char) -> bool {
match self {
PatternElem::Char(c) => text_char == c,
PatternElem::Wildcard => true,
}
}
fn is_wildcard(&self) -> bool {
matches!(self, PatternElem::Wildcard)
}
}
impl Pattern {
pub fn wildcard_match(&self, text: &str) -> bool {
let pattern = self.get_elems();
if pattern.is_empty() {
return text.is_empty();
}
let text: Vec<char> = text.chars().collect();
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();
let pattern_len = pattern.len();
while i < text_len && (!contains_star || star_idx != pattern_len - 1) {
#[allow(clippy::indexing_slicing)]
if j < pattern_len && pattern[j].is_wildcard() {
contains_star = true;
star_idx = j;
tmp_idx = i;
j += 1;
} else if j < pattern_len && pattern[j].match_char(&text[i]) {
i += 1;
j += 1;
} else if contains_star {
j = star_idx + 1;
i = tmp_idx + 1;
tmp_idx = i;
} else {
return false;
}
}
#[allow(clippy::indexing_slicing)]
while j < pattern_len && pattern[j].is_wildcard() {
j += 1;
}
j == pattern_len
}
}
#[cfg(test)]
mod test {
use super::*;
impl std::ops::Add for Pattern {
type Output = Pattern;
fn add(self, rhs: Self) -> Self::Output {
let elems = [self.get_elems(), rhs.get_elems()].concat();
Pattern::from(elems)
}
}
fn string_map(text: &str) -> Pattern {
text.chars().map(PatternElem::Char).collect()
}
fn star() -> Pattern {
Pattern::from(vec![PatternElem::Wildcard])
}
fn empty() -> Pattern {
Pattern::from(vec![])
}
#[test]
fn test_wildcard_match_basic() {
assert!((string_map("foo") + star()).wildcard_match("foo bar"));
assert!((star() + string_map("bar")).wildcard_match("foo bar"));
assert!((star() + string_map("o b") + star()).wildcard_match("foo bar"));
assert!((string_map("f") + star() + string_map(" bar")).wildcard_match("foo bar"));
assert!((string_map("f") + star() + star() + string_map("r")).wildcard_match("foo bar"));
assert!((star() + string_map("f") + star() + star() + star()).wildcard_match("foo bar"));
assert!(!(star() + string_map("foo")).wildcard_match("foo bar"));
assert!(!(string_map("bar") + star()).wildcard_match("foo bar"));
assert!(!(star() + string_map("bo") + star()).wildcard_match("foo bar"));
assert!(!(string_map("f") + star() + string_map("br")).wildcard_match("foo bar"));
assert!(!(star() + string_map("x") + star() + star() + star()).wildcard_match("foo bar"));
assert!(!empty().wildcard_match("foo bar"));
assert!(empty().wildcard_match(""));
assert!(star().wildcard_match(""));
assert!(!string_map("foo bar").wildcard_match(""));
assert!(string_map("*").wildcard_match("*"));
assert!(star().wildcard_match("*"));
assert!(!string_map("\u{0000}").wildcard_match("*"));
assert!(!string_map(r"\u{0000}").wildcard_match("*"));
}
#[test]
fn test_wildcard_match_unicode() {
assert!((string_map("y") + star()).wildcard_match("y̆"));
assert!(string_map("y̆").wildcard_match("y̆"));
assert!(!(star() + string_map("p") + star()).wildcard_match("y̆"));
assert!((star() + string_map("p") + star()).wildcard_match("ḛ̶͑͝x̶͔͛a̵̰̯͛m̴͉̋́p̷̠͂l̵͇̍̔ȩ̶̣͝"));
assert!((star() + string_map("a̵̰̯͛m̴͉̋́") + star()).wildcard_match("ḛ̶͑͝x̶͔͛a̵̰̯͛m̴͉̋́p̷̠͂l̵͇̍̔ȩ̶̣͝"));
assert!(!(string_map("y") + star()).wildcard_match("ḛ̶͑͝x̶͔͛a̵̰̯͛m̴͉̋́p̷̠͂l̵͇̍̔ȩ̶̣͝"));
}
}