1#[cfg(test)]
4pub(crate) mod test;
5
6use std::fmt;
7
8use crate::{
9 from_warning_all, json,
10 warning::{self, GatherWarnings as _},
11 IntoCaveat as _, Verdict,
12};
13
14#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
16pub enum Warning {
17 ContainsEscapeCodes,
19
20 Decode(json::decode::Warning),
22
23 PreferUpperCase,
25
26 InvalidCode,
28
29 InvalidType { type_found: json::ValueKind },
31
32 InvalidLength,
34
35 InvalidCodeXTS,
37
38 InvalidCodeXXX,
40}
41
42impl Warning {
43 fn invalid_type(elem: &json::Element<'_>) -> Self {
44 Self::InvalidType {
45 type_found: elem.value().kind(),
46 }
47 }
48}
49
50impl fmt::Display for Warning {
51 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
52 match self {
53 Self::ContainsEscapeCodes => write!(
54 f,
55 "The currency-code contains escape-codes but it does not need them.",
56 ),
57 Self::Decode(warning) => fmt::Display::fmt(warning, f),
58 Self::PreferUpperCase => write!(
59 f,
60 "The currency-code follows the ISO 4217 standard whch states: the chars should be uppercase.",
61 ),
62 Self::InvalidCode => {
63 write!(f, "The currency-code is not a valid ISO 4217 code.")
64 }
65 Self::InvalidType { .. } => write!(f, "The currency-code should be a string."),
66 Self::InvalidLength => write!(f, "The currency-code follows the ISO 4217 standard whch states: the code should be three chars."),
67 Self::InvalidCodeXTS => write!(
68 f,
69 "The currency-code is `XTS`. This is a code for testing only",
70 ),
71 Self::InvalidCodeXXX => write!(
72 f,
73 "The currency-code is `XXX`. This means there is no currency",
74 ),
75 }
76 }
77}
78
79impl crate::Warning for Warning {
80 fn id(&self) -> warning::Id {
81 match self {
82 Self::ContainsEscapeCodes => warning::Id::from_static("contains_escape_codes"),
83 Self::Decode(kind) => kind.id(),
84 Self::PreferUpperCase => warning::Id::from_static("prefer_upper_case"),
85 Self::InvalidCode => warning::Id::from_static("invalid_code"),
86 Self::InvalidType { type_found } => {
87 warning::Id::from_string(format!("invalid_type({type_found})"))
88 }
89 Self::InvalidLength => warning::Id::from_static("invalid_length"),
90 Self::InvalidCodeXTS => warning::Id::from_static("invalid_code_xts"),
91 Self::InvalidCodeXXX => warning::Id::from_static("invalid_code_xxx"),
92 }
93 }
94}
95
96from_warning_all!(json::decode::Warning => Warning::Decode);
97
98impl json::FromJson<'_> for Code {
99 type Warning = Warning;
100
101 fn from_json(elem: &json::Element<'_>) -> Verdict<Self, Self::Warning> {
102 let mut warnings = warning::Set::new();
103 let value = elem.as_value();
104
105 let Some(s) = value.to_raw_str() else {
106 return warnings.bail(Warning::invalid_type(elem), elem);
107 };
108
109 let pending_str = s.has_escapes(elem).gather_warnings_into(&mut warnings);
110
111 let s = match pending_str {
112 json::decode::PendingStr::NoEscapes(s) => s,
113 json::decode::PendingStr::HasEscapes(_) => {
114 return warnings.bail(Warning::ContainsEscapeCodes, elem);
115 }
116 };
117
118 let bytes = s.as_bytes();
119
120 let [a, b, c] = bytes else {
122 return warnings.bail(Warning::InvalidLength, elem);
123 };
124
125 let triplet: [u8; 3] = [
126 a.to_ascii_uppercase(),
127 b.to_ascii_uppercase(),
128 c.to_ascii_uppercase(),
129 ];
130
131 if triplet != bytes {
132 warnings.insert(Warning::PreferUpperCase, elem);
133 }
134
135 let Some(code) = Code::from_alpha_3(triplet) else {
136 return warnings.bail(Warning::InvalidCode, elem);
137 };
138
139 if matches!(code, Code::Xts) {
140 warnings.insert(Warning::InvalidCodeXTS, elem);
141 } else if matches!(code, Code::Xxx) {
142 warnings.insert(Warning::InvalidCodeXXX, elem);
143 }
144
145 Ok(code.into_caveat(warnings))
146 }
147}
148
149impl fmt::Display for Code {
150 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
151 f.write_str(self.into_str())
152 }
153}
154
155macro_rules! currency_codes {
157 [$(($name:ident, $alph3:literal, $symbol:literal)),*] => {
158 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
160 pub enum Code {
161 $($name),*
162 }
163
164 impl Code {
165 const fn from_alpha_3(code: [u8; 3]) -> Option<Self> {
167 match &code {
168 $($alph3 => Some(Self::$name),)*
169 _ => None
170 }
171 }
172
173 pub fn into_str(self) -> &'static str {
175 let bytes = match self {
176 $(Self::$name => $alph3),*
177 };
178 std::str::from_utf8(bytes).expect("The currency code bytes are known to be valid UTF8 as they are embedded into the binary")
179 }
180
181 pub fn into_symbol(self) -> &'static str {
183 match self {
184 $(Self::$name => $symbol),*
185 }
186 }
187 }
188 };
189}
190
191currency_codes![
192 (Aed, b"AED", "D"),
193 (Afn, b"AFN", "Afs"),
194 (All, b"ALL", "L"),
195 (Amd, b"AMD", "֏"),
196 (Ang, b"ANG", "NAƒ"),
197 (Aoa, b"AOA", "Kz"),
198 (Ars, b"ARS", "$"),
199 (Aud, b"AUD", "$"),
200 (Awg, b"AWG", "Aƒ"),
201 (Azn, b"AZN", "₼"),
202 (Bam, b"BAM", "KM"),
203 (Bbd, b"BBD", "$"),
204 (Bdt, b"BDT", "৳"),
205 (Bgn, b"BGN", "лв"),
206 (Bhd, b"BHD", "BD"),
207 (Bif, b"BIF", "FBu"),
208 (Bmd, b"BMD", "$"),
209 (Bnd, b"BND", "$"),
210 (Bob, b"BOB", "Bs"),
211 (Bov, b"BOV", "BOV"),
212 (Brl, b"BRL", "$"),
213 (Bsd, b"BSD", "$"),
214 (Btn, b"BTN", "Nu."),
215 (Bwp, b"BWP", "p"),
216 (Byn, b"BYN", "BYN"),
217 (Bzd, b"BZD", "$"),
218 (Cad, b"CAD", "$"),
219 (Cdf, b"CDF", "FC"),
220 (Che, b"CHE", "CHE"),
221 (Chf, b"CHF", "CHF"),
222 (Chw, b"CHW", "CHW"),
223 (Clf, b"CLF", "UF"),
224 (Clp, b"CLP", "$"),
225 (Cny, b"CNY", "¥"),
226 (Cop, b"COP", "$"),
227 (Cou, b"COU", "COU"),
228 (Crc, b"CRC", "₡"),
229 (Cuc, b"CUC", "$"),
230 (Cup, b"CUP", "$"),
231 (Cve, b"CVE", "$"),
232 (Czk, b"CZK", "Kč"),
233 (Djf, b"DJF", "Fdj"),
234 (Dkk, b"DKK", "kr."),
235 (Dop, b"DOP", "$"),
236 (Dzd, b"DZD", "DA"),
237 (Egp, b"EGP", "£"),
238 (Ern, b"ERN", "Nfk"),
239 (Etb, b"ETB", "Br"),
240 (Eur, b"EUR", "€"),
241 (Fjd, b"FJD", "$"),
242 (Fkp, b"FKP", "£"),
243 (Gbp, b"GBP", "£"),
244 (Gel, b"GEL", "₾"),
245 (Ghs, b"GHS", "GH₵"),
246 (Gip, b"GIP", "£"),
247 (Gmd, b"GMD", "D"),
248 (Gnf, b"GNF", "FG"),
249 (Gtq, b"GTQ", "Q"),
250 (Gyd, b"GYD", "$"),
251 (Hkd, b"HKD", "$"),
252 (Hnl, b"HNL", "L"),
253 (Hrk, b"HRK", "lp"),
254 (Htg, b"HTG", "G"),
255 (Huf, b"HUF", "Ft"),
256 (Idr, b"IDR", "Rp"),
257 (Ils, b"ILS", "₪"),
258 (Inr, b"INR", "₹"),
259 (Iqd, b"IQD", "IQD"),
260 (Irr, b"IRR", "Rl"),
261 (Isk, b"ISK", "kr"),
262 (Jmd, b"JMD", "$"),
263 (Jod, b"JOD", "د.أ"),
264 (Jpy, b"JPY", "¥"),
265 (Kes, b"KES", "KES"),
266 (Kgs, b"KGS", "⃀"),
267 (Khr, b"KHR", "៛"),
268 (Kmf, b"KMF", "KMF"),
269 (Kpw, b"KPW", "₩"),
270 (Krw, b"KRW", "₩"),
271 (Kwd, b"KWD", "KD"),
272 (Kyd, b"KYD", "$"),
273 (Kzt, b"KZT", "₸"),
274 (Lak, b"LAK", "₭"),
275 (Lbp, b"LBP", "LL"),
276 (Lkr, b"LKR", "₨"),
277 (Lrd, b"LRD", "$"),
278 (Lsl, b"LSL", "L"),
279 (Lyd, b"LYD", "LD"),
280 (Mad, b"MAD", "DH"),
281 (Mdl, b"MDL", "L"),
282 (Mga, b"MGA", "Ar"),
283 (Mkd, b"MKD", "den"),
284 (Mmk, b"MMK", "Ks."),
285 (Mnt, b"MNT", "₮"),
286 (Mop, b"MOP", "$"),
287 (Mru, b"MRU", "UM"),
288 (Mur, b"MUR", "₨"),
289 (Mvr, b"MVR", "Rf"),
290 (Mwk, b"MWK", "K"),
291 (Mxn, b"MXN", "$"),
292 (Mxv, b"MXV", "MXV"),
293 (Myr, b"MYR", "RM"),
294 (Mzn, b"MZN", "MT"),
295 (Nad, b"NAD", "$"),
296 (Ngn, b"NGN", "₦"),
297 (Nio, b"NIO", "C$"),
298 (Nok, b"NOK", "kr"),
299 (Npr, b"NPR", "रु"),
300 (Nzd, b"NZD", "$"),
301 (Omr, b"OMR", "OMR"),
302 (Pab, b"PAB", "฿"),
303 (Pen, b"PEN", "S/"),
304 (Pgk, b"PGK", "K"),
305 (Php, b"PHP", "₱"),
306 (Pkr, b"PKR", "Re"),
307 (Pln, b"PLN", "zł"),
308 (Pyg, b"PYG", "₲"),
309 (Qar, b"QAR", "QR"),
310 (Ron, b"RON", "lei"),
311 (Rsd, b"RSD", "DIN"),
312 (Rub, b"RUB", "₽"),
313 (Rwf, b"RWF", "R₣"),
314 (Sar, b"SAR", "\u{20C1}"),
315 (Sbd, b"SBD", "$"),
316 (Scr, b"SCR", "R"),
317 (Sdg, b"SDG", "PT"),
318 (Sek, b"SEK", "kr"),
319 (Sgd, b"SGD", "$"),
320 (Shp, b"SHP", "£"),
321 (Sle, b"SLE", "Le"),
322 (Sll, b"SLL", "Le"),
323 (Sos, b"SOS", "Sh.So."),
324 (Srd, b"SRD", "$"),
325 (Ssp, b"SSP", "£"),
326 (Stn, b"STN", "Db"),
327 (Svc, b"SVC", "₡"),
328 (Syp, b"SYP", "LS"),
329 (Szl, b"SZL", "L"),
330 (Thb, b"THB", "฿"),
331 (Tjs, b"TJS", "SM"),
332 (Tmt, b"TMT", "m"),
333 (Tnd, b"TND", "DT"),
334 (Top, b"TOP", "$"),
335 (Try, b"TRY", "₺"),
336 (Ttd, b"TTD", "$"),
337 (Twd, b"TWD", "$"),
338 (Tzs, b"TZS", "TZS"),
339 (Uah, b"UAH", "₴"),
340 (Ugx, b"UGX", "UGX"),
341 (Usd, b"USD", "$"),
342 (Usn, b"USN", "USN"),
343 (Uyi, b"UYI", "UYI"),
344 (Uyu, b"UYU", "$"),
345 (Uyw, b"UYW", "UYW"),
346 (Uzs, b"UZS", "UZS"),
347 (Ved, b"VED", "Bs.D"),
348 (Ves, b"VES", "Bs.S"),
349 (Vnd, b"VND", "₫"),
350 (Vuv, b"VUV", "Vt"),
351 (Wst, b"WST", "$"),
352 (Xaf, b"XAF", "F.CFA"),
353 (Xag, b"XAG", "XAG"),
354 (Xau, b"XAU", "XAU"),
355 (Xba, b"XBA", "XBA"),
356 (Xbb, b"XBB", "XBB"),
357 (Xbc, b"XBC", "XBC"),
358 (Xbd, b"XBD", "XBD"),
359 (Xcd, b"XCD", "$"),
360 (Xdr, b"XDR", "SDR"),
361 (Xof, b"XOF", "F.CFA"),
362 (Xpd, b"XPD", "XPD"),
363 (Xpf, b"XPF", "F"),
364 (Xpt, b"XPT", "XPT"),
365 (Xsu, b"XSU", "sucre"),
366 (Xts, b"XTS", "XTS"),
367 (Xua, b"XUA", "XUA"),
368 (Xxx, b"XXX", "XXX"),
369 (Yer, b"YER", "﷼"),
370 (Zar, b"ZAR", "R"),
371 (Zmw, b"ZMW", "K"),
372 (Zwl, b"ZWL", "$")
373];