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.

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:
extern crate hex_buffer_serde;
use hex_buffer_serde::Hex;

enum BufferHex {} // a single-purpose type for use in `#[serde(with)]`
impl Hex<Buffer> for BufferHex {
    fn create_bytes(buffer: &Buffer) -> Cow<[u8]> {
        buffer.as_ref().into()
    }

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

#[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 TryFromSlice (which is a makeshift replacement for TryFrom<&[u8]> until TryFrom is stabilized). 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, TryFromSlice, TryFromSliceError};

pub struct OurBuffer([u8; 8]);

impl TryFromSlice for OurBuffer {
    type Error = TryFromSliceError;

    fn try_from_slice(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

A dummy container for use inside #[serde(with)] attribute.
Error converting a slice into an array.

Traits

Provides hex-encoded (de)serialization for serde.
Fallible conversion from a byte slice.