reifydb_type/util/
hex.rs

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