1use crate::error::{DecodeError, EncodeError, InvalidFieldTag};
16use bytes::Bytes;
17use rust_decimal::Decimal;
18use serde::{Deserialize, Serialize};
19use std::fmt;
20use std::str::FromStr;
21
22pub const USER_DEFINED_TAG_MIN: u32 = 5000;
27
28pub const USER_DEFINED_TAG_MAX: u32 = 9999;
33
34pub const USER_DEFINED_EXT_TAG_MIN: u32 = 20000;
40
41pub const USER_DEFINED_EXT_TAG_MAX: u32 = 39999;
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
59#[repr(transparent)]
60#[serde(transparent)]
61pub struct FieldTag(u32);
62
63impl FieldTag {
64 #[inline]
71 #[must_use]
72 pub const fn new(tag: u32) -> Self {
73 Self(tag)
74 }
75
76 #[inline]
81 pub const fn try_new(tag: u32) -> Result<Self, InvalidFieldTag> {
82 if tag == 0 {
83 return Err(InvalidFieldTag::new(tag));
84 }
85 Ok(Self(tag))
86 }
87
88 #[inline]
90 #[must_use]
91 pub const fn value(self) -> u32 {
92 self.0
93 }
94
95 #[inline]
97 #[must_use]
98 pub const fn is_valid(self) -> bool {
99 self.0 >= 1
100 }
101
102 #[inline]
108 #[must_use]
109 pub const fn is_standard(self) -> bool {
110 self.0 >= 1 && self.0 < USER_DEFINED_TAG_MIN
111 }
112
113 #[inline]
121 #[must_use]
122 pub const fn is_user_defined(self) -> bool {
123 (self.0 >= USER_DEFINED_TAG_MIN && self.0 <= USER_DEFINED_TAG_MAX)
124 || (self.0 >= USER_DEFINED_EXT_TAG_MIN && self.0 <= USER_DEFINED_EXT_TAG_MAX)
125 }
126}
127
128impl From<u32> for FieldTag {
129 fn from(tag: u32) -> Self {
130 Self(tag)
131 }
132}
133
134impl From<FieldTag> for u32 {
135 fn from(tag: FieldTag) -> Self {
136 tag.0
137 }
138}
139
140impl fmt::Display for FieldTag {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 write!(f, "{}", self.0)
143 }
144}
145
146#[derive(Debug, Clone, Copy)]
151pub struct FieldRef<'a> {
152 pub tag: u32,
154 pub value: &'a [u8],
156}
157
158impl<'a> FieldRef<'a> {
159 #[inline]
165 #[must_use]
166 pub const fn new(tag: u32, value: &'a [u8]) -> Self {
167 Self { tag, value }
168 }
169
170 #[inline]
172 #[must_use]
173 pub const fn tag(&self) -> FieldTag {
174 FieldTag(self.tag)
175 }
176
177 pub fn as_str(&self) -> Result<&'a str, DecodeError> {
182 std::str::from_utf8(self.value).map_err(DecodeError::from)
183 }
184
185 pub fn to_string(&self) -> Result<String, DecodeError> {
190 self.as_str().map(String::from)
191 }
192
193 pub fn parse<T: FromStr>(&self) -> Result<T, DecodeError> {
198 let s = self.as_str()?;
199 s.parse().map_err(|_| DecodeError::InvalidFieldValue {
200 tag: self.tag,
201 reason: format!("failed to parse '{}' as {}", s, std::any::type_name::<T>()),
202 })
203 }
204
205 pub fn as_u64(&self) -> Result<u64, DecodeError> {
210 self.parse()
211 }
212
213 pub fn as_i64(&self) -> Result<i64, DecodeError> {
218 self.parse()
219 }
220
221 pub fn as_decimal(&self) -> Result<Decimal, DecodeError> {
226 self.parse()
227 }
228
229 pub fn as_bool(&self) -> Result<bool, DecodeError> {
234 match self.value {
235 b"Y" => Ok(true),
236 b"N" => Ok(false),
237 _ => Err(DecodeError::InvalidFieldValue {
238 tag: self.tag,
239 reason: "expected 'Y' or 'N'".to_string(),
240 }),
241 }
242 }
243
244 pub fn as_char(&self) -> Result<char, DecodeError> {
249 if self.value.len() == 1 && self.value[0].is_ascii() {
250 Ok(self.value[0] as char)
251 } else {
252 Err(DecodeError::InvalidFieldValue {
253 tag: self.tag,
254 reason: "expected single ASCII character".to_string(),
255 })
256 }
257 }
258
259 #[inline]
261 #[must_use]
262 pub const fn as_bytes(&self) -> &'a [u8] {
263 self.value
264 }
265
266 #[inline]
268 #[must_use]
269 pub const fn len(&self) -> usize {
270 self.value.len()
271 }
272
273 #[inline]
275 #[must_use]
276 pub const fn is_empty(&self) -> bool {
277 self.value.is_empty()
278 }
279}
280
281#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
283pub enum FieldValue {
284 String(String),
286 Int(i64),
288 UInt(u64),
290 Decimal(Decimal),
292 Bool(bool),
294 Char(char),
296 Data(Bytes),
298}
299
300impl FieldValue {
301 #[must_use]
303 pub fn as_str(&self) -> Option<&str> {
304 match self {
305 Self::String(s) => Some(s),
306 _ => None,
307 }
308 }
309
310 #[must_use]
312 pub const fn as_i64(&self) -> Option<i64> {
313 match self {
314 Self::Int(v) => Some(*v),
315 _ => None,
316 }
317 }
318
319 #[must_use]
321 pub const fn as_u64(&self) -> Option<u64> {
322 match self {
323 Self::UInt(v) => Some(*v),
324 _ => None,
325 }
326 }
327
328 #[must_use]
330 pub const fn as_decimal(&self) -> Option<Decimal> {
331 match self {
332 Self::Decimal(v) => Some(*v),
333 _ => None,
334 }
335 }
336
337 #[must_use]
339 pub const fn as_bool(&self) -> Option<bool> {
340 match self {
341 Self::Bool(v) => Some(*v),
342 _ => None,
343 }
344 }
345
346 #[must_use]
348 pub const fn as_char(&self) -> Option<char> {
349 match self {
350 Self::Char(v) => Some(*v),
351 _ => None,
352 }
353 }
354}
355
356impl fmt::Display for FieldValue {
357 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
358 match self {
359 Self::String(s) => write!(f, "{}", s),
360 Self::Int(v) => write!(f, "{}", v),
361 Self::UInt(v) => write!(f, "{}", v),
362 Self::Decimal(v) => write!(f, "{}", v),
363 Self::Bool(v) => write!(f, "{}", if *v { "Y" } else { "N" }),
364 Self::Char(c) => write!(f, "{}", c),
365 Self::Data(d) => write!(f, "<{} bytes>", d.len()),
366 }
367 }
368}
369
370pub trait FixField: Sized {
375 const TAG: u32;
377
378 type Value;
380
381 fn decode(bytes: &[u8]) -> Result<Self::Value, DecodeError>;
389
390 fn encode(value: &Self::Value, buf: &mut Vec<u8>) -> Result<(), EncodeError>;
404}
405
406#[cfg(test)]
407mod tests {
408 use super::*;
409
410 #[track_caller]
412 fn ok<T, E: fmt::Debug>(result: Result<T, E>, what: &str) -> T {
413 match result {
414 Ok(value) => value,
415 Err(err) => panic!("{what}: {err:?}"),
416 }
417 }
418
419 #[track_caller]
421 fn assert_invalid_field_value<T: fmt::Debug>(result: Result<T, DecodeError>, tag: u32) {
422 match result {
423 Err(DecodeError::InvalidFieldValue { tag: actual, .. }) => assert_eq!(actual, tag),
424 other => panic!("expected InvalidFieldValue for tag {tag}, got {other:?}"),
425 }
426 }
427
428 #[test]
429 fn test_field_tag_standard_range() {
430 let tag = FieldTag::new(35);
431 assert_eq!(tag.value(), 35);
432 assert!(tag.is_valid());
433 assert!(tag.is_standard());
434 assert!(!tag.is_user_defined());
435 }
436
437 #[test]
438 fn test_field_tag_zero_is_neither_standard_nor_user_defined() {
439 let zero = FieldTag::new(0);
440 assert!(!zero.is_valid());
441 assert!(!zero.is_standard());
442 assert!(!zero.is_user_defined());
443 assert_eq!(FieldTag::try_new(0), Err(InvalidFieldTag::new(0)));
444 }
445
446 #[test]
447 fn test_field_tag_boundary_4999_is_standard() {
448 let tag = ok(FieldTag::try_new(4999), "4999 is a legal tag");
449 assert!(tag.is_standard());
450 assert!(!tag.is_user_defined());
451 }
452
453 #[test]
454 fn test_field_tag_boundary_5000_is_user_defined() {
455 let tag = ok(FieldTag::try_new(5000), "5000 is a legal tag");
456 assert!(!tag.is_standard());
457 assert!(tag.is_user_defined());
458 assert_eq!(tag.value(), USER_DEFINED_TAG_MIN);
459 }
460
461 #[test]
462 fn test_field_tag_boundary_9999_is_user_defined() {
463 let tag = ok(FieldTag::try_new(9999), "9999 is a legal tag");
464 assert!(!tag.is_standard());
465 assert!(tag.is_user_defined());
466 assert_eq!(tag.value(), USER_DEFINED_TAG_MAX);
467 }
468
469 #[test]
470 fn test_field_tag_boundary_10000_is_not_user_defined() {
471 let tag = ok(FieldTag::try_new(10000), "10000 is a legal tag");
473 assert!(!tag.is_user_defined());
474 assert!(!tag.is_standard());
475 }
476
477 #[test]
478 fn test_field_tag_extended_user_defined_range_boundaries() {
479 let cases = [
483 (19999u32, false),
484 (20000, true),
485 (USER_DEFINED_EXT_TAG_MIN, true),
486 (30000, true),
487 (39999, true),
488 (USER_DEFINED_EXT_TAG_MAX, true),
489 (40000, false),
490 ];
491 for (tag_num, expected) in cases {
492 let tag = ok(FieldTag::try_new(tag_num), "high tag is legal");
493 assert_eq!(
494 tag.is_user_defined(),
495 expected,
496 "is_user_defined({tag_num}) should be {expected}"
497 );
498 assert!(!tag.is_standard(), "{tag_num} is not a standard low tag");
499 }
500 }
501
502 #[test]
503 fn test_field_tag_reserved_high_range_is_not_user_defined() {
504 for tag_num in [40000, 40001, 49999, 50000] {
507 let tag = ok(FieldTag::try_new(tag_num), "reserved high tag is legal");
508 assert!(
509 !tag.is_user_defined(),
510 "reserved tag {tag_num} must not be user-defined"
511 );
512 }
513 }
514
515 #[test]
516 fn test_field_ref_as_str() {
517 let field = FieldRef::new(11, b"ORDER123");
518 assert_eq!(field.as_str(), Ok("ORDER123"));
519 }
520
521 #[test]
522 fn test_field_ref_as_u64() {
523 let field = FieldRef::new(34, b"12345");
524 assert_eq!(field.as_u64(), Ok(12345));
525 }
526
527 #[test]
528 fn test_field_ref_as_u64_non_numeric_is_typed_error() {
529 assert_invalid_field_value(FieldRef::new(34, b"abc").as_u64(), 34);
530 }
531
532 #[test]
533 fn test_field_ref_as_u64_negative_is_typed_error() {
534 assert_invalid_field_value(FieldRef::new(34, b"-1").as_u64(), 34);
535 }
536
537 #[test]
538 fn test_field_ref_as_u64_empty_is_typed_error() {
539 assert_invalid_field_value(FieldRef::new(34, b"").as_u64(), 34);
540 }
541
542 #[test]
543 fn test_field_ref_as_i64_accepts_negative() {
544 assert_eq!(FieldRef::new(14, b"-42").as_i64(), Ok(-42));
545 }
546
547 #[test]
548 fn test_field_ref_as_decimal_parses_price() {
549 let field = FieldRef::new(44, b"123.45");
550 let price = ok(field.as_decimal(), "123.45 is a valid price");
551 assert_eq!(price, Decimal::new(12345, 2));
552 }
553
554 #[test]
555 fn test_field_ref_as_decimal_preserves_scale() {
556 let field = FieldRef::new(44, b"1.500");
558 let price = ok(field.as_decimal(), "1.500 is a valid price");
559 assert_eq!(price.to_string(), "1.500");
560 }
561
562 #[test]
563 fn test_field_ref_as_decimal_negative_price() {
564 let field = FieldRef::new(44, b"-0.01");
565 let price = ok(field.as_decimal(), "-0.01 is a valid price");
566 assert_eq!(price, Decimal::new(-1, 2));
567 }
568
569 #[test]
570 fn test_field_ref_as_decimal_two_points_is_typed_error() {
571 assert_invalid_field_value(FieldRef::new(44, b"1.2.3").as_decimal(), 44);
572 }
573
574 #[test]
575 fn test_field_ref_as_decimal_garbage_is_typed_error() {
576 assert_invalid_field_value(FieldRef::new(44, b"abc").as_decimal(), 44);
577 assert_invalid_field_value(FieldRef::new(44, b"").as_decimal(), 44);
578 assert_invalid_field_value(FieldRef::new(44, b"1,50").as_decimal(), 44);
579 }
580
581 #[test]
582 fn test_field_ref_as_decimal_invalid_utf8_is_typed_error() {
583 let field = FieldRef::new(44, &[0xFF, 0xFE]);
584 assert!(matches!(
585 field.as_decimal(),
586 Err(DecodeError::InvalidUtf8(_))
587 ));
588 }
589
590 #[test]
591 fn test_field_ref_as_bool() {
592 assert_eq!(FieldRef::new(141, b"Y").as_bool(), Ok(true));
593 assert_eq!(FieldRef::new(141, b"N").as_bool(), Ok(false));
594 }
595
596 #[test]
597 fn test_field_ref_as_bool_rejects_lowercase_and_words() {
598 assert_invalid_field_value(FieldRef::new(141, b"y").as_bool(), 141);
599 assert_invalid_field_value(FieldRef::new(141, b"n").as_bool(), 141);
600 assert_invalid_field_value(FieldRef::new(141, b"YES").as_bool(), 141);
601 assert_invalid_field_value(FieldRef::new(141, b"").as_bool(), 141);
602 }
603
604 #[test]
605 fn test_field_ref_as_char() {
606 assert_eq!(FieldRef::new(54, b"1").as_char(), Ok('1'));
607 }
608
609 #[test]
610 fn test_field_ref_as_char_rejects_multi_byte_and_non_ascii() {
611 assert_invalid_field_value(FieldRef::new(54, b"12").as_char(), 54);
612 assert_invalid_field_value(FieldRef::new(54, b"").as_char(), 54);
613 assert_invalid_field_value(FieldRef::new(54, "ñ".as_bytes()).as_char(), 54);
615 assert_invalid_field_value(FieldRef::new(54, &[0x80]).as_char(), 54);
616 }
617
618 #[test]
619 fn test_field_ref_invalid_utf8() {
620 let field = FieldRef::new(1, &[0xFF, 0xFE]);
621 assert!(matches!(field.as_str(), Err(DecodeError::InvalidUtf8(_))));
622 }
623
624 #[test]
625 fn test_field_value_display() {
626 assert_eq!(FieldValue::String("test".to_string()).to_string(), "test");
627 assert_eq!(FieldValue::Int(42).to_string(), "42");
628 assert_eq!(FieldValue::Bool(true).to_string(), "Y");
629 assert_eq!(FieldValue::Bool(false).to_string(), "N");
630 }
631}