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 rust_decimal::Decimal;
10use std::cmp::Ordering;
11use std::sync::Arc;
12
13/// Validate basic type declaration structure in a document
14///
15/// This performs lightweight structural validation (e.g., checking that TypeDeclaration
16/// base is not empty). Type registration, resolution, and specification validation
17/// are handled during graph building where types are actually needed.
18pub fn validate_types(
19    document: &LemmaDoc,
20    _all_docs: Option<&[LemmaDoc]>,
21) -> Result<(), Vec<LemmaError>> {
22    let mut errors = Vec::new();
23
24    // Validate type declarations in facts (inline type definitions)
25    for fact in &document.facts {
26        if let FactValue::TypeDeclaration {
27            base,
28            overrides: _,
29            from: _,
30        } = &fact.value
31        {
32            // Basic validation - check that base is not empty
33            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
59/// Validate that TypeSpecification constraints are internally consistent
60///
61/// This checks:
62/// - minimum <= maximum (for types that support ranges)
63/// - default values satisfy all constraints
64/// - length constraints are consistent (for Text)
65/// - precision/decimals are within valid ranges
66///
67/// Returns a vector of errors (empty if valid)
68pub 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            // Validate range consistency
82            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            // Validate decimals range (0-28 is rust_decimal limit)
105            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            // Validate precision is positive if set
128            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            // Validate default value constraints
151            if let Some((def_value, def_unit)) = default {
152                // Validate that the default unit exists
153                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            // Validate units (if present)
223            // Scale types can have units - validate them
224            if !units.is_empty() {
225                let mut seen_names: Vec<String> = Vec::new();
226                for unit in units {
227                    // Validate unit name is not empty
228                    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                    // Validate unit names are unique within the type (case-insensitive)
249                    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                    // Validate unit values are positive (conversion factors relative to type base of 1)
268                    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            // Validate range consistency
291            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            // Validate decimals range (0-28 is rust_decimal limit)
314            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            // Validate precision is positive if set
337            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            // Validate default value constraints
360            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            // Note: Number types are dimensionless and cannot have units (validated in apply_override)
405        }
406
407        TypeSpecification::Ratio {
408            minimum,
409            maximum,
410            default,
411            units,
412            ..
413        } => {
414            // Validate range consistency
415            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            // Validate default value constraints
438            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            // Validate units (if present)
484            // Types can have zero units (e.g., type ratio = number -> ratio) - this is valid
485            // Only validate if units are defined
486            if !units.is_empty() {
487                let mut seen_names: Vec<String> = Vec::new();
488                for unit in units {
489                    // Validate unit name is not empty
490                    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                    // Validate unit names are unique within the type (case-insensitive)
511                    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                    // Validate unit values are positive (conversion factors relative to type base of 1)
530                    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            // Validate range consistency
554            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            // Validate length consistency
569            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            // Validate default value constraints
599            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            // Validate range consistency
686            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            // Validate default value constraints
709            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            // Validate range consistency
762            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            // Validate default value constraints
785            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            // No constraint validation needed for these types
833        }
834        TypeSpecification::Veto { .. } => {
835            // Veto is not a user-declarable type, so validation should not be called on it
836            // But if it is, there's nothing to validate
837        }
838    }
839
840    errors
841}
842
843/// Compare two DateTimeValue instances for ordering
844fn compare_date_values(left: &DateTimeValue, right: &DateTimeValue) -> Ordering {
845    // Compare by year, month, day, hour, minute, second
846    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
855/// Compare two TimeValue instances for ordering
856fn compare_time_values(left: &TimeValue, right: &TimeValue) -> Ordering {
857    // Compare by hour, minute, second
858    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        // This test now validates that type specification validation works correctly.
1106        // The actual validation happens during graph building, but we test the validation
1107        // function directly here.
1108        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        // Register and resolve the type to get its specifications
1121        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        // Validate the specifications
1130        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}