Crate hex_buffer_serde

source ·
Expand description

Serializing byte buffers as hex strings with serde.

Problem

Sometimes, you need to serialize a byte buffer (say, a newtype around [u8; 32] or Vec<u8>) as a hex string. The problem is, the newtype in question can be defined in another crate (for example, cryptographic types from sodiumoxide), so you can’t implement Serialize / Deserialize for the type due to Rust orphaning rules. (Or maybe Serialize / Deserialize are implemented, but not in the desirable way.)

Solution

The core of this crate is the Hex trait. It provides methods serialize and deserialize, which signatures match the ones expected by serde. These methods use the other two required methods of the trait. As all trait methods have no self argument, the trait can be implemented for external types; the implementor may be an empty enum designated specifically for this purpose. The implementor can then be used for (de)serialization with the help of the #[serde(with)] attribute.

ConstHex is an analogue of Hex that can be used if the serialized buffer has constant length known in compile time.

Crate Features

  • alloc (enabled by default). Enables types that depend on the alloc crate: Hex and HexForm.
  • const_len (disabled by default). Enables types that depend on const generics: ConstHex and ConstHexForm.

Examples

// Assume this type is defined in an external crate.
pub struct Buffer([u8; 8]);

impl Buffer {
    pub fn from_slice(slice: &[u8]) -> Option<Self> {
        // snip
    }
}

impl AsRef<[u8]> for Buffer {
    fn as_ref(&self) -> &[u8] {
        &self.0
    }
}

// We define in our crate:
use hex_buffer_serde::Hex;
use serde_derive::{Deserialize, Serialize};

struct BufferHex; // a single-purpose type for use in `#[serde(with)]`
impl Hex<Buffer> for BufferHex {
    type Error = &'static str;

    fn create_bytes(buffer: &Buffer) -> Cow<[u8]> {
        buffer.as_ref().into()
    }

    fn from_bytes(bytes: &[u8]) -> Result<Buffer, Self::Error> {
        Buffer::from_slice(bytes).ok_or_else(|| "invalid byte length")
    }
}

#[derive(Serialize, Deserialize)]
pub struct Example {
    #[serde(with = "BufferHex")]
    buffer: Buffer,
    // other fields...
}

Use with internal types

The crate could still be useful if you have control over the serialized buffer type. Hex<T> has a blanket implementation for types T satisfying certain constraints: AsRef<[u8]> and TryFrom<&[u8]>. If these constraints are satisfied, you can use HexForm::<T> in #[serde(with)]:

// It is necessary for `Hex` to be in scope in order
// for `serde`-generated code to use its `serialize` / `deserialize` methods.
use hex_buffer_serde::{Hex, HexForm};
use core::{array::TryFromSliceError, convert::TryFrom};

pub struct OurBuffer([u8; 8]);

impl TryFrom<&[u8]> for OurBuffer {
    type Error = TryFromSliceError;

    fn try_from(slice: &[u8]) -> Result<Self, Self::Error> {
        // snip
    }
}

impl AsRef<[u8]> for OurBuffer {
    fn as_ref(&self) -> &[u8] {
        &self.0
    }
}

#[derive(Serialize, Deserialize)]
pub struct Example {
    #[serde(with = "HexForm::<OurBuffer>")]
    buffer: OurBuffer,
    // other fields...
}

Structs

ConstHexFormconst_len
A dummy container for use inside #[serde(with)] attribute if the underlying type implements ConstHex.
HexFormalloc
A dummy container for use inside #[serde(with)] attribute if the underlying type implements Hex.

Traits

ConstHexconst_len
Analogue of Hex for values that have constant-length byte presentation. This allows to avoid dependency on the alloc crate and expresses the byte length constraint via types.
Hexalloc
Provides hex-encoded (de)serialization for serde.