1use serde::{Serialize, Serializer, ser::SerializeMap};
2use xxhash_rust::xxh3::Xxh3;
3
4use crate::scalar::ScalarTyp;
5
6pub type NamedField = (&'static str, Typ);
7pub type Arr<T> = &'static [T];
8pub type Str = &'static str;
9pub type Map<T> = Arr<(u8, T)>;
10
11#[derive(PartialEq, Clone, Serialize, Debug, Copy, Hash)]
12#[serde(tag = "type", content = "data")]
13pub enum Fields {
14 Named(Arr<NamedField>),
15 Unnamed(Arr<Typ>),
16}
17
18#[derive(PartialEq, Clone, Serialize, Debug, Copy, Hash)]
23pub struct StructType {
24 pub name: &'static str,
25 pub fields: Fields,
26}
27
28fn variants<S, T>(variants: Map<T>, serializer: S) -> Result<S::Ok, S::Error>
29where
30 S: Serializer,
31 T: Serialize,
32{
33 let mut map = serializer.serialize_map(Some(variants.len()))?;
34 for (key, value) in variants {
35 map.serialize_entry(key, value)?;
36 }
37 map.end()
38}
39
40#[derive(PartialEq, Clone, Serialize, Debug, Copy, Hash)]
41pub struct EnumType {
42 pub name: Str,
43 #[serde(serialize_with = "variants")]
44 pub variants: Map<NamedField>,
45}
46
47#[derive(PartialEq, Eq, Clone, Serialize, Debug, Copy, Hash)]
48pub struct SimpleEnumType {
49 pub name: Str,
50 #[serde(serialize_with = "variants")]
51 pub variants: Map<Str>,
52}
53
54#[derive(PartialEq, Clone, Debug, Copy, Hash)]
55pub enum Typ {
56 Scalar(ScalarTyp),
57 Array(u32, &'static Typ),
58 Vec(&'static Typ),
59 Optional(&'static Typ),
60 SimpleEnum(SimpleEnumType),
61 Struct(StructType),
62 Enum(EnumType),
63 Custom(&'static str, &'static [Typ]),
64}
65
66#[derive(Serialize)]
68#[serde(tag = "type", content = "data")]
69enum TypComposite {
70 Array(u32, &'static Typ),
71 Vec(&'static Typ),
72 Optional(&'static Typ),
73 SimpleEnum(SimpleEnumType),
74 Struct(StructType),
75 Enum(EnumType),
76 Custom(&'static str, &'static [Typ]),
77}
78
79impl Serialize for Typ {
80 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
81 match self {
82 Typ::Scalar(s) => s.serialize(serializer),
83 Typ::Array(n, inner) => TypComposite::Array(*n, inner).serialize(serializer),
84 Typ::Vec(inner) => TypComposite::Vec(inner).serialize(serializer),
85 Typ::Optional(inner) => TypComposite::Optional(inner).serialize(serializer),
86 Typ::SimpleEnum(e) => TypComposite::SimpleEnum(*e).serialize(serializer),
87 Typ::Struct(s) => TypComposite::Struct(*s).serialize(serializer),
88 Typ::Enum(e) => TypComposite::Enum(*e).serialize(serializer),
89 Typ::Custom(name, args) => TypComposite::Custom(name, args).serialize(serializer),
90 }
91 }
92}
93
94const HASH_FORMAT_TAG: &[u8] = b"armour-typ/typ/v1";
97
98#[inline]
102fn hash_len(hasher: &mut Xxh3, len: usize) {
103 hasher.update(&(len as u64).to_le_bytes());
104}
105
106#[inline]
107fn hash_str(hasher: &mut Xxh3, value: &str) {
108 hash_len(hasher, value.len());
109 hasher.update(value.as_bytes());
110}
111
112fn hash_scalar(hasher: &mut Xxh3, scalar: &ScalarTyp) {
113 use ScalarTyp::*;
114 let tag: u8 = match scalar {
117 Bool => 0,
118 U8 => 1,
119 U16 => 2,
120 U32 => 3,
121 U64 => 4,
122 I32 => 5,
123 I64 => 6,
124 F32 => 7,
125 F64 => 8,
126 Str => 9,
127 Datetime => 10,
128 Timestamp => 11,
129 Decimal => 12,
130 Id32 => 13,
131 Id64 => 14,
132 Fuid => 15,
133 LowId => 16,
134 Bytes => 17,
135 Void => 18,
136 RustJson => 19,
137 JsonBytes => 20,
138 ArrayBytes(_) => 21,
139 };
140 hasher.update(&[tag]);
141 if let ArrayBytes(n) = scalar {
142 hasher.update(&n.to_le_bytes());
143 }
144}
145
146fn hash_fields(hasher: &mut Xxh3, fields: &Fields) {
147 match fields {
148 Fields::Named(items) => {
149 hasher.update(&[0u8]);
150 hash_len(hasher, items.len());
151 for (name, typ) in *items {
152 hash_str(hasher, name);
153 hash_typ(hasher, typ);
154 }
155 }
156 Fields::Unnamed(items) => {
157 hasher.update(&[1u8]);
158 hash_len(hasher, items.len());
159 for typ in *items {
160 hash_typ(hasher, typ);
161 }
162 }
163 }
164}
165
166fn hash_typ(hasher: &mut Xxh3, typ: &Typ) {
167 match typ {
168 Typ::Scalar(s) => {
169 hasher.update(&[0u8]);
170 hash_scalar(hasher, s);
171 }
172 Typ::Array(n, inner) => {
173 hasher.update(&[1u8]);
174 hasher.update(&n.to_le_bytes());
175 hash_typ(hasher, inner);
176 }
177 Typ::Vec(inner) => {
178 hasher.update(&[2u8]);
179 hash_typ(hasher, inner);
180 }
181 Typ::Optional(inner) => {
182 hasher.update(&[3u8]);
183 hash_typ(hasher, inner);
184 }
185 Typ::SimpleEnum(e) => {
186 hasher.update(&[4u8]);
187 hash_str(hasher, e.name);
188 hash_len(hasher, e.variants.len());
189 for (discr, name) in e.variants {
190 hasher.update(&[*discr]);
191 hash_str(hasher, name);
192 }
193 }
194 Typ::Struct(s) => {
195 hasher.update(&[5u8]);
196 hash_str(hasher, s.name);
197 hash_fields(hasher, &s.fields);
198 }
199 Typ::Enum(e) => {
200 hasher.update(&[6u8]);
201 hash_str(hasher, e.name);
202 hash_len(hasher, e.variants.len());
203 for (discr, (name, payload)) in e.variants {
204 hasher.update(&[*discr]);
205 hash_str(hasher, name);
206 hash_typ(hasher, payload);
207 }
208 }
209 Typ::Custom(name, args) => {
210 hasher.update(&[7u8]);
211 hash_str(hasher, name);
212 hash_len(hasher, args.len());
213 for arg in *args {
214 hash_typ(hasher, arg);
215 }
216 }
217 }
218}
219
220impl Typ {
221 pub fn h(&self) -> u64 {
232 let mut hasher = Xxh3::new();
233 hasher.update(HASH_FORMAT_TAG);
234 hash_typ(&mut hasher, self);
235 hasher.digest()
236 }
237}
238
239#[inline]
245pub fn schema_hash(t: &Typ) -> u64 {
246 t.h()
247}
248
249#[cfg(test)]
250mod golden {
251 use super::*;
252 use crate::scalar::ScalarTyp;
253
254 fn golden_cases() -> Vec<(&'static str, Typ, u64)> {
259 use ScalarTyp::*;
260 vec![
261 ("Bool", Typ::Scalar(Bool), 0x844969503cd52142),
262 ("U8", Typ::Scalar(U8), 0xc8cadc679de83264),
263 ("U16", Typ::Scalar(U16), 0x31f140c9e9d24a6a),
264 ("U32", Typ::Scalar(U32), 0xb6e382b85cc8ad16),
265 ("U64", Typ::Scalar(U64), 0x3e2513ed9422dbf3),
266 ("I32", Typ::Scalar(I32), 0x3eed686985c17e30),
267 ("I64", Typ::Scalar(I64), 0x6dcdfb5efac1eff5),
268 ("F32", Typ::Scalar(F32), 0xd4256e6355df3aa6),
269 ("F64", Typ::Scalar(F64), 0x25b32f6d7a5db6d7),
270 ("Str", Typ::Scalar(Str), 0x2aa6e56e9ce67c6d),
271 ("Datetime", Typ::Scalar(Datetime), 0xf789bd4fa70b626b),
272 ("Timestamp", Typ::Scalar(Timestamp), 0x31205013816d027c),
273 ("Decimal", Typ::Scalar(Decimal), 0x8bda406d6c8e133b),
274 ("Id32", Typ::Scalar(Id32), 0x30e0670bf6dea824),
275 ("Id64", Typ::Scalar(Id64), 0xc1c4537aede104ae),
276 ("Fuid", Typ::Scalar(Fuid), 0x313781a3f9e8ffbb),
277 ("LowId", Typ::Scalar(LowId), 0x09b9aaafe34aeb45),
278 ("Bytes", Typ::Scalar(Bytes), 0x08276244715f6ff1),
279 ("Void", Typ::Scalar(Void), 0xed774f18fb9a6e91),
280 ("RustJson", Typ::Scalar(RustJson), 0x3874a1693f60d5d7),
281 ("JsonBytes", Typ::Scalar(JsonBytes), 0xb4ccb5d58b3e668b),
282 ("ArrayBytes", Typ::Scalar(ArrayBytes(4)), 0x58f9ebfb19da8930),
283 (
284 "Array",
285 Typ::Array(3, &Typ::Scalar(U64)),
286 0xe8fb2cd993687efe,
287 ),
288 ("Vec", Typ::Vec(&Typ::Scalar(U64)), 0x0def01338fe2656c),
289 (
290 "Optional",
291 Typ::Optional(&Typ::Scalar(U64)),
292 0xa03ca0cb992f08da,
293 ),
294 (
295 "SimpleEnum",
296 Typ::SimpleEnum(SimpleEnumType {
297 name: "SE",
298 variants: &[(0, "A"), (1, "B")],
299 }),
300 0x52315acdab3124db,
301 ),
302 (
303 "StructNamed",
304 Typ::Struct(StructType {
305 name: "S",
306 fields: Fields::Named(&[("a", Typ::Scalar(U64)), ("b", Typ::Scalar(Str))]),
307 }),
308 0xa453f5fe90303c85,
309 ),
310 (
311 "StructUnnamed",
312 Typ::Struct(StructType {
313 name: "",
314 fields: Fields::Unnamed(&[Typ::Scalar(U64), Typ::Scalar(Bool)]),
315 }),
316 0x067f1d66238d5e94,
317 ),
318 (
319 "Enum",
320 Typ::Enum(EnumType {
321 name: "E",
322 variants: &[(0, ("A", Typ::Scalar(U64))), (1, ("B", Typ::Scalar(Bool)))],
323 }),
324 0x9d717d3549fb9528,
325 ),
326 (
327 "Custom",
328 Typ::Custom("C", &[Typ::Scalar(U64), Typ::Scalar(Str)]),
329 0x04ff19c55360b278,
330 ),
331 ]
332 }
333
334 #[test]
337 #[ignore]
338 fn print_golden() {
339 for (name, t, _) in golden_cases() {
340 println!("(\"{name}\", …, 0x{:016x}),", t.h());
341 }
342 }
343
344 #[test]
345 fn golden_typ_hash() {
346 for (name, t, expected) in golden_cases() {
347 assert_eq!(t.h(), expected, "typ_hash drift for variant `{name}`");
348 }
349 }
350
351 #[allow(dead_code)]
353 fn _scalar_guard(s: &ScalarTyp) {
354 use ScalarTyp::*;
355 match s {
356 Bool | U8 | U16 | U32 | U64 | I32 | I64 | F32 | F64 | Str | Datetime | Timestamp
357 | Decimal | Id32 | Id64 | Fuid | LowId | Bytes | Void | RustJson | JsonBytes => {}
358 ArrayBytes(_) => {}
359 }
360 }
361
362 #[allow(dead_code)]
364 fn _typ_guard(t: &Typ) {
365 match t {
366 Typ::Scalar(_) => {}
367 Typ::Array(_, _) => {}
368 Typ::Vec(_) => {}
369 Typ::Optional(_) => {}
370 Typ::SimpleEnum(_) => {}
371 Typ::Struct(_) => {}
372 Typ::Enum(_) => {}
373 Typ::Custom(_, _) => {}
374 }
375 }
376}
377
378#[cfg(test)]
379#[allow(dead_code)]
380mod schema_hash_tests {
381 use super::*;
382 use crate::get_type::GetType;
383 use crate::scalar::ScalarTyp;
384
385 #[derive(crate::GetType)]
386 struct NamedOrderA {
387 first: u32,
388 second: u64,
389 }
390
391 #[derive(crate::GetType)]
392 struct NamedOrderB {
393 second: u64,
394 first: u32,
395 }
396
397 #[test]
398 fn free_schema_hash_matches_typ_h() {
399 let typ = Typ::Vec(&Typ::Scalar(ScalarTyp::U64));
400 assert_eq!(schema_hash(&typ), typ.h());
401 }
402
403 #[test]
404 fn get_type_schema_hash_matches_type_constant_hash() {
405 assert_eq!(<u32 as GetType>::schema_hash(), <u32 as GetType>::TYPE.h());
406 assert_eq!(
407 <NamedOrderA as GetType>::schema_hash(),
408 <NamedOrderA as GetType>::TYPE.h()
409 );
410 }
411
412 #[test]
413 fn reordered_named_fields_have_distinct_hashes() {
414 assert_ne!(
415 <NamedOrderA as GetType>::schema_hash(),
416 <NamedOrderB as GetType>::schema_hash()
417 );
418 }
419
420 #[test]
423 fn named_and_unnamed_fields_differ() {
424 use ScalarTyp::*;
425 let named = Typ::Struct(StructType {
426 name: "S",
427 fields: Fields::Named(&[("a", Typ::Scalar(U64)), ("b", Typ::Scalar(Bool))]),
428 });
429 let unnamed = Typ::Struct(StructType {
430 name: "S",
431 fields: Fields::Unnamed(&[Typ::Scalar(U64), Typ::Scalar(Bool)]),
432 });
433 assert_ne!(named.h(), unnamed.h());
434 }
435
436 #[test]
437 fn scalar_variants_are_distinct() {
438 use ScalarTyp::*;
439 assert_ne!(Typ::Scalar(U32).h(), Typ::Scalar(I32).h());
442 assert_ne!(Typ::Scalar(U64).h(), Typ::Scalar(Timestamp).h());
443 }
444
445 #[test]
446 fn enum_variant_discriminant_value_matters() {
447 use ScalarTyp::*;
448 let a = Typ::Enum(EnumType {
449 name: "E",
450 variants: &[(0, ("A", Typ::Scalar(U64)))],
451 });
452 let b = Typ::Enum(EnumType {
453 name: "E",
454 variants: &[(1, ("A", Typ::Scalar(U64)))],
455 });
456 assert_ne!(a.h(), b.h());
457 }
458
459 #[test]
460 fn scalar_and_array_counts_matter() {
461 use ScalarTyp::*;
462 assert_ne!(
463 Typ::Scalar(ArrayBytes(4)).h(),
464 Typ::Scalar(ArrayBytes(8)).h()
465 );
466 assert_ne!(
467 Typ::Array(3, &Typ::Scalar(U64)).h(),
468 Typ::Array(4, &Typ::Scalar(U64)).h()
469 );
470 }
471}