#![cfg(feature = "schema")]
use armour_typ::scalar::ScalarTyp;
use armour_typ::typ::{Fields, StructType, Typ};
use armour_typ::{GetType, SchemaFields, SchemaTyp};
use rapira::schema::{ShapeRel, shape_compat, skip_value};
use rapira::{Rapira, RapiraError, RapiraFlags, serialize};
#[derive(Debug, PartialEq, Default)]
struct OldMsg {
a: u32,
}
impl GetType for OldMsg {
const TYPE: Typ = Typ::Struct(StructType {
name: "Msg",
fields: Fields::Named(&[("a", Typ::Scalar(ScalarTyp::U32))]),
});
}
impl Rapira for OldMsg {
const MIN_SIZE: usize = 4;
fn size(&self) -> usize {
4
}
fn check_bytes(slice: &mut &[u8]) -> rapira::Result<()> {
u32::check_bytes(slice)
}
fn from_slice(slice: &mut &[u8]) -> rapira::Result<Self> {
Ok(OldMsg {
a: u32::from_slice(slice)?,
})
}
fn convert_to_bytes(&self, slice: &mut [u8], cursor: &mut usize) {
self.a.convert_to_bytes(slice, cursor);
}
fn from_slice_with_peer_ctx(
slice: &mut &[u8],
peer: &SchemaTyp,
flags: RapiraFlags,
) -> rapira::Result<Self> {
let SchemaTyp::Struct {
name,
fields: SchemaFields::Named(pf),
} = peer
else {
return Err(RapiraError::SchemaMismatch);
};
if name != "Msg" {
return Err(RapiraError::SchemaMismatch);
}
let mut temp_slice = *slice;
let mut out = OldMsg::default();
let mut seen_a = false;
for (pname, pty) in pf {
match pname.as_str() {
"a" => {
match shape_compat(&Typ::Scalar(ScalarTyp::U32), pty) {
ShapeRel::Exact => out.a = u32::from_slice_ctx(&mut temp_slice, flags)?,
_ => return Err(RapiraError::SchemaMismatch),
}
seen_a = true;
}
_ => skip_value(&mut temp_slice, pty)?,
}
}
let _ = seen_a;
*slice = temp_slice;
Ok(out)
}
}
#[test]
fn new_sender_old_receiver_skips_extra_field() {
let peer = SchemaTyp::Struct {
name: "Msg".into(),
fields: SchemaFields::Named(vec![
("a".into(), SchemaTyp::Scalar(ScalarTyp::U32)),
("b".into(), SchemaTyp::Scalar(ScalarTyp::Str)),
]),
};
let mut bytes = serialize(&7u32);
bytes.extend_from_slice(&serialize(&String::from("hi")));
let mut s = bytes.as_slice();
let msg = OldMsg::from_slice_with_peer(&mut s, &peer).unwrap();
assert_eq!(msg, OldMsg { a: 7 });
assert!(s.is_empty(), "extra field b must be skipped via skip_value");
}
#[test]
fn mismatch_on_field_a_does_not_consume_slice() {
let peer = SchemaTyp::Struct {
name: "Msg".into(),
fields: SchemaFields::Named(vec![("a".into(), SchemaTyp::Scalar(ScalarTyp::U64))]),
};
let bytes = serialize(&42u64);
let mut s = bytes.as_slice();
let len_before = s.len();
let r = OldMsg::from_slice_with_peer(&mut s, &peer);
assert!(matches!(r, Err(RapiraError::SchemaMismatch)));
assert_eq!(s.len(), len_before);
}
#[test]
fn old_sender_new_receiver_defaults_missing() {
let peer = SchemaTyp::Struct {
name: "Msg".into(),
fields: SchemaFields::Named(vec![("a".into(), SchemaTyp::Scalar(ScalarTyp::U32))]),
};
let bytes = serialize(&9u32);
let mut s = bytes.as_slice();
let msg = OldMsg::from_slice_with_peer(&mut s, &peer).unwrap();
assert_eq!(msg, OldMsg { a: 9 });
}