rustbinary 0.1.4

A bounded Serde binary codec with adaptive frames, zero-allocation paths, schema evolution, and authenticated pipelines
Documentation
#[test]
fn core_compact_and_legacy_vectors_are_stable() {
    let value = (251_u64, -2_i32, "A");
    let compact = [251, 251, 0, 3, 1, b'A'];
    let legacy = [
        251, 0, 0, 0, 0, 0, 0, 0, 254, 255, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, b'A',
    ];

    let mut compact_output = [0_u8; 32];
    let compact_written = rustbinary::core::options()
        .serialize_into_slice(&mut compact_output, &value)
        .unwrap();
    assert_eq!(&compact_output[..compact_written], compact);
    assert_eq!(
        rustbinary::core::options()
            .deserialize::<(u64, i32, String)>(&compact)
            .unwrap(),
        (251, -2, "A".to_owned())
    );
    let mut legacy_output = [0_u8; 32];
    let legacy_written = rustbinary::core::legacy_options()
        .serialize_into_slice(&mut legacy_output, &value)
        .unwrap();
    assert_eq!(&legacy_output[..legacy_written], legacy);
    assert_eq!(
        rustbinary::core::legacy_options()
            .deserialize::<(u64, i32, String)>(&legacy)
            .unwrap(),
        (251, -2, "A".to_owned())
    );
}

#[cfg(all(feature = "derive", feature = "bit-packing"))]
#[derive(Debug, PartialEq, rustbinary::protocol::BitPacked)]
struct PackedHeader {
    active: bool,
    #[bits = 3]
    mode: u8,
    #[bits = 12]
    sequence: u16,
}

#[cfg(all(feature = "derive", feature = "bit-packing"))]
#[test]
fn protocol_bit_packed_vector_is_stable() {
    let value = PackedHeader {
        active: true,
        mode: 5,
        sequence: 0xabc,
    };
    let golden = [0xcb, 0xab];
    let config = rustbinary::options().with_bit_packing();
    assert_eq!(config.serialize(&value).unwrap(), golden);
    assert_eq!(config.deserialize::<PackedHeader>(&golden).unwrap(), value);
}

#[cfg(feature = "adaptive")]
#[test]
fn protocol_adaptive_delta_vector_is_stable() {
    let values = [1_000_i64, 1_001, 1_002, 1_003];
    let golden = [1, 4, 251, 208, 7, 2, 2, 2];
    let config = rustbinary::options().with_adaptive_encoding();
    let mut encoded = [0_u8; 16];
    let written = config
        .encode_i64_slice_into_slice(&mut encoded, &values)
        .unwrap();
    assert_eq!(&encoded[..written], golden);

    let mut decoded = [0_i64; 4];
    assert_eq!(
        config.decode_i64_slice_into(&mut decoded, &golden).unwrap(),
        values.len()
    );
    assert_eq!(decoded, values);
}

#[cfg(all(feature = "derive", feature = "fingerprint"))]
#[derive(serde::Deserialize, rustbinary::protocol::Fingerprint, serde::Serialize)]
struct FingerprintedRecord {
    id: u16,
    active: bool,
}

#[cfg(all(feature = "derive", feature = "fingerprint"))]
#[test]
fn protocol_fingerprint_vector_is_stable() {
    let value = FingerprintedRecord {
        id: 251,
        active: true,
    };
    let frame = rustbinary::options()
        .with_fingerprint()
        .serialize(&value)
        .unwrap();
    let golden = [
        b'R', b'B', b'F', b'P', 1, 0, 0, 0, 78, 190, 14, 153, 0, 102, 91, 209, 251, 251, 0, 1,
    ];
    assert_eq!(frame, golden);
    rustbinary::options()
        .with_fingerprint()
        .deserialize::<FingerprintedRecord>(&golden)
        .unwrap();
}

#[cfg(feature = "schema-evolution")]
#[derive(Debug, PartialEq)]
struct EvolvingRecord {
    active: bool,
    id: u16,
}

#[cfg(feature = "schema-evolution")]
impl rustbinary::protocol::SchemaEncode for EvolvingRecord {
    const SCHEMA_ID: u64 = 0x0102_0304_0506_0708;
    const SCHEMA_VERSION: u32 = 2;

    fn encode_fields(
        &self,
        encoder: &mut rustbinary::protocol::FieldEncoder,
    ) -> rustbinary::Result<()> {
        encoder.field(2, &self.id)?;
        encoder.field(1, &self.active)
    }
}

#[cfg(feature = "schema-evolution")]
impl<'de> rustbinary::protocol::SchemaDecode<'de> for EvolvingRecord {
    const SCHEMA_ID: u64 = <Self as rustbinary::protocol::SchemaEncode>::SCHEMA_ID;

    fn decode_fields(
        decoder: &mut rustbinary::protocol::FieldDecoder<'de>,
        encoded_version: u32,
    ) -> rustbinary::Result<Self> {
        if encoded_version != 2 {
            return Err(rustbinary::Error::SchemaEvolution(
                "unsupported test schema revision",
            ));
        }
        Ok(Self {
            active: decoder.required(1)?,
            id: decoder.required(2)?,
        })
    }
}

#[cfg(feature = "schema-evolution")]
#[test]
fn protocol_evolution_vector_is_stable() {
    let value = EvolvingRecord {
        active: true,
        id: 0x0102,
    };
    let golden = [
        b'R', b'B', b'E', b'1', 1, 0, 0, 0, 8, 7, 6, 5, 4, 3, 2, 1, 2, 0, 0, 0, 2, 0, 0, 0, 1, 0,
        0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 251, 2, 1,
    ];
    let config = rustbinary::options().with_schema_evolution();
    assert_eq!(config.serialize(&value).unwrap(), golden);
    assert_eq!(
        config.deserialize::<EvolvingRecord>(&golden).unwrap(),
        value
    );
}

#[cfg(feature = "cbor")]
#[test]
fn pipeline_deterministic_cbor_vector_is_stable() {
    let value = (1_u8, "A");
    let golden = [0x82, 0x01, 0x61, b'A'];
    let config: rustbinary::pipeline::CborConfig = rustbinary::options()
        .with_cbor_format()
        .with_deterministic_encoding();
    assert_eq!(config.serialize(&value).unwrap(), golden);
    assert_eq!(
        config.deserialize::<(u8, String)>(&golden).unwrap(),
        (1, "A".to_owned())
    );
}

#[cfg(feature = "compression")]
#[test]
fn pipeline_raw_compression_vector_is_stable() {
    let golden = [
        b'R', b'B', b'Z', b'1', 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 7,
    ];
    let config: rustbinary::pipeline::CompressedConfig =
        rustbinary::options().with_zstd_compression(3);
    assert_eq!(config.serialize(&7_u8).unwrap(), golden);
    assert_eq!(config.deserialize::<u8>(&golden).unwrap(), 7);
}

#[cfg(feature = "parallel")]
#[test]
fn pipeline_parallel_vector_is_stable() {
    let golden = [
        b'R', b'B', b'P', b'1', 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0,
        0, 0, 0, 0, 0, 0, 1, 251, 251, 0,
    ];
    let config: rustbinary::pipeline::ParallelConfig =
        rustbinary::options().with_parallel_serialization();
    assert_eq!(config.serialize_batch(&[1_u16, 251]).unwrap(), golden);
    assert_eq!(config.deserialize_batch::<u16>(&golden).unwrap(), [1, 251]);
}

#[cfg(feature = "encryption")]
#[test]
fn pipeline_encryption_decoder_vector_is_stable() {
    let key = rustbinary::EncryptionKey::new([0x42; 32]);
    let config = rustbinary::options().with_encryption(key);
    let golden = [
        b'R', b'B', b'X', b'1', 1, 0, 1, 0, 244, 187, 38, 1, 226, 129, 252, 13, 97, 62, 69, 215,
        186, 23, 180, 251, 124, 108, 62, 49, 215, 85, 17, 244, 1, 0, 0, 0, 0, 0, 0, 0, 17, 0, 0, 0,
        0, 0, 0, 0, 54, 51, 18, 172, 70, 14, 71, 116, 249, 191, 206, 78, 253, 200, 31, 94, 56,
    ];
    assert_eq!(config.deserialize::<u8>(&golden).unwrap(), 7);

    let fresh = config.serialize(&7_u8).unwrap();
    assert_ne!(
        fresh, golden,
        "fresh encryption must not reuse the golden nonce"
    );
    assert_eq!(config.deserialize::<u8>(&fresh).unwrap(), 7);
}