use serde::{Serialize, Serializer, ser::SerializeMap};
use xxhash_rust::xxh3::Xxh3;
use crate::scalar::ScalarTyp;
pub type NamedField = (&'static str, Typ);
pub type Arr<T> = &'static [T];
pub type Str = &'static str;
pub type Map<T> = Arr<(u8, T)>;
#[derive(PartialEq, Clone, Serialize, Debug, Copy, Hash)]
#[serde(tag = "type", content = "data")]
pub enum Fields {
Named(Arr<NamedField>),
Unnamed(Arr<Typ>),
}
#[derive(PartialEq, Clone, Serialize, Debug, Copy, Hash)]
pub struct StructType {
pub name: &'static str,
pub fields: Fields,
}
fn variants<S, T>(variants: Map<T>, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Serialize,
{
let mut map = serializer.serialize_map(Some(variants.len()))?;
for (key, value) in variants {
map.serialize_entry(key, value)?;
}
map.end()
}
#[derive(PartialEq, Clone, Serialize, Debug, Copy, Hash)]
pub struct EnumType {
pub name: Str,
#[serde(serialize_with = "variants")]
pub variants: Map<NamedField>,
}
#[derive(PartialEq, Eq, Clone, Serialize, Debug, Copy, Hash)]
pub struct SimpleEnumType {
pub name: Str,
#[serde(serialize_with = "variants")]
pub variants: Map<Str>,
}
#[derive(PartialEq, Clone, Debug, Copy, Hash)]
pub enum Typ {
Scalar(ScalarTyp),
Array(u32, &'static Typ),
Vec(&'static Typ),
Optional(&'static Typ),
SimpleEnum(SimpleEnumType),
Struct(StructType),
Enum(EnumType),
Custom(&'static str, &'static [Typ]),
}
#[derive(Serialize)]
#[serde(tag = "type", content = "data")]
enum TypComposite {
Array(u32, &'static Typ),
Vec(&'static Typ),
Optional(&'static Typ),
SimpleEnum(SimpleEnumType),
Struct(StructType),
Enum(EnumType),
Custom(&'static str, &'static [Typ]),
}
impl Serialize for Typ {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
match self {
Typ::Scalar(s) => s.serialize(serializer),
Typ::Array(n, inner) => TypComposite::Array(*n, inner).serialize(serializer),
Typ::Vec(inner) => TypComposite::Vec(inner).serialize(serializer),
Typ::Optional(inner) => TypComposite::Optional(inner).serialize(serializer),
Typ::SimpleEnum(e) => TypComposite::SimpleEnum(*e).serialize(serializer),
Typ::Struct(s) => TypComposite::Struct(*s).serialize(serializer),
Typ::Enum(e) => TypComposite::Enum(*e).serialize(serializer),
Typ::Custom(name, args) => TypComposite::Custom(name, args).serialize(serializer),
}
}
}
const HASH_FORMAT_TAG: &[u8] = b"armour-typ/typ/v1";
#[inline]
fn hash_len(hasher: &mut Xxh3, len: usize) {
hasher.update(&(len as u64).to_le_bytes());
}
#[inline]
fn hash_str(hasher: &mut Xxh3, value: &str) {
hash_len(hasher, value.len());
hasher.update(value.as_bytes());
}
fn hash_scalar(hasher: &mut Xxh3, scalar: &ScalarTyp) {
use ScalarTyp::*;
let tag: u8 = match scalar {
Bool => 0,
U8 => 1,
U16 => 2,
U32 => 3,
U64 => 4,
I32 => 5,
I64 => 6,
F32 => 7,
F64 => 8,
Str => 9,
Datetime => 10,
Timestamp => 11,
Decimal => 12,
Id32 => 13,
Id64 => 14,
Fuid => 15,
LowId => 16,
Bytes => 17,
Void => 18,
RustJson => 19,
JsonBytes => 20,
ArrayBytes(_) => 21,
};
hasher.update(&[tag]);
if let ArrayBytes(n) = scalar {
hasher.update(&n.to_le_bytes());
}
}
fn hash_fields(hasher: &mut Xxh3, fields: &Fields) {
match fields {
Fields::Named(items) => {
hasher.update(&[0u8]);
hash_len(hasher, items.len());
for (name, typ) in *items {
hash_str(hasher, name);
hash_typ(hasher, typ);
}
}
Fields::Unnamed(items) => {
hasher.update(&[1u8]);
hash_len(hasher, items.len());
for typ in *items {
hash_typ(hasher, typ);
}
}
}
}
fn hash_typ(hasher: &mut Xxh3, typ: &Typ) {
match typ {
Typ::Scalar(s) => {
hasher.update(&[0u8]);
hash_scalar(hasher, s);
}
Typ::Array(n, inner) => {
hasher.update(&[1u8]);
hasher.update(&n.to_le_bytes());
hash_typ(hasher, inner);
}
Typ::Vec(inner) => {
hasher.update(&[2u8]);
hash_typ(hasher, inner);
}
Typ::Optional(inner) => {
hasher.update(&[3u8]);
hash_typ(hasher, inner);
}
Typ::SimpleEnum(e) => {
hasher.update(&[4u8]);
hash_str(hasher, e.name);
hash_len(hasher, e.variants.len());
for (discr, name) in e.variants {
hasher.update(&[*discr]);
hash_str(hasher, name);
}
}
Typ::Struct(s) => {
hasher.update(&[5u8]);
hash_str(hasher, s.name);
hash_fields(hasher, &s.fields);
}
Typ::Enum(e) => {
hasher.update(&[6u8]);
hash_str(hasher, e.name);
hash_len(hasher, e.variants.len());
for (discr, (name, payload)) in e.variants {
hasher.update(&[*discr]);
hash_str(hasher, name);
hash_typ(hasher, payload);
}
}
Typ::Custom(name, args) => {
hasher.update(&[7u8]);
hash_str(hasher, name);
hash_len(hasher, args.len());
for arg in *args {
hash_typ(hasher, arg);
}
}
}
}
impl Typ {
pub fn h(&self) -> u64 {
let mut hasher = Xxh3::new();
hasher.update(HASH_FORMAT_TAG);
hash_typ(&mut hasher, self);
hasher.digest()
}
}
#[inline]
pub fn schema_hash(t: &Typ) -> u64 {
t.h()
}
#[cfg(test)]
mod golden {
use super::*;
use crate::scalar::ScalarTyp;
fn golden_cases() -> Vec<(&'static str, Typ, u64)> {
use ScalarTyp::*;
vec![
("Bool", Typ::Scalar(Bool), 0x844969503cd52142),
("U8", Typ::Scalar(U8), 0xc8cadc679de83264),
("U16", Typ::Scalar(U16), 0x31f140c9e9d24a6a),
("U32", Typ::Scalar(U32), 0xb6e382b85cc8ad16),
("U64", Typ::Scalar(U64), 0x3e2513ed9422dbf3),
("I32", Typ::Scalar(I32), 0x3eed686985c17e30),
("I64", Typ::Scalar(I64), 0x6dcdfb5efac1eff5),
("F32", Typ::Scalar(F32), 0xd4256e6355df3aa6),
("F64", Typ::Scalar(F64), 0x25b32f6d7a5db6d7),
("Str", Typ::Scalar(Str), 0x2aa6e56e9ce67c6d),
("Datetime", Typ::Scalar(Datetime), 0xf789bd4fa70b626b),
("Timestamp", Typ::Scalar(Timestamp), 0x31205013816d027c),
("Decimal", Typ::Scalar(Decimal), 0x8bda406d6c8e133b),
("Id32", Typ::Scalar(Id32), 0x30e0670bf6dea824),
("Id64", Typ::Scalar(Id64), 0xc1c4537aede104ae),
("Fuid", Typ::Scalar(Fuid), 0x313781a3f9e8ffbb),
("LowId", Typ::Scalar(LowId), 0x09b9aaafe34aeb45),
("Bytes", Typ::Scalar(Bytes), 0x08276244715f6ff1),
("Void", Typ::Scalar(Void), 0xed774f18fb9a6e91),
("RustJson", Typ::Scalar(RustJson), 0x3874a1693f60d5d7),
("JsonBytes", Typ::Scalar(JsonBytes), 0xb4ccb5d58b3e668b),
("ArrayBytes", Typ::Scalar(ArrayBytes(4)), 0x58f9ebfb19da8930),
(
"Array",
Typ::Array(3, &Typ::Scalar(U64)),
0xe8fb2cd993687efe,
),
("Vec", Typ::Vec(&Typ::Scalar(U64)), 0x0def01338fe2656c),
(
"Optional",
Typ::Optional(&Typ::Scalar(U64)),
0xa03ca0cb992f08da,
),
(
"SimpleEnum",
Typ::SimpleEnum(SimpleEnumType {
name: "SE",
variants: &[(0, "A"), (1, "B")],
}),
0x52315acdab3124db,
),
(
"StructNamed",
Typ::Struct(StructType {
name: "S",
fields: Fields::Named(&[("a", Typ::Scalar(U64)), ("b", Typ::Scalar(Str))]),
}),
0xa453f5fe90303c85,
),
(
"StructUnnamed",
Typ::Struct(StructType {
name: "",
fields: Fields::Unnamed(&[Typ::Scalar(U64), Typ::Scalar(Bool)]),
}),
0x067f1d66238d5e94,
),
(
"Enum",
Typ::Enum(EnumType {
name: "E",
variants: &[(0, ("A", Typ::Scalar(U64))), (1, ("B", Typ::Scalar(Bool)))],
}),
0x9d717d3549fb9528,
),
(
"Custom",
Typ::Custom("C", &[Typ::Scalar(U64), Typ::Scalar(Str)]),
0x04ff19c55360b278,
),
]
}
#[test]
#[ignore]
fn print_golden() {
for (name, t, _) in golden_cases() {
println!("(\"{name}\", …, 0x{:016x}),", t.h());
}
}
#[test]
fn golden_typ_hash() {
for (name, t, expected) in golden_cases() {
assert_eq!(t.h(), expected, "typ_hash drift for variant `{name}`");
}
}
#[allow(dead_code)]
fn _scalar_guard(s: &ScalarTyp) {
use ScalarTyp::*;
match s {
Bool | U8 | U16 | U32 | U64 | I32 | I64 | F32 | F64 | Str | Datetime | Timestamp
| Decimal | Id32 | Id64 | Fuid | LowId | Bytes | Void | RustJson | JsonBytes => {}
ArrayBytes(_) => {}
}
}
#[allow(dead_code)]
fn _typ_guard(t: &Typ) {
match t {
Typ::Scalar(_) => {}
Typ::Array(_, _) => {}
Typ::Vec(_) => {}
Typ::Optional(_) => {}
Typ::SimpleEnum(_) => {}
Typ::Struct(_) => {}
Typ::Enum(_) => {}
Typ::Custom(_, _) => {}
}
}
}
#[cfg(test)]
#[allow(dead_code)]
mod schema_hash_tests {
use super::*;
use crate::get_type::GetType;
use crate::scalar::ScalarTyp;
#[derive(crate::GetType)]
struct NamedOrderA {
first: u32,
second: u64,
}
#[derive(crate::GetType)]
struct NamedOrderB {
second: u64,
first: u32,
}
#[test]
fn free_schema_hash_matches_typ_h() {
let typ = Typ::Vec(&Typ::Scalar(ScalarTyp::U64));
assert_eq!(schema_hash(&typ), typ.h());
}
#[test]
fn get_type_schema_hash_matches_type_constant_hash() {
assert_eq!(<u32 as GetType>::schema_hash(), <u32 as GetType>::TYPE.h());
assert_eq!(
<NamedOrderA as GetType>::schema_hash(),
<NamedOrderA as GetType>::TYPE.h()
);
}
#[test]
fn reordered_named_fields_have_distinct_hashes() {
assert_ne!(
<NamedOrderA as GetType>::schema_hash(),
<NamedOrderB as GetType>::schema_hash()
);
}
#[test]
fn named_and_unnamed_fields_differ() {
use ScalarTyp::*;
let named = Typ::Struct(StructType {
name: "S",
fields: Fields::Named(&[("a", Typ::Scalar(U64)), ("b", Typ::Scalar(Bool))]),
});
let unnamed = Typ::Struct(StructType {
name: "S",
fields: Fields::Unnamed(&[Typ::Scalar(U64), Typ::Scalar(Bool)]),
});
assert_ne!(named.h(), unnamed.h());
}
#[test]
fn scalar_variants_are_distinct() {
use ScalarTyp::*;
assert_ne!(Typ::Scalar(U32).h(), Typ::Scalar(I32).h());
assert_ne!(Typ::Scalar(U64).h(), Typ::Scalar(Timestamp).h());
}
#[test]
fn enum_variant_discriminant_value_matters() {
use ScalarTyp::*;
let a = Typ::Enum(EnumType {
name: "E",
variants: &[(0, ("A", Typ::Scalar(U64)))],
});
let b = Typ::Enum(EnumType {
name: "E",
variants: &[(1, ("A", Typ::Scalar(U64)))],
});
assert_ne!(a.h(), b.h());
}
#[test]
fn scalar_and_array_counts_matter() {
use ScalarTyp::*;
assert_ne!(
Typ::Scalar(ArrayBytes(4)).h(),
Typ::Scalar(ArrayBytes(8)).h()
);
assert_ne!(
Typ::Array(3, &Typ::Scalar(U64)).h(),
Typ::Array(4, &Typ::Scalar(U64)).h()
);
}
}