p2panda_rs/entry/
log_id.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3use 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/// Authors can write entries to multiple logs identified by log ids.
13#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, StdHash)]
14pub struct LogId(u64);
15
16impl LogId {
17    /// Returns a new `LogId` instance.
18    pub fn new(value: u64) -> Self {
19        Self(value)
20    }
21
22    /// Returns `LogId` as u64 integer.
23    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
54/// Convert any borrowed string representation of an u64 integer into a `LogId` instance.
55impl 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
65/// Convert any owned string representation of an u64 integer into a `LogId` instance.
66impl 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)] // u64::MAX + 1
134    #[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}