#[cfg(test)]
pub(crate) mod test;
use std::fmt;
use crate::{
from_warning_all, json,
warning::{self, GatherWarnings as _},
IntoCaveat, Verdict,
};
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum Warning {
ContainsEscapeCodes,
Decode(json::decode::Warning),
PreferUpperCase,
InvalidCode,
InvalidType { type_found: json::ValueKind },
InvalidLength,
InvalidCodeXTS,
InvalidCodeXXX,
}
impl Warning {
fn invalid_type(elem: &json::Element<'_>) -> Self {
Self::InvalidType {
type_found: elem.value().kind(),
}
}
}
impl fmt::Display for Warning {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::ContainsEscapeCodes => write!(
f,
"The currency-code contains escape-codes but it does not need them.",
),
Self::Decode(warning) => fmt::Display::fmt(warning, f),
Self::PreferUpperCase => write!(
f,
"The currency-code follows the ISO 4217 standard whch states: the chars should be uppercase.",
),
Self::InvalidCode => {
write!(f, "The currency-code is not a valid ISO 4217 code.")
}
Self::InvalidType { .. } => write!(f, "The currency-code should be a string."),
Self::InvalidLength => write!(f, "The currency-code follows the ISO 4217 standard whch states: the code should be three chars."),
Self::InvalidCodeXTS => write!(
f,
"The currency-code is `XTS`. This is a code for testing only",
),
Self::InvalidCodeXXX => write!(
f,
"The currency-code is `XXX`. This means there is no currency",
),
}
}
}
impl crate::Warning for Warning {
fn id(&self) -> warning::Id {
match self {
Self::ContainsEscapeCodes => warning::Id::from_static("contains_escape_codes"),
Self::Decode(kind) => kind.id(),
Self::PreferUpperCase => warning::Id::from_static("prefer_upper_case"),
Self::InvalidCode => warning::Id::from_static("invalid_code"),
Self::InvalidType { .. } => warning::Id::from_static("invalid_type"),
Self::InvalidLength => warning::Id::from_static("invalid_length"),
Self::InvalidCodeXTS => warning::Id::from_static("invalid_code_xts"),
Self::InvalidCodeXXX => warning::Id::from_static("invalid_code_xxx"),
}
}
}
from_warning_all!(json::decode::Warning => Warning::Decode);
impl json::FromJson<'_> for Code {
type Warning = Warning;
fn from_json(elem: &json::Element<'_>) -> Verdict<Self, Self::Warning> {
let mut warnings = warning::Set::new();
let value = elem.as_value();
let Some(s) = value.to_raw_str() else {
return warnings.bail(Warning::invalid_type(elem), elem);
};
let pending_str = s.has_escapes(elem).gather_warnings_into(&mut warnings);
let s = match pending_str {
json::decode::PendingStr::NoEscapes(s) => s,
json::decode::PendingStr::HasEscapes(_) => {
return warnings.bail(Warning::ContainsEscapeCodes, elem);
}
};
let bytes = s.as_bytes();
let [a, b, c] = bytes else {
return warnings.bail(Warning::InvalidLength, elem);
};
let triplet: [u8; 3] = [
a.to_ascii_uppercase(),
b.to_ascii_uppercase(),
c.to_ascii_uppercase(),
];
if triplet != bytes {
warnings.insert(Warning::PreferUpperCase, elem);
}
let Some(code) = Code::from_alpha_3(triplet) else {
return warnings.bail(Warning::InvalidCode, elem);
};
if matches!(code, Code::Xts) {
warnings.insert(Warning::InvalidCodeXTS, elem);
} else if matches!(code, Code::Xxx) {
warnings.insert(Warning::InvalidCodeXXX, elem);
}
Ok(code.into_caveat(warnings))
}
}
impl fmt::Display for Code {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.into_str())
}
}
macro_rules! currency_codes {
[$(($name:ident, $alph3:literal, $symbol:literal)),*] => {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Code {
$($name),*
}
impl Code {
const fn from_alpha_3(code: [u8; 3]) -> Option<Self> {
match &code {
$($alph3 => Some(Self::$name),)*
_ => None
}
}
pub fn into_str(self) -> &'static str {
let bytes = match self {
$(Self::$name => $alph3),*
};
std::str::from_utf8(bytes).expect("The currency code bytes are known to be valid UTF8 as they are embedded into the binary")
}
pub fn into_symbol(self) -> &'static str {
match self {
$(Self::$name => $symbol),*
}
}
}
};
}
currency_codes![
(Aed, b"AED", "D"),
(Afn, b"AFN", "Afs"),
(All, b"ALL", "L"),
(Amd, b"AMD", "֏"),
(Ang, b"ANG", "NAƒ"),
(Aoa, b"AOA", "Kz"),
(Ars, b"ARS", "$"),
(Aud, b"AUD", "$"),
(Awg, b"AWG", "Aƒ"),
(Azn, b"AZN", "₼"),
(Bam, b"BAM", "KM"),
(Bbd, b"BBD", "$"),
(Bdt, b"BDT", "৳"),
(Bgn, b"BGN", "лв"),
(Bhd, b"BHD", "BD"),
(Bif, b"BIF", "FBu"),
(Bmd, b"BMD", "$"),
(Bnd, b"BND", "$"),
(Bob, b"BOB", "Bs"),
(Bov, b"BOV", "BOV"),
(Brl, b"BRL", "$"),
(Bsd, b"BSD", "$"),
(Btn, b"BTN", "Nu."),
(Bwp, b"BWP", "p"),
(Byn, b"BYN", "BYN"),
(Bzd, b"BZD", "$"),
(Cad, b"CAD", "$"),
(Cdf, b"CDF", "FC"),
(Che, b"CHE", "CHE"),
(Chf, b"CHF", "CHF"),
(Chw, b"CHW", "CHW"),
(Clf, b"CLF", "UF"),
(Clp, b"CLP", "$"),
(Cny, b"CNY", "¥"),
(Cop, b"COP", "$"),
(Cou, b"COU", "COU"),
(Crc, b"CRC", "₡"),
(Cuc, b"CUC", "$"),
(Cup, b"CUP", "$"),
(Cve, b"CVE", "$"),
(Czk, b"CZK", "Kč"),
(Djf, b"DJF", "Fdj"),
(Dkk, b"DKK", "kr."),
(Dop, b"DOP", "$"),
(Dzd, b"DZD", "DA"),
(Egp, b"EGP", "£"),
(Ern, b"ERN", "Nfk"),
(Etb, b"ETB", "Br"),
(Eur, b"EUR", "€"),
(Fjd, b"FJD", "$"),
(Fkp, b"FKP", "£"),
(Gbp, b"GBP", "£"),
(Gel, b"GEL", "₾"),
(Ghs, b"GHS", "GH₵"),
(Gip, b"GIP", "£"),
(Gmd, b"GMD", "D"),
(Gnf, b"GNF", "FG"),
(Gtq, b"GTQ", "Q"),
(Gyd, b"GYD", "$"),
(Hkd, b"HKD", "$"),
(Hnl, b"HNL", "L"),
(Hrk, b"HRK", "lp"),
(Htg, b"HTG", "G"),
(Huf, b"HUF", "Ft"),
(Idr, b"IDR", "Rp"),
(Ils, b"ILS", "₪"),
(Inr, b"INR", "₹"),
(Iqd, b"IQD", "IQD"),
(Irr, b"IRR", "Rl"),
(Isk, b"ISK", "kr"),
(Jmd, b"JMD", "$"),
(Jod, b"JOD", "د.أ"),
(Jpy, b"JPY", "¥"),
(Kes, b"KES", "KES"),
(Kgs, b"KGS", "⃀"),
(Khr, b"KHR", "៛"),
(Kmf, b"KMF", "KMF"),
(Kpw, b"KPW", "₩"),
(Krw, b"KRW", "₩"),
(Kwd, b"KWD", "KD"),
(Kyd, b"KYD", "$"),
(Kzt, b"KZT", "₸"),
(Lak, b"LAK", "₭"),
(Lbp, b"LBP", "LL"),
(Lkr, b"LKR", "₨"),
(Lrd, b"LRD", "$"),
(Lsl, b"LSL", "L"),
(Lyd, b"LYD", "LD"),
(Mad, b"MAD", "DH"),
(Mdl, b"MDL", "L"),
(Mga, b"MGA", "Ar"),
(Mkd, b"MKD", "den"),
(Mmk, b"MMK", "Ks."),
(Mnt, b"MNT", "₮"),
(Mop, b"MOP", "$"),
(Mru, b"MRU", "UM"),
(Mur, b"MUR", "₨"),
(Mvr, b"MVR", "Rf"),
(Mwk, b"MWK", "K"),
(Mxn, b"MXN", "$"),
(Mxv, b"MXV", "MXV"),
(Myr, b"MYR", "RM"),
(Mzn, b"MZN", "MT"),
(Nad, b"NAD", "$"),
(Ngn, b"NGN", "₦"),
(Nio, b"NIO", "C$"),
(Nok, b"NOK", "kr"),
(Npr, b"NPR", "रु"),
(Nzd, b"NZD", "$"),
(Omr, b"OMR", "OMR"),
(Pab, b"PAB", "฿"),
(Pen, b"PEN", "S/"),
(Pgk, b"PGK", "K"),
(Php, b"PHP", "₱"),
(Pkr, b"PKR", "Re"),
(Pln, b"PLN", "zł"),
(Pyg, b"PYG", "₲"),
(Qar, b"QAR", "QR"),
(Ron, b"RON", "lei"),
(Rsd, b"RSD", "DIN"),
(Rub, b"RUB", "₽"),
(Rwf, b"RWF", "R₣"),
(Sar, b"SAR", "\u{20C1}"),
(Sbd, b"SBD", "$"),
(Scr, b"SCR", "R"),
(Sdg, b"SDG", "PT"),
(Sek, b"SEK", "kr"),
(Sgd, b"SGD", "$"),
(Shp, b"SHP", "£"),
(Sle, b"SLE", "Le"),
(Sll, b"SLL", "Le"),
(Sos, b"SOS", "Sh.So."),
(Srd, b"SRD", "$"),
(Ssp, b"SSP", "£"),
(Stn, b"STN", "Db"),
(Svc, b"SVC", "₡"),
(Syp, b"SYP", "LS"),
(Szl, b"SZL", "L"),
(Thb, b"THB", "฿"),
(Tjs, b"TJS", "SM"),
(Tmt, b"TMT", "m"),
(Tnd, b"TND", "DT"),
(Top, b"TOP", "$"),
(Try, b"TRY", "₺"),
(Ttd, b"TTD", "$"),
(Twd, b"TWD", "$"),
(Tzs, b"TZS", "TZS"),
(Uah, b"UAH", "₴"),
(Ugx, b"UGX", "UGX"),
(Usd, b"USD", "$"),
(Usn, b"USN", "USN"),
(Uyi, b"UYI", "UYI"),
(Uyu, b"UYU", "$"),
(Uyw, b"UYW", "UYW"),
(Uzs, b"UZS", "UZS"),
(Ved, b"VED", "Bs.D"),
(Ves, b"VES", "Bs.S"),
(Vnd, b"VND", "₫"),
(Vuv, b"VUV", "Vt"),
(Wst, b"WST", "$"),
(Xaf, b"XAF", "F.CFA"),
(Xag, b"XAG", "XAG"),
(Xau, b"XAU", "XAU"),
(Xba, b"XBA", "XBA"),
(Xbb, b"XBB", "XBB"),
(Xbc, b"XBC", "XBC"),
(Xbd, b"XBD", "XBD"),
(Xcd, b"XCD", "$"),
(Xdr, b"XDR", "SDR"),
(Xof, b"XOF", "F.CFA"),
(Xpd, b"XPD", "XPD"),
(Xpf, b"XPF", "F"),
(Xpt, b"XPT", "XPT"),
(Xsu, b"XSU", "sucre"),
(Xts, b"XTS", "XTS"),
(Xua, b"XUA", "XUA"),
(Xxx, b"XXX", "XXX"),
(Yer, b"YER", "﷼"),
(Zar, b"ZAR", "R"),
(Zmw, b"ZMW", "K"),
(Zwl, b"ZWL", "$")
];