1#[cfg(feature = "serde_impl")]
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5#[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 USD,
12 EUR,
14 GBP,
16 JPY,
18 CHF,
20 CAD,
22 AUD,
24 CNY,
26 INR,
28 BRL,
30 KRW,
32 MXN,
34 SEK,
36 NOK,
38 DKK,
40 PLN,
42 CZK,
44 HUF,
46 RUB,
48 ZAR,
50 SGD,
52 HKD,
54 NZD,
56 THB,
58 TRY,
60 AED,
62 SAR,
64 NGN,
66 EGP,
68 PHP,
70 IDR,
72 MYR,
74 VND,
76 PKR,
78 BDT,
80 BTC,
82 ETH,
84}
85
86impl Currency {
87 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 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 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 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}