use serde::{Deserialize, Serialize};
use crate::error::MoneyError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Currency {
code: [u8; 3],
minor_units: u8,
}
impl Currency {
pub const fn new(code: [u8; 3], minor_units: u8) -> Self {
Self { code, minor_units }
}
pub const USD: Currency = Currency::new(*b"USD", 2);
pub const EUR: Currency = Currency::new(*b"EUR", 2);
pub const GBP: Currency = Currency::new(*b"GBP", 2);
pub const JPY: Currency = Currency::new(*b"JPY", 0);
pub const CHF: Currency = Currency::new(*b"CHF", 2);
pub fn from_code(code: &str) -> Result<Currency, MoneyError> {
let bytes = code.as_bytes();
if bytes.len() != 3 || !bytes.iter().all(|b| b.is_ascii_alphabetic()) {
return Err(MoneyError::BadCurrencyCode(code.to_string()));
}
let upper =
[bytes[0].to_ascii_uppercase(), bytes[1].to_ascii_uppercase(), bytes[2].to_ascii_uppercase()];
Ok(match &upper {
b"USD" => Currency::USD,
b"EUR" => Currency::EUR,
b"GBP" => Currency::GBP,
b"JPY" => Currency::JPY,
b"CHF" => Currency::CHF,
other => Currency::new(*other, 2),
})
}
pub fn code(&self) -> &str {
std::str::from_utf8(&self.code).unwrap_or("???")
}
pub fn minor_units(&self) -> u8 {
self.minor_units
}
}
impl std::fmt::Display for Currency {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.code())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn from_code_known_and_unknown() {
assert_eq!(Currency::from_code("usd").unwrap(), Currency::USD);
assert_eq!(Currency::from_code("JPY").unwrap().minor_units(), 0);
assert_eq!(Currency::from_code("xyz").unwrap().minor_units(), 2);
assert!(Currency::from_code("US").is_err());
assert!(Currency::from_code("US1").is_err());
}
#[test]
fn code_round_trips() {
assert_eq!(Currency::USD.code(), "USD");
assert_eq!(Currency::USD.to_string(), "USD");
}
}