1use serde::{Deserialize, Serialize};
4use std::fmt;
5use std::str::FromStr;
6
7#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
9pub struct Lsn(pub u64);
10
11impl Lsn {
12 pub const ZERO: Lsn = Lsn(0);
13
14 pub fn as_u64(self) -> u64 {
15 self.0
16 }
17
18 pub fn is_zero(self) -> bool {
19 self.0 == 0
20 }
21
22 pub fn bytes_since(self, other: Lsn) -> u64 {
24 self.0.saturating_sub(other.0)
25 }
26
27 pub fn saturating_add(self, n: u64) -> Lsn {
28 Lsn(self.0.saturating_add(n))
29 }
30}
31
32impl fmt::Display for Lsn {
33 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34 write!(f, "{:X}/{:X}", self.0 >> 32, self.0 as u32)
35 }
36}
37
38impl fmt::Debug for Lsn {
39 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40 write!(f, "Lsn({self})")
41 }
42}
43
44#[derive(Debug, thiserror::Error, PartialEq, Eq)]
45#[error("invalid LSN {0:?}: expected hexadecimal `HIGH/LOW`")]
46pub struct ParseLsnError(pub String);
47
48impl FromStr for Lsn {
49 type Err = ParseLsnError;
50
51 fn from_str(s: &str) -> Result<Self, Self::Err> {
52 let (hi, lo) = s
53 .split_once('/')
54 .ok_or_else(|| ParseLsnError(s.to_owned()))?;
55 if hi.is_empty() || lo.is_empty() || hi.len() > 8 || lo.len() > 8 {
56 return Err(ParseLsnError(s.to_owned()));
57 }
58 let hi = u32::from_str_radix(hi, 16).map_err(|_| ParseLsnError(s.to_owned()))?;
59 let lo = u32::from_str_radix(lo, 16).map_err(|_| ParseLsnError(s.to_owned()))?;
60 Ok(Lsn(((hi as u64) << 32) | lo as u64))
61 }
62}
63
64impl From<u64> for Lsn {
65 fn from(v: u64) -> Self {
66 Lsn(v)
67 }
68}
69
70impl Serialize for Lsn {
71 fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
72 s.collect_str(self)
73 }
74}
75
76impl<'de> Deserialize<'de> for Lsn {
77 fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
78 let s = String::deserialize(d)?;
79 s.parse().map_err(serde::de::Error::custom)
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86
87 #[test]
88 fn parses_and_formats_round_trip() {
89 for s in ["0/0", "0/16B3748", "1/0", "FFFFFFFF/FFFFFFFF", "A/B"] {
90 let lsn: Lsn = s.parse().unwrap();
91 assert_eq!(lsn.to_string(), s, "round trip of {s}");
92 }
93 }
94
95 #[test]
96 fn parses_numeric_value_correctly() {
97 assert_eq!("0/16B3748".parse::<Lsn>().unwrap(), Lsn(0x16B3748));
98 assert_eq!("1/0".parse::<Lsn>().unwrap(), Lsn(1 << 32));
99 assert_eq!("1/5".parse::<Lsn>().unwrap(), Lsn((1 << 32) + 5));
100 }
101
102 #[test]
103 fn rejects_malformed() {
104 for s in [
105 "",
106 "/",
107 "1",
108 "1/",
109 "/1",
110 "G/1",
111 "1/G",
112 "123456789/0",
113 "1/2/3",
114 "0x1/0",
115 ] {
116 assert!(s.parse::<Lsn>().is_err(), "should reject {s:?}");
117 }
118 }
119
120 #[test]
121 fn ordering_follows_numeric_value() {
122 let a: Lsn = "0/FFFFFFFF".parse().unwrap();
123 let b: Lsn = "1/0".parse().unwrap();
124 assert!(a < b);
125 assert_eq!(b.bytes_since(a), 1);
126 assert_eq!(a.bytes_since(b), 0);
127 assert_eq!(Lsn::ZERO.max(a), a);
128 }
129
130 #[test]
131 fn serde_uses_text_form() {
132 let lsn: Lsn = "0/16B3748".parse().unwrap();
133 let json = serde_json::to_string(&lsn).unwrap();
134 assert_eq!(json, "\"0/16B3748\"");
135 let back: Lsn = serde_json::from_str(&json).unwrap();
136 assert_eq!(back, lsn);
137 assert!(serde_json::from_str::<Lsn>("\"nope\"").is_err());
138 }
139}