1use crate::editing::{ErrorConverter, ErrorKind, ErrorPath, FeatureValidationError};
6use crate::error::FMLError;
7use crate::intermediate_representation::{FeatureDef, PropDef, TypeRef};
8use crate::{
9 error::Result,
10 intermediate_representation::{EnumDef, ObjectDef},
11};
12use serde_json::{Map, Value};
13use std::collections::{BTreeMap, HashMap, HashSet};
14
15pub(crate) struct DefaultsValidator<'a> {
16 enum_defs: &'a BTreeMap<String, EnumDef>,
17 object_defs: &'a BTreeMap<String, ObjectDef>,
18}
19
20impl<'a> DefaultsValidator<'a> {
21 pub(crate) fn new(
22 enum_defs: &'a BTreeMap<String, EnumDef>,
23 object_defs: &'a BTreeMap<String, ObjectDef>,
24 ) -> Self {
25 Self {
26 enum_defs,
27 object_defs,
28 }
29 }
30
31 pub(crate) fn validate_object_def(&self, object_def: &ObjectDef) -> Result<(), FMLError> {
32 let mut errors = Default::default();
33 let path = ErrorPath::object(&object_def.name);
34 for prop in &object_def.props {
35 self.validate_types(
36 &path.property(&prop.name),
37 &prop.typ,
38 &prop.default,
39 &mut errors,
40 );
41 }
42 if errors.is_empty() {
43 Ok(())
44 } else {
45 let converter = ErrorConverter::new(self.enum_defs, self.object_defs);
46 Err(converter.convert_object_error(errors.pop().unwrap()))
47 }
48 }
49
50 pub(crate) fn validate_feature_def(&self, feature_def: &FeatureDef) -> Result<()> {
63 let defaults = feature_def.default_json();
64 let errors = self.get_errors(feature_def, &defaults, &defaults);
65 self.guard_errors(feature_def, &defaults, errors)?;
66
67 self.validate_feature_enum_maps(feature_def)?;
69
70 let path = ErrorPath::feature(&feature_def.name);
72 for ex in &feature_def.examples {
73 let path = path.example(&ex.metadata.name);
74 let errors = self.get_errors_with_path(&path, feature_def, &defaults, &ex.value);
75 self.guard_errors(feature_def, &defaults, errors)?;
76 }
77
78 Ok(())
79 }
80
81 pub(crate) fn guard_errors(
82 &self,
83 feature_def: &FeatureDef,
84 defaults: &Value,
85 mut errors: Vec<FeatureValidationError>,
86 ) -> Result<()> {
87 if !errors.is_empty() {
88 let converter = ErrorConverter::new(self.enum_defs, self.object_defs);
89 Err(converter.convert_feature_error(feature_def, defaults, errors.pop().unwrap()))
90 } else {
91 Ok(())
92 }
93 }
94
95 pub(crate) fn get_errors(
98 &self,
99 feature_def: &FeatureDef,
100 merged_value: &Value,
101 unmerged_value: &Value,
102 ) -> Vec<FeatureValidationError> {
103 let path = ErrorPath::feature(&feature_def.name);
104 self.get_errors_with_path(&path, feature_def, merged_value, unmerged_value)
105 }
106
107 pub(crate) fn get_errors_with_path(
108 &self,
109 path: &ErrorPath,
110 feature_def: &FeatureDef,
111 merged_value: &Value,
112 unmerged_value: &Value,
113 ) -> Vec<FeatureValidationError> {
114 let mut errors = Default::default();
115 let unmerged_map = unmerged_value
116 .as_object()
117 .expect("Assumption: an object is the only type that can get here");
118 self.validate_props_types(path, &feature_def.props, unmerged_map, &mut errors);
119 if !errors.is_empty() {
120 return errors;
121 }
122
123 let string_aliases = feature_def.get_string_aliases();
124 for prop in &feature_def.props {
125 if let Some(value) = unmerged_map.get(&prop.name) {
126 self.validate_string_aliases(
127 &path.property(&prop.name),
128 &prop.typ,
129 value,
130 &string_aliases,
131 merged_value,
132 &prop.string_alias,
133 &mut errors,
134 );
135 }
136 }
137 errors
138 }
139
140 fn get_enum(&self, nm: &str) -> Option<&EnumDef> {
141 self.enum_defs.get(nm)
142 }
143
144 fn get_object(&self, nm: &str) -> Option<&ObjectDef> {
145 self.object_defs.get(nm)
146 }
147
148 fn validate_feature_enum_maps(&self, feature_def: &FeatureDef) -> Result<()> {
149 let path = ErrorPath::feature(&feature_def.name);
150 for prop in &feature_def.props {
151 let path = path.property(&prop.name);
152 self.validate_enum_maps(&path, &prop.typ, &prop.default)?;
153 }
154 Ok(())
155 }
156
157 fn validate_enum_maps(
163 &self,
164 path: &ErrorPath,
165 type_ref: &TypeRef,
166 default: &Value,
167 ) -> Result<()> {
168 match (type_ref, default) {
169 (TypeRef::Option(inner), v) => {
170 self.validate_enum_maps(path, inner, v)?
171 }
172
173 (TypeRef::EnumMap(enum_type, map_type), Value::Object(map))
174 if matches!(**enum_type, TypeRef::Enum(_)) =>
175 {
176 let enum_name = enum_type.name().unwrap();
177 let enum_def = self
178 .get_enum(enum_name)
179 .unwrap_or_else(|| {
181 unreachable!("Enum {enum_name} is not defined in the manifest")
182 });
183
184 let mut unseen = HashSet::new();
185 if !matches!(**map_type, TypeRef::Option(_)) {
186 for variant in &enum_def.variants {
187 if !map.contains_key(&variant.name) {
188 unseen.insert(variant.name());
189 }
190 }
191
192 if !unseen.is_empty() {
193 let path = path.open_brace();
194 return Err(FMLError::ValidationError(
195 path.path,
196 format!("Enum map {enum_name} is missing values for {unseen:?}"),
197 ));
198 }
199 }
200
201 for (key, value) in map {
202 self.validate_enum_maps(&path.enum_map_key(enum_name, key), map_type, value)?
203 }
204 }
205
206 (TypeRef::EnumMap(_, map_type), Value::Object(map)) | (TypeRef::StringMap(map_type), Value::Object(map)) => {
208 for (key, value) in map {
209 self.validate_enum_maps(&path.map_key(key), map_type, value)?
210 }
211 }
212
213 (TypeRef::List(list_type), Value::Array(arr)) => {
214 for (index, value) in arr.iter().enumerate() {
215 self.validate_enum_maps(&path.array_index(index), list_type, value)?
216 }
217 }
218
219 (TypeRef::Object(obj_name), Value::Object(map)) => {
220 let obj_def = self
221 .get_object(obj_name)
222 .unwrap_or_else(|| {
224 unreachable!("Object {obj_name} is not defined in the manifest")
225 });
226 let path = path.object_value(obj_name);
227 for prop in &obj_def.props {
228 if let Some(value) = map.get(&prop.name) {
229 self.validate_enum_maps(&path.property(&prop.name), &prop.typ, value)?
230 }
231 }
232 }
233
234 _ => (),
235 };
236 Ok(())
237 }
238
239 fn validate_types(
240 &self,
241 path: &ErrorPath,
242 type_ref: &TypeRef,
243 default: &Value,
244 errors: &mut Vec<FeatureValidationError>,
245 ) {
246 match (type_ref, default) {
247 (TypeRef::Boolean, Value::Bool(_))
248 | (TypeRef::BundleImage, Value::String(_))
249 | (TypeRef::BundleText, Value::String(_))
250 | (TypeRef::String, Value::String(_))
251 | (TypeRef::StringAlias(_), Value::String(_))
252 | (TypeRef::Int, Value::Number(_))
253 | (TypeRef::Option(_), Value::Null) => (),
254 (TypeRef::Option(inner), v) => {
255 self.validate_types(path, inner, v, errors)
256 }
257 (TypeRef::Enum(enum_name), Value::String(s)) => {
258 let enum_def = self
259 .get_enum(enum_name)
260 .unwrap_or_else(|| {
262 unreachable!("Enum {enum_name} is not defined in the manifest")
263 });
264 let mut valid = HashSet::new();
265 for variant in enum_def.variants() {
266 let name = variant.name();
267 if *s == name {
268 return;
269 }
270 valid.insert(name);
271 }
272 let path = path.final_error_quoted(s);
273 errors.push(FeatureValidationError {
274 path,
275 kind: ErrorKind::invalid_value(type_ref),
276 });
277 }
278 (TypeRef::EnumMap(enum_type, map_type), Value::Object(map))
279 if matches!(**enum_type, TypeRef::Enum(_)) =>
280 {
281 let enum_name = enum_type.name().unwrap();
282 let enum_def = self
283 .get_enum(enum_name)
284 .unwrap_or_else(|| {
286 unreachable!("Enum {enum_name} is not defined in the manifest")
287 });
288
289 let mut valid = HashSet::new();
291 for variant in &enum_def.variants {
292 let nm = &variant.name;
293 valid.insert(nm.clone());
294
295 let map_value = map.get(nm);
296 match (map_type.as_ref(), map_value) {
297 (TypeRef::Option(_), None) => (),
298 (_, Some(inner)) => {
299 self.validate_types(&path.enum_map_key(enum_name, nm), map_type, inner, errors);
300 }
301 _ => ()
302 }
303 }
304
305 for (map_key, map_value) in map {
306 if !valid.contains(map_key) {
307 let path = path.map_key(map_key);
308 errors.push(FeatureValidationError {
309 path,
310 kind: ErrorKind::invalid_key(enum_type, map),
311 });
312 }
313
314 self.validate_types(&path.enum_map_key(&enum_def.name, map_key), map_type, map_value, errors);
315 }
316 }
317 (TypeRef::EnumMap(_, map_type), Value::Object(map)) | (TypeRef::StringMap(map_type), Value::Object(map)) => {
319 for (key, value) in map {
320 self.validate_types(&path.map_key(key), map_type, value, errors);
321 }
322 }
323 (TypeRef::List(list_type), Value::Array(arr)) => {
324 for (index, value) in arr.iter().enumerate() {
325 self.validate_types(&path.array_index(index), list_type, value, errors);
326 }
327 }
328 (TypeRef::Object(obj_name), Value::Object(map)) => {
329 let obj_def = self
330 .get_object(obj_name)
331 .unwrap_or_else(|| {
333 unreachable!("Object {obj_name} is not defined in the manifest")
334 });
335 self.validate_props_types(&path.object_value(obj_name), &obj_def.props, map, errors);
336 }
337 _ => {
338 let path = path.final_error_value(default);
339 errors.push(FeatureValidationError {
340 path,
341 kind: ErrorKind::type_mismatch(type_ref),
342 });
343 }
344 };
345 }
346
347 fn validate_props_types(
348 &self,
349 path: &ErrorPath,
350 props: &Vec<PropDef>,
351 map: &Map<String, Value>,
352 errors: &mut Vec<FeatureValidationError>,
353 ) {
354 let mut valid = HashSet::new();
355
356 for prop in props {
357 let prop_name = &prop.name;
361 if let Some(map_val) = map.get(prop_name) {
362 self.validate_types(&path.property(prop_name), &prop.typ, map_val, errors);
363 }
364
365 valid.insert(prop_name.clone());
366 }
367 for map_key in map.keys() {
368 if !valid.contains(map_key) {
369 let path = path.final_error_quoted(map_key);
370 errors.push(FeatureValidationError {
371 path,
372 kind: ErrorKind::invalid_prop(props, map),
373 });
374 }
375 }
376 }
377
378 #[allow(clippy::too_many_arguments)]
391 fn validate_string_aliases(
392 &self,
393 path: &ErrorPath,
394 typ: &TypeRef,
395 value: &Value,
396 definitions: &HashMap<&str, &PropDef>,
397 feature_value: &Value,
398 skip: &Option<TypeRef>,
399 errors: &mut Vec<FeatureValidationError>,
400 ) {
401 let should_validate = |v: &TypeRef| -> bool { skip.as_ref() != Some(v) };
405 match (typ, value) {
406 (TypeRef::StringAlias(_), Value::String(s)) => {
407 if !is_string_alias_value_valid(typ, s, definitions, feature_value) {
408 let path = path.final_error_quoted(s);
409 errors.push(FeatureValidationError {
410 path,
411 kind: ErrorKind::invalid_value(typ),
412 });
413 }
414 }
415 (TypeRef::Option(_), &Value::Null) => (),
416 (TypeRef::Option(inner), _) => self.validate_string_aliases(
417 path,
418 inner,
419 value,
420 definitions,
421 feature_value,
422 skip,
423 errors,
424 ),
425 (TypeRef::List(inner), Value::Array(array)) => {
426 if should_validate(inner) {
427 for (index, value) in array.iter().enumerate() {
428 self.validate_string_aliases(
429 &path.array_index(index),
430 inner,
431 value,
432 definitions,
433 feature_value,
434 skip,
435 errors,
436 );
437 }
438 }
439 }
440 (TypeRef::EnumMap(key_type, value_type), Value::Object(map)) => {
441 if should_validate(key_type) && matches!(**key_type, TypeRef::StringAlias(_)) {
442 for key in map.keys() {
443 if !is_string_alias_value_valid(key_type, key, definitions, feature_value) {
444 let path = path.final_error_quoted(key);
445 errors.push(FeatureValidationError {
446 path,
447 kind: ErrorKind::invalid_key(key_type, map),
448 });
449 }
450 }
451 }
452
453 if should_validate(value_type) {
454 for (key, value) in map {
455 self.validate_string_aliases(
456 &path.map_key(key),
457 value_type,
458 value,
459 definitions,
460 feature_value,
461 skip,
462 errors,
463 );
464 }
465 }
466 }
467 (TypeRef::StringMap(vt), Value::Object(map)) => {
468 if should_validate(vt) {
469 for (key, value) in map {
470 self.validate_string_aliases(
471 &path.map_key(key),
472 vt,
473 value,
474 definitions,
475 feature_value,
476 skip,
477 errors,
478 );
479 }
480 }
481 }
482 (TypeRef::Object(obj_nm), Value::Object(map)) => {
483 let path = path.object_value(obj_nm);
484 let obj_def = self.get_object(obj_nm).unwrap();
485
486 for prop in &obj_def.props {
487 let prop_nm = &prop.name;
488 if let Some(value) = map.get(prop_nm) {
489 self.validate_string_aliases(
492 &path.property(prop_nm),
493 &prop.typ,
494 value,
495 definitions,
496 feature_value,
497 &None,
498 errors,
499 );
500 } else {
501 let mut suberrors = Default::default();
504 self.validate_string_aliases(
505 &ErrorPath::object(obj_nm),
506 &prop.typ,
507 &prop.default,
508 definitions,
509 feature_value,
510 &None,
511 &mut suberrors,
512 );
513
514 if !suberrors.is_empty() {
517 let path = path.open_brace();
518 errors.push(FeatureValidationError {
519 path,
520 kind: ErrorKind::invalid_nested_value(prop_nm, &prop.typ),
521 });
522 }
523 }
524 }
525 }
526 _ => {}
527 }
528 }
529}
530
531fn is_string_alias_value_valid(
532 alias_type: &TypeRef,
533 value: &str,
534 definitions: &HashMap<&str, &PropDef>,
535 merged_value: &Value,
536) -> bool {
537 let alias_name = alias_type
538 .name()
539 .expect("Assumption: this is a StringAlias type, and it has a name");
540 let prop = definitions
542 .get(alias_name)
543 .expect("Assumption: prop is defined by this feature");
544 let prop_value = merged_value
545 .get(&prop.name)
546 .expect("Assumption: value is defined in this feature");
547 validate_string_alias_value(value, alias_type, &prop.typ, prop_value)
548}
549
550fn validate_string_alias_value(
567 value: &str,
568 alias_type: &TypeRef,
569 def_type: &TypeRef,
570 def_value: &Value,
571) -> bool {
572 match (def_type, def_value) {
573 (TypeRef::StringAlias(_), Value::String(s)) if alias_type == def_type => value == s,
574
575 (TypeRef::Option(dt), dv) if dv != &Value::Null => {
576 validate_string_alias_value(value, alias_type, dt, dv)
577 }
578 (TypeRef::EnumMap(kt, _), Value::Object(map)) if alias_type == &**kt => {
579 map.contains_key(value)
580 }
581 (TypeRef::EnumMap(_, vt), Value::Object(map))
582 | (TypeRef::StringMap(vt), Value::Object(map)) => {
583 let mut found = false;
584 for item in map.values() {
585 if validate_string_alias_value(value, alias_type, vt, item) {
586 found = true;
587 break;
588 }
589 }
590 found
591 }
592 (TypeRef::List(k), Value::Array(array)) => {
593 let mut found = false;
594 for item in array {
595 if validate_string_alias_value(value, alias_type, k, item) {
596 found = true;
597 break;
598 }
599 }
600 found
601 }
602
603 _ => false,
604 }
605}
606
607#[cfg(test)]
608mod test_types {
609
610 use serde_json::json;
611
612 use crate::{error::FMLError, intermediate_representation::PropDef};
613
614 use super::*;
615
616 impl DefaultsValidator<'_> {
617 fn validate_prop_defaults(&self, prop: &PropDef) -> Result<()> {
618 let mut errors = Default::default();
619 let path = ErrorPath::feature("test");
620 self.validate_types(&path, &prop.typ, &prop.default, &mut errors);
621 if let Some(err) = errors.pop() {
622 return Err(FMLError::ValidationError(
623 err.path.path,
624 "Error".to_string(),
625 ));
626 }
627 self.validate_enum_maps(&path, &prop.typ, &prop.default)
628 }
629 }
630
631 fn enums() -> BTreeMap<String, EnumDef> {
632 let enum_ = EnumDef::new("ButtonColor", &["blue", "green"]);
633
634 EnumDef::into_map(&[enum_])
635 }
636
637 fn objects() -> BTreeMap<String, ObjectDef> {
638 let obj1 = ObjectDef::new(
639 "SampleObj",
640 &[
641 PropDef::new("int", &TypeRef::Int, &json!(1)),
642 PropDef::new("string", &TypeRef::String, &json!("a string")),
643 PropDef::new("enum", &TypeRef::Enum("ButtonColor".into()), &json!("blue")),
644 PropDef::new(
645 "list",
646 &TypeRef::List(Box::new(TypeRef::Boolean)),
647 &json!([true, false]),
648 ),
649 PropDef::new(
650 "optional",
651 &TypeRef::Option(Box::new(TypeRef::Int)),
652 &json!(null),
653 ),
654 PropDef::new(
655 "nestedObj",
656 &TypeRef::Object("NestedObject".into()),
657 &json!({
658 "enumMap": {
659 "blue": 1,
660 },
661 }),
662 ),
663 ],
664 );
665
666 let obj2 = ObjectDef::new(
667 "NestedObject",
668 &[PropDef::new(
669 "enumMap",
670 &TypeRef::EnumMap(
671 Box::new(TypeRef::Enum("ButtonColor".into())),
672 Box::new(TypeRef::Int),
673 ),
674 &json!({
675 "blue": 4,
676 "green": 2,
677 }),
678 )],
679 );
680 ObjectDef::into_map(&[obj1, obj2])
681 }
682
683 #[test]
684 fn test_validate_prop_defaults_string() -> Result<()> {
685 let mut prop = PropDef::new("key", &TypeRef::String, &json!("default!"));
686 let enums1 = Default::default();
687 let objs = Default::default();
688 let fm = DefaultsValidator::new(&enums1, &objs);
689 fm.validate_prop_defaults(&prop)?;
690
691 prop.default = json!(100);
692 fm.validate_prop_defaults(&prop)
693 .expect_err("Should error out, default is number when it should be string");
694 Ok(())
695 }
696
697 #[test]
698 fn test_validate_prop_defaults_int() -> Result<()> {
699 let mut prop = PropDef::new("key", &TypeRef::Int, &json!(100));
700 let enums1 = Default::default();
701 let objs = Default::default();
702 let fm = DefaultsValidator::new(&enums1, &objs);
703 fm.validate_prop_defaults(&prop)?;
704 prop.default = json!("100");
705
706 fm.validate_prop_defaults(&prop)
707 .expect_err("Should error out, default is string when it should be number");
708 Ok(())
709 }
710
711 #[test]
712 fn test_validate_prop_defaults_bool() -> Result<()> {
713 let mut prop = PropDef::new("key", &TypeRef::Boolean, &json!(true));
714 let enums1 = Default::default();
715 let objs = Default::default();
716 let fm = DefaultsValidator::new(&enums1, &objs);
717 fm.validate_prop_defaults(&prop)?;
718 prop.default = json!("100");
719
720 fm.validate_prop_defaults(&prop)
721 .expect_err("Should error out, default is string when it should be a boolean");
722 Ok(())
723 }
724
725 #[test]
726 fn test_validate_prop_defaults_bundle_image() -> Result<()> {
727 let mut prop = PropDef::new("key", &TypeRef::BundleImage, &json!("IconBlue"));
728 let enums1 = Default::default();
729 let objs = Default::default();
730 let fm = DefaultsValidator::new(&enums1, &objs);
731 fm.validate_prop_defaults(&prop)?;
732 prop.default = json!(100);
733
734 fm.validate_prop_defaults(&prop).expect_err(
735 "Should error out, default is number when it should be a string (bundleImage string)",
736 );
737 Ok(())
738 }
739
740 #[test]
741 fn test_validate_prop_defaults_bundle_text() -> Result<()> {
742 let mut prop = PropDef::new("key", &TypeRef::BundleText, &json!("BundledText"));
743 let enums1 = Default::default();
744 let objs = Default::default();
745 let fm = DefaultsValidator::new(&enums1, &objs);
746 fm.validate_prop_defaults(&prop)?;
747 prop.default = json!(100);
748
749 fm.validate_prop_defaults(&prop).expect_err(
750 "Should error out, default is number when it should be a string (bundleText string)",
751 );
752 Ok(())
753 }
754
755 #[test]
756 fn test_validate_prop_defaults_option_null() -> Result<()> {
757 let mut prop = PropDef::new(
758 "key",
759 &TypeRef::Option(Box::new(TypeRef::Boolean)),
760 &json!(null),
761 );
762 let enums1 = Default::default();
763 let objs = Default::default();
764 let fm = DefaultsValidator::new(&enums1, &objs);
765 fm.validate_prop_defaults(&prop)?;
766 prop.default = json!(100);
767
768 fm.validate_prop_defaults(&prop).expect_err(
769 "Should error out, default is number when it should be a boolean (Optional boolean)",
770 );
771 Ok(())
772 }
773
774 #[test]
775 fn test_validate_prop_defaults_option_non_null() -> Result<()> {
776 let mut prop = PropDef::new(
777 "key",
778 &TypeRef::Option(Box::new(TypeRef::Boolean)),
779 &json!(true),
780 );
781 let enums1 = Default::default();
782 let objs = Default::default();
783 let fm = DefaultsValidator::new(&enums1, &objs);
784 fm.validate_prop_defaults(&prop)?;
785
786 prop.default = json!(100);
787 fm.validate_prop_defaults(&prop).expect_err(
788 "Should error out, default is number when it should be a boolean (Optional boolean)",
789 );
790 Ok(())
791 }
792
793 #[test]
794 fn test_validate_prop_defaults_enum() -> Result<()> {
795 let mut prop = PropDef::new("key", &TypeRef::Enum("ButtonColor".into()), &json!("blue"));
796
797 let enums1 = enums();
798 let objs = Default::default();
799 let fm = DefaultsValidator::new(&enums1, &objs);
800 fm.validate_prop_defaults(&prop)?;
801 prop.default = json!("green");
802
803 fm.validate_prop_defaults(&prop)?;
804 prop.default = json!("not a valid color");
805
806 fm.validate_prop_defaults(&prop)
807 .expect_err("Should error out since default is not a valid enum variant");
808 Ok(())
809 }
810
811 #[test]
812 fn test_validate_prop_defaults_enum_map() -> Result<()> {
813 let mut prop = PropDef::new(
814 "key",
815 &TypeRef::EnumMap(
816 Box::new(TypeRef::Enum("ButtonColor".into())),
817 Box::new(TypeRef::Int),
818 ),
819 &json!({
820 "blue": 1,
821 "green": 22,
822 }),
823 );
824 let enums1 = enums();
825 let objs = Default::default();
826 let fm = DefaultsValidator::new(&enums1, &objs);
827 fm.validate_prop_defaults(&prop)?;
828 prop.default = json!({
829 "blue": 1,
830 });
831 fm.validate_prop_defaults(&prop)
832 .expect_err("Should error out because the enum map is missing the green key");
833
834 prop.default = json!({
835 "blue": 1,
836 "green": 22,
837 "red": 3,
838 });
839 fm.validate_prop_defaults(&prop).expect_err("Should error out because the default includes an extra key that is not a variant of the enum (red)");
840 Ok(())
841 }
842
843 #[test]
844 fn test_validate_prop_defaults_string_map() -> Result<()> {
845 let mut prop = PropDef::new(
846 "key",
847 &TypeRef::StringMap(Box::new(TypeRef::Int)),
848 &json!({
849 "blue": 1,
850 "green": 22,
851 }),
852 );
853 let enums1 = Default::default();
854 let objs = Default::default();
855 let fm = DefaultsValidator::new(&enums1, &objs);
856 fm.validate_prop_defaults(&prop)?;
857 prop.default = json!({
858 "blue": 1,
859 });
860 fm.validate_prop_defaults(&prop)?;
861
862 prop.default = json!({
863 "blue": 1,
864 "green": 22,
865 "red": 3,
866 "white": "AHA not a number"
867 });
868 fm.validate_prop_defaults(&prop).expect_err("Should error out because the string map includes a value that is not an int as defined by the TypeRef");
869 Ok(())
870 }
871
872 #[test]
873 fn test_validate_prop_defaults_list() -> Result<()> {
874 let mut prop = PropDef::new(
875 "key",
876 &TypeRef::List(Box::new(TypeRef::Int)),
877 &json!([1, 3, 100]),
878 );
879 let enums1 = Default::default();
880 let objs = Default::default();
881 let fm = DefaultsValidator::new(&enums1, &objs);
882 fm.validate_prop_defaults(&prop)?;
883
884 prop.default = json!([1, 2, "oops"]);
885 fm.validate_prop_defaults(&prop)
886 .expect_err("Should error out because one of the values in the array is not an int");
887 Ok(())
888 }
889
890 #[test]
891 fn test_validate_prop_defaults_object() -> Result<()> {
892 let mut prop = PropDef::new(
893 "key",
894 &TypeRef::Object("SampleObj".into()),
895 &json!({
896 "int": 1,
897 "string": "bobo",
898 "enum": "green",
899 "list": [true, false, true],
900 "nestedObj": {
901 "enumMap": {
902 "blue": 1,
903 "green": 2,
904 }
905 },
906 "optional": 2,
907 }),
908 );
909
910 let enums1 = enums();
911 let objs = objects();
912 let fm = DefaultsValidator::new(&enums1, &objs);
913 fm.validate_prop_defaults(&prop)?;
914
915 prop.default = json!({
916 "int": 1,
917 "string": "bobo",
918 "enum": "green",
919 "list": [true, false, true],
920 "nestedObj": {
921 "enumMap": {
922 "blue": 1,
923 "green": "Wrong type!"
924 }
925 }
926 });
927 fm.validate_prop_defaults(&prop).expect_err(
928 "Should error out because the nested object has an enumMap with the wrong type",
929 );
930
931 prop.default = json!({
932 "int": 1,
933 "string": "bobo",
934 "enum": "green",
935 "list": [true, false, true],
936 "nestedObj": {
937 "enumMap": {
938 "blue": 1,
939 "green": 2,
940 }
941 },
942 "optional": 3,
943 "extra-property": 2
944 });
945 fm.validate_prop_defaults(&prop)
946 .expect_err("Should error out because the object has an extra property");
947
948 prop.default = json!({
950 "int": 1,
951 "string": "bobo",
952 "enum": "green",
953 "nestedObj": {
954 "enumMap": {
955 "blue": 1,
956 "green": 2,
957 }
958 },
959 "optional": 2,
960 });
961 fm.validate_prop_defaults(&prop)?;
962
963 prop.default = json!({
964 "int": 1,
965 "string": "bobo",
966 "enum": "green",
967 "list": [true, false, true],
968 "nestedObj": {
969 "enumMap": {
970 "blue": 1,
971 "green": 2,
972 }
973 },
974 });
975
976 fm.validate_prop_defaults(&prop)?;
978 Ok(())
979 }
980
981 #[test]
982 fn test_validate_prop_defaults_enum_map_optional() -> Result<()> {
983 let prop = PropDef::new(
984 "key",
985 &TypeRef::EnumMap(
986 Box::new(TypeRef::Enum("ButtonColor".into())),
987 Box::new(TypeRef::Option(Box::new(TypeRef::Int))),
988 ),
989 &json!({
990 "blue": 1,
991 }),
992 );
993 let enums1 = enums();
994 let objs = Default::default();
995 let fm = DefaultsValidator::new(&enums1, &objs);
996 fm.validate_prop_defaults(&prop)?;
998 Ok(())
999 }
1000}
1001
1002#[cfg(test)]
1003mod string_alias {
1004
1005 use super::*;
1006 use serde_json::json;
1007
1008 #[test]
1010 fn test_validate_value() -> Result<()> {
1011 let sa = TypeRef::StringAlias("Name".to_string());
1012
1013 let def = sa.clone();
1015 let value = json!("yes");
1016 assert!(validate_string_alias_value("yes", &sa, &def, &value));
1017 assert!(!validate_string_alias_value("no", &sa, &def, &value));
1018
1019 let def = TypeRef::Option(Box::new(sa.clone()));
1021 let value = json!("yes");
1022 assert!(validate_string_alias_value("yes", &sa, &def, &value));
1023 assert!(!validate_string_alias_value("no", &sa, &def, &value));
1024
1025 let value = json!(null);
1026 assert!(!validate_string_alias_value("no", &sa, &def, &value));
1027
1028 let def = TypeRef::EnumMap(Box::new(sa.clone()), Box::new(TypeRef::Boolean));
1030 let value = json!({
1031 "yes": true,
1032 "YES": false,
1033 });
1034 assert!(validate_string_alias_value("yes", &sa, &def, &value));
1035 assert!(validate_string_alias_value("YES", &sa, &def, &value));
1036 assert!(!validate_string_alias_value("no", &sa, &def, &value));
1037
1038 let def = TypeRef::EnumMap(Box::new(TypeRef::String), Box::new(sa.clone()));
1040 let value = json!({
1041 "ok": "yes",
1042 "OK": "YES",
1043 });
1044 assert!(validate_string_alias_value("yes", &sa, &def, &value));
1045 assert!(validate_string_alias_value("YES", &sa, &def, &value));
1046 assert!(!validate_string_alias_value("no", &sa, &def, &value));
1047
1048 let def = TypeRef::List(Box::new(sa.clone()));
1050 let value = json!(["yes", "YES"]);
1051 assert!(validate_string_alias_value("yes", &sa, &def, &value));
1052 assert!(validate_string_alias_value("YES", &sa, &def, &value));
1053 assert!(!validate_string_alias_value("no", &sa, &def, &value));
1054
1055 let def = TypeRef::List(Box::new(TypeRef::StringMap(Box::new(sa.clone()))));
1057 let value = json!([{"y": "yes"}, {"Y": "YES"}]);
1058 assert!(validate_string_alias_value("yes", &sa, &def, &value));
1059 assert!(validate_string_alias_value("YES", &sa, &def, &value));
1060 assert!(!validate_string_alias_value("no", &sa, &def, &value));
1061
1062 let def = TypeRef::StringMap(Box::new(TypeRef::List(Box::new(sa.clone()))));
1064 let value = json!({"y": ["yes"], "Y": ["YES"]});
1065 assert!(validate_string_alias_value("yes", &sa, &def, &value));
1066 assert!(validate_string_alias_value("YES", &sa, &def, &value));
1067 assert!(!validate_string_alias_value("no", &sa, &def, &value));
1068
1069 Ok(())
1070 }
1071
1072 fn objects(nm: &str, props: &[PropDef]) -> BTreeMap<String, ObjectDef> {
1073 let obj1 = ObjectDef::new(nm, props);
1074 ObjectDef::into_map(&[obj1])
1075 }
1076
1077 fn feature(props: &[PropDef]) -> FeatureDef {
1078 FeatureDef {
1079 name: "TestFeature".to_string(),
1080 props: props.into(),
1081 ..Default::default()
1082 }
1083 }
1084
1085 #[test]
1086 fn test_string_alias() -> Result<()> {
1087 let mate = TypeRef::StringAlias("TeamMate".to_string());
1088 let the_team = {
1089 let team = TypeRef::List(Box::new(mate.clone()));
1090 let value = json!(["Alice", "Bonnie", "Charlie", "Deborah", "Eve"]);
1091
1092 PropDef::with_string_alias("team", &team, &value, &mate)
1093 };
1094 test_with_simple_string_alias(&mate, &the_team)?;
1095 test_with_objects(&mate, &the_team)?;
1096
1097 let the_team = {
1098 let team = TypeRef::EnumMap(Box::new(mate.clone()), Box::new(TypeRef::Boolean));
1099 let value = json!({"Alice": true, "Bonnie": true, "Charlie": true, "Deborah": true, "Eve": true});
1100
1101 PropDef::with_string_alias("team", &team, &value, &mate)
1102 };
1103 test_with_simple_string_alias(&mate, &the_team)?;
1104 test_with_objects(&mate, &the_team)?;
1105
1106 Ok(())
1107 }
1108
1109 fn test_with_simple_string_alias(mate: &TypeRef, the_team: &PropDef) -> Result<()> {
1110 let objs = Default::default();
1111 let enums = Default::default();
1112 let validator = DefaultsValidator::new(&enums, &objs);
1113
1114 let nm = "captain";
1118 let t = mate.clone();
1119 let f = {
1120 let v = json!("Eve");
1121 feature(&[the_team.clone(), PropDef::new(nm, &t, &v)])
1122 };
1123
1124 validator.validate_feature_def(&f)?;
1125
1126 let t = mate.clone();
1127 let f = {
1128 let v = json!("Nope");
1129 feature(&[the_team.clone(), PropDef::new(nm, &t, &v)])
1130 };
1131 assert!(validator.validate_feature_def(&f).is_err());
1132
1133 let nm = "goalkeeper";
1135 let t = TypeRef::Option(Box::new(mate.clone()));
1136 let f = {
1137 let v = json!(null);
1138 feature(&[the_team.clone(), PropDef::new(nm, &t, &v)])
1139 };
1140 validator.validate_feature_def(&f)?;
1141
1142 let f = {
1143 let v = json!("Charlie");
1144 feature(&[the_team.clone(), PropDef::new(nm, &t, &v)])
1145 };
1146 validator.validate_feature_def(&f)?;
1147
1148 let f = {
1149 let v = json!("Nope");
1150 feature(&[the_team.clone(), PropDef::new(nm, &t, &v)])
1151 };
1152 assert!(validator.validate_feature_def(&f).is_err());
1153
1154 let nm = "defenders";
1156 let t = TypeRef::List(Box::new(mate.clone()));
1157
1158 let f = {
1159 let v = json!([]);
1160 feature(&[the_team.clone(), PropDef::new(nm, &t, &v)])
1161 };
1162 validator.validate_feature_def(&f)?;
1163
1164 let f = {
1165 let v = json!(["Alice", "Charlie"]);
1166 feature(&[the_team.clone(), PropDef::new(nm, &t, &v)])
1167 };
1168 validator.validate_feature_def(&f)?;
1169
1170 let f = {
1171 let v = json!(["Alice", "Nope"]);
1172 feature(&[the_team.clone(), PropDef::new(nm, &t, &v)])
1173 };
1174 assert!(validator.validate_feature_def(&f).is_err());
1175
1176 let nm = "injury-status";
1178 let t = TypeRef::EnumMap(Box::new(mate.clone()), Box::new(TypeRef::Boolean));
1179 let f = {
1180 let v = json!({"Bonnie": false, "Deborah": true});
1181 feature(&[the_team.clone(), PropDef::new(nm, &t, &v)])
1182 };
1183 validator.validate_feature_def(&f)?;
1184
1185 let f = {
1186 let v = json!({"Bonnie": false, "Nope": true});
1187 feature(&[the_team.clone(), PropDef::new(nm, &t, &v)])
1188 };
1189 assert!(validator.validate_feature_def(&f).is_err());
1190
1191 let nm = "positions";
1193 let position = TypeRef::StringAlias("PositionName".to_string());
1194 let t = TypeRef::EnumMap(
1195 Box::new(position.clone()),
1196 Box::new(TypeRef::List(Box::new(mate.clone()))),
1197 );
1198 let f = {
1199 let v = json!({"DEFENDER": ["Bonnie", "Charlie"], "MIDFIELD": ["Alice", "Deborah"], "FORWARD": ["Eve"]});
1200 feature(&[
1201 the_team.clone(),
1202 PropDef::with_string_alias(nm, &t, &v, &position),
1203 ])
1204 };
1205 validator.validate_feature_def(&f)?;
1206
1207 let f = {
1208 let v = json!({"DEFENDER": ["Bonnie", "Charlie"], "MIDFIELD": ["Alice", "Deborah"], "STRIKER": ["Eve"]});
1209 feature(&[
1210 the_team.clone(),
1211 PropDef::with_string_alias(nm, &t, &v, &position),
1212 ])
1213 };
1214 validator.validate_feature_def(&f)?;
1215
1216 let f = {
1217 let v = json!({"DEFENDER": ["Bonnie", "Charlie"], "MIDFIELD": ["Nope", "Deborah"], "STRIKER": ["Eve"]});
1218 feature(&[
1219 the_team.clone(),
1220 PropDef::with_string_alias(nm, &t, &v, &position),
1221 ])
1222 };
1223 assert!(validator.validate_feature_def(&f).is_err());
1224 Ok(())
1225 }
1226
1227 fn test_with_objects(mate: &TypeRef, the_team: &PropDef) -> Result<()> {
1228 let position = TypeRef::StringAlias("PositionName".to_string());
1229 let positions = {
1230 let nm = "positions";
1231 let t = TypeRef::EnumMap(
1232 Box::new(position.clone()),
1233 Box::new(TypeRef::List(Box::new(mate.clone()))),
1234 );
1235 let v = json!({"DEFENDER": ["Bonnie", "Charlie"], "MIDFIELD": ["Alice", "Deborah"], "FORWARD": ["Eve"]});
1236 PropDef::with_string_alias(nm, &t, &v, &position)
1237 };
1238
1239 let objects = objects(
1240 "Player",
1241 &[
1242 PropDef::new("name", mate, &json!("Untested")),
1243 PropDef::new("position", &position, &json!("Untested")),
1244 ],
1245 );
1246 let enums = Default::default();
1247 let validator = DefaultsValidator::new(&enums, &objects);
1248
1249 let nm = "newest-player";
1251 let t = TypeRef::Object("Player".to_string());
1252 let f = {
1253 let v = json!({"name": "Eve", "position": "FORWARD"});
1254 feature(&[
1255 the_team.clone(),
1256 positions.clone(),
1257 PropDef::new(nm, &t, &v),
1258 ])
1259 };
1260 validator.validate_feature_def(&f)?;
1261
1262 let f = {
1263 let v = json!({"name": "Nope", "position": "FORWARD"});
1264 feature(&[
1265 the_team.clone(),
1266 positions.clone(),
1267 PropDef::new(nm, &t, &v),
1268 ])
1269 };
1270 assert!(validator.validate_feature_def(&f).is_err());
1271
1272 let positions = {
1275 let t = TypeRef::List(Box::new(position.clone()));
1276 let v = json!(["FORWARD", "DEFENDER"]);
1277 PropDef::with_string_alias("positions", &t, &v, &position)
1278 };
1279 let nm = "players";
1280 let t = TypeRef::EnumMap(
1281 Box::new(mate.clone()),
1282 Box::new(TypeRef::Object("Player".to_string())),
1283 );
1284 let f = {
1285 let v = json!({ "Eve": {"name": "Eve", "position": "FORWARD"}});
1286 feature(&[
1287 positions.clone(),
1288 PropDef::with_string_alias(nm, &t, &v, mate),
1289 ])
1290 };
1291 validator.validate_feature_def(&f)?;
1292
1293 let f = {
1294 let v = json!({ "Nope": {"name": "Eve", "position": "FORWARD"}});
1295 feature(&[
1296 positions.clone(),
1297 PropDef::with_string_alias(nm, &t, &v, mate),
1298 ])
1299 };
1300 assert!(validator.validate_feature_def(&f).is_err());
1301
1302 Ok(())
1303 }
1304}