use core::convert::Infallible;
use core::fmt;
use core::fmt::Write as _;
use encoding::{Decodable, Decoder, Encodable, EncodableByteIter};
use internals::hex::{BytesToHexIter, Case, HexToBytesIter, HexToBytesIterError};
use internals::write_err;
pub(crate) struct HexPrimitive<'a, T>(pub(crate) &'a T);
impl<'a, T: Encodable + Decodable> IntoIterator for &HexPrimitive<'a, T> {
type Item = u8;
type IntoIter = EncodableByteIter<'a, T>;
fn into_iter(self) -> Self::IntoIter {
EncodableByteIter::new(self.0)
}
}
impl<T: Decodable> HexPrimitive<'_, T> {
pub(crate) fn from_str(s: &str) -> Result<T, ParsePrimitiveError<T>> {
let iter = HexToBytesIter::new(s)?;
let mut decoder = T::decoder();
let mut buffer = [0u8; 4096]; let mut index = 0;
for result in iter {
if index == buffer.len() {
decoder
.push_bytes(&mut (buffer.as_slice()))
.map_err(encoding::DecodeError::Parse)
.map_err(ParsePrimitiveError::Decode)?;
index = 0;
}
buffer[index] = result?;
index += 1;
}
decoder
.push_bytes(&mut (&buffer[..index]))
.map_err(encoding::DecodeError::Parse)
.map_err(ParsePrimitiveError::Decode)?;
decoder.end().map_err(encoding::DecodeError::Parse).map_err(ParsePrimitiveError::Decode)
}
}
impl<T: Encodable> HexPrimitive<'_, T> {
#[inline]
fn fmt_hex(&self, f: &mut fmt::Formatter, case: Case) -> fmt::Result {
let write_pad = |f: &mut fmt::Formatter, pad_len: usize| -> fmt::Result {
for _ in 0..pad_len {
f.write_char(f.fill())?;
}
Ok(())
};
let len = EncodableByteIter::new(self.0).count() * 2;
let iter = BytesToHexIter::new(EncodableByteIter::new(self.0), case);
let extra_len = if f.alternate() { 2 } else { 0 };
let total_len = len + extra_len;
let pad_width = f.width().unwrap_or(total_len);
let trunc_width = f.precision().map_or(len, |v| v.saturating_sub(extra_len));
let pad_diff = pad_width.saturating_sub(total_len);
let left_pad = match f.align() {
Some(fmt::Alignment::Left) => 0,
Some(fmt::Alignment::Center) => pad_diff / 2,
Some(fmt::Alignment::Right) => pad_diff,
None => 0,
};
write_pad(f, left_pad)?;
if f.alternate() {
f.write_str(match case {
Case::Lower => "0x",
Case::Upper => "0X",
})?;
}
for (i, ch) in iter.enumerate() {
if i >= trunc_width {
break;
}
f.write_char(ch)?;
}
write_pad(f, pad_diff.saturating_sub(left_pad))?;
Ok(())
}
}
impl<T: Encodable> fmt::Display for HexPrimitive<'_, T> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::LowerHex::fmt(self, f)
}
}
impl<T: Encodable> fmt::Debug for HexPrimitive<'_, T> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
fmt::LowerHex::fmt(self, f)
}
}
impl<T: Encodable> fmt::LowerHex for HexPrimitive<'_, T> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.fmt_hex(f, Case::Lower)
}
}
impl<T: Encodable> fmt::UpperHex for HexPrimitive<'_, T> {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.fmt_hex(f, Case::Upper)
}
}
pub(crate) enum ParsePrimitiveError<T: Decodable> {
OddLengthString(HexToBytesIterError),
InvalidChar(HexToBytesIterError),
Decode(encoding::DecodeError<<T::Decoder as encoding::Decoder>::Error>),
}
impl<T: Decodable> From<Infallible> for ParsePrimitiveError<T> {
fn from(never: Infallible) -> Self {
match never {}
}
}
impl<T: Decodable> fmt::Debug for ParsePrimitiveError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OddLengthString(ref e) => write_err!(f, "odd length string"; e),
Self::InvalidChar(ref e) => write_err!(f, "invalid character"; e),
Self::Decode(_) => {
write!(f, "failure decoding hex string into {}", core::any::type_name::<T>())
}
}
}
}
impl<T: Decodable> Clone for ParsePrimitiveError<T>
where
<<T as Decodable>::Decoder as Decoder>::Error: Clone,
{
fn clone(&self) -> Self {
match self {
Self::OddLengthString(ref e) => Self::OddLengthString(e.clone()),
Self::InvalidChar(ref e) => Self::InvalidChar(e.clone()),
Self::Decode(ref e) => Self::Decode(e.clone()),
}
}
}
impl<T: Decodable> PartialEq for ParsePrimitiveError<T>
where
<<T as Decodable>::Decoder as Decoder>::Error: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::OddLengthString(ref e1), Self::OddLengthString(ref e2)) => e1 == e2,
(Self::InvalidChar(ref e1), Self::InvalidChar(ref e2)) => e1 == e2,
(Self::Decode(ref e1), Self::Decode(ref e2)) => e1 == e2,
_ => false,
}
}
}
impl<T: Decodable> Eq for ParsePrimitiveError<T> where
<<T as Decodable>::Decoder as Decoder>::Error: PartialEq
{
}
impl<T: Decodable> fmt::Display for ParsePrimitiveError<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self, f)
}
}
impl<T: Decodable> From<HexToBytesIterError> for ParsePrimitiveError<T> {
fn from(err: HexToBytesIterError) -> Self {
match err {
HexToBytesIterError::OddLengthString => Self::OddLengthString(err),
HexToBytesIterError::InvalidChar { .. } => Self::InvalidChar(err),
}
}
}
#[cfg(feature = "std")]
impl<T: Decodable> std::error::Error for ParsePrimitiveError<T> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::OddLengthString(ref e) => Some(e),
Self::InvalidChar(ref e) => Some(e),
Self::Decode(_) => None,
}
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "alloc")]
use alloc::{format, string::ToString};
#[cfg(feature = "alloc")]
use super::*;
use crate::block;
#[test]
#[cfg(feature = "alloc")]
fn parse_primitive_error_display() {
let odd: ParsePrimitiveError<block::Header> = HexPrimitive::from_str("0").unwrap_err();
let invalid: ParsePrimitiveError<block::Header> = HexPrimitive::from_str("zz").unwrap_err();
let decode: ParsePrimitiveError<block::Header> = HexPrimitive::from_str("00").unwrap_err();
assert!(!odd.to_string().is_empty());
assert!(!invalid.to_string().is_empty());
assert!(!decode.to_string().is_empty());
}
#[test]
#[cfg(feature = "std")]
fn parse_primitive_error_source() {
use std::error::Error as _;
let odd: ParsePrimitiveError<block::Header> = HexPrimitive::from_str("0").unwrap_err();
let invalid: ParsePrimitiveError<block::Header> = HexPrimitive::from_str("zz").unwrap_err();
let decode: ParsePrimitiveError<block::Header> = HexPrimitive::from_str("00").unwrap_err();
assert!(odd.source().is_some());
assert!(invalid.source().is_some());
assert!(decode.source().is_none());
}
#[test]
#[cfg(feature = "alloc")]
fn hex_primitive_iter_and_debug() {
let header: block::Header =
encoding::decode_from_slice(&[0u8; block::Header::SIZE]).expect("valid header");
let hex = HexPrimitive(&header);
assert_eq!((&hex).into_iter().next(), Some(0u8));
assert!(!format!("{hex:?}").is_empty());
}
}