currency.rs (4142B)
1 //! Canonical three-letter currency codes and currency-specific minor units. 2 3 use core::fmt; 4 use core::str::FromStr; 5 6 #[cfg(all(feature = "serde", not(feature = "std")))] 7 use alloc::string::String; 8 #[cfg(feature = "serde")] 9 #[cfg(feature = "std")] 10 use std::string::String; 11 12 #[cfg(feature = "serde")] 13 use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error as DeError}; 14 15 #[cfg_attr(all(test, feature = "std"), derive(dto_bindgen::Dto))] 16 #[cfg_attr(all(test, feature = "std"), dto(as = "string"))] 17 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 18 pub struct Currency([u8; 3]); 19 20 impl Currency { 21 /// Builds a currency from its canonical three-byte ASCII code. 22 #[inline] 23 pub const fn from_const(bytes: [u8; 3]) -> Result<Self, ParseError> { 24 if Self::is_ascii_upper(bytes[0]) 25 && Self::is_ascii_upper(bytes[1]) 26 && Self::is_ascii_upper(bytes[2]) 27 { 28 Ok(Self(bytes)) 29 } else { 30 Err(ParseError::InvalidFormat) 31 } 32 } 33 34 const fn is_ascii_upper(byte: u8) -> bool { 35 byte >= b'A' && byte <= b'Z' 36 } 37 38 #[inline] 39 pub fn from_str_upper(s: &str) -> Result<Self, ParseError> { 40 let b = s.as_bytes(); 41 if b.len() != 3 || b.iter().any(|c| !c.is_ascii_uppercase()) { 42 return Err(ParseError::InvalidFormat); 43 } 44 Ok(Self([b[0], b[1], b[2]])) 45 } 46 47 #[inline] 48 pub fn as_str(&self) -> &str { 49 core::str::from_utf8(&self.0).unwrap_or("???") 50 } 51 52 #[inline] 53 pub const fn as_bytes(&self) -> &[u8; 3] { 54 &self.0 55 } 56 57 pub const USD: Currency = Currency(*b"USD"); 58 pub const EUR: Currency = Currency(*b"EUR"); 59 pub const GBP: Currency = Currency(*b"GBP"); 60 pub const JPY: Currency = Currency(*b"JPY"); 61 pub const CAD: Currency = Currency(*b"CAD"); 62 pub const AUD: Currency = Currency(*b"AUD"); 63 64 #[inline] 65 pub const fn minor_unit_exponent(&self) -> u32 { 66 match self.0 { 67 [b'J', b'P', b'Y'] | [b'K', b'R', b'W'] | [b'V', b'N', b'D'] => 0, 68 [b'B', b'H', b'D'] 69 | [b'I', b'Q', b'D'] 70 | [b'J', b'O', b'D'] 71 | [b'K', b'W', b'D'] 72 | [b'L', b'Y', b'D'] 73 | [b'O', b'M', b'R'] 74 | [b'T', b'N', b'D'] => 3, 75 _ => 2, 76 } 77 } 78 } 79 80 impl fmt::Debug for Currency { 81 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 82 f.debug_tuple("Currency").field(&self.as_str()).finish() 83 } 84 } 85 86 impl fmt::Display for Currency { 87 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 88 f.write_str(self.as_str()) 89 } 90 } 91 92 impl TryFrom<&str> for Currency { 93 type Error = ParseError; 94 fn try_from(s: &str) -> Result<Self, Self::Error> { 95 s.parse() 96 } 97 } 98 99 impl FromStr for Currency { 100 type Err = ParseError; 101 102 fn from_str(s: &str) -> Result<Self, Self::Err> { 103 // Currency input is canonicalized to uppercase ASCII. Serialization 104 // and Display always emit that canonical representation. 105 let s = s.trim(); 106 if s.len() != 3 || !s.chars().all(|c| c.is_ascii_alphabetic()) { 107 return Err(ParseError::InvalidFormat); 108 } 109 let upper = s.to_ascii_uppercase(); 110 Self::from_str_upper(&upper) 111 } 112 } 113 114 #[non_exhaustive] 115 #[derive(Debug, Clone, Copy, PartialEq, Eq)] 116 pub enum ParseError { 117 InvalidFormat, 118 } 119 120 impl fmt::Display for ParseError { 121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 122 match self { 123 ParseError::InvalidFormat => { 124 write!(f, "currency must be a 3-letter code") 125 } 126 } 127 } 128 } 129 130 #[cfg(feature = "std")] 131 impl std::error::Error for ParseError {} 132 133 #[cfg(feature = "serde")] 134 impl Serialize for Currency { 135 fn serialize<S: Serializer>(&self, ser: S) -> Result<S::Ok, S::Error> { 136 ser.serialize_str(self.as_str()) 137 } 138 } 139 140 #[cfg(feature = "serde")] 141 impl<'de> Deserialize<'de> for Currency { 142 fn deserialize<D: Deserializer<'de>>(de: D) -> Result<Self, D::Error> { 143 let s = String::deserialize(de)?; 144 s.parse().map_err(D::Error::custom) 145 } 146 }