use std::error;
use std::fmt;
use std::fmt::Write;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum Denomination {
Handshake,
DollaryDoo,
}
impl Denomination {
fn precision(self) -> u32 {
match self {
Denomination::DollaryDoo => 0,
Denomination::Handshake => 6,
}
}
}
impl fmt::Display for Denomination {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match *self {
Denomination::DollaryDoo => "dollarydoo",
Denomination::Handshake => "HNS",
})
}
}
impl FromStr for Denomination {
type Err = ParseAmountError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"dollarydoo" => Ok(Denomination::DollaryDoo),
"HNS" => Ok(Denomination::Handshake),
d => Err(ParseAmountError::UnknownDenomination(d.to_owned())),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseAmountError {
TooBig,
TooPrecise,
InvalidFormat,
InputTooLarge,
InvalidCharacter(char),
UnknownDenomination(String),
}
impl fmt::Display for ParseAmountError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let desc = ::std::error::Error::description(self);
match *self {
ParseAmountError::InvalidCharacter(c) => write!(f, "{}: {}", desc, c),
ParseAmountError::UnknownDenomination(ref d) => write!(f, "{}: {}", desc, d),
_ => f.write_str(desc),
}
}
}
impl error::Error for ParseAmountError {
fn cause(&self) -> Option<&error::Error> {
None
}
fn description(&self) -> &'static str {
match *self {
ParseAmountError::TooBig => "amount is too big",
ParseAmountError::TooPrecise => "amount has a too high precision",
ParseAmountError::InvalidFormat => "invalid number format",
ParseAmountError::InputTooLarge => "input string was too large",
ParseAmountError::InvalidCharacter(_) => "invalid character in input",
ParseAmountError::UnknownDenomination(_) => "unknown denomination",
}
}
}
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
pub struct Amount(u64);
impl Amount {
pub const ZERO: Amount = Amount(0);
pub const ONE_DOO: Amount = Amount(1);
pub const ONE_HNS: Amount = Amount(1_000_000);
pub fn from_doos(dollary_doo: u64) -> Amount {
Amount(dollary_doo)
}
pub fn as_doos(self) -> u64 {
self.0
}
pub fn min_value() -> Amount {
Amount(u64::min_value())
}
pub fn from_hns(hns: f64) -> Result<Amount, ParseAmountError> {
Amount::from_float_in(hns, Denomination::Handshake)
}
pub fn from_str_in(mut s: &str, denom: Denomination) -> Result<Amount, ParseAmountError> {
if s.len() == 0 {
return Err(ParseAmountError::InvalidFormat);
}
if s.len() > 50 {
return Err(ParseAmountError::InputTooLarge);
}
let max_decimals = {
let precision_diff = denom.precision();
if precision_diff > 0 {
let last_n = precision_diff as usize;
if s.contains(".") || s.chars().rev().take(last_n).any(|d| d != '0') {
return Err(ParseAmountError::TooPrecise);
}
s = &s[0..s.len() - last_n];
0
} else {
precision_diff
}
};
let mut decimals = None;
let mut value: u64 = 0; for c in s.chars() {
match c {
'0'...'9' => {
match 10_u64.checked_mul(value) {
None => return Err(ParseAmountError::TooBig),
Some(val) => match val.checked_add((c as u8 - b'0') as u64) {
None => return Err(ParseAmountError::TooBig),
Some(val) => value = val,
},
}
decimals = match decimals {
None => None,
Some(d) if d < max_decimals => Some(d + 1),
_ => return Err(ParseAmountError::TooPrecise),
};
}
'.' => match decimals {
None => decimals = Some(0),
_ => return Err(ParseAmountError::InvalidFormat),
},
c => return Err(ParseAmountError::InvalidCharacter(c)),
}
}
let scale_factor = max_decimals - decimals.unwrap_or(0);
for _ in 0..scale_factor {
value = match 10_u64.checked_mul(value) {
Some(v) => v,
None => return Err(ParseAmountError::TooBig),
};
}
Ok(Amount::from_doos(value))
}
pub fn from_str_with_denomination(s: &str) -> Result<Amount, ParseAmountError> {
let mut split = s.splitn(3, " ");
let amt_str = split.next().unwrap();
let denom_str = split.next().ok_or(ParseAmountError::InvalidFormat)?;
if split.next().is_some() {
return Err(ParseAmountError::InvalidFormat);
}
Ok(Amount::from_str_in(amt_str, denom_str.parse()?)?)
}
pub fn to_float_in(&self, denom: Denomination) -> f64 {
(self.as_doos() as f64) * 10_f64.powi(denom.precision() as i32)
}
pub fn as_btc(&self) -> f64 {
self.to_float_in(Denomination::Handshake)
}
pub fn from_float_in(value: f64, denom: Denomination) -> Result<Amount, ParseAmountError> {
Amount::from_str_in(&value.to_string(), denom)
}
pub fn fmt_value_in(&self, f: &mut fmt::Write, denom: Denomination) -> fmt::Result {
if denom.precision() > 0 {
let width = denom.precision() as usize;
write!(f, "{}{:0width$}", self.as_doos(), 0, width = width)?;
} else {
write!(f, "{}", self.as_doos())?;
}
Ok(())
}
pub fn to_string_in(&self, denom: Denomination) -> String {
let mut buf = String::new();
self.fmt_value_in(&mut buf, denom).unwrap();
buf
}
pub fn to_string_with_denomination(&self, denom: Denomination) -> String {
let mut buf = String::new();
self.fmt_value_in(&mut buf, denom).unwrap();
write!(buf, " {}", denom).unwrap();
buf
}
pub fn abs(self) -> Amount {
Amount(self.0)
}
pub fn checked_add(self, rhs: Amount) -> Option<Amount> {
self.0.checked_add(rhs.0).map(Amount)
}
pub fn checked_sub(self, rhs: Amount) -> Option<Amount> {
self.0.checked_sub(rhs.0).map(Amount)
}
pub fn checked_mul(self, rhs: u64) -> Option<Amount> {
self.0.checked_mul(rhs).map(Amount)
}
pub fn checked_div(self, rhs: u64) -> Option<Amount> {
self.0.checked_div(rhs).map(Amount)
}
pub fn checked_rem(self, rhs: u64) -> Option<Amount> {
self.0.checked_rem(rhs).map(Amount)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_denomination_from_string() {
let denom = Denomination::from_str("HNS").unwrap();
assert_eq!(denom, Denomination::Handshake);
let denom = Denomination::from_str("dollarydoo").unwrap();
assert_eq!(denom, Denomination::DollaryDoo);
}
}