use std::fmt;
use std::str::FromStr;
use crate::error::RiegeliError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct RecordPosition {
pub chunk_begin: u64,
pub record_index: u64,
}
impl RecordPosition {
pub fn new(chunk_begin: u64, record_index: u64) -> Self {
Self {
chunk_begin,
record_index,
}
}
pub fn numeric(&self) -> u64 {
self.chunk_begin.saturating_add(self.record_index)
}
}
impl fmt::Display for RecordPosition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}/{}", self.chunk_begin, self.record_index)
}
}
impl FromStr for RecordPosition {
type Err = RiegeliError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let (chunk_str, index_str) = s.split_once('/').ok_or_else(|| {
RiegeliError::MalformedData(
format!("invalid RecordPosition format (expected 'chunk/index'): {s}").into(),
)
})?;
let chunk_begin = chunk_str.parse::<u64>().map_err(|e| {
RiegeliError::MalformedData(
format!("invalid chunk_begin in RecordPosition: {e}").into(),
)
})?;
let record_index = index_str.parse::<u64>().map_err(|e| {
RiegeliError::MalformedData(
format!("invalid record_index in RecordPosition: {e}").into(),
)
})?;
if record_index > u64::MAX - chunk_begin {
return Err(RiegeliError::MalformedData(
format!(
"RecordPosition overflow: chunk_begin {chunk_begin} + record_index {record_index} exceeds u64::MAX"
)
.into(),
));
}
Ok(Self {
chunk_begin,
record_index,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn numeric() {
let pos = RecordPosition::new(100, 5);
assert_eq!(pos.numeric(), 105);
}
#[test]
fn display() {
let pos = RecordPosition::new(24, 0);
assert_eq!(pos.to_string(), "24/0");
let pos2 = RecordPosition::new(65560, 42);
assert_eq!(pos2.to_string(), "65560/42");
}
#[test]
fn from_str_roundtrip() {
let pos = RecordPosition::new(12345, 67);
let s = pos.to_string();
let parsed: RecordPosition = s.parse().expect("parse ok");
assert_eq!(parsed, pos);
}
#[test]
fn from_str_invalid() {
assert!("no-slash".parse::<RecordPosition>().is_err());
assert!("abc/0".parse::<RecordPosition>().is_err());
assert!("0/abc".parse::<RecordPosition>().is_err());
}
#[test]
fn from_str_rejects_numeric_overflow() {
assert!("18446744073709551615/1".parse::<RecordPosition>().is_err());
assert!("18446744073709551615/5".parse::<RecordPosition>().is_err());
assert!("1/18446744073709551615".parse::<RecordPosition>().is_err());
let pos: RecordPosition = "18446744073709551615/0".parse().expect("parse ok");
assert_eq!(pos.numeric(), u64::MAX);
let pos2: RecordPosition = "0/18446744073709551615".parse().expect("parse ok");
assert_eq!(pos2.numeric(), u64::MAX);
}
}