rapira 0.13.3

serialization library like borsh, bincode and speedy
Documentation
#![cfg(feature = "schema")]

use armour_typ::{ScalarTyp, SchemaFields, SchemaTyp};
use rapira::{RapiraError, check_bytes, deserialize, max_cap::VEC_MAX_CAP, serialize};

fn sample_schema() -> SchemaTyp {
    SchemaTyp::Struct {
        name: "MessageView".to_string(),
        fields: SchemaFields::Named(vec![
            ("id".to_string(), SchemaTyp::Scalar(ScalarTyp::Fuid)),
            (
                "content".to_string(),
                SchemaTyp::Enum {
                    name: "MessageContent".to_string(),
                    variants: vec![
                        (0, "Text".to_string(), SchemaTyp::Scalar(ScalarTyp::Str)),
                        (
                            1,
                            "Image".to_string(),
                            SchemaTyp::Struct {
                                name: "ImageRef".to_string(),
                                fields: SchemaFields::Unnamed(vec![
                                    SchemaTyp::Scalar(ScalarTyp::Bytes),
                                    SchemaTyp::Optional(Box::new(SchemaTyp::Scalar(
                                        ScalarTyp::Str,
                                    ))),
                                ]),
                            },
                        ),
                    ],
                },
            ),
            (
                "attachments".to_string(),
                SchemaTyp::Vec(Box::new(SchemaTyp::Custom(
                    "Attachment".to_string(),
                    vec![SchemaTyp::Scalar(ScalarTyp::U64)],
                ))),
            ),
        ]),
    }
}

#[test]
fn schema_typ_roundtrips_all_composites() {
    let cases = vec![
        SchemaTyp::Scalar(ScalarTyp::ArrayBytes(32)),
        SchemaTyp::Array(3, Box::new(SchemaTyp::Scalar(ScalarTyp::U16))),
        SchemaTyp::Vec(Box::new(SchemaTyp::Scalar(ScalarTyp::Str))),
        SchemaTyp::Optional(Box::new(SchemaTyp::Scalar(ScalarTyp::RustJson))),
        SchemaTyp::SimpleEnum {
            name: "Kind".to_string(),
            variants: vec![(0, "A".to_string()), (1, "B".to_string())],
        },
        SchemaTyp::Struct {
            name: "Pair".to_string(),
            fields: SchemaFields::Unnamed(vec![
                SchemaTyp::Scalar(ScalarTyp::U32),
                SchemaTyp::Scalar(ScalarTyp::U64),
            ]),
        },
        sample_schema(),
    ];

    for schema in cases {
        let bytes = serialize(&schema);
        let decoded: SchemaTyp = deserialize(&bytes).unwrap();
        assert_eq!(decoded, schema);
    }
}

#[test]
fn schema_fields_roundtrip_named_and_unnamed() {
    let named = SchemaFields::Named(vec![
        ("a".to_string(), SchemaTyp::Scalar(ScalarTyp::U32)),
        ("b".to_string(), SchemaTyp::Scalar(ScalarTyp::Bytes)),
    ]);
    let unnamed = SchemaFields::Unnamed(vec![
        SchemaTyp::Scalar(ScalarTyp::Bool),
        SchemaTyp::Scalar(ScalarTyp::Decimal),
    ]);

    assert_eq!(
        deserialize::<SchemaFields>(&serialize(&named)).unwrap(),
        named
    );
    assert_eq!(
        deserialize::<SchemaFields>(&serialize(&unnamed)).unwrap(),
        unnamed
    );
}

#[test]
fn large_schema_wire_size_is_kilobyte_scale() {
    let mut fields = Vec::new();
    for i in 0..128 {
        fields.push((
            format!("field_{i}"),
            SchemaTyp::Vec(Box::new(SchemaTyp::Struct {
                name: "Nested".to_string(),
                fields: SchemaFields::Named(vec![
                    ("id".to_string(), SchemaTyp::Scalar(ScalarTyp::Fuid)),
                    ("body".to_string(), SchemaTyp::Scalar(ScalarTyp::Str)),
                ]),
            })),
        ));
    }
    let schema = SchemaTyp::Struct {
        name: "Large".to_string(),
        fields: SchemaFields::Named(fields),
    };

    let bytes = serialize(&schema);
    assert!(
        bytes.len() > 4 * 1024,
        "large fixture must actually be large"
    );
    assert!(
        bytes.len() < 128 * 1024,
        "schema wire size should stay KB-scale"
    );
    assert_eq!(deserialize::<SchemaTyp>(&bytes).unwrap(), schema);
}

#[test]
fn scalar_wire_format_is_stable_for_u32() {
    assert_eq!(serialize(&ScalarTyp::U32), vec![3]);
}

#[test]
fn scalar_wire_format_is_stable_for_array_bytes() {
    assert_eq!(serialize(&ScalarTyp::ArrayBytes(32)), vec![21, 32, 0, 0, 0]);
}

#[test]
fn schema_fields_wire_tags_are_stable() {
    assert_eq!(serialize(&SchemaFields::Named(vec![])), vec![0, 0, 0, 0, 0]);
    assert_eq!(
        serialize(&SchemaFields::Unnamed(vec![])),
        vec![1, 0, 0, 0, 0]
    );
}

#[test]
fn schema_typ_wire_tags_are_stable() {
    assert_eq!(first_tag(serialize(&SchemaTyp::Scalar(ScalarTyp::U8))), 0);
    assert_eq!(
        first_tag(serialize(&SchemaTyp::Array(
            1,
            Box::new(SchemaTyp::Scalar(ScalarTyp::U16))
        ))),
        1
    );
    assert_eq!(
        first_tag(serialize(&SchemaTyp::Vec(Box::new(SchemaTyp::Scalar(
            ScalarTyp::Str
        ))))),
        2
    );
    assert_eq!(
        first_tag(serialize(&SchemaTyp::Optional(Box::new(
            SchemaTyp::Scalar(ScalarTyp::Str)
        )))),
        3
    );
    assert_eq!(
        first_tag(serialize(&SchemaTyp::SimpleEnum {
            name: "Kind".to_string(),
            variants: vec![]
        })),
        4
    );
    assert_eq!(
        first_tag(serialize(&SchemaTyp::Struct {
            name: "Pair".to_string(),
            fields: SchemaFields::Named(vec![]),
        })),
        5
    );
    assert_eq!(
        first_tag(serialize(&SchemaTyp::Enum {
            name: "Kind".to_string(),
            variants: vec![],
        })),
        6
    );
    assert_eq!(
        first_tag(serialize(&SchemaTyp::Custom("C".to_string(), vec![]))),
        7
    );
}

#[test]
fn schema_typ_check_bytes_rejects_unknown_scalar_tag() {
    let mut bytes = serialize(&SchemaTyp::Scalar(ScalarTyp::U32));
    bytes[1] = 255;

    let mut slice = bytes.as_slice();
    assert!(matches!(
        check_bytes::<SchemaTyp>(&mut slice),
        Err(RapiraError::EnumVariant)
    ));
}

#[test]
fn schema_typ_from_slice_rejects_excessive_simple_enum_variant_len() {
    let mut bytes = serialize(&SchemaTyp::SimpleEnum {
        name: "Kind".to_string(),
        variants: Vec::new(),
    });
    let len_pos = bytes.len() - 4;
    let too_many = VEC_MAX_CAP as u32 + 1;
    bytes[len_pos..].copy_from_slice(&too_many.to_le_bytes());

    let mut slice = bytes.as_slice();
    assert!(matches!(
        check_bytes::<SchemaTyp>(&mut slice),
        Err(RapiraError::MaxCapacity)
    ));
    assert!(matches!(
        deserialize::<SchemaTyp>(&bytes),
        Err(RapiraError::MaxCapacity)
    ));
}

#[test]
fn schema_typ_from_slice_rejects_excessive_enum_variant_len() {
    let mut bytes = serialize(&SchemaTyp::Enum {
        name: "Kind".to_string(),
        variants: Vec::new(),
    });
    let len_pos = bytes.len() - 4;
    let too_many = VEC_MAX_CAP as u32 + 1;
    bytes[len_pos..].copy_from_slice(&too_many.to_le_bytes());

    let mut slice = bytes.as_slice();
    assert!(matches!(
        check_bytes::<SchemaTyp>(&mut slice),
        Err(RapiraError::MaxCapacity)
    ));
    assert!(matches!(
        deserialize::<SchemaTyp>(&bytes),
        Err(RapiraError::MaxCapacity)
    ));
}

fn first_tag(bytes: Vec<u8>) -> u8 {
    bytes[0]
}