#![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]
}