motorx_core/config/
match_type.rs1use std::fmt::Display;
2use std::hash::Hash;
3use std::{cmp::Ordering, str::FromStr};
4
5use once_cell::sync::Lazy;
6
7use regex::{Captures, Regex};
8
9#[cfg_attr(feature = "serde-config", derive(serde::Deserialize))]
10#[cfg_attr(feature = "serde-config", serde(rename_all = "snake_case"))]
11#[derive(Debug, Clone)]
12pub enum MatchType {
13 Start(String),
15 Contains(String),
17 #[cfg_attr(feature = "serde-config", serde(with = "de_regex"))]
19 Regex(Regex),
20}
21
22pub enum MatchResult<'t> {
23 Start(bool),
24 Contains(bool),
25 Regex(Option<Captures<'t>>),
26}
27
28impl<'t> MatchResult<'t> {
29 #[inline]
30 pub(crate) fn is_match(&self) -> bool {
31 match self {
32 MatchResult::Start(matched) => *matched,
33 MatchResult::Contains(matched) => *matched,
34 MatchResult::Regex(captures) => captures.is_some(),
35 }
36 }
37}
38
39impl MatchType {
40 #[inline]
41 pub(crate) fn matches<'a>(&self, string: &'a str) -> MatchResult<'a> {
42 match self {
43 MatchType::Start(pattern) => MatchResult::Start(string.starts_with(pattern)),
44 MatchType::Contains(pattern) => MatchResult::Contains(string.contains(pattern)),
45 MatchType::Regex(regex) => MatchResult::Regex(regex.captures(string)),
46 }
47 }
48
49 #[inline]
50 pub(crate) fn priority(&self) -> usize {
51 match self {
52 MatchType::Start(_) => usize::MIN,
54 MatchType::Contains(_) => 1,
55 MatchType::Regex(_) => usize::MAX,
57 }
58 }
59
60 #[inline]
61 fn length(&self) -> usize {
62 match self {
63 MatchType::Start(pat) => pat.len(),
64 MatchType::Contains(pat) => pat.len(),
65 MatchType::Regex(re) => re.as_str().len(),
66 }
67 }
68}
69
70impl PartialEq for MatchType {
71 fn eq(&self, other: &Self) -> bool {
72 match self {
73 MatchType::Start(pat) => match other {
74 MatchType::Start(other_pat) => pat == other_pat,
75 _ => false,
76 },
77 MatchType::Contains(pat) => match other {
78 MatchType::Contains(other_pat) => pat == other_pat,
79 _ => false,
80 },
81 MatchType::Regex(re) => match other {
82 MatchType::Regex(other_re) => re.as_str() == other_re.as_str(),
83 _ => false,
84 },
85 }
86 }
87}
88
89impl Eq for MatchType {}
90
91impl Ord for MatchType {
92 fn cmp(&self, other: &Self) -> Ordering {
93 match self.priority().cmp(&other.priority()) {
94 Ordering::Greater => Ordering::Greater,
95 Ordering::Less => Ordering::Less,
96 Ordering::Equal => {
97 other.length().cmp(&self.length())
100 }
101 }
102 }
103}
104
105impl PartialOrd for MatchType {
106 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
107 Some(self.cmp(other))
108 }
109}
110
111impl Display for MatchType {
112 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
113 match self {
114 MatchType::Start(path) => write!(f, "start({})", path),
115 MatchType::Contains(pat) => write!(f, "contains({})", pat),
116 MatchType::Regex(re) => write!(f, "regex({})", re.as_str()),
117 }
118 }
119}
120
121impl Hash for MatchType {
122 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
123 self.to_string().hash(state)
124 }
125}
126
127static MATCH_RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^regex\((.*)\)$").unwrap());
128static MATCH_CONTAINS: Lazy<Regex> = Lazy::new(|| Regex::new(r"^contains\((.*)\)$").unwrap());
129
130#[derive(Debug)]
131pub struct MatchTypeFromStrError(String);
132
133impl Display for MatchTypeFromStrError {
134 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135 self.0.fmt(f)
136 }
137}
138impl std::error::Error for MatchTypeFromStrError {}
139
140impl FromStr for MatchType {
141 type Err = MatchTypeFromStrError;
142
143 fn from_str(s: &str) -> Result<Self, Self::Err> {
144 if let Some(captures) = MATCH_RE.captures(s) {
145 captures
147 .get(1)
148 .map(|re| MatchType::Regex(Regex::new(re.into()).unwrap()))
149 .ok_or(MatchTypeFromStrError("".into()))
150 } else if let Some(captures) = MATCH_CONTAINS.captures(s) {
151 captures
153 .get(1)
154 .map(|pat| MatchType::Contains(pat.as_str().into()))
155 .ok_or(MatchTypeFromStrError("".into()))
156 } else {
157 Ok(MatchType::Start(s.into()))
159 }
160 }
161}
162
163#[cfg(feature = "serde-config")]
164mod de_regex {
165 use std::str::FromStr;
166
167 use regex::Regex;
168 use serde::{de::Visitor, Deserializer};
169
170 struct RegexVisitor;
171
172 impl<'de> Visitor<'de> for RegexVisitor {
173 type Value = Regex;
174
175 fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
176 write!(formatter, "A valid string of regex.")
177 }
178
179 fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
180 where
181 E: serde::de::Error,
182 {
183 Regex::from_str(v).map_err(|e| E::custom(e.to_string()))
184 }
185 }
186
187 pub(super) fn deserialize<'de, D: Deserializer<'de>>(
188 deserializer: D,
189 ) -> Result<Regex, D::Error> {
190 deserializer.deserialize_str(RegexVisitor)
191 }
192}