feat: Possibility to put an array of expressions and they must all matches

Signed-off-by: Awiteb <a@4rs.nl>
This commit is contained in:
Awiteb 2024-11-17 17:53:21 +00:00
parent c96b859931
commit cc2f8a791b
Signed by: awiteb
GPG key ID: 3F6B55640AA6682F
2 changed files with 66 additions and 35 deletions

View file

@ -51,40 +51,66 @@ where
Ok(url) Ok(url)
} }
/// Deserialize a vector of strings into a vector of `regex::Regex` /// Parse the `re` key in the table, which can be a string or an array of string
fn parse_re<'de, D>(toml_value: &Value) -> Result<Vec<String>, D::Error>
where
D: de::Deserializer<'de>,
{
match toml_value {
Value::String(str_re) => Ok(vec![str_re.to_owned()]),
Value::Array(re_vec) => {
re_vec
.iter()
.map(|str_re| {
str_re.as_str().map(String::from).ok_or_else(|| {
<D::Error as de::Error>::custom(format!(
"expected an array of string, found `{str_re}`"
))
})
})
.collect()
}
value => {
Err(<D::Error as de::Error>::custom(format!(
"expected a string value or an array of string for `re`, found `{value}`"
)))
}
}
}
/// Parse the vector of string regex to `Vec<Regex>`
fn parse_re_vec<'de, D>(re_vec: Vec<String>) -> Result<Vec<Regex>, D::Error>
where
D: de::Deserializer<'de>,
{
re_vec
.into_iter()
.map(|re| re.parse().map_err(custom_de_err::<'de, D>))
.collect()
}
/// Deserialize `RegexReason`
fn deserialize_regex_reason<'de, D>(deserializer: D) -> Result<Vec<RegexReason>, D::Error> fn deserialize_regex_reason<'de, D>(deserializer: D) -> Result<Vec<RegexReason>, D::Error>
where where
D: de::Deserializer<'de>, D: de::Deserializer<'de>,
{ {
let Ok(json_value) = Vec::<Value>::deserialize(deserializer) else { let Ok(toml_value) = Vec::<Value>::deserialize(deserializer) else {
return Err(de::Error::custom( return Err(de::Error::custom(
"expected an array of strings or tables with the keys `re` and optional `reason`", "expected an array contains strings or arrays of string or tables with the keys `re` \
and optional `reason`",
)); ));
}; };
json_value toml_value
.into_iter() .into_iter()
.map(|value| { .map(|value| {
if let Value::String(re) = value { if let Value::Table(table) = value {
Ok(RegexReason::new( let re_vec = table.get("re").map(parse_re::<D>).ok_or_else(|| {
re.parse().map_err(custom_de_err::<'de, D>)?, <D::Error as de::Error>::custom(
None, "The table must contain a `re` key with a string value or an array of \
)) string",
} else if let Value::Table(table) = value { )
let re = table })??;
.get("re")
.map(|re| {
re.as_str().map(String::from).ok_or_else(|| {
<D::Error as de::Error>::custom(format!(
"expected a string value for `re`, found `{re}`"
))
})
})
.ok_or_else(|| {
<D::Error as de::Error>::custom(
"The table must contain a `re` key with a string value",
)
})??;
let reason = table let reason = table
.get("reason") .get("reason")
.map(|reason| { .map(|reason| {
@ -103,14 +129,16 @@ where
} }
} }
Ok(RegexReason::new(parse_re_vec::<D>(re_vec)?, reason))
} else if matches!(value, Value::String(_) | Value::Array(_)) {
Ok(RegexReason::new( Ok(RegexReason::new(
re.parse().map_err(custom_de_err::<'de, D>)?, parse_re_vec::<D>(parse_re::<D>(&value)?)?,
reason, None,
)) ))
} else { } else {
Err(de::Error::custom(format!( Err(de::Error::custom(format!(
"unexpected value in the regex list, expected a string or a table with `re` \ "unexpected value in the regex list, expected a string or an array of string \
(string) and optional `reason` (string), found `{value}`" or a table with `re` (string) and optional `reason` (string), found `{value}`"
))) )))
} }
}) })
@ -149,7 +177,7 @@ pub struct Telegram {
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct RegexReason { pub struct RegexReason {
/// The regular expression /// The regular expression
pub re: Regex, pub re_vec: Vec<Regex>,
/// Optional reason /// Optional reason
pub reason: Option<String>, pub reason: Option<String>,
} }
@ -231,14 +259,16 @@ pub struct Config {
impl RegexReason { impl RegexReason {
/// Create a new `RegexReason` instance /// Create a new `RegexReason` instance
fn new(re: Regex, reason: Option<String>) -> Self { fn new(re: Vec<Regex>, reason: Option<String>) -> Self {
Self { re, reason } Self { re_vec: re, reason }
} }
} }
impl Display for RegexReason { impl Display for RegexReason {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.re).ok(); for re in &self.re_vec {
write!(f, "{re} ").ok();
}
if let Some(ref reason) = self.reason { if let Some(ref reason) = self.reason {
write!(f, " ({reason})").ok(); write!(f, " ({reason})").ok();
}; };

View file

@ -28,9 +28,10 @@ pub trait ExprChecker {
impl ExprChecker for Expr { impl ExprChecker for Expr {
fn is_match<'a>(&'a self, user: &ForgejoUser) -> Option<RegexReason> { fn is_match<'a>(&'a self, user: &ForgejoUser) -> Option<RegexReason> {
let one_of = |hay: &str, exprs: &'a Vec<RegexReason>| { let one_of = |hay: &str, exprs: &'a Vec<RegexReason>| {
exprs exprs.iter().find(|re| {
.iter() hay.split('\n')
.find(|re| hay.split('\n').any(|line| re.re.is_match(line.trim()))) .any(|line| re.re_vec.iter().all(|re| re.is_match(line.trim())))
})
}; };
[ [
one_of(&user.username, &self.usernames), one_of(&user.username, &self.usernames),