etopay_sdk/types/
currencies.rs1use super::error::{Result, TypeError};
2use api_types::api::{generic::ApiCryptoCurrency, viviswap::detail::SwapPaymentDetailKey};
3use serde::Serialize;
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)]
8pub enum Currency {
9 Iota,
11 Eth,
13}
14
15impl TryFrom<String> for Currency {
16 type Error = TypeError;
17 fn try_from(currency: String) -> Result<Self> {
19 match currency.to_lowercase().as_str() {
20 "iota" => Ok(Self::Iota),
21 "eth" => Ok(Self::Eth),
22 _ => Err(TypeError::InvalidCurrency(currency)),
23 }
24 }
25}
26
27impl Currency {
28 pub fn to_vivi_payment_method_key(self) -> SwapPaymentDetailKey {
30 match self {
31 Self::Iota => SwapPaymentDetailKey::Iota,
32 Self::Eth => SwapPaymentDetailKey::Eth,
33 }
34 }
35}
36
37impl From<Currency> for ApiCryptoCurrency {
39 fn from(value: Currency) -> Self {
40 match value {
41 Currency::Iota => ApiCryptoCurrency::Iota,
42 Currency::Eth => ApiCryptoCurrency::Eth,
43 }
44 }
45}
46impl From<ApiCryptoCurrency> for Currency {
47 fn from(value: ApiCryptoCurrency) -> Self {
48 match value {
49 ApiCryptoCurrency::Iota => Currency::Iota,
50 ApiCryptoCurrency::Eth => Currency::Eth,
51 }
52 }
53}
54
55impl std::fmt::Display for Currency {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 match self {
60 Currency::Iota => write!(f, "Iota"),
61 Currency::Eth => write!(f, "Eth"),
62 }
63 }
64}
65
66#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
68pub struct CryptoAmount(rust_decimal::Decimal);
69
70impl CryptoAmount {
71 pub const ZERO: Self = Self(rust_decimal::Decimal::ZERO);
73
74 pub fn inner(&self) -> rust_decimal::Decimal {
76 self.0
77 }
78
79 pub(crate) const unsafe fn new_unchecked(value: rust_decimal::Decimal) -> Self {
82 Self(value)
83 }
84}
85
86impl From<u64> for CryptoAmount {
88 fn from(value: u64) -> Self {
89 Self(rust_decimal::Decimal::from(value))
90 }
91}
92
93impl TryFrom<f64> for CryptoAmount {
94 type Error = crate::Error;
95
96 fn try_from(value: f64) -> std::result::Result<Self, Self::Error> {
97 Self::try_from(rust_decimal::Decimal::try_from(value)?)
98 }
99}
100
101impl TryFrom<rust_decimal::Decimal> for CryptoAmount {
102 type Error = crate::Error;
103
104 fn try_from(value: rust_decimal::Decimal) -> std::result::Result<Self, Self::Error> {
105 if value < rust_decimal::Decimal::ZERO {
106 return Err(crate::Error::NegativeAmount);
107 }
108 Ok(Self(value))
109 }
110}
111impl TryFrom<api_types::api::decimal::Decimal> for CryptoAmount {
112 type Error = crate::Error;
113
114 fn try_from(value: api_types::api::decimal::Decimal) -> std::result::Result<Self, Self::Error> {
115 Self::try_from(value.0)
116 }
117}
118
119impl From<CryptoAmount> for api_types::api::decimal::Decimal {
120 fn from(val: CryptoAmount) -> Self {
121 Self(val.0)
122 }
123}
124
125impl TryFrom<CryptoAmount> for f64 {
126 type Error = crate::Error;
127
128 fn try_from(value: CryptoAmount) -> std::result::Result<Self, Self::Error> {
129 Ok(value.0.try_into()?)
130 }
131}
132
133impl std::ops::Add for CryptoAmount {
135 type Output = Self;
136
137 fn add(self, rhs: Self) -> Self::Output {
138 Self(self.0 + rhs.0)
139 }
140}
141
142impl std::ops::Div for CryptoAmount {
144 type Output = Self;
145
146 fn div(self, rhs: Self) -> Self::Output {
147 Self(self.0 / rhs.0)
148 }
149}
150
151impl std::ops::Mul for CryptoAmount {
153 type Output = Self;
154
155 fn mul(self, rhs: Self) -> Self::Output {
156 Self(self.0 * rhs.0)
157 }
158}
159
160#[cfg(test)]
161mod test {
162 use crate::types::currencies::Currency;
163
164 use rust_decimal_macros::dec;
165
166 use super::CryptoAmount;
167
168 #[rstest::rstest]
169 fn test_display_roundtrip(#[values(Currency::Iota, Currency::Eth)] c: Currency) {
170 assert_eq!(c, Currency::try_from(c.to_string()).unwrap());
171 }
172
173 #[rstest::rstest]
174 #[case(dec!(0.0), Some(CryptoAmount(dec!(0.0))))]
175 #[case(dec!(-0.0), Some(CryptoAmount(dec!(-0.0))))]
176 #[case(dec!(-1.0), None)]
177 #[case(dec!(-10.5), None)]
178 #[case(dec!(1.0), Some(CryptoAmount(dec!(1.0))))]
179 #[case(dec!(10.0), Some(CryptoAmount(dec!(10.0))))]
180 fn test_try_from_non_zero_dec(#[case] value: rust_decimal::Decimal, #[case] expected_value: Option<CryptoAmount>) {
181 let amount = CryptoAmount::try_from(value);
182
183 match (amount, expected_value) {
184 (Ok(amount), Some(expected)) => assert_eq!(amount, expected),
185 (Err(error), None) => assert!(matches!(error, crate::Error::NegativeAmount)),
186 (amount, expected) => panic!("expected {expected:?} but got {amount:?} for {value}"),
187 }
188 }
189
190 #[rstest::rstest]
191 #[case(0.0, Some(CryptoAmount(dec!(0.0))))]
192 #[case(-0.0, Some(CryptoAmount(dec!(0.0))))] #[case(-1.0, None)]
194 #[case(-10.5, None)]
195 #[case(1.0, Some(CryptoAmount(dec!(1.0))))]
196 #[case(10.0, Some(CryptoAmount(dec!(10.0))))]
197 fn test_try_from_non_zero_f64(#[case] value: f64, #[case] expected_value: Option<CryptoAmount>) {
198 let amount = CryptoAmount::try_from(value);
199
200 match (amount, expected_value) {
201 (Ok(amount), Some(expected)) => assert_eq!(amount, expected),
202 (Err(error), None) => assert!(matches!(error, crate::Error::NegativeAmount)),
203 (amount, expected) => panic!("expected {expected:?} but got {amount:?} for {value}"),
204 }
205 }
206}