1use rust_decimal::Decimal;
4use time::OffsetDateTime;
5
6use crate::error::FieldError;
7
8#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct Field {
14 tag: u32,
15 value: Vec<u8>,
16}
17
18impl Field {
19 pub fn new(tag: u32, value: impl Into<Vec<u8>>) -> Self {
21 Self {
22 tag,
23 value: value.into(),
24 }
25 }
26
27 pub fn string(tag: u32, value: &str) -> Self {
29 Self::new(tag, value.as_bytes().to_vec())
30 }
31
32 pub fn int(tag: u32, value: i64) -> Self {
34 Self::new(tag, value.to_string().into_bytes())
35 }
36
37 pub fn utc_timestamp(tag: u32, value: OffsetDateTime) -> Self {
42 Self::string(tag, &format_utc_timestamp_millis(value))
43 }
44
45 pub fn bytes(tag: u32, value: &[u8]) -> Self {
50 Self::new(tag, value.to_vec())
51 }
52
53 pub fn as_bytes(&self) -> &[u8] {
56 &self.value
57 }
58
59 pub fn utc_date_only(tag: u32, value: time::Date) -> Self {
61 Self::string(tag, &format_utc_date_only(value))
62 }
63
64 pub fn as_utc_date_only(&self) -> Result<time::Date, FieldError> {
66 let s = self.as_str()?;
67 parse_utc_date_only(s).ok_or_else(|| FieldError::NotDateOnly {
68 tag: self.tag,
69 value: s.to_owned(),
70 })
71 }
72
73 pub fn utc_time_only(tag: u32, value: time::Time) -> Self {
76 Self::string(tag, &format_utc_time_only_millis(value))
77 }
78
79 pub fn as_utc_time_only(&self) -> Result<time::Time, FieldError> {
83 let s = self.as_str()?;
84 parse_utc_time_only(s).ok_or_else(|| FieldError::NotTimeOnly {
85 tag: self.tag,
86 value: s.to_owned(),
87 })
88 }
89
90 pub fn tag(&self) -> u32 {
92 self.tag
93 }
94
95 pub fn value_bytes(&self) -> &[u8] {
97 &self.value
98 }
99
100 pub fn as_str(&self) -> Result<&str, FieldError> {
102 core::str::from_utf8(&self.value).map_err(|_| FieldError::NotUtf8 { tag: self.tag })
103 }
104
105 pub fn as_int(&self) -> Result<i64, FieldError> {
112 let s = self.as_str()?;
113 s.parse().map_err(|_| FieldError::NotInt {
114 tag: self.tag,
115 value: s.to_owned(),
116 })
117 }
118
119 pub fn as_decimal(&self) -> Result<Decimal, FieldError> {
124 let s = self.as_str()?;
125 s.parse().map_err(|_| FieldError::NotDecimal {
126 tag: self.tag,
127 value: s.to_owned(),
128 })
129 }
130
131 pub fn as_char(&self) -> Result<char, FieldError> {
133 let s = self.as_str()?;
134 let mut chars = s.chars();
135 match (chars.next(), chars.next()) {
136 (Some(c), None) => Ok(c),
137 _ => Err(FieldError::NotChar {
138 tag: self.tag,
139 value: s.to_owned(),
140 }),
141 }
142 }
143
144 pub fn as_bool(&self) -> Result<bool, FieldError> {
146 match self.value.as_slice() {
147 b"Y" => Ok(true),
148 b"N" => Ok(false),
149 _ => Err(FieldError::NotBool {
154 tag: self.tag,
155 value: match core::str::from_utf8(&self.value) {
156 Ok(s) => s.to_owned(),
157 Err(_) => format!("0x{}", hex_encode(&self.value)),
158 },
159 }),
160 }
161 }
162
163 pub fn as_utc_timestamp(&self) -> Result<OffsetDateTime, FieldError> {
167 let s = self.as_str()?;
168 parse_utc_timestamp(s).ok_or_else(|| FieldError::NotTimestamp {
169 tag: self.tag,
170 value: s.to_owned(),
171 })
172 }
173}
174
175fn hex_encode(bytes: &[u8]) -> String {
179 let mut s = String::with_capacity(bytes.len() * 2);
180 for b in bytes {
181 s.push_str(&format!("{b:02x}"));
182 }
183 s
184}
185
186fn format_utc_timestamp_millis(dt: OffsetDateTime) -> String {
188 format!(
189 "{:04}{:02}{:02}-{:02}:{:02}:{:02}.{:03}",
190 dt.year(),
191 u8::from(dt.month()),
192 dt.day(),
193 dt.hour(),
194 dt.minute(),
195 dt.second(),
196 dt.millisecond()
197 )
198}
199
200fn parse_utc_timestamp(s: &str) -> Option<OffsetDateTime> {
202 let (date, time_part) = s.split_once('-')?;
203 if date.len() != 8 {
204 return None;
205 }
206 let year: i32 = date.get(0..4)?.parse().ok()?;
207 let month: u8 = date.get(4..6)?.parse().ok()?;
208 let day: u8 = date.get(6..8)?.parse().ok()?;
209
210 let (hms, frac) = match time_part.split_once('.') {
211 Some((h, f)) => (h, Some(f)),
212 None => (time_part, None),
213 };
214 let mut parts = hms.split(':');
215 let hour: u8 = parts.next()?.parse().ok()?;
216 let min: u8 = parts.next()?.parse().ok()?;
217 let sec: u8 = parts.next()?.parse().ok()?;
218 if parts.next().is_some() {
219 return None;
220 }
221
222 let nanos: u32 = match frac {
227 None => 0,
228 Some(f) if matches!(f.len(), 3 | 6 | 9) && f.bytes().all(|b| b.is_ascii_digit()) => {
229 let mut buf = [b'0'; 9];
230 for (slot, digit) in buf.iter_mut().zip(f.bytes()) {
231 *slot = digit;
232 }
233 core::str::from_utf8(&buf).ok()?.parse().ok()?
234 }
235 Some(_) => return None,
236 };
237
238 let month = time::Month::try_from(month).ok()?;
239 let date = time::Date::from_calendar_date(year, month, day).ok()?;
240 let (sec, nanos) = if sec == 60 {
244 (59, 999_999_999)
245 } else {
246 (sec, nanos)
247 };
248 let time = time::Time::from_hms_nano(hour, min, sec, nanos).ok()?;
249 Some(time::PrimitiveDateTime::new(date, time).assume_utc())
250}
251
252fn format_utc_date_only(date: time::Date) -> String {
254 format!(
255 "{:04}{:02}{:02}",
256 date.year(),
257 u8::from(date.month()),
258 date.day()
259 )
260}
261
262fn parse_utc_date_only(s: &str) -> Option<time::Date> {
264 if s.len() != 8 || !s.bytes().all(|b| b.is_ascii_digit()) {
265 return None;
266 }
267 let year: i32 = s.get(0..4)?.parse().ok()?;
268 let month: u8 = s.get(4..6)?.parse().ok()?;
269 let day: u8 = s.get(6..8)?.parse().ok()?;
270 let month = time::Month::try_from(month).ok()?;
271 time::Date::from_calendar_date(year, month, day).ok()
272}
273
274fn format_utc_time_only_millis(t: time::Time) -> String {
276 format!(
277 "{:02}:{:02}:{:02}.{:03}",
278 t.hour(),
279 t.minute(),
280 t.second(),
281 t.millisecond()
282 )
283}
284
285fn parse_utc_time_only(s: &str) -> Option<time::Time> {
289 let (hms, frac) = match s.split_once('.') {
290 Some((h, f)) => (h, Some(f)),
291 None => (s, None),
292 };
293 let mut parts = hms.split(':');
294 let hour: u8 = parts.next()?.parse().ok()?;
295 let min: u8 = parts.next()?.parse().ok()?;
296 let sec: u8 = parts.next()?.parse().ok()?;
297 if parts.next().is_some() {
298 return None;
299 }
300
301 let nanos: u32 = match frac {
302 None => 0,
303 Some(f) if matches!(f.len(), 3 | 6 | 9) && f.bytes().all(|b| b.is_ascii_digit()) => {
304 let mut buf = [b'0'; 9];
305 for (slot, digit) in buf.iter_mut().zip(f.bytes()) {
306 *slot = digit;
307 }
308 core::str::from_utf8(&buf).ok()?.parse().ok()?
309 }
310 Some(_) => return None,
311 };
312
313 let (sec, nanos) = if sec == 60 {
314 (59, 999_999_999)
315 } else {
316 (sec, nanos)
317 };
318 time::Time::from_hms_nano(hour, min, sec, nanos).ok()
319}