p2panda_rs/entry/
log_id.rs1use std::convert::TryFrom;
4use std::hash::Hash as StdHash;
5use std::str::FromStr;
6
7use serde::{Deserialize, Serialize};
8
9use crate::entry::error::LogIdError;
10use crate::serde::StringOrU64;
11
12#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, StdHash)]
14pub struct LogId(u64);
15
16impl LogId {
17 pub fn new(value: u64) -> Self {
19 Self(value)
20 }
21
22 pub fn as_u64(&self) -> u64 {
24 self.0
25 }
26}
27
28impl Default for LogId {
29 fn default() -> Self {
30 Self::new(0)
31 }
32}
33
34impl Iterator for LogId {
35 type Item = LogId;
36
37 fn next(&mut self) -> Option<Self::Item> {
38 match self.0 == std::u64::MAX {
39 true => None,
40 false => {
41 self.0 += 1;
42 Some(*self)
43 }
44 }
45 }
46}
47
48impl From<u64> for LogId {
49 fn from(value: u64) -> Self {
50 Self::new(value)
51 }
52}
53
54impl FromStr for LogId {
56 type Err = LogIdError;
57
58 fn from_str(s: &str) -> Result<Self, Self::Err> {
59 Ok(Self::new(
60 u64::from_str(s).map_err(|_| LogIdError::InvalidU64String)?,
61 ))
62 }
63}
64
65impl TryFrom<String> for LogId {
67 type Error = LogIdError;
68
69 fn try_from(str: String) -> Result<Self, Self::Error> {
70 Ok(Self::new(
71 u64::from_str(&str).map_err(|_| LogIdError::InvalidU64String)?,
72 ))
73 }
74}
75
76impl<'de> Deserialize<'de> for LogId {
77 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
78 where
79 D: serde::Deserializer<'de>,
80 {
81 deserializer.deserialize_any(StringOrU64::<LogId>::new())
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use std::convert::TryFrom;
88
89 use rstest::rstest;
90 use serde::Serialize;
91
92 use super::LogId;
93
94 #[test]
95 fn log_ids() {
96 let mut log_id = LogId::default();
97
98 let mut next_log_id = log_id.next().unwrap();
99 assert_eq!(next_log_id, LogId::new(1));
100
101 let next_log_id = next_log_id.next().unwrap();
102 assert_eq!(next_log_id, LogId::new(2));
103 }
104
105 #[test]
106 fn iterator() {
107 let mut log_id = LogId::default();
108
109 assert_eq!(Some(LogId(1)), log_id.next());
110 assert_eq!(Some(LogId(2)), log_id.next());
111 assert_eq!(Some(LogId(3)), log_id.next());
112
113 let mut log_id = LogId(std::u64::MAX - 1);
114
115 assert_eq!(Some(LogId(std::u64::MAX)), log_id.next());
116 assert_eq!(None, log_id.next());
117 }
118
119 #[test]
120 fn string_conversions() {
121 let large_number = "291919188205818203";
122 let log_id_from_str: LogId = large_number.parse().unwrap();
123 let log_id_try_from = LogId::try_from(String::from(large_number)).unwrap();
124 assert_eq!(291919188205818203, log_id_from_str.as_u64());
125 assert_eq!(log_id_from_str, log_id_try_from);
126 }
127
128 #[rstest]
129 #[case("0", Some(LogId::new(0)))]
130 #[case(12, Some(LogId::new(12)))]
131 #[case("12", Some(LogId::new(12)))]
132 #[case(u64::MAX, Some(LogId::new(u64::MAX)))]
133 #[case("18446744073709551616", None)] #[case(-12, None)]
135 #[case("-12", None)]
136 #[case("Not a log id", None)]
137 fn deserialize_str_and_u64(
138 #[case] value: impl Serialize + Sized,
139 #[case] expected_result: Option<LogId>,
140 ) {
141 fn convert<T: Serialize + Sized>(value: T) -> Result<LogId, Box<dyn std::error::Error>> {
142 let mut cbor_bytes = Vec::new();
143 ciborium::ser::into_writer(&value, &mut cbor_bytes)?;
144 let log_id: LogId = ciborium::de::from_reader(&cbor_bytes[..])?;
145 Ok(log_id)
146 }
147
148 match expected_result {
149 Some(result) => {
150 assert_eq!(convert(value).unwrap(), result);
151 }
152 None => {
153 assert!(convert(value).is_err());
154 }
155 }
156 }
157}