1use anni_artist::ArtistList;
2use once_cell::sync::Lazy;
3use regex::Regex;
4use serde::de::Error;
5use serde::{Deserialize, Deserializer};
6use std::str::FromStr;
7
8pub struct Validator(&'static str, fn(&str) -> ValidateResult);
9
10impl Validator {
11 #[inline]
12 pub fn name(&self) -> &'static str {
13 self.0
14 }
15
16 #[inline]
17 pub fn validate(&self, input: &str) -> ValidateResult {
18 self.1(input)
19 }
20}
21
22impl FromStr for Validator {
23 type Err = ();
24
25 fn from_str(s: &str) -> Result<Self, Self::Err> {
26 match s {
27 "number" => Ok(Self("number", number_validator)),
28 "trim" => Ok(Self("trim", trim_validator)),
29 "date" => Ok(Self("date", date_validator)),
30 "artist" => Ok(Self("artist", artist_validator)),
31 "dot" => Ok(Self("dot", middle_dot_validator)),
32 "tidle" => Ok(Self("tidle", tidal_validator)),
33 _ => Err(()),
34 }
35 }
36}
37
38impl<'de> Deserialize<'de> for Validator {
39 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
40 where
41 D: Deserializer<'de>,
42 {
43 let s = String::deserialize(deserializer)?;
44 Validator::from_str(s.as_str()).map_err(|_| D::Error::custom(s))
45 }
46}
47
48impl std::fmt::Debug for Validator {
49 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
50 write!(f, "{}", self.name())
51 }
52}
53
54#[derive(Default, Debug, Deserialize)]
55#[serde(transparent)]
56pub struct ValidatorList(Vec<Validator>);
57
58impl ValidatorList {
59 pub fn new(validators: &[&str]) -> Option<Self> {
60 validators
61 .iter()
62 .map(|v| Validator::from_str(v))
63 .collect::<Result<_, _>>()
64 .map(ValidatorList)
65 .ok()
66 }
67
68 pub fn validate(&self, input: &str) -> Vec<(&'static str, ValidateResult)> {
69 self.0
70 .iter()
71 .map(|v| (v.0, v.1(input)))
72 .filter(|v| !v.1.is_pass())
73 .collect()
74 }
75}
76
77pub enum ValidateResult {
78 Pass,
79 Warning(String),
80 Error(String),
81}
82
83impl ValidateResult {
84 fn pass() -> Self {
85 Self::Pass
86 }
87
88 fn pass_or(pass: bool, message: String) -> Self {
89 if pass {
90 Self::Pass
91 } else {
92 Self::Error(message)
93 }
94 }
95
96 fn warn(message: String) -> Self {
97 Self::Warning(message)
98 }
99
100 fn fail(message: String) -> Self {
101 Self::Error(message)
102 }
103
104 pub fn is_pass(&self) -> bool {
105 matches!(self, Self::Pass)
106 }
107
108 pub fn into_message(self) -> String {
109 match self {
110 Self::Warning(m) => m,
111 Self::Error(m) => m,
112 Self::Pass => unreachable!(),
113 }
114 }
115}
116
117pub fn number_validator(str: &str) -> ValidateResult {
118 let pass = str.chars().all(|c| c.is_numeric());
119 ValidateResult::pass_or(pass, "not a number".to_string())
120}
121
122pub fn trim_validator(str: &str) -> ValidateResult {
123 let mut is_start = true;
124 let mut is_empty = false;
125 for c in str.chars() {
126 is_empty = c.is_whitespace();
127 if is_start && is_empty {
128 break;
129 }
130 is_start = false;
131 }
132 let pass = !is_empty;
133 ValidateResult::pass_or(pass, "whitespaces need to be trimmed".to_string())
134}
135
136pub fn date_validator(str: &str) -> ValidateResult {
137 let mut mode = 0;
140 for c in str.chars() {
141 if mode > 9 || (!c.is_numeric() && c != '-') {
142 return ValidateResult::fail("invalid date".to_string());
143 }
144 if c == '-' {
145 if mode != 4 && mode != 7 {
146 return ValidateResult::fail("invalid date".to_string());
147 }
148 } else if !c.is_numeric() {
149 return ValidateResult::fail("invalid date".to_string());
150 }
151 mode += 1;
152 }
153 let is_year_month_day = mode == 10;
154 let is_year_month = mode == 7;
155 let is_year = mode == 4;
156 if is_year_month_day {
157 ValidateResult::pass()
158 } else if is_year_month {
159 ValidateResult::warn("Empty day field, could it be more accurate?".to_string())
160 } else if is_year {
161 ValidateResult::warn("Empty month and day fields, could it be more accurate?".to_string())
162 } else {
163 ValidateResult::fail("invalid date".to_string())
164 }
165}
166
167pub fn artist_validator(str: &str) -> ValidateResult {
168 match ArtistList::parse(str) {
169 Ok(_) => ValidateResult::pass(),
170 Err(err) => {
171 log::debug!("ArtistList parse error: {}", err);
172 ValidateResult::fail(err)
173 }
174 }
175}
176
177static DOTS: Lazy<Regex> = Lazy::new(|| {
178 Regex::new(r"[\u{00B7}\u{0387}\u{16eb}\u{2022}\u{2027}\u{2218}\u{2219}\u{22c5}\u{25e6}\u{2981}\u{2e30}\u{2e31}\u{ff65}\u{10101}]").unwrap()
179});
180
181pub fn middle_dot_validator(input: &str) -> ValidateResult {
183 let pass = !DOTS.is_match(input);
184 ValidateResult::pass_or(pass, "invalid dots detected".to_string())
185}
186
187pub fn middle_dot_replace(input: &str) -> String {
188 DOTS.replace_all(input, "\u{30fb}").to_string()
189}
190
191pub fn tidal_validator(input: &str) -> ValidateResult {
192 let pass = !input.contains('\u{301c}');
193 ValidateResult::pass_or(pass, "invalid tidal detected".to_string())
194}
195
196pub fn tidal_replace(input: &str) -> String {
197 input.replace('\u{301c}', "\u{ff5e}")
198}
199
200#[cfg(test)]
201mod tests {
202 use crate::validator::{
203 date_validator, middle_dot_replace, middle_dot_validator, trim_validator, ValidateResult,
204 };
205
206 #[test]
207 fn trim_exist() {
208 assert!(matches!(trim_validator(" 1234"), ValidateResult::Error(_)));
209 assert!(matches!(
210 trim_validator("1234 "),
211 ValidateResult::Error(_)
212 ));
213 assert!(matches!(trim_validator("\n1234"), ValidateResult::Error(_)));
214 }
215
216 #[test]
217 fn trim_not_exist() {
218 assert!(matches!(trim_validator("1234"), ValidateResult::Pass));
219 }
220
221 #[test]
222 fn test_date_validator() {
223 assert!(matches!(date_validator("2021-01-01"), ValidateResult::Pass));
224
225 assert!(matches!(
226 date_validator("2020-01-012"),
227 ValidateResult::Error(_)
228 ));
229 assert!(matches!(
230 date_validator("2020~01-01"),
231 ValidateResult::Error(_)
232 ));
233 assert!(matches!(date_validator("?"), ValidateResult::Error(_)));
234 }
235
236 #[test]
237 fn middle_dot_detect() {
238 assert!(matches!(middle_dot_validator("123"), ValidateResult::Pass));
239
240 assert!(matches!(
241 middle_dot_validator("\u{00B7}"),
242 ValidateResult::Error(_)
243 ));
244 assert!(matches!(
245 middle_dot_validator("\u{0387}"),
246 ValidateResult::Error(_)
247 ));
248 assert!(matches!(
249 middle_dot_validator("\u{16eb}"),
250 ValidateResult::Error(_)
251 ));
252 assert!(matches!(
253 middle_dot_validator("\u{2022}"),
254 ValidateResult::Error(_)
255 ));
256 assert!(matches!(
257 middle_dot_validator("\u{2027}"),
258 ValidateResult::Error(_)
259 ));
260 assert!(matches!(
261 middle_dot_validator("\u{2218}"),
262 ValidateResult::Error(_)
263 ));
264 assert!(matches!(
265 middle_dot_validator("\u{2219}"),
266 ValidateResult::Error(_)
267 ));
268 assert!(matches!(
269 middle_dot_validator("\u{22c5}"),
270 ValidateResult::Error(_)
271 ));
272 assert!(matches!(
273 middle_dot_validator("\u{25e6}"),
274 ValidateResult::Error(_)
275 ));
276 assert!(matches!(
277 middle_dot_validator("\u{2981}"),
278 ValidateResult::Error(_)
279 ));
280 assert!(matches!(
281 middle_dot_validator("\u{2e30}"),
282 ValidateResult::Error(_)
283 ));
284 assert!(matches!(
285 middle_dot_validator("\u{2e31}"),
286 ValidateResult::Error(_)
287 ));
288 assert!(matches!(
289 middle_dot_validator("\u{ff65}"),
290 ValidateResult::Error(_)
291 ));
292 assert!(matches!(
293 middle_dot_validator("\u{10101}"),
294 ValidateResult::Error(_)
295 ));
296 }
297
298 #[test]
299 fn middle_dot_replace_all() {
300 assert_eq!(
301 middle_dot_replace("1\u{00B7}2\u{0387}3\u{16eb}4\u{2022}5\u{2027}6\u{2218}7\u{2219}8\u{22c5}9\u{25e6}1\u{2981}2\u{2e30}3\u{2e31}4\u{ff65}5\u{10101}6"),
302 "1・2・3・4・5・6・7・8・9・1・2・3・4・5・6"
303 );
304 }
305}