use std::cmp::Ordering;
use std::default;
use std::fmt::{self, Write};
use std::ops;
use std::str::FromStr;
use thiserror::Error;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub enum Denomination {
Monero,
Millinero,
Micronero,
Nanonero,
Piconero,
}
impl Denomination {
fn precision(self) -> i32 {
match self {
Denomination::Monero => -12,
Denomination::Millinero => -9,
Denomination::Micronero => -6,
Denomination::Nanonero => -3,
Denomination::Piconero => 0,
}
}
}
impl fmt::Display for Denomination {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str(match *self {
Denomination::Monero => "xmr",
Denomination::Millinero => "millinero",
Denomination::Micronero => "micronero",
Denomination::Nanonero => "nanonero",
Denomination::Piconero => "piconero",
})
}
}
impl FromStr for Denomination {
type Err = ParsingError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"xmr" | "XMR" | "monero" => Ok(Denomination::Monero),
"millinero" | "mXMR" => Ok(Denomination::Millinero),
"micronero" | "µXMR" | "mcXMR" => Ok(Denomination::Micronero),
"nanonero" | "nXMR" => Ok(Denomination::Nanonero),
"piconero" | "pXMR" => Ok(Denomination::Piconero),
d => Err(ParsingError::UnknownDenomination(d.to_owned())),
}
}
}
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum ParsingError {
#[error("Amount is negative")]
Negative,
#[error("Amount is too big to fit inside the type")]
TooBig,
#[error("Amount has higher precision than supported by the type")]
TooPrecise,
#[error("Invalid number format")]
InvalidFormat,
#[error("Input string was too large")]
InputTooLarge,
#[error("Invalid character in input: {0}")]
InvalidCharacter(char),
#[error("The denomination was unknown: {0}")]
UnknownDenomination(String),
}
fn is_too_precise(s: &str, precision: usize) -> bool {
s.contains('.') || precision >= s.len() || s.chars().rev().take(precision).any(|d| d != '0')
}
fn parse_signed_to_piconero(mut s: &str, denom: Denomination) -> Result<(bool, u64), ParsingError> {
if s.is_empty() {
return Err(ParsingError::InvalidFormat);
}
if s.len() > 50 {
return Err(ParsingError::InputTooLarge);
}
let is_negative = s.starts_with('-');
if is_negative {
if s.len() == 1 {
return Err(ParsingError::InvalidFormat);
}
s = &s[1..];
}
let max_decimals = {
let precision_diff = -denom.precision();
if precision_diff < 0 {
let last_n = precision_diff.unsigned_abs() as usize;
if is_too_precise(s, last_n) {
return Err(ParsingError::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(ParsingError::TooBig),
Some(val) => match val.checked_add((c as u8 - b'0') as u64) {
None => return Err(ParsingError::TooBig),
Some(val) => value = val,
},
}
decimals = match decimals {
None => None,
Some(d) if d < max_decimals => Some(d + 1),
_ => return Err(ParsingError::TooPrecise),
};
}
'.' => match decimals {
None => decimals = Some(0),
_ => return Err(ParsingError::InvalidFormat),
},
c => return Err(ParsingError::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(ParsingError::TooBig),
};
}
Ok((is_negative, value))
}
fn fmt_piconero_in(
piconero: u64,
negative: bool,
f: &mut dyn fmt::Write,
denom: Denomination,
) -> fmt::Result {
if negative {
f.write_str("-")?;
}
let precision = denom.precision();
match precision.cmp(&0) {
Ordering::Greater => {
let width = precision as usize;
write!(f, "{}{:0width$}", piconero, 0, width = width)?;
}
Ordering::Less => {
let nb_decimals = precision.unsigned_abs() as usize;
let real = format!("{:0width$}", piconero, width = nb_decimals);
if real.len() == nb_decimals {
write!(f, "0.{}", &real[real.len() - nb_decimals..])?;
} else {
write!(
f,
"{}.{}",
&real[0..(real.len() - nb_decimals)],
&real[real.len() - nb_decimals..]
)?;
}
}
Ordering::Equal => write!(f, "{}", piconero)?,
}
Ok(())
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Amount(u64);
impl Amount {
pub const ZERO: Amount = Amount(0);
pub const ONE_PICO: Amount = Amount(1);
pub const ONE_XMR: Amount = Amount(1_000_000_000_000);
pub fn from_pico(piconero: u64) -> Amount {
Amount(piconero)
}
pub fn as_pico(self) -> u64 {
self.0
}
pub fn max_value() -> Amount {
Amount(u64::max_value())
}
pub fn min_value() -> Amount {
Amount(u64::min_value())
}
pub fn from_xmr(xmr: f64) -> Result<Amount, ParsingError> {
Amount::from_float_in(xmr, Denomination::Monero)
}
pub fn from_str_in(s: &str, denom: Denomination) -> Result<Amount, ParsingError> {
let (negative, piconero) = parse_signed_to_piconero(s, denom)?;
if negative {
return Err(ParsingError::Negative);
}
if piconero > i64::max_value() as u64 {
return Err(ParsingError::TooBig);
}
Ok(Amount::from_pico(piconero))
}
pub fn from_str_with_denomination(s: &str) -> Result<Amount, ParsingError> {
let mut split = s.splitn(3, ' ');
let amt_str = split.next().unwrap();
let denom_str = split.next().ok_or(ParsingError::InvalidFormat)?;
if split.next().is_some() {
return Err(ParsingError::InvalidFormat);
}
Amount::from_str_in(amt_str, denom_str.parse()?)
}
pub fn to_float_in(self, denom: Denomination) -> f64 {
f64::from_str(&self.to_string_in(denom)).unwrap()
}
pub fn as_xmr(self) -> f64 {
self.to_float_in(Denomination::Monero)
}
pub fn from_float_in(value: f64, denom: Denomination) -> Result<Amount, ParsingError> {
if value < 0.0 {
return Err(ParsingError::Negative);
}
Amount::from_str_in(&value.to_string(), denom)
}
pub fn fmt_value_in(self, f: &mut dyn fmt::Write, denom: Denomination) -> fmt::Result {
fmt_piconero_in(self.as_pico(), false, f, denom)
}
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 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)
}
pub fn to_signed(self) -> Result<SignedAmount, ParsingError> {
if self.as_pico() > SignedAmount::max_value().as_pico() as u64 {
Err(ParsingError::TooBig)
} else {
Ok(SignedAmount::from_pico(self.as_pico() as i64))
}
}
}
impl default::Default for Amount {
fn default() -> Self {
Amount::ZERO
}
}
impl fmt::Debug for Amount {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Amount({:.12} xmr)", self.as_xmr())
}
}
impl fmt::Display for Amount {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.fmt_value_in(f, Denomination::Monero)?;
write!(f, " {}", Denomination::Monero)
}
}
impl ops::Add for Amount {
type Output = Amount;
fn add(self, rhs: Amount) -> Self::Output {
self.checked_add(rhs).expect("Amount addition error")
}
}
impl ops::AddAssign for Amount {
fn add_assign(&mut self, other: Amount) {
*self = *self + other
}
}
impl ops::Sub for Amount {
type Output = Amount;
fn sub(self, rhs: Amount) -> Self::Output {
self.checked_sub(rhs).expect("Amount subtraction error")
}
}
impl ops::SubAssign for Amount {
fn sub_assign(&mut self, other: Amount) {
*self = *self - other
}
}
impl ops::Rem<u64> for Amount {
type Output = Amount;
fn rem(self, modulus: u64) -> Self {
self.checked_rem(modulus).expect("Amount remainder error")
}
}
impl ops::RemAssign<u64> for Amount {
fn rem_assign(&mut self, modulus: u64) {
*self = *self % modulus
}
}
impl ops::Mul<u64> for Amount {
type Output = Amount;
fn mul(self, rhs: u64) -> Self::Output {
self.checked_mul(rhs).expect("Amount multiplication error")
}
}
impl ops::MulAssign<u64> for Amount {
fn mul_assign(&mut self, rhs: u64) {
*self = *self * rhs
}
}
impl ops::Div<u64> for Amount {
type Output = Amount;
fn div(self, rhs: u64) -> Self::Output {
self.checked_div(rhs).expect("Amount division error")
}
}
impl ops::DivAssign<u64> for Amount {
fn div_assign(&mut self, rhs: u64) {
*self = *self / rhs
}
}
impl FromStr for Amount {
type Err = ParsingError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Amount::from_str_with_denomination(s)
}
}
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SignedAmount(i64);
impl SignedAmount {
pub const ZERO: SignedAmount = SignedAmount(0);
pub const ONE_PICO: SignedAmount = SignedAmount(1);
pub const ONE_XMR: SignedAmount = SignedAmount(1_000_000_000_000);
pub fn from_pico(piconero: i64) -> SignedAmount {
SignedAmount(piconero)
}
pub fn as_pico(self) -> i64 {
self.0
}
pub fn max_value() -> SignedAmount {
SignedAmount(i64::max_value())
}
pub fn min_value() -> SignedAmount {
SignedAmount(i64::min_value())
}
pub fn from_xmr(xmr: f64) -> Result<SignedAmount, ParsingError> {
SignedAmount::from_float_in(xmr, Denomination::Monero)
}
pub fn from_str_in(s: &str, denom: Denomination) -> Result<SignedAmount, ParsingError> {
let (negative, piconero) = parse_signed_to_piconero(s, denom)?;
if piconero > i64::max_value() as u64 {
return Err(ParsingError::TooBig);
}
Ok(match negative {
true => SignedAmount(-(piconero as i64)),
false => SignedAmount(piconero as i64),
})
}
pub fn from_str_with_denomination(s: &str) -> Result<SignedAmount, ParsingError> {
let mut split = s.splitn(3, ' ');
let amt_str = split.next().unwrap();
let denom_str = split.next().ok_or(ParsingError::InvalidFormat)?;
if split.next().is_some() {
return Err(ParsingError::InvalidFormat);
}
SignedAmount::from_str_in(amt_str, denom_str.parse()?)
}
pub fn to_float_in(self, denom: Denomination) -> f64 {
f64::from_str(&self.to_string_in(denom)).unwrap()
}
pub fn as_xmr(self) -> f64 {
self.to_float_in(Denomination::Monero)
}
pub fn from_float_in(value: f64, denom: Denomination) -> Result<SignedAmount, ParsingError> {
SignedAmount::from_str_in(&value.to_string(), denom)
}
pub fn fmt_value_in(self, f: &mut dyn fmt::Write, denom: Denomination) -> fmt::Result {
let picos = self
.as_pico()
.checked_abs()
.map(|a: i64| a as u64)
.unwrap_or_else(|| {
u64::max_value() - self.as_pico() as u64 + 1
});
fmt_piconero_in(picos, self.is_negative(), f, denom)
}
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) -> SignedAmount {
SignedAmount(self.0.abs())
}
pub fn signum(self) -> i64 {
self.0.signum()
}
pub fn is_positive(self) -> bool {
self.0.is_positive()
}
pub fn is_negative(self) -> bool {
self.0.is_negative()
}
pub fn checked_abs(self) -> Option<SignedAmount> {
self.0.checked_abs().map(SignedAmount)
}
pub fn checked_add(self, rhs: SignedAmount) -> Option<SignedAmount> {
self.0.checked_add(rhs.0).map(SignedAmount)
}
pub fn checked_sub(self, rhs: SignedAmount) -> Option<SignedAmount> {
self.0.checked_sub(rhs.0).map(SignedAmount)
}
pub fn checked_mul(self, rhs: i64) -> Option<SignedAmount> {
self.0.checked_mul(rhs).map(SignedAmount)
}
pub fn checked_div(self, rhs: i64) -> Option<SignedAmount> {
self.0.checked_div(rhs).map(SignedAmount)
}
pub fn checked_rem(self, rhs: i64) -> Option<SignedAmount> {
self.0.checked_rem(rhs).map(SignedAmount)
}
pub fn positive_sub(self, rhs: SignedAmount) -> Option<SignedAmount> {
if self.is_negative() || rhs.is_negative() || rhs > self {
None
} else {
self.checked_sub(rhs)
}
}
pub fn to_unsigned(self) -> Result<Amount, ParsingError> {
if self.is_negative() {
Err(ParsingError::Negative)
} else {
Ok(Amount::from_pico(self.as_pico() as u64))
}
}
}
impl default::Default for SignedAmount {
fn default() -> Self {
SignedAmount::ZERO
}
}
impl fmt::Debug for SignedAmount {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "SignedAmount({:.12} xmr)", self.as_xmr())
}
}
impl fmt::Display for SignedAmount {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.fmt_value_in(f, Denomination::Monero)?;
write!(f, " {}", Denomination::Monero)
}
}
impl ops::Add for SignedAmount {
type Output = SignedAmount;
fn add(self, rhs: SignedAmount) -> Self::Output {
self.checked_add(rhs).expect("SignedAmount addition error")
}
}
impl ops::AddAssign for SignedAmount {
fn add_assign(&mut self, other: SignedAmount) {
*self = *self + other
}
}
impl ops::Sub for SignedAmount {
type Output = SignedAmount;
fn sub(self, rhs: SignedAmount) -> Self::Output {
self.checked_sub(rhs)
.expect("SignedAmount subtraction error")
}
}
impl ops::SubAssign for SignedAmount {
fn sub_assign(&mut self, other: SignedAmount) {
*self = *self - other
}
}
impl ops::Rem<i64> for SignedAmount {
type Output = SignedAmount;
fn rem(self, modulus: i64) -> Self {
self.checked_rem(modulus)
.expect("SignedAmount remainder error")
}
}
impl ops::RemAssign<i64> for SignedAmount {
fn rem_assign(&mut self, modulus: i64) {
*self = *self % modulus
}
}
impl ops::Mul<i64> for SignedAmount {
type Output = SignedAmount;
fn mul(self, rhs: i64) -> Self::Output {
self.checked_mul(rhs)
.expect("SignedAmount multiplication error")
}
}
impl ops::MulAssign<i64> for SignedAmount {
fn mul_assign(&mut self, rhs: i64) {
*self = *self * rhs
}
}
impl ops::Div<i64> for SignedAmount {
type Output = SignedAmount;
fn div(self, rhs: i64) -> Self::Output {
self.checked_div(rhs).expect("SignedAmount division error")
}
}
impl ops::DivAssign<i64> for SignedAmount {
fn div_assign(&mut self, rhs: i64) {
*self = *self / rhs
}
}
impl FromStr for SignedAmount {
type Err = ParsingError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
SignedAmount::from_str_with_denomination(s)
}
}
#[cfg(feature = "serde")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
pub mod serde {
use super::{Amount, Denomination, SignedAmount};
use sealed::sealed;
use serde_crate::{ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer};
#[sealed]
pub trait SerdeAmount: Copy + Sized {
fn ser_pico<S: Serializer>(self, s: S) -> Result<S::Ok, S::Error>;
fn des_pico<'d, D: Deserializer<'d>>(d: D) -> Result<Self, D::Error>;
fn ser_xmr<S: Serializer>(self, s: S) -> Result<S::Ok, S::Error>;
fn des_xmr<'d, D: Deserializer<'d>>(d: D) -> Result<Self, D::Error>;
}
#[sealed]
pub trait SerdeAmountForOpt: Copy + Sized + SerdeAmount {
fn type_prefix() -> &'static str;
fn ser_pico_opt<S: Serializer>(self, s: S) -> Result<S::Ok, S::Error>;
fn ser_xmr_opt<S: Serializer>(self, s: S) -> Result<S::Ok, S::Error>;
}
#[sealed]
pub trait SerdeAmountForSlice: Copy + Sized + SerdeAmount {
fn type_prefix() -> &'static str;
fn ser_pico_slice<S: SerializeSeq>(&self, s: &mut S) -> Result<(), S::Error>;
fn ser_xmr_slice<S: SerializeSeq>(&self, s: &mut S) -> Result<(), S::Error>;
}
#[sealed]
impl SerdeAmount for Amount {
fn ser_pico<S: Serializer>(self, s: S) -> Result<S::Ok, S::Error> {
u64::serialize(&self.as_pico(), s)
}
fn des_pico<'d, D: Deserializer<'d>>(d: D) -> Result<Self, D::Error> {
Ok(Amount::from_pico(u64::deserialize(d)?))
}
fn ser_xmr<S: Serializer>(self, s: S) -> Result<S::Ok, S::Error> {
String::serialize(&self.to_string_in(Denomination::Monero), s)
}
fn des_xmr<'d, D: Deserializer<'d>>(d: D) -> Result<Self, D::Error> {
use serde_crate::de::Error;
Amount::from_str_in(&String::deserialize(d)?, Denomination::Monero)
.map_err(D::Error::custom)
}
}
#[sealed]
impl SerdeAmountForOpt for Amount {
fn type_prefix() -> &'static str {
"u"
}
fn ser_pico_opt<S: Serializer>(self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_some(&self.as_pico())
}
fn ser_xmr_opt<S: Serializer>(self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_some(&self.to_string_in(Denomination::Monero))
}
}
#[sealed]
impl SerdeAmountForSlice for Amount {
fn type_prefix() -> &'static str {
"u"
}
fn ser_pico_slice<S: SerializeSeq>(&self, s: &mut S) -> Result<(), S::Error> {
s.serialize_element(&self.as_pico())
}
fn ser_xmr_slice<S: SerializeSeq>(&self, s: &mut S) -> Result<(), S::Error> {
s.serialize_element(&self.to_string_in(Denomination::Monero))
}
}
#[sealed]
impl SerdeAmount for SignedAmount {
fn ser_pico<S: Serializer>(self, s: S) -> Result<S::Ok, S::Error> {
i64::serialize(&self.as_pico(), s)
}
fn des_pico<'d, D: Deserializer<'d>>(d: D) -> Result<Self, D::Error> {
Ok(SignedAmount::from_pico(i64::deserialize(d)?))
}
fn ser_xmr<S: Serializer>(self, s: S) -> Result<S::Ok, S::Error> {
String::serialize(&self.to_string_in(Denomination::Monero), s)
}
fn des_xmr<'d, D: Deserializer<'d>>(d: D) -> Result<Self, D::Error> {
use serde_crate::de::Error;
SignedAmount::from_str_in(&String::deserialize(d)?, Denomination::Monero)
.map_err(D::Error::custom)
}
}
#[sealed]
impl SerdeAmountForOpt for SignedAmount {
fn type_prefix() -> &'static str {
"i"
}
fn ser_pico_opt<S: Serializer>(self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_some(&self.as_pico())
}
fn ser_xmr_opt<S: Serializer>(self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_some(&self.to_string_in(Denomination::Monero))
}
}
#[sealed]
impl SerdeAmountForSlice for SignedAmount {
fn type_prefix() -> &'static str {
"i"
}
fn ser_pico_slice<S: SerializeSeq>(&self, s: &mut S) -> Result<(), S::Error> {
s.serialize_element(&self.as_pico())
}
fn ser_xmr_slice<S: SerializeSeq>(&self, s: &mut S) -> Result<(), S::Error> {
s.serialize_element(&self.to_string_in(Denomination::Monero))
}
}
pub mod as_pico {
#![allow(missing_docs)]
use super::SerdeAmount;
use serde_crate::{Deserializer, Serializer};
pub fn serialize<A: SerdeAmount, S: Serializer>(a: &A, s: S) -> Result<S::Ok, S::Error> {
a.ser_pico(s)
}
pub fn deserialize<'d, A: SerdeAmount, D: Deserializer<'d>>(d: D) -> Result<A, D::Error> {
A::des_pico(d)
}
pub mod opt {
use super::super::SerdeAmountForOpt;
use core::fmt;
use core::marker::PhantomData;
use serde_crate::{de, Deserializer, Serializer};
pub fn serialize<A: SerdeAmountForOpt, S: Serializer>(
a: &Option<A>,
s: S,
) -> Result<S::Ok, S::Error> {
match *a {
Some(a) => a.ser_pico_opt(s),
None => s.serialize_none(),
}
}
pub fn deserialize<'d, A: SerdeAmountForOpt, D: Deserializer<'d>>(
d: D,
) -> Result<Option<A>, D::Error> {
struct VisitOptAmt<X>(PhantomData<X>);
impl<'de, X: SerdeAmountForOpt> de::Visitor<'de> for VisitOptAmt<X> {
type Value = Option<X>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "an Option<{}64>", X::type_prefix())
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(None)
}
fn visit_some<D>(self, d: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
Ok(Some(X::des_pico(d)?))
}
}
d.deserialize_option(VisitOptAmt::<A>(PhantomData))
}
}
pub mod slice {
use super::super::SerdeAmountForSlice;
use serde_crate::{ser::SerializeSeq, Serializer};
pub fn serialize<A: SerdeAmountForSlice, S: Serializer>(
a_slice: &[A],
s: S,
) -> Result<S::Ok, S::Error> {
let mut seq = s.serialize_seq(Some(a_slice.len()))?;
for e in a_slice {
e.ser_pico_slice(&mut seq)?;
}
seq.end()
}
}
pub mod vec {
use super::super::{Amount, SignedAmount};
use core::marker::PhantomData;
use serde_crate::{de, Deserializer, __private::size_hint};
pub fn deserialize_amount<'d, D: Deserializer<'d>>(
d: D,
) -> Result<Vec<Amount>, D::Error> {
struct VisitVecAmt(PhantomData<Amount>);
impl<'de> de::Visitor<'de> for VisitVecAmt {
type Value = Vec<Amount>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(formatter, "a Vec<u64>")
}
fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
where
S: de::SeqAccess<'de>,
{
let mut amt_vec = Vec::with_capacity(size_hint::cautious(seq.size_hint()));
while let Some(amt) = seq.next_element::<u64>()? {
amt_vec.push(Amount::from_pico(amt));
}
Ok(amt_vec)
}
}
d.deserialize_seq(VisitVecAmt(PhantomData))
}
pub fn deserialize_signed_amount<'d, D: Deserializer<'d>>(
d: D,
) -> Result<Vec<SignedAmount>, D::Error> {
struct VisitVecAmt(PhantomData<SignedAmount>);
impl<'de> de::Visitor<'de> for VisitVecAmt {
type Value = Vec<SignedAmount>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(formatter, "a Vec<i64>")
}
fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
where
S: de::SeqAccess<'de>,
{
let mut amt_vec = Vec::with_capacity(size_hint::cautious(seq.size_hint()));
while let Some(amt) = seq.next_element::<i64>()? {
amt_vec.push(SignedAmount::from_pico(amt));
}
Ok(amt_vec)
}
}
d.deserialize_seq(VisitVecAmt(PhantomData))
}
}
}
pub mod as_xmr {
#![allow(missing_docs)]
use super::SerdeAmount;
use serde_crate::{Deserializer, Serializer};
pub fn serialize<A: SerdeAmount, S: Serializer>(a: &A, s: S) -> Result<S::Ok, S::Error> {
a.ser_xmr(s)
}
pub fn deserialize<'d, A: SerdeAmount, D: Deserializer<'d>>(d: D) -> Result<A, D::Error> {
A::des_xmr(d)
}
pub mod opt {
use super::super::SerdeAmountForOpt;
use core::fmt;
use core::marker::PhantomData;
use serde_crate::{de, Deserializer, Serializer};
pub fn serialize<A: SerdeAmountForOpt, S: Serializer>(
a: &Option<A>,
s: S,
) -> Result<S::Ok, S::Error> {
match *a {
Some(a) => a.ser_xmr_opt(s),
None => s.serialize_none(),
}
}
pub fn deserialize<'d, A: SerdeAmountForOpt, D: Deserializer<'d>>(
d: D,
) -> Result<Option<A>, D::Error> {
struct VisitOptAmt<X>(PhantomData<X>);
impl<'de, X: SerdeAmountForOpt> de::Visitor<'de> for VisitOptAmt<X> {
type Value = Option<X>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
write!(formatter, "an Option<String>")
}
fn visit_none<E>(self) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(None)
}
fn visit_some<D>(self, d: D) -> Result<Self::Value, D::Error>
where
D: Deserializer<'de>,
{
Ok(Some(X::des_xmr(d)?))
}
}
d.deserialize_option(VisitOptAmt::<A>(PhantomData))
}
}
pub mod slice {
use super::super::SerdeAmountForSlice;
use serde_crate::{ser::SerializeSeq, Serializer};
pub fn serialize<A: SerdeAmountForSlice, S: Serializer>(
a_slice: &[A],
s: S,
) -> Result<S::Ok, S::Error> {
let mut seq = s.serialize_seq(Some(a_slice.len()))?;
for e in a_slice {
e.ser_xmr_slice(&mut seq)?;
}
seq.end()
}
}
pub mod vec {
use super::super::{super::Denomination, Amount, SignedAmount};
use core::marker::PhantomData;
use serde_crate::{de, Deserializer, __private::size_hint};
pub fn deserialize_amount<'d, D: Deserializer<'d>>(
d: D,
) -> Result<Vec<Amount>, D::Error> {
struct VisitVecAmt(PhantomData<Amount>);
impl<'de> de::Visitor<'de> for VisitVecAmt {
type Value = Vec<Amount>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(formatter, "a Vec<String>")
}
fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
where
S: de::SeqAccess<'de>,
{
let mut amt_vec = Vec::with_capacity(size_hint::cautious(seq.size_hint()));
while let Some(amt) = seq.next_element()? {
let amt = Amount::from_str_in(amt, Denomination::Monero)
.map_err(|e| de::Error::custom(e.to_string()))?;
amt_vec.push(amt);
}
Ok(amt_vec)
}
}
d.deserialize_seq(VisitVecAmt(PhantomData))
}
pub fn deserialize_signed_amount<'d, D: Deserializer<'d>>(
d: D,
) -> Result<Vec<SignedAmount>, D::Error> {
struct VisitVecAmt(PhantomData<SignedAmount>);
impl<'de> de::Visitor<'de> for VisitVecAmt {
type Value = Vec<SignedAmount>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(formatter, "a Vec<String>")
}
fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
where
S: de::SeqAccess<'de>,
{
let mut amt_vec = Vec::with_capacity(size_hint::cautious(seq.size_hint()));
while let Some(amt) = seq.next_element()? {
let amt = SignedAmount::from_str_in(amt, Denomination::Monero)
.map_err(|e| de::Error::custom(e.to_string()))?;
amt_vec.push(amt);
}
Ok(amt_vec)
}
}
d.deserialize_seq(VisitVecAmt(PhantomData))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::panic;
use std::str::FromStr;
#[cfg(feature = "serde")]
use serde_test;
#[test]
fn add_sub_mul_div() {
let pico = Amount::from_pico;
let spico = SignedAmount::from_pico;
assert_eq!(pico(15) + pico(15), pico(30));
assert_eq!(pico(15) - pico(15), pico(0));
assert_eq!(pico(14) * 3, pico(42));
assert_eq!(pico(14) / 2, pico(7));
assert_eq!(pico(14) % 3, pico(2));
assert_eq!(spico(15) - spico(20), spico(-5));
assert_eq!(spico(-14) * 3, spico(-42));
assert_eq!(spico(-14) / 2, spico(-7));
assert_eq!(spico(-14) % 3, spico(-2));
let mut b = spico(-5);
b += spico(13);
assert_eq!(b, spico(8));
b -= spico(3);
assert_eq!(b, spico(5));
b *= 6;
assert_eq!(b, spico(30));
b /= 3;
assert_eq!(b, spico(10));
b %= 3;
assert_eq!(b, spico(1));
let result = panic::catch_unwind(|| Amount::max_value() + Amount::from_pico(1));
assert!(result.is_err());
let result = panic::catch_unwind(|| Amount::from_pico(8446744073709551615) * 3);
assert!(result.is_err());
}
#[test]
fn checked_arithmetic() {
let pico = Amount::from_pico;
let spico = SignedAmount::from_pico;
assert_eq!(pico(42).checked_add(pico(1)), Some(pico(43)));
assert_eq!(SignedAmount::max_value().checked_add(spico(1)), None);
assert_eq!(SignedAmount::min_value().checked_sub(spico(1)), None);
assert_eq!(Amount::max_value().checked_add(pico(1)), None);
assert_eq!(Amount::min_value().checked_sub(pico(1)), None);
assert_eq!(pico(5).checked_sub(pico(3)), Some(pico(2)));
assert_eq!(pico(5).checked_sub(pico(6)), None);
assert_eq!(spico(5).checked_sub(spico(6)), Some(spico(-1)));
assert_eq!(pico(5).checked_rem(2), Some(pico(1)));
assert_eq!(pico(5).checked_div(2), Some(pico(2))); assert_eq!(spico(-6).checked_div(2), Some(spico(-3)));
assert_eq!(spico(-5).positive_sub(spico(3)), None);
assert_eq!(spico(5).positive_sub(spico(-3)), None);
assert_eq!(spico(3).positive_sub(spico(5)), None);
assert_eq!(spico(3).positive_sub(spico(3)), Some(spico(0)));
assert_eq!(spico(5).positive_sub(spico(3)), Some(spico(2)));
}
#[test]
#[allow(clippy::float_cmp)]
fn floating_point() {
use super::Denomination as D;
let f = Amount::from_float_in;
let sf = SignedAmount::from_float_in;
let pico = Amount::from_pico;
let spico = SignedAmount::from_pico;
assert_eq!(f(11.22, D::Monero), Ok(pico(11220000000000)));
assert_eq!(sf(-11.22, D::Millinero), Ok(spico(-11220000000)));
assert_eq!(f(11.22, D::Micronero), Ok(pico(11220000)));
assert_eq!(f(0.0001234, D::Monero), Ok(pico(123400000)));
assert_eq!(sf(-0.00012345, D::Monero), Ok(spico(-123450000)));
assert_eq!(f(-100.0, D::Piconero), Err(ParsingError::Negative));
assert_eq!(f(11.22, D::Piconero), Err(ParsingError::TooPrecise));
assert_eq!(sf(-0.1, D::Piconero), Err(ParsingError::TooPrecise));
assert_eq!(
f(42.000_000_000_000_1, D::Monero),
Err(ParsingError::TooPrecise)
);
assert_eq!(sf(-184467440738.0, D::Monero), Err(ParsingError::TooBig));
assert_eq!(
f(18446744073709551617.0, D::Piconero),
Err(ParsingError::TooBig)
);
assert_eq!(
f(
SignedAmount::max_value().to_float_in(D::Piconero) + 1.0,
D::Piconero
),
Err(ParsingError::TooBig)
);
assert_eq!(
f(
Amount::max_value().to_float_in(D::Piconero) + 1.0,
D::Piconero
),
Err(ParsingError::TooBig)
);
let xmr = move |f| SignedAmount::from_xmr(f).unwrap();
assert_eq!(xmr(2.5).to_float_in(D::Monero), 2.5);
assert_eq!(xmr(-2.5).to_float_in(D::Millinero), -2500.0);
assert_eq!(xmr(-2.5).to_float_in(D::Micronero), -2500000.0);
assert_eq!(xmr(-2.5).to_float_in(D::Nanonero), -2500000000.0);
assert_eq!(xmr(2.5).to_float_in(D::Piconero), 2500000000000.0);
let xmr = move |f| Amount::from_xmr(f).unwrap();
assert_eq!(&xmr(0.0012).to_float_in(D::Monero).to_string(), "0.0012")
}
#[test]
fn parsing() {
use super::ParsingError as E;
let xmr = Denomination::Monero;
let pico = Denomination::Piconero;
let p = Amount::from_str_in;
let sp = SignedAmount::from_str_in;
assert_eq!(p("x", xmr), Err(E::InvalidCharacter('x')));
assert_eq!(p("-", xmr), Err(E::InvalidFormat));
assert_eq!(sp("-", xmr), Err(E::InvalidFormat));
assert_eq!(p("-1.0x", xmr), Err(E::InvalidCharacter('x')));
assert_eq!(p("0.0 ", xmr), Err(ParsingError::InvalidCharacter(' ')));
assert_eq!(p("0.000.000", xmr), Err(E::InvalidFormat));
let more_than_max = format!("1{}", Amount::max_value());
assert_eq!(p(&more_than_max, xmr), Err(E::TooBig));
assert_eq!(p("0.0000000000042", xmr), Err(E::TooPrecise));
assert_eq!(p("1", xmr), Ok(Amount::from_pico(1_000_000_000_000)));
assert_eq!(
sp("-.5", xmr),
Ok(SignedAmount::from_pico(-500_000_000_000))
);
assert_eq!(p("1.1", xmr), Ok(Amount::from_pico(1_100_000_000_000)));
assert_eq!(p("100", pico), Ok(Amount::from_pico(100)));
assert_eq!(p("55", pico), Ok(Amount::from_pico(55)));
assert_eq!(
p("5500000000000000000", pico),
Ok(Amount::from_pico(5_500_000_000_000_000_000))
);
assert_eq!(
p("5500000000000000000.", pico),
Ok(Amount::from_pico(5_500_000_000_000_000_000))
);
assert_eq!(
p("1234567.123456789123", xmr),
Ok(Amount::from_pico(1_234_567_123_456_789_123))
);
let amount = Amount::from_pico(i64::max_value() as u64);
assert_eq!(
Amount::from_str_in(&amount.to_string_in(pico), pico),
Ok(amount)
);
assert_eq!(
Amount::from_str_in(&(amount + Amount(1)).to_string_in(pico), pico),
Err(E::TooBig)
);
assert_eq!(
p(
"100000000000000.0000000000000000000000000000000000",
Denomination::Monero
),
Err(E::TooBig)
);
assert_eq!(
p(
"100000000000000.00000000000000000000000000000000000",
Denomination::Monero
),
Err(E::InputTooLarge)
);
}
#[test]
fn to_string() {
use super::Denomination as D;
assert_eq!(Amount::ONE_XMR.to_string_in(D::Monero), "1.000000000000");
assert_eq!(Amount::ONE_XMR.to_string_in(D::Piconero), "1000000000000");
assert_eq!(Amount::ONE_PICO.to_string_in(D::Monero), "0.000000000001");
assert_eq!(
SignedAmount::from_pico(-42).to_string_in(D::Monero),
"-0.000000000042"
);
assert_eq!(
Amount::ONE_XMR.to_string_with_denomination(D::Monero),
"1.000000000000 xmr"
);
assert_eq!(
SignedAmount::ONE_XMR.to_string_with_denomination(D::Piconero),
"1000000000000 piconero"
);
assert_eq!(
Amount::ONE_PICO.to_string_with_denomination(D::Monero),
"0.000000000001 xmr"
);
assert_eq!(
SignedAmount::from_pico(-42).to_string_with_denomination(D::Monero),
"-0.000000000042 xmr"
);
}
#[test]
fn test_unsigned_signed_conversion() {
use super::ParsingError as E;
let p = Amount::from_pico;
let sp = SignedAmount::from_pico;
assert_eq!(Amount::max_value().to_signed(), Err(E::TooBig));
assert_eq!(
p(i64::max_value() as u64).to_signed(),
Ok(sp(i64::max_value()))
);
assert_eq!(p(0).to_signed(), Ok(sp(0)));
assert_eq!(p(1).to_signed(), Ok(sp(1)));
assert_eq!(p(1).to_signed(), Ok(sp(1)));
assert_eq!(p(i64::max_value() as u64 + 1).to_signed(), Err(E::TooBig));
assert_eq!(sp(-1).to_unsigned(), Err(E::Negative));
assert_eq!(
sp(i64::max_value()).to_unsigned(),
Ok(p(i64::max_value() as u64))
);
assert_eq!(sp(0).to_unsigned().unwrap().to_signed(), Ok(sp(0)));
assert_eq!(sp(1).to_unsigned().unwrap().to_signed(), Ok(sp(1)));
assert_eq!(
sp(i64::max_value()).to_unsigned().unwrap().to_signed(),
Ok(sp(i64::max_value()))
);
}
#[test]
fn from_str() {
use super::ParsingError as E;
let p = Amount::from_str;
let sp = SignedAmount::from_str;
assert_eq!(p("x XMR"), Err(E::InvalidCharacter('x')));
assert_eq!(p("5 XMR XMR"), Err(E::InvalidFormat));
assert_eq!(p("5 5 XMR"), Err(E::InvalidFormat));
assert_eq!(p("5 BCH"), Err(E::UnknownDenomination("BCH".to_owned())));
assert_eq!(p("-1 XMR"), Err(E::Negative));
assert_eq!(p("-0.0 XMR"), Err(E::Negative));
assert_eq!(p("0.1234567891234 XMR"), Err(E::TooPrecise));
assert_eq!(sp("-0.1 piconero"), Err(E::TooPrecise));
assert_eq!(p("0.1234567 micronero"), Err(E::TooPrecise));
assert_eq!(sp("-1.0001 nanonero"), Err(E::TooPrecise));
assert_eq!(sp("-200000000000 XMR"), Err(E::TooBig));
assert_eq!(p("18446744073709551616 piconero"), Err(E::TooBig));
assert_eq!(p(".5 nanonero"), Ok(Amount::from_pico(500)));
assert_eq!(sp("-.5 nanonero"), Ok(SignedAmount::from_pico(-500)));
assert_eq!(p("0.000000253583 XMR"), Ok(Amount::from_pico(253583)));
assert_eq!(sp("-5 piconero"), Ok(SignedAmount::from_pico(-5)));
assert_eq!(
p("0.100000000000 XMR"),
Ok(Amount::from_pico(100_000_000_000))
);
assert_eq!(sp("-10 nanonero"), Ok(SignedAmount::from_pico(-10_000)));
assert_eq!(
sp("-10 micronero"),
Ok(SignedAmount::from_pico(-10_000_000))
);
assert_eq!(
sp("-10 millinero"),
Ok(SignedAmount::from_pico(-10_000_000_000))
);
}
#[test]
fn to_from_string_in() {
use super::Denomination as D;
let ua_str = Amount::from_str_in;
let ua_pic = Amount::from_pico;
let sa_str = SignedAmount::from_str_in;
let sa_pic = SignedAmount::from_pico;
assert_eq!("0.500", Amount::from_pico(500).to_string_in(D::Nanonero));
assert_eq!(
"-0.500",
SignedAmount::from_pico(-500).to_string_in(D::Nanonero)
);
assert_eq!(
"0.002535830000",
Amount::from_pico(2535830000).to_string_in(D::Monero)
);
assert_eq!("-5", SignedAmount::from_pico(-5).to_string_in(D::Piconero));
assert_eq!(
"0.100000000000",
Amount::from_pico(100_000_000_000).to_string_in(D::Monero)
);
assert_eq!(
"-10.000",
SignedAmount::from_pico(-10_000).to_string_in(D::Nanonero)
);
assert_eq!(
ua_str(&ua_pic(0).to_string_in(D::Piconero), D::Piconero),
Ok(ua_pic(0))
);
assert_eq!(
ua_str(&ua_pic(500).to_string_in(D::Monero), D::Monero),
Ok(ua_pic(500))
);
assert_eq!(
ua_str(&ua_pic(21_000_000).to_string_in(D::Nanonero), D::Nanonero),
Ok(ua_pic(21_000_000))
);
assert_eq!(
ua_str(&ua_pic(1).to_string_in(D::Micronero), D::Micronero),
Ok(ua_pic(1))
);
assert_eq!(
ua_str(
&ua_pic(1_000_000_000_000).to_string_in(D::Millinero),
D::Millinero
),
Ok(ua_pic(1_000_000_000_000))
);
assert_eq!(
ua_str(
&ua_pic(u64::max_value()).to_string_in(D::Millinero),
D::Millinero
),
Err(ParsingError::TooBig)
);
assert_eq!(
sa_str(&sa_pic(-1).to_string_in(D::Micronero), D::Micronero),
Ok(sa_pic(-1))
);
assert_eq!(
sa_str(
&sa_pic(i64::max_value()).to_string_in(D::Piconero),
D::Micronero
),
Err(ParsingError::TooBig)
);
assert_eq!(
sa_str(
&sa_pic(i64::min_value()).to_string_in(D::Piconero),
D::Micronero
),
Err(ParsingError::TooBig)
);
}
#[test]
fn to_string_with_denomination_from_str_roundtrip() {
use super::Denomination as D;
let amt = Amount::from_pico(42);
let denom = Amount::to_string_with_denomination;
assert_eq!(Amount::from_str(&denom(amt, D::Monero)), Ok(amt));
assert_eq!(Amount::from_str(&denom(amt, D::Millinero)), Ok(amt));
assert_eq!(Amount::from_str(&denom(amt, D::Micronero)), Ok(amt));
assert_eq!(Amount::from_str(&denom(amt, D::Nanonero)), Ok(amt));
assert_eq!(Amount::from_str(&denom(amt, D::Piconero)), Ok(amt));
assert_eq!(
Amount::from_str("42 piconero XMR"),
Err(ParsingError::InvalidFormat)
);
assert_eq!(
SignedAmount::from_str("-42 piconero XMR"),
Err(ParsingError::InvalidFormat)
);
}
#[cfg(feature = "serde")]
#[test]
fn serde_as_pico() {
use serde_crate::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, PartialEq, Debug)]
#[serde(crate = "serde_crate")]
struct T {
#[serde(with = "super::serde::as_pico")]
pub amt: Amount,
#[serde(with = "super::serde::as_pico")]
pub samt: SignedAmount,
}
serde_test::assert_tokens(
&T {
amt: Amount::from_pico(123456789),
samt: SignedAmount::from_pico(-123456789),
},
&[
serde_test::Token::Struct { name: "T", len: 2 },
serde_test::Token::Str("amt"),
serde_test::Token::U64(123456789),
serde_test::Token::Str("samt"),
serde_test::Token::I64(-123456789),
serde_test::Token::StructEnd,
],
);
}
#[cfg(feature = "serde")]
#[test]
fn serde_as_pico_opt() {
use serde_crate::{Deserialize, Serialize};
use serde_json;
#[derive(Serialize, Deserialize, PartialEq, Debug, Eq)]
#[serde(crate = "serde_crate")]
struct T {
#[serde(default, with = "super::serde::as_pico::opt")]
pub amt: Option<Amount>,
#[serde(default, with = "super::serde::as_pico::opt")]
pub samt: Option<SignedAmount>,
}
let with = T {
amt: Some(Amount::from_pico(2_500_000_000_000)),
samt: Some(SignedAmount::from_pico(-2_500_000_000_000)),
};
let without = T {
amt: None,
samt: None,
};
for s in [&with, &without].iter() {
let v = serde_json::to_string(s).unwrap();
let w: T = serde_json::from_str(&v).unwrap();
assert_eq!(w, **s);
}
let t: T =
serde_json::from_str("{\"amt\": 2500000000000, \"samt\": -2500000000000}").unwrap();
assert_eq!(t, with);
let t: T = serde_json::from_str("{}").unwrap();
assert_eq!(t, without);
let value_with: serde_json::Value =
serde_json::from_str("{\"amt\": 2500000000000, \"samt\": -2500000000000}").unwrap();
assert_eq!(with, serde_json::from_value(value_with).unwrap());
let value_without: serde_json::Value = serde_json::from_str("{}").unwrap();
assert_eq!(without, serde_json::from_value(value_without).unwrap());
}
#[cfg(feature = "serde")]
#[test]
fn serde_as_pico_slice_serialize() {
use serde_crate::Serialize;
#[derive(Serialize, PartialEq, Debug, Eq)]
#[serde(crate = "serde_crate")]
struct T<'a> {
#[serde(default, serialize_with = "super::serde::as_pico::slice::serialize")]
pub amt1: Vec<Amount>,
#[serde(default, serialize_with = "super::serde::as_pico::slice::serialize")]
pub amt2: [Amount; 2],
#[serde(default, serialize_with = "super::serde::as_pico::slice::serialize")]
pub amt3: &'a [Amount],
#[serde(default, serialize_with = "super::serde::as_pico::slice::serialize")]
pub samt1: Vec<SignedAmount>,
#[serde(default, serialize_with = "super::serde::as_pico::slice::serialize")]
pub samt2: [SignedAmount; 2],
#[serde(default, serialize_with = "super::serde::as_pico::slice::serialize")]
pub samt3: &'a [SignedAmount],
}
let with = T {
amt1: vec![
Amount::from_pico(1_000_000_000),
Amount::from_pico(2_000_000_000),
],
amt2: [
Amount::from_pico(3_000_000_000),
Amount::from_pico(4_000_000_000),
],
amt3: &[
Amount::from_pico(5_000_000_000),
Amount::from_pico(6_000_000_000),
],
samt1: vec![
SignedAmount::from_pico(-1_000_000_000),
SignedAmount::from_pico(-2_000_000_000),
],
samt2: [
SignedAmount::from_pico(-3_000_000_000),
SignedAmount::from_pico(-4_000_000_000),
],
samt3: &[
SignedAmount::from_pico(-5_000_000_000),
SignedAmount::from_pico(-6_000_000_000),
],
};
let without = T {
amt1: vec![],
amt2: [
Amount::from_pico(3_000_000_000),
Amount::from_pico(4_000_000_000),
], amt3: &[],
samt1: vec![],
samt2: [
SignedAmount::from_pico(-3_000_000_000),
SignedAmount::from_pico(-4_000_000_000),
], samt3: &[],
};
let expected_with = r#"{"amt1":[1000000000,2000000000],"amt2":[3000000000,4000000000],"amt3":[5000000000,6000000000],"samt1":[-1000000000,-2000000000],"samt2":[-3000000000,-4000000000],"samt3":[-5000000000,-6000000000]}"#;
assert_eq!(serde_json::to_string(&with).unwrap(), expected_with);
let expected_without = r#"{"amt1":[],"amt2":[3000000000,4000000000],"amt3":[],"samt1":[],"samt2":[-3000000000,-4000000000],"samt3":[]}"#;
assert_eq!(serde_json::to_string(&without).unwrap(), expected_without);
}
#[cfg(feature = "serde")]
#[test]
fn serde_as_pico_vec_deserialize() {
use serde_crate::Deserialize;
#[derive(Deserialize, PartialEq, Debug, Eq)]
#[serde(crate = "serde_crate")]
struct T {
#[serde(
default,
deserialize_with = "super::serde::as_pico::vec::deserialize_amount"
)]
pub amt1: Vec<Amount>,
#[serde(
default,
deserialize_with = "super::serde::as_pico::vec::deserialize_amount"
)]
pub amt2: Vec<Amount>,
#[serde(
default,
deserialize_with = "super::serde::as_pico::vec::deserialize_signed_amount"
)]
pub samt1: Vec<SignedAmount>,
#[serde(
default,
deserialize_with = "super::serde::as_pico::vec::deserialize_signed_amount"
)]
pub samt2: Vec<SignedAmount>,
}
let t = T {
amt1: vec![Amount(1_000)],
amt2: vec![],
samt1: vec![SignedAmount(-1_000)],
samt2: vec![],
};
let t_str = r#"{"amt1": [1000], "amt2": [], "samt1": [-1000], "samt2": []}"#;
let t_from_str: T = serde_json::from_str(t_str).unwrap();
assert_eq!(t_from_str, t);
}
#[cfg(feature = "serde")]
#[test]
fn serde_as_pico_vec_deserialize_invalid_amounts_error() {
use serde_crate::Deserialize;
#[derive(Deserialize, PartialEq, Debug, Eq)]
#[serde(crate = "serde_crate")]
struct T {
#[serde(
default,
deserialize_with = "super::serde::as_pico::vec::deserialize_amount"
)]
pub amt: Vec<Amount>,
#[serde(
default,
deserialize_with = "super::serde::as_pico::vec::deserialize_signed_amount"
)]
pub samt: Vec<SignedAmount>,
}
let t_str = r#"{"amt": [], "samt": [18446744073709551615]}"#;
let t: Result<T, serde_json::Error> = serde_json::from_str(t_str);
let t_err = t.unwrap_err();
assert!(t_err.is_data());
assert_eq!(
t_err.to_string(),
"invalid value: integer `18446744073709551615`, expected i64 at line 1 column 41"
);
let t_str = r#"{"amt": [], "samt": 1}"#;
let t: Result<T, serde_json::Error> = serde_json::from_str(t_str);
let t_err = t.unwrap_err();
assert!(t_err.is_data());
assert_eq!(
t_err.to_string(),
"invalid type: integer `1`, expected a Vec<i64> at line 1 column 21"
);
let t_str = r#"{"amt": [-1000], "samt": []}"#;
let t: Result<T, serde_json::Error> = serde_json::from_str(t_str);
let t_err = t.unwrap_err();
assert!(t_err.is_data());
assert_eq!(
t_err.to_string(),
"invalid value: integer `-1000`, expected u64 at line 1 column 14"
);
let t_str = r#"{"amt": 1, "samt": []}"#;
let t: Result<T, serde_json::Error> = serde_json::from_str(t_str);
let t_err = t.unwrap_err();
assert!(t_err.is_data());
assert_eq!(
t_err.to_string(),
"invalid type: integer `1`, expected a Vec<u64> at line 1 column 9"
);
}
#[cfg(feature = "serde")]
#[test]
fn serde_as_xmr() {
use serde_crate::{Deserialize, Serialize};
use serde_json;
#[derive(Serialize, Deserialize, PartialEq, Debug)]
#[serde(crate = "serde_crate")]
struct T {
#[serde(with = "super::serde::as_xmr")]
pub amt: Amount,
#[serde(with = "super::serde::as_xmr")]
pub samt: SignedAmount,
}
let orig = T {
amt: Amount::from_pico(9_000_000_000_000_000_001),
samt: SignedAmount::from_pico(-9_000_000_000_000_000_001),
};
let json = "{\"amt\": \"9000000.000000000001\", \
\"samt\": \"-9000000.000000000001\"}";
let t: T = serde_json::from_str(json).unwrap();
assert_eq!(t, orig);
let value: serde_json::Value = serde_json::from_str(json).unwrap();
assert_eq!(t, serde_json::from_value(value).unwrap());
let t: Result<T, serde_json::Error> =
serde_json::from_str("{\"amt\": \"1000000.0000000000001\", \"samt\": \"1\"}");
assert!(t
.unwrap_err()
.to_string()
.contains(&ParsingError::TooPrecise.to_string()));
let t: Result<T, serde_json::Error> =
serde_json::from_str("{\"amt\": \"-1\", \"samt\": \"1\"}");
assert!(t
.unwrap_err()
.to_string()
.contains(&ParsingError::Negative.to_string()));
}
#[cfg(feature = "serde")]
#[test]
fn serde_as_xmr_opt() {
use serde_crate::{Deserialize, Serialize};
use serde_json;
#[derive(Serialize, Deserialize, PartialEq, Debug, Eq)]
#[serde(crate = "serde_crate")]
struct T {
#[serde(default, with = "super::serde::as_xmr::opt")]
pub amt: Option<Amount>,
#[serde(default, with = "super::serde::as_xmr::opt")]
pub samt: Option<SignedAmount>,
}
let with = T {
amt: Some(Amount::from_pico(2_500_000_000_000)),
samt: Some(SignedAmount::from_pico(-2_500_000_000_000)),
};
let without = T {
amt: None,
samt: None,
};
for s in [&with, &without].iter() {
let v = serde_json::to_string(s).unwrap();
let w: T = serde_json::from_str(&v).unwrap();
assert_eq!(w, **s);
}
let t: T = serde_json::from_str("{\"amt\": \"2.5\", \"samt\": \"-2.5\"}").unwrap();
assert_eq!(t, with);
let t: T = serde_json::from_str("{}").unwrap();
assert_eq!(t, without);
let value_with: serde_json::Value =
serde_json::from_str("{\"amt\": \"2.5\", \"samt\": \"-2.5\"}").unwrap();
assert_eq!(with, serde_json::from_value(value_with).unwrap());
let value_without: serde_json::Value = serde_json::from_str("{}").unwrap();
assert_eq!(without, serde_json::from_value(value_without).unwrap());
}
#[cfg(feature = "serde")]
#[test]
fn serde_as_xmr_slice_serialize() {
use serde_crate::Serialize;
#[derive(Serialize, PartialEq, Debug, Eq)]
#[serde(crate = "serde_crate")]
struct T<'a> {
#[serde(default, serialize_with = "super::serde::as_xmr::slice::serialize")]
pub amt1: Vec<Amount>,
#[serde(default, serialize_with = "super::serde::as_xmr::slice::serialize")]
pub amt2: [Amount; 2],
#[serde(default, serialize_with = "super::serde::as_xmr::slice::serialize")]
pub amt3: &'a [Amount],
#[serde(default, serialize_with = "super::serde::as_xmr::slice::serialize")]
pub samt1: Vec<SignedAmount>,
#[serde(default, serialize_with = "super::serde::as_xmr::slice::serialize")]
pub samt2: [SignedAmount; 2],
#[serde(default, serialize_with = "super::serde::as_xmr::slice::serialize")]
pub samt3: &'a [SignedAmount],
}
let with = T {
amt1: vec![
Amount::from_pico(1_000_000_000),
Amount::from_pico(2_000_000_000),
],
amt2: [
Amount::from_pico(3_000_000_000),
Amount::from_pico(4_000_000_000),
],
amt3: &[
Amount::from_pico(5_000_000_000),
Amount::from_pico(6_000_000_000),
],
samt1: vec![
SignedAmount::from_pico(-1_000_000_000),
SignedAmount::from_pico(-2_000_000_000),
],
samt2: [
SignedAmount::from_pico(-3_000_000_000),
SignedAmount::from_pico(-4_000_000_000),
],
samt3: &[
SignedAmount::from_pico(-5_000_000_000),
SignedAmount::from_pico(-6_000_000_000),
],
};
let without = T {
amt1: vec![],
amt2: [
Amount::from_pico(3_000_000_000),
Amount::from_pico(4_000_000_000),
], amt3: &[],
samt1: vec![],
samt2: [
SignedAmount::from_pico(-3_000_000_000),
SignedAmount::from_pico(-4_000_000_000),
], samt3: &[],
};
let expected_with = r#"{"amt1":["0.001000000000","0.002000000000"],"amt2":["0.003000000000","0.004000000000"],"amt3":["0.005000000000","0.006000000000"],"samt1":["-0.001000000000","-0.002000000000"],"samt2":["-0.003000000000","-0.004000000000"],"samt3":["-0.005000000000","-0.006000000000"]}"#;
assert_eq!(serde_json::to_string(&with).unwrap(), expected_with);
let expected_without = r#"{"amt1":[],"amt2":["0.003000000000","0.004000000000"],"amt3":[],"samt1":[],"samt2":["-0.003000000000","-0.004000000000"],"samt3":[]}"#;
assert_eq!(serde_json::to_string(&without).unwrap(), expected_without);
}
#[cfg(feature = "serde")]
#[test]
fn serde_as_xmr_vec_deserialize() {
use serde_crate::Deserialize;
#[derive(Deserialize, PartialEq, Debug, Eq)]
#[serde(crate = "serde_crate")]
struct T {
#[serde(
default,
deserialize_with = "super::serde::as_xmr::vec::deserialize_amount"
)]
pub amt1: Vec<Amount>,
#[serde(
default,
deserialize_with = "super::serde::as_xmr::vec::deserialize_amount"
)]
pub amt2: Vec<Amount>,
#[serde(
default,
deserialize_with = "super::serde::as_xmr::vec::deserialize_signed_amount"
)]
pub samt1: Vec<SignedAmount>,
#[serde(
default,
deserialize_with = "super::serde::as_xmr::vec::deserialize_signed_amount"
)]
pub samt2: Vec<SignedAmount>,
}
let t = T {
amt1: vec![Amount(1_000_000)],
amt2: vec![],
samt1: vec![SignedAmount(-1_000_000)],
samt2: vec![],
};
let t_str = r#"{"amt1": ["0.000001"], "amt2": [], "samt1": ["-0.000001"], "samt2": []}"#;
let t_from_str: T = serde_json::from_str(t_str).unwrap();
assert_eq!(t_from_str, t);
}
#[cfg(feature = "serde")]
#[test]
fn serde_as_xmr_vec_deserialize_invalid_amounts_error() {
use serde_crate::Deserialize;
#[derive(Deserialize, PartialEq, Debug, Eq)]
#[serde(crate = "serde_crate")]
struct T {
#[serde(
default,
deserialize_with = "super::serde::as_xmr::vec::deserialize_amount"
)]
pub amt: Vec<Amount>,
#[serde(
default,
deserialize_with = "super::serde::as_xmr::vec::deserialize_signed_amount"
)]
pub samt: Vec<SignedAmount>,
}
let t_str = r#"{"amt": [], "samt": ["18446744073709551615"]}"#;
let t: Result<T, serde_json::Error> = serde_json::from_str(t_str);
let t_err = t.unwrap_err();
assert!(t_err.is_data());
assert_eq!(
t_err.to_string(),
"Amount is too big to fit inside the type at line 1 column 44"
);
let t_str = r#"{"amt": [], "samt": 1}"#;
let t: Result<T, serde_json::Error> = serde_json::from_str(t_str);
let t_err = t.unwrap_err();
assert!(t_err.is_data());
assert_eq!(
t_err.to_string(),
"invalid type: integer `1`, expected a Vec<String> at line 1 column 21"
);
let t_str = r#"{"amt": ["-0.001"], "samt": []}"#;
let t: Result<T, serde_json::Error> = serde_json::from_str(t_str);
let t_err = t.unwrap_err();
assert!(t_err.is_data());
assert_eq!(t_err.to_string(), "Amount is negative at line 1 column 18");
let t_str = r#"{"amt": 1, "samt": []}"#;
let t: Result<T, serde_json::Error> = serde_json::from_str(t_str);
let t_err = t.unwrap_err();
assert!(t_err.is_data());
assert_eq!(
t_err.to_string(),
"invalid type: integer `1`, expected a Vec<String> at line 1 column 9"
);
}
}