use crate::{ amount::{AmountCents, Error}, Amount, MaxLengthString, }; use serde::{de, Serialize, Serializer}; use std::{ fmt::{self, Display}, str::FromStr, }; #[derive(Debug, Hash, PartialEq, Eq, Clone)] /// An asset type pub struct Asset { /// The name of the asset pub name: MaxLengthString<10>, /// The precision of the asset pub precision: u8, } impl FromStr for Asset { type Err = Error; fn from_str(s: &str) -> Result { let mut split = s.splitn(2, '/'); let name = split .next() .ok_or_else(|| Error::InvalidAssetName(s.to_owned()))?; let precision = split .next() .ok_or_else(|| Error::InvalidAssetName(s.to_owned()))? .parse::() .map_err(|_| Error::NoANumber(s.to_owned()))?; Ok(Asset { name: MaxLengthString::new(name.to_owned()) .map_err(|_| Error::InvalidAssetName(name.to_owned()))?, precision, }) } } impl Display for Asset { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}/{}", self.name, self.precision) } } impl Asset { /// Creates a new amount from a given amount in cents pub fn new_amount(&self, cents: AmountCents) -> Amount { Amount::new(self.clone(), cents) } /// Creates a new amount from a human amount (a floating number serialized /// as string to avoid loss precision) pub fn from_human(&self, human_amount: &str) -> Result { let mut dot_at = None; for (pos, i) in human_amount.chars().enumerate() { match i { '-' => { if pos != 0 { return Err(Error::NoANumber(human_amount.to_owned())); } } '.' => { if dot_at.is_some() { return Err(Error::NoANumber(human_amount.to_owned())); } dot_at = Some(pos); } '0'..='9' => {} _ => { return Err(Error::NoANumber(human_amount.to_owned())); } } } let (whole, fractional_part) = if let Some(dot_at) = dot_at { let (whole, fractional_part) = human_amount.split_at(dot_at); (whole, fractional_part[1..].to_owned()) } else { (human_amount, "".to_owned()) }; let fractional_part = fractional_part + &"0".repeat(self.precision.into()); let cents = (whole.to_owned() + &fractional_part[..self.precision.into()]) .parse::() .map_err(|_| Error::NoANumber(format!("{}.{}", whole, fractional_part)))?; Ok(Amount::new(self.clone(), cents)) } } impl Serialize for Asset { fn serialize(&self, serializer: S) -> Result where S: Serializer, { serializer.serialize_str(&format!("{}/{}", self.name, self.precision)) } } struct AssetVisitor; impl<'de> de::Visitor<'de> for AssetVisitor { type Value = Asset; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("a string in the format 'name/precision'") } fn visit_str(self, value: &str) -> Result where E: de::Error, { value .parse::() .map_err(|e| de::Error::custom(e.to_string())) } } impl<'de> de::Deserialize<'de> for Asset { fn deserialize(deserializer: D) -> Result where D: de::Deserializer<'de>, { deserializer.deserialize_str(AssetVisitor) } }