Skip to main content

armour_typ/
schema.rs

1use crate::scalar::ScalarTyp;
2use crate::typ::{Fields, Typ};
3
4/// Owned runtime mirror of `Typ`. Reuses `ScalarTyp`.
5#[derive(PartialEq, Eq, Clone, Debug)]
6pub enum SchemaTyp {
7    Scalar(ScalarTyp),
8    Array(u32, Box<SchemaTyp>),
9    Vec(Box<SchemaTyp>),
10    Optional(Box<SchemaTyp>),
11    SimpleEnum {
12        name: String,
13        variants: Vec<(u8, String)>,
14    },
15    Struct {
16        name: String,
17        fields: SchemaFields,
18    },
19    Enum {
20        name: String,
21        variants: Vec<(u8, String, SchemaTyp)>,
22    },
23    Custom(String, Vec<SchemaTyp>),
24}
25
26#[derive(PartialEq, Eq, Clone, Debug)]
27pub enum SchemaFields {
28    Named(Vec<(String, SchemaTyp)>),
29    Unnamed(Vec<SchemaTyp>),
30}
31
32impl From<&Typ> for SchemaTyp {
33    fn from(t: &Typ) -> Self {
34        match t {
35            Typ::Scalar(s) => SchemaTyp::Scalar(*s),
36            Typ::Array(n, inner) => SchemaTyp::Array(*n, Box::new(SchemaTyp::from(*inner))),
37            Typ::Vec(inner) => SchemaTyp::Vec(Box::new(SchemaTyp::from(*inner))),
38            Typ::Optional(inner) => SchemaTyp::Optional(Box::new(SchemaTyp::from(*inner))),
39            Typ::SimpleEnum(e) => SchemaTyp::SimpleEnum {
40                name: e.name.to_string(),
41                variants: e
42                    .variants
43                    .iter()
44                    .map(|(i, n)| (*i, n.to_string()))
45                    .collect(),
46            },
47            Typ::Struct(s) => SchemaTyp::Struct {
48                name: s.name.to_string(),
49                fields: match s.fields {
50                    Fields::Named(fs) => SchemaFields::Named(
51                        fs.iter()
52                            .map(|(n, ty)| (n.to_string(), SchemaTyp::from(ty)))
53                            .collect(),
54                    ),
55                    Fields::Unnamed(fs) => {
56                        SchemaFields::Unnamed(fs.iter().map(SchemaTyp::from).collect())
57                    }
58                },
59            },
60            Typ::Enum(e) => SchemaTyp::Enum {
61                name: e.name.to_string(),
62                variants: e
63                    .variants
64                    .iter()
65                    .map(|(i, (n, ty))| (*i, n.to_string(), SchemaTyp::from(ty)))
66                    .collect(),
67            },
68            Typ::Custom(name, items) => SchemaTyp::Custom(
69                name.to_string(),
70                items.iter().map(SchemaTyp::from).collect(),
71            ),
72        }
73    }
74}
75
76/// Exhaustiveness guard (spec §11): a new composite variant in `Typ` must be
77/// mirrored here in `SchemaTyp`, otherwise this match fails to compile. Pairs
78/// with the compiler-enforced `From<&Typ>` match above (which guards the
79/// `Typ → SchemaTyp` direction) to keep both enums in lockstep.
80#[cfg(test)]
81#[allow(dead_code)]
82fn _schema_guard(s: &SchemaTyp) {
83    match s {
84        SchemaTyp::Scalar(_) => {}
85        SchemaTyp::Array(_, _) => {}
86        SchemaTyp::Vec(_) => {}
87        SchemaTyp::Optional(_) => {}
88        SchemaTyp::SimpleEnum { .. } => {}
89        SchemaTyp::Struct { .. } => {}
90        SchemaTyp::Enum { .. } => {}
91        SchemaTyp::Custom(_, _) => {}
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use crate::scalar::ScalarTyp;
99    use crate::typ::{Fields, StructType, Typ};
100
101    #[test]
102    fn from_typ_struct_roundtrips_shape() {
103        static T: Typ = Typ::Struct(StructType {
104            name: "S",
105            fields: Fields::Named(&[
106                ("a", Typ::Scalar(ScalarTyp::U64)),
107                ("b", Typ::Vec(&Typ::Scalar(ScalarTyp::Str))),
108            ]),
109        });
110        let s = SchemaTyp::from(&T);
111        let expected = SchemaTyp::Struct {
112            name: "S".to_string(),
113            fields: SchemaFields::Named(vec![
114                ("a".to_string(), SchemaTyp::Scalar(ScalarTyp::U64)),
115                (
116                    "b".to_string(),
117                    SchemaTyp::Vec(Box::new(SchemaTyp::Scalar(ScalarTyp::Str))),
118                ),
119            ]),
120        };
121        assert_eq!(s, expected);
122    }
123
124    #[test]
125    fn from_typ_covers_scalars_and_composites() {
126        assert_eq!(
127            SchemaTyp::from(&Typ::Scalar(ScalarTyp::Fuid)),
128            SchemaTyp::Scalar(ScalarTyp::Fuid)
129        );
130        assert_eq!(
131            SchemaTyp::from(&Typ::Optional(&Typ::Scalar(ScalarTyp::Id64))),
132            SchemaTyp::Optional(Box::new(SchemaTyp::Scalar(ScalarTyp::Id64)))
133        );
134        assert_eq!(
135            SchemaTyp::from(&Typ::Custom("c", &[Typ::Scalar(ScalarTyp::U8)])),
136            SchemaTyp::Custom("c".to_string(), vec![SchemaTyp::Scalar(ScalarTyp::U8)])
137        );
138    }
139}