1#[cfg(test)]
3pub(crate) mod test;
4
5use std::fmt;
6
7use crate::{
8 from_warning_all, into_caveat, 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.as_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
146into_caveat!(Code);
147
148impl fmt::Display for Code {
149 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
150 f.write_str(self.into_str())
151 }
152}
153
154macro_rules! currency_codes {
156 [$(($name:ident, $alph3:literal, $symbol:literal)),*] => {
157 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
159 pub enum Code {
160 $($name),*
161 }
162
163 impl Code {
164 const fn from_alpha_3(code: [u8; 3]) -> Option<Self> {
166 match &code {
167 $($alph3 => Some(Self::$name),)*
168 _ => None
169 }
170 }
171
172 pub fn into_str(self) -> &'static str {
174 let bytes = match self {
175 $(Self::$name => $alph3),*
176 };
177 std::str::from_utf8(bytes).expect("The currency code bytes are known to be valid UTF8 as they are embedded into the binary")
178 }
179
180 pub fn into_symbol(self) -> &'static str {
182 match self {
183 $(Self::$name => $symbol),*
184 }
185 }
186 }
187 };
188}
189
190currency_codes![
191 (Aed, b"AED", "D"),
192 (Afn, b"AFN", "Afs"),
193 (All, b"ALL", "L"),
194 (Amd, b"AMD", "֏"),
195 (Ang, b"ANG", "NAƒ"),
196 (Aoa, b"AOA", "Kz"),
197 (Ars, b"ARS", "$"),
198 (Aud, b"AUD", "$"),
199 (Awg, b"AWG", "Aƒ"),
200 (Azn, b"AZN", "₼"),
201 (Bam, b"BAM", "KM"),
202 (Bbd, b"BBD", "$"),
203 (Bdt, b"BDT", "৳"),
204 (Bgn, b"BGN", "лв"),
205 (Bhd, b"BHD", "BD"),
206 (Bif, b"BIF", "FBu"),
207 (Bmd, b"BMD", "$"),
208 (Bnd, b"BND", "$"),
209 (Bob, b"BOB", "Bs"),
210 (Bov, b"BOV", "BOV"),
211 (Brl, b"BRL", "$"),
212 (Bsd, b"BSD", "$"),
213 (Btn, b"BTN", "Nu."),
214 (Bwp, b"BWP", "p"),
215 (Byn, b"BYN", "BYN"),
216 (Bzd, b"BZD", "$"),
217 (Cad, b"CAD", "$"),
218 (Cdf, b"CDF", "FC"),
219 (Che, b"CHE", "CHE"),
220 (Chf, b"CHF", "CHF"),
221 (Chw, b"CHW", "CHW"),
222 (Clf, b"CLF", "UF"),
223 (Clp, b"CLP", "$"),
224 (Cny, b"CNY", "¥"),
225 (Cop, b"COP", "$"),
226 (Cou, b"COU", "COU"),
227 (Crc, b"CRC", "₡"),
228 (Cuc, b"CUC", "$"),
229 (Cup, b"CUP", "$"),
230 (Cve, b"CVE", "$"),
231 (Czk, b"CZK", "Kč"),
232 (Djf, b"DJF", "Fdj"),
233 (Dkk, b"DKK", "kr."),
234 (Dop, b"DOP", "$"),
235 (Dzd, b"DZD", "DA"),
236 (Egp, b"EGP", "£"),
237 (Ern, b"ERN", "Nfk"),
238 (Etb, b"ETB", "Br"),
239 (Eur, b"EUR", "€"),
240 (Fjd, b"FJD", "$"),
241 (Fkp, b"FKP", "£"),
242 (Gbp, b"GBP", "£"),
243 (Gel, b"GEL", "₾"),
244 (Ghs, b"GHS", "GH₵"),
245 (Gip, b"GIP", "£"),
246 (Gmd, b"GMD", "D"),
247 (Gnf, b"GNF", "FG"),
248 (Gtq, b"GTQ", "Q"),
249 (Gyd, b"GYD", "$"),
250 (Hkd, b"HKD", "$"),
251 (Hnl, b"HNL", "L"),
252 (Hrk, b"HRK", "lp"),
253 (Htg, b"HTG", "G"),
254 (Huf, b"HUF", "Ft"),
255 (Idr, b"IDR", "Rp"),
256 (Ils, b"ILS", "₪"),
257 (Inr, b"INR", "₹"),
258 (Iqd, b"IQD", "IQD"),
259 (Irr, b"IRR", "Rl"),
260 (Isk, b"ISK", "kr"),
261 (Jmd, b"JMD", "$"),
262 (Jod, b"JOD", "د.أ"),
263 (Jpy, b"JPY", "¥"),
264 (Kes, b"KES", "KES"),
265 (Kgs, b"KGS", "⃀"),
266 (Khr, b"KHR", "៛"),
267 (Kmf, b"KMF", "KMF"),
268 (Kpw, b"KPW", "₩"),
269 (Krw, b"KRW", "₩"),
270 (Kwd, b"KWD", "KD"),
271 (Kyd, b"KYD", "$"),
272 (Kzt, b"KZT", "₸"),
273 (Lak, b"LAK", "₭"),
274 (Lbp, b"LBP", "LL"),
275 (Lkr, b"LKR", "₨"),
276 (Lrd, b"LRD", "$"),
277 (Lsl, b"LSL", "L"),
278 (Lyd, b"LYD", "LD"),
279 (Mad, b"MAD", "DH"),
280 (Mdl, b"MDL", "L"),
281 (Mga, b"MGA", "Ar"),
282 (Mkd, b"MKD", "den"),
283 (Mmk, b"MMK", "Ks."),
284 (Mnt, b"MNT", "₮"),
285 (Mop, b"MOP", "$"),
286 (Mru, b"MRU", "UM"),
287 (Mur, b"MUR", "₨"),
288 (Mvr, b"MVR", "Rf"),
289 (Mwk, b"MWK", "K"),
290 (Mxn, b"MXN", "$"),
291 (Mxv, b"MXV", "MXV"),
292 (Myr, b"MYR", "RM"),
293 (Mzn, b"MZN", "MT"),
294 (Nad, b"NAD", "$"),
295 (Ngn, b"NGN", "₦"),
296 (Nio, b"NIO", "C$"),
297 (Nok, b"NOK", "kr"),
298 (Npr, b"NPR", "रु"),
299 (Nzd, b"NZD", "$"),
300 (Omr, b"OMR", "OMR"),
301 (Pab, b"PAB", "฿"),
302 (Pen, b"PEN", "S/"),
303 (Pgk, b"PGK", "K"),
304 (Php, b"PHP", "₱"),
305 (Pkr, b"PKR", "Re"),
306 (Pln, b"PLN", "zł"),
307 (Pyg, b"PYG", "₲"),
308 (Qar, b"QAR", "QR"),
309 (Ron, b"RON", "lei"),
310 (Rsd, b"RSD", "DIN"),
311 (Rub, b"RUB", "₽"),
312 (Rwf, b"RWF", "R₣"),
313 (Sar, b"SAR", "\u{20C1}"),
314 (Sbd, b"SBD", "$"),
315 (Scr, b"SCR", "R"),
316 (Sdg, b"SDG", "PT"),
317 (Sek, b"SEK", "kr"),
318 (Sgd, b"SGD", "$"),
319 (Shp, b"SHP", "£"),
320 (Sle, b"SLE", "Le"),
321 (Sll, b"SLL", "Le"),
322 (Sos, b"SOS", "Sh.So."),
323 (Srd, b"SRD", "$"),
324 (Ssp, b"SSP", "£"),
325 (Stn, b"STN", "Db"),
326 (Svc, b"SVC", "₡"),
327 (Syp, b"SYP", "LS"),
328 (Szl, b"SZL", "L"),
329 (Thb, b"THB", "฿"),
330 (Tjs, b"TJS", "SM"),
331 (Tmt, b"TMT", "m"),
332 (Tnd, b"TND", "DT"),
333 (Top, b"TOP", "$"),
334 (Try, b"TRY", "₺"),
335 (Ttd, b"TTD", "$"),
336 (Twd, b"TWD", "$"),
337 (Tzs, b"TZS", "TZS"),
338 (Uah, b"UAH", "₴"),
339 (Ugx, b"UGX", "UGX"),
340 (Usd, b"USD", "$"),
341 (Usn, b"USN", "USN"),
342 (Uyi, b"UYI", "UYI"),
343 (Uyu, b"UYU", "$"),
344 (Uyw, b"UYW", "UYW"),
345 (Uzs, b"UZS", "UZS"),
346 (Ved, b"VED", "Bs.D"),
347 (Ves, b"VES", "Bs.S"),
348 (Vnd, b"VND", "₫"),
349 (Vuv, b"VUV", "Vt"),
350 (Wst, b"WST", "$"),
351 (Xaf, b"XAF", "F.CFA"),
352 (Xag, b"XAG", "XAG"),
353 (Xau, b"XAU", "XAU"),
354 (Xba, b"XBA", "XBA"),
355 (Xbb, b"XBB", "XBB"),
356 (Xbc, b"XBC", "XBC"),
357 (Xbd, b"XBD", "XBD"),
358 (Xcd, b"XCD", "$"),
359 (Xdr, b"XDR", "SDR"),
360 (Xof, b"XOF", "F.CFA"),
361 (Xpd, b"XPD", "XPD"),
362 (Xpf, b"XPF", "F"),
363 (Xpt, b"XPT", "XPT"),
364 (Xsu, b"XSU", "sucre"),
365 (Xts, b"XTS", "XTS"),
366 (Xua, b"XUA", "XUA"),
367 (Xxx, b"XXX", "XXX"),
368 (Yer, b"YER", "﷼"),
369 (Zar, b"ZAR", "R"),
370 (Zmw, b"ZMW", "K"),
371 (Zwl, b"ZWL", "$")
372];