nimbus_fml/editing/
values_finder.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 std::collections::{BTreeMap, BTreeSet, HashMap};
6
7use serde_json::Value;
8
9use crate::intermediate_representation::{EnumDef, FeatureDef, PropDef, TypeRef};
10
11pub(crate) struct ValuesFinder<'a> {
12    enum_defs: &'a BTreeMap<String, EnumDef>,
13    string_aliases: HashMap<&'a str, &'a PropDef>,
14    feature_value: &'a Value,
15}
16
17impl<'a> ValuesFinder<'a> {
18    pub(crate) fn new(
19        enum_defs: &'a BTreeMap<String, EnumDef>,
20        feature_def: &'a FeatureDef,
21        feature_value: &'a Value,
22    ) -> Self {
23        Self {
24            enum_defs,
25            string_aliases: feature_def.get_string_aliases(),
26            feature_value,
27        }
28    }
29
30    pub(crate) fn all_specific_strings(&self, type_ref: &TypeRef) -> BTreeSet<String> {
31        match type_ref {
32            TypeRef::StringAlias(_) => self.get_string_alias_values(type_ref),
33            TypeRef::Enum(type_name) => self.get_enum_values(type_name),
34            _ => Default::default(),
35        }
36    }
37
38    #[allow(dead_code)]
39    #[cfg(feature = "client-lib")]
40    pub(crate) fn all_placeholders(&self, type_ref: &TypeRef) -> BTreeSet<String> {
41        let strings: &[&str] = match type_ref {
42            TypeRef::Boolean => &["true", "false"],
43            TypeRef::Int => &["0"],
44            TypeRef::String | TypeRef::BundleText | TypeRef::BundleImage => &["\"\""],
45            TypeRef::List(_) => &["[]"],
46            TypeRef::Object(_) | TypeRef::EnumMap(_, _) | TypeRef::StringMap(_) => &["{}"],
47
48            _ => &[],
49        };
50
51        strings.iter().cloned().map(String::from).collect()
52    }
53}
54
55impl ValuesFinder<'_> {
56    fn get_enum_values(&self, type_name: &str) -> BTreeSet<String> {
57        if let Some(def) = self.enum_defs.get(type_name) {
58            def.variants.iter().map(|v| v.name()).collect()
59        } else {
60            Default::default()
61        }
62    }
63
64    fn get_string_alias_values(&self, alias_type: &TypeRef) -> BTreeSet<String> {
65        let type_name = alias_type.name().unwrap();
66        let prop = self.string_aliases[type_name];
67
68        let def_type = &prop.typ;
69        let def_value = self.feature_value.get(&prop.name).unwrap();
70
71        let mut set = BTreeSet::new();
72        collect_string_alias_values(alias_type, def_type, def_value, &mut set);
73        set
74    }
75}
76
77/// Takes
78/// - a string-alias type, StringAlias("TeammateName") / TeamMateName
79/// - a type definition of a wider collection of teammates: e.g. Map<TeamMateName, TeamMate>
80/// - an a value for the collection of teammates: e.g. {"Alice": {}, "Bonnie": {}, "Charlie": {}, "Dawn"}
81///
82/// and fills a hash set with the full set of TeamMateNames, in this case: ["Alice", "Bonnie", "Charlie", "Dawn"]
83fn collect_string_alias_values(
84    alias_type: &TypeRef,
85    def_type: &TypeRef,
86    def_value: &Value,
87    set: &mut BTreeSet<String>,
88) {
89    match (def_type, def_value) {
90        (TypeRef::StringAlias(_), Value::String(s)) if alias_type == def_type => {
91            set.insert(s.clone());
92        }
93        (TypeRef::Option(dt), dv) if dv != &Value::Null => {
94            collect_string_alias_values(alias_type, dt, dv, set);
95        }
96        (TypeRef::EnumMap(kt, _), Value::Object(map)) if alias_type == &**kt => {
97            set.extend(map.keys().cloned());
98        }
99        (TypeRef::EnumMap(_, vt), Value::Object(map))
100        | (TypeRef::StringMap(vt), Value::Object(map)) => {
101            for item in map.values() {
102                collect_string_alias_values(alias_type, vt, item, set);
103            }
104        }
105        (TypeRef::List(vt), Value::Array(array)) => {
106            for item in array {
107                collect_string_alias_values(alias_type, vt, item, set);
108            }
109        }
110        _ => {}
111    }
112}
113
114#[cfg(test)]
115mod string_alias {
116
117    use super::*;
118    use serde_json::json;
119
120    fn test_set(alias_type: &TypeRef, def_type: &TypeRef, def_value: &Value, set: &[&str]) {
121        let mut observed = BTreeSet::new();
122        collect_string_alias_values(alias_type, def_type, def_value, &mut observed);
123
124        let expected: BTreeSet<_> = set.iter().map(|s| s.to_owned().to_owned()).collect();
125        assert_eq!(expected, observed);
126    }
127
128    // Does this string belong in the type definition?
129    #[test]
130    fn test_validate_value() {
131        let sa = TypeRef::StringAlias("Name".to_string());
132
133        // type definition is Name
134        let def = sa.clone();
135        let value = json!("yes");
136        test_set(&sa, &def, &value, &["yes"]);
137
138        // type definition is Name?
139        let def = TypeRef::Option(Box::new(sa.clone()));
140        let value = json!("yes");
141        test_set(&sa, &def, &value, &["yes"]);
142
143        let value = json!(null);
144        test_set(&sa, &def, &value, &[]);
145
146        // type definition is Map<Name, Boolean>
147        let def = TypeRef::EnumMap(Box::new(sa.clone()), Box::new(TypeRef::Boolean));
148        let value = json!({
149            "yes": true,
150            "YES": false,
151        });
152        test_set(&sa, &def, &value, &["yes", "YES"]);
153
154        // type definition is Map<String, Name>
155        let def = TypeRef::EnumMap(Box::new(TypeRef::String), Box::new(sa.clone()));
156        let value = json!({
157            "ok": "yes",
158            "OK": "YES",
159        });
160        test_set(&sa, &def, &value, &["yes", "YES"]);
161
162        // type definition is List<String>
163        let def = TypeRef::List(Box::new(sa.clone()));
164        let value = json!(["yes", "YES"]);
165        test_set(&sa, &def, &value, &["yes", "YES"]);
166
167        // type definition is List<Map<String, Name>>
168        let def = TypeRef::List(Box::new(TypeRef::StringMap(Box::new(sa.clone()))));
169        let value = json!([{"y": "yes"}, {"Y": "YES"}]);
170        test_set(&sa, &def, &value, &["yes", "YES"]);
171
172        // type definition is Map<String, List<Name>>
173        let def = TypeRef::StringMap(Box::new(TypeRef::List(Box::new(sa.clone()))));
174        let value = json!({"y": ["yes"], "Y": ["YES"]});
175        test_set(&sa, &def, &value, &["yes", "YES"]);
176    }
177}