1use regex::Regex;
7
8#[derive(Debug, Clone)]
13pub struct CurrencyOptions {
14 pub symbol: String,
16 pub require_symbol: bool,
18 pub allow_space_after_symbol: bool,
20 pub symbol_after_digits: bool,
22 pub allow_negatives: bool,
24 pub parens_for_negatives: bool,
26 pub negative_sign_before_digits: bool,
28 pub negative_sign_after_digits: bool,
30 pub allow_negative_sign_placeholder: bool,
32 pub thousands_separator: char,
34 pub decimal_separator: char,
36 pub allow_decimal: bool,
38 pub require_decimal: bool,
40 pub digits_after_decimal: Vec<usize>,
42 pub allow_space_after_digits: bool,
44}
45
46impl Default for CurrencyOptions {
47 fn default() -> Self {
48 Self {
49 symbol: "$".to_string(),
50 require_symbol: false,
51 allow_space_after_symbol: false,
52 symbol_after_digits: false,
53 allow_negatives: true,
54 parens_for_negatives: false,
55 negative_sign_before_digits: false,
56 negative_sign_after_digits: false,
57 allow_negative_sign_placeholder: false,
58 thousands_separator: ',',
59 decimal_separator: '.',
60 allow_decimal: true,
61 require_decimal: false,
62 digits_after_decimal: vec![2],
63 allow_space_after_digits: false,
64 }
65 }
66}
67
68impl CurrencyOptions {
69 pub fn new() -> Self {
71 Self::default()
72 }
73
74 pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
76 self.symbol = symbol.into();
77 self
78 }
79
80 pub fn require_symbol(mut self, require: bool) -> Self {
82 self.require_symbol = require;
83 self
84 }
85
86 pub fn allow_space_after_symbol(mut self, allow: bool) -> Self {
88 self.allow_space_after_symbol = allow;
89 self
90 }
91
92 pub fn symbol_after_digits(mut self, after: bool) -> Self {
94 self.symbol_after_digits = after;
95 self
96 }
97
98 pub fn allow_negatives(mut self, allow: bool) -> Self {
100 self.allow_negatives = allow;
101 self
102 }
103
104 pub fn parens_for_negatives(mut self, use_parens: bool) -> Self {
106 self.parens_for_negatives = use_parens;
107 self
108 }
109
110 pub fn thousands_separator(mut self, sep: char) -> Self {
112 self.thousands_separator = sep;
113 self
114 }
115
116 pub fn decimal_separator(mut self, sep: char) -> Self {
118 self.decimal_separator = sep;
119 self
120 }
121
122 pub fn allow_decimal(mut self, allow: bool) -> Self {
124 self.allow_decimal = allow;
125 self
126 }
127
128 pub fn require_decimal(mut self, require: bool) -> Self {
130 self.require_decimal = require;
131 self
132 }
133
134 pub fn digits_after_decimal(mut self, digits: Vec<usize>) -> Self {
136 self.digits_after_decimal = digits;
137 self
138 }
139
140 pub fn negative_sign_before_digits(mut self, before: bool) -> Self {
142 self.negative_sign_before_digits = before;
143 self
144 }
145
146 pub fn negative_sign_after_digits(mut self, after: bool) -> Self {
148 self.negative_sign_after_digits = after;
149 self
150 }
151
152 pub fn allow_negative_sign_placeholder(mut self, allow: bool) -> Self {
154 self.allow_negative_sign_placeholder = allow;
155 self
156 }
157
158 pub fn allow_space_after_digits(mut self, allow: bool) -> Self {
160 self.allow_space_after_digits = allow;
161 self
162 }
163}
164
165fn build_currency_regex(options: &CurrencyOptions) -> Result<Regex, regex::Error> {
168 let mut decimal_digits = format!(r"\d{{{}}}", options.digits_after_decimal[0]);
170 for digit in options.digits_after_decimal.iter().skip(1) {
171 decimal_digits.push_str(&format!(r"|\d{{{}}}", digit));
172 }
173
174 let escaped_symbol = regex::escape(&options.symbol);
177
178 let symbol = format!(
179 "({}){}",
180 escaped_symbol,
181 if options.require_symbol { "" } else { "?" }
182 );
183
184 let negative = r"-?";
185 let whole_dollar_amount_without_sep = r"[1-9]\d*";
186
187 let escaped_thousands_sep = if options.thousands_separator.is_alphanumeric() || options.thousands_separator == '_' {
189 options.thousands_separator.to_string()
190 } else {
191 format!(r"\{}", options.thousands_separator)
192 };
193
194 let whole_dollar_amount_with_sep = format!(
195 r"[1-9]\d{{0,2}}({}\d{{3}})*",
196 escaped_thousands_sep
197 );
198
199 let valid_whole_dollar_amounts = vec![
200 "0",
201 whole_dollar_amount_without_sep,
202 &whole_dollar_amount_with_sep,
203 ];
204
205 let whole_dollar_amount = format!("({})?", valid_whole_dollar_amounts.join("|"));
206
207 let escaped_decimal_sep = if options.decimal_separator.is_alphanumeric() || options.decimal_separator == '_' {
209 options.decimal_separator.to_string()
210 } else {
211 format!(r"\{}", options.decimal_separator)
212 };
213
214 let decimal_amount = format!(
215 r"({}({})){}",
216 escaped_decimal_sep,
217 decimal_digits,
218 if options.require_decimal { "" } else { "?" }
219 );
220
221 let mut pattern = whole_dollar_amount.clone();
222 if options.allow_decimal || options.require_decimal {
223 pattern.push_str(&decimal_amount);
224 }
225
226 if options.allow_negatives && !options.parens_for_negatives {
228 if options.negative_sign_after_digits {
229 pattern.push_str(negative);
230 } else if options.negative_sign_before_digits {
231 pattern = format!("{}{}", negative, pattern);
232 }
233 }
234
235 if options.allow_negative_sign_placeholder {
237 pattern = format!(r"( ?-?)?{}", pattern);
239 } else if options.allow_space_after_symbol {
240 pattern = format!(r" ?{}", pattern);
241 } else if options.allow_space_after_digits {
242 pattern.push_str(r" ?");
243 }
244
245 if options.symbol_after_digits {
247 pattern.push_str(&symbol);
248 } else {
249 pattern = format!("{}{}", symbol, pattern);
250 }
251
252 if options.allow_negatives {
254 if options.parens_for_negatives {
255 pattern = format!(r"(\({}\)|{})", pattern, pattern);
257 } else if !options.negative_sign_before_digits && !options.negative_sign_after_digits {
258 pattern = format!("{}{}", negative, pattern);
259 }
260 }
261
262 let final_pattern = format!(r"^{}$", pattern);
264
265 Regex::new(&final_pattern)
266}
267
268fn validate_currency_manual(value: &str, options: &CurrencyOptions) -> bool {
270 if value.is_empty() {
272 return false;
273 }
274
275 if value.starts_with(' ') || value.ends_with(' ') {
277 return false;
278 }
279
280 if value.starts_with("- ") {
282 return false;
283 }
284
285 if !value.chars().any(|c| c.is_ascii_digit()) {
287 return false;
288 }
289
290 if !options.allow_space_after_symbol && !options.allow_negative_sign_placeholder {
293 if value.contains(&format!("{} ", options.symbol)) {
294 return false;
295 }
296 }
297
298 if options.allow_negative_sign_placeholder && !options.allow_space_after_symbol {
301 if value.contains(&format!("{} -", options.symbol)) {
302 return false;
303 }
304 }
305
306 if !options.allow_space_after_digits && !options.allow_negative_sign_placeholder {
310 let trimmed = value.trim_end_matches(&options.symbol);
311 let trimmed = trimmed.trim_end_matches(')');
312 if trimmed.ends_with(' ') {
313 return false;
314 }
315 }
316
317 true
318}
319
320pub fn is_currency(value: &str, options: Option<CurrencyOptions>) -> bool {
340 let opts = options.unwrap_or_default();
341
342 if !validate_currency_manual(value, &opts) {
344 return false;
345 }
346
347 match build_currency_regex(&opts) {
348 Ok(regex) => regex.is_match(value),
349 Err(_) => false,
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356
357 #[test]
359 fn test_default_currency() {
360 let valid = vec![
361 "-$10,123.45",
362 "$10,123.45",
363 "$10123.45",
364 "10,123.45",
365 "10123.45",
366 "10,123",
367 "1,123,456",
368 "1123456",
369 "1.39",
370 ".03",
371 "0.10",
372 "$0.10",
373 "-$0.01",
374 "-$.99",
375 "$100,234,567.89",
376 "$10,123",
377 "10,123",
378 "-10123",
379 ];
380
381 let invalid = vec![
382 "1.234",
383 "$1.1",
384 "$ 32.50",
385 "500$",
386 ".0001",
387 "$.001",
388 "$0.001",
389 "12,34.56",
390 "123456,123,123456",
391 "123,4",
392 ",123",
393 "$-,123",
394 "$",
395 ".",
396 ",",
397 "00",
398 "$-",
399 "$-,.",
400 "-",
401 "-$",
402 "",
403 "- $",
404 ];
405
406 for val in valid {
407 assert!(is_currency(val, None), "Expected '{}' to be valid", val);
408 }
409
410 for val in invalid {
411 assert!(!is_currency(val, None), "Expected '{}' to be invalid", val);
412 }
413 }
414
415 #[test]
417 fn test_no_decimal() {
418 let options = CurrencyOptions::new().allow_decimal(false);
419
420 let valid = vec![
421 "-$10,123",
422 "$10,123",
423 "$10123",
424 "10,123",
425 "10123",
426 "1,123,456",
427 "1123456",
428 "1",
429 "0",
430 "$0",
431 "-$0",
432 "$100,234,567",
433 "$10,123",
434 "10,123",
435 "-10123",
436 ];
437
438 let invalid = vec![
439 "-$10,123.45",
440 "$10,123.45",
441 "$10123.45",
442 "10,123.45",
443 "10123.45",
444 "1.39",
445 ".03",
446 "0.10",
447 "$0.10",
448 "-$0.01",
449 "-$.99",
450 "$100,234,567.89",
451 "1.234",
452 "$1.1",
453 "$ 32.50",
454 ".0001",
455 "$.001",
456 "$0.001",
457 "12,34.56",
458 "123,4",
459 ",123",
460 "$-,123",
461 "$",
462 ".",
463 ",",
464 "00",
465 "$-",
466 "$-,.",
467 "-",
468 "-$",
469 "",
470 "- $",
471 ];
472
473 for val in valid {
474 assert!(is_currency(val, Some(options.clone())), "Expected '{}' to be valid", val);
475 }
476
477 for val in invalid {
478 assert!(!is_currency(val, Some(options.clone())), "Expected '{}' to be invalid", val);
479 }
480 }
481
482 #[test]
484 fn test_require_decimal() {
485 let options = CurrencyOptions::new().require_decimal(true);
486
487 let valid = vec![
488 "-$10,123.45",
489 "$10,123.45",
490 "$10123.45",
491 "10,123.45",
492 "10123.45",
493 "10,123.00",
494 "1.39",
495 ".03",
496 "0.10",
497 "$0.10",
498 "-$0.01",
499 "-$.99",
500 "$100,234,567.89",
501 ];
502
503 let invalid = vec![
504 "$10,123",
505 "10,123",
506 "-10123",
507 "1,123,456",
508 "1123456",
509 "1.234",
510 "$1.1",
511 "$ 32.50",
512 "500$",
513 ".0001",
514 "$.001",
515 "$0.001",
516 "12,34.56",
517 "123456,123,123456",
518 "123,4",
519 ",123",
520 "$-,123",
521 "$",
522 ".",
523 ",",
524 "00",
525 "$-",
526 "$-,.",
527 "-",
528 "-$",
529 "",
530 "- $",
531 ];
532
533 for val in valid {
534 assert!(is_currency(val, Some(options.clone())), "Expected '{}' to be valid", val);
535 }
536
537 for val in invalid {
538 assert!(!is_currency(val, Some(options.clone())), "Expected '{}' to be invalid", val);
539 }
540 }
541
542 #[test]
544 fn test_digits_after_decimal() {
545 let options = CurrencyOptions::new()
546 .digits_after_decimal(vec![1, 3]);
547
548 let valid = vec![
549 "-$10,123.4",
550 "$10,123.454",
551 "$10123.452",
552 "10,123.453",
553 "10123.450",
554 "10,123",
555 "1,123,456",
556 "1123456",
557 "1.3",
558 ".030",
559 "0.100",
560 "$0.1",
561 "-$0.0",
562 "-$.9",
563 "$100,234,567.893",
564 "$10,123",
565 "10,123.123",
566 "-10123.1",
567 ];
568
569 let invalid = vec![
570 "1.23",
571 "$1.13322",
572 "$ 32.50",
573 "500$",
574 ".0001",
575 "$.01",
576 "$0.01",
577 "12,34.56",
578 "123456,123,123456",
579 "123,4",
580 ",123",
581 "$-,123",
582 "$",
583 ".",
584 ",",
585 "00",
586 "$-",
587 "$-,.",
588 "-",
589 "-$",
590 "",
591 "- $",
592 ];
593
594 for val in valid {
595 assert!(is_currency(val, Some(options.clone())), "Expected '{}' to be valid", val);
596 }
597
598 for val in invalid {
599 assert!(!is_currency(val, Some(options.clone())), "Expected '{}' to be invalid", val);
600 }
601 }
602
603 #[test]
605 fn test_require_symbol() {
606 let options = CurrencyOptions::new().require_symbol(true);
607
608 let valid = vec![
609 "-$10,123.45",
610 "$10,123.45",
611 "$10123.45",
612 "$10,123.45",
613 "$10,123",
614 "$1,123,456",
615 "$1123456",
616 "$1.39",
617 "$.03",
618 "$0.10",
619 "$0.10",
620 "-$0.01",
621 "-$.99",
622 "$100,234,567.89",
623 "$10,123",
624 "-$10123",
625 ];
626
627 let invalid = vec![
628 "1.234",
629 "$1.234",
630 "1.1",
631 "$1.1",
632 "$ 32.50",
633 " 32.50",
634 "500",
635 "10,123,456",
636 ".0001",
637 "$.001",
638 "$0.001",
639 "1,234.56",
640 "123456,123,123456",
641 "$123456,123,123456",
642 "123.4",
643 "$123.4",
644 ",123",
645 "$,123",
646 "$-,123",
647 "$",
648 ".",
649 "$.",
650 ",",
651 "$,",
652 "00",
653 "$00",
654 "$-",
655 "$-,.",
656 "-",
657 "-$",
658 "",
659 "$ ",
660 "- $",
661 ];
662
663 for val in valid {
664 assert!(is_currency(val, Some(options.clone())), "Expected '{}' to be valid", val);
665 }
666
667 for val in invalid {
668 assert!(!is_currency(val, Some(options.clone())), "Expected '{}' to be invalid", val);
669 }
670 }
671
672 #[test]
674 fn test_yuan_format() {
675 let mut options = CurrencyOptions::new();
676 options.symbol = "¥".to_string();
677 options.negative_sign_before_digits = true;
678
679 let valid = vec![
680 "123,456.78",
681 "-123,456.78",
682 "¥6,954,231",
683 "¥-6,954,231",
684 "¥10.03",
685 "¥-10.03",
686 "10.03",
687 "1.39",
688 ".03",
689 "0.10",
690 "¥-10567.01",
691 "¥0.01",
692 "¥1,234,567.89",
693 "¥10,123",
694 "¥-10,123",
695 "¥-10,123.45",
696 "10,123",
697 "10123",
698 "¥-100",
699 ];
700
701 let invalid = vec![
702 "1.234",
703 "¥1.1",
704 "5,00",
705 ".0001",
706 "¥.001",
707 "¥0.001",
708 "12,34.56",
709 "123456,123,123456",
710 "123 456",
711 ",123",
712 "¥-,123",
713 "",
714 " ",
715 "¥",
716 "¥-",
717 "¥-,.",
718 "-",
719 "- ¥",
720 "-¥",
721 ];
722
723 for val in valid {
724 assert!(is_currency(val, Some(options.clone())), "Expected '{}' to be valid", val);
725 }
726
727 for val in invalid {
728 assert!(!is_currency(val, Some(options.clone())), "Expected '{}' to be invalid", val);
729 }
730 }
731
732 #[test]
734 fn test_negative_sign_after_digits() {
735 let mut options = CurrencyOptions::new();
736 options.negative_sign_after_digits = true;
737
738 let valid = vec![
739 "$10,123.45-",
740 "$10,123.45",
741 "$10123.45",
742 "10,123.45",
743 "10123.45",
744 "10,123",
745 "1,123,456",
746 "1123456",
747 "1.39",
748 ".03",
749 "0.10",
750 "$0.10",
751 "$0.01-",
752 "$.99-",
753 "$100,234,567.89",
754 "$10,123",
755 "10,123",
756 "10123-",
757 ];
758
759 let invalid = vec![
760 "-123",
761 "1.234",
762 "$1.1",
763 "$ 32.50",
764 "500$",
765 ".0001",
766 "$.001",
767 "$0.001",
768 "12,34.56",
769 "123456,123,123456",
770 "123,4",
771 ",123",
772 "$-,123",
773 "$",
774 ".",
775 ",",
776 "00",
777 "$-",
778 "$-,.",
779 "-",
780 "-$",
781 "",
782 "- $",
783 ];
784
785 for val in valid {
786 assert!(is_currency(val, Some(options.clone())), "Expected '{}' to be valid", val);
787 }
788
789 for val in invalid {
790 assert!(!is_currency(val, Some(options.clone())), "Expected '{}' to be invalid", val);
791 }
792 }
793
794 #[test]
796 fn test_no_negatives_yuan() {
797 let mut options = CurrencyOptions::new();
798 options.symbol = "¥".to_string();
799 options.allow_negatives = false;
800
801 let valid = vec![
802 "123,456.78",
803 "¥6,954,231",
804 "¥10.03",
805 "10.03",
806 "1.39",
807 ".03",
808 "0.10",
809 "¥0.01",
810 "¥1,234,567.89",
811 "¥10,123",
812 "10,123",
813 "10123",
814 "¥100",
815 ];
816
817 let invalid = vec![
818 "1.234",
819 "-123,456.78",
820 "¥-6,954,231",
821 "¥-10.03",
822 "¥-10567.01",
823 "¥1.1",
824 "¥-10,123",
825 "¥-10,123.45",
826 "5,00",
827 "¥-100",
828 ".0001",
829 "¥.001",
830 "¥-.001",
831 "¥0.001",
832 "12,34.56",
833 "123456,123,123456",
834 "123 456",
835 ",123",
836 "¥-,123",
837 "",
838 " ",
839 "¥",
840 "¥-",
841 "¥-,.",
842 "-",
843 "- ¥",
844 "-¥",
845 ];
846
847 for val in valid {
848 assert!(is_currency(val, Some(options.clone())), "Expected '{}' to be valid", val);
849 }
850
851 for val in invalid {
852 assert!(!is_currency(val, Some(options.clone())), "Expected '{}' to be invalid", val);
853 }
854 }
855
856 #[test]
858 fn test_south_african_rand() {
859 let mut options = CurrencyOptions::new();
860 options.symbol = "R".to_string();
861 options.negative_sign_before_digits = true;
862 options.thousands_separator = ' ';
863 options.decimal_separator = ',';
864 options.allow_negative_sign_placeholder = true;
865
866 let valid = vec![
867 "123 456,78",
868 "-10 123",
869 "R-10 123",
870 "R 6 954 231",
871 "R10,03",
872 "10,03",
873 "1,39",
874 ",03",
875 "0,10",
876 "R10567,01",
877 "R0,01",
878 "R1 234 567,89",
879 "R10 123",
880 "R 10 123",
881 "R 10123",
882 "R-10123",
883 "10 123",
884 "10123",
885 ];
886
887 let invalid = vec![
888 "1,234",
889 "R -10123",
890 "R- 10123",
891 "R,1",
892 ",0001",
893 "R,001",
894 "R0,001",
895 "12 34,56",
896 "123456 123 123456",
897 " 123",
898 "- 123",
899 "123 ",
900 "",
901 " ",
902 "R",
903 "R- .1",
904 "R-",
905 "-",
906 "-R 10123",
907 "R00",
908 "R -",
909 "-R",
910 ];
911
912 for val in valid {
913 assert!(is_currency(val, Some(options.clone())), "Expected '{}' to be valid", val);
914 }
915
916 for val in invalid {
917 assert!(!is_currency(val, Some(options.clone())), "Expected '{}' to be invalid", val);
918 }
919 }
920
921 #[test]
923 fn test_euro_italian() {
924 let mut options = CurrencyOptions::new();
925 options.symbol = "€".to_string();
926 options.thousands_separator = '.';
927 options.decimal_separator = ',';
928 options.allow_space_after_symbol = true;
929
930 let valid = vec![
931 "123.456,78",
932 "-123.456,78",
933 "€6.954.231",
934 "-€6.954.231",
935 "€ 896.954.231",
936 "-€ 896.954.231",
937 "16.954.231",
938 "-16.954.231",
939 "€10,03",
940 "-€10,03",
941 "10,03",
942 "-10,03",
943 "-1,39",
944 ",03",
945 "0,10",
946 "-€10567,01",
947 "-€ 10567,01",
948 "€ 0,01",
949 "€1.234.567,89",
950 "€10.123",
951 "10.123",
952 "-€10.123",
953 "€ 10.123",
954 "€10.123",
955 "€ 10123",
956 "10.123",
957 "-10123",
958 ];
959
960 let invalid = vec![
961 "1,234",
962 "€ 1,1",
963 "50#,50",
964 "123,@€ ",
965 "€€500",
966 ",0001",
967 "€ ,001",
968 "€0,001",
969 "12.34,56",
970 "123456.123.123456",
971 "€123€",
972 "",
973 " ",
974 "€",
975 " €",
976 "€ ",
977 "€€",
978 " 123",
979 "- 123",
980 ".123",
981 "-€.123",
982 "123 ",
983 "€-",
984 "- €",
985 "€ - ",
986 "-",
987 "- ",
988 "-€",
989 ];
990
991 for val in valid {
992 assert!(is_currency(val, Some(options.clone())), "Expected '{}' to be valid", val);
993 }
994
995 for val in invalid {
996 assert!(!is_currency(val, Some(options.clone())), "Expected '{}' to be invalid", val);
997 }
998 }
999
1000 #[test]
1002 fn test_euro_greek() {
1003 let mut options = CurrencyOptions::new();
1004 options.symbol = "€".to_string();
1005 options.thousands_separator = '.';
1006 options.symbol_after_digits = true;
1007 options.decimal_separator = ',';
1008 options.allow_space_after_digits = true;
1009
1010 let valid = vec![
1011 "123.456,78",
1012 "-123.456,78",
1013 "6.954.231 €",
1014 "-6.954.231 €",
1015 "896.954.231",
1016 "-896.954.231",
1017 "16.954.231",
1018 "-16.954.231",
1019 "10,03€",
1020 "-10,03€",
1021 "10,03",
1022 "-10,03",
1023 "1,39",
1024 ",03",
1025 "-,03",
1026 "-,03 €",
1027 "-,03€",
1028 "0,10",
1029 "10567,01€",
1030 "0,01 €",
1031 "1.234.567,89€",
1032 "10.123€",
1033 "10.123",
1034 "10.123€",
1035 "10.123 €",
1036 "10123 €",
1037 "10.123",
1038 "10123",
1039 ];
1040
1041 let invalid = vec![
1042 "1,234",
1043 "1,1 €",
1044 ",0001",
1045 ",001 €",
1046 "0,001€",
1047 "12.34,56",
1048 "123456.123.123456",
1049 "€123€",
1050 "",
1051 " ",
1052 "€",
1053 " €",
1054 "€ ",
1055 " 123",
1056 "- 123",
1057 ".123",
1058 "-.123€",
1059 "-.123 €",
1060 "123 ",
1061 "-€",
1062 "- €",
1063 "-",
1064 "- ",
1065 ];
1066
1067 for val in valid {
1068 assert!(is_currency(val, Some(options.clone())), "Expected '{}' to be valid", val);
1069 }
1070
1071 for val in invalid {
1072 assert!(!is_currency(val, Some(options.clone())), "Expected '{}' to be invalid", val);
1073 }
1074 }
1075
1076 #[test]
1078 fn test_danish_krone() {
1079 let mut options = CurrencyOptions::new();
1080 options.symbol = "kr.".to_string();
1081 options.negative_sign_before_digits = true;
1082 options.thousands_separator = '.';
1083 options.decimal_separator = ',';
1084 options.allow_space_after_symbol = true;
1085
1086 let valid = vec![
1087 "123.456,78",
1088 "-10.123",
1089 "kr. -10.123",
1090 "kr.-10.123",
1091 "kr. 6.954.231",
1092 "kr.10,03",
1093 "kr. -10,03",
1094 "10,03",
1095 "1,39",
1096 ",03",
1097 "0,10",
1098 "kr. 10567,01",
1099 "kr. 0,01",
1100 "kr. 1.234.567,89",
1101 "kr. -1.234.567,89",
1102 "10.123",
1103 "kr. 10.123",
1104 "kr.10.123",
1105 "10123",
1106 "10.123",
1107 "kr.-10123",
1108 ];
1109
1110 let invalid = vec![
1111 "1,234",
1112 "kr. -10123",
1113 "kr.,1",
1114 ",0001",
1115 "kr. ,001",
1116 "kr.0,001",
1117 "12.34,56",
1118 "123456.123.123456",
1119 ".123",
1120 "kr.-.123",
1121 "kr. -.123",
1122 "- 123",
1123 "123 ",
1124 "",
1125 " ",
1126 "kr.",
1127 " kr.",
1128 "kr. ",
1129 "kr.-",
1130 "kr. -",
1131 "kr. - ",
1132 " - ",
1133 "-",
1134 "- kr.",
1135 "-kr.",
1136 ];
1137
1138 for val in valid {
1139 assert!(is_currency(val, Some(options.clone())), "Expected '{}' to be valid", val);
1140 }
1141
1142 for val in invalid {
1143 assert!(!is_currency(val, Some(options.clone())), "Expected '{}' to be invalid", val);
1144 }
1145 }
1146
1147 #[test]
1149 fn test_danish_krone_no_negatives() {
1150 let mut options = CurrencyOptions::new();
1151 options.symbol = "kr.".to_string();
1152 options.allow_negatives = false;
1153 options.negative_sign_before_digits = true;
1154 options.thousands_separator = '.';
1155 options.decimal_separator = ',';
1156 options.allow_space_after_symbol = true;
1157
1158 let valid = vec![
1159 "123.456,78",
1160 "10.123",
1161 "kr. 10.123",
1162 "kr.10.123",
1163 "kr. 6.954.231",
1164 "kr.10,03",
1165 "kr. 10,03",
1166 "10,03",
1167 "1,39",
1168 ",03",
1169 "0,10",
1170 "kr. 10567,01",
1171 "kr. 0,01",
1172 "kr. 1.234.567,89",
1173 "kr.1.234.567,89",
1174 "10.123",
1175 "kr. 10.123",
1176 "kr.10.123",
1177 "10123",
1178 "10.123",
1179 "kr.10123",
1180 ];
1181
1182 let invalid = vec![
1183 "1,234",
1184 "-10.123",
1185 "kr. -10.123",
1186 "kr. -1.234.567,89",
1187 "kr.-10123",
1188 "kr. -10123",
1189 "kr.-10.123",
1190 "kr. -10,03",
1191 "kr.,1",
1192 ",0001",
1193 "kr. ,001",
1194 "kr.0,001",
1195 "12.34,56",
1196 "123456.123.123456",
1197 ".123",
1198 "kr.-.123",
1199 "kr. -.123",
1200 "- 123",
1201 "123 ",
1202 "",
1203 " ",
1204 "kr.",
1205 " kr.",
1206 "kr. ",
1207 "kr.-",
1208 "kr. -",
1209 "kr. - ",
1210 " - ",
1211 "-",
1212 "- kr.",
1213 "-kr.",
1214 ];
1215
1216 for val in valid {
1217 assert!(is_currency(val, Some(options.clone())), "Expected '{}' to be valid", val);
1218 }
1219
1220 for val in invalid {
1221 assert!(!is_currency(val, Some(options.clone())), "Expected '{}' to be invalid", val);
1222 }
1223 }
1224
1225 #[test]
1227 fn test_parens_for_negatives() {
1228 let mut options = CurrencyOptions::new();
1229 options.parens_for_negatives = true;
1230
1231 let valid = vec![
1232 "1,234",
1233 "(1,234)",
1234 "($6,954,231)",
1235 "$10.03",
1236 "(10.03)",
1237 "($10.03)",
1238 "1.39",
1239 ".03",
1240 "(.03)",
1241 "($.03)",
1242 "0.10",
1243 "$10567.01",
1244 "($0.01)",
1245 "$1,234,567.89",
1246 "$10,123",
1247 "(10,123)",
1248 "10123",
1249 ];
1250
1251 let invalid = vec![
1252 "1.234",
1253 "($1.1)",
1254 "-$1.10",
1255 "$ 32.50",
1256 "500$",
1257 ".0001",
1258 "$.001",
1259 "($0.001)",
1260 "12,34.56",
1261 "123456,123,123456",
1262 "( 123)",
1263 ",123",
1264 "$-,123",
1265 "",
1266 " ",
1267 " ",
1268 " ",
1269 "$",
1270 "$ ",
1271 " $",
1272 " 123",
1273 "(123) ",
1274 ".",
1275 ",",
1276 "00",
1277 "$-",
1278 "$ - ",
1279 "$- ",
1280 " - ",
1281 "-",
1282 "- $",
1283 "-$",
1284 "()",
1285 "( )",
1286 "( -)",
1287 "( - )",
1288 "( - )",
1289 "(-)",
1290 "(-$)",
1291 ];
1292
1293 for val in valid {
1294 assert!(is_currency(val, Some(options.clone())), "Expected '{}' to be valid", val);
1295 }
1296
1297 for val in invalid {
1298 assert!(!is_currency(val, Some(options.clone())), "Expected '{}' to be invalid", val);
1299 }
1300 }
1301
1302 #[test]
1304 fn test_no_negatives_usd() {
1305 let mut options = CurrencyOptions::new();
1306 options.allow_negatives = false;
1307
1308 let valid = vec![
1309 "$10,123.45",
1310 "$10123.45",
1311 "10,123.45",
1312 "10123.45",
1313 "10,123",
1314 "1,123,456",
1315 "1123456",
1316 "1.39",
1317 ".03",
1318 "0.10",
1319 "$0.10",
1320 "$100,234,567.89",
1321 "$10,123",
1322 "10,123",
1323 ];
1324
1325 let invalid = vec![
1326 "1.234",
1327 "-1.234",
1328 "-10123",
1329 "-$0.01",
1330 "-$.99",
1331 "$1.1",
1332 "-$1.1",
1333 "$ 32.50",
1334 "500$",
1335 ".0001",
1336 "$.001",
1337 "$0.001",
1338 "12,34.56",
1339 "123456,123,123456",
1340 "-123456,123,123456",
1341 "123,4",
1342 ",123",
1343 "$-,123",
1344 "$",
1345 ".",
1346 ",",
1347 "00",
1348 "$-",
1349 "$-,.",
1350 "-",
1351 "-$",
1352 "",
1353 "- $",
1354 "-$10,123.45",
1355 ];
1356
1357 for val in valid {
1358 assert!(is_currency(val, Some(options.clone())), "Expected '{}' to be valid", val);
1359 }
1360
1361 for val in invalid {
1362 assert!(!is_currency(val, Some(options.clone())), "Expected '{}' to be invalid", val);
1363 }
1364 }
1365
1366 #[test]
1368 fn test_brazilian_real() {
1369 let mut options = CurrencyOptions::new();
1370 options.symbol = "R$".to_string();
1371 options.require_symbol = true;
1372 options.allow_space_after_symbol = true;
1373 options.symbol_after_digits = false;
1374 options.thousands_separator = '.';
1375 options.decimal_separator = ',';
1376
1377 let valid = vec![
1378 "R$ 1.400,00",
1379 "R$ 400,00",
1380 ];
1381
1382 let invalid = vec![
1383 "$ 1.400,00",
1384 "$R 1.400,00",
1385 ];
1386
1387 for val in valid {
1388 assert!(is_currency(val, Some(options.clone())), "Expected '{}' to be valid", val);
1389 }
1390
1391 for val in invalid {
1392 assert!(!is_currency(val, Some(options.clone())), "Expected '{}' to be invalid", val);
1393 }
1394 }
1395}
1396