Skip to main content

armour_typ/
typ.rs

1use std::hash::{Hash, Hasher};
2
3use serde::{Serialize, Serializer, ser::SerializeMap};
4use xxhash_rust::xxh3::Xxh3;
5
6use crate::scalar::ScalarTyp;
7
8pub type NamedField = (&'static str, Typ);
9pub type Arr<T> = &'static [T];
10pub type Str = &'static str;
11pub type Map<T> = Arr<(u8, T)>;
12
13#[derive(PartialEq, Clone, Serialize, Debug, Copy, Hash)]
14#[serde(tag = "type", content = "data")]
15pub enum Fields {
16    Named(Arr<NamedField>),
17    Unnamed(Arr<Typ>),
18}
19
20/**
21named structs, unnamed structs and tuples
22если нет названий полей в структуре, то это неименованная структура или кортеж.
23*/
24#[derive(PartialEq, Clone, Serialize, Debug, Copy, Hash)]
25pub struct StructType {
26    pub name: &'static str,
27    pub fields: Fields,
28}
29
30fn variants<S, T>(variants: Map<T>, serializer: S) -> Result<S::Ok, S::Error>
31where
32    S: Serializer,
33    T: Serialize,
34{
35    let mut map = serializer.serialize_map(Some(variants.len()))?;
36    for (key, value) in variants {
37        map.serialize_entry(key, value)?;
38    }
39    map.end()
40}
41
42#[derive(PartialEq, Clone, Serialize, Debug, Copy, Hash)]
43pub struct EnumType {
44    pub name: Str,
45    #[serde(serialize_with = "variants")]
46    pub variants: Map<NamedField>,
47}
48
49#[derive(PartialEq, Eq, Clone, Serialize, Debug, Copy, Hash)]
50pub struct SimpleEnumType {
51    pub name: Str,
52    #[serde(serialize_with = "variants")]
53    pub variants: Map<Str>,
54}
55
56#[derive(PartialEq, Clone, Debug, Copy, Hash)]
57pub enum Typ {
58    Scalar(ScalarTyp),
59    Array(u32, &'static Typ),
60    Vec(&'static Typ),
61    Optional(&'static Typ),
62    SimpleEnum(SimpleEnumType),
63    Struct(StructType),
64    Enum(EnumType),
65    Custom(&'static str, &'static [Typ]),
66}
67
68/// Composite-only mirror for serde; leaves serialize via [`ScalarTyp`] (flat wire).
69#[derive(Serialize)]
70#[serde(tag = "type", content = "data")]
71enum TypComposite {
72    Array(u32, &'static Typ),
73    Vec(&'static Typ),
74    Optional(&'static Typ),
75    SimpleEnum(SimpleEnumType),
76    Struct(StructType),
77    Enum(EnumType),
78    Custom(&'static str, &'static [Typ]),
79}
80
81impl Serialize for Typ {
82    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
83        match self {
84            Typ::Scalar(s) => s.serialize(serializer),
85            Typ::Array(n, inner) => TypComposite::Array(*n, inner).serialize(serializer),
86            Typ::Vec(inner) => TypComposite::Vec(inner).serialize(serializer),
87            Typ::Optional(inner) => TypComposite::Optional(inner).serialize(serializer),
88            Typ::SimpleEnum(e) => TypComposite::SimpleEnum(*e).serialize(serializer),
89            Typ::Struct(s) => TypComposite::Struct(*s).serialize(serializer),
90            Typ::Enum(e) => TypComposite::Enum(*e).serialize(serializer),
91            Typ::Custom(name, args) => TypComposite::Custom(name, args).serialize(serializer),
92        }
93    }
94}
95
96impl Typ {
97    /// Schema hash persisted in armdb (`db.info`/`CollectionInfo`) and checked on
98    /// open. Byte output MUST stay stable; drift is caught by the golden test below.
99    pub fn h(&self) -> u64 {
100        let mut hasher = Xxh3::new();
101        self.hash(&mut hasher);
102        hasher.finish()
103    }
104}
105
106#[cfg(test)]
107mod golden {
108    use super::*;
109    use crate::scalar::ScalarTyp;
110
111    /// One entry per `Typ` variant family. The third field is the expected
112    /// `typ_hash`, captured once from `print_golden` (Step 5). NEVER hand-edit
113    /// these values to silence a failure — a mismatch means the persisted
114    /// schema-hash format drifted and must be investigated.
115    fn golden_cases() -> Vec<(&'static str, Typ, u64)> {
116        use ScalarTyp::*;
117        vec![
118            ("Bool", Typ::Scalar(Bool), 0xd0a66a65c7528968),
119            ("U8", Typ::Scalar(U8), 0x0cfbaea74a8cc0e5),
120            ("U16", Typ::Scalar(U16), 0x59ea21c711c350c2),
121            ("U32", Typ::Scalar(U32), 0xa9c537f75b27a974),
122            ("U64", Typ::Scalar(U64), 0xeb8a271e1e70f1b5),
123            ("I32", Typ::Scalar(I32), 0xace144d934729048),
124            ("I64", Typ::Scalar(I64), 0x280869ef8929d9c9),
125            ("F32", Typ::Scalar(F32), 0xa19e70708438a272),
126            ("F64", Typ::Scalar(F64), 0x843be9b447f64cd1),
127            ("Str", Typ::Scalar(Str), 0xf372327de2e0d2a4),
128            ("Datetime", Typ::Scalar(Datetime), 0x54672872443b2b9b),
129            ("Timestamp", Typ::Scalar(Timestamp), 0xf41318623c6f44b3),
130            ("Decimal", Typ::Scalar(Decimal), 0x04d54e82812b9535),
131            ("Id32", Typ::Scalar(Id32), 0xa1254b871fc40f7c),
132            ("Id64", Typ::Scalar(Id64), 0x08b62024788c9438),
133            ("Fuid", Typ::Scalar(Fuid), 0x6d2bac21c16d425f),
134            ("LowId", Typ::Scalar(LowId), 0xb4e78e7417625641),
135            ("Bytes", Typ::Scalar(Bytes), 0xaeb2d89f37dc9398),
136            ("Void", Typ::Scalar(Void), 0xebae81600548f719),
137            ("RustJson", Typ::Scalar(RustJson), 0x4843696f209475a3),
138            ("JsonBytes", Typ::Scalar(JsonBytes), 0x32c39c1af3fd6a18),
139            ("ArrayBytes", Typ::Scalar(ArrayBytes(4)), 0xdd59821af9eecd6b),
140            (
141                "Array",
142                Typ::Array(3, &Typ::Scalar(U64)),
143                0xd03c6458e1eab738,
144            ),
145            ("Vec", Typ::Vec(&Typ::Scalar(U64)), 0x0b478fcad27d8bf8),
146            (
147                "Optional",
148                Typ::Optional(&Typ::Scalar(U64)),
149                0xb5c98357ef64570e,
150            ),
151            (
152                "SimpleEnum",
153                Typ::SimpleEnum(SimpleEnumType {
154                    name: "SE",
155                    variants: &[(0, "A"), (1, "B")],
156                }),
157                0xb604588033cf6cbf,
158            ),
159            (
160                "StructNamed",
161                Typ::Struct(StructType {
162                    name: "S",
163                    fields: Fields::Named(&[("a", Typ::Scalar(U64)), ("b", Typ::Scalar(Str))]),
164                }),
165                0x34afafe7990e4fe5,
166            ),
167            (
168                "StructUnnamed",
169                Typ::Struct(StructType {
170                    name: "",
171                    fields: Fields::Unnamed(&[Typ::Scalar(U64), Typ::Scalar(Bool)]),
172                }),
173                0x50bb6b127532a167,
174            ),
175            (
176                "Enum",
177                Typ::Enum(EnumType {
178                    name: "E",
179                    variants: &[(0, ("A", Typ::Scalar(U64))), (1, ("B", Typ::Scalar(Bool)))],
180                }),
181                0xf06c341aa43fa98a,
182            ),
183            (
184                "Custom",
185                Typ::Custom("C", &[Typ::Scalar(U64), Typ::Scalar(Str)]),
186                0xb9d53c9e69fcd4c5,
187            ),
188        ]
189    }
190
191    /// Run explicitly to (re)generate golden values:
192    /// `cargo test -p armour-typ golden::print_golden -- --ignored --nocapture`
193    #[test]
194    #[ignore]
195    fn print_golden() {
196        for (name, t, _) in golden_cases() {
197            println!("(\"{name}\", …, 0x{:016x}),", t.h());
198        }
199    }
200
201    #[test]
202    fn golden_typ_hash() {
203        for (name, t, expected) in golden_cases() {
204            assert_eq!(t.h(), expected, "typ_hash drift for variant `{name}`");
205        }
206    }
207
208    /// Exhaustiveness: new `ScalarTyp` variant fails to compile without a golden case.
209    #[allow(dead_code)]
210    fn _scalar_guard(s: &ScalarTyp) {
211        use ScalarTyp::*;
212        match s {
213            Bool | U8 | U16 | U32 | U64 | I32 | I64 | F32 | F64 | Str | Datetime | Timestamp
214            | Decimal | Id32 | Id64 | Fuid | LowId | Bytes | Void | RustJson | JsonBytes => {}
215            ArrayBytes(_) => {}
216        }
217    }
218
219    /// Exhaustiveness: new composite `Typ` variant fails to compile without a golden case.
220    #[allow(dead_code)]
221    fn _typ_guard(t: &Typ) {
222        match t {
223            Typ::Scalar(_) => {}
224            Typ::Array(_, _) => {}
225            Typ::Vec(_) => {}
226            Typ::Optional(_) => {}
227            Typ::SimpleEnum(_) => {}
228            Typ::Struct(_) => {}
229            Typ::Enum(_) => {}
230            Typ::Custom(_, _) => {}
231        }
232    }
233}