use std::fmt;
use serde::de::{self, Deserializer, Visitor};
use serde::{Deserialize, Serialize};
use super::error::ConfError;
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct TokenComponent {
pub signum: i8,
pub digits: String,
}
impl TokenComponent {
pub fn parse(raw: &str) -> Result<Self, ConfError> {
if raw.is_empty() {
return Err(ConfError::BadToken {
value: raw.to_string(),
reason: "empty token component".to_string(),
});
}
let (signum, digits): (i8, &str) = if let Some(rest) = raw.strip_prefix('-') {
if rest.is_empty() {
return Err(ConfError::BadToken {
value: raw.to_string(),
reason: "lone minus sign".to_string(),
});
}
(-1, rest)
} else if raw == "0" {
return Ok(Self {
signum: 0,
digits: "0".to_string(),
});
} else {
(1, raw)
};
if !digits.bytes().all(|b| b.is_ascii_digit()) {
return Err(ConfError::BadToken {
value: raw.to_string(),
reason: "non-digit character in token".to_string(),
});
}
Ok(Self {
signum,
digits: digits.to_string(),
})
}
}
impl fmt::Display for TokenComponent {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.signum < 0 {
f.write_str("-")?;
}
f.write_str(&self.digits)
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
pub struct TokenList {
components: Vec<TokenComponent>,
raw: String,
}
impl TokenList {
pub fn parse(raw: &str) -> Result<Self, ConfError> {
if raw.is_empty() {
return Err(ConfError::BadToken {
value: raw.to_string(),
reason: "empty token list".to_string(),
});
}
let mut components = Vec::new();
for piece in raw.split(',') {
components.push(TokenComponent::parse(piece)?);
}
Ok(Self {
components,
raw: raw.to_string(),
})
}
pub fn components(&self) -> &[TokenComponent] {
&self.components
}
pub fn len(&self) -> usize {
self.components.len()
}
pub fn is_empty(&self) -> bool {
self.components.is_empty()
}
pub fn raw(&self) -> &str {
&self.raw
}
}
impl fmt::Display for TokenList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (i, c) in self.components.iter().enumerate() {
if i > 0 {
f.write_str(",")?;
}
c.fmt(f)?;
}
Ok(())
}
}
impl Serialize for TokenList {
fn serialize<S: serde::Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> {
ser.collect_str(self)
}
}
impl<'de> Deserialize<'de> for TokenList {
fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> {
struct V;
impl Visitor<'_> for V {
type Value = TokenList;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a comma-separated big-integer token list")
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
TokenList::parse(v).map_err(|e| E::custom(e.to_string()))
}
fn visit_string<E: de::Error>(self, v: String) -> Result<Self::Value, E> {
self.visit_str(&v)
}
fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
self.visit_str(&v.to_string())
}
fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
self.visit_str(&v.to_string())
}
}
de.deserialize_any(V)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn single_token() {
let t = TokenList::parse("101134286").unwrap();
assert_eq!(t.len(), 1);
assert_eq!(t.components()[0].signum, 1);
assert_eq!(t.components()[0].digits, "101134286");
}
#[test]
fn comma_separated() {
let t = TokenList::parse("0,1,2,4294967295").unwrap();
assert_eq!(t.len(), 4);
assert_eq!(t.to_string(), "0,1,2,4294967295");
}
#[test]
fn negative_token() {
let t = TokenList::parse("-7").unwrap();
assert_eq!(t.components()[0].signum, -1);
assert_eq!(t.components()[0].digits, "7");
assert_eq!(t.to_string(), "-7");
}
#[test]
fn zero_normalised() {
let t = TokenList::parse("0").unwrap();
assert_eq!(t.components()[0].signum, 0);
}
#[test]
fn empty_rejected() {
assert!(TokenList::parse("").is_err());
}
#[test]
fn non_digit_rejected() {
assert!(TokenList::parse("12a").is_err());
}
#[test]
fn lone_minus_rejected() {
assert!(TokenList::parse("-").is_err());
}
#[test]
fn empty_component_rejected() {
assert!(TokenList::parse("1,,2").is_err());
}
}