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