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 thealloc
crate:Hex
andHexForm
.const_len
(disabled by default). Enables types that depend on const generics:ConstHex
andConstHexForm
.
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
const_len
#[serde(with)]
attribute if the underlying type
implements ConstHex
.