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
#[serde(with)]
attribute.Traits
serde
.