Skip to main content

decimal_money/
currency.rs

1#[cfg(feature = "serde_impl")]
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5/// Represents a supported currency.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7#[cfg_attr(feature = "serde_impl", derive(Serialize, Deserialize))]
8#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
9pub enum Currency {
10    /// US Dollar.
11    USD,
12    /// Euro.
13    EUR,
14    /// British Pound.
15    GBP,
16    /// Japanese Yen.
17    JPY,
18    /// Swiss Franc.
19    CHF,
20    /// Canadian Dollar.
21    CAD,
22    /// Australian Dollar.
23    AUD,
24    /// Chinese Yuan.
25    CNY,
26    /// Indian Rupee.
27    INR,
28    /// Brazilian Real.
29    BRL,
30    /// South Korean Won.
31    KRW,
32    /// Mexican Peso.
33    MXN,
34    /// Swedish Krona.
35    SEK,
36    /// Norwegian Krone.
37    NOK,
38    /// Danish Krone.
39    DKK,
40    /// Polish Zloty.
41    PLN,
42    /// Czech Koruna.
43    CZK,
44    /// Hungarian Forint.
45    HUF,
46    /// Russian Ruble.
47    RUB,
48    /// South African Rand.
49    ZAR,
50    /// Singapore Dollar.
51    SGD,
52    /// Hong Kong Dollar.
53    HKD,
54    /// New Zealand Dollar.
55    NZD,
56    /// Thai Baht.
57    THB,
58    /// Turkish Lira.
59    TRY,
60    /// UAE Dirham.
61    AED,
62    /// Saudi Riyal.
63    SAR,
64    /// Nigerian Naira.
65    NGN,
66    /// Egyptian Pound.
67    EGP,
68    /// Philippine Peso.
69    PHP,
70    /// Indonesian Rupiah.
71    IDR,
72    /// Malaysian Ringgit.
73    MYR,
74    /// Vietnamese Dong.
75    VND,
76    /// Pakistani Rupee.
77    PKR,
78    /// Bangladeshi Taka.
79    BDT,
80    /// Bitcoin.
81    BTC,
82    /// Ethereum.
83    ETH,
84}
85
86impl Currency {
87    /// Returns the ISO 4217 currency code.
88    pub fn code(&self) -> &'static str {
89        match self {
90            Currency::USD => "USD",
91            Currency::EUR => "EUR",
92            Currency::GBP => "GBP",
93            Currency::JPY => "JPY",
94            Currency::CHF => "CHF",
95            Currency::CAD => "CAD",
96            Currency::AUD => "AUD",
97            Currency::CNY => "CNY",
98            Currency::INR => "INR",
99            Currency::BRL => "BRL",
100            Currency::KRW => "KRW",
101            Currency::MXN => "MXN",
102            Currency::SEK => "SEK",
103            Currency::NOK => "NOK",
104            Currency::DKK => "DKK",
105            Currency::PLN => "PLN",
106            Currency::CZK => "CZK",
107            Currency::HUF => "HUF",
108            Currency::RUB => "RUB",
109            Currency::ZAR => "ZAR",
110            Currency::SGD => "SGD",
111            Currency::HKD => "HKD",
112            Currency::NZD => "NZD",
113            Currency::THB => "THB",
114            Currency::TRY => "TRY",
115            Currency::AED => "AED",
116            Currency::SAR => "SAR",
117            Currency::NGN => "NGN",
118            Currency::EGP => "EGP",
119            Currency::PHP => "PHP",
120            Currency::IDR => "IDR",
121            Currency::MYR => "MYR",
122            Currency::VND => "VND",
123            Currency::PKR => "PKR",
124            Currency::BDT => "BDT",
125            Currency::BTC => "BTC",
126            Currency::ETH => "ETH",
127        }
128    }
129
130    /// Returns the display symbol for the currency.
131    pub fn symbol(&self) -> &'static str {
132        match self {
133            Currency::USD => "$",
134            Currency::EUR => "\u{20AC}",
135            Currency::GBP => "\u{00A3}",
136            Currency::JPY => "\u{00A5}",
137            Currency::CHF => "CHF",
138            Currency::CAD => "C$",
139            Currency::AUD => "A$",
140            Currency::CNY => "\u{00A5}",
141            Currency::INR => "\u{20B9}",
142            Currency::BRL => "R$",
143            Currency::KRW => "\u{20A9}",
144            Currency::MXN => "MX$",
145            Currency::SEK => "kr",
146            Currency::NOK => "kr",
147            Currency::DKK => "kr",
148            Currency::PLN => "z\u{0142}",
149            Currency::CZK => "K\u{010D}",
150            Currency::HUF => "Ft",
151            Currency::RUB => "\u{20BD}",
152            Currency::ZAR => "R",
153            Currency::SGD => "S$",
154            Currency::HKD => "HK$",
155            Currency::NZD => "NZ$",
156            Currency::THB => "\u{0E3F}",
157            Currency::TRY => "\u{20BA}",
158            Currency::AED => "AED",
159            Currency::SAR => "SAR",
160            Currency::NGN => "\u{20A6}",
161            Currency::EGP => "E\u{00A3}",
162            Currency::PHP => "\u{20B1}",
163            Currency::IDR => "Rp",
164            Currency::MYR => "RM",
165            Currency::VND => "\u{20AB}",
166            Currency::PKR => "\u{20A8}",
167            Currency::BDT => "\u{09F3}",
168            Currency::BTC => "\u{20BF}",
169            Currency::ETH => "\u{039E}",
170        }
171    }
172
173    /// Returns the number of decimal places for the currency.
174    pub fn decimal_places(&self) -> u32 {
175        match self {
176            Currency::JPY | Currency::KRW => 0,
177            Currency::BTC => 8,
178            Currency::ETH => 18,
179            _ => 2,
180        }
181    }
182
183    /// Returns true if this is a cryptocurrency.
184    pub fn is_crypto(&self) -> bool {
185        matches!(self, Currency::BTC | Currency::ETH)
186    }
187}
188
189impl fmt::Display for Currency {
190    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
191        write!(f, "{}", self.code())
192    }
193}
194
195impl std::str::FromStr for Currency {
196    type Err = crate::error::MoneyError;
197
198    fn from_str(s: &str) -> Result<Self, Self::Err> {
199        match s.to_uppercase().as_str() {
200            "USD" => Ok(Currency::USD),
201            "EUR" => Ok(Currency::EUR),
202            "GBP" => Ok(Currency::GBP),
203            "JPY" => Ok(Currency::JPY),
204            "CHF" => Ok(Currency::CHF),
205            "CAD" => Ok(Currency::CAD),
206            "AUD" => Ok(Currency::AUD),
207            "CNY" => Ok(Currency::CNY),
208            "INR" => Ok(Currency::INR),
209            "BRL" => Ok(Currency::BRL),
210            "KRW" => Ok(Currency::KRW),
211            "MXN" => Ok(Currency::MXN),
212            "SEK" => Ok(Currency::SEK),
213            "NOK" => Ok(Currency::NOK),
214            "DKK" => Ok(Currency::DKK),
215            "PLN" => Ok(Currency::PLN),
216            "CZK" => Ok(Currency::CZK),
217            "HUF" => Ok(Currency::HUF),
218            "RUB" => Ok(Currency::RUB),
219            "ZAR" => Ok(Currency::ZAR),
220            "SGD" => Ok(Currency::SGD),
221            "HKD" => Ok(Currency::HKD),
222            "NZD" => Ok(Currency::NZD),
223            "THB" => Ok(Currency::THB),
224            "TRY" => Ok(Currency::TRY),
225            "AED" => Ok(Currency::AED),
226            "SAR" => Ok(Currency::SAR),
227            "NGN" => Ok(Currency::NGN),
228            "EGP" => Ok(Currency::EGP),
229            "PHP" => Ok(Currency::PHP),
230            "IDR" => Ok(Currency::IDR),
231            "MYR" => Ok(Currency::MYR),
232            "VND" => Ok(Currency::VND),
233            "PKR" => Ok(Currency::PKR),
234            "BDT" => Ok(Currency::BDT),
235            "BTC" => Ok(Currency::BTC),
236            "ETH" => Ok(Currency::ETH),
237            _ => Err(crate::error::MoneyError::InvalidAmount(format!(
238                "Unknown currency: {s}"
239            ))),
240        }
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247
248    #[test]
249    fn test_currency_codes() {
250        assert_eq!(Currency::USD.code(), "USD");
251        assert_eq!(Currency::BTC.code(), "BTC");
252    }
253
254    #[test]
255    fn test_currency_symbols() {
256        assert_eq!(Currency::USD.symbol(), "$");
257        assert_eq!(Currency::EUR.symbol(), "\u{20AC}");
258    }
259
260    #[test]
261    fn test_decimal_places() {
262        assert_eq!(Currency::USD.decimal_places(), 2);
263        assert_eq!(Currency::JPY.decimal_places(), 0);
264        assert_eq!(Currency::BTC.decimal_places(), 8);
265    }
266
267    #[test]
268    fn test_is_crypto() {
269        assert!(Currency::BTC.is_crypto());
270        assert!(Currency::ETH.is_crypto());
271        assert!(!Currency::USD.is_crypto());
272    }
273
274    #[test]
275    fn test_from_str() {
276        assert_eq!("USD".parse::<Currency>().unwrap(), Currency::USD);
277        assert_eq!("btc".parse::<Currency>().unwrap(), Currency::BTC);
278        assert!("XYZ".parse::<Currency>().is_err());
279    }
280}