1use serde::{Serialize, Deserialize};
2use crate::models::koordinaten::Koordinaten;
3use crate::models::transport::TransportBezeichnung;
4use crate::models::realtime_info::{SectionRealtimeInfo, RealtimeInfo};
5use crate::models::legend::{LegendOccupancy, LegendItem};
6use crate::models::ticketing::TicketingInfo;
7use core::fmt;
8use std::time::Duration;
9use std::str::FromStr;
10
11use mockall::predicate::*;
12use regex::bytes::Regex;
13
14#[derive(Serialize, Deserialize, Debug)]
15pub struct Verbindung {
16 #[serde(rename = "abfahrt")]
17 pub abfahrt: String,
18
19 #[serde(rename = "abfahrtDate")]
20 pub abfahrt_date: String,
21
22 #[serde(rename = "abfahrtGleis")]
23 pub abfahrt_gleis: Option<String>,
24
25 #[serde(rename = "abfahrtTime")]
26 pub abfahrt_time: String,
27
28 #[serde(rename = "angeboteUrl")]
29 pub angebote_url: String,
30
31 #[serde(rename = "ankunft")]
32 pub ankunft: String,
33
34 #[serde(rename = "ankunftDate")]
35 pub ankunft_date: String,
36
37 #[serde(rename = "ankunftTime")]
38 pub ankunft_time: String,
39
40 #[serde(rename = "belegungErste")]
41 pub belegung_erste: String,
42
43 #[serde(rename = "belegungZweite")]
44 pub belegung_zweite: String,
45
46 #[serde(rename = "dayDifference")]
47 pub day_difference: String,
48
49 #[serde(rename = "dayDifferenceAccessibility")]
50 pub day_difference_accessibility: String,
51
52 #[serde(rename = "departureTrackLabel")]
53 pub departure_track_label: String,
54
55 #[serde(rename = "departureTrackLabelAccessibility")]
56 pub departure_track_label_accessibility: String,
57
58 #[serde(rename = "duration")]
59 pub duration: String,
60
61 #[serde(rename = "durationAccessibility")]
62 pub duration_accessibility: String,
63
64 #[serde(rename = "isInternationalVerbindung")]
65 pub is_international_verbindung: Option<bool>,
66
67 #[serde(rename = "legendBfrItems")]
68 pub legend_bfr_items: Vec<String>,
69
70 #[serde(rename = "legendItems")]
71 pub legend_items: Vec<LegendItem>,
72
73 #[serde(rename = "legendOccupancyItems")]
74 pub legend_occupancy_items: Vec<LegendOccupancy>,
75
76 #[serde(rename = "realtimeInfo")]
77 pub realtime_info: RealtimeInfo,
78
79 #[serde(rename = "reconstructionContext")]
80 pub reconstruction_context: String,
81
82 #[serde(rename = "serviceAttributes")]
83 pub service_attributes: Vec<String>,
84
85 #[serde(rename = "ticketingInfo")]
86 pub ticketing_info: TicketingInfo,
87
88 #[serde(rename = "transfers")]
89 pub transfers: i32,
90
91 #[serde(rename = "transportBezeichnung")]
92 pub transport_bezeichnung: TransportBezeichnung,
93
94 #[serde(rename = "verbindungAbpreisContext")]
95 pub verbindung_abpreis_context: String,
96
97 #[serde(rename = "verbindungId")]
98 pub verbindung_id: String,
99
100 #[serde(rename = "verbindungSections")]
101 pub verbindung_sections: Vec<VerbindungSection>,
102
103 #[serde(rename = "verkehrstage")]
104 pub verkehrstage: Vec<String>,
105
106 #[serde(rename = "vias")]
107 pub vias: Option<Vec<String>>,
108
109 #[serde(rename = "zuschlagspflicht")]
110 pub zuschlagspflicht: bool,
111}
112
113#[derive(Serialize, Deserialize, Debug)]
114pub struct VerbindungSection {
115 #[serde(rename = "abfahrtCancellation")]
116 pub abfahrt_cancellation: bool,
117
118 #[serde(rename = "abfahrtDatum")]
119 pub abfahrt_datum: String,
120
121 #[serde(rename = "abfahrtGleis")]
122 pub abfahrt_gleis: Option<String>,
123
124 #[serde(rename = "abfahrtKoordinaten")]
125 pub abfahrt_koordinaten: Koordinaten,
126
127 #[serde(rename = "abfahrtName")]
128 pub abfahrt_name: String,
129
130 #[serde(rename = "abfahrtPlatformChange")]
131 pub abfahrt_platform_change: bool,
132
133 #[serde(rename = "abfahrtTime")]
134 pub abfahrt_time: String,
135
136 #[serde(rename = "actionUrl")]
137 pub action_url: Option<String>,
138
139 #[serde(rename = "ankunftCancellation")]
140 pub ankunft_cancellation: bool,
141
142 #[serde(rename = "ankunftDatum")]
143 pub ankunft_datum: String,
144
145 #[serde(rename = "ankunftGleis")]
146 pub ankunft_gleis: Option<String>,
147
148 #[serde(rename = "ankunftKoordinaten")]
149 pub ankunft_koordinaten: Koordinaten,
150
151 #[serde(rename = "ankunftName")]
152 pub ankunft_name: String,
153
154 #[serde(rename = "ankunftPlatformChange")]
155 pub ankunft_platform_change: bool,
156
157 #[serde(rename = "ankunftTime")]
158 pub ankunft_time: String,
159
160 #[serde(rename = "arrivalTrackLabel")]
161 pub arrival_track_label: Option<String>,
162
163 #[serde(rename = "arrivalTrackLabelAccessibility")]
164 pub arrival_track_label_accessibility: Option<String>,
165
166 #[serde(rename = "belegungErste")]
167 pub belegung_erste: String,
168
169 #[serde(rename = "belegungZweite")]
170 pub belegung_zweite: String,
171
172 #[serde(rename = "departureTrackLabel")]
173 pub departure_track_label: Option<String>,
174
175 #[serde(rename = "departureTrackLabelAccessibility")]
176 pub departure_track_label_accessibility: Option<String>,
177
178 #[serde(rename = "durationProzent")]
179 pub duration_prozent: Option<String>,
180
181 #[serde(rename = "formationUrl")]
182 pub formation_url: Option<String>,
183
184 #[serde(rename = "previewType")]
185 pub preview_type: String,
186
187 #[serde(rename = "realtimeInfo")]
188 pub realtime_info: SectionRealtimeInfo,
189
190 #[serde(rename = "transportBezeichnung")]
191 pub transport_bezeichnung: Option<TransportBezeichnung>,
192
193 #[serde(rename = "transportHinweis")]
194 pub transport_hinweis: Option<String>,
195
196 #[serde(rename = "transportServiceAttributes")]
197 pub transport_service_attributes: Vec<String>,
198
199 #[serde(rename = "type")]
200 pub verbindung_type: String,
201}
202
203impl fmt::Display for Verbindung {
204 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
205 let mut verbindungen= String::new();
206 for vs in self.verbindung_sections.as_slice() {
207 verbindungen = format!("{}, {}", verbindungen, vs)
208 }
209
210 write!(f, "{} ({}): {}", self.transport_bezeichnung, self.duration, verbindungen)
211 }
212}
213
214impl fmt::Display for VerbindungSection {
215 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
216 write!(f, "{} {} {} - {} {}",
217 match &self.transport_bezeichnung {
218 Some(t) => format!("{}", t),
219 None => String::new()
220 },
221 self.abfahrt_name,
222 self.abfahrt_time,
223 self.ankunft_name,
224 self.ankunft_time)
225 }
226}
227
228fn cap_to_u64(m: Option<regex::bytes::Match>) -> u64 {
229 if m.is_none() {
230 return 0;
231 }
232
233 let s = std::str::from_utf8(m.unwrap().as_bytes()).unwrap_or(&"");
234 return u64::from_str(s).unwrap_or(0);
235}
236
237impl Verbindung {
238 pub fn duration(&self) -> Duration {
239 let duration = &self.duration;
240 let re = Regex::new(r"^(?:(\d+) h|)(?: |)(?:(\d+) min|)$").unwrap();
241
242 if !re.is_match(duration.as_bytes()) {
243 return Duration::from_millis(0)
244 }
245
246 for cap in re.captures_iter(duration.as_bytes()) {
247 let hours = cap_to_u64(cap.get(1));
248 let minutes = cap_to_u64(cap.get(2));
249
250
251 return Duration::from_secs(hours*60*60 + minutes*60);
252 }
253
254 return Duration::from_millis(1)
255 }
256}
257
258impl AsRef<Verbindung> for Verbindung {
259 fn as_ref(&self) -> &Verbindung {
260 return self
261 }
262}
263
264#[cfg(test)]
265mod test {
266 use std::fs;
267
268 use std::time::Duration;
269 use crate::models::results::VerbindungenResults;
270 use crate::models::verbindung::Verbindung;
271
272 #[test]
273 fn test_verbindung_duration() {
274 let f = fs::read("./resources/test/verbindung-1.json")
275 .expect("File not found");
276
277 let vr : Verbindung = serde_json::from_str(
278 std::str::from_utf8(&f)
279 .expect("Unable to parse file into string"))
280 .expect("Unable to decode from JSON");
281
282 assert_eq!(Duration::from_secs(56 * 60), vr.duration())
283 }
284
285 #[test]
286 fn test_verbindung_duration_2() {
287 let f = fs::read("./resources/test/verbindung-2.json")
288 .expect("File not found");
289
290 let vr : Verbindung = serde_json::from_str(
291 std::str::from_utf8(&f)
292 .expect("Unable to parse file into string"))
293 .expect("Unable to decode from JSON");
294
295 assert_eq!(Duration::from_secs(1 * 60 * 60 + 5 * 60), vr.duration())
296 }
297
298 #[test]
299 fn test_decode_verbindung() {
300 let f = fs::read("./resources/test/verbindungen-6.json")
301 .expect("File not found");
302
303 let vr : VerbindungenResults = serde_json::from_str(
304 std::str::from_utf8(&f)
305 .expect("Unable to parse file into string"))
306 .expect("Unable to decode from JSON");
307
308 println!("Verbindungen = {:?}", vr)
309 }
310
311 #[test]
312 fn test_decode_verbindung_7() {
313 let f = fs::read("./resources/test/verbindungen-7.json")
314 .expect("File not found");
315
316 let vr : VerbindungenResults = serde_json::from_str(
317 std::str::from_utf8(&f)
318 .expect("Unable to parse file into string"))
319 .expect("Unable to decode from JSON");
320
321 println!("Verbindungen = {:?}", vr)
322 }
323}