Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

- `Base64Slice`, the borrowed counterpart of `Base64Vec`, analogous to `str`
and `String`.
- `HexSlice` and `HexVec`, a borrowed/owned pair of hex types for
variable-length bytes, analogous to `str` and `String`.

### Changed

Expand All @@ -19,6 +21,9 @@
require both `base64` and `alloc`. `alloc` is enabled by default; if you use
`default-features = false` with `features = ["base64"]`, add `"alloc"` to keep
`Base64Vec`.
- **Breaking:** `ParseHexError` has a new `OddLength` variant, returned when parsing a
`HexVec` from a string with an odd number of characters. This is a breaking
change for code that matches exhaustively on `ParseHexError`.
- `Base64Slice` and `Base64Vec` now format with `Display` and `Debug` without
allocating an intermediate `String`.

Expand Down
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ including automatic opt-in replacement with

* [`HexArray<N>`] encodes a fixed-length byte array as a hex
string. (Requires the `hex` feature.)
* [`HexSlice`] encodes a borrowed byte slice as a hex string.
(Requires the `hex` feature.)
* [`HexVec`] encodes a variable-length byte vector as a hex
string. (Requires the `hex` and `alloc` features.)
* [`Base64Vec`] encodes a variable-length byte vector as a base64
string. (Requires the `base64` and `alloc` features.)
* [`Base64Slice`] encodes a borrowed byte slice as a base64 string.
Expand Down Expand Up @@ -108,11 +112,12 @@ The closest alternatives are:

## Features

* **`hex`**: enables [`HexArray`]. *Enabled by default.*
* **`hex`**: enables [`HexArray`] and [`HexSlice`], and [`HexVec`]
together with `alloc`. *Enabled by default.*
* **`base64`**: enables [`Base64Slice`], and [`Base64Vec`] together with
`alloc`. *Enabled by default.*
* **`alloc`**: enables `alloc` support (required by [`Base64Vec`]).
*Enabled by default.*
* **`alloc`**: enables `alloc` support (required by [`Base64Vec`] and
[`HexVec`]). *Enabled by default.*
* **`serde`**: implements `Serialize` and `Deserialize` for
enabled types. *Not enabled by default.*
* **`schemars08`**: derives `JsonSchema` for enabled types.
Expand All @@ -124,6 +129,8 @@ All features other than `schemars08` are compatible with `no_std`.
[`Base64Vec`]: https://docs.rs/byte-wrapper/0.1.1/byte_wrapper/base64_vec/struct.Base64Vec.html "struct byte_wrapper::base64_vec::Base64Vec"
[CBOR]: https://cbor.io/
[`JsonSchema`]: https://docs.rs/schemars/0.8/schemars/trait.JsonSchema.html
[`HexSlice`]: https://docs.rs/byte-wrapper/0.1.1/byte_wrapper/hex_slice/struct.HexSlice.html "struct byte_wrapper::hex_slice::HexSlice"
[`HexVec`]: https://docs.rs/byte-wrapper/0.1.1/byte_wrapper/hex_vec/struct.HexVec.html "struct byte_wrapper::hex_vec::HexVec"
[`Base64Slice`]: https://docs.rs/byte-wrapper/0.1.1/byte_wrapper/base64_slice/struct.Base64Slice.html "struct byte_wrapper::base64_slice::Base64Slice"
[`serde-human-bytes`]: https://docs.rs/serde-human-bytes
[`serde-encoded-bytes`]: https://docs.rs/serde-encoded-bytes
Expand Down
116 changes: 23 additions & 93 deletions src/hex_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,8 @@

//! The [`HexArray`] newtype wrapper.

use core::{
array::TryFromSliceError,
error,
fmt::{self, Write},
str::FromStr,
};
use crate::{HexSlice, hex_slice::HexDisplay};
use core::{array::TryFromSliceError, error, fmt, str::FromStr};

/// A byte array that displays and parses as hex.
///
Expand Down Expand Up @@ -58,28 +54,8 @@ impl<const N: usize> HexArray<N> {
}
}

/// Formats a byte slice as lower-case hex.
///
/// This is used both for serialization (via `Display`) and as the
/// inner value in `HexArray`'s `Debug` output (via `Debug`).
struct HexDisplay<'a>(&'a [u8]);

impl fmt::Display for HexDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for byte in self.0 {
write!(f, "{byte:02x}")?;
}
Ok(())
}
}

impl fmt::Debug for HexDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}

/// Error returned by [`HexArray::from_str`].
/// Error returned by [`HexArray::from_str`] and
/// [`HexVec::from_str`](crate::HexVec).
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseHexError {
/// The input string had the wrong length.
Expand All @@ -89,6 +65,11 @@ pub enum ParseHexError {
/// Actual number of characters in the input.
actual: usize,
},
/// The input string had an odd number of characters.
OddLength {
/// Actual number of characters in the input.
actual: usize,
},
/// The input contained an invalid hex character.
InvalidHexCharacter {
/// The invalid character.
Expand All @@ -108,6 +89,12 @@ impl fmt::Display for ParseHexError {
expected, actual,
)
}
ParseHexError::OddLength { actual } => {
write!(
f,
"expected an even number of hex characters, got {actual}",
)
}
ParseHexError::InvalidHexCharacter { c, index } => {
write!(f, "invalid hex character '{}' at index {}", c, index,)
}
Expand Down Expand Up @@ -158,63 +145,21 @@ impl<const N: usize> fmt::Debug for HexArray<N> {
}
}

/// Writes hex bytes with padding/alignment support.
///
/// `write_byte` is called for each byte in the slice to allow
/// callers to choose between lowercase and uppercase hex.
fn fmt_hex_padded(
bytes: &[u8],
f: &mut fmt::Formatter<'_>,
write_byte: fn(&mut fmt::Formatter<'_>, u8) -> fmt::Result,
) -> fmt::Result {
let content_len = bytes.len() * 2;

match f.width() {
Some(width) if width > content_len => {
let padding = width - content_len;
let fill = f.fill();
let (pre, post) = match f.align() {
Some(fmt::Alignment::Left) => (0, padding),
Some(fmt::Alignment::Right) | None => (padding, 0),
Some(fmt::Alignment::Center) => {
(padding / 2, padding - padding / 2)
}
};
for _ in 0..pre {
f.write_char(fill)?;
}
for &byte in bytes {
write_byte(f, byte)?;
}
for _ in 0..post {
f.write_char(fill)?;
}
Ok(())
}
Some(_) | None => {
for &byte in bytes {
write_byte(f, byte)?;
}
Ok(())
}
}
}

impl<const N: usize> fmt::Display for HexArray<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_hex_padded(&self.0, f, |f, b| write!(f, "{b:02x}"))
fmt::Display::fmt(HexSlice::new(&self.0), f)
}
}

impl<const N: usize> fmt::LowerHex for HexArray<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_hex_padded(&self.0, f, |f, b| write!(f, "{b:02x}"))
fmt::LowerHex::fmt(HexSlice::new(&self.0), f)
}
}

impl<const N: usize> fmt::UpperHex for HexArray<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_hex_padded(&self.0, f, |f, b| write!(f, "{b:02X}"))
fmt::UpperHex::fmt(HexSlice::new(&self.0), f)
}
}

Expand Down Expand Up @@ -273,29 +218,14 @@ impl<const N: usize> TryFrom<&[u8]> for HexArray<N> {

#[cfg(feature = "serde")]
mod serde_impls {
use super::{HexArray, HexDisplay};
use super::HexArray;
use crate::HexSlice;
use core::fmt;
use serde_core::{
Deserializer,
Deserializer, Serialize,
de::{Expected, SeqAccess, Visitor},
};

/// Serializes a byte slice as lower-case hex if human-readable,
/// or as raw bytes if not.
fn serialize_lower<S>(
bytes: &[u8],
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde_core::Serializer,
{
if serializer.is_human_readable() {
serializer.collect_str(&HexDisplay(bytes))
} else {
serializer.serialize_bytes(bytes)
}
}

struct HexExpected<const N: usize>;

impl<const N: usize> Expected for HexExpected<N> {
Expand Down Expand Up @@ -447,7 +377,7 @@ mod serde_impls {
where
S: serde_core::Serializer,
{
serialize_lower(bytes, serializer)
HexSlice::new(bytes).serialize(serializer)
}

/// Deserializes a byte array from hex if the format is
Expand Down Expand Up @@ -486,7 +416,7 @@ mod serde_impls {
where
S: serde_core::Serializer,
{
serialize_lower(&self.0, serializer)
HexSlice::new(&self.0).serialize(serializer)
}
}

Expand Down
Loading
Loading