1use crate::util;
2use std::collections::HashMap;
3
4#[derive(Default, Debug)]
6pub struct Detr<'a> {
7 pub issued_by: Option<&'a str>,
9 pub org: Option<&'a str>,
11 pub dst: Option<&'a str>,
13 pub et: Option<&'a str>,
15 pub er: Option<&'a str>,
17 pub tour_code: Option<&'a str>,
19 pub receipt_printed: bool,
21 pub passenger: Option<&'a str>,
23 pub exchange: Option<&'a str>,
25 pub conj_tickets: Option<&'a str>,
27 pub flight_segs: Vec<DetrFlightSeg<'a>>,
29 pub fc: Option<&'a str>,
31 pub fare: Option<DetrFareItem<'a>>,
33 pub taxs: Option<HashMap<Option<&'a str>, DetrFareItem<'a>>>,
35 pub total: Option<DetrFareItem<'a>>,
37 pub fop: Option<&'a str>,
39 pub oi: Option<&'a str>,
40 pub tktn: Option<&'a str>,
42}
43
44#[derive(Default, Debug, PartialEq)]
45pub struct DetrFareItem<'a> {
46 pub item_type: Option<&'a str>,
47 pub amount: Option<f32>,
48 pub currency: Option<&'a str>,
49 pub is_exempt: bool,
50}
51
52#[derive(Default, Debug)]
53pub struct DetrFlightSeg<'a> {
54 pub transit_flag: Option<&'a str>,
56 pub index: Option<i32>,
58 pub org: Option<&'a str>,
60 pub dst: Option<&'a str>,
62 pub airline: Option<&'a str>,
64 pub carrier: Option<&'a str>,
66 pub flight_no: Option<&'a str>,
68 pub flight_deptdate: Option<&'a str>,
70 pub flight_depttime: Option<&'a str>,
72 pub flight_class: Option<&'a str>,
74 pub seat_status: Option<&'a str>,
80 pub fare_basis: Option<&'a str>,
82 pub nvb: Option<&'a str>,
84 pub nva: Option<&'a str>,
86 pub baggage: Option<&'a str>,
91 pub ticket_status: Option<&'a str>,
104 pub org_term: Option<&'a str>,
106 pub dst_term: Option<&'a str>,
108 pub bpnr: Option<&'a str>,
110 pub cpnr: Option<&'a str>,
112 pub system: Option<&'a str>,
113 pub passenger_status_flag: Option<&'a str>,
117}
118
119impl<'a> Detr<'a> {
120 pub fn parse(text: &'a str) -> anyhow::Result<Self> {
121 if text.is_empty() {
122 return Err(anyhow::Error::msg(
123 "detr parameter shouldn't be empty.".to_owned(),
124 ));
125 }
126 let re = regex::Regex::new(
128 r"(?<TRANSITFLAG>[O|X]) (FM|TO):(?<INDEX>\d)(?<ORG>[A-Z]{3}) (?<AIRLINE>\w{2}) (?<CARRIER>..{2}) *(?<FLIGHTNO>\d+|OPEN)\s+(?<CABIN>[A-Z]) (?<DETPDATE>\d{2}[A-Z]{3}|OPEN ) (?<DEPTTIME>.{4}) (?<SEATSTATUS>.{2}) (?<FAREBASIS>.{10}) (?<NVB>.{6}).(?<NVA>.{6}) (?<BAGGAGE>.{3}) (?<TICKETSTATUS>[^\r\n]+)(\r|\n)+.....(?<ORGTERMINAL>..)(?<DSTTERMINAL>..) RL:(?:(?<BPNR>\w{6})(\s+)/((?<CPNR>\w{6})(?<SYSTEM>\w{2}))?)?(\s*[\r|\n]+\s+)TO:\s+(?<DST>[A-Z]{3})",
129 )?;
130 let detr_flight_segs = re
131 .captures_iter(text)
132 .filter_map(|caps| {
133 match (
134 caps.name("TRANSITFLAG"),
135 caps.name("INDEX"),
136 caps.name("ORG"),
137 caps.name("AIRLINE"),
138 caps.name("CARRIER"),
139 caps.name("FLIGHTNO"),
140 caps.name("CABIN"),
141 caps.name("DETPDATE"),
142 caps.name("DEPTTIME"),
143 caps.name("SEATSTATUS"),
144 caps.name("FAREBASIS"),
145 caps.name("NVB"),
146 caps.name("NVA"),
147 caps.name("BAGGAGE"),
148 caps.name("TICKETSTATUS"),
149 caps.name("ORGTERMINAL"),
150 caps.name("DSTTERMINAL"),
151 caps.name("BPNR"),
152 caps.name("CPNR"),
153 caps.name("SYSTEM"),
154 caps.name("DST"),
155 ) {
156 (
157 Some(cap_transit),
158 Some(cap_index),
159 cap_org,
160 cap_airline,
161 cap_carrier,
162 cap_flightno,
163 cap_cabin,
164 cap_deptdate,
165 cap_depttime,
166 cap_seatstatus,
167 cap_farebasis,
168 cap_nvb,
169 cap_nva,
170 cap_baggage,
171 cap_ticketstatus,
172 cap_orgterminal,
173 cap_dstterminal,
174 cap_bpnr,
175 cap_cpnr,
176 cap_system,
177 cap_dst,
178 ) => {
179 if cap_transit.as_str().contains("VOID") {
180 Some(DetrFlightSeg {
181 transit_flag: Some(cap_transit.as_str()),
182 index: cap_index.as_str().parse::<i32>().ok(),
183 org: util::regex_extact_text(cap_org),
184 seat_status: Some("VOID"),
185 ticket_status: Some("VOID"),
186 ..Default::default()
187 })
188 } else {
189 Some(DetrFlightSeg {
190 transit_flag: Some(cap_transit.as_str()),
191 index: cap_index.as_str().parse::<i32>().ok(),
192 org: util::regex_extact_text(cap_org),
193 airline: util::regex_extact_text(cap_airline),
194 carrier: util::regex_extact_text(cap_carrier),
195 flight_no: util::regex_extact_text(cap_flightno),
196 flight_class: util::regex_extact_text(cap_cabin),
197 flight_deptdate: util::regex_extact_text(cap_deptdate),
198 flight_depttime: util::regex_extact_text(cap_depttime),
199 seat_status: util::regex_extact_text(cap_seatstatus),
200 fare_basis: util::regex_extact_text(cap_farebasis),
201 nvb: util::regex_extact_text(cap_nvb),
202 nva: util::regex_extact_text(cap_nva),
203 baggage: util::regex_extact_text(cap_baggage),
204 ticket_status: util::regex_extact_text(cap_ticketstatus),
205 org_term: util::regex_extact_text(cap_orgterminal),
206 dst_term: util::regex_extact_text(cap_dstterminal),
207 bpnr: util::regex_extact_text(cap_bpnr),
208 cpnr: util::regex_extact_text(cap_cpnr),
209 system: util::regex_extact_text(cap_system),
210 dst: util::regex_extact_text(cap_dst),
211 ..Default::default()
212 })
213 }
214 }
215 _ => None,
216 }
217 })
218 .collect::<Vec<_>>();
219
220 let re = regex::Regex::new(r"FARE:\s+(?<CURRENCY>[A-Z]{3})\s*(?<AMOUNT>\d+\.\d{2})\|")?;
221 let fare = match re.captures(text) {
222 Some(caps) => match (caps.name("CURRENCY"), caps.name("AMOUNT")) {
223 (Some(cap2), Some(cap3)) => Some(DetrFareItem {
224 amount: cap3.as_str().parse::<f32>().ok(),
225 currency: Some(cap2.as_str()),
226 is_exempt: false,
227 ..Default::default()
228 }),
229 _ => None,
230 },
231 _ => None,
232 };
233
234 let re = regex::Regex::new(r"TOTAL:\s+(?<CURRENCY>[A-Z]{3})\s*(?<AMOUNT>\d+\.\d{2})\|")?;
235 let total = match re.captures(text) {
236 Some(caps) => match (caps.name("CURRENCY"), caps.name("AMOUNT")) {
237 (Some(cap2), Some(cap3)) => Some(DetrFareItem {
238 amount: cap3.as_str().parse::<f32>().ok(),
239 currency: Some(cap2.as_str()),
240 is_exempt: false,
241 ..Default::default()
242 }),
243 _ => None,
244 },
245 _ => None,
246 };
247
248 let re = regex::Regex::new(
249 r"TAX:\s+(?:(?<EXEMPT>EXEMPT)|(?<CURRENCY>[A-Z]{3})\s*(?<PRICE>\d+\.\d{2}))(?<TYPE>[A-Z]{2})\|",
250 )?;
251
252 let items = re
253 .captures_iter(text)
254 .filter_map(|caps| {
255 match (
256 caps.name("TYPE"),
257 caps.name("EXEMPT"),
258 caps.name("CURRENCY"),
259 caps.name("PRICE"),
260 ) {
261 (Some(cap_type), cap_exempt, cap_curr, cap_price) => Some(DetrFareItem {
262 amount: cap_price.map_or(None, |x| x.as_str().parse::<f32>().ok()),
263 currency: util::regex_extact_text(cap_curr),
264 is_exempt: cap_exempt.map_or(false, |x| x.as_str() == "EXEMPT"),
265 item_type: Some(cap_type.as_str()),
266 }),
267 _ => None,
268 }
269 })
270 .collect::<Vec<_>>();
271 let taxs = if items.len() == 0 {
272 None
273 } else {
274 let mut map = HashMap::new();
275 for item in items {
276 map.insert(item.item_type, item);
277 }
278 Some(map)
279 };
280
281 Ok(Self {
282 issued_by: util::regex_extact(r"\bISSUED BY: ?(.*)ORG/DST:", text)?,
283 org: util::regex_extact(r"ORG/DST: ?([A-Z]{3})/[A-Z]{3}", text)?,
284 dst: util::regex_extact(r"ORG/DST: ?[A-Z]{3}/([A-Z]{3})", text)?,
285 et: util::regex_extact(r"ORG/DST: ?[A-Z]{3}/[A-Z]{3}\s+([A-Z\-]+)", text)?,
286 er: util::regex_extact(r"E/R: ?(.*?)(\r|\n)+", text)?,
287 tour_code: util::regex_extact(r"TOUR CODE: ?(\S[^\r]*?)(\r|\n)+", text)?,
288 receipt_printed: text.contains("RECEIPT PRINTED"),
289 passenger: util::regex_extact(r"PASSENGER: ?(\S[^\r]*?)(\r|\n)+", text)?,
290 exchange: util::regex_extact(r"EXCH: ?(\S[^\r]*?)\S", text)?,
291 conj_tickets: util::regex_extact(r"CONJ TKT: ?(\S[^\r]*?)(\r|\n)+", text)?,
292 flight_segs: detr_flight_segs,
293 fc: util::regex_extact(r"FC: ?(\S[^\r]*?)(\r|\n)+", text)?,
294 fare,
295 taxs,
296 total,
297 fop: util::regex_extact(r"\|FOP: ?(\S[^\r]*?)(\r|\n)+", text)?,
298 oi: util::regex_extact(r"\|OI: ?(\S[^\r]*?)(\r|\n)+", text)?,
299 tktn: util::regex_extact(r"\|TKTN: ?(\S[^\r]*?)(\r|\n)+", text)?,
300 })
301 }
302}