nimbus_fml/defaults/
validator.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2* License, v. 2.0. If a copy of the MPL was not distributed with this
3* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5use 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    /// This is called as part of the _manifest_ validation only, as part of `fm.validate_defaults()`,
51    /// shortly after `fm.validate_schema()`.
52    ///
53    /// It is not called as part of feature validation, i.e. once the manifest has been loaded
54    /// and validated, and now to be used to validate arbitrary JSON.
55    ///
56    /// It bails with the first detected error. The error detection itself occurs with
57    /// the `get_errors` call below.
58    ///
59    /// It does not check if there are spurious keys in a feature than are defined (this is done in the DefaultsMerger).
60    /// It does check if the features enum maps have a complete set of variants as keys.
61    ///
62    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        // This is only checking if a Map with an Enum as key has a complete set of keys (i.e. all variants)
68        self.validate_feature_enum_maps(feature_def)?;
69
70        // Now check the examples for this feature.
71        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    /// Called as part of validating any feature def against a full JSON defaults (either the default json, or something
96    /// merged on to a default json).
97    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    /// Check enum maps (Map<Enum, T>) have all keys represented.
158    ///
159    /// We split this out because if the FML has all keys, then any feature configs do as well.
160    ///
161    /// Thus, we don't need to do the detection when editing a feature config.
162    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                    // If this is thrown, there's a problem in validate_type_ref.
180                    .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)) // Map<string-alias, T>
207            | (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                    // If this is thrown, there's a problem in validate_type_ref.
223                    .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                    // If this is thrown, there's a problem in validate_type_ref.
261                    .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                    // If this is thrown, there's a problem in validate_type_ref.
285                    .unwrap_or_else(|| {
286                        unreachable!("Enum {enum_name} is not defined in the manifest")
287                    });
288
289                // We first validate that the keys of the map cover all all the enum variants, and no more or less
290                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)) // Map<string-alias, T>
318            | (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                    // If this is thrown, there's a problem in validate_type_ref.
332                    .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            // We only check the defaults overriding the property defaults
358            // from the object's own property defaults.
359            // We check the object property defaults previously.
360            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    /// Validate a property against the string aliases in the feature.
379    ///
380    /// A property can be of any type: this will recurse into the structural types and object types
381    /// looking for strings to validate.
382    ///
383    /// - path The error path at which to report any errors
384    /// - typ The type of the value we're validating. Only objects, structural types and string-aliases will do anything.
385    ///   We'll be recursing into this type.
386    /// - value The value we're validating. We'll be recursing into this value.
387    /// - definitions The properties in this feature that define the string-alias types.
388    /// - feature_value The merged value for the entire feature
389    /// - skip The property we're validating may include a definition
390    #[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        // As an optimization (to stop validating the definition against itself),
402        // we want to skip validation on the `skip` type ref: this is only set by the property defining
403        // a string-alias.
404        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                        // string-alias definitions aren't allowed in Object definitions,
490                        // so `skip` is None.
491                        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                        // There is no value in the map, so we need to validate the
502                        // default.
503                        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 the default is invalid, then it doesn't really matter
515                        // what the error is, we can just error out.
516                        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    // SchemaValidator checked that the property definitely exists.
541    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
550/// Takes
551/// - a string value e.g. "Alice"
552/// - a string-alias type, StringAlias("TeamMateName") / TeamMateName
553/// - a type definition of a wider collection of teammates: e.g. List<TeamMateName>
554/// - an a value for the collection of teammates: e.g. ["Alice", "Bonnie", "Charlie", "Dawn"]
555///
556/// Given the args, returns a boolean: is the string value in the collection?
557///
558/// This should work with arbitrary collection types, e.g.
559/// - TeamMate,
560/// - Option<TeamMate>,
561/// - List<TeamMate>,
562/// - Map<TeamMate, _>
563/// - Map<_, TeamMate>
564///
565/// and any arbitrary nesting of the collection types.
566fn 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        // This test is missing a `list` property. But that's ok, because we'll get it from the object definition.
949        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        // OK, because we are missing `optional` which is optional anyways
977        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        // OK because the value is optional, and thus it's okay if it's missing (green is missing from the default)
997        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    // Does this string belong in the type definition?
1009    #[test]
1010    fn test_validate_value() -> Result<()> {
1011        let sa = TypeRef::StringAlias("Name".to_string());
1012
1013        // type definition is Name
1014        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        // type definition is Name?
1020        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        // type definition is Map<Name, Boolean>
1029        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        // type definition is Map<String, Name>
1039        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        // type definition is List<String>
1049        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        // type definition is List<Map<String, Name>>
1056        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        // type definition is Map<String, List<Name>>
1063        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        // For all these tests, the_team defines the set of strings which are valid TeamMate strings.
1115
1116        // captain is a TeamMate
1117        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        // goalkeeper is an Option<TeamMate>
1134        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        // defenders are List<TeamMate>
1155        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        // injury-status are Map<TeamMate, Boolean>
1177        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        // positions are Map<PositionName, List<TeamMate>>
1192        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        // newest-player: Player
1250        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        // positions: List<PositionName>
1273        // players: Map<TeamMateName, Player>
1274        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}