sbb_api/models/
verbindung.rs

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}