rapira 0.13.2

serialization library like borsh, bincode and speedy
Documentation
#![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};

// Local (old) struct: single field `a: u32`.
#[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))]),
    });
}

// Minimal Rapira impl for the test + manual override.
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);
    }

    // Manual override — mimics Plan 3 derive output per spec §4.1.
    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() {
    // Новый отправитель: поля a:u32, b:String. Старый получатель (OldMsg) знает только `a`.
    let peer = SchemaTyp::Struct {
        name: "Msg".into(),
        fields: SchemaFields::Named(vec![
            ("a".into(), SchemaTyp::Scalar(ScalarTyp::U32)),
            ("b".into(), SchemaTyp::Scalar(ScalarTyp::Str)),
        ]),
    };
    // Байты нового отправителя: a=7, b="hi".
    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() {
    // Peer (old sender) sends only `a`; receiver OldMsg also knows only `a`.
    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 });
}