Skip to main content

x12_types/util/
mod.rs

1use nom::bytes::complete::tag;
2use nom::bytes::complete::take_until;
3use nom::bytes::complete::take_while;
4use nom::character::complete::newline;
5use nom::combinator::opt;
6use nom::multi::separated_list0;
7use nom::sequence::delimited;
8use nom::IResult;
9use nom::Parser as _;
10
11pub mod dt;
12pub mod tm;
13
14/// Compare two transmissions by their functional-group payload.
15///
16/// Gated behind `v004010` because it references that version's `Transmission`
17/// type; without this gate `util` would not compile when `v004010` is disabled.
18#[cfg(feature = "v004010")]
19pub fn is_equal_payload<T: PartialEq>(
20    src: &crate::v004010::Transmission<T>,
21    target: &crate::v004010::Transmission<T>,
22) -> bool {
23    let Some(target_first) = target.functional_group.first() else {
24        return src.functional_group.is_empty();
25    };
26    src.functional_group
27        .iter()
28        .all(|item| item.eq(target_first))
29}
30
31pub fn parse_line<'a>(input: &'a str, segment_name: &str) -> IResult<&'a str, Vec<&'a str>> {
32    let tag_name = format!("{segment_name}*");
33    let (rest, vars) = delimited(tag(tag_name.as_str()), take_until("~"), tag("~")).parse(input)?;
34    let (_, vars) = separated_list0(
35        tag("*"),
36        take_while(|x: char| {
37            x != '*' && (x.is_alphanumeric() || x.is_whitespace() || x.is_ascii_punctuation())
38        }),
39    )
40    .parse(vars)?;
41    // look for trailing newline
42    let (rest, _) = opt(newline).parse(rest)?;
43    Ok((rest, vars))
44}
45
46pub trait Parser<I, O, E> {
47    fn parse(str: I) -> IResult<I, O>;
48}
49
50pub fn unborrow_string(input: &&str) -> String {
51    input.to_string()
52}
53
54/// Define an X12 numeric data element (`R` decimal or `N` numeric).
55///
56/// Stores the raw text exactly (so `"007"`, `"5.00"`, `"+5"` all round-trip
57/// byte-for-byte and never lose precision or format) and exposes typed views via
58/// `as_f64()` / `as_i64()`. Named by X12 element number (e.g. `E739`).
59#[macro_export]
60macro_rules! num_element {
61    ($(#[$meta:meta])* $name:ident) => {
62        $(#[$meta])*
63        #[derive(Clone, Default, Debug, PartialEq, Eq)]
64        pub struct $name {
65            raw: ::std::string::String,
66        }
67
68        impl $name {
69            /// The raw element text, exactly as parsed.
70            pub fn raw(&self) -> &str {
71                &self.raw
72            }
73            /// The value as `f64`, parsed on demand (`None` if empty/unparseable).
74            pub fn as_f64(&self) -> ::core::option::Option<f64> {
75                self.raw.parse().ok()
76            }
77            /// The value as `i64`, parsed on demand (`None` if empty/unparseable).
78            pub fn as_i64(&self) -> ::core::option::Option<i64> {
79                self.raw.parse().ok()
80            }
81        }
82
83        impl ::core::fmt::Display for $name {
84            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
85                f.write_str(&self.raw)
86            }
87        }
88
89        impl $crate::util::X12Element for $name {
90            fn from_x12(s: &str) -> Self {
91                Self { raw: s.to_string() }
92            }
93        }
94
95        impl ::serde::Serialize for $name {
96            fn serialize<S: ::serde::Serializer>(&self, ser: S) -> ::core::result::Result<S::Ok, S::Error> {
97                ser.serialize_str(&self.raw)
98            }
99        }
100
101        impl<'de> ::serde::Deserialize<'de> for $name {
102            fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> ::core::result::Result<Self, D::Error> {
103                let raw = <::std::string::String as ::serde::Deserialize>::deserialize(d)?;
104                ::core::result::Result::Ok(Self { raw })
105            }
106        }
107    };
108}
109
110/// Define an X12 time data element (`TM`, format `HHMM`/`HHMMSS`).
111///
112/// Stores the raw text exactly (byte-for-byte round-trip) and exposes a typed
113/// [`chrono::NaiveTime`] view via `time()`. Named by X12 element number (e.g. `E337`).
114#[macro_export]
115macro_rules! time_element {
116    ($(#[$meta:meta])* $name:ident) => {
117        $(#[$meta])*
118        #[derive(Clone, Default, Debug, PartialEq, Eq)]
119        pub struct $name {
120            raw: ::std::string::String,
121        }
122
123        impl $name {
124            /// The raw element text, exactly as parsed.
125            pub fn raw(&self) -> &str {
126                &self.raw
127            }
128            /// The typed time, parsed on demand from `HHMMSS` or `HHMM`.
129            pub fn time(&self) -> ::core::option::Option<::chrono::NaiveTime> {
130                ::chrono::NaiveTime::parse_from_str(&self.raw, "%H%M%S")
131                    .or_else(|_| ::chrono::NaiveTime::parse_from_str(&self.raw, "%H%M"))
132                    .ok()
133            }
134        }
135
136        impl ::core::fmt::Display for $name {
137            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
138                f.write_str(&self.raw)
139            }
140        }
141
142        impl $crate::util::X12Element for $name {
143            fn from_x12(s: &str) -> Self {
144                Self { raw: s.to_string() }
145            }
146        }
147
148        impl ::serde::Serialize for $name {
149            fn serialize<S: ::serde::Serializer>(&self, ser: S) -> ::core::result::Result<S::Ok, S::Error> {
150                ser.serialize_str(&self.raw)
151            }
152        }
153
154        impl<'de> ::serde::Deserialize<'de> for $name {
155            fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> ::core::result::Result<Self, D::Error> {
156                let raw = <::std::string::String as ::serde::Deserialize>::deserialize(d)?;
157                ::core::result::Result::Ok(Self { raw })
158            }
159        }
160    };
161}
162
163/// Conversion from a raw X12 element string into a field type.
164///
165/// Implemented for [`String`] (identity) and for the typed data-element types
166/// generated by [`crate::code_enum`] / [`crate::date_element`]. The `ParseSegment`
167/// derive uses this trait to populate any segment field that is not a plain
168/// `String`/`Option<String>`, so new field types can opt in to typing without the
169/// macro needing to know about them.
170pub trait X12Element {
171    fn from_x12(s: &str) -> Self;
172}
173
174impl X12Element for String {
175    fn from_x12(s: &str) -> Self {
176        s.to_string()
177    }
178}
179
180/// Define an X12 code-list data element as an enum.
181///
182/// Generates the enum (one variant per code plus an `Unknown(String)` catch-all so
183/// unknown/future codes still round-trip), its `Display` (variant -> code string),
184/// `X12Element` parsing (code string -> variant), and serde (as the code string, so
185/// JSON stays a plain string). The variant identifiers are supplied explicitly
186/// because many X12 codes are not valid Rust identifiers (e.g. "9L", "C1").
187///
188/// At full scale (element 737 has 196 codes, 738 ~600, 355 has 844) these
189/// invocations are expected to be machine-generated from the code lists.
190#[macro_export]
191macro_rules! code_enum {
192    (
193        $(#[$meta:meta])*
194        $name:ident {
195            $($(#[$vmeta:meta])* $code:literal => $variant:ident),* $(,)?
196        }
197    ) => {
198        $(#[$meta])*
199        #[derive(Clone, Debug, PartialEq, Eq)]
200        pub enum $name {
201            $($(#[$vmeta])* $variant,)*
202            /// A code not present in the published list; preserved verbatim so the
203            /// element still renders and round-trips byte-for-byte.
204            Unknown(String),
205        }
206
207        impl ::core::fmt::Display for $name {
208            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
209                let s = match self {
210                    $(Self::$variant => $code,)*
211                    Self::Unknown(s) => s.as_str(),
212                };
213                f.write_str(s)
214            }
215        }
216
217        impl $crate::util::X12Element for $name {
218            fn from_x12(s: &str) -> Self {
219                match s {
220                    $($code => Self::$variant,)*
221                    other => Self::Unknown(other.to_string()),
222                }
223            }
224        }
225
226        // Lets the enum sit in a mandatory (non-`Option`) field whose segment derives
227        // `Default`. A real value is always supplied by the parser; the default is the
228        // empty-string `Unknown`, which renders as nothing.
229        impl ::core::default::Default for $name {
230            fn default() -> Self {
231                Self::Unknown(::std::string::String::new())
232            }
233        }
234
235        impl ::serde::Serialize for $name {
236            fn serialize<S: ::serde::Serializer>(&self, ser: S) -> ::core::result::Result<S::Ok, S::Error> {
237                ser.serialize_str(&::std::string::ToString::to_string(self))
238            }
239        }
240
241        impl<'de> ::serde::Deserialize<'de> for $name {
242            fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> ::core::result::Result<Self, D::Error> {
243                let s = <::std::string::String as ::serde::Deserialize>::deserialize(d)?;
244                ::core::result::Result::Ok(<Self as $crate::util::X12Element>::from_x12(&s))
245            }
246        }
247    };
248}
249
250/// Define an X12 date data element (format `CCYYMMDD`).
251///
252/// The element stores the raw 8-char text (so `Display` reproduces it exactly and it
253/// round-trips byte-for-byte) and exposes a typed [`chrono::NaiveDate`] view via
254/// `date()`. Naming follows the X12 element number (e.g. `E373`, `E109`).
255#[macro_export]
256macro_rules! date_element {
257    ($(#[$meta:meta])* $name:ident) => {
258        $(#[$meta])*
259        #[derive(Clone, Default, Debug, PartialEq, Eq)]
260        pub struct $name {
261            raw: ::std::string::String,
262        }
263
264        impl $name {
265            /// The raw element text, exactly as parsed (`CCYYMMDD`).
266            pub fn raw(&self) -> &str {
267                &self.raw
268            }
269            /// The typed date, parsed on demand from `CCYYMMDD`. `None` if empty or
270            /// not a valid date (never panics, never alters the stored text).
271            pub fn date(&self) -> ::core::option::Option<::chrono::NaiveDate> {
272                ::chrono::NaiveDate::parse_from_str(&self.raw, "%Y%m%d").ok()
273            }
274            /// Build from a typed date, rendering it as `CCYYMMDD`.
275            pub fn from_date(d: ::chrono::NaiveDate) -> Self {
276                Self { raw: d.format("%Y%m%d").to_string() }
277            }
278        }
279
280        impl ::core::fmt::Display for $name {
281            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
282                f.write_str(&self.raw)
283            }
284        }
285
286        impl $crate::util::X12Element for $name {
287            fn from_x12(s: &str) -> Self {
288                Self { raw: s.to_string() }
289            }
290        }
291
292        impl ::serde::Serialize for $name {
293            fn serialize<S: ::serde::Serializer>(&self, ser: S) -> ::core::result::Result<S::Ok, S::Error> {
294                ser.serialize_str(&self.raw)
295            }
296        }
297
298        impl<'de> ::serde::Deserialize<'de> for $name {
299            fn deserialize<D: ::serde::Deserializer<'de>>(d: D) -> ::core::result::Result<Self, D::Error> {
300                let raw = <::std::string::String as ::serde::Deserialize>::deserialize(d)?;
301                ::core::result::Result::Ok(Self { raw })
302            }
303        }
304    };
305}