armour-typ 0.1.0

Shared schema/type descriptors (Typ, ScalarTyp, GetType) for the armour ecosystem
Documentation
use crate::scalar::ScalarTyp;
use crate::typ::{Fields, Typ};

/// Owned runtime mirror of `Typ`. Reuses `ScalarTyp`. Deserializable
/// (`impl Rapira` — in the rapira crate, Plan 2).
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum SchemaTyp {
    Scalar(ScalarTyp),
    Array(u32, Box<SchemaTyp>),
    Vec(Box<SchemaTyp>),
    Optional(Box<SchemaTyp>),
    SimpleEnum {
        name: String,
        variants: Vec<(u8, String)>,
    },
    Struct {
        name: String,
        fields: SchemaFields,
    },
    Enum {
        name: String,
        variants: Vec<(u8, String, SchemaTyp)>,
    },
    Custom(String, Vec<SchemaTyp>),
}

#[derive(PartialEq, Eq, Clone, Debug)]
pub enum SchemaFields {
    Named(Vec<(String, SchemaTyp)>),
    Unnamed(Vec<SchemaTyp>),
}

impl From<&Typ> for SchemaTyp {
    fn from(t: &Typ) -> Self {
        match t {
            Typ::Scalar(s) => SchemaTyp::Scalar(*s),
            Typ::Array(n, inner) => SchemaTyp::Array(*n, Box::new(SchemaTyp::from(*inner))),
            Typ::Vec(inner) => SchemaTyp::Vec(Box::new(SchemaTyp::from(*inner))),
            Typ::Optional(inner) => SchemaTyp::Optional(Box::new(SchemaTyp::from(*inner))),
            Typ::SimpleEnum(e) => SchemaTyp::SimpleEnum {
                name: e.name.to_string(),
                variants: e
                    .variants
                    .iter()
                    .map(|(i, n)| (*i, n.to_string()))
                    .collect(),
            },
            Typ::Struct(s) => SchemaTyp::Struct {
                name: s.name.to_string(),
                fields: match s.fields {
                    Fields::Named(fs) => SchemaFields::Named(
                        fs.iter()
                            .map(|(n, ty)| (n.to_string(), SchemaTyp::from(ty)))
                            .collect(),
                    ),
                    Fields::Unnamed(fs) => {
                        SchemaFields::Unnamed(fs.iter().map(SchemaTyp::from).collect())
                    }
                },
            },
            Typ::Enum(e) => SchemaTyp::Enum {
                name: e.name.to_string(),
                variants: e
                    .variants
                    .iter()
                    .map(|(i, (n, ty))| (*i, n.to_string(), SchemaTyp::from(ty)))
                    .collect(),
            },
            Typ::Custom(name, items) => SchemaTyp::Custom(
                name.to_string(),
                items.iter().map(SchemaTyp::from).collect(),
            ),
        }
    }
}

/// Exhaustiveness guard (spec §11): a new composite variant in `Typ` must be
/// mirrored here in `SchemaTyp`, otherwise this match fails to compile. Pairs
/// with the compiler-enforced `From<&Typ>` match above (which guards the
/// `Typ → SchemaTyp` direction) to keep both enums in lockstep.
#[cfg(test)]
#[allow(dead_code)]
fn _schema_guard(s: &SchemaTyp) {
    match s {
        SchemaTyp::Scalar(_) => {}
        SchemaTyp::Array(_, _) => {}
        SchemaTyp::Vec(_) => {}
        SchemaTyp::Optional(_) => {}
        SchemaTyp::SimpleEnum { .. } => {}
        SchemaTyp::Struct { .. } => {}
        SchemaTyp::Enum { .. } => {}
        SchemaTyp::Custom(_, _) => {}
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::scalar::ScalarTyp;
    use crate::typ::{Fields, StructType, Typ};

    #[test]
    fn from_typ_struct_roundtrips_shape() {
        static T: Typ = Typ::Struct(StructType {
            name: "S",
            fields: Fields::Named(&[
                ("a", Typ::Scalar(ScalarTyp::U64)),
                ("b", Typ::Vec(&Typ::Scalar(ScalarTyp::Str))),
            ]),
        });
        let s = SchemaTyp::from(&T);
        let expected = SchemaTyp::Struct {
            name: "S".to_string(),
            fields: SchemaFields::Named(vec![
                ("a".to_string(), SchemaTyp::Scalar(ScalarTyp::U64)),
                (
                    "b".to_string(),
                    SchemaTyp::Vec(Box::new(SchemaTyp::Scalar(ScalarTyp::Str))),
                ),
            ]),
        };
        assert_eq!(s, expected);
    }

    #[test]
    fn from_typ_covers_scalars_and_composites() {
        assert_eq!(
            SchemaTyp::from(&Typ::Scalar(ScalarTyp::Fuid)),
            SchemaTyp::Scalar(ScalarTyp::Fuid)
        );
        assert_eq!(
            SchemaTyp::from(&Typ::Optional(&Typ::Scalar(ScalarTyp::Id64))),
            SchemaTyp::Optional(Box::new(SchemaTyp::Scalar(ScalarTyp::Id64)))
        );
        assert_eq!(
            SchemaTyp::from(&Typ::Custom("c", &[Typ::Scalar(ScalarTyp::U8)])),
            SchemaTyp::Custom("c".to_string(), vec![SchemaTyp::Scalar(ScalarTyp::U8)])
        );
    }
}