hex_conservative/
lib.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Hex encoding and decoding.
4//!
5//! General purpose hex encoding/decoding library with a conservative MSRV and dependency policy.
6//!
7//! ## Stabilization strategy
8//!
9//! Because downstream crates may need to return hex errors in their APIs and they need to be
10//! stabilized soon, this crate only exposes the errors and two basic decoding functions. This
11//! should already help with the vast majority of the cases and we're sufficiently confident that
12//! these errors won't have a breaking change any time soon (possibly never).
13//!
14//! If you're writing a binary you don't need to worry about any of this and just use the unstable
15//! version for now. If you're writing a library you should use these stable errors in the API but
16//! you may internally depend on the unstable crate version to get the advanced features that won't
17//! affect your API. This way your API can stabilize before all features in this crate are fully
18//! stable and you still can use all of them.
19//!
20//! ## Crate feature flags
21//!
22//! * `std` - enables the standard library, on by default.
23//! * `alloc` - enables features that require allocation such as decoding into `Vec<u8>`, implied
24//!   by `std`.
25//! * `newer-rust-version` - enables Rust version detection and thus newer features, may add
26//!   dependency on a feature detection crate to reduce compile times. This feature is expected to
27//!   do nothing once the native detection is in Rust and our MSRV is at least that version. We may
28//!   also remove the feature gate in 2.0 with semver trick once that happens.
29//!
30//! ## MSRV policy
31//!
32//! The MSRV of the crate is currently 1.63.0 and we don't intend to bump it until the newer Rust
33//! version is at least two years old and also included in Debian stable (1.63 is in Debian 12 at
34//! the moment).
35//!
36//! Note though that the dependencies may have looser policy. This is not considered
37//! breaking/wrong - you would just need to pin them in `Cargo.lock` (not `.toml`).
38
39#![no_std]
40// Experimental features we need.
41#![cfg_attr(docsrs, feature(doc_cfg))]
42// Coding conventions
43#![warn(missing_docs)]
44
45#[cfg(feature = "std")]
46extern crate std;
47
48#[cfg(feature = "alloc")]
49#[allow(unused_imports)] // false positive regarding macro
50#[macro_use]
51extern crate alloc;
52
53#[doc(hidden)]
54pub mod _export {
55    /// A re-export of `core::*`.
56    pub mod _core {
57        pub use core::*;
58    }
59}
60
61pub mod error;
62mod iter;
63
64#[cfg(feature = "alloc")]
65use alloc::vec::Vec;
66
67use crate::iter::HexToBytesIter;
68
69#[rustfmt::skip]                // Keep public re-exports separate.
70#[doc(inline)]
71pub use self::{
72    error::{
73        DecodeFixedLengthBytesError, DecodeVariableLengthBytesError,
74    },
75};
76
77/// Decodes a hex string with variable length.
78///
79/// The length of the returned `Vec` is determined by the length of the input, meaning all even
80/// lengths of the input string are allowed. If you know the required length at compile time using
81/// [`decode_to_array`] is most likely a better choice.
82///
83/// # Errors
84///
85/// Returns an error if `hex` contains invalid characters or doesn't have even length.
86#[cfg(feature = "alloc")]
87pub fn decode_to_vec(hex: &str) -> Result<Vec<u8>, DecodeVariableLengthBytesError> {
88    Ok(HexToBytesIter::new(hex)?.drain_to_vec()?)
89}
90
91/// Decodes a hex string with an expected length known at compile time.
92///
93/// If you don't know the required length at compile time you need to use [`decode_to_vec`]
94/// instead.
95///
96/// # Errors
97///
98/// Returns an error if `hex` contains invalid characters or has incorrect length. (Should be
99/// `N * 2`.)
100pub fn decode_to_array<const N: usize>(hex: &str) -> Result<[u8; N], DecodeFixedLengthBytesError> {
101    if hex.len() == N * 2 {
102        let mut ret = [0u8; N];
103        // checked above
104        HexToBytesIter::new_unchecked(hex).drain_to_slice(&mut ret)?;
105        Ok(ret)
106    } else {
107        Err(error::InvalidLengthError { invalid: hex.len(), expected: 2 * N }.into())
108    }
109}
110
111#[cfg(test)]
112#[cfg(feature = "alloc")]
113mod tests {
114    #[test]
115    fn parse_hex_into_vector() {
116        let got = crate::decode_to_vec("deadbeef").unwrap();
117        let want = vec![0xde, 0xad, 0xbe, 0xef];
118        assert_eq!(got, want);
119    }
120}