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