1use std::fmt;
11
12use serde::de::{self, Deserializer, Visitor};
13use serde::{Deserialize, Serialize};
14
15use super::error::ConfError;
16
17#[derive(Debug, Clone, Eq, PartialEq, Hash)]
30pub struct TokenComponent {
31 pub signum: i8,
33 pub digits: String,
35}
36
37impl TokenComponent {
38 pub fn parse(raw: &str) -> Result<Self, ConfError> {
52 if raw.is_empty() {
53 return Err(ConfError::BadToken {
54 value: raw.to_string(),
55 reason: "empty token component".to_string(),
56 });
57 }
58 let (signum, digits): (i8, &str) = if let Some(rest) = raw.strip_prefix('-') {
59 if rest.is_empty() {
60 return Err(ConfError::BadToken {
61 value: raw.to_string(),
62 reason: "lone minus sign".to_string(),
63 });
64 }
65 (-1, rest)
66 } else if raw == "0" {
67 return Ok(Self {
68 signum: 0,
69 digits: "0".to_string(),
70 });
71 } else {
72 (1, raw)
73 };
74 if !digits.bytes().all(|b| b.is_ascii_digit()) {
75 return Err(ConfError::BadToken {
76 value: raw.to_string(),
77 reason: "non-digit character in token".to_string(),
78 });
79 }
80 Ok(Self {
81 signum,
82 digits: digits.to_string(),
83 })
84 }
85}
86
87impl fmt::Display for TokenComponent {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 if self.signum < 0 {
90 f.write_str("-")?;
91 }
92 f.write_str(&self.digits)
93 }
94}
95
96#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
107pub struct TokenList {
108 components: Vec<TokenComponent>,
109 raw: String,
110}
111
112impl TokenList {
113 pub fn parse(raw: &str) -> Result<Self, ConfError> {
125 if raw.is_empty() {
126 return Err(ConfError::BadToken {
127 value: raw.to_string(),
128 reason: "empty token list".to_string(),
129 });
130 }
131 let mut components = Vec::new();
132 for piece in raw.split(',') {
133 components.push(TokenComponent::parse(piece)?);
134 }
135 Ok(Self {
136 components,
137 raw: raw.to_string(),
138 })
139 }
140
141 pub fn components(&self) -> &[TokenComponent] {
151 &self.components
152 }
153
154 pub fn len(&self) -> usize {
163 self.components.len()
164 }
165
166 pub fn is_empty(&self) -> bool {
177 self.components.is_empty()
178 }
179
180 pub fn raw(&self) -> &str {
189 &self.raw
190 }
191}
192
193impl fmt::Display for TokenList {
194 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195 for (i, c) in self.components.iter().enumerate() {
196 if i > 0 {
197 f.write_str(",")?;
198 }
199 c.fmt(f)?;
200 }
201 Ok(())
202 }
203}
204
205impl Serialize for TokenList {
206 fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
207 ser.collect_str(self)
208 }
209}
210
211impl<'de> Deserialize<'de> for TokenList {
212 fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
213 struct V;
214 impl Visitor<'_> for V {
215 type Value = TokenList;
216 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217 f.write_str("a comma-separated big-integer token list")
218 }
219 fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
220 TokenList::parse(v).map_err(|e| E::custom(e.to_string()))
221 }
222 fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
223 self.visit_str(&v)
224 }
225 fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
226 self.visit_str(&v.to_string())
227 }
228 fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
229 self.visit_str(&v.to_string())
230 }
231 }
232 de.deserialize_any(V)
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239
240 #[test]
241 fn single_token() {
242 let t = TokenList::parse("101134286").unwrap();
243 assert_eq!(t.len(), 1);
244 assert_eq!(t.components()[0].signum, 1);
245 assert_eq!(t.components()[0].digits, "101134286");
246 }
247
248 #[test]
249 fn comma_separated() {
250 let t = TokenList::parse("0,1,2,4294967295").unwrap();
251 assert_eq!(t.len(), 4);
252 assert_eq!(t.to_string(), "0,1,2,4294967295");
253 }
254
255 #[test]
256 fn negative_token() {
257 let t = TokenList::parse("-7").unwrap();
258 assert_eq!(t.components()[0].signum, -1);
259 assert_eq!(t.components()[0].digits, "7");
260 assert_eq!(t.to_string(), "-7");
261 }
262
263 #[test]
264 fn zero_normalised() {
265 let t = TokenList::parse("0").unwrap();
266 assert_eq!(t.components()[0].signum, 0);
267 }
268
269 #[test]
270 fn empty_rejected() {
271 assert!(TokenList::parse("").is_err());
272 }
273
274 #[test]
275 fn non_digit_rejected() {
276 assert!(TokenList::parse("12a").is_err());
277 }
278
279 #[test]
280 fn lone_minus_rejected() {
281 assert!(TokenList::parse("-").is_err());
282 }
283
284 #[test]
285 fn empty_component_rejected() {
286 assert!(TokenList::parse("1,,2").is_err());
287 }
288}