italo_api/journey/
mod.rs

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/// Input object for [crate::ItaloApi::find_journeys]
42#[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    /// Set the number of adults
59    adult_number: u8,
60
61    /// Set the number of children
62    child_number: u8,
63
64    /// Set the number of infants
65    infant_number: u8,
66
67    /// Set the number of seniors
68    senior_number: u8,
69
70    /// Ignore interval dateTime values
71    override_interval_time_restriction: bool,
72
73    /// Currency for the amount. Default is EUR.
74    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    /// Set the departure station for the journey search
114    pub fn set_departure_station(&mut self, val: Station) -> &mut Self {
115        self.departure_station = val.code().to_owned();
116        self
117    }
118
119    /// Set the arrival station for the journey search
120    pub fn set_arrival_station(&mut self, val: Station) -> &mut Self {
121        self.arrival_station = val.code().to_owned();
122        self
123    }
124
125    /// Set the DateTime from which to start the search for journeys
126    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    /// Set the DateTime limit for the search for journeys
132    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    /// Set data to search for round trip solutions
138    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/// Output object for [crate::ItaloApi::find_journeys]
163#[derive(Deserialize, Debug, Getters)]
164#[serde(rename_all = "PascalCase")]
165#[get = "pub"]
166pub struct JourneyResults {
167    /// Array of alternative solutions
168    #[serde(rename(deserialize = "JourneyDateMarkets"))]
169    solutions: Vec<JourneysSolution>,
170}
171
172/// Array of journeys for the date specified by [JourneysSolution::departure_date]
173#[derive(Deserialize, Debug, Getters)]
174#[serde(rename_all = "PascalCase")]
175#[get = "pub"]
176pub struct JourneysSolution {
177    #[getset(skip)]
178    departure_date: String,
179
180    /// Array of journeys for the specified date
181    journeys: Vec<Journey>,
182}
183
184impl JourneysSolution {
185    /// Date on which the journeys are valid
186    pub fn departure_date(&self) -> anyhow::Result<NaiveDate> {
187        //Something is wrong on italo side
188        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/// Describes a journey using one or more trains
206#[derive(Deserialize, Debug, Getters)]
207#[serde(rename_all = "PascalCase")]
208#[get = "pub"]
209pub struct Journey {
210    /// Different parts by which the journey has been divided
211    segments: Vec<JourneySegment>,
212}
213
214/// Single train journey
215#[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    /// Italo train ID
228    train_number: String,
229
230    /// Describes the train as direct
231    no_stop_train: bool,
232
233    /// Train Stops
234    #[serde(rename(deserialize = "Legs"))]
235    stops: Vec<Stop>,
236}
237
238impl JourneySegment {
239    /// Departure time
240    pub fn departure_time(&self) -> anyhow::Result<DateTime<Utc>> {
241        extract_utc_time(&self.departure_time)
242    }
243
244    /// Arrival time
245    pub fn arrival_time(&self) -> anyhow::Result<DateTime<Utc>> {
246        extract_utc_time(&self.arrival_time)
247    }
248}
249
250/// Train stop
251#[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
264    departure_station: String,
265
266    /// Arrival station
267    arrival_station: String,
268}
269
270impl Stop {
271    /// Departure time
272    pub fn departure_time(&self) -> anyhow::Result<DateTime<Utc>> {
273        extract_utc_time(&self.departure_time)
274    }
275    /// Arrival time
276    pub fn arrival_time(&self) -> anyhow::Result<DateTime<Utc>> {
277        extract_utc_time(&self.arrival_time)
278    }
279}