p2panda_rs/entry/
seq_num.rs1use std::convert::TryFrom;
4use std::hash::Hash as StdHash;
5use std::str::FromStr;
6
7use bamboo_rs_core_ed25519_yasmf::lipmaa;
8use serde::{Deserialize, Serialize};
9
10use crate::entry::error::SeqNumError;
11use crate::serde::StringOrU64;
12
13pub const FIRST_SEQ_NUM: u64 = 1;
15
16#[derive(Clone, Copy, Debug, Serialize, Eq, PartialEq, Ord, PartialOrd, StdHash)]
18pub struct SeqNum(u64);
19
20impl SeqNum {
21 pub fn new(value: u64) -> Result<Self, SeqNumError> {
37 if value < FIRST_SEQ_NUM {
38 Err(SeqNumError::NotZeroOrNegative)
39 } else {
40 Ok(Self(value))
41 }
42 }
43
44 pub fn backlink_seq_num(&self) -> Option<Self> {
62 Self::new(self.0 - 1).ok()
63 }
64
65 pub fn skiplink_seq_num(&self) -> Option<Self> {
71 Some(Self(lipmaa(self.0)))
72 }
73
74 pub fn is_first(&self) -> bool {
76 self.0 == FIRST_SEQ_NUM
77 }
78
79 pub fn as_u64(&self) -> u64 {
81 self.0
82 }
83}
84
85impl Default for SeqNum {
86 fn default() -> Self {
87 Self::new(FIRST_SEQ_NUM).unwrap()
88 }
89}
90
91impl Iterator for SeqNum {
92 type Item = SeqNum;
93
94 fn next(&mut self) -> Option<Self::Item> {
95 match self.0 == std::u64::MAX {
96 true => None,
97 false => {
98 self.0 += 1;
99 Some(*self)
100 }
101 }
102 }
103}
104
105impl TryFrom<u64> for SeqNum {
106 type Error = SeqNumError;
107
108 fn try_from(value: u64) -> Result<Self, Self::Error> {
109 Self::new(value)
110 }
111}
112
113impl FromStr for SeqNum {
115 type Err = SeqNumError;
116
117 fn from_str(s: &str) -> Result<Self, Self::Err> {
118 Self::new(u64::from_str(s).map_err(|_| SeqNumError::InvalidU64String)?)
119 }
120}
121
122impl TryFrom<String> for SeqNum {
124 type Error = SeqNumError;
125
126 fn try_from(str: String) -> Result<Self, Self::Error> {
127 Self::new(u64::from_str(&str).map_err(|_| SeqNumError::InvalidU64String)?)
128 }
129}
130
131impl<'de> Deserialize<'de> for SeqNum {
132 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
133 where
134 D: serde::Deserializer<'de>,
135 {
136 deserializer.deserialize_any(StringOrU64::<SeqNum>::new())
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use std::convert::TryFrom;
143
144 use rstest::rstest;
145 use serde::Serialize;
146
147 use super::SeqNum;
148
149 #[test]
150 fn validate() {
151 assert!(SeqNum::new(0).is_err());
152 assert!(SeqNum::new(100).is_ok());
153 }
154
155 #[test]
156 fn iterator() {
157 let mut seq_num = SeqNum::new(1).unwrap();
158
159 assert_eq!(Some(SeqNum(2)), seq_num.next());
160 assert_eq!(Some(SeqNum(3)), seq_num.next());
161
162 seq_num = SeqNum(std::u64::MAX - 1);
163
164 assert_eq!(Some(SeqNum(std::u64::MAX)), seq_num.next());
165 assert_eq!(None, seq_num.next());
166 }
167
168 #[test]
169 fn skiplink_seq_num() {
170 assert_eq!(
171 SeqNum::new(13).unwrap().skiplink_seq_num().unwrap(),
172 SeqNum::new(4).unwrap()
173 );
174 }
175
176 #[test]
177 fn backlink_seq_num() {
178 assert_eq!(
179 SeqNum::new(12).unwrap().backlink_seq_num().unwrap(),
180 SeqNum::new(11).unwrap()
181 );
182
183 assert!(SeqNum::new(1).unwrap().backlink_seq_num().is_none());
184 }
185
186 #[test]
187 fn string_conversions() {
188 let large_number = "91772991776239";
189 let seq_num_from_str: SeqNum = large_number.parse().unwrap();
190 let seq_num_try_from = SeqNum::try_from(String::from(large_number)).unwrap();
191 assert_eq!(91772991776239, seq_num_from_str.as_u64());
192 assert_eq!(seq_num_from_str, seq_num_try_from);
193 }
194
195 #[rstest]
196 #[case("1", Some(SeqNum::new(1).unwrap()))]
197 #[case(12, Some(SeqNum::new(12).unwrap()))]
198 #[case("-1", None)]
199 #[case(-12, None)]
200 #[case("18446744073709551616", None)] #[case("0", None)]
202 #[case("Not a sequence number", None)]
203 fn deserialize_str_and_u64(
204 #[case] value: impl Serialize + Sized,
205 #[case] expected_result: Option<SeqNum>,
206 ) {
207 fn convert<T: Serialize + Sized>(value: T) -> Result<SeqNum, Box<dyn std::error::Error>> {
208 let mut cbor_bytes = Vec::new();
209 ciborium::ser::into_writer(&value, &mut cbor_bytes)?;
210 let log_id: SeqNum = ciborium::de::from_reader(&cbor_bytes[..])?;
211 Ok(log_id)
212 }
213
214 match expected_result {
215 Some(result) => {
216 assert_eq!(convert(value).unwrap(), result);
217 }
218 None => {
219 assert!(convert(value).is_err());
220 }
221 }
222 }
223}