serdapt-hex 0.1.0

Adapter to serialize as hex
Documentation
// Copyright (c) 2025 Stephane Raux. Distributed under the 0BSD license.

//! # Overview
//! - [📦 crates.io](https://crates.io/crates/serdapt-hex)
//! - [📖 Documentation](https://docs.rs/serdapt-hex)
//! - [âš– 0BSD license](https://spdx.org/licenses/0BSD.html)
//!
//! Hex adapter for `#[serde(with = ...)]`. See [`serdapt`](https://docs.rs/serdapt) for more
//! information on how to use such adapters.
//!
//! The documentation for [`Hex`] and [`HexArray`] provides examples.
//!
//! # Contribute
//! All contributions shall be licensed under the [0BSD license](https://spdx.org/licenses/0BSD.html).

#![deny(missing_docs)]
#![no_std]

extern crate alloc;

use alloc::vec::Vec;
use core::fmt::{self, Display};
use serdapt::{DeserializeWith, SerializeWith};
use serde::{de::Visitor, Deserializer, Serialize, Serializer};

/// Adapter to serialize bytes as a hex string
///
/// `UPPERCASE` only affects serialization. Deserialization always accepts both lower and upper
/// cases.
///
/// If the target type is an array, the [`HexArray`] adapter should perform better.
///
/// # Example
/// ```
/// # extern crate alloc;
/// # use alloc::{vec, vec::Vec};
/// use serde::{Deserialize, Serialize};
/// use serde_json::json;
///
/// #[derive(Debug, Deserialize, PartialEq, Serialize)]
/// struct Foo(#[serde(with = "serdapt_hex::LowHex")] Vec<u8>);
///
/// let x = Foo(vec![9, 1, 74]);
/// let v = serde_json::to_value(&x).unwrap();
/// assert_eq!(v, json!("09014a"));
/// let x2 = serde_json::from_value::<Foo>(v).unwrap();
/// assert_eq!(x, x2);
/// ```
#[non_exhaustive]
pub struct Hex<const UPPERCASE: bool = false> {}

/// Adapter to serialize bytes as a lowercase hex string
pub type LowHex = Hex<false>;

/// Adapter to serialize bytes as an uppercase hex string
pub type UpHex = Hex<true>;

impl<const UPPERCASE: bool> Hex<UPPERCASE> {
    /// Serializes value with adapter
    pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
    where
        T: ?Sized,
        S: Serializer,
        Self: SerializeWith<T>,
    {
        Self::serialize_with(value, serializer)
    }

    /// Deserializes value with adapter
    pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
    where
        D: Deserializer<'de>,
        Self: DeserializeWith<'de, T>,
    {
        Self::deserialize_with(deserializer)
    }
}

impl<T> SerializeWith<T> for Hex<false>
where
    T: AsRef<[u8]>,
{
    fn serialize_with<S: Serializer>(value: &T, serializer: S) -> Result<S::Ok, S::Error> {
        Serialize::serialize(&hex::encode(value), serializer)
    }
}

impl<T> SerializeWith<T> for Hex<true>
where
    T: AsRef<[u8]>,
{
    fn serialize_with<S: Serializer>(value: &T, serializer: S) -> Result<S::Ok, S::Error> {
        Serialize::serialize(&hex::encode_upper(value), serializer)
    }
}

impl<'de, const U: bool, T> DeserializeWith<'de, T> for Hex<U>
where
    T: TryFrom<Vec<u8>>,
    T::Error: Display,
{
    fn deserialize_with<D>(deserializer: D) -> Result<T, D::Error>
    where
        D: Deserializer<'de>,
    {
        let bytes = deserializer.deserialize_str(HexVisitor)?;
        bytes.try_into().map_err(serde::de::Error::custom)
    }
}

struct HexVisitor;

impl Visitor<'_> for HexVisitor {
    type Value = Vec<u8>;

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

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: serde::de::Error,
    {
        hex::decode(v).map_err(serde::de::Error::custom)
    }
}

/// Adapter to serialize a byte array as a hex string
///
/// `UPPERCASE` only affects serialization. Deserialization always accepts both lower and upper
/// cases.
///
/// # Example
/// ```
/// # extern crate alloc;
/// # use alloc::vec;
/// use serde::{Deserialize, Serialize};
/// use serde_json::json;
///
/// #[derive(Debug, Deserialize, PartialEq, Serialize)]
/// struct Foo(#[serde(with = "serdapt_hex::UpHexArray")] [u8; 3]);
///
/// let x = Foo([9, 1, 74]);
/// let v = serde_json::to_value(&x).unwrap();
/// assert_eq!(v, json!("09014A"));
/// let x2 = serde_json::from_value::<Foo>(v).unwrap();
/// assert_eq!(x, x2);
/// ```
#[non_exhaustive]
pub struct HexArray<const UPPERCASE: bool = false> {}

/// Adapter to serialize a byte array as a lowercase hex string
pub type LowHexArray = HexArray<false>;

/// Adapter to serialize a byte array as an uppercase hex string
pub type UpHexArray = HexArray<true>;

impl<const U: bool> HexArray<U> {
    /// Serializes value with adapter
    pub fn serialize<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
    where
        T: ?Sized,
        S: Serializer,
        Self: SerializeWith<T>,
    {
        Self::serialize_with(value, serializer)
    }

    /// Deserializes value with adapter
    pub fn deserialize<'de, T, D>(deserializer: D) -> Result<T, D::Error>
    where
        D: Deserializer<'de>,
        Self: DeserializeWith<'de, T>,
    {
        Self::deserialize_with(deserializer)
    }
}

impl<T> SerializeWith<T> for HexArray<false>
where
    T: AsRef<[u8]>,
{
    fn serialize_with<S: Serializer>(value: &T, serializer: S) -> Result<S::Ok, S::Error> {
        Serialize::serialize(&hex::encode(value), serializer)
    }
}

impl<T> SerializeWith<T> for HexArray<true>
where
    T: AsRef<[u8]>,
{
    fn serialize_with<S: Serializer>(value: &T, serializer: S) -> Result<S::Ok, S::Error> {
        Serialize::serialize(&hex::encode_upper(value), serializer)
    }
}

impl<'de, const U: bool, const N: usize> DeserializeWith<'de, [u8; N]> for HexArray<U> {
    fn deserialize_with<D>(deserializer: D) -> Result<[u8; N], D::Error>
    where
        D: Deserializer<'de>,
    {
        deserializer.deserialize_str(HexArrayVisitor::<N>)
    }
}

struct HexArrayVisitor<const N: usize>;

impl<const N: usize> Visitor<'_> for HexArrayVisitor<N> {
    type Value = [u8; N];

    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "a hex string encoding {N} bytes")
    }

    fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
    where
        E: serde::de::Error,
    {
        let mut out = [0u8; N];
        hex::decode_to_slice(v, &mut out).map_err(|e| match e {
            hex::FromHexError::InvalidStringLength => E::invalid_length(v.len() / 2, &self),
            _ => E::custom(e),
        })?;
        Ok(out)
    }
}