Module ext_format

Module ext_format 

Source
Expand description

Provides a generic module for use with #[serde(with = "...")] to serialize and deserialize custom types as the MessagePack Ext format.

This module is used with serde’s with attribute to easily serialize and deserialize custom types that implement the EncodeExt and DecodeExt traits into the MessagePack Ext format.

Note: When the chrono feature is enabled, built-in implementations of EncodeExt and DecodeExt are provided for chrono::DateTime<Utc> and chrono::NaiveDateTime. This allows these types to be serialized and deserialized as MessagePack’s Timestamp format (which is an ext type) using #[serde(with = "shmp::ext_format")].

§Example

Let’s say you have a custom type MyCustomType that you want to serialize as an Ext format. The data part of the Ext can be any custom binary format you define.

use serde::{Serialize, Deserialize};
use shmp::{EncodeExt, DecodeExt, to_vec, from_slice, Error};

// 1. Define your custom type to be handled as an Ext format.
#[derive(Debug, PartialEq)]
struct MyCustomType {
    id: u32,
    value: String,
}

// 2. Implement the `EncodeExt` trait to define the type's unique tag and serialization logic.
impl EncodeExt for MyCustomType {
    // Choose a unique tag from -128 to 127 to identify this type.
    const EXT_TAG: i8 = 42;

    fn to_ext_data(&self) -> Result<Vec<u8>, Error> {
        // Convert the internal data into a byte vector using your custom format.
        // Here, we'll simply concatenate the big-endian bytes of `id` and the UTF-8 bytes of `value`.
        let mut bytes = self.id.to_be_bytes().to_vec();
        bytes.extend_from_slice(self.value.as_bytes());
        Ok(bytes)
    }
}

// 3. Implement the `DecodeExt` trait to define the deserialization logic.
impl DecodeExt for MyCustomType {
    const EXT_TAG: i8 = 42; // Must match the tag in `EncodeExt`.

    fn from_ext_data(data: &[u8]) -> Result<Self, Error> {
        // Restore the original type from the byte slice.
        if data.len() < 4 {
            return Err(Error::DeserializeError("Not enough data for MyCustomType id".into()));
        }
        let id = u32::from_be_bytes(data[0..4].try_into().unwrap());
        let value = String::from_utf8(data[4..].to_vec())?;
        Ok(MyCustomType { id, value})
    }
}

// 4. Use `#[serde(with = "shmp::ext_format")]` on a field in another struct.
#[derive(Serialize, Deserialize, Debug, PartialEq)]
struct Packet {
    sequence: u64,
    #[serde(with = "shmp::ext_format")]
    payload: MyCustomType,
}

// Now, when a `Packet` is serialized, the `payload` field will be encoded in the Ext format.
let packet = Packet {
    sequence: 1,
    payload: MyCustomType { id: 101, value: "hello".to_string() },
};

let encoded = to_vec(&packet).unwrap();
let decoded: Packet = from_slice(&encoded).unwrap();

assert_eq!(packet, decoded);