1#![allow(clippy::missing_inline_in_public_items)]
4
5use serde::{Deserialize, Serialize};
6use std::{fmt, str::FromStr};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10pub enum Codepage {
11 ASCII,
13 CP037,
15 CP273,
17 CP500,
19 CP1047,
21 CP1140,
23}
24
25#[derive(Debug, Clone, Copy)]
26struct CodepageMetadata {
27 display_name: &'static str,
28 description: &'static str,
29 code_page_number: Option<u16>,
30}
31
32impl CodepageMetadata {
33 const ASCII: Self = Self {
34 display_name: "ascii",
35 description: "ASCII encoding",
36 code_page_number: None,
37 };
38
39 const fn ebcdic(
40 display_name: &'static str,
41 description: &'static str,
42 code_page_number: u16,
43 ) -> Self {
44 Self {
45 display_name,
46 description,
47 code_page_number: Some(code_page_number),
48 }
49 }
50}
51
52impl Codepage {
53 const METADATA: [(Self, CodepageMetadata); 6] = [
54 (Self::ASCII, CodepageMetadata::ASCII),
55 (
56 Self::CP037,
57 CodepageMetadata::ebcdic("cp037", "EBCDIC Code Page 037 (US/Canada)", 37),
58 ),
59 (
60 Self::CP273,
61 CodepageMetadata::ebcdic("cp273", "EBCDIC Code Page 273 (Germany/Austria)", 273),
62 ),
63 (
64 Self::CP500,
65 CodepageMetadata::ebcdic("cp500", "EBCDIC Code Page 500 (International)", 500),
66 ),
67 (
68 Self::CP1047,
69 CodepageMetadata::ebcdic("cp1047", "EBCDIC Code Page 1047 (Open Systems)", 1047),
70 ),
71 (
72 Self::CP1140,
73 CodepageMetadata::ebcdic(
74 "cp1140",
75 "EBCDIC Code Page 1140 (US/Canada with Euro)",
76 1140,
77 ),
78 ),
79 ];
80
81 #[must_use]
83 pub const fn variants() -> &'static [Self; 6] {
84 &[
85 Self::ASCII,
86 Self::CP037,
87 Self::CP273,
88 Self::CP500,
89 Self::CP1047,
90 Self::CP1140,
91 ]
92 }
93
94 const fn metadata(self) -> CodepageMetadata {
95 match self {
96 Self::ASCII => Self::METADATA[0].1,
97 Self::CP037 => Self::METADATA[1].1,
98 Self::CP273 => Self::METADATA[2].1,
99 Self::CP500 => Self::METADATA[3].1,
100 Self::CP1047 => Self::METADATA[4].1,
101 Self::CP1140 => Self::METADATA[5].1,
102 }
103 }
104
105 #[must_use]
107 pub const fn is_ascii(self) -> bool {
108 self.metadata().code_page_number.is_none()
109 }
110
111 #[must_use]
113 pub const fn is_ebcdic(self) -> bool {
114 !self.is_ascii()
115 }
116
117 #[must_use]
119 pub const fn code_page_number(self) -> Option<u16> {
120 self.metadata().code_page_number
121 }
122
123 #[must_use]
125 pub const fn description(self) -> &'static str {
126 self.metadata().description
127 }
128
129 #[must_use]
131 pub const fn as_str(self) -> &'static str {
132 self.metadata().display_name
133 }
134
135 pub fn parse(input: &str) -> Result<Self, ParseCodepageError> {
142 Self::METADATA
143 .iter()
144 .find_map(|(codepage, metadata)| {
145 metadata
146 .display_name
147 .eq_ignore_ascii_case(input)
148 .then_some(*codepage)
149 })
150 .ok_or_else(|| ParseCodepageError {
151 input: input.to_owned(),
152 })
153 }
154}
155
156impl fmt::Display for Codepage {
157 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
158 f.write_str(self.as_str())
159 }
160}
161
162impl FromStr for Codepage {
163 type Err = ParseCodepageError;
164
165 fn from_str(input: &str) -> Result<Self, Self::Err> {
166 Self::parse(input)
167 }
168}
169
170#[derive(Debug, Clone, PartialEq, Eq)]
172pub struct ParseCodepageError {
173 input: String,
174}
175
176impl fmt::Display for ParseCodepageError {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 write!(f, "unsupported codepage `{}`", self.input)
179 }
180}
181
182impl std::error::Error for ParseCodepageError {}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
186pub enum UnmappablePolicy {
187 Error,
189 Replace,
191 Skip,
193}
194
195impl UnmappablePolicy {
196 #[must_use]
198 pub const fn as_str(self) -> &'static str {
199 match self {
200 Self::Error => "error",
201 Self::Replace => "replace",
202 Self::Skip => "skip",
203 }
204 }
205
206 pub fn parse(input: &str) -> Result<Self, ParseUnmappablePolicyError> {
213 match input.to_ascii_lowercase().as_str() {
214 "error" => Ok(Self::Error),
215 "replace" => Ok(Self::Replace),
216 "skip" => Ok(Self::Skip),
217 _ => Err(ParseUnmappablePolicyError {
218 input: input.to_owned(),
219 }),
220 }
221 }
222}
223
224impl fmt::Display for UnmappablePolicy {
225 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
226 f.write_str(self.as_str())
227 }
228}
229
230impl FromStr for UnmappablePolicy {
231 type Err = ParseUnmappablePolicyError;
232
233 fn from_str(input: &str) -> Result<Self, Self::Err> {
234 Self::parse(input)
235 }
236}
237
238#[derive(Debug, Clone, PartialEq, Eq)]
240pub struct ParseUnmappablePolicyError {
241 input: String,
242}
243
244impl fmt::Display for ParseUnmappablePolicyError {
245 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
246 write!(
247 f,
248 "unsupported unmappable-character policy `{}`",
249 self.input
250 )
251 }
252}
253
254impl std::error::Error for ParseUnmappablePolicyError {}
255
256#[must_use]
258pub const fn space_byte(codepage: Codepage) -> u8 {
259 match codepage {
260 Codepage::ASCII => 0x20,
261 Codepage::CP037
262 | Codepage::CP273
263 | Codepage::CP500
264 | Codepage::CP1047
265 | Codepage::CP1140 => 0x40,
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn parsing_codepage_metadata_and_validation() {
275 assert_eq!(
276 Codepage::CP037.description(),
277 "EBCDIC Code Page 037 (US/Canada)"
278 );
279 assert_eq!(Codepage::parse("CP1140"), Ok(Codepage::CP1140));
280 assert!(Codepage::parse("unknown").is_err());
281 assert_eq!(
282 UnmappablePolicy::parse("REPLACE"),
283 Ok(UnmappablePolicy::Replace)
284 );
285 assert!(UnmappablePolicy::parse("unknown").is_err());
286 assert_eq!("cp037".parse::<Codepage>(), Ok(Codepage::CP037));
287 assert_eq!(
288 "replace".parse::<UnmappablePolicy>(),
289 Ok(UnmappablePolicy::Replace)
290 );
291 assert_eq!(
292 "unknown".parse::<Codepage>(),
293 Err(ParseCodepageError {
294 input: "unknown".to_owned(),
295 })
296 );
297 assert_eq!(
298 "unknown".parse::<UnmappablePolicy>(),
299 Err(ParseUnmappablePolicyError {
300 input: "unknown".to_owned(),
301 })
302 );
303 }
304
305 #[test]
306 fn parsing_codepage_padding_bytes() {
307 assert_eq!(space_byte(Codepage::ASCII), 0x20);
308 assert_eq!(space_byte(Codepage::CP037), 0x40);
309 }
310}