1use crate::parsing::ast::Span;
7use crate::semantic::{DateTimeValue, FactValue, LemmaDoc, TimeValue, TypeSpecification};
8use crate::LemmaError;
9use rust_decimal::Decimal;
10use std::cmp::Ordering;
11use std::sync::Arc;
12
13pub fn validate_types(
19 document: &LemmaDoc,
20 _all_docs: Option<&[LemmaDoc]>,
21) -> Result<(), Vec<LemmaError>> {
22 let mut errors = Vec::new();
23
24 for fact in &document.facts {
26 if let FactValue::TypeDeclaration {
27 base,
28 overrides: _,
29 from: _,
30 } = &fact.value
31 {
32 if base.is_empty() {
34 errors.push(LemmaError::engine(
35 "TypeDeclaration base cannot be empty",
36 Span {
37 start: 0,
38 end: 0,
39 line: 1,
40 col: 0,
41 },
42 "<unknown>",
43 Arc::from(""),
44 "<unknown>",
45 1,
46 None::<String>,
47 ));
48 }
49 }
50 }
51
52 if errors.is_empty() {
53 Ok(())
54 } else {
55 Err(errors)
56 }
57}
58
59pub fn validate_type_specifications(specs: &TypeSpecification, type_name: &str) -> Vec<LemmaError> {
69 let mut errors = Vec::new();
70
71 match specs {
72 TypeSpecification::Scale {
73 minimum,
74 maximum,
75 decimals,
76 precision,
77 default,
78 units,
79 ..
80 } => {
81 if let (Some(min), Some(max)) = (minimum, maximum) {
83 if min > max {
84 errors.push(LemmaError::engine(
85 format!(
86 "Type '{}' has invalid range: minimum {} is greater than maximum {}",
87 type_name, min, max
88 ),
89 Span {
90 start: 0,
91 end: 0,
92 line: 1,
93 col: 0,
94 },
95 "<unknown>",
96 Arc::from(""),
97 "<unknown>",
98 1,
99 None::<String>,
100 ));
101 }
102 }
103
104 if let Some(d) = decimals {
106 if *d > 28 {
107 errors.push(LemmaError::engine(
108 format!(
109 "Type '{}' has invalid decimals value: {}. Must be between 0 and 28",
110 type_name, d
111 ),
112 Span {
113 start: 0,
114 end: 0,
115 line: 1,
116 col: 0,
117 },
118 "<unknown>",
119 Arc::from(""),
120 "<unknown>",
121 1,
122 None::<String>,
123 ));
124 }
125 }
126
127 if let Some(prec) = precision {
129 if *prec <= Decimal::ZERO {
130 errors.push(LemmaError::engine(
131 format!(
132 "Type '{}' has invalid precision: {}. Must be positive",
133 type_name, prec
134 ),
135 Span {
136 start: 0,
137 end: 0,
138 line: 1,
139 col: 0,
140 },
141 "<unknown>",
142 Arc::from(""),
143 "<unknown>",
144 1,
145 None::<String>,
146 ));
147 }
148 }
149
150 if let Some((def_value, def_unit)) = default {
152 if !units.iter().any(|u| u.name == *def_unit) {
154 errors.push(LemmaError::engine(
155 format!(
156 "Type '{}' default unit '{}' is not a valid unit. Valid units: {}",
157 type_name,
158 def_unit,
159 units
160 .iter()
161 .map(|u| u.name.clone())
162 .collect::<Vec<_>>()
163 .join(", ")
164 ),
165 Span {
166 start: 0,
167 end: 0,
168 line: 1,
169 col: 0,
170 },
171 "<unknown>",
172 Arc::from(""),
173 "<unknown>",
174 1,
175 None::<String>,
176 ));
177 }
178 if let Some(min) = minimum {
179 if *def_value < *min {
180 errors.push(LemmaError::engine(
181 format!(
182 "Type '{}' default value {} {} is less than minimum {}",
183 type_name, def_value, def_unit, min
184 ),
185 Span {
186 start: 0,
187 end: 0,
188 line: 1,
189 col: 0,
190 },
191 "<unknown>",
192 Arc::from(""),
193 "<unknown>",
194 1,
195 None::<String>,
196 ));
197 }
198 }
199 if let Some(max) = maximum {
200 if *def_value > *max {
201 errors.push(LemmaError::engine(
202 format!(
203 "Type '{}' default value {} {} is greater than maximum {}",
204 type_name, def_value, def_unit, max
205 ),
206 Span {
207 start: 0,
208 end: 0,
209 line: 1,
210 col: 0,
211 },
212 "<unknown>",
213 Arc::from(""),
214 "<unknown>",
215 1,
216 None::<String>,
217 ));
218 }
219 }
220 }
221
222 if !units.is_empty() {
225 let mut seen_names: Vec<String> = Vec::new();
226 for unit in units {
227 if unit.name.trim().is_empty() {
229 errors.push(LemmaError::engine(
230 format!(
231 "Type '{}' has a unit with empty name. Unit names cannot be empty.",
232 type_name
233 ),
234 Span {
235 start: 0,
236 end: 0,
237 line: 1,
238 col: 0,
239 },
240 "<unknown>",
241 Arc::from(""),
242 "<unknown>",
243 1,
244 None::<String>,
245 ));
246 }
247
248 let lower_name = unit.name.to_lowercase();
250 if seen_names
251 .iter()
252 .any(|seen| seen.to_lowercase() == lower_name)
253 {
254 errors.push(LemmaError::engine(
255 format!("Type '{}' has duplicate unit name '{}' (case-insensitive). Unit names must be unique within a type.", type_name, unit.name),
256 Span { start: 0, end: 0, line: 1, col: 0 },
257 "<unknown>",
258 Arc::from(""),
259 "<unknown>",
260 1,
261 None::<String>,
262 ));
263 } else {
264 seen_names.push(unit.name.clone());
265 }
266
267 if unit.value <= Decimal::ZERO {
269 errors.push(LemmaError::engine(
270 format!("Type '{}' has unit '{}' with invalid value {}. Unit values must be positive (conversion factor relative to type base).", type_name, unit.name, unit.value),
271 Span { start: 0, end: 0, line: 1, col: 0 },
272 "<unknown>",
273 Arc::from(""),
274 "<unknown>",
275 1,
276 None::<String>,
277 ));
278 }
279 }
280 }
281 }
282 TypeSpecification::Number {
283 minimum,
284 maximum,
285 decimals,
286 precision,
287 default,
288 ..
289 } => {
290 if let (Some(min), Some(max)) = (minimum, maximum) {
292 if min > max {
293 errors.push(LemmaError::engine(
294 format!(
295 "Type '{}' has invalid range: minimum {} is greater than maximum {}",
296 type_name, min, max
297 ),
298 Span {
299 start: 0,
300 end: 0,
301 line: 1,
302 col: 0,
303 },
304 "<unknown>",
305 Arc::from(""),
306 "<unknown>",
307 1,
308 None::<String>,
309 ));
310 }
311 }
312
313 if let Some(d) = decimals {
315 if *d > 28 {
316 errors.push(LemmaError::engine(
317 format!(
318 "Type '{}' has invalid decimals value: {}. Must be between 0 and 28",
319 type_name, d
320 ),
321 Span {
322 start: 0,
323 end: 0,
324 line: 1,
325 col: 0,
326 },
327 "<unknown>",
328 Arc::from(""),
329 "<unknown>",
330 1,
331 None::<String>,
332 ));
333 }
334 }
335
336 if let Some(prec) = precision {
338 if *prec <= Decimal::ZERO {
339 errors.push(LemmaError::engine(
340 format!(
341 "Type '{}' has invalid precision: {}. Must be positive",
342 type_name, prec
343 ),
344 Span {
345 start: 0,
346 end: 0,
347 line: 1,
348 col: 0,
349 },
350 "<unknown>",
351 Arc::from(""),
352 "<unknown>",
353 1,
354 None::<String>,
355 ));
356 }
357 }
358
359 if let Some(def) = default {
361 if let Some(min) = minimum {
362 if *def < *min {
363 errors.push(LemmaError::engine(
364 format!(
365 "Type '{}' default value {} is less than minimum {}",
366 type_name, def, min
367 ),
368 Span {
369 start: 0,
370 end: 0,
371 line: 1,
372 col: 0,
373 },
374 "<unknown>",
375 Arc::from(""),
376 "<unknown>",
377 1,
378 None::<String>,
379 ));
380 }
381 }
382 if let Some(max) = maximum {
383 if *def > *max {
384 errors.push(LemmaError::engine(
385 format!(
386 "Type '{}' default value {} is greater than maximum {}",
387 type_name, def, max
388 ),
389 Span {
390 start: 0,
391 end: 0,
392 line: 1,
393 col: 0,
394 },
395 "<unknown>",
396 Arc::from(""),
397 "<unknown>",
398 1,
399 None::<String>,
400 ));
401 }
402 }
403 }
404 }
406
407 TypeSpecification::Ratio {
408 minimum,
409 maximum,
410 default,
411 units,
412 ..
413 } => {
414 if let (Some(min), Some(max)) = (minimum, maximum) {
416 if min > max {
417 errors.push(LemmaError::engine(
418 format!(
419 "Type '{}' has invalid range: minimum {} is greater than maximum {}",
420 type_name, min, max
421 ),
422 Span {
423 start: 0,
424 end: 0,
425 line: 1,
426 col: 0,
427 },
428 "<unknown>",
429 Arc::from(""),
430 "<unknown>",
431 1,
432 None::<String>,
433 ));
434 }
435 }
436
437 if let Some(def) = default {
439 if let Some(min) = minimum {
440 if *def < *min {
441 errors.push(LemmaError::engine(
442 format!(
443 "Type '{}' default value {} is less than minimum {}",
444 type_name, def, min
445 ),
446 Span {
447 start: 0,
448 end: 0,
449 line: 1,
450 col: 0,
451 },
452 "<unknown>",
453 Arc::from(""),
454 "<unknown>",
455 1,
456 None::<String>,
457 ));
458 }
459 }
460 if let Some(max) = maximum {
461 if *def > *max {
462 errors.push(LemmaError::engine(
463 format!(
464 "Type '{}' default value {} is greater than maximum {}",
465 type_name, def, max
466 ),
467 Span {
468 start: 0,
469 end: 0,
470 line: 1,
471 col: 0,
472 },
473 "<unknown>",
474 Arc::from(""),
475 "<unknown>",
476 1,
477 None::<String>,
478 ));
479 }
480 }
481 }
482
483 if !units.is_empty() {
487 let mut seen_names: Vec<String> = Vec::new();
488 for unit in units {
489 if unit.name.trim().is_empty() {
491 errors.push(LemmaError::engine(
492 format!(
493 "Type '{}' has a unit with empty name. Unit names cannot be empty.",
494 type_name
495 ),
496 Span {
497 start: 0,
498 end: 0,
499 line: 1,
500 col: 0,
501 },
502 "<unknown>",
503 Arc::from(""),
504 "<unknown>",
505 1,
506 None::<String>,
507 ));
508 }
509
510 let lower_name = unit.name.to_lowercase();
512 if seen_names
513 .iter()
514 .any(|seen| seen.to_lowercase() == lower_name)
515 {
516 errors.push(LemmaError::engine(
517 format!("Type '{}' has duplicate unit name '{}' (case-insensitive). Unit names must be unique within a type.", type_name, unit.name),
518 Span { start: 0, end: 0, line: 1, col: 0 },
519 "<unknown>",
520 Arc::from(""),
521 "<unknown>",
522 1,
523 None::<String>,
524 ));
525 } else {
526 seen_names.push(unit.name.clone());
527 }
528
529 if unit.value <= Decimal::ZERO {
531 errors.push(LemmaError::engine(
532 format!("Type '{}' has unit '{}' with invalid value {}. Unit values must be positive (conversion factor relative to type base).", type_name, unit.name, unit.value),
533 Span { start: 0, end: 0, line: 1, col: 0 },
534 "<unknown>",
535 Arc::from(""),
536 "<unknown>",
537 1,
538 None::<String>,
539 ));
540 }
541 }
542 }
543 }
544
545 TypeSpecification::Text {
546 minimum,
547 maximum,
548 length,
549 options,
550 default,
551 ..
552 } => {
553 if let (Some(min), Some(max)) = (minimum, maximum) {
555 if min > max {
556 errors.push(LemmaError::engine(
557 format!("Type '{}' has invalid range: minimum length {} is greater than maximum length {}", type_name, min, max),
558 Span { start: 0, end: 0, line: 1, col: 0 },
559 "<unknown>",
560 Arc::from(""),
561 "<unknown>",
562 1,
563 None::<String>,
564 ));
565 }
566 }
567
568 if let Some(len) = length {
570 if let Some(min) = minimum {
571 if *len < *min {
572 errors.push(LemmaError::engine(
573 format!("Type '{}' has inconsistent length constraint: length {} is less than minimum {}", type_name, len, min),
574 Span { start: 0, end: 0, line: 1, col: 0 },
575 "<unknown>",
576 Arc::from(""),
577 "<unknown>",
578 1,
579 None::<String>,
580 ));
581 }
582 }
583 if let Some(max) = maximum {
584 if *len > *max {
585 errors.push(LemmaError::engine(
586 format!("Type '{}' has inconsistent length constraint: length {} is greater than maximum {}", type_name, len, max),
587 Span { start: 0, end: 0, line: 1, col: 0 },
588 "<unknown>",
589 Arc::from(""),
590 "<unknown>",
591 1,
592 None::<String>,
593 ));
594 }
595 }
596 }
597
598 if let Some(def) = default {
600 let def_len = def.len();
601
602 if let Some(min) = minimum {
603 if def_len < *min {
604 errors.push(LemmaError::engine(
605 format!(
606 "Type '{}' default value length {} is less than minimum {}",
607 type_name, def_len, min
608 ),
609 Span {
610 start: 0,
611 end: 0,
612 line: 1,
613 col: 0,
614 },
615 "<unknown>",
616 Arc::from(""),
617 "<unknown>",
618 1,
619 None::<String>,
620 ));
621 }
622 }
623 if let Some(max) = maximum {
624 if def_len > *max {
625 errors.push(LemmaError::engine(
626 format!(
627 "Type '{}' default value length {} is greater than maximum {}",
628 type_name, def_len, max
629 ),
630 Span {
631 start: 0,
632 end: 0,
633 line: 1,
634 col: 0,
635 },
636 "<unknown>",
637 Arc::from(""),
638 "<unknown>",
639 1,
640 None::<String>,
641 ));
642 }
643 }
644 if let Some(len) = length {
645 if def_len != *len {
646 errors.push(LemmaError::engine(
647 format!("Type '{}' default value length {} does not match required length {}", type_name, def_len, len),
648 Span { start: 0, end: 0, line: 1, col: 0 },
649 "<unknown>",
650 Arc::from(""),
651 "<unknown>",
652 1,
653 None::<String>,
654 ));
655 }
656 }
657 if !options.is_empty() && !options.contains(def) {
658 errors.push(LemmaError::engine(
659 format!(
660 "Type '{}' default value '{}' is not in allowed options: {:?}",
661 type_name, def, options
662 ),
663 Span {
664 start: 0,
665 end: 0,
666 line: 1,
667 col: 0,
668 },
669 "<unknown>",
670 Arc::from(""),
671 "<unknown>",
672 1,
673 None::<String>,
674 ));
675 }
676 }
677 }
678
679 TypeSpecification::Date {
680 minimum,
681 maximum,
682 default,
683 ..
684 } => {
685 if let (Some(min), Some(max)) = (minimum, maximum) {
687 if compare_date_values(min, max) == Ordering::Greater {
688 errors.push(LemmaError::engine(
689 format!(
690 "Type '{}' has invalid date range: minimum {} is after maximum {}",
691 type_name, min, max
692 ),
693 Span {
694 start: 0,
695 end: 0,
696 line: 1,
697 col: 0,
698 },
699 "<unknown>",
700 Arc::from(""),
701 "<unknown>",
702 1,
703 None::<String>,
704 ));
705 }
706 }
707
708 if let Some(def) = default {
710 if let Some(min) = minimum {
711 if compare_date_values(def, min) == Ordering::Less {
712 errors.push(LemmaError::engine(
713 format!(
714 "Type '{}' default date {} is before minimum {}",
715 type_name, def, min
716 ),
717 Span {
718 start: 0,
719 end: 0,
720 line: 1,
721 col: 0,
722 },
723 "<unknown>",
724 Arc::from(""),
725 "<unknown>",
726 1,
727 None::<String>,
728 ));
729 }
730 }
731 if let Some(max) = maximum {
732 if compare_date_values(def, max) == Ordering::Greater {
733 errors.push(LemmaError::engine(
734 format!(
735 "Type '{}' default date {} is after maximum {}",
736 type_name, def, max
737 ),
738 Span {
739 start: 0,
740 end: 0,
741 line: 1,
742 col: 0,
743 },
744 "<unknown>",
745 Arc::from(""),
746 "<unknown>",
747 1,
748 None::<String>,
749 ));
750 }
751 }
752 }
753 }
754
755 TypeSpecification::Time {
756 minimum,
757 maximum,
758 default,
759 ..
760 } => {
761 if let (Some(min), Some(max)) = (minimum, maximum) {
763 if compare_time_values(min, max) == Ordering::Greater {
764 errors.push(LemmaError::engine(
765 format!(
766 "Type '{}' has invalid time range: minimum {} is after maximum {}",
767 type_name, min, max
768 ),
769 Span {
770 start: 0,
771 end: 0,
772 line: 1,
773 col: 0,
774 },
775 "<unknown>",
776 Arc::from(""),
777 "<unknown>",
778 1,
779 None::<String>,
780 ));
781 }
782 }
783
784 if let Some(def) = default {
786 if let Some(min) = minimum {
787 if compare_time_values(def, min) == Ordering::Less {
788 errors.push(LemmaError::engine(
789 format!(
790 "Type '{}' default time {} is before minimum {}",
791 type_name, def, min
792 ),
793 Span {
794 start: 0,
795 end: 0,
796 line: 1,
797 col: 0,
798 },
799 "<unknown>",
800 Arc::from(""),
801 "<unknown>",
802 1,
803 None::<String>,
804 ));
805 }
806 }
807 if let Some(max) = maximum {
808 if compare_time_values(def, max) == Ordering::Greater {
809 errors.push(LemmaError::engine(
810 format!(
811 "Type '{}' default time {} is after maximum {}",
812 type_name, def, max
813 ),
814 Span {
815 start: 0,
816 end: 0,
817 line: 1,
818 col: 0,
819 },
820 "<unknown>",
821 Arc::from(""),
822 "<unknown>",
823 1,
824 None::<String>,
825 ));
826 }
827 }
828 }
829 }
830
831 TypeSpecification::Boolean { .. } | TypeSpecification::Duration { .. } => {
832 }
834 TypeSpecification::Veto { .. } => {
835 }
838 }
839
840 errors
841}
842
843fn compare_date_values(left: &DateTimeValue, right: &DateTimeValue) -> Ordering {
845 left.year
847 .cmp(&right.year)
848 .then_with(|| left.month.cmp(&right.month))
849 .then_with(|| left.day.cmp(&right.day))
850 .then_with(|| left.hour.cmp(&right.hour))
851 .then_with(|| left.minute.cmp(&right.minute))
852 .then_with(|| left.second.cmp(&right.second))
853}
854
855fn compare_time_values(left: &TimeValue, right: &TimeValue) -> Ordering {
857 left.hour
859 .cmp(&right.hour)
860 .then_with(|| left.minute.cmp(&right.minute))
861 .then_with(|| left.second.cmp(&right.second))
862}
863
864#[cfg(test)]
865mod tests {
866 use super::*;
867 use crate::semantic::{FactReference, FactValue, LemmaFact, LiteralValue, TypeSpecification};
868 use rust_decimal::Decimal;
869
870 fn make_doc(name: &str) -> LemmaDoc {
871 LemmaDoc::new(name.to_string())
872 }
873
874 fn make_fact(name: &str) -> LemmaFact {
875 LemmaFact {
876 reference: FactReference::local(name.to_string()),
877 value: FactValue::Literal(LiteralValue::number(Decimal::from(1))),
878 source_location: None,
879 }
880 }
881
882 #[test]
883 fn validate_basic_document() {
884 let mut doc = make_doc("test");
885 doc.facts.push(make_fact("age"));
886
887 let result = validate_types(&doc, None);
888 assert!(result.is_ok());
889 }
890
891 #[test]
892 fn validate_number_minimum_greater_than_maximum() {
893 let mut specs = TypeSpecification::number();
894 specs = specs
895 .apply_override("minimum", &["100".to_string()])
896 .unwrap();
897 specs = specs
898 .apply_override("maximum", &["50".to_string()])
899 .unwrap();
900
901 let errors = validate_type_specifications(&specs, "test");
902 assert_eq!(errors.len(), 1);
903 assert!(errors[0]
904 .to_string()
905 .contains("minimum 100 is greater than maximum 50"));
906 }
907
908 #[test]
909 fn validate_number_valid_range() {
910 let mut specs = TypeSpecification::number();
911 specs = specs.apply_override("minimum", &["0".to_string()]).unwrap();
912 specs = specs
913 .apply_override("maximum", &["100".to_string()])
914 .unwrap();
915
916 let errors = validate_type_specifications(&specs, "test");
917 assert!(errors.is_empty());
918 }
919
920 #[test]
921 fn validate_number_default_below_minimum() {
922 let specs = TypeSpecification::Number {
923 minimum: Some(Decimal::from(10)),
924 maximum: None,
925 decimals: None,
926 precision: None,
927 help: None,
928 default: Some(Decimal::from(5)),
929 };
930
931 let errors = validate_type_specifications(&specs, "test");
932 assert_eq!(errors.len(), 1);
933 assert!(errors[0]
934 .to_string()
935 .contains("default value 5 is less than minimum 10"));
936 }
937
938 #[test]
939 fn validate_number_default_above_maximum() {
940 let specs = TypeSpecification::Number {
941 minimum: None,
942 maximum: Some(Decimal::from(100)),
943 decimals: None,
944 precision: None,
945 help: None,
946 default: Some(Decimal::from(150)),
947 };
948
949 let errors = validate_type_specifications(&specs, "test");
950 assert_eq!(errors.len(), 1);
951 assert!(errors[0]
952 .to_string()
953 .contains("default value 150 is greater than maximum 100"));
954 }
955
956 #[test]
957 fn validate_number_default_valid() {
958 let specs = TypeSpecification::Number {
959 minimum: Some(Decimal::from(0)),
960 maximum: Some(Decimal::from(100)),
961 decimals: None,
962 precision: None,
963 help: None,
964 default: Some(Decimal::from(50)),
965 };
966
967 let errors = validate_type_specifications(&specs, "test");
968 assert!(errors.is_empty());
969 }
970
971 #[test]
972 fn validate_text_minimum_greater_than_maximum() {
973 let mut specs = TypeSpecification::text();
974 specs = specs
975 .apply_override("minimum", &["100".to_string()])
976 .unwrap();
977 specs = specs
978 .apply_override("maximum", &["50".to_string()])
979 .unwrap();
980
981 let errors = validate_type_specifications(&specs, "test");
982 assert_eq!(errors.len(), 1);
983 assert!(errors[0]
984 .to_string()
985 .contains("minimum length 100 is greater than maximum length 50"));
986 }
987
988 #[test]
989 fn validate_text_length_inconsistent_with_minimum() {
990 let mut specs = TypeSpecification::text();
991 specs = specs
992 .apply_override("minimum", &["10".to_string()])
993 .unwrap();
994 specs = specs.apply_override("length", &["5".to_string()]).unwrap();
995
996 let errors = validate_type_specifications(&specs, "test");
997 assert_eq!(errors.len(), 1);
998 assert!(errors[0]
999 .to_string()
1000 .contains("length 5 is less than minimum 10"));
1001 }
1002
1003 #[test]
1004 fn validate_text_default_not_in_options() {
1005 let specs = TypeSpecification::Text {
1006 minimum: None,
1007 maximum: None,
1008 length: None,
1009 options: vec!["red".to_string(), "blue".to_string()],
1010 help: None,
1011 default: Some("green".to_string()),
1012 };
1013
1014 let errors = validate_type_specifications(&specs, "test");
1015 assert_eq!(errors.len(), 1);
1016 assert!(errors[0]
1017 .to_string()
1018 .contains("default value 'green' is not in allowed options"));
1019 }
1020
1021 #[test]
1022 fn validate_text_default_valid_in_options() {
1023 let specs = TypeSpecification::Text {
1024 minimum: None,
1025 maximum: None,
1026 length: None,
1027 options: vec!["red".to_string(), "blue".to_string()],
1028 help: None,
1029 default: Some("red".to_string()),
1030 };
1031
1032 let errors = validate_type_specifications(&specs, "test");
1033 assert!(errors.is_empty());
1034 }
1035
1036 #[test]
1037 fn validate_ratio_minimum_greater_than_maximum() {
1038 let specs = TypeSpecification::Ratio {
1039 minimum: Some(Decimal::from(2)),
1040 maximum: Some(Decimal::from(1)),
1041 units: vec![],
1042 help: None,
1043 default: None,
1044 };
1045
1046 let errors = validate_type_specifications(&specs, "test");
1047 assert_eq!(errors.len(), 1);
1048 assert!(errors[0]
1049 .to_string()
1050 .contains("minimum 2 is greater than maximum 1"));
1051 }
1052
1053 #[test]
1054 fn validate_date_minimum_after_maximum() {
1055 let mut specs = TypeSpecification::date();
1056 specs = specs
1057 .apply_override("minimum", &["2024-12-31".to_string()])
1058 .unwrap();
1059 specs = specs
1060 .apply_override("maximum", &["2024-01-01".to_string()])
1061 .unwrap();
1062
1063 let errors = validate_type_specifications(&specs, "test");
1064 assert_eq!(errors.len(), 1);
1065 assert!(
1066 errors[0].to_string().contains("minimum")
1067 && errors[0].to_string().contains("is after maximum")
1068 );
1069 }
1070
1071 #[test]
1072 fn validate_date_valid_range() {
1073 let mut specs = TypeSpecification::date();
1074 specs = specs
1075 .apply_override("minimum", &["2024-01-01".to_string()])
1076 .unwrap();
1077 specs = specs
1078 .apply_override("maximum", &["2024-12-31".to_string()])
1079 .unwrap();
1080
1081 let errors = validate_type_specifications(&specs, "test");
1082 assert!(errors.is_empty());
1083 }
1084
1085 #[test]
1086 fn validate_time_minimum_after_maximum() {
1087 let mut specs = TypeSpecification::time();
1088 specs = specs
1089 .apply_override("minimum", &["23:00:00".to_string()])
1090 .unwrap();
1091 specs = specs
1092 .apply_override("maximum", &["10:00:00".to_string()])
1093 .unwrap();
1094
1095 let errors = validate_type_specifications(&specs, "test");
1096 assert_eq!(errors.len(), 1);
1097 assert!(
1098 errors[0].to_string().contains("minimum")
1099 && errors[0].to_string().contains("is after maximum")
1100 );
1101 }
1102
1103 #[test]
1104 fn validate_type_definition_with_invalid_constraints() {
1105 use crate::planning::types::TypeRegistry;
1109 use crate::semantic::TypeDef;
1110
1111 let type_def = TypeDef::Regular {
1112 name: "invalid_money".to_string(),
1113 parent: "number".to_string(),
1114 overrides: Some(vec![
1115 ("minimum".to_string(), vec!["100".to_string()]),
1116 ("maximum".to_string(), vec!["50".to_string()]),
1117 ]),
1118 };
1119
1120 let mut type_registry = TypeRegistry::new();
1122 type_registry
1123 .register_type("test", type_def)
1124 .expect("Should register type");
1125 let resolved_types = type_registry
1126 .resolve_named_types("test")
1127 .expect("Should resolve types");
1128
1129 let lemma_type = resolved_types
1131 .named_types
1132 .get("invalid_money")
1133 .expect("Should have invalid_money type");
1134 let errors = validate_type_specifications(&lemma_type.specifications, "invalid_money");
1135 assert!(!errors.is_empty());
1136 assert!(errors.iter().any(|e| e
1137 .to_string()
1138 .contains("minimum 100 is greater than maximum 50")));
1139 }
1140}