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()) {
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 unknown_field_detected() {
622 let errs = validate_str("(\n name: String,\n)", "(name: \"hi\", colour: \"red\")");
623 assert_eq!(errs.len(), 1);
624 assert!(matches!(&errs[0].kind, ErrorKind::UnknownField { field_name } if field_name == "colour"));
625 }
626
627 #[test]
629 fn unknown_field_has_correct_path() {
630 let errs = validate_str("(\n name: String,\n)", "(name: \"hi\", extra: 5)");
631 assert_eq!(errs[0].path, "extra");
632 }
633
634 #[test]
640 fn invalid_enum_variant() {
641 let schema = "(\n kind: Kind,\n)\nenum Kind { A, B }";
642 let errs = validate_str(schema, "(kind: C)");
643 assert_eq!(errs.len(), 1);
644 assert!(matches!(&errs[0].kind, ErrorKind::InvalidEnumVariant { variant, .. } if variant == "C"));
645 }
646
647 #[test]
649 fn enum_rejects_string() {
650 let schema = "(\n kind: Kind,\n)\nenum Kind { A, B }";
651 let errs = validate_str(schema, "(kind: \"A\")");
652 assert_eq!(errs.len(), 1);
653 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { .. }));
654 }
655
656 #[test]
662 fn expected_option_got_bare_value() {
663 let errs = validate_str("(\n power: Option(Integer),\n)", "(power: 5)");
664 assert_eq!(errs.len(), 1);
665 assert!(matches!(&errs[0].kind, ErrorKind::ExpectedOption { .. }));
666 }
667
668 #[test]
670 fn option_some_wrong_inner_type() {
671 let errs = validate_str("(\n power: Option(Integer),\n)", "(power: Some(\"five\"))");
672 assert_eq!(errs.len(), 1);
673 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { expected, .. } if expected == "Integer"));
674 }
675
676 #[test]
682 fn expected_list_got_string() {
683 let errs = validate_str("(\n tags: [String],\n)", "(tags: \"hi\")");
684 assert_eq!(errs.len(), 1);
685 assert!(matches!(&errs[0].kind, ErrorKind::ExpectedList { .. }));
686 }
687
688 #[test]
690 fn list_element_wrong_type() {
691 let errs = validate_str("(\n tags: [String],\n)", "(tags: [\"ok\", 42])");
692 assert_eq!(errs.len(), 1);
693 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { expected, .. } if expected == "String"));
694 }
695
696 #[test]
698 fn list_element_error_has_bracket_path() {
699 let errs = validate_str("(\n tags: [String],\n)", "(tags: [\"ok\", 42])");
700 assert_eq!(errs[0].path, "tags[1]");
701 }
702
703 #[test]
709 fn expected_struct_got_integer() {
710 let schema = "(\n cost: (\n generic: Integer,\n ),\n)";
711 let errs = validate_str(schema, "(cost: 5)");
712 assert_eq!(errs.len(), 1);
713 assert!(matches!(&errs[0].kind, ErrorKind::ExpectedStruct { .. }));
714 }
715
716 #[test]
722 fn nested_struct_type_mismatch_path() {
723 let schema = "(\n cost: (\n generic: Integer,\n ),\n)";
724 let errs = validate_str(schema, "(cost: (generic: \"two\"))");
725 assert_eq!(errs.len(), 1);
726 assert_eq!(errs[0].path, "cost.generic");
727 }
728
729 #[test]
731 fn nested_struct_missing_field_path() {
732 let schema = "(\n cost: (\n generic: Integer,\n sigil: Integer,\n ),\n)";
733 let errs = validate_str(schema, "(cost: (generic: 1))");
734 assert_eq!(errs.len(), 1);
735 assert_eq!(errs[0].path, "cost.sigil");
736 }
737
738 #[test]
744 fn multiple_errors_collected() {
745 let schema = "(\n name: String,\n age: Integer,\n active: Bool,\n)";
746 let data = "(name: 42, age: \"five\", active: \"yes\")";
747 let errs = validate_str(schema, data);
748 assert_eq!(errs.len(), 3);
749 }
750
751 #[test]
753 fn mixed_error_types_collected() {
754 let schema = "(\n name: String,\n age: Integer,\n)";
755 let data = "(name: \"hi\", age: \"five\", extra: true)";
756 let errs = validate_str(schema, data);
757 assert_eq!(errs.len(), 2);
759 }
760
761 #[test]
767 fn valid_card_data() {
768 let schema = r#"(
769 name: String,
770 card_types: [CardType],
771 legendary: Bool,
772 power: Option(Integer),
773 toughness: Option(Integer),
774 keywords: [String],
775 )
776 enum CardType { Creature, Trap, Artifact }"#;
777 let data = r#"(
778 name: "Ashborn Hound",
779 card_types: [Creature],
780 legendary: false,
781 power: Some(1),
782 toughness: Some(1),
783 keywords: [],
784 )"#;
785 let errs = validate_str(schema, data);
786 assert!(errs.is_empty());
787 }
788
789 #[test]
791 fn card_data_multiple_errors() {
792 let schema = r#"(
793 name: String,
794 card_types: [CardType],
795 legendary: Bool,
796 power: Option(Integer),
797 )
798 enum CardType { Creature, Trap }"#;
799 let data = r#"(
800 name: 42,
801 card_types: [Pirates],
802 legendary: false,
803 power: Some("five"),
804 )"#;
805 let errs = validate_str(schema, data);
806 assert_eq!(errs.len(), 3);
808 }
809
810 #[test]
816 fn alias_struct_valid() {
817 let schema = "(\n cost: Cost,\n)\ntype Cost = (generic: Integer,)";
818 let data = "(cost: (generic: 5))";
819 let errs = validate_str(schema, data);
820 assert!(errs.is_empty());
821 }
822
823 #[test]
825 fn alias_struct_type_mismatch() {
826 let schema = "(\n cost: Cost,\n)\ntype Cost = (generic: Integer,)";
827 let data = "(cost: (generic: \"five\"))";
828 let errs = validate_str(schema, data);
829 assert_eq!(errs.len(), 1);
830 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { expected, .. } if expected == "Integer"));
831 }
832
833 #[test]
835 fn alias_primitive_valid() {
836 let schema = "(\n name: Name,\n)\ntype Name = String";
837 let data = "(name: \"hello\")";
838 let errs = validate_str(schema, data);
839 assert!(errs.is_empty());
840 }
841
842 #[test]
844 fn alias_primitive_mismatch() {
845 let schema = "(\n name: Name,\n)\ntype Name = String";
846 let data = "(name: 42)";
847 let errs = validate_str(schema, data);
848 assert_eq!(errs.len(), 1);
849 }
850
851 #[test]
853 fn alias_in_list_valid() {
854 let schema = "(\n costs: [Cost],\n)\ntype Cost = (generic: Integer,)";
855 let data = "(costs: [(generic: 1), (generic: 2)])";
856 let errs = validate_str(schema, data);
857 assert!(errs.is_empty());
858 }
859
860 #[test]
862 fn alias_in_list_element_error() {
863 let schema = "(\n costs: [Cost],\n)\ntype Cost = (generic: Integer,)";
864 let data = "(costs: [(generic: 1), (generic: \"two\")])";
865 let errs = validate_str(schema, data);
866 assert_eq!(errs.len(), 1);
867 assert_eq!(errs[0].path, "costs[1].generic");
868 }
869
870 #[test]
876 fn map_valid() {
877 let schema = "(\n attrs: {String: Integer},\n)";
878 let data = "(attrs: {\"str\": 5, \"dex\": 3})";
879 let errs = validate_str(schema, data);
880 assert!(errs.is_empty());
881 }
882
883 #[test]
885 fn map_empty_valid() {
886 let schema = "(\n attrs: {String: Integer},\n)";
887 let data = "(attrs: {})";
888 let errs = validate_str(schema, data);
889 assert!(errs.is_empty());
890 }
891
892 #[test]
894 fn map_expected_got_string() {
895 let schema = "(\n attrs: {String: Integer},\n)";
896 let data = "(attrs: \"not a map\")";
897 let errs = validate_str(schema, data);
898 assert_eq!(errs.len(), 1);
899 assert!(matches!(&errs[0].kind, ErrorKind::ExpectedMap { .. }));
900 }
901
902 #[test]
904 fn map_wrong_value_type() {
905 let schema = "(\n attrs: {String: Integer},\n)";
906 let data = "(attrs: {\"str\": \"five\"})";
907 let errs = validate_str(schema, data);
908 assert_eq!(errs.len(), 1);
909 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { expected, .. } if expected == "Integer"));
910 }
911
912 #[test]
914 fn map_wrong_key_type() {
915 let schema = "(\n attrs: {String: Integer},\n)";
916 let data = "(attrs: {42: 5})";
917 let errs = validate_str(schema, data);
918 assert_eq!(errs.len(), 1);
919 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { expected, .. } if expected == "String"));
920 }
921
922 #[test]
928 fn tuple_valid() {
929 let schema = "(\n pos: (Float, Float),\n)";
930 let data = "(pos: (1.0, 2.5))";
931 let errs = validate_str(schema, data);
932 assert!(errs.is_empty());
933 }
934
935 #[test]
937 fn tuple_expected_got_string() {
938 let schema = "(\n pos: (Float, Float),\n)";
939 let data = "(pos: \"not a tuple\")";
940 let errs = validate_str(schema, data);
941 assert_eq!(errs.len(), 1);
942 assert!(matches!(&errs[0].kind, ErrorKind::ExpectedTuple { .. }));
943 }
944
945 #[test]
947 fn tuple_wrong_length() {
948 let schema = "(\n pos: (Float, Float),\n)";
949 let data = "(pos: (1.0, 2.5, 3.0))";
950 let errs = validate_str(schema, data);
951 assert_eq!(errs.len(), 1);
952 assert!(matches!(&errs[0].kind, ErrorKind::TupleLengthMismatch { expected: 2, found: 3 }));
953 }
954
955 #[test]
957 fn tuple_wrong_element_type() {
958 let schema = "(\n pos: (Float, Float),\n)";
959 let data = "(pos: (1.0, \"bad\"))";
960 let errs = validate_str(schema, data);
961 assert_eq!(errs.len(), 1);
962 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { expected, .. } if expected == "Float"));
963 }
964
965 #[test]
967 fn tuple_element_error_path() {
968 let schema = "(\n pos: (Float, Float),\n)";
969 let data = "(pos: (1.0, \"bad\"))";
970 let errs = validate_str(schema, data);
971 assert_eq!(errs[0].path, "pos.1");
972 }
973
974 #[test]
980 fn enum_data_variant_valid() {
981 let schema = "(\n effect: Effect,\n)\nenum Effect { Damage(Integer), Draw }";
982 let data = "(effect: Damage(5))";
983 let errs = validate_str(schema, data);
984 assert!(errs.is_empty());
985 }
986
987 #[test]
989 fn enum_unit_variant_valid() {
990 let schema = "(\n effect: Effect,\n)\nenum Effect { Damage(Integer), Draw }";
991 let data = "(effect: Draw)";
992 let errs = validate_str(schema, data);
993 assert!(errs.is_empty());
994 }
995
996 #[test]
998 fn enum_data_variant_wrong_type() {
999 let schema = "(\n effect: Effect,\n)\nenum Effect { Damage(Integer), Draw }";
1000 let data = "(effect: Damage(\"five\"))";
1001 let errs = validate_str(schema, data);
1002 assert_eq!(errs.len(), 1);
1003 assert!(matches!(&errs[0].kind, ErrorKind::TypeMismatch { expected, .. } if expected == "Integer"));
1004 }
1005
1006 #[test]
1008 fn enum_data_variant_unknown() {
1009 let schema = "(\n effect: Effect,\n)\nenum Effect { Damage(Integer), Draw }";
1010 let data = "(effect: Explode(10))";
1011 let errs = validate_str(schema, data);
1012 assert_eq!(errs.len(), 1);
1013 assert!(matches!(&errs[0].kind, ErrorKind::InvalidEnumVariant { .. }));
1014 }
1015
1016 #[test]
1018 fn enum_missing_data() {
1019 let schema = "(\n effect: Effect,\n)\nenum Effect { Damage(Integer), Draw }";
1020 let data = "(effect: Damage)";
1021 let errs = validate_str(schema, data);
1022 assert_eq!(errs.len(), 1);
1023 assert!(matches!(&errs[0].kind, ErrorKind::InvalidVariantData { .. }));
1024 }
1025
1026 #[test]
1028 fn enum_unexpected_data() {
1029 let schema = "(\n effect: Effect,\n)\nenum Effect { Damage(Integer), Draw }";
1030 let data = "(effect: Draw(5))";
1031 let errs = validate_str(schema, data);
1032 assert_eq!(errs.len(), 1);
1033 assert!(matches!(&errs[0].kind, ErrorKind::InvalidVariantData { .. }));
1034 }
1035}