1use rust_decimal::prelude::ToPrimitive;
2use rust_decimal::Decimal;
3use thiserror::Error;
4
5#[derive(Error, Debug)]
6pub enum OverpunchError {
7 #[error("cannot extract from an empty field")]
8 EmptyField,
9
10 #[error("failed to parse result as decimal: {0}")]
11 ParseError(String),
12
13 #[error("failed with overflow while serializing value: {0}")]
14 OverflowError(String),
15}
16
17pub fn convert_to_signed_format(value: Decimal, field_format: &str) -> Option<String> {
34 let number_of_decimal_places = if let Some(pos) = field_format.find('v') {
35 field_format[pos + 1..].len()
36 } else {
37 0
38 };
39
40 format(value, number_of_decimal_places).ok()
41}
42
43pub fn convert_from_signed_format(value: &str, field_format: &str) -> Option<Decimal> {
60 let number_of_decimal_places = if let Some(pos) = field_format.find('v') {
61 field_format[pos + 1..].len()
62 } else {
63 0
64 };
65
66 extract(value, number_of_decimal_places).ok()
67}
68
69pub fn extract(raw: &str, decimals: usize) -> Result<Decimal, OverpunchError> {
86 let length = raw.len();
87 if length == 0 {
88 return Err(OverpunchError::EmptyField);
89 }
90
91 let mut val: i64 = 0;
92
93 let mut sign: i64 = 1;
94
95 for c in raw.chars() {
96 let char_val: i64 = match c {
97 '0' => 0,
98 '1' => 1,
99 '2' => 2,
100 '3' => 3,
101 '4' => 4,
102 '5' => 5,
103 '6' => 6,
104 '7' => 7,
105 '8' => 8,
106 '9' => 9,
107 '{' => 0,
108 'A' => 1,
109 'B' => 2,
110 'C' => 3,
111 'D' => 4,
112 'E' => 5,
113 'F' => 6,
114 'G' => 7,
115 'H' => 8,
116 'I' => 9,
117 '}' => 0,
118 'J' => 1,
119 'K' => 2,
120 'L' => 3,
121 'M' => 4,
122 'N' => 5,
123 'O' => 6,
124 'P' => 7,
125 'Q' => 8,
126 'R' => 9,
127 _ => return Err(OverpunchError::ParseError(raw.to_string())),
128 };
129
130 sign = match c {
131 '}' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' | 'Q' | 'R' => -1,
132 _ => 1,
133 };
134
135 val = val * 10 + char_val;
136 }
137
138 let extracted = if sign == -1 {
139 -Decimal::new(val, decimals as u32)
140 } else {
141 Decimal::new(val, decimals as u32)
142 };
143
144 Ok(extracted)
145}
146
147pub fn format(value: Decimal, decimals: usize) -> Result<String, OverpunchError> {
165 let is_negative: bool = value.is_sign_negative();
166
167 let scale_factor: Decimal = Decimal::new(10_i64.pow(decimals.try_into().unwrap()), 0);
168
169 let mut working_value = value.abs();
170 working_value.rescale(decimals.try_into().unwrap());
171
172 let mut as_int: i64 = match (working_value * scale_factor).to_i64() {
173 Some(valid_i64) => valid_i64,
174 None => return Err(OverpunchError::OverflowError(value.to_string())),
175 };
176
177 let mut v: Vec<char> = Vec::with_capacity(10);
178
179 let mut last_digit = as_int % 10;
180 as_int /= 10;
181
182 let mut c = match (is_negative, last_digit) {
183 (false, 0) => '{',
184 (false, 1) => 'A',
185 (false, 2) => 'B',
186 (false, 3) => 'C',
187 (false, 4) => 'D',
188 (false, 5) => 'E',
189 (false, 6) => 'F',
190 (false, 7) => 'G',
191 (false, 8) => 'H',
192 (false, 9) => 'I',
193 (true, 0) => '}',
194 (true, 1) => 'J',
195 (true, 2) => 'K',
196 (true, 3) => 'L',
197 (true, 4) => 'M',
198 (true, 5) => 'N',
199 (true, 6) => 'O',
200 (true, 7) => 'P',
201 (true, 8) => 'Q',
202 (true, 9) => 'R',
203 _ => unreachable!(),
204 };
205
206 v.push(c);
207
208 while as_int > 0 {
209 last_digit = as_int % 10;
210 as_int /= 10;
211
212 c = match last_digit {
213 0 => '0',
214 1 => '1',
215 2 => '2',
216 3 => '3',
217 4 => '4',
218 5 => '5',
219 6 => '6',
220 7 => '7',
221 8 => '8',
222 9 => '9',
223 _ => unreachable!(),
224 };
225
226 v.push(c);
227 }
228
229 while v.len() < decimals + 1 {
230 v.push('0');
231 }
232
233 v.reverse();
234
235 Ok(String::from_iter(v))
236}