1use crate::schema::TypeQuery;
6use crate::{
7 intermediate_representation::{FeatureDef, ObjectDef, PropDef, TypeRef},
8 schema::Sha256Hasher,
9};
10use serde_json::Value;
11use std::{
12 collections::{BTreeMap, BTreeSet, HashSet},
13 hash::{Hash, Hasher},
14};
15
16pub(crate) struct DefaultsHasher<'a> {
17 object_defs: &'a BTreeMap<String, ObjectDef>,
18}
19
20impl<'a> DefaultsHasher<'a> {
21 pub(crate) fn new(objs: &'a BTreeMap<String, ObjectDef>) -> Self {
22 Self { object_defs: objs }
23 }
24
25 pub(crate) fn hash(&self, feature_def: &FeatureDef) -> u64 {
26 let mut hasher = Sha256Hasher::default();
27 feature_def.defaults_hash(&mut hasher);
28
29 let types = self.all_types(feature_def);
30
31 for (name, obj_def) in self.object_defs {
35 if types.contains(&TypeRef::Object(name.clone())) {
36 obj_def.defaults_hash(&mut hasher);
37 }
38 }
39
40 hasher.finish()
41 }
42
43 fn all_types(&self, feature_def: &FeatureDef) -> HashSet<TypeRef> {
44 TypeQuery::new(self.object_defs).all_types(feature_def)
45 }
46}
47
48trait DefaultsHash {
49 fn defaults_hash<H: Hasher>(&self, state: &mut H);
50}
51
52impl DefaultsHash for FeatureDef {
53 fn defaults_hash<H: Hasher>(&self, state: &mut H) {
54 self.props.defaults_hash(state);
55 }
56}
57
58impl DefaultsHash for Vec<PropDef> {
59 fn defaults_hash<H: Hasher>(&self, state: &mut H) {
60 let mut vec = self.iter().collect::<Vec<_>>();
61 vec.sort_by_key(|item| &item.name);
62
63 for item in vec {
64 item.defaults_hash(state);
65 }
66 }
67}
68
69impl DefaultsHash for PropDef {
70 fn defaults_hash<H: Hasher>(&self, state: &mut H) {
71 self.name.hash(state);
72 self.default.defaults_hash(state);
73 }
74}
75
76impl DefaultsHash for ObjectDef {
77 fn defaults_hash<H: Hasher>(&self, state: &mut H) {
78 self.props.defaults_hash(state);
79 }
80}
81
82impl DefaultsHash for Value {
83 fn defaults_hash<H: Hasher>(&self, state: &mut H) {
84 match self {
85 Self::Null => 0_u8.hash(state),
86 Self::Number(v) => v.hash(state),
87 Self::Bool(v) => v.hash(state),
88 Self::String(v) => v.hash(state),
89 Self::Array(array) => {
90 for v in array {
91 v.defaults_hash(state);
92 }
93 }
94 Self::Object(map) => {
95 let keys = map.keys().collect::<BTreeSet<_>>();
96 for k in keys {
97 let v = map.get(k).unwrap();
98 v.defaults_hash(state);
99 }
100 }
101 }
102 }
103}
104
105#[cfg(test)]
106mod unit_tests {
107 use super::*;
108 use crate::error::Result;
109
110 use serde_json::json;
111
112 #[test]
113 fn test_simple_feature_stable_over_time() -> Result<()> {
114 let objs = Default::default();
115
116 let feature_def = {
117 let p1 = PropDef::new("my-int", &TypeRef::Int, &json!(1));
118 let p2 = PropDef::new("my-bool", &TypeRef::Boolean, &json!(true));
119 let p3 = PropDef::new("my-string", &TypeRef::String, &json!("string"));
120 FeatureDef::new("test_feature", "", vec![p1, p2, p3], false)
121 };
122
123 let mut prev: Option<u64> = None;
124 for _ in 0..100 {
125 let hasher = DefaultsHasher::new(&objs);
126 let hash = hasher.hash(&feature_def);
127 if let Some(prev) = prev {
128 assert_eq!(prev, hash);
129 }
130 prev = Some(hash);
131 }
132
133 Ok(())
134 }
135
136 #[test]
137 fn test_simple_feature_is_stable_with_props_in_any_order() -> Result<()> {
138 let objs = Default::default();
139
140 let p1 = PropDef::new("my-int", &TypeRef::Int, &json!(1));
141 let p2 = PropDef::new("my-bool", &TypeRef::Boolean, &json!(true));
142 let p3 = PropDef::new("my-string", &TypeRef::String, &json!("string"));
143
144 let f1 = FeatureDef::new(
145 "test_feature",
146 "",
147 vec![p1.clone(), p2.clone(), p3.clone()],
148 false,
149 );
150 let f2 = FeatureDef::new("test_feature", "", vec![p3, p2, p1], false);
151
152 let hasher = DefaultsHasher::new(&objs);
153 assert_eq!(hasher.hash(&f1), hasher.hash(&f2));
154 Ok(())
155 }
156
157 #[test]
158 fn test_simple_feature_is_stable_changing_types() -> Result<()> {
159 let objs = Default::default();
160
161 let f1 = {
163 let prop1 = PropDef::new("p1", &TypeRef::Int, &json!(42));
164 let prop2 = PropDef::new("p2", &TypeRef::String, &json!("Yes"));
165 FeatureDef::new("test_feature", "documentation", vec![prop1, prop2], false)
166 };
167
168 let f2 = {
169 let prop1 = PropDef::new("p1", &TypeRef::String, &json!(42));
170 let prop2 = PropDef::new("p2", &TypeRef::Int, &json!("Yes"));
171 FeatureDef::new("test_feature", "documentation", vec![prop1, prop2], false)
172 };
173
174 let hasher = DefaultsHasher::new(&objs);
175 assert_eq!(hasher.hash(&f1), hasher.hash(&f2));
176
177 Ok(())
178 }
179
180 #[test]
181 fn test_simple_feature_is_sensitive_to_change() -> Result<()> {
182 let objs = Default::default();
183
184 let f1 = {
185 let prop1 = PropDef::new("p1", &TypeRef::String, &json!("Yes"));
186 let prop2 = PropDef::new("p2", &TypeRef::Int, &json!(1));
187 FeatureDef::new("test_feature", "documentation", vec![prop1, prop2], false)
188 };
189
190 let hasher = DefaultsHasher::new(&objs);
191
192 let ne = {
194 let prop1 = PropDef::new("p1", &TypeRef::String, &json!("Nope"));
195 let prop2 = PropDef::new("p2", &TypeRef::Boolean, &json!(1));
196 FeatureDef::new("test_feature", "documentation", vec![prop1, prop2], false)
197 };
198 assert_ne!(hasher.hash(&f1), hasher.hash(&ne));
199
200 let ne = {
202 let prop1 = PropDef::new("p1_", &TypeRef::String, &json!("Yes"));
203 let prop2 = PropDef::new("p2", &TypeRef::Int, &json!(1));
204 FeatureDef::new("test_feature", "documentation", vec![prop1, prop2], false)
205 };
206 assert_ne!(hasher.hash(&f1), hasher.hash(&ne));
207
208 let eq = {
210 let prop1 = PropDef::new("p1", &TypeRef::String, &json!("Yes"));
211 let prop2 = PropDef::new("p2", &TypeRef::Int, &json!(1));
212 FeatureDef::new("test_feature", "documentation", vec![prop1, prop2], true)
213 };
214 assert_eq!(hasher.hash(&f1), hasher.hash(&eq));
215
216 Ok(())
217 }
218
219 #[test]
220 fn test_feature_is_sensitive_to_object_change() -> Result<()> {
221 let obj_nm = "MyObject";
222 let obj_t = TypeRef::Object(obj_nm.to_string());
223
224 let f1 = {
225 let prop1 = PropDef::new("p1", &obj_t, &json!({}));
226 FeatureDef::new("test_feature", "documentation", vec![prop1], false)
227 };
228
229 let objs = {
230 let obj_def = ObjectDef::new(
231 obj_nm,
232 &[PropDef::new("obj-p1", &TypeRef::Boolean, &json!(true))],
233 );
234
235 ObjectDef::into_map(&[obj_def])
236 };
237
238 let hasher = DefaultsHasher::new(&objs);
239 let h1 = hasher.hash(&f1);
241
242 let objs = {
244 let obj_def = ObjectDef::new(
245 obj_nm,
246 &[PropDef::new("obj-p1", &TypeRef::Boolean, &json!(false))],
247 );
248
249 ObjectDef::into_map(&[obj_def])
250 };
251
252 let hasher = DefaultsHasher::new(&objs);
253 let ne = hasher.hash(&f1);
254
255 assert_ne!(h1, ne);
256
257 Ok(())
258 }
259
260 #[test]
261 fn test_hash_is_sensitive_to_nested_change() -> Result<()> {
262 let obj1_nm = "MyObject";
263 let obj1_t = TypeRef::Object(obj1_nm.to_string());
264
265 let obj2_nm = "MyNestedObject";
266 let obj2_t = TypeRef::Object(obj2_nm.to_string());
267
268 let obj1_def = ObjectDef::new(obj1_nm, &[PropDef::new("p1-obj2", &obj2_t, &json!({}))]);
269
270 let f1 = {
271 let prop1 = PropDef::new("p1", &obj1_t.clone(), &json!({}));
272 FeatureDef::new("test_feature", "documentation", vec![prop1], false)
273 };
274
275 let objs = {
276 let obj2_def = ObjectDef::new(
277 obj2_nm,
278 &[PropDef::new("p1-string", &TypeRef::String, &json!("one"))],
279 );
280 ObjectDef::into_map(&[obj1_def.clone(), obj2_def])
281 };
282
283 let hasher = DefaultsHasher::new(&objs);
284 let h1 = hasher.hash(&f1);
286
287 let objs = {
289 let obj2_def = ObjectDef::new(
290 obj2_nm,
291 &[PropDef::new("p1-string", &TypeRef::String, &json!("two"))],
292 );
293 ObjectDef::into_map(&[obj1_def.clone(), obj2_def])
294 };
295 let hasher = DefaultsHasher::new(&objs);
296 let ne = hasher.hash(&f1);
297
298 assert_ne!(h1, ne);
299 Ok(())
300 }
301}