1use std::collections::{HashMap, HashSet};
6
7use crate::error::{ErrorKind, ValidationError};
8
9#[must_use]
14pub fn validate(schema: &Schema, value: &Spanned<RonValue>) -> Vec<ValidationError> {
15 let mut errors = Vec::new();
16 validate_struct(&schema.root, value, "", &mut errors, &schema.enums, &schema.aliases);
17 errors
18}
19use crate::ron::RonValue;
20use crate::schema::{EnumDef, Schema, SchemaType, StructDef};
21use crate::span::Spanned;
22
23fn describe(value: &RonValue) -> String {
25 match value {
26 RonValue::String(s) => {
27 if s.len() > 20 {
28 format!("String(\"{}...\")", &s[..20])
29 } else {
30 format!("String(\"{s}\")")
31 }
32 }
33 RonValue::Integer(n) => format!("Integer({n})"),
34 RonValue::Float(f) => format!("Float({f})"),
35 RonValue::Bool(b) => format!("Bool({b})"),
36 RonValue::Option(_) => "Option".to_string(),
37 RonValue::Identifier(s) => format!("Identifier({s})"),
38 RonValue::EnumVariant(name, _) => format!("{name}(...)"),
39 RonValue::List(_) => "List".to_string(),
40 RonValue::Map(_) => "Map".to_string(),
41 RonValue::Tuple(_) => "Tuple".to_string(),
42 RonValue::Struct(_) => "Struct".to_string(),
43 }
44}
45
46fn build_path(parent: &str, field: &str) -> String {
51 if parent.is_empty() {
52 field.to_string()
53 } else {
54 format!("{parent}.{field}")
55 }
56}
57
58#[allow(clippy::too_many_lines)]
64fn validate_type(
65 expected: &SchemaType,
66 actual: &Spanned<RonValue>,
67 path: &str,
68 errors: &mut Vec<ValidationError>,
69 enums: &HashMap<String, EnumDef>,
70 aliases: &HashMap<String, Spanned<SchemaType>>,
71) {
72 match expected {
73 SchemaType::String => {
75 if !matches!(actual.value, RonValue::String(_)) {
76 errors.push(ValidationError {
77 path: path.to_string(),
78 span: actual.span,
79 kind: ErrorKind::TypeMismatch {
80 expected: "String".to_string(),
81 found: describe(&actual.value),
82 },
83 });
84 }
85 }
86 SchemaType::Integer => {
87 if !matches!(actual.value, RonValue::Integer(_)) {
88 errors.push(ValidationError {
89 path: path.to_string(),
90 span: actual.span,
91 kind: ErrorKind::TypeMismatch {
92 expected: "Integer".to_string(),
93 found: describe(&actual.value),
94 },
95 });
96 }
97 }
98 SchemaType::Float => {
99 if !matches!(actual.value, RonValue::Float(_)) {
100 errors.push(ValidationError {
101 path: path.to_string(),
102 span: actual.span,
103 kind: ErrorKind::TypeMismatch {
104 expected: "Float".to_string(),
105 found: describe(&actual.value),
106 },
107 });
108 }
109 }
110 SchemaType::Bool => {
111 if !matches!(actual.value, RonValue::Bool(_)) {
112 errors.push(ValidationError {
113 path: path.to_string(),
114 span: actual.span,
115 kind: ErrorKind::TypeMismatch {
116 expected: "Bool".to_string(),
117 found: describe(&actual.value),
118 },
119 });
120 }
121 }
122
123 SchemaType::Option(inner_type) => match &actual.value {
126 RonValue::Option(None) => {}
127 RonValue::Option(Some(inner_value)) => {
128 validate_type(inner_type, inner_value, path, errors, enums, aliases);
129 }
130 _ => {
131 errors.push(ValidationError {
132 path: path.to_string(),
133 span: actual.span,
134 kind: ErrorKind::ExpectedOption {
135 found: describe(&actual.value),
136 },
137 });
138 }
139 },
140
141 SchemaType::List(element_type) => {
144 if let RonValue::List(elements) = &actual.value {
145 for (index, element) in elements.iter().enumerate() {
146 let element_path = format!("{path}[{index}]");
147 validate_type(element_type, element, &element_path, errors, enums, aliases);
148 }
149 } else {
150 errors.push(ValidationError {
151 path: path.to_string(),
152 span: actual.span,
153 kind: ErrorKind::ExpectedList {
154 found: describe(&actual.value),
155 },
156 });
157 }
158 }
159
160 SchemaType::EnumRef(enum_name) => {
164 let enum_def = &enums[enum_name];
165 let variant_names: Vec<String> = enum_def.variants.keys().cloned().collect();
166
167 match &actual.value {
168 RonValue::Identifier(variant) => {
170 match enum_def.variants.get(variant) {
171 None => {
172 errors.push(ValidationError {
173 path: path.to_string(),
174 span: actual.span,
175 kind: ErrorKind::InvalidEnumVariant {
176 enum_name: enum_name.clone(),
177 variant: variant.clone(),
178 valid: variant_names,
179 },
180 });
181 }
182 Some(Some(_expected_data_type)) => {
183 errors.push(ValidationError {
185 path: path.to_string(),
186 span: actual.span,
187 kind: ErrorKind::InvalidVariantData {
188 enum_name: enum_name.clone(),
189 variant: variant.clone(),
190 expected: "data".to_string(),
191 found: "unit variant".to_string(),
192 },
193 });
194 }
195 Some(None) => {} }
197 }
198 RonValue::EnumVariant(variant, data) => {
200 match enum_def.variants.get(variant) {
201 None => {
202 errors.push(ValidationError {
203 path: path.to_string(),
204 span: actual.span,
205 kind: ErrorKind::InvalidEnumVariant {
206 enum_name: enum_name.clone(),
207 variant: variant.clone(),
208 valid: variant_names,
209 },
210 });
211 }
212 Some(None) => {
213 errors.push(ValidationError {
215 path: path.to_string(),
216 span: actual.span,
217 kind: ErrorKind::InvalidVariantData {
218 enum_name: enum_name.clone(),
219 variant: variant.clone(),
220 expected: "unit variant".to_string(),
221 found: describe(&data.value),
222 },
223 });
224 }
225 Some(Some(expected_data_type)) => {
226 validate_type(expected_data_type, data, path, errors, enums, aliases);
228 }
229 }
230 }
231 _ => {
233 errors.push(ValidationError {
234 path: path.to_string(),
235 span: actual.span,
236 kind: ErrorKind::TypeMismatch {
237 expected: enum_name.clone(),
238 found: describe(&actual.value),
239 },
240 });
241 }
242 }
243 }
244
245 SchemaType::Map(key_type, value_type) => {
247 if let RonValue::Map(entries) = &actual.value {
248 for (key, value) in entries {
249 let key_desc = describe(&key.value);
250 validate_type(key_type, key, path, errors, enums, aliases);
251 let entry_path = format!("{path}[{key_desc}]");
252 validate_type(value_type, value, &entry_path, errors, enums, aliases);
253 }
254 } else {
255 errors.push(ValidationError {
256 path: path.to_string(),
257 span: actual.span,
258 kind: ErrorKind::ExpectedMap {
259 found: describe(&actual.value),
260 },
261 });
262 }
263 }
264
265 SchemaType::Tuple(element_types) => {
267 if let RonValue::Tuple(elements) = &actual.value {
268 if elements.len() == element_types.len() {
269 for (index, (expected_type, element)) in element_types.iter().zip(elements).enumerate() {
270 let element_path = format!("{path}.{index}");
271 validate_type(expected_type, element, &element_path, errors, enums, aliases);
272 }
273 } else {
274 errors.push(ValidationError {
275 path: path.to_string(),
276 span: actual.span,
277 kind: ErrorKind::TupleLengthMismatch {
278 expected: element_types.len(),
279 found: elements.len(),
280 },
281 });
282 }
283 } else {
284 errors.push(ValidationError {
285 path: path.to_string(),
286 span: actual.span,
287 kind: ErrorKind::ExpectedTuple {
288 found: describe(&actual.value),
289 },
290 });
291 }
292 }
293
294 SchemaType::AliasRef(alias_name) => {
297 if let Some(resolved) = aliases.get(alias_name) {
298 validate_type(&resolved.value, actual, path, errors, enums, aliases);
299 }
300 }
302
303 SchemaType::Struct(struct_def) => {
305 validate_struct(struct_def, actual, path, errors, enums, aliases);
306 }
307 }
308}
309
310fn validate_struct(
317 struct_def: &StructDef,
318 actual: &Spanned<RonValue>,
319 path: &str,
320 errors: &mut Vec<ValidationError>,
321 enums: &HashMap<String, EnumDef>,
322 aliases: &HashMap<String, Spanned<SchemaType>>,
323) {
324 let RonValue::Struct(data_struct) = &actual.value else {
326 errors.push(ValidationError {
327 path: path.to_string(),
328 span: actual.span,
329 kind: ErrorKind::ExpectedStruct {
330 found: describe(&actual.value),
331 },
332 });
333 return;
334 };
335
336 let data_map: HashMap<&str, &Spanned<RonValue>> = data_struct
338 .fields
339 .iter()
340 .map(|(name, value)| (name.value.as_str(), value))
341 .collect();
342
343 let schema_names: HashSet<&str> = struct_def
345 .fields
346 .iter()
347 .map(|f| f.name.value.as_str())
348 .collect();
349
350 for field_def in &struct_def.fields {
352 if !data_map.contains_key(field_def.name.value.as_str()) && field_def.default.is_none() {
353 errors.push(ValidationError {
354 path: build_path(path, &field_def.name.value),
355 span: data_struct.close_span,
356 kind: ErrorKind::MissingField {
357 field_name: field_def.name.value.clone(),
358 },
359 });
360 }
361 }
362
363 for (name, _value) in &data_struct.fields {
365 if !schema_names.contains(name.value.as_str()) {
366 errors.push(ValidationError {
367 path: build_path(path, &name.value),
368 span: name.span,
369 kind: ErrorKind::UnknownField {
370 field_name: name.value.clone(),
371 },
372 });
373 }
374 }
375
376 for field_def in &struct_def.fields {
378 if let Some(data_value) = data_map.get(field_def.name.value.as_str()) {
379 let field_path = build_path(path, &field_def.name.value);
380 validate_type(&field_def.type_.value, data_value, &field_path, errors, enums, aliases);
381 }
382 }
383}
384
385#[cfg(test)]
386mod tests {
387 use super::*;
388 use crate::schema::parser::parse_schema;
389 use crate::ron::parser::parse_ron;
390
391 fn validate_str(schema_src: &str, data_src: &str) -> Vec<ValidationError> {
393 let schema = parse_schema(schema_src).expect("test schema should parse");
394 let data = parse_ron(data_src).expect("test data should parse");
395 validate(&schema, &data)
396 }
397
398 #[test]
404 fn describe_string() {
405 assert_eq!(describe(&RonValue::String("hi".to_string())), "String(\"hi\")");
406 }
407
408 #[test]
410 fn describe_string_truncated() {
411 let long = "a".repeat(30);
412 let desc = describe(&RonValue::String(long));
413 assert!(desc.contains("..."));
414 }
415
416 #[test]
418 fn describe_integer() {
419 assert_eq!(describe(&RonValue::Integer(42)), "Integer(42)");
420 }
421
422 #[test]
424 fn describe_float() {
425 assert_eq!(describe(&RonValue::Float(3.14)), "Float(3.14)");
426 }
427
428 #[test]
430 fn describe_bool() {
431 assert_eq!(describe(&RonValue::Bool(true)), "Bool(true)");
432 }
433
434 #[test]
436 fn describe_identifier() {
437 assert_eq!(describe(&RonValue::Identifier("Creature".to_string())), "Identifier(Creature)");
438 }
439
440 #[test]
446 fn build_path_root() {
447 assert_eq!(build_path("", "name"), "name");
448 }
449
450 #[test]
452 fn build_path_nested() {
453 assert_eq!(build_path("cost", "generic"), "cost.generic");
454 }
455
456 #[test]
458 fn build_path_deep() {
459 assert_eq!(build_path("a.b", "c"), "a.b.c");
460 }
461
462 #[test]
468 fn valid_single_string_field() {
469 let errs = validate_str("(\n name: String,\n)", "(name: \"hello\")");
470 assert!(errs.is_empty());
471 }
472
473 #[test]
475 fn valid_all_primitives() {
476 let schema = "(\n s: String,\n i: Integer,\n f: Float,\n b: Bool,\n)";
477 let data = "(s: \"hi\", i: 42, f: 3.14, b: true)";
478 let errs = validate_str(schema, data);
479 assert!(errs.is_empty());
480 }
481
482 #[test]
484 fn valid_option_none() {
485 let errs = validate_str("(\n power: Option(Integer),\n)", "(power: None)");
486 assert!(errs.is_empty());
487 }
488
489 #[test]
491 fn valid_option_some() {
492 let errs = validate_str("(\n power: Option(Integer),\n)", "(power: Some(5))");
493 assert!(errs.is_empty());
494 }
495
496 #[test]
498 fn valid_list_empty() {
499 let errs = validate_str("(\n tags: [String],\n)", "(tags: [])");
500 assert!(errs.is_empty());
501 }
502
503 #[test]
505 fn valid_list_populated() {
506 let errs = validate_str("(\n tags: [String],\n)", "(tags: [\"a\", \"b\"])");
507 assert!(errs.is_empty());
508 }
509
510 #[test]
512 fn valid_enum_variant() {
513 let schema = "(\n kind: Kind,\n)\nenum Kind { A, B, C }";
514 let data = "(kind: B)";
515 let errs = validate_str(schema, data);
516 assert!(errs.is_empty());
517 }
518
519 #[test]
521 fn valid_enum_list() {
522 let schema = "(\n types: [CardType],\n)\nenum CardType { Creature, Trap }";
523 let data = "(types: [Creature, Trap])";
524 let errs = validate_str(schema, data);
525 assert!(errs.is_empty());
526 }
527
528 #[test]
530 fn valid_nested_struct() {
531 let schema = "(\n cost: (\n generic: Integer,\n sigil: Integer,\n ),\n)";
532 let data = "(cost: (generic: 2, sigil: 1))";
533 let errs = validate_str(schema, data);
534 assert!(errs.is_empty());
535 }
536
537 #[test]
543 fn type_mismatch_string_got_integer() {
544 let errs = validate_str("(\n name: String,\n)", "(name: 42)");
545 assert_eq!(errs.len(), 1);
546 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { expected, .. } if expected == "String"));
547 }
548
549 #[test]
551 fn type_mismatch_integer_got_string() {
552 let errs = validate_str("(\n age: Integer,\n)", "(age: \"five\")");
553 assert_eq!(errs.len(), 1);
554 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { expected, .. } if expected == "Integer"));
555 }
556
557 #[test]
559 fn type_mismatch_float_got_integer() {
560 let errs = validate_str("(\n rate: Float,\n)", "(rate: 5)");
561 assert_eq!(errs.len(), 1);
562 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { expected, .. } if expected == "Float"));
563 }
564
565 #[test]
567 fn type_mismatch_bool_got_string() {
568 let errs = validate_str("(\n flag: Bool,\n)", "(flag: \"yes\")");
569 assert_eq!(errs.len(), 1);
570 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { expected, .. } if expected == "Bool"));
571 }
572
573 #[test]
575 fn type_mismatch_has_correct_path() {
576 let errs = validate_str("(\n name: String,\n)", "(name: 42)");
577 assert_eq!(errs[0].path, "name");
578 }
579
580 #[test]
586 fn missing_field_detected() {
587 let errs = validate_str("(\n name: String,\n age: Integer,\n)", "(name: \"hi\")");
588 assert_eq!(errs.len(), 1);
589 assert!(matches!(&errs[0].kind, ErrorKind::MissingField { field_name } if field_name == "age"));
590 }
591
592 #[test]
594 fn missing_field_has_correct_path() {
595 let errs = validate_str("(\n name: String,\n age: Integer,\n)", "(name: \"hi\")");
596 assert_eq!(errs[0].path, "age");
597 }
598
599 #[test]
601 fn missing_field_span_points_to_close_paren() {
602 let data = "(name: \"hi\")";
603 let errs = validate_str("(\n name: String,\n age: Integer,\n)", data);
604 assert_eq!(errs[0].span.start.offset, data.len() - 1);
606 }
607
608 #[test]
610 fn missing_fields_all_reported() {
611 let errs = validate_str("(\n a: String,\n b: Integer,\n c: Bool,\n)", "()");
612 assert_eq!(errs.len(), 3);
613 }
614
615 #[test]
621 fn default_field_not_required() {
622 let errs = validate_str("(\n name: String,\n label: String = \"none\",\n)", "(name: \"hi\")");
623 assert!(errs.is_empty());
624 }
625
626 #[test]
628 fn default_field_still_validates_type() {
629 let errs = validate_str("(\n name: String,\n label: String = \"none\",\n)", "(name: \"hi\", label: 42)");
630 assert_eq!(errs.len(), 1);
631 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { .. }));
632 }
633
634 #[test]
636 fn default_field_accepts_correct_type() {
637 let errs = validate_str("(\n name: String,\n label: String = \"none\",\n)", "(name: \"hi\", label: \"custom\")");
638 assert!(errs.is_empty());
639 }
640
641 #[test]
643 fn non_default_field_still_required() {
644 let errs = validate_str("(\n name: String,\n label: String = \"none\",\n)", "(label: \"hi\")");
645 assert_eq!(errs.len(), 1);
646 assert!(matches!(&errs[0].kind, ErrorKind::MissingField { field_name } if field_name == "name"));
647 }
648
649 #[test]
651 fn multiple_default_fields_all_absent() {
652 let errs = validate_str(
653 "(\n name: String,\n a: Integer = 0,\n b: Bool = false,\n c: String = \"x\",\n)",
654 "(name: \"hi\")",
655 );
656 assert!(errs.is_empty());
657 }
658
659 #[test]
661 fn default_option_field_not_required() {
662 let errs = validate_str("(\n name: String,\n tag: Option(String) = None,\n)", "(name: \"hi\")");
663 assert!(errs.is_empty());
664 }
665
666 #[test]
668 fn default_list_field_not_required() {
669 let errs = validate_str("(\n name: String,\n tags: [String] = [],\n)", "(name: \"hi\")");
670 assert!(errs.is_empty());
671 }
672
673 #[test]
679 fn unknown_field_detected() {
680 let errs = validate_str("(\n name: String,\n)", "(name: \"hi\", colour: \"red\")");
681 assert_eq!(errs.len(), 1);
682 assert!(matches!(&errs[0].kind, ErrorKind::UnknownField { field_name } if field_name == "colour"));
683 }
684
685 #[test]
687 fn unknown_field_has_correct_path() {
688 let errs = validate_str("(\n name: String,\n)", "(name: \"hi\", extra: 5)");
689 assert_eq!(errs[0].path, "extra");
690 }
691
692 #[test]
698 fn invalid_enum_variant() {
699 let schema = "(\n kind: Kind,\n)\nenum Kind { A, B }";
700 let errs = validate_str(schema, "(kind: C)");
701 assert_eq!(errs.len(), 1);
702 assert!(matches!(&errs[0].kind, ErrorKind::InvalidEnumVariant { variant, .. } if variant == "C"));
703 }
704
705 #[test]
707 fn enum_rejects_string() {
708 let schema = "(\n kind: Kind,\n)\nenum Kind { A, B }";
709 let errs = validate_str(schema, "(kind: \"A\")");
710 assert_eq!(errs.len(), 1);
711 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { .. }));
712 }
713
714 #[test]
720 fn expected_option_got_bare_value() {
721 let errs = validate_str("(\n power: Option(Integer),\n)", "(power: 5)");
722 assert_eq!(errs.len(), 1);
723 assert!(matches!(&errs[0].kind, ErrorKind::ExpectedOption { .. }));
724 }
725
726 #[test]
728 fn option_some_wrong_inner_type() {
729 let errs = validate_str("(\n power: Option(Integer),\n)", "(power: Some(\"five\"))");
730 assert_eq!(errs.len(), 1);
731 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { expected, .. } if expected == "Integer"));
732 }
733
734 #[test]
740 fn expected_list_got_string() {
741 let errs = validate_str("(\n tags: [String],\n)", "(tags: \"hi\")");
742 assert_eq!(errs.len(), 1);
743 assert!(matches!(&errs[0].kind, ErrorKind::ExpectedList { .. }));
744 }
745
746 #[test]
748 fn list_element_wrong_type() {
749 let errs = validate_str("(\n tags: [String],\n)", "(tags: [\"ok\", 42])");
750 assert_eq!(errs.len(), 1);
751 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { expected, .. } if expected == "String"));
752 }
753
754 #[test]
756 fn list_element_error_has_bracket_path() {
757 let errs = validate_str("(\n tags: [String],\n)", "(tags: [\"ok\", 42])");
758 assert_eq!(errs[0].path, "tags[1]");
759 }
760
761 #[test]
767 fn expected_struct_got_integer() {
768 let schema = "(\n cost: (\n generic: Integer,\n ),\n)";
769 let errs = validate_str(schema, "(cost: 5)");
770 assert_eq!(errs.len(), 1);
771 assert!(matches!(&errs[0].kind, ErrorKind::ExpectedStruct { .. }));
772 }
773
774 #[test]
780 fn nested_struct_type_mismatch_path() {
781 let schema = "(\n cost: (\n generic: Integer,\n ),\n)";
782 let errs = validate_str(schema, "(cost: (generic: \"two\"))");
783 assert_eq!(errs.len(), 1);
784 assert_eq!(errs[0].path, "cost.generic");
785 }
786
787 #[test]
789 fn nested_struct_missing_field_path() {
790 let schema = "(\n cost: (\n generic: Integer,\n sigil: Integer,\n ),\n)";
791 let errs = validate_str(schema, "(cost: (generic: 1))");
792 assert_eq!(errs.len(), 1);
793 assert_eq!(errs[0].path, "cost.sigil");
794 }
795
796 #[test]
802 fn multiple_errors_collected() {
803 let schema = "(\n name: String,\n age: Integer,\n active: Bool,\n)";
804 let data = "(name: 42, age: \"five\", active: \"yes\")";
805 let errs = validate_str(schema, data);
806 assert_eq!(errs.len(), 3);
807 }
808
809 #[test]
811 fn mixed_error_types_collected() {
812 let schema = "(\n name: String,\n age: Integer,\n)";
813 let data = "(name: \"hi\", age: \"five\", extra: true)";
814 let errs = validate_str(schema, data);
815 assert_eq!(errs.len(), 2);
817 }
818
819 #[test]
825 fn valid_card_data() {
826 let schema = r#"(
827 name: String,
828 card_types: [CardType],
829 legendary: Bool,
830 power: Option(Integer),
831 toughness: Option(Integer),
832 keywords: [String],
833 )
834 enum CardType { Creature, Trap, Artifact }"#;
835 let data = r#"(
836 name: "Ashborn Hound",
837 card_types: [Creature],
838 legendary: false,
839 power: Some(1),
840 toughness: Some(1),
841 keywords: [],
842 )"#;
843 let errs = validate_str(schema, data);
844 assert!(errs.is_empty());
845 }
846
847 #[test]
849 fn card_data_multiple_errors() {
850 let schema = r#"(
851 name: String,
852 card_types: [CardType],
853 legendary: Bool,
854 power: Option(Integer),
855 )
856 enum CardType { Creature, Trap }"#;
857 let data = r#"(
858 name: 42,
859 card_types: [Pirates],
860 legendary: false,
861 power: Some("five"),
862 )"#;
863 let errs = validate_str(schema, data);
864 assert_eq!(errs.len(), 3);
866 }
867
868 #[test]
874 fn alias_struct_valid() {
875 let schema = "(\n cost: Cost,\n)\ntype Cost = (generic: Integer,)";
876 let data = "(cost: (generic: 5))";
877 let errs = validate_str(schema, data);
878 assert!(errs.is_empty());
879 }
880
881 #[test]
883 fn alias_struct_type_mismatch() {
884 let schema = "(\n cost: Cost,\n)\ntype Cost = (generic: Integer,)";
885 let data = "(cost: (generic: \"five\"))";
886 let errs = validate_str(schema, data);
887 assert_eq!(errs.len(), 1);
888 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { expected, .. } if expected == "Integer"));
889 }
890
891 #[test]
893 fn alias_primitive_valid() {
894 let schema = "(\n name: Name,\n)\ntype Name = String";
895 let data = "(name: \"hello\")";
896 let errs = validate_str(schema, data);
897 assert!(errs.is_empty());
898 }
899
900 #[test]
902 fn alias_primitive_mismatch() {
903 let schema = "(\n name: Name,\n)\ntype Name = String";
904 let data = "(name: 42)";
905 let errs = validate_str(schema, data);
906 assert_eq!(errs.len(), 1);
907 }
908
909 #[test]
911 fn alias_in_list_valid() {
912 let schema = "(\n costs: [Cost],\n)\ntype Cost = (generic: Integer,)";
913 let data = "(costs: [(generic: 1), (generic: 2)])";
914 let errs = validate_str(schema, data);
915 assert!(errs.is_empty());
916 }
917
918 #[test]
920 fn alias_in_list_element_error() {
921 let schema = "(\n costs: [Cost],\n)\ntype Cost = (generic: Integer,)";
922 let data = "(costs: [(generic: 1), (generic: \"two\")])";
923 let errs = validate_str(schema, data);
924 assert_eq!(errs.len(), 1);
925 assert_eq!(errs[0].path, "costs[1].generic");
926 }
927
928 #[test]
934 fn map_valid() {
935 let schema = "(\n attrs: {String: Integer},\n)";
936 let data = "(attrs: {\"str\": 5, \"dex\": 3})";
937 let errs = validate_str(schema, data);
938 assert!(errs.is_empty());
939 }
940
941 #[test]
943 fn map_empty_valid() {
944 let schema = "(\n attrs: {String: Integer},\n)";
945 let data = "(attrs: {})";
946 let errs = validate_str(schema, data);
947 assert!(errs.is_empty());
948 }
949
950 #[test]
952 fn map_expected_got_string() {
953 let schema = "(\n attrs: {String: Integer},\n)";
954 let data = "(attrs: \"not a map\")";
955 let errs = validate_str(schema, data);
956 assert_eq!(errs.len(), 1);
957 assert!(matches!(&errs[0].kind, ErrorKind::ExpectedMap { .. }));
958 }
959
960 #[test]
962 fn map_wrong_value_type() {
963 let schema = "(\n attrs: {String: Integer},\n)";
964 let data = "(attrs: {\"str\": \"five\"})";
965 let errs = validate_str(schema, data);
966 assert_eq!(errs.len(), 1);
967 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { expected, .. } if expected == "Integer"));
968 }
969
970 #[test]
972 fn map_wrong_key_type() {
973 let schema = "(\n attrs: {String: Integer},\n)";
974 let data = "(attrs: {42: 5})";
975 let errs = validate_str(schema, data);
976 assert_eq!(errs.len(), 1);
977 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { expected, .. } if expected == "String"));
978 }
979
980 #[test]
986 fn tuple_valid() {
987 let schema = "(\n pos: (Float, Float),\n)";
988 let data = "(pos: (1.0, 2.5))";
989 let errs = validate_str(schema, data);
990 assert!(errs.is_empty());
991 }
992
993 #[test]
995 fn tuple_expected_got_string() {
996 let schema = "(\n pos: (Float, Float),\n)";
997 let data = "(pos: \"not a tuple\")";
998 let errs = validate_str(schema, data);
999 assert_eq!(errs.len(), 1);
1000 assert!(matches!(&errs[0].kind, ErrorKind::ExpectedTuple { .. }));
1001 }
1002
1003 #[test]
1005 fn tuple_wrong_length() {
1006 let schema = "(\n pos: (Float, Float),\n)";
1007 let data = "(pos: (1.0, 2.5, 3.0))";
1008 let errs = validate_str(schema, data);
1009 assert_eq!(errs.len(), 1);
1010 assert!(matches!(&errs[0].kind, ErrorKind::TupleLengthMismatch { expected: 2, found: 3 }));
1011 }
1012
1013 #[test]
1015 fn tuple_wrong_element_type() {
1016 let schema = "(\n pos: (Float, Float),\n)";
1017 let data = "(pos: (1.0, \"bad\"))";
1018 let errs = validate_str(schema, data);
1019 assert_eq!(errs.len(), 1);
1020 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { expected, .. } if expected == "Float"));
1021 }
1022
1023 #[test]
1025 fn tuple_element_error_path() {
1026 let schema = "(\n pos: (Float, Float),\n)";
1027 let data = "(pos: (1.0, \"bad\"))";
1028 let errs = validate_str(schema, data);
1029 assert_eq!(errs[0].path, "pos.1");
1030 }
1031
1032 #[test]
1038 fn enum_data_variant_valid() {
1039 let schema = "(\n effect: Effect,\n)\nenum Effect { Damage(Integer), Draw }";
1040 let data = "(effect: Damage(5))";
1041 let errs = validate_str(schema, data);
1042 assert!(errs.is_empty());
1043 }
1044
1045 #[test]
1047 fn enum_unit_variant_valid() {
1048 let schema = "(\n effect: Effect,\n)\nenum Effect { Damage(Integer), Draw }";
1049 let data = "(effect: Draw)";
1050 let errs = validate_str(schema, data);
1051 assert!(errs.is_empty());
1052 }
1053
1054 #[test]
1056 fn enum_data_variant_wrong_type() {
1057 let schema = "(\n effect: Effect,\n)\nenum Effect { Damage(Integer), Draw }";
1058 let data = "(effect: Damage(\"five\"))";
1059 let errs = validate_str(schema, data);
1060 assert_eq!(errs.len(), 1);
1061 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { expected, .. } if expected == "Integer"));
1062 }
1063
1064 #[test]
1066 fn enum_data_variant_unknown() {
1067 let schema = "(\n effect: Effect,\n)\nenum Effect { Damage(Integer), Draw }";
1068 let data = "(effect: Explode(10))";
1069 let errs = validate_str(schema, data);
1070 assert_eq!(errs.len(), 1);
1071 assert!(matches!(&errs[0].kind, ErrorKind::InvalidEnumVariant { .. }));
1072 }
1073
1074 #[test]
1076 fn enum_missing_data() {
1077 let schema = "(\n effect: Effect,\n)\nenum Effect { Damage(Integer), Draw }";
1078 let data = "(effect: Damage)";
1079 let errs = validate_str(schema, data);
1080 assert_eq!(errs.len(), 1);
1081 assert!(matches!(&errs[0].kind, ErrorKind::InvalidVariantData { .. }));
1082 }
1083
1084 #[test]
1086 fn enum_unexpected_data() {
1087 let schema = "(\n effect: Effect,\n)\nenum Effect { Damage(Integer), Draw }";
1088 let data = "(effect: Draw(5))";
1089 let errs = validate_str(schema, data);
1090 assert_eq!(errs.len(), 1);
1091 assert!(matches!(&errs[0].kind, ErrorKind::InvalidVariantData { .. }));
1092 }
1093}