1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// Copyright 2022 IOTA Stiftung
// SPDX-License-Identifier: Apache-2.0

//! The `prefix-hex` crates offers encoding and decoding of hex strings with a `0x` prefix.
//!
//! Its API aims to mimic that of the [`hex`](https://docs.rs/hex/latest/hex/) crate, which we also use internally.
//!
//! This crate is compatible with the hex encoding rules of the [Ethereum RPC API](https://eth.wiki/json-rpc/API#hex-value-encoding).

#![cfg_attr(not(feature = "std"), no_std)]

extern crate alloc;
#[cfg(feature = "std")]
extern crate std;

mod data;
mod error;
#[cfg(feature = "primitive-types")]
mod primitive_types;

use alloc::string::String;

pub use error::Error;

/// Tries to decode an hexadecimal encoded string with a `0x` prefix.
pub trait FromHexPrefixed: Sized {
    /// Tries to decode an hexadecimal encoded string with a `0x` prefix.
    fn from_hex_prefixed(hex: &str) -> Result<Self, Error>;
}

/// Encodes data into an hexadecimal encoded string with a `0x` prefix.
pub trait ToHexPrefixed {
    /// Encodes data into an hexadecimal encoded string with a `0x` prefix.
    fn to_hex_prefixed(self) -> String;
}

/// Decodes a hex string with `0x` prefix into a type `T`.
///
/// ## Decode into `Vec<u8>`
/// ```
/// let result = prefix_hex::decode("0x000102");
/// assert_eq!(result, Ok(vec![0x0, 0x1, 0x2]));
/// ```
/// ## Decode into `[u8;N]`
/// ```
/// let result = prefix_hex::decode("0x000102");
/// assert_eq!(result, Ok([0x0, 0x1, 0x2]));
/// ```
pub fn decode<T: FromHexPrefixed>(hex: &str) -> Result<T, Error> {
    T::from_hex_prefixed(hex)
}

/// Encodes `T` as a hex string with a `0x` prefix.
///
/// ## Encode `Vec<u8>`
/// ```
/// assert_eq!(prefix_hex::encode(vec![0x1, 0x2, 0x3]), "0x010203");
/// ```
/// ## Encode `[u8; N]`
/// ```
/// assert_eq!(prefix_hex::encode([0x1, 0x2, 0x3]), "0x010203");
/// ```
pub fn encode<T: ToHexPrefixed>(value: T) -> String {
    ToHexPrefixed::to_hex_prefixed(value)
}

// TODO: Maybe introduce `handle_error` function with `#[cold]` attribute.
fn strip_prefix(hex: &str) -> Result<&str, Error> {
    if let Some(hex) = hex.strip_prefix("0x") {
        Ok(hex)
    } else if hex.len() < 2 {
        Err(Error::InvalidStringLength)
    } else {
        let mut chars = hex.chars();
        // Panic: the following two operations cannot panic because we checked for `hex.len()` in the `else if` branch.
        let c0 = chars.next().unwrap();
        let c1 = chars.next().unwrap();
        Err(Error::InvalidPrefix { c0, c1 })
    }
}