use anyhow::{anyhow, Result};
use std::cmp::Ordering;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Lsn([u8; 10]);
impl Lsn {
pub fn zero() -> Self {
Lsn([0u8; 10])
}
pub fn from_hex(hex: &str) -> Result<Self> {
let hex_clean = hex.strip_prefix("0x").unwrap_or(hex);
if hex_clean.len() != 20 {
return Err(anyhow!(
"LSN hex string must be 20 characters (10 bytes), got {}",
hex_clean.len()
));
}
let mut bytes = [0u8; 10];
for (i, chunk) in hex_clean.as_bytes().chunks(2).enumerate() {
let hex_byte = std::str::from_utf8(chunk)
.map_err(|e| anyhow!("Invalid UTF-8 in hex string: {e}"))?;
bytes[i] = u8::from_str_radix(hex_byte, 16)
.map_err(|e| anyhow!("Invalid hex character: {e}"))?;
}
Ok(Lsn(bytes))
}
pub fn to_hex(&self) -> String {
format!("0x{}", hex::encode(self.0))
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self> {
if bytes.len() != 10 {
return Err(anyhow!("LSN must be exactly 10 bytes, got {}", bytes.len()));
}
let mut arr = [0u8; 10];
arr.copy_from_slice(bytes);
Ok(Lsn(arr))
}
pub fn to_bytes(&self) -> Vec<u8> {
self.0.to_vec()
}
pub fn as_bytes(&self) -> &[u8; 10] {
&self.0
}
}
impl PartialOrd for Lsn {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Lsn {
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
impl std::fmt::Display for Lsn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_hex())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lsn_zero() {
let lsn = Lsn::zero();
assert_eq!(lsn.to_hex(), "0x00000000000000000000");
assert_eq!(lsn.to_bytes(), vec![0u8; 10]);
}
#[test]
fn test_lsn_from_hex_with_prefix() {
let lsn = Lsn::from_hex("0x00000027000000680004").unwrap();
assert_eq!(lsn.to_hex(), "0x00000027000000680004");
}
#[test]
fn test_lsn_from_hex_without_prefix() {
let lsn = Lsn::from_hex("00000027000000680004").unwrap();
assert_eq!(lsn.to_hex(), "0x00000027000000680004");
}
#[test]
fn test_lsn_from_hex_invalid_length() {
let result = Lsn::from_hex("0x123");
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("must be 20 characters"));
}
#[test]
fn test_lsn_from_hex_invalid_chars() {
let result = Lsn::from_hex("0xGGGGGGGGGGGGGGGGGGGG");
assert!(result.is_err());
}
#[test]
fn test_lsn_from_bytes() {
let bytes = vec![0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x00, 0x68, 0x00, 0x04];
let lsn = Lsn::from_bytes(&bytes).unwrap();
assert_eq!(lsn.to_hex(), "0x00000027000000680004");
}
#[test]
fn test_lsn_from_bytes_invalid_length() {
let bytes = vec![0x00, 0x01, 0x02];
let result = Lsn::from_bytes(&bytes);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("must be exactly 10 bytes"));
}
#[test]
fn test_lsn_roundtrip() {
let original = Lsn::from_hex("0x00000027000000680004").unwrap();
let bytes = original.to_bytes();
let restored = Lsn::from_bytes(&bytes).unwrap();
assert_eq!(original, restored);
}
#[test]
fn test_lsn_ordering() {
let lsn1 = Lsn::from_hex("0x00000027000000680004").unwrap();
let lsn2 = Lsn::from_hex("0x00000027000000680005").unwrap();
let lsn3 = Lsn::from_hex("0x00000028000000000000").unwrap();
assert!(lsn1 < lsn2);
assert!(lsn2 < lsn3);
assert!(lsn1 < lsn3);
}
#[test]
fn test_lsn_equality() {
let lsn1 = Lsn::from_hex("0x00000027000000680004").unwrap();
let lsn2 = Lsn::from_hex("0x00000027000000680004").unwrap();
let lsn3 = Lsn::from_hex("0x00000027000000680005").unwrap();
assert_eq!(lsn1, lsn2);
assert_ne!(lsn1, lsn3);
}
#[test]
fn test_lsn_display() {
let lsn = Lsn::from_hex("0x00000027000000680004").unwrap();
assert_eq!(format!("{lsn}"), "0x00000027000000680004");
}
}