1#[cfg(test)]
4pub(crate) mod test;
5
6#[cfg(test)]
7mod test_from_schema;
8
9mod data;
10
11use std::fmt;
12
13#[doc(inline)]
14pub use data::Code;
15
16use crate::{
17 from_warning_all, json,
18 schema::{self, HasElement as _},
19 warning::{self, GatherWarnings as _},
20 FromSchema, IntoCaveat as _, Verdict,
21};
22
23#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
25pub enum Warning {
26 ContainsEscapeCodes,
28
29 Decode(json::decode::Warning),
31
32 PreferUpperCase,
34
35 InvalidCode,
37
38 InvalidLength,
40
41 InvalidCodeXTS,
43
44 InvalidCodeXXX,
46}
47
48impl fmt::Display for Warning {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 match self {
51 Self::ContainsEscapeCodes => write!(
52 f,
53 "The currency-code contains escape-codes but it does not need them.",
54 ),
55 Self::Decode(warning) => fmt::Display::fmt(warning, f),
56 Self::PreferUpperCase => write!(
57 f,
58 "The currency-code follows the ISO 4217 standard which states: the chars should be uppercase.",
59 ),
60 Self::InvalidCode => {
61 write!(f, "The currency-code is not a valid ISO 4217 code.")
62 }
63 Self::InvalidLength => write!(f, "The currency-code follows the ISO 4217 standard which states: the code should be three chars."),
64 Self::InvalidCodeXTS => write!(
65 f,
66 "The currency-code is `XTS`. This is a code for testing only",
67 ),
68 Self::InvalidCodeXXX => write!(
69 f,
70 "The currency-code is `XXX`. This means there is no currency",
71 ),
72 }
73 }
74}
75
76impl crate::Warning for Warning {
77 fn id(&self) -> warning::Id {
78 match self {
79 Self::ContainsEscapeCodes => warning::Id::from_static("contains_escape_codes"),
80 Self::Decode(kind) => kind.id(),
81 Self::PreferUpperCase => warning::Id::from_static("prefer_upper_case"),
82 Self::InvalidCode => warning::Id::from_static("invalid_code"),
83 Self::InvalidLength => warning::Id::from_static("invalid_length"),
84 Self::InvalidCodeXTS => warning::Id::from_static("invalid_code_xts"),
85 Self::InvalidCodeXXX => warning::Id::from_static("invalid_code_xxx"),
86 }
87 }
88}
89
90from_warning_all!(json::decode::Warning => Warning::Decode);
91
92impl<'buf> FromSchema<'buf, schema::Str<'buf>> for Code {
93 type Warning = Warning;
94
95 fn from_schema(source: &schema::Str<'buf>) -> Verdict<Self, Self::Warning> {
96 let mut warnings = warning::Set::new();
97 let elem = source.element();
98
99 let pending_str = source
102 .value()
103 .has_escapes(elem)
104 .gather_warnings_into(&mut warnings);
105
106 let s = match pending_str {
107 json::PendingStr::NoEscapes(s) => s,
108 json::PendingStr::HasEscapes(_) => {
109 return warnings.bail(elem, Warning::ContainsEscapeCodes);
110 }
111 };
112
113 let bytes = s.as_bytes();
114
115 let [a, b, c] = bytes else {
117 return warnings.bail(elem, Warning::InvalidLength);
118 };
119
120 let triplet: [u8; 3] = [
121 a.to_ascii_uppercase(),
122 b.to_ascii_uppercase(),
123 c.to_ascii_uppercase(),
124 ];
125
126 if triplet != bytes {
127 warnings.insert(elem, Warning::PreferUpperCase);
128 }
129
130 let Some(code) = Code::from_alpha_3(triplet) else {
131 return warnings.bail(elem, Warning::InvalidCode);
132 };
133
134 if matches!(code, Code::Xts) {
135 warnings.insert(elem, Warning::InvalidCodeXTS);
136 } else if matches!(code, Code::Xxx) {
137 warnings.insert(elem, Warning::InvalidCodeXXX);
138 }
139
140 Ok(code.into_caveat(warnings))
141 }
142}
143
144impl fmt::Display for Code {
145 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146 f.write_str(self.into_str())
147 }
148}
149
150macro_rules! currency_codes {
152 [$(($name:ident, $alpha3:literal, $symbol:literal)),*] => {
153 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
155 pub enum Code {
156 $($name),*
157 }
158
159 impl Code {
160 pub(super) const fn from_alpha_3(code: [u8; 3]) -> Option<Self> {
162 match &code {
163 $($alpha3 => Some(Self::$name),)*
164 _ => None
165 }
166 }
167
168 pub fn into_str(self) -> &'static str {
170 let bytes = match self {
171 $(Self::$name => $alpha3),*
172 };
173 std::str::from_utf8(bytes).expect("The currency code bytes are known to be valid UTF8 as they are embedded into the binary")
174 }
175
176 pub fn into_symbol(self) -> &'static str {
178 match self {
179 $(Self::$name => $symbol),*
180 }
181 }
182 }
183 };
184}
185
186pub(crate) use currency_codes;