use regex::Regex;
use url::Url;
use super::Input;
#[derive(Clone, Debug)]
enum MatchType {
Any,
Value(Matcher),
}
impl MatchType {
fn matches_input(&self, input: Option<&str>) -> bool {
match self {
Self::Any => true,
Self::Value(val) => {
if let Some(input) = input {
val.is_match(input)
} else {
false
}
}
}
}
}
#[derive(Clone, Debug)]
pub enum Matcher {
Regex(Regex),
String(String),
}
impl Matcher {
fn is_match(&self, search: &str) -> bool {
match self {
Self::Regex(re) => re.is_match(search),
Self::String(s) => s == search,
}
}
}
impl From<Regex> for Matcher {
fn from(value: Regex) -> Self {
Self::Regex(value)
}
}
impl From<String> for Matcher {
fn from(value: String) -> Self {
Self::String(value)
}
}
impl From<&str> for Matcher {
fn from(value: &str) -> Self {
Self::String(value.to_string())
}
}
#[derive(Clone, Debug)]
pub enum Rule {
Prefix(PrefixMatcher),
String(StringMatcher),
Url(UrlMatcher),
}
impl Rule {
pub fn prefix<S>(prefix: S) -> Self
where
S: Into<String>,
{
Self::Prefix(PrefixMatcher {
prefix: prefix.into(),
})
}
pub fn string<M>(s: M) -> Self
where
M: Into<Matcher>,
{
Self::String(StringMatcher {
match_type: MatchType::Value(s.into()),
})
}
pub fn any_string() -> Self {
Self::String(StringMatcher {
match_type: MatchType::Any,
})
}
pub fn any_url() -> Self {
Self::Url(UrlMatcher {
scheme: MatchType::Any,
domain: MatchType::Any,
})
}
pub fn url_scheme<M>(scheme: M) -> Self
where
M: Into<Matcher>,
{
Self::Url(UrlMatcher {
scheme: MatchType::Value(scheme.into()),
domain: MatchType::Any,
})
}
pub fn any_http() -> Self {
Self::Url(UrlMatcher {
scheme: MatchType::Value(http_regex().into()),
domain: MatchType::Any,
})
}
pub fn url_domain<M>(domain: M) -> Self
where
M: Into<Matcher>,
{
Self::Url(UrlMatcher {
scheme: MatchType::Any,
domain: MatchType::Value(domain.into()),
})
}
pub fn http_domain<M>(domain: M) -> Self
where
M: Into<Matcher>,
{
Self::Url(UrlMatcher {
scheme: MatchType::Value(http_regex().into()),
domain: MatchType::Value(domain.into()),
})
}
pub fn url<M1, M2>(scheme: M1, domain: M2) -> Self
where
M1: Into<Matcher>,
M2: Into<Matcher>,
{
Self::Url(UrlMatcher {
scheme: MatchType::Value(scheme.into()),
domain: MatchType::Value(domain.into()),
})
}
}
#[expect(clippy::missing_panics_doc)]
pub fn http_regex() -> Regex {
Regex::new(r"^https?$").expect("invalid regex")
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PrefixMatcher {
pub(super) prefix: String,
}
impl PrefixMatcher {
pub(super) fn match_input(&self, input: &str) -> Option<String> {
if input.starts_with(&self.prefix) {
Some(input.replacen(&self.prefix, "", 1))
} else {
None
}
}
pub(super) fn matches_parsed(&self, input: &Input) -> bool {
input.prefix.as_deref() == Some(&self.prefix)
}
}
#[derive(Debug, Clone)]
pub struct StringMatcher {
match_type: MatchType,
}
impl StringMatcher {
pub(super) fn matches_input(&self, input: &str) -> bool {
self.match_type.matches_input(Some(input))
}
}
#[derive(Debug, Clone)]
pub struct UrlMatcher {
scheme: MatchType,
domain: MatchType,
}
impl UrlMatcher {
pub(super) fn matches_input(&self, input: &Url) -> bool {
self.scheme.matches_input(Some(input.scheme())) && self.domain.matches_input(input.domain())
}
}