1use sha2::{Digest, Sha256};
6
7use crate::intermediate_representation::{
8 EnumDef, FeatureDef, ObjectDef, PropDef, TypeRef, VariantDef,
9};
10use std::{
11 collections::{BTreeMap, HashSet},
12 hash::{Hash, Hasher},
13};
14
15use super::TypeQuery;
16
17pub(crate) struct SchemaHasher<'a> {
18 enum_defs: &'a BTreeMap<String, EnumDef>,
19 object_defs: &'a BTreeMap<String, ObjectDef>,
20}
21
22impl<'a> SchemaHasher<'a> {
23 pub(crate) fn new(
24 enums: &'a BTreeMap<String, EnumDef>,
25 objs: &'a BTreeMap<String, ObjectDef>,
26 ) -> Self {
27 Self {
28 enum_defs: enums,
29 object_defs: objs,
30 }
31 }
32
33 pub(crate) fn hash(&self, feature_def: &FeatureDef) -> u64 {
34 let mut hasher: Sha256Hasher = Default::default();
35 feature_def.schema_hash(&mut hasher);
36
37 let types = self.all_types(feature_def);
38
39 for (obj_nm, obj_def) in self.object_defs {
43 if types.contains(&TypeRef::Object(obj_nm.clone())) {
44 obj_def.schema_hash(&mut hasher);
45 }
46 }
47
48 for (enum_nm, enum_def) in self.enum_defs {
49 if types.contains(&TypeRef::Enum(enum_nm.clone())) {
50 enum_def.schema_hash(&mut hasher);
51 }
52 }
53
54 hasher.finish()
55 }
56
57 fn all_types(&self, feature_def: &FeatureDef) -> HashSet<TypeRef> {
58 let all_types = TypeQuery::new(self.object_defs);
59 all_types.all_types(feature_def)
60 }
61}
62
63trait SchemaHash {
64 fn schema_hash<H: Hasher>(&self, state: &mut H);
65}
66
67impl SchemaHash for FeatureDef {
68 fn schema_hash<H: Hasher>(&self, state: &mut H) {
69 self.props.schema_hash(state);
70 self.allow_coenrollment.hash(state);
71 }
72}
73
74impl SchemaHash for Vec<PropDef> {
75 fn schema_hash<H: Hasher>(&self, state: &mut H) {
76 let mut vec: Vec<_> = self.iter().collect();
77 vec.sort_by_key(|item| &item.name);
78
79 for item in vec {
80 item.schema_hash(state);
81 }
82 }
83}
84
85impl SchemaHash for Vec<VariantDef> {
86 fn schema_hash<H: Hasher>(&self, state: &mut H) {
87 let mut vec: Vec<_> = self.iter().collect();
88 vec.sort_by_key(|item| &item.name);
89
90 for item in vec {
91 item.schema_hash(state);
92 }
93 }
94}
95
96impl SchemaHash for PropDef {
97 fn schema_hash<H: Hasher>(&self, state: &mut H) {
98 self.name.hash(state);
99 self.typ.hash(state);
100 self.string_alias.hash(state);
101 }
102}
103
104impl SchemaHash for ObjectDef {
105 fn schema_hash<H: Hasher>(&self, state: &mut H) {
106 self.props.schema_hash(state);
107 }
108}
109
110impl SchemaHash for EnumDef {
111 fn schema_hash<H: Hasher>(&self, state: &mut H) {
112 self.variants.schema_hash(state);
113 }
114}
115
116impl SchemaHash for VariantDef {
117 fn schema_hash<H: Hasher>(&self, state: &mut H) {
118 self.name.hash(state);
119 }
120}
121
122#[derive(Default)]
123pub(crate) struct Sha256Hasher {
124 hasher: Sha256,
125}
126
127impl std::hash::Hasher for Sha256Hasher {
128 fn finish(&self) -> u64 {
129 let v = self.hasher.clone().finalize();
130 u64::from_le_bytes(v[0..8].try_into().unwrap())
131 }
132
133 fn write(&mut self, bytes: &[u8]) {
134 self.hasher.update(bytes);
135 }
136}
137
138#[cfg(test)]
139mod unit_tests {
140
141 use crate::error::Result;
142 use serde_json::json;
143
144 use super::*;
145
146 #[test]
147 fn test_simple_schema_is_stable() -> Result<()> {
148 let enums = Default::default();
149 let objs = Default::default();
150
151 let prop1 = PropDef::new("p1", &TypeRef::String, &json!("No"));
152 let prop2 = PropDef::new("p2", &TypeRef::Int, &json!(42));
153
154 let feature_def =
155 FeatureDef::new("test_feature", "documentation", vec![prop1, prop2], false);
156 let mut prev: Option<u64> = None;
157 for _ in 0..100 {
158 let hasher = SchemaHasher::new(&enums, &objs);
159 let hash = hasher.hash(&feature_def);
160 if let Some(prev) = prev {
161 assert_eq!(prev, hash);
162 }
163 prev = Some(hash);
164 }
165
166 Ok(())
167 }
168
169 #[test]
170 fn test_simple_schema_is_stable_with_props_in_any_order() -> Result<()> {
171 let enums = Default::default();
172 let objs = Default::default();
173
174 let prop1 = PropDef::new("p1", &TypeRef::String, &json!("No"));
175 let prop2 = PropDef::new("p2", &TypeRef::Int, &json!(42));
176
177 let f1 = {
178 FeatureDef::new(
179 "test_feature",
180 "documentation",
181 vec![prop1.clone(), prop2.clone()],
182 false,
183 )
184 };
185
186 let f2 = { FeatureDef::new("test_feature", "documentation", vec![prop2, prop1], false) };
187
188 let hasher = SchemaHasher::new(&enums, &objs);
189 assert_eq!(hasher.hash(&f1), hasher.hash(&f2));
190
191 Ok(())
192 }
193
194 #[test]
195 fn test_simple_schema_is_stable_changing_defaults() -> Result<()> {
196 let enums = Default::default();
197 let objs = Default::default();
198
199 let f1 = {
200 let prop1 = PropDef::new("p1", &TypeRef::String, &json!("No"));
201 let prop2 = PropDef::new("p2", &TypeRef::Int, &json!(42));
202 FeatureDef::new("test_feature", "documentation", vec![prop1, prop2], false)
203 };
204
205 let f2 = {
206 let prop1 = PropDef::new("p1", &TypeRef::String, &json!("Nope"));
207 let prop2 = PropDef::new("p2", &TypeRef::Int, &json!(1));
208 FeatureDef::new("test_feature", "documentation", vec![prop1, prop2], false)
209 };
210
211 let hasher = SchemaHasher::new(&enums, &objs);
212 assert_eq!(hasher.hash(&f1), hasher.hash(&f2));
213
214 Ok(())
215 }
216
217 #[test]
218 fn test_simple_schema_is_sensitive_to_change() -> Result<()> {
219 let enums = Default::default();
220 let objs = Default::default();
221
222 let f1 = {
223 let prop1 = PropDef::new("p1", &TypeRef::String, &json!("Nope"));
224 let prop2 = PropDef::new("p2", &TypeRef::Int, &json!(1));
225 FeatureDef::new("test_feature", "documentation", vec![prop1, prop2], false)
226 };
227
228 let hasher = SchemaHasher::new(&enums, &objs);
229
230 let ne = {
232 let prop1 = PropDef::new("p1", &TypeRef::String, &json!("Nope"));
233 let prop2 = PropDef::new("p2", &TypeRef::Boolean, &json!(1));
234 FeatureDef::new("test_feature", "documentation", vec![prop1, prop2], false)
235 };
236 assert_ne!(hasher.hash(&f1), hasher.hash(&ne));
237
238 let ne = {
240 let prop1 = PropDef::new("p1_", &TypeRef::String, &json!("Nope"));
241 let prop2 = PropDef::new("p2", &TypeRef::Int, &json!(1));
242 FeatureDef::new("test_feature", "documentation", vec![prop1, prop2], false)
243 };
244 assert_ne!(hasher.hash(&f1), hasher.hash(&ne));
245
246 let ne = {
248 let prop1 = PropDef::new("p1", &TypeRef::String, &json!("Nope"));
249 let prop2 = PropDef::new("p2", &TypeRef::Int, &json!(1));
250 FeatureDef::new("test_feature", "documentation", vec![prop1, prop2], true)
251 };
252 assert_ne!(hasher.hash(&f1), hasher.hash(&ne));
253
254 Ok(())
255 }
256
257 #[test]
258 fn test_schema_is_sensitive_to_enum_change() -> Result<()> {
259 let objs = Default::default();
260
261 let enum_nm = "MyEnum";
262 let enum_t = TypeRef::Enum(enum_nm.to_string());
263
264 let f1 = {
265 let prop1 = PropDef::new("p1", &enum_t, &json!("one"));
266 FeatureDef::new("test_feature", "documentation", vec![prop1], false)
267 };
268
269 let enums = {
270 let enum1 = EnumDef::new(enum_nm, &["one", "two"]);
271 EnumDef::into_map(&[enum1])
272 };
273
274 let hasher = SchemaHasher::new(&enums, &objs);
275 let h1 = hasher.hash(&f1);
276
277 let enums = {
278 let enum1 = EnumDef::new(enum_nm, &["one", "two", "newly-added"]);
279 EnumDef::into_map(&[enum1])
280 };
281 let hasher = SchemaHasher::new(&enums, &objs);
282 let ne = hasher.hash(&f1);
283
284 assert_ne!(h1, ne);
285
286 Ok(())
287 }
288
289 #[test]
290 fn test_schema_is_sensitive_only_to_the_enums_used() -> Result<()> {
291 let objs = Default::default();
292
293 let enum_nm = "MyEnum";
294 let enum_t = TypeRef::Enum(enum_nm.to_string());
295
296 let f1 = {
297 let prop1 = PropDef::new("p1", &enum_t, &json!("one"));
298 FeatureDef::new("test_feature", "documentation", vec![prop1], false)
299 };
300
301 let enums = {
302 let enum1 = EnumDef::new(enum_nm, &["one", "two"]);
303 let enums1 = &[enum1];
304 EnumDef::into_map(enums1)
305 };
306
307 let hasher = SchemaHasher::new(&enums, &objs);
308 let h1 = hasher.hash(&f1);
310
311 let enums = {
312 let enum1 = EnumDef::new(enum_nm, &["one", "two"]);
313 let enum2 = EnumDef::new("AnotherEnum", &["one", "two"]);
315 let enums1 = &[enum1, enum2];
316 EnumDef::into_map(enums1)
317 };
318 let hasher = SchemaHasher::new(&enums, &objs);
319 let h2 = hasher.hash(&f1);
320
321 assert_eq!(h1, h2);
322
323 Ok(())
324 }
325
326 #[test]
327 fn test_schema_is_sensitive_to_object_change() -> Result<()> {
328 let enums = Default::default();
329 let obj_nm = "MyObject";
330 let obj_t = TypeRef::Object(obj_nm.to_string());
331
332 let f1 = {
333 let prop1 = PropDef::new("p1", &obj_t, &json!({}));
334 FeatureDef::new("test_feature", "documentation", vec![prop1], false)
335 };
336
337 let objs = {
338 let obj_def = ObjectDef::new(
339 obj_nm,
340 &[PropDef::new("obj-p1", &TypeRef::Boolean, &json!(true))],
341 );
342
343 ObjectDef::into_map(&[obj_def])
344 };
345
346 let hasher = SchemaHasher::new(&enums, &objs);
347 let h1 = hasher.hash(&f1);
349
350 let objs = {
351 let obj_def = ObjectDef::new(
352 obj_nm,
353 &[
354 PropDef::new("obj-p1", &TypeRef::Boolean, &json!(true)),
355 PropDef::new("obj-p2", &TypeRef::Boolean, &json!(true)),
356 ],
357 );
358
359 ObjectDef::into_map(&[obj_def])
360 };
361
362 let hasher = SchemaHasher::new(&enums, &objs);
363 let ne = hasher.hash(&f1);
364
365 assert_ne!(h1, ne);
366
367 Ok(())
368 }
369
370 #[test]
371 fn test_schema_is_sensitive_only_to_the_objects_used() -> Result<()> {
372 let enums = Default::default();
373
374 let obj_nm = "MyObject";
375 let obj_t = TypeRef::Object(obj_nm.to_string());
376
377 let f1 = {
378 let prop1 = PropDef::new("p1", &obj_t, &json!({}));
379 FeatureDef::new("test_feature", "documentation", vec![prop1], false)
380 };
381
382 let objects = {
383 let obj1 = ObjectDef::new(
384 obj_nm,
385 &[PropDef::new("obj-p1", &TypeRef::Boolean, &json!(true))],
386 );
387 ObjectDef::into_map(&[obj1])
388 };
389
390 let hasher = SchemaHasher::new(&enums, &objects);
391 let h1 = hasher.hash(&f1);
393
394 let objects = {
396 let obj1 = ObjectDef::new(
397 obj_nm,
398 &[PropDef::new("obj-p1", &TypeRef::Boolean, &json!(true))],
399 );
400 let obj2 = ObjectDef::new(
401 "AnotherObject",
402 &[PropDef::new("obj-p1", &TypeRef::Boolean, &json!(true))],
403 );
404 ObjectDef::into_map(&[obj1, obj2])
405 };
406
407 let hasher = SchemaHasher::new(&enums, &objects);
408 let h2 = hasher.hash(&f1);
409
410 assert_eq!(h1, h2);
411
412 Ok(())
413 }
414
415 #[test]
416 fn test_schema_is_sensitive_to_nested_change() -> Result<()> {
417 let obj_nm = "MyObject";
418 let obj_t = TypeRef::Object(obj_nm.to_string());
419
420 let enum_nm = "MyEnum";
421 let enum_t = TypeRef::Enum(enum_nm.to_string());
422
423 let f1 = {
424 let prop1 = PropDef::new("p1", &obj_t, &json!({}));
425 FeatureDef::new("test_feature", "documentation", vec![prop1], false)
426 };
427
428 let objs = {
429 let obj_def = ObjectDef::new(obj_nm, &[PropDef::new("obj-p1", &enum_t, &json!("one"))]);
430
431 ObjectDef::into_map(&[obj_def])
432 };
433
434 let enums = {
435 let enum1 = EnumDef::new(enum_nm, &["one", "two"]);
436 EnumDef::into_map(&[enum1])
437 };
438
439 let hasher = SchemaHasher::new(&enums, &objs);
440 let h1 = hasher.hash(&f1);
442
443 let enums = {
445 let enum1 = EnumDef::new(enum_nm, &["one", "two", "newly-added"]);
446 EnumDef::into_map(&[enum1])
447 };
448 let hasher = SchemaHasher::new(&enums, &objs);
449 let ne = hasher.hash(&f1);
450
451 assert_ne!(h1, ne);
452 Ok(())
453 }
454}