use crate::error::TypeError;
const MAX_DIGITS: usize = 8;
fn hex_len(b: &[u8]) -> usize {
b.iter().take_while(|c| c.is_ascii_hexdigit()).count()
}
pub fn pg_lsn_in(s: &str) -> Result<u64, TypeError> {
let b = s.as_bytes();
let syntax = || TypeError::syntax("pg_lsn", s);
let high = hex_len(b);
if !(1..=MAX_DIGITS).contains(&high) || b.get(high) != Some(&b'/') {
return Err(syntax());
}
let low = hex_len(&b[high + 1..]);
if !(1..=MAX_DIGITS).contains(&low) || high + 1 + low != b.len() {
return Err(syntax());
}
let part = |text: &str| u64::from_str_radix(text, 16).map_err(|_| syntax());
Ok((part(&s[..high])? << 32) | part(&s[high + 1..])?)
}
pub fn pg_lsn_out(lsn: u64, out: &mut Vec<u8>) {
let text = format!("{:X}/{:08X}", lsn >> 32, lsn & 0xFFFF_FFFF);
out.extend_from_slice(text.as_bytes());
}
#[cfg(test)]
mod tests {
use super::*;
fn text(lsn: u64) -> String {
let mut out = Vec::new();
pg_lsn_out(lsn, &mut out);
String::from_utf8(out).unwrap()
}
#[test]
fn text_forms() {
assert_eq!(text(pg_lsn_in("16/B374D848").unwrap()), "16/B374D848");
assert_eq!(text(pg_lsn_in("0/0").unwrap()), "0/00000000");
assert_eq!(text(pg_lsn_in("ff/ab").unwrap()), "FF/000000AB");
assert_eq!(pg_lsn_in("FFFFFFFF/FFFFFFFF").unwrap(), u64::MAX);
for bad in ["16/B374D848x", "1/100000000", " 1/1", "/1", "1/", "1", ""] {
let error = pg_lsn_in(bad).unwrap_err();
assert_eq!(error.message, format!("invalid input syntax for type pg_lsn: \"{bad}\""));
}
}
}