edi/
functional_group.rs

1use crate::edi_parse_error::EdiParseError;
2
3use crate::transaction::Transaction;
4
5use crate::tokenizer::SegmentTokens;
6use serde::{Deserialize, Serialize};
7use std::borrow::Cow;
8use std::collections::VecDeque;
9
10/// Represents a GS/GE segment which wraps a functional group.
11/// Documentation here gleaned mostly from [here](http://u.sezna.dev/b)
12#[derive(PartialEq, Debug, Serialize, Deserialize)]
13pub struct FunctionalGroup<'a> {
14    /// Identifies the function of this group.
15    /// See http://ecomgx17.ecomtoday.com/edi/EDI_4010/el479.htm for a list of
16    /// functional identifier codes.
17    #[serde(borrow)]
18    pub functional_identifier_code: Cow<'a, str>,
19    /// Identifies the sender of this group.
20    #[serde(borrow)]
21    pub application_sender_code: Cow<'a, str>,
22    /// Identifies the receiver of this group.
23    #[serde(borrow)]
24    pub application_receiver_code: Cow<'a, str>,
25    /// Identifies the date of the function performed.
26    #[serde(borrow)]
27    pub date: Cow<'a, str>,
28    /// Identifies the time of the function performed.
29    ///  Expressed in 24-hour clock time as follows: HHMM, or HHMMSS, or
30    /// HHMMSSD, or HHMMSSDD, where H = hours (00-23), M = minutes (00-59), S = integer
31    /// seconds (00-59) and DD = decimal seconds; decimal seconds are expressed as follows: D
32    /// = tenths (0-9) and DD = hundredths (00-99)
33    #[serde(borrow)]
34    pub time: Cow<'a, str>,
35    /// An ID code for this specific control group. Should
36    /// be the same in the GE (group end) segment.
37    #[serde(borrow)]
38    pub group_control_number: Cow<'a, str>,
39    /// Code identifying the issuer of the standard
40    #[serde(borrow)]
41    pub responsible_agency_code: Cow<'a, str>,
42    ///  Code indicating the version, release, subrelease, and industry identifier of the
43    ///  EDI standard being used, including the GS and GE segments; If code DE455 in GS
44    /// segment is X, then in DE 480 positions 1-3 are the version number; positions 4-6 are the
45    /// release and subrelease, level of the version; and positions 7-12 are the industry or trade
46    /// association identifiers (optionally assigned by user); if code in DE455 in GS segment is T,
47    /// then other formats are allowed
48    #[serde(borrow)]
49    pub version: Cow<'a, str>,
50    /// The transactions that this functional group contains.
51    #[serde(borrow = "'a")]
52    pub transactions: VecDeque<Transaction<'a>>,
53}
54
55impl<'a> FunctionalGroup<'a> {
56    /// Given [SegmentTokens](struct.SegmentTokens.html) (where the first token is "GS"), construct a [FunctionalGroup].
57    pub(crate) fn parse_from_tokens(
58        input: SegmentTokens<'a>,
59    ) -> Result<FunctionalGroup<'a>, EdiParseError> {
60        let elements: Vec<&str> = input.iter().map(|x| x.trim()).collect();
61        // I always inject invariants wherever I can to ensure debugging is quick and painless,
62        // and to check my assumptions.
63        edi_assert!(
64            elements[0] == "GS",
65            "attempted to parse GS from non-GS segment",
66            input
67        );
68        edi_assert!(
69            elements.len() >= 9,
70            "GS segment does not contain enough elements. At least 9 required",
71            input
72        );
73        let (
74            functional_identifier_code,
75            application_sender_code,
76            application_receiver_code,
77            date,
78            time,
79            group_control_number,
80            responsible_agency_code,
81            version,
82        ) = (
83            Cow::from(elements[1]),
84            Cow::from(elements[2]),
85            Cow::from(elements[3]),
86            Cow::from(elements[4]),
87            Cow::from(elements[5]),
88            Cow::from(elements[6]),
89            Cow::from(elements[7]),
90            Cow::from(elements[8]),
91        );
92
93        Ok(FunctionalGroup {
94            functional_identifier_code,
95            application_sender_code,
96            application_receiver_code,
97            date,
98            time,
99            group_control_number,
100            responsible_agency_code,
101            version,
102            transactions: VecDeque::new(),
103        })
104    }
105
106    /// Enqueue a [Transaction] into the group. Subsequent segments will be enqueued into this transaction.
107    pub(crate) fn add_transaction(
108        &mut self,
109        tokens: SegmentTokens<'a>,
110    ) -> Result<(), EdiParseError> {
111        self.transactions
112            .push_back(Transaction::parse_from_tokens(tokens)?);
113        Ok(())
114    }
115
116    /// Enqueue a [GenericSegment](struct.GenericSegment.html) into the most recently enqueued [Transaction].
117    pub(crate) fn add_generic_segment(
118        &mut self,
119        tokens: SegmentTokens<'a>,
120    ) -> Result<(), EdiParseError> {
121        if let Some(transaction) = self.transactions.back_mut() {
122            transaction.add_generic_segment(tokens)
123        } else {
124            Err(EdiParseError::new(
125                "unable to enqueue generic segment when no transactions have been enqueued",
126                Some(tokens),
127            ))
128        }
129    }
130
131    /// Verify this [FunctionalGroup] with a GE segment.
132    pub(crate) fn validate_functional_group(
133        &self,
134        tokens: SegmentTokens<'a>,
135    ) -> Result<(), EdiParseError> {
136        edi_assert!(
137            tokens[0] == "GE",
138            "attempted to call GE verification on non-GE segment",
139            tokens
140        );
141        edi_assert!(
142            self.transactions.len() == str::parse::<usize>(tokens[1]).unwrap(),
143            "functional group validation failed: incorrect number of transactions",
144            self.transactions.len(),
145            str::parse::<usize>(tokens[1]).unwrap(),
146            tokens
147        );
148        edi_assert!(
149            self.group_control_number == tokens[2],
150            "functional group validation failed: mismatched ID",
151            self.group_control_number,
152            tokens[2],
153            tokens
154        );
155        Ok(())
156    }
157
158    /// Validate the latest [Transaction] within this functional group with an SE segment.
159    pub(crate) fn validate_transaction(
160        &self,
161        tokens: SegmentTokens<'a>,
162    ) -> Result<(), EdiParseError> {
163        if let Some(transaction) = self.transactions.back() {
164            transaction.validate_transaction(tokens)
165        } else {
166            Err(EdiParseError::new(
167                "unable to validate nonexistent transaction",
168                Some(tokens),
169            ))
170        }
171    }
172
173    /// Converts this functional group into an ANSI x12 string for use in an EDI document.
174    pub fn to_x12_string(&self, segment_delimiter: char, element_delimiter: char) -> String {
175        let header = String::from("GS");
176        let elements_of_gs = [
177            self.functional_identifier_code.clone(),
178            self.application_sender_code.clone(),
179            self.application_receiver_code.clone(),
180            self.date.clone(),
181            self.time.clone(),
182            self.group_control_number.clone(),
183            self.responsible_agency_code.clone(),
184            self.version.clone(),
185        ];
186
187        let mut buffer = elements_of_gs.iter().fold(header, |mut acc, elem| {
188            acc.push(element_delimiter);
189            acc.push_str(elem);
190            acc
191        });
192        let transactions = self
193            .transactions
194            .iter()
195            .fold(String::new(), |mut acc, transaction| {
196                acc.push(segment_delimiter);
197                acc.push_str(&transaction.to_x12_string(segment_delimiter, element_delimiter));
198                acc
199            });
200
201        buffer.push_str(&transactions);
202
203        let mut closer = String::from("GE");
204        closer.push(element_delimiter);
205        closer.push_str(&self.transactions.len().to_string());
206        closer.push(element_delimiter);
207        closer.push_str(&self.group_control_number);
208
209        buffer.push(segment_delimiter);
210        buffer.push_str(&closer);
211        buffer
212    }
213}
214
215#[test]
216fn functional_group_to_string() {
217    use crate::GenericSegment;
218    use std::iter::FromIterator;
219    let segments = VecDeque::from_iter(vec![
220        GenericSegment {
221            segment_abbreviation: Cow::from("BGN"),
222            elements: ["20", "TEST_ID", "200615", "0000"]
223                .iter()
224                .map(|x| Cow::from(*x))
225                .collect::<VecDeque<Cow<str>>>(),
226        },
227        GenericSegment {
228            segment_abbreviation: Cow::from("BGN"),
229            elements: ["15", "OTHER_TEST_ID", "", "", "END"]
230                .iter()
231                .map(|x| Cow::from(*x))
232                .collect::<VecDeque<Cow<str>>>(),
233        },
234    ]);
235    let transaction = Transaction {
236        transaction_code: Cow::from("140"),
237        transaction_name: Cow::from(""),
238        transaction_set_control_number: Cow::from("100000001"),
239        implementation_convention_reference: None,
240        segments,
241    };
242
243    let functional_group = FunctionalGroup {
244        functional_identifier_code: Cow::from("PO"),
245        application_sender_code: Cow::from("SENDERGS"),
246        application_receiver_code: Cow::from("007326879"),
247        date: Cow::from("20020226"),
248        time: Cow::from("1534"),
249        group_control_number: Cow::from("1"),
250        responsible_agency_code: Cow::from("X"),
251        version: Cow::from("004010"),
252        transactions: VecDeque::from_iter(vec![transaction]),
253    };
254    assert_eq!(functional_group.to_x12_string('\n', '*'), "GS*PO*SENDERGS*007326879*20020226*1534*1*X*004010\nST*140*100000001*\nBGN*20*TEST_ID*200615*0000\nBGN*15*OTHER_TEST_ID***END\nSE*4*100000001\nGE*1*1");
255}
256
257#[test]
258fn construct_functional_group() {
259    let expected_result = FunctionalGroup {
260        functional_identifier_code: Cow::from("PO"),
261        application_sender_code: Cow::from("SENDERGS"),
262        application_receiver_code: Cow::from("007326879"),
263        date: Cow::from("20020226"),
264        time: Cow::from("1534"),
265        group_control_number: Cow::from("1"),
266        responsible_agency_code: Cow::from("X"),
267        version: Cow::from("004010"),
268        transactions: VecDeque::new(),
269    };
270
271    let test_input = vec![
272        "GS",
273        "PO",
274        "SENDERGS",
275        "007326879",
276        "20020226",
277        "1534",
278        "1",
279        "X",
280        "004010",
281    ];
282
283    assert_eq!(
284        FunctionalGroup::parse_from_tokens(test_input).unwrap(),
285        expected_result
286    );
287}