1use crate::edi_parse_error::EdiParseError;
2use crate::generic_segment::GenericSegment;
3use crate::tokenizer::SegmentTokens;
4use csv::ReaderBuilder;
5use lazy_static::lazy_static;
6use rust_embed::RustEmbed;
7use serde::{Deserialize, Serialize};
8use std::borrow::Cow;
9use std::collections::{HashMap, VecDeque};
10
11#[derive(PartialEq, Debug, Serialize, Deserialize)]
14pub struct Transaction<'a> {
15 #[serde(borrow)]
17 pub transaction_code: Cow<'a, str>,
18 #[serde(borrow)]
20 pub transaction_name: Cow<'a, str>,
21 #[serde(borrow)]
24 pub transaction_set_control_number: Cow<'a, str>,
25 #[serde(borrow)]
27 pub implementation_convention_reference: Option<Cow<'a, str>>,
28 #[serde(borrow)]
30 pub segments: VecDeque<GenericSegment<'a>>,
31}
32
33#[derive(RustEmbed)]
34#[folder = "$CARGO_MANIFEST_DIR/resources"]
35#[prefix = "resources/"]
36struct Resources;
37
38lazy_static! {
41 static ref SCHEMAS: HashMap<String, String> = {
42 let mut map = HashMap::new();
43 let file_content = Resources::get("resources/schemas.csv").unwrap();
44 let file_content_str = String::from_utf8_lossy(file_content.data.as_ref());
45 let mut schemas_csv = ReaderBuilder::new()
46 .has_headers(false)
47 .from_reader(file_content_str.as_bytes());
48 for record in schemas_csv.records() {
49 let record = record.unwrap();
50 map.insert(record[0].to_string(), record[1].to_string());
51 }
52 map
53 };
54}
55
56impl<'a> Transaction<'a> {
57 pub(crate) fn parse_from_tokens(
59 input: SegmentTokens<'a>,
60 ) -> Result<Transaction<'a>, EdiParseError> {
61 let elements: Vec<&str> = input.iter().map(|x| x.trim()).collect();
62 edi_assert!(
65 elements[0] == "ST",
66 "attempted to parse ST from non-ST segment",
67 input
68 );
69 edi_assert!(
70 elements.len() >= 3,
71 "ST segment does not contain enough elements. At least 3 required",
72 input
73 );
74
75 let (transaction_code, transaction_set_control_number) =
76 (Cow::from(elements[1]), Cow::from(elements[2]));
77 let implementation_convention_reference = if elements.len() >= 4 {
78 Some(Cow::from(elements[3]))
79 } else {
80 None
81 };
82 let transaction_name = if let Some(name) = SCHEMAS.get(&transaction_code.to_string()) {
83 name
84 } else {
85 "unidentified"
86 };
87
88 Ok(Transaction {
89 transaction_code,
90 transaction_name: Cow::from(transaction_name),
91 transaction_set_control_number,
92 implementation_convention_reference,
93 segments: VecDeque::new(),
94 })
95 }
96
97 pub(crate) fn add_generic_segment(
99 &mut self,
100 tokens: SegmentTokens<'a>,
101 ) -> Result<(), EdiParseError> {
102 self.segments
103 .push_back(GenericSegment::parse_from_tokens(tokens)?);
104 Ok(())
105 }
106
107 pub(crate) fn validate_transaction(
109 &self,
110 tokens: SegmentTokens<'a>,
111 ) -> Result<(), EdiParseError> {
112 edi_assert!(
113 tokens[0] == "SE",
114 "attempted to validate transaction with non-SE segment",
115 tokens
116 );
117 edi_assert!(
119 str::parse::<usize>(tokens[1]).unwrap() == self.segments.len() + 2,
120 "transaction validation failed: incorrect number of segments",
121 tokens[1],
122 self.segments.len() + 2,
123 tokens
124 );
125 edi_assert!(
126 tokens[2] == self.transaction_set_control_number,
127 "transaction validation failed: incorrect transaction ID",
128 tokens[2],
129 self.transaction_set_control_number,
130 tokens
131 );
132 Ok(())
133 }
134
135 pub fn to_x12_string(&self, segment_delimiter: char, element_delimiter: char) -> String {
137 let mut header = "ST".to_string();
138 header.push(element_delimiter);
139 header.push_str(&self.transaction_code);
140 header.push(element_delimiter);
141 header.push_str(&self.transaction_set_control_number);
142 header.push(element_delimiter);
143 header.push_str(
144 &self
145 .implementation_convention_reference
146 .clone()
147 .unwrap_or(Cow::Borrowed("")),
148 );
149
150 let mut final_string = self.segments.iter().fold(header, |mut acc, segment| {
151 acc.push(segment_delimiter);
152 acc.push_str(&segment.to_x12_string(element_delimiter));
153 acc
154 });
155
156 let mut closer = "SE".to_string();
157 closer.push(element_delimiter);
158 closer.push_str(&(self.segments.len() + 2).to_string()); closer.push(element_delimiter);
160 closer.push_str(&self.transaction_set_control_number.clone());
161
162 final_string.push(segment_delimiter);
163 final_string.push_str(&closer);
164
165 final_string
166 }
167}
168
169#[test]
170fn transaction_to_string() {
171 use std::iter::FromIterator;
172 let segments = VecDeque::from_iter(vec![
173 GenericSegment {
174 segment_abbreviation: Cow::from("BGN"),
175 elements: ["20", "TEST_ID", "200615", "0000"]
176 .iter()
177 .map(|x| Cow::from(*x))
178 .collect::<VecDeque<Cow<str>>>(),
179 },
180 GenericSegment {
181 segment_abbreviation: Cow::from("BGN"),
182 elements: ["15", "OTHER_TEST_ID", "", "", "END"]
183 .iter()
184 .map(|x| Cow::from(*x))
185 .collect::<VecDeque<Cow<str>>>(),
186 },
187 ]);
188 let transaction = Transaction {
189 transaction_code: Cow::from("140"),
190 transaction_name: Cow::from(""),
191 transaction_set_control_number: Cow::from("100000001"),
192 implementation_convention_reference: None,
193 segments,
194 };
195
196 assert_eq!(
197 transaction.to_x12_string('~', '*'),
198 "ST*140*100000001*~BGN*20*TEST_ID*200615*0000~BGN*15*OTHER_TEST_ID***END~SE*4*100000001"
199 );
200}
201
202#[test]
203fn construct_transaction() {
204 let expected_result = Transaction {
205 transaction_code: Cow::from("850"),
206 transaction_name: Cow::from(SCHEMAS.get(&"850".to_string()).unwrap()), transaction_set_control_number: Cow::from("000000001"),
208 implementation_convention_reference: None,
209 segments: VecDeque::new(),
210 };
211 let test_input = vec!["ST", "850", "000000001"];
212
213 assert_eq!(
214 Transaction::parse_from_tokens(test_input).unwrap(),
215 expected_result
216 );
217}
218
219#[test]
220fn spot_check_schemas() {
221 assert_eq!(SCHEMAS.get(&"850".to_string()).unwrap(), "Purchase Order");
222 assert_eq!(
223 SCHEMAS.get(&"100".to_string()).unwrap(),
224 "Insurance Plan Description"
225 );
226 assert_eq!(
227 SCHEMAS.get(&"999".to_string()).unwrap(),
228 "Implementation Acknowledgment"
229 );
230}