diff --git a/.gitlab-ci-matrix.yml b/.gitlab-ci-matrix.yml index fd08f03..1add6e2 100644 --- a/.gitlab-ci-matrix.yml +++ b/.gitlab-ci-matrix.yml @@ -22,6 +22,9 @@ steps: script: - cargo test - cargo test --no-default-features + - cargo test --no-default-features --features alloc + - cargo test --no-default-features --features std + - cargo test --no-default-features --features serde dependencies: - compile diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index fcbc0ec..292412d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -115,6 +115,9 @@ test:rust-stable: - cargo test - cargo test --features serde - cargo test --no-default-features + - cargo test --no-default-features --features std + - cargo test --no-default-features --features alloc + - cargo test --no-default-features --features serde variables: RUST_KEY: rust-stable @@ -127,6 +130,9 @@ test:rust-stable-musl: - cargo test - cargo test --features serde - cargo test --no-default-features + - cargo test --no-default-features --features std + - cargo test --no-default-features --features alloc + - cargo test --no-default-features --features serde variables: RUST_KEY: rust-stable-musl @@ -139,6 +145,9 @@ test:rust-beta: - cargo test - cargo test --features serde - cargo test --no-default-features + - cargo test --no-default-features --features std + - cargo test --no-default-features --features alloc + - cargo test --no-default-features --features serde variables: RUST_KEY: rust-beta @@ -151,6 +160,9 @@ test:rust-beta-musl: - cargo test - cargo test --features serde - cargo test --no-default-features + - cargo test --no-default-features --features std + - cargo test --no-default-features --features alloc + - cargo test --no-default-features --features serde variables: RUST_KEY: rust-beta-musl @@ -163,6 +175,9 @@ test:rust-nightly: - cargo test - cargo test --features serde - cargo test --no-default-features + - cargo test --no-default-features --features std + - cargo test --no-default-features --features alloc + - cargo test --no-default-features --features serde variables: RUST_KEY: rust-nightly allow_failure: true @@ -176,7 +191,9 @@ test:rust-nightly-musl: - cargo test - cargo test --features serde - cargo test --no-default-features + - cargo test --no-default-features --features std + - cargo test --no-default-features --features alloc + - cargo test --no-default-features --features serde variables: RUST_KEY: rust-nightly-musl allow_failure: true - diff --git a/.travis.yml b/.travis.yml index 6349d45..8c16686 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,9 +21,12 @@ install: script: - if [[ $TRAVIS_RUST_VERSION == "stable" && $TRAVIS_OS_NAME == "linux" ]]; then cargo fmt -- --check; fi - - if [[ $TRAVIS_RUST_VERSION == "stable" && $TRAVIS_OS_NAME == "linux" ]]; then cargo clippy -- -D clippy::all; fi + - if [[ $TRAVIS_RUST_VERSION == "stable" && $TRAVIS_OS_NAME == "linux" ]]; then cargo clippy -- -W clippy::all; fi - cargo test - cargo test --features serde - cargo test --no-default-features + - cargo test --no-default-features --features std + - cargo test --no-default-features --features alloc + - cargo test --no-default-features --features serde # Validate benches still work. - cargo bench --all -- --test diff --git a/Cargo.toml b/Cargo.toml index 66801ac..f73a0a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,14 +16,15 @@ travis-ci = { repository = "KokaKiwi/rust-hex", branch = "master" } [features] default = ["std"] -std = [] +alloc = [] +std = ["alloc"] [[bench]] name = "hex" harness = false [dependencies] -serde = { version = "1.0", optional = true } +serde = { version = "1.0", default-features = false, optional = true } [dev-dependencies] criterion = "0.3" diff --git a/src/error.rs b/src/error.rs index b9f2acf..ff7a3b5 100644 --- a/src/error.rs +++ b/src/error.rs @@ -33,13 +33,17 @@ impl fmt::Display for FromHexError { } #[cfg(test)] +// this feature flag is here to suppress unused +// warnings of `super::*` and `pretty_assertions::assert_eq` +#[cfg(feature = "alloc")] mod tests { use super::*; - #[cfg(not(feature = "std"))] + #[cfg(feature = "alloc")] use alloc::string::ToString; use pretty_assertions::assert_eq; #[test] + #[cfg(feature = "alloc")] fn test_display() { assert_eq!( FromHexError::InvalidHexCharacter { c: '\n', index: 5 }.to_string(), diff --git a/src/lib.rs b/src/lib.rs index e9fb779..51e6aa4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,6 +15,16 @@ //! # Example //! //! ``` +//! # #[cfg(not(feature = "alloc"))] +//! # let mut output = [0; 0x18]; +//! # +//! # #[cfg(not(feature = "alloc"))] +//! # hex::encode_to_slice(b"Hello world!", &mut output).unwrap(); +//! # +//! # #[cfg(not(feature = "alloc"))] +//! # let hex_string = ::core::str::from_utf8(&output).unwrap(); +//! # +//! # #[cfg(feature = "alloc")] //! let hex_string = hex::encode("Hello world!"); //! //! println!("{}", hex_string); // Prints "48656c6c6f20776f726c6421" @@ -27,9 +37,9 @@ #![cfg_attr(docsrs, feature(doc_cfg))] #![allow(clippy::unreadable_literal)] -#[cfg(not(feature = "std"))] +#[cfg(feature = "alloc")] extern crate alloc; -#[cfg(not(feature = "std"))] +#[cfg(feature = "alloc")] use alloc::{string::String, vec::Vec}; use core::iter; @@ -41,7 +51,9 @@ pub use crate::error::FromHexError; #[cfg_attr(docsrs, doc(cfg(feature = "serde")))] pub mod serde; #[cfg(feature = "serde")] -pub use crate::serde::{deserialize, serialize, serialize_upper}; +pub use crate::serde::deserialize; +#[cfg(all(feature = "alloc", feature = "serde"))] +pub use crate::serde::{serialize, serialize_upper}; /// Encoding values as hex string. /// @@ -95,7 +107,7 @@ impl<'a> Iterator for BytesToHexChars<'a> { Some(current) => Some(current), None => self.inner.next().map(|byte| { let current = self.table[(byte >> 4) as usize] as char; - self.next = Some(self.table[(byte & 0xf) as usize] as char); + self.next = Some(self.table[(byte & 0x0F) as usize] as char); current }), } @@ -117,6 +129,7 @@ impl<'a> iter::ExactSizeIterator for BytesToHexChars<'a> { } } +#[inline] fn encode_to_iter>(table: &'static [u8; 16], source: &[u8]) -> T { BytesToHexChars::new(source, table).collect() } @@ -139,17 +152,14 @@ impl> ToHex for T { /// /// ``` /// use hex::FromHex; +/// use core::str; /// -/// match Vec::from_hex("48656c6c6f20776f726c6421") { -/// Ok(vec) => { -/// for b in vec { -/// println!("{}", b as char); -/// } -/// } -/// Err(e) => { -/// // Deal with the error ... -/// } -/// } +/// let buffer = <[u8; 12]>::from_hex("48656c6c6f20776f726c6421")?; +/// let string = str::from_utf8(&buffer).expect("invalid buffer length"); +/// +/// println!("{}", string); // prints "Hello world!" +/// # assert_eq!("Hello world!", string); +/// # Ok::<(), hex::FromHexError>(()) /// ``` pub trait FromHex: Sized { type Error; @@ -174,6 +184,7 @@ fn val(c: u8, idx: usize) -> Result { } } +#[cfg(feature = "alloc")] impl FromHex for Vec { type Error = FromHexError; @@ -198,7 +209,7 @@ macro_rules! from_hex_array_impl { type Error = FromHexError; fn from_hex>(hex: T) -> Result { - let mut out = [0u8; $len]; + let mut out = [0_u8; $len]; decode_to_slice(hex, &mut out as &mut [u8])?; Ok(out) } @@ -243,6 +254,8 @@ from_hex_array_impl! { /// assert_eq!(hex::encode("Hello world!"), "48656c6c6f20776f726c6421"); /// assert_eq!(hex::encode(vec![1, 2, 3, 15, 16]), "0102030f10"); /// ``` +#[must_use] +#[cfg(feature = "alloc")] pub fn encode>(data: T) -> String { data.encode_hex() } @@ -257,6 +270,8 @@ pub fn encode>(data: T) -> String { /// assert_eq!(hex::encode_upper("Hello world!"), "48656C6C6F20776F726C6421"); /// assert_eq!(hex::encode_upper(vec![1, 2, 3, 15, 16]), "0102030F10"); /// ``` +#[must_use] +#[cfg(feature = "alloc")] pub fn encode_upper>(data: T) -> String { data.encode_hex_upper() } @@ -277,6 +292,7 @@ pub fn encode_upper>(data: T) -> String { /// assert_eq!(hex::decode("123"), Err(hex::FromHexError::OddLength)); /// assert!(hex::decode("foo").is_err()); /// ``` +#[cfg(feature = "alloc")] pub fn decode>(data: T) -> Result, FromHexError> { FromHex::from_hex(data) } @@ -316,11 +332,14 @@ pub fn decode_to_slice>(data: T, out: &mut [u8]) -> Result<(), Fr // (4, 5) // (6, 7) // ... +#[inline] fn generate_iter(len: usize) -> impl Iterator { (0..len).step_by(2).zip((0..len).skip(1).step_by(2)) } // the inverse of `val`. +#[inline] +#[must_use] fn byte2hex(byte: u8, table: &[u8; 16]) -> (u8, u8) { let high = table[((byte & 0xf0) >> 4) as usize]; let low = table[(byte & 0x0f) as usize]; @@ -350,7 +369,11 @@ pub fn encode_to_slice>(input: T, output: &mut [u8]) -> Result<() return Err(FromHexError::InvalidStringLength); } - for (byte, (i, j)) in input.as_ref().iter().zip(generate_iter(input.as_ref().len() * 2)) { + for (byte, (i, j)) in input + .as_ref() + .iter() + .zip(generate_iter(input.as_ref().len() * 2)) + { let (high, low) = byte2hex(*byte, HEX_CHARS_LOWER); output[i] = high; output[j] = low; @@ -362,11 +385,12 @@ pub fn encode_to_slice>(input: T, output: &mut [u8]) -> Result<() #[cfg(test)] mod test { use super::*; - #[cfg(not(feature = "std"))] + #[cfg(feature = "alloc")] use alloc::string::ToString; use pretty_assertions::assert_eq; #[test] + #[cfg(feature = "alloc")] fn test_gen_iter() { let mut result = Vec::new(); result.push((0, 1)); @@ -405,38 +429,53 @@ mod test { let mut output_3 = [0; 4]; - assert_eq!(decode_to_slice(b"6", &mut output_3), Err(FromHexError::OddLength)); + assert_eq!( + decode_to_slice(b"6", &mut output_3), + Err(FromHexError::OddLength) + ); } #[test] + #[cfg(feature = "alloc")] fn test_encode() { assert_eq!(encode("foobar"), "666f6f626172"); } #[test] + #[cfg(feature = "alloc")] fn test_decode() { - assert_eq!(decode("666f6f626172"), Ok(String::from("foobar").into_bytes())); + assert_eq!( + decode("666f6f626172"), + Ok(String::from("foobar").into_bytes()) + ); } #[test] + #[cfg(feature = "alloc")] pub fn test_from_hex_okay_str() { assert_eq!(Vec::from_hex("666f6f626172").unwrap(), b"foobar"); assert_eq!(Vec::from_hex("666F6F626172").unwrap(), b"foobar"); } #[test] + #[cfg(feature = "alloc")] pub fn test_from_hex_okay_bytes() { assert_eq!(Vec::from_hex(b"666f6f626172").unwrap(), b"foobar"); assert_eq!(Vec::from_hex(b"666F6F626172").unwrap(), b"foobar"); } #[test] + #[cfg(feature = "alloc")] pub fn test_invalid_length() { assert_eq!(Vec::from_hex("1").unwrap_err(), FromHexError::OddLength); - assert_eq!(Vec::from_hex("666f6f6261721").unwrap_err(), FromHexError::OddLength); + assert_eq!( + Vec::from_hex("666f6f6261721").unwrap_err(), + FromHexError::OddLength + ); } #[test] + #[cfg(feature = "alloc")] pub fn test_invalid_char() { assert_eq!( Vec::from_hex("66ag").unwrap_err(), @@ -445,11 +484,13 @@ mod test { } #[test] + #[cfg(feature = "alloc")] pub fn test_empty() { assert_eq!(Vec::from_hex("").unwrap(), b""); } #[test] + #[cfg(feature = "alloc")] pub fn test_from_hex_whitespace() { assert_eq!( Vec::from_hex("666f 6f62617").unwrap_err(), @@ -471,6 +512,7 @@ mod test { } #[test] + #[cfg(feature = "alloc")] fn test_to_hex() { assert_eq!( [0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72].encode_hex::(), diff --git a/src/serde.rs b/src/serde.rs index a90e360..b5ed8e3 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -1,28 +1,41 @@ //! Hex encoding with `serde`. //! -//! # Example -//! -//! ``` -//! use serde::{Serialize, Deserialize}; -//! -//! #[derive(Serialize, Deserialize)] -//! struct Foo { -//! #[serde(with = "hex")] -//! bar: Vec, -//! } -//! ``` -//! +#[cfg_attr( + all(feature = "alloc", feature = "serde"), + doc = r##" +# Example + +``` +use serde::{Serialize, Deserialize}; + +#[derive(Serialize, Deserialize)] +struct Foo { + #[serde(with = "hex")] + bar: Vec, +} +``` +"## +)] use serde::de::{Error, Visitor}; -use serde::{Deserializer, Serializer}; +use serde::Deserializer; +#[cfg(feature = "alloc")] +use serde::Serializer; + +#[cfg(feature = "alloc")] +use alloc::string::String; + +use core::fmt; +use core::marker::PhantomData; -use std::fmt; -use std::marker::PhantomData; +use crate::FromHex; -use crate::{FromHex, ToHex}; +#[cfg(feature = "alloc")] +use crate::ToHex; /// Serializes `data` as hex string using uppercase characters. /// /// Apart from the characters' casing, this works exactly like `serialize()`. +#[cfg(feature = "alloc")] pub fn serialize_upper(data: T, serializer: S) -> Result where S: Serializer, @@ -37,6 +50,7 @@ where /// Lowercase characters are used (e.g. `f9b4ca`). The resulting string's length /// is always even, each byte in data is always encoded using two hex digits. /// Thus, the resulting string contains exactly twice as many bytes as the input data. +#[cfg(feature = "alloc")] pub fn serialize(data: T, serializer: S) -> Result where S: Serializer, @@ -73,14 +87,14 @@ where where E: Error, { - FromHex::from_hex(data).map_err(|e| Error::custom(e)) + FromHex::from_hex(data).map_err(Error::custom) } fn visit_borrowed_str(self, data: &'de str) -> Result where E: Error, { - FromHex::from_hex(data).map_err(|e| Error::custom(e)) + FromHex::from_hex(data).map_err(Error::custom) } } diff --git a/tests/serde.rs b/tests/serde.rs index aedf8c7..decc901 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -1,4 +1,4 @@ -#![cfg(feature = "serde")] +#![cfg(all(feature = "serde", feature = "alloc"))] use serde::{Deserialize, Serialize};