psibase/
account_number.rs

1use crate::{serialize_as_str, Pack, ToKey, ToSchema, Unpack};
2use custom_error::custom_error;
3use psibase_names::{account_number_from_str, account_number_to_string};
4use std::{num::ParseIntError, str::FromStr};
5
6custom_error! { pub AccountNumberError
7    Invalid{s:String} = "Invalid AccountNumber {s}",
8}
9
10/// An account number.
11///
12/// The `AccountNumber` is used to reference accounts in psibase. This type
13/// is a convenient handler to allow consumers to parse and convert their readable
14/// names.
15///
16/// # Examples
17///
18/// You can create an `AccountNumber` from [a literal string][`&str`] with [`AccountNumber::from`]:
19///
20/// ```
21/// use psibase::AccountNumber;
22/// let hello = AccountNumber::from("hello");
23/// ```
24#[derive(Debug, Default, PartialEq, Eq, Copy, Clone, Hash, Pack, Unpack, ToKey, ToSchema)]
25#[fracpack(
26    definition_will_not_change,
27    fracpack_mod = "fracpack",
28    custom = "AccountNumber"
29)]
30#[to_key(psibase_mod = "crate")]
31pub struct AccountNumber {
32    pub value: u64,
33}
34
35serialize_as_str!(AccountNumber, "account number");
36
37impl AccountNumber {
38    pub const fn new(value: u64) -> Self {
39        AccountNumber { value }
40    }
41
42    pub fn from_exact(s: &str) -> Result<Self, AccountNumberError> {
43        let result: Self = s.into();
44        if result.to_string() != s {
45            return Err(AccountNumberError::Invalid { s: s.into() });
46        }
47        Ok(result)
48    }
49}
50
51impl From<u64> for AccountNumber {
52    fn from(n: u64) -> Self {
53        AccountNumber { value: n }
54    }
55}
56
57impl From<ExactAccountNumber> for AccountNumber {
58    fn from(n: ExactAccountNumber) -> Self {
59        AccountNumber { value: n.value }
60    }
61}
62
63impl FromStr for AccountNumber {
64    type Err = ParseIntError;
65
66    fn from_str(s: &str) -> Result<Self, Self::Err> {
67        Ok(AccountNumber {
68            value: account_number_from_str(s),
69        })
70    }
71}
72
73impl From<&str> for AccountNumber {
74    fn from(s: &str) -> Self {
75        AccountNumber::from_str(s).unwrap()
76    }
77}
78
79impl std::fmt::Display for AccountNumber {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        f.write_str(account_number_to_string(self.value).as_str())
82    }
83}
84
85/// Like AccountNumber, except FromStr and deserializing JSON require exact round-trip conversion
86#[derive(Debug, Default, PartialEq, Eq, Copy, Clone, Pack, Unpack, ToKey)]
87#[fracpack(
88    definition_will_not_change,
89    fracpack_mod = "fracpack",
90    custom = "AccountNumber"
91)]
92#[to_key(psibase_mod = "crate")]
93pub struct ExactAccountNumber {
94    pub value: u64,
95}
96
97serialize_as_str!(ExactAccountNumber, "account number");
98
99impl ExactAccountNumber {
100    pub fn new(value: u64) -> Self {
101        ExactAccountNumber { value }
102    }
103}
104
105impl From<u64> for ExactAccountNumber {
106    fn from(n: u64) -> Self {
107        ExactAccountNumber { value: n }
108    }
109}
110
111impl From<AccountNumber> for ExactAccountNumber {
112    fn from(n: AccountNumber) -> Self {
113        ExactAccountNumber { value: n.value }
114    }
115}
116
117impl FromStr for ExactAccountNumber {
118    type Err = AccountNumberError;
119
120    fn from_str(s: &str) -> Result<Self, Self::Err> {
121        Ok(ExactAccountNumber {
122            value: AccountNumber::from_exact(s)?.value,
123        })
124    }
125}
126
127impl std::fmt::Display for ExactAccountNumber {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        f.write_str(account_number_to_string(self.value).as_str())
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn empty_name_is_zero() {
139        assert_eq!(AccountNumber::from_str("").unwrap(), AccountNumber::new(0));
140    }
141
142    #[test]
143    fn name_longer_than_limit_of_18_is_zero() {
144        assert_eq!(
145            AccountNumber::from_str("abcdefghijklmnopqrstuvwxyz").unwrap(),
146            AccountNumber::new(0)
147        );
148    }
149
150    #[test]
151    fn name_starting_with_a_char_less_than_9_returns_zero() {
152        assert_eq!(AccountNumber::from_str("9").unwrap(), AccountNumber::new(0));
153        assert_eq!(AccountNumber::from_str("3").unwrap(), AccountNumber::new(0));
154        assert_eq!(
155            AccountNumber::from_str("1abc").unwrap(),
156            AccountNumber::new(0)
157        );
158        assert_eq!(
159            AccountNumber::from_str("1234").unwrap(),
160            AccountNumber::new(0)
161        );
162        assert_eq!(
163            AccountNumber::from_str("9asdf").unwrap(),
164            AccountNumber::new(0)
165        );
166        assert_ne!(
167            AccountNumber::from_str("abcd").unwrap(),
168            AccountNumber::new(0)
169        );
170        assert_ne!(
171            AccountNumber::from_str("asdf9").unwrap(),
172            AccountNumber::new(0)
173        );
174        assert_ne!(
175            AccountNumber::from_str("abc1").unwrap(),
176            AccountNumber::new(0)
177        );
178    }
179
180    #[test]
181    fn any_unknown_char_returns_zero() {
182        assert_eq!(AccountNumber::from_str("?").unwrap(), AccountNumber::new(0));
183        assert_eq!(
184            AccountNumber::from_str("what?").unwrap(),
185            AccountNumber::new(0)
186        );
187        assert_eq!(
188            AccountNumber::from_str("?what").unwrap(),
189            AccountNumber::new(0)
190        );
191        assert_eq!(
192            AccountNumber::from_str("eaorintsl?").unwrap(),
193            AccountNumber::new(0)
194        );
195        assert_eq!(
196            AccountNumber::from_str("????").unwrap(),
197            AccountNumber::new(0)
198        );
199    }
200
201    #[test]
202    fn returns_proper_numbers_from_str() {
203        assert_eq!(
204            AccountNumber::from_str("a").unwrap(),
205            AccountNumber::new(49158)
206        );
207        assert_eq!(
208            AccountNumber::from_str("b").unwrap(),
209            AccountNumber::new(184)
210        );
211        assert_eq!(
212            AccountNumber::from_str("c").unwrap(),
213            AccountNumber::new(16538)
214        );
215        assert_eq!(
216            AccountNumber::from_str("abc123").unwrap(),
217            AccountNumber::new(1754468116)
218        );
219        assert_eq!(
220            AccountNumber::from_str("spiderman").unwrap(),
221            AccountNumber::new(483466201442)
222        );
223        assert_eq!(
224            AccountNumber::from_str("brucewayne").unwrap(),
225            AccountNumber::new(132946582102463)
226        );
227        assert_eq!(
228            AccountNumber::from_str("anthonystark").unwrap(),
229            AccountNumber::new(183678712946955)
230        );
231        assert_eq!(
232            AccountNumber::from_str("natasharomanoff").unwrap(),
233            AccountNumber::new(5818245174062392369)
234        );
235    }
236
237    #[test]
238    fn name_number_value_to_string_is_converted_successfully() {
239        let name = AccountNumber::from_str("a").unwrap();
240        assert_eq!(name.value, 49158);
241        assert_eq!(name.to_string(), "a");
242
243        let name = AccountNumber::from_str("b").unwrap();
244        assert_eq!(name.to_string(), "b");
245        let name = AccountNumber::from(184);
246        assert_eq!(name.to_string(), "b");
247
248        let name = AccountNumber::from_str("c").unwrap();
249        assert_eq!(name.to_string(), "c");
250        let name = AccountNumber::from(16538);
251        assert_eq!(name.to_string(), "c");
252
253        let name = AccountNumber::from_str("abc123").unwrap();
254        assert_eq!(name.to_string(), "abc123");
255        let name = AccountNumber::from(1754468116);
256        assert_eq!(name.to_string(), "abc123");
257
258        let name = AccountNumber::from_str("spiderman").unwrap();
259        assert_eq!(name.to_string(), "spiderman");
260        let name = AccountNumber::from(483466201442);
261        assert_eq!(name.to_string(), "spiderman");
262
263        let name = AccountNumber::from(0);
264        assert_eq!(name.to_string(), "");
265    }
266}