1use crate::Station;
2use anyhow::anyhow;
3use anyhow::Context;
4use anyhow::Ok;
5use chrono::NaiveDate;
6use chrono::NaiveDateTime;
7use chrono::{DateTime, Utc};
8use derive_new::new;
9use getset::Getters;
10use getset::Setters;
11use serde::Deserialize;
12use serde::Serialize;
13
14static DATE_TIME_PATTERN: &str = "/Date(%s000+0000)/";
15pub type RoundTrip = (bool, Option<DateTime<Utc>>, Option<DateTime<Utc>>);
16
17fn extract_utc_time(val: &str) -> anyhow::Result<DateTime<Utc>> {
18 DateTime::from_timestamp(
19 val.split_once('(')
20 .context("Failed to extract dateTime")?
21 .1
22 .split_once('+')
23 .context("Failed to extract dateTime")?
24 .0
25 .parse::<i64>()
26 .expect("Timestamp conversion failed")
27 / 1000,
28 0,
29 )
30 .context("invalid timestamp")
31}
32
33#[derive(Serialize, Debug, new)]
34#[serde(rename_all = "PascalCase")]
35pub struct InternalJourneyRequest<'a> {
36 signature: &'a str,
37 source_system: u8,
38 get_available_trains: &'a JourneyRequest,
39}
40
41#[derive(Serialize, Debug, Setters)]
43#[serde(rename_all = "PascalCase")]
44#[set = "pub"]
45pub struct JourneyRequest {
46 #[getset(skip)]
47 departure_station: String,
48
49 #[getset(skip)]
50 arrival_station: String,
51
52 #[getset(skip)]
53 interval_start_date_time: String,
54
55 #[getset(skip)]
56 interval_end_date_time: String,
57
58 adult_number: u8,
60
61 child_number: u8,
63
64 infant_number: u8,
66
67 senior_number: u8,
69
70 override_interval_time_restriction: bool,
72
73 currency_code: String,
75
76 #[getset(skip)]
77 is_guest: bool,
78
79 #[getset(skip)]
80 round_trip: bool,
81
82 #[getset(skip)]
83 #[serde(skip_serializing_if = "Option::is_none")]
84 round_trip_interval_start_date_time: Option<String>,
85
86 #[getset(skip)]
87 #[serde(skip_serializing_if = "Option::is_none")]
88 round_trip_interval_end_date_time: Option<String>,
89}
90
91impl Default for JourneyRequest {
92 fn default() -> Self {
93 Self {
94 departure_station: Default::default(),
95 arrival_station: Default::default(),
96 interval_start_date_time: Default::default(),
97 interval_end_date_time: Default::default(),
98 adult_number: 1,
99 child_number: 0,
100 infant_number: 0,
101 senior_number: 0,
102 override_interval_time_restriction: false,
103 currency_code: "EUR".to_owned(),
104 is_guest: true,
105 round_trip: false,
106 round_trip_interval_start_date_time: Default::default(),
107 round_trip_interval_end_date_time: Default::default(),
108 }
109 }
110}
111
112impl JourneyRequest {
113 pub fn set_departure_station(&mut self, val: Station) -> &mut Self {
115 self.departure_station = val.code().to_owned();
116 self
117 }
118
119 pub fn set_arrival_station(&mut self, val: Station) -> &mut Self {
121 self.arrival_station = val.code().to_owned();
122 self
123 }
124
125 pub fn set_interval_start_date_time(&mut self, val: DateTime<Utc>) -> &mut Self {
127 self.interval_start_date_time = val.format(DATE_TIME_PATTERN).to_string();
128 self
129 }
130
131 pub fn set_interval_end_date_time(&mut self, val: DateTime<Utc>) -> &mut Self {
133 self.interval_end_date_time = val.format(DATE_TIME_PATTERN).to_string();
134 self
135 }
136
137 pub fn set_round_trip(&mut self, val: RoundTrip) -> anyhow::Result<&mut Self> {
139 match val {
140 (false, _, _) => {
141 self.round_trip = false;
142 self.round_trip_interval_start_date_time = None;
143 self.round_trip_interval_end_date_time = None;
144 Ok(self)
145 }
146 (true, Some(start), Some(end)) => {
147 self.round_trip = true;
148 self.round_trip_interval_start_date_time =
149 Some(start.format(DATE_TIME_PATTERN).to_string());
150 self.round_trip_interval_end_date_time =
151 Some(end.format(DATE_TIME_PATTERN).to_string());
152 Ok(self)
153 }
154 (true, _, _) => Err(anyhow!(
155 "Round trip requires both valued date_time, got {:?}",
156 val
157 )),
158 }
159 }
160}
161
162#[derive(Deserialize, Debug, Getters)]
164#[serde(rename_all = "PascalCase")]
165#[get = "pub"]
166pub struct JourneyResults {
167 #[serde(rename(deserialize = "JourneyDateMarkets"))]
169 solutions: Vec<JourneysSolution>,
170}
171
172#[derive(Deserialize, Debug, Getters)]
174#[serde(rename_all = "PascalCase")]
175#[get = "pub"]
176pub struct JourneysSolution {
177 #[getset(skip)]
178 departure_date: String,
179
180 journeys: Vec<Journey>,
182}
183
184impl JourneysSolution {
185 pub fn departure_date(&self) -> anyhow::Result<NaiveDate> {
187 Ok(NaiveDate::from(
189 NaiveDateTime::from_timestamp_millis(
190 self.departure_date
191 .split_once('(')
192 .context("Failed to extract date")?
193 .1
194 .split_once('+')
195 .context("Failed to extract date")?
196 .0
197 .parse()
198 .context("Failed to parse timestamp")?,
199 )
200 .context("Failed to parse Date")?,
201 ))
202 }
203}
204
205#[derive(Deserialize, Debug, Getters)]
207#[serde(rename_all = "PascalCase")]
208#[get = "pub"]
209pub struct Journey {
210 segments: Vec<JourneySegment>,
212}
213
214#[derive(Deserialize, Debug, Getters)]
216#[serde(rename_all = "PascalCase")]
217#[get = "pub"]
218pub struct JourneySegment {
219 #[serde(rename(deserialize = "STD"))]
220 #[getset(skip)]
221 departure_time: String,
222
223 #[serde(rename(deserialize = "STA"))]
224 #[getset(skip)]
225 arrival_time: String,
226
227 train_number: String,
229
230 no_stop_train: bool,
232
233 #[serde(rename(deserialize = "Legs"))]
235 stops: Vec<Stop>,
236}
237
238impl JourneySegment {
239 pub fn departure_time(&self) -> anyhow::Result<DateTime<Utc>> {
241 extract_utc_time(&self.departure_time)
242 }
243
244 pub fn arrival_time(&self) -> anyhow::Result<DateTime<Utc>> {
246 extract_utc_time(&self.arrival_time)
247 }
248}
249
250#[derive(Deserialize, Debug, Getters)]
252#[serde(rename_all = "PascalCase")]
253#[get = "pub"]
254pub struct Stop {
255 #[serde(rename(deserialize = "STD"))]
256 #[getset(skip)]
257 departure_time: String,
258
259 #[serde(rename(deserialize = "STA"))]
260 #[getset(skip)]
261 arrival_time: String,
262
263 departure_station: String,
265
266 arrival_station: String,
268}
269
270impl Stop {
271 pub fn departure_time(&self) -> anyhow::Result<DateTime<Utc>> {
273 extract_utc_time(&self.departure_time)
274 }
275 pub fn arrival_time(&self) -> anyhow::Result<DateTime<Utc>> {
277 extract_utc_time(&self.arrival_time)
278 }
279}