1use std::cmp::Ordering;
2use std::collections::HashSet;
3
4use chrono::NaiveDate;
5use rex_db::ConnCache;
6use rex_db::models::TxType;
7use strum::IntoEnumIterator;
8
9use crate::conn::MutDbConn;
10use crate::ui_helper::{DateType, Field, Output, VerifierError, get_best_match};
11
12pub struct Verifier<'a> {
13 conn: MutDbConn<'a>,
14}
15
16impl<'a> Verifier<'a> {
17 pub(crate) fn new(conn: MutDbConn<'a>) -> Self {
18 Self { conn }
19 }
20
21 pub fn date(
37 self,
38 user_date: &mut String,
39 date_type: DateType,
40 ) -> Result<Output, VerifierError> {
41 if user_date.is_empty() {
42 return Ok(Output::Nothing(Field::Date));
43 }
44
45 *user_date = user_date
46 .chars()
47 .filter(|c| c.is_numeric() || *c == '-')
48 .collect();
49
50 let split_date = user_date
51 .split('-')
52 .map(ToString::to_string)
53 .collect::<Vec<String>>();
54
55 match date_type {
57 DateType::Exact => {
58 if split_date.len() != 3 {
59 *user_date = "2022-01-01".to_string();
60 return Err(VerifierError::InvalidDate);
61 }
62 }
63 DateType::Monthly => {
64 if split_date.len() != 2 {
65 *user_date = "2022-01".to_string();
66 return Err(VerifierError::InvalidDate);
67 }
68 }
69 DateType::Yearly => {
70 if split_date.len() != 1 {
71 *user_date = "2022".to_string();
72 return Err(VerifierError::InvalidDate);
73 }
74 }
75 }
76
77 let (int_month, int_day): (Option<u16>, Option<u16>) = match date_type {
78 DateType::Exact => {
79 let month = split_date[1]
80 .parse()
81 .map_err(|_| VerifierError::ParsingError(Field::Date))?;
82
83 let day = split_date[2]
84 .parse()
85 .map_err(|_| VerifierError::ParsingError(Field::Date))?;
86
87 (Some(month), Some(day))
88 }
89 DateType::Monthly => {
90 let month = split_date[1]
91 .parse()
92 .map_err(|_| VerifierError::ParsingError(Field::Date))?;
93
94 (Some(month), None)
95 }
96 DateType::Yearly => (None, None),
97 };
98
99 if split_date[0].len() != 4 {
102 match split_date[0].len().cmp(&4) {
103 Ordering::Less => match date_type {
104 DateType::Exact => {
105 *user_date = format!("2022-{}-{}", split_date[1], split_date[2]);
106 }
107 DateType::Monthly => *user_date = format!("2022-{}", split_date[1]),
108 DateType::Yearly => *user_date = "2022".to_string(),
109 },
110 Ordering::Greater => match date_type {
111 DateType::Exact => {
112 *user_date = format!(
113 "{}-{}-{}",
114 &split_date[0][..4],
115 split_date[1],
116 split_date[2]
117 );
118 }
119 DateType::Monthly => {
120 *user_date = format!("{}-{}", &split_date[0][..4], split_date[1]);
121 }
122 DateType::Yearly => *user_date = split_date[0][..4].to_string(),
123 },
124 Ordering::Equal => {}
125 }
126 return Err(VerifierError::InvalidYear);
127 }
128
129 match date_type {
132 DateType::Exact => {
133 if split_date[1].len() != 2 {
134 let unwrapped_month = int_month.unwrap();
135 if unwrapped_month < 10 {
136 *user_date =
137 format!("{}-0{unwrapped_month}-{}", split_date[0], split_date[2]);
138 } else if unwrapped_month > 12 {
139 *user_date = format!("{}-12-{}", split_date[0], split_date[2]);
140 }
141
142 return Err(VerifierError::InvalidMonth);
143 }
144 }
145 DateType::Monthly => {
146 let unwrapped_month = int_month.unwrap();
147 if split_date[1].len() != 2 {
148 if unwrapped_month < 10 {
149 *user_date = format!("{}-0{unwrapped_month}", split_date[0]);
150 } else if unwrapped_month > 12 {
151 *user_date = format!("{}-12", split_date[0]);
152 }
153
154 return Err(VerifierError::InvalidMonth);
155 }
156 }
157 DateType::Yearly => {}
158 }
159
160 if let DateType::Exact = date_type {
163 let unwrapped_day = int_day.unwrap();
164 if split_date[2].len() != 2 {
165 if unwrapped_day < 10 {
166 *user_date = format!("{}-{}-0{unwrapped_day}", split_date[0], split_date[1]);
167 } else if unwrapped_day > 31 {
168 *user_date = format!("{}-{}-31", split_date[0], split_date[1]);
169 }
170
171 return Err(VerifierError::InvalidDay);
172 }
173 }
174
175 match date_type {
177 DateType::Exact => {
178 let unwrapped_month = int_month.unwrap();
179 if !(1..=12).contains(&unwrapped_month) {
180 if unwrapped_month < 1 {
181 *user_date = format!("{}-01-{}", split_date[0], split_date[2]);
182 } else if unwrapped_month > 12 {
183 *user_date = format!("{}-12-{}", split_date[0], split_date[2]);
184 }
185
186 return Err(VerifierError::MonthTooBig);
187 }
188 }
189 DateType::Monthly => {
190 let unwrapped_month = int_month.unwrap();
191 if !(1..=12).contains(&unwrapped_month) {
192 if unwrapped_month < 1 {
193 *user_date = format!("{}-01", split_date[0]);
194 } else if unwrapped_month > 12 {
195 *user_date = format!("{}-12", split_date[0]);
196 }
197
198 return Err(VerifierError::MonthTooBig);
199 }
200 }
201 DateType::Yearly => {}
202 }
203
204 if let DateType::Exact = date_type {
206 let unwrapped_day = int_day.unwrap();
207 if !(1..=31).contains(&unwrapped_day) {
208 if unwrapped_day < 1 {
209 *user_date = format!("{}-{}-01", split_date[0], split_date[1]);
210 } else if unwrapped_day > 31 {
211 *user_date = format!("{}-{}-31", split_date[0], split_date[1]);
212 }
213
214 return Err(VerifierError::DayTooBig);
215 }
216 }
217
218 if let DateType::Exact = date_type {
221 NaiveDate::parse_from_str(user_date, "%Y-%m-%d")
222 .map_err(|_| VerifierError::NonExistingDate)?;
223 }
224
225 Ok(Output::Accepted(Field::Date))
226 }
227
228 pub fn amount(&self, user_amount: &mut String) -> Result<Output, VerifierError> {
238 if user_amount.is_empty() {
240 return Ok(Output::Nothing(Field::Amount));
241 }
242
243 let calc_symbols = vec!['*', '/', '+', '-'];
244
245 *user_amount = user_amount
246 .chars()
247 .filter(|c| c.is_numeric() || *c == '.' || calc_symbols.contains(c))
248 .collect();
249
250 if user_amount.is_empty() {
253 return Err(VerifierError::ParsingError(Field::Amount));
254 }
255
256 if calc_symbols.iter().any(|s| user_amount.contains(*s)) {
258 let count = user_amount
269 .chars()
270 .filter(|c| calc_symbols.contains(c))
271 .count();
272
273 let mut working_value = user_amount.to_owned();
275
276 for _i in 0..count {
277 for symbol in &calc_symbols {
278 if let Some(location) = working_value.find(*symbol) {
279 let mut first_value = String::new();
282 let mut last_value = String::new();
283
284 for char in working_value.chars().skip(location + 1) {
287 if calc_symbols.contains(&char) {
288 break;
289 }
290 last_value.push(char);
291 }
292
293 for char in working_value
295 .chars()
296 .rev()
297 .skip(working_value.len() - location)
298 {
299 if calc_symbols.contains(&char) {
300 break;
301 }
302 first_value.push(char);
303 }
304 first_value = first_value.chars().rev().collect();
306
307 let final_value = if first_value.is_empty() || last_value.is_empty() {
309 if first_value.is_empty() {
310 last_value.clone()
311 } else {
312 first_value.clone()
313 }
314 } else {
315 let first_num: f64 = match first_value.parse() {
317 Ok(v) => v,
318 Err(_) => {
319 return Err(VerifierError::ParsingError(Field::Amount));
320 }
321 };
322
323 let last_num: f64 = match last_value.parse() {
324 Ok(v) => v,
325 Err(_) => {
326 return Err(VerifierError::ParsingError(Field::Amount));
327 }
328 };
329
330 match *symbol {
331 '*' => format!("{:.2}", (first_num * last_num)),
332 '/' => format!("{:.2}", (first_num / last_num)),
333 '+' => format!("{:.2}", (first_num + last_num)),
334 '-' => format!("{:.2}", (first_num - last_num)),
335 _ => String::new(),
336 }
337 };
338
339 working_value = working_value
343 .replace(&format!("{first_value}{symbol}{last_value}"), &final_value);
344
345 break;
346 }
347 }
348 }
349 *user_amount = working_value;
350 }
351
352 if user_amount.contains('.') {
355 let state = user_amount.split('.').collect::<Vec<&str>>();
356 if state[1].is_empty() {
357 *user_amount += "00";
358 }
359 } else {
360 *user_amount = format!("{user_amount}.00");
361 }
362
363 let float_amount: f64 = user_amount
364 .parse()
365 .map_err(|_| VerifierError::ParsingError(Field::Amount))?;
366
367 if float_amount <= 0.0 {
368 *user_amount = format!("{:.2}", (float_amount - (float_amount * 2.0)));
369 return Err(VerifierError::AmountBelowZero);
370 }
371
372 if user_amount.contains('.') {
374 let split_amount = user_amount.split('.').collect::<Vec<&str>>();
375
376 match split_amount[1].len().cmp(&2) {
377 Ordering::Less => *user_amount = format!("{user_amount}0"),
378 Ordering::Greater => {
379 *user_amount = format!("{}.{}", split_amount[0], &split_amount[1][..2]);
380 }
381 Ordering::Equal => (),
382 }
383 }
384
385 let split_amount = user_amount.split('.').collect::<Vec<&str>>();
388
389 if split_amount[0].len() > 10 {
391 *user_amount = format!("{}.{}", &split_amount[0][..10], split_amount[1]);
392 }
393
394 Ok(Output::Accepted(Field::Amount))
395 }
396
397 pub fn tx_method(&self, user_method: &mut String) -> Result<Output, VerifierError> {
406 *user_method = user_method.trim().to_string();
409
410 if user_method.is_empty() {
412 return Ok(Output::Nothing(Field::TxMethod));
413 }
414
415 let all_tx_methods = self.conn.cache().get_methods();
416
417 for method in &all_tx_methods {
418 let method_name = &method.name;
419
420 if method_name.to_lowercase() == user_method.to_lowercase() {
421 *user_method = method_name.clone();
422 return Ok(Output::Accepted(Field::Amount));
423 }
424 }
425
426 let user_method_names = all_tx_methods
427 .iter()
428 .map(|m| m.name.clone())
429 .collect::<Vec<String>>();
430
431 let best_match = get_best_match(user_method, &user_method_names);
432
433 *user_method = best_match;
434
435 Err(VerifierError::InvalidTxMethod)
436 }
437
438 pub fn tx_type(&self, user_type: &mut String) -> Result<Output, VerifierError> {
444 let trimmed_input = user_type.trim();
445
446 if user_type.is_empty() {
447 return Ok(Output::Nothing(Field::TxType));
448 }
449
450 let tx_types = TxType::iter()
451 .map(|s| s.to_string())
452 .collect::<Vec<String>>();
453
454 let return_best_match = || {
455 let best_match = get_best_match(user_type, &tx_types);
456
457 if best_match == trimmed_input {
458 String::new()
459 } else {
460 best_match
461 }
462 };
463
464 let lowercase = user_type.to_lowercase();
465
466 if lowercase.len() <= 2 {
467 if lowercase.starts_with('e') {
468 *user_type = TxType::Expense.to_string();
469 } else if lowercase.starts_with('i') {
470 *user_type = TxType::Income.to_string();
471 } else if lowercase.starts_with('t') {
472 *user_type = TxType::Transfer.to_string();
473 } else if lowercase.starts_with("br") {
474 *user_type = TxType::BorrowRepay.to_string();
475 } else if lowercase.starts_with("lr") {
476 *user_type = TxType::LendRepay.to_string();
477 } else if lowercase.starts_with('b') {
478 *user_type = TxType::Borrow.to_string();
479 } else if lowercase.starts_with('l') {
480 *user_type = TxType::Lend.to_string();
481 } else {
482 *user_type = return_best_match();
483 return Err(VerifierError::InvalidTxType);
484 }
485 } else {
486 if tx_types.contains(user_type) {
487 return Ok(Output::Accepted(Field::TxType));
488 }
489 *user_type = return_best_match();
490 return Err(VerifierError::InvalidTxType);
491 }
492
493 Ok(Output::Accepted(Field::TxType))
494 }
495
496 pub fn tags(&self, user_tag: &mut String) {
500 let mut split_tags = user_tag.split(',').map(str::trim).collect::<Vec<&str>>();
501 split_tags.retain(|s| !s.is_empty());
502
503 let mut seen = HashSet::new();
504
505 let mut unique = Vec::new();
507
508 for item in split_tags {
509 if seen.insert(item) {
510 unique.push(item);
511 }
512 }
513
514 *user_tag = unique.join(", ");
515 }
516
517 pub fn tags_forced(&self, user_tag: &mut String) -> Result<Output, VerifierError> {
522 if user_tag.is_empty() {
523 return Ok(Output::Nothing(Field::Tags));
524 }
525
526 let all_tags = self.conn.cache().get_tags_set();
527
528 let mut split_tags = user_tag.split(',').map(str::trim).collect::<Vec<&str>>();
529 split_tags.retain(|s| !s.is_empty());
530
531 let mut seen = HashSet::new();
532 let mut unique = Vec::new();
533
534 for item in split_tags {
535 if seen.insert(item) {
536 unique.push(item);
537 }
538 }
539
540 let old_tags_len = unique.len();
541
542 unique.retain(|&tag| all_tags.contains(tag));
543
544 let new_tags_len = unique.len();
545
546 *user_tag = unique.join(", ");
547
548 if old_tags_len == new_tags_len {
549 Ok(Output::Accepted(Field::Tags))
550 } else {
551 Err(VerifierError::NonExistingTag)
552 }
553 }
554}