Skip to main content

reifydb_type/util/
hex.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2025 ReifyDB
3
4use std::{
5	error,
6	fmt::{self, Write},
7};
8
9pub struct DisplayHex<'a>(&'a [u8]);
10
11pub fn display(data: &[u8]) -> DisplayHex<'_> {
12	DisplayHex(data)
13}
14
15impl fmt::Display for DisplayHex<'_> {
16	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17		for byte in self.0 {
18			write!(f, "{:02x}", byte)?;
19		}
20		Ok(())
21	}
22}
23
24pub fn encode(data: &[u8]) -> String {
25	let mut result = String::with_capacity(data.len() * 2);
26	for byte in data {
27		write!(result, "{:02x}", byte).unwrap();
28	}
29	result
30}
31
32pub fn decode(hex: &str) -> Result<Vec<u8>, DecodeError> {
33	let hex = hex.trim();
34
35	if hex.is_empty() {
36		return Ok(Vec::new());
37	}
38
39	if !hex.len().is_multiple_of(2) {
40		return Err(DecodeError::OddLength);
41	}
42
43	let mut result = Vec::with_capacity(hex.len() / 2);
44
45	for chunk in hex.as_bytes().chunks(2) {
46		let high = decode_hex_digit(chunk[0])?;
47		let low = decode_hex_digit(chunk[1])?;
48		result.push((high << 4) | low);
49	}
50
51	Ok(result)
52}
53
54fn decode_hex_digit(byte: u8) -> Result<u8, DecodeError> {
55	match byte {
56		b'0'..=b'9' => Ok(byte - b'0'),
57		b'a'..=b'f' => Ok(byte - b'a' + 10),
58		b'A'..=b'F' => Ok(byte - b'A' + 10),
59		_ => Err(DecodeError::InvalidCharacter(byte as char)),
60	}
61}
62
63#[derive(Debug)]
64pub enum DecodeError {
65	InvalidCharacter(char),
66	OddLength,
67}
68
69impl fmt::Display for DecodeError {
70	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71		match self {
72			DecodeError::InvalidCharacter(ch) => {
73				write!(f, "Invalid hex character: '{}'", ch)
74			}
75			DecodeError::OddLength => {
76				write!(f, "Hex string has odd length")
77			}
78		}
79	}
80}
81
82impl error::Error for DecodeError {}
83
84#[cfg(test)]
85pub mod tests {
86	use super::*;
87
88	#[test]
89	fn test_encode() {
90		assert_eq!(encode(b"Hello"), "48656c6c6f");
91		assert_eq!(encode(b""), "");
92		assert_eq!(encode(&[0x00, 0xFF, 0x42]), "00ff42");
93	}
94
95	#[test]
96	fn test_decode() {
97		assert_eq!(decode("48656c6c6f").unwrap(), b"Hello");
98		assert_eq!(decode("48656C6C6F").unwrap(), b"Hello");
99		assert_eq!(decode("").unwrap(), b"");
100		assert_eq!(decode("00ff42").unwrap(), vec![0x00, 0xFF, 0x42]);
101	}
102
103	#[test]
104	fn test_decode_errors() {
105		assert!(decode("xyz").is_err());
106		assert!(decode("48656c6c6").is_err()); // odd length
107	}
108
109	#[test]
110	fn test_roundtrip() {
111		let data = b"Hello, World! \x00\x01\x02\xFF";
112		let encoded = encode(data);
113		let decoded = decode(&encoded).unwrap();
114		assert_eq!(decoded, data);
115	}
116}