Skip to main content

lemma/planning/
validation.rs

1//! Semantic validation for Lemma documents
2//!
3//! Validates document structure and type declarations
4//! to catch errors early with clear messages.
5
6use 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
14/// Validate basic type declaration structure in a document
15///
16/// This performs lightweight structural validation (e.g., checking that TypeDeclaration
17/// base is not empty). Type registration, resolution, and specification validation
18/// are handled during graph building where types are actually needed.
19pub fn validate_types(
20    document: &LemmaDoc,
21    _all_docs: Option<&[LemmaDoc]>,
22) -> Result<(), Vec<LemmaError>> {
23    let mut errors = Vec::new();
24
25    // Validate type declarations in facts (inline type definitions)
26    for fact in &document.facts {
27        if let FactValue::TypeDeclaration {
28            base,
29            overrides: _,
30            from: _,
31        } = &fact.value
32        {
33            // Basic validation - check that base is not empty
34            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
66/// Validate that TypeSpecification constraints are internally consistent
67///
68/// This checks:
69/// - minimum <= maximum (for types that support ranges)
70/// - default values satisfy all constraints
71/// - length constraints are consistent (for Text)
72/// - precision/decimals are within valid ranges
73///
74/// Returns a vector of errors (empty if valid)
75pub 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            // Validate range consistency
93            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            // Validate decimals range (0-28 is rust_decimal limit)
111            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            // Validate precision is positive if set
129            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            // Validate default value constraints
147            if let Some((def_value, def_unit)) = default {
148                // Validate that the default unit exists
149                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            // Validate units (if present)
204            // Scale types can have units - validate them
205            if !units.is_empty() {
206                let mut seen_names: Vec<String> = Vec::new();
207                for unit in units {
208                    // Validate unit name is not empty
209                    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                    // Validate unit names are unique within the type (case-insensitive)
225                    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                    // Validate unit values are positive (conversion factors relative to type base of 1)
244                    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            // Validate range consistency
267            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            // Validate decimals range (0-28 is rust_decimal limit)
285            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            // Validate precision is positive if set
303            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            // Validate default value constraints
321            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            // Note: Number types are dimensionless and cannot have units (validated in apply_override)
356        }
357
358        TypeSpecification::Ratio {
359            minimum,
360            maximum,
361            default,
362            units,
363            ..
364        } => {
365            // Validate range consistency
366            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            // Validate default value constraints
384            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            // Validate units (if present)
420            // Types can have zero units (e.g., type ratio = number -> ratio) - this is valid
421            // Only validate if units are defined
422            if !units.is_empty() {
423                let mut seen_names: Vec<String> = Vec::new();
424                for unit in units {
425                    // Validate unit name is not empty
426                    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                    // Validate unit names are unique within the type (case-insensitive)
442                    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                    // Validate unit values are positive (conversion factors relative to type base of 1)
461                    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            // Validate range consistency
485            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            // Validate length consistency
500            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            // Validate default value constraints
530            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            // Validate range consistency
602            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            // Validate default value constraints
620            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            // Validate range consistency
663            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            // Validate default value constraints
681            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            // No constraint validation needed for these types
719        }
720        TypeSpecification::Veto { .. } => {
721            // Veto is not a user-declarable type, so validation should not be called on it
722            // But if it is, there's nothing to validate
723        }
724    }
725
726    errors
727}
728
729/// Compare two DateTimeValue instances for ordering
730fn compare_date_values(left: &DateTimeValue, right: &DateTimeValue) -> Ordering {
731    // Compare by year, month, day, hour, minute, second
732    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
741/// Compare two TimeValue instances for ordering
742fn compare_time_values(left: &TimeValue, right: &TimeValue) -> Ordering {
743    // Compare by hour, minute, second
744    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        // This test now validates that type specification validation works correctly.
1018        // The actual validation happens during graph building, but we test the validation
1019        // function directly here.
1020        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        // Register and resolve the type to get its specifications
1043        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        // Validate the specifications
1052        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}