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