1use 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
77fn 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 #[test]
130 fn test_validate_value() {
131 let sa = TypeRef::StringAlias("Name".to_string());
132
133 let def = sa.clone();
135 let value = json!("yes");
136 test_set(&sa, &def, &value, &["yes"]);
137
138 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 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 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 let def = TypeRef::List(Box::new(sa.clone()));
164 let value = json!(["yes", "YES"]);
165 test_set(&sa, &def, &value, &["yes", "YES"]);
166
167 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 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}