Skip to content

Commit

Permalink
Add serde module
Browse files Browse the repository at this point in the history
Add a `serde` module and feature gate it behind the "serde" feature as
is customary. Add an example showing how to use the new module to
serialize struct fields as hex.

Fix: #6
  • Loading branch information
tcharding committed Oct 26, 2023
1 parent e163872 commit acdf8e3
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 1 deletion.
8 changes: 8 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,12 @@ alloc = []

[dependencies]
core2 = { version = "0.3.2", default_features = false, optional = true }
serde = { version = "1.0", default-features = false, optional = true }


[dev-dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

[[example]]
name = "hexy"
Expand All @@ -40,3 +44,7 @@ name = "wrap_array_display_hex_trait"

[[example]]
name = "wrap_array_fmt_traits"

[[example]]
name = "serde"
required-features = ["std", "serde"]
3 changes: 2 additions & 1 deletion contrib/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

set -ex

FEATURES="std alloc core2"
FEATURES="std alloc core2 serde"
MSRV="1\.48\.0"

cargo --version
Expand Down Expand Up @@ -32,6 +32,7 @@ if [ "$DO_LINT" = true ]
then
cargo clippy --all-features --all-targets -- -D warnings
cargo clippy --locked --example hexy -- -D warnings
cargo clippy --locked --example serde --features=serde -- -D warnings
fi

if [ "$DO_FEATURE_MATRIX" = true ]; then
Expand Down
33 changes: 33 additions & 0 deletions examples/serde.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//! Demonstrate how to use the serde module with struct fields.
#![allow(clippy::disallowed_names)] // Foo is a valid name.

use hex_conservative as hex;
use serde::{Deserialize, Serialize};

fn main() {
let v = vec![0xde, 0xad, 0xbe, 0xef];

let foo = Foo { v: v.clone() };
let bar = Bar { v: v.clone() };

let ser_foo = serde_json::to_string(&foo).expect("failed to serialize foo");
let ser_bar = serde_json::to_string(&bar).expect("failed to serialize bar");

println!();
println!("foo: {}", ser_foo);
println!("bar: {}", ser_bar);
}

/// Abstracts over foo.
#[derive(Debug, Serialize, Deserialize)]
struct Foo {
#[serde(with = "hex")]
v: Vec<u8>,
}

/// Abstracts over bar.
#[derive(Debug, Serialize, Deserialize)]
struct Bar {
v: Vec<u8>,
}
5 changes: 5 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ pub mod display;
mod error;
mod iter;
pub mod parse;
#[cfg(feature = "serde")]
pub mod serde;

/// Reexports of the common crate types and traits.
pub mod prelude {
Expand All @@ -65,6 +67,9 @@ pub use self::{
iter::{BytesToHexIter, HexToBytesIter},
parse::{FromHex, HexToArrayError, HexToBytesError},
};
#[cfg(feature = "serde")]
#[doc(inline)]
pub use crate::serde::{deserialize, serialize, serialize_lower, serialize_upper};

/// Possible case of hex.
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
Expand Down
98 changes: 98 additions & 0 deletions src/serde.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//! Hex encoding with `serde`.
//!
//! # Examples
//!
//! ```
//! # #[cfg(feature = "std")] {
//! use hex_conservative as hex;
//! use serde::{Serialize, Deserialize};
//!
//! #[derive(Debug, Serialize, Deserialize)]
//! struct Foo {
//! #[serde(with = "hex")]
//! bar: Vec<u8>,
//! }
//! # }
//! ```
use core::fmt;
use core::marker::PhantomData;

use serde::de::{Error, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use crate::prelude::*;

/// Serializes `data` as a hex string using lowercase characters.
pub fn serialize<S, T>(data: T, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Serialize + DisplayHex,
{
serialize_lower(data, s)
}

/// Serializes `data` as a hex string using lowercase characters.
pub fn serialize_lower<S, T>(data: T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Serialize + DisplayHex,
{
// Don't do anything special when not human readable.
if !serializer.is_human_readable() {
serde::Serialize::serialize(&data, serializer)
} else {
serializer.collect_str(&format_args!("{:x}", data.as_hex()))
}
}

/// Serializes `data` as hex string using uppercase characters.
pub fn serialize_upper<S, T>(data: T, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: Serialize + DisplayHex,
{
// Don't do anything special when not human readable.
if !serializer.is_human_readable() {
serde::Serialize::serialize(&data, serializer)
} else {
serializer.collect_str(&format_args!("{:X}", data.as_hex()))
}
}

/// Deserializes a hex string into raw bytes.
///
/// Allows upper, lower, and mixed case characters (e.g. `a5b3c1`, `A5B3C1` and `A5b3C1`).
pub fn deserialize<'de, D, T>(d: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de> + FromHex,
{
struct HexVisitor<T>(PhantomData<T>);

impl<'de, T> Visitor<'de> for HexVisitor<T>
where
T: FromHex,
{
type Value = T;

fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("an ASCII hex string")
}

fn visit_str<E: Error>(self, data: &str) -> Result<Self::Value, E> {
FromHex::from_hex(data).map_err(Error::custom)
}

fn visit_borrowed_str<E: Error>(self, data: &'de str) -> Result<Self::Value, E> {
FromHex::from_hex(data).map_err(Error::custom)
}
}

// Don't do anything special when not human readable.
if !d.is_human_readable() {
serde::Deserialize::deserialize(d)
} else {
d.deserialize_map(HexVisitor(PhantomData))
}
}

0 comments on commit acdf8e3

Please sign in to comment.