1use chrono::{NaiveDateTime, NaiveTime};
2use serde::Deserialize;
3use std::{collections::HashMap, convert::TryFrom};
4
5use crate::{
6 deserialize::{
7 deserialize_api_error, deserialize_bool, deserialize_f64, deserialize_naive_date_time,
8 deserialize_naive_time, deserialize_naive_time_with_space,
9 deserialize_option_csv_encoded_string, deserialize_option_naive_time_with_space,
10 deserialize_optional_f64, deserialize_optional_string_enum, deserialize_string_enum,
11 },
12 types::{RegionalRailStop, RegionalRailsLine, ServiceType},
13};
14
15#[derive(Debug, Deserialize)]
16#[serde(untagged)]
17pub enum ApiResponse<T> {
18 Response(T),
19 #[serde(deserialize_with = "deserialize_api_error")]
20 Error(String),
21}
22
23pub type ArrivalsApiResponse = ApiResponse<ArrivalsResponse>;
24
25#[derive(Debug, Deserialize, Clone)]
26#[serde(try_from = "ArrivalsResponseBuilder")]
27pub struct ArrivalsResponse {
28 pub title: String,
29 pub northbound: Vec<Arrivals>,
30 pub southbound: Vec<Arrivals>,
31}
32
33#[derive(Debug, Deserialize)]
34struct ArrivalsResponseBuilder(HashMap<String, Vec<serde_json::Value>>);
35
36impl TryFrom<ArrivalsResponseBuilder> for ArrivalsResponse {
37 type Error = String;
38
39 fn try_from(builder: ArrivalsResponseBuilder) -> Result<Self, Self::Error> {
40 if builder.0.len() != 1 {
41 return Err(format!("expected 1 key, found {}", builder.0.len()));
42 }
43
44 let (title, values) = builder.0.into_iter().next().unwrap();
45
46 let mut northbound = None;
47 let mut southbound = None;
48
49 for value in values.into_iter() {
50 if let Ok(result) = serde_json::from_value::<Vec<serde_json::Value>>(value.clone()) {
53 match result.len() {
54 0 => continue,
55 _ => {
56 return Err("Unknown response".to_string());
57 }
58 }
59 }
60
61 let mut inner_values = serde_json::from_value::<HashMap<String, Vec<Arrivals>>>(value)
62 .map_err(|e| e.to_string())?;
63
64 if let Some(northbound_values) = inner_values.remove("Northbound") {
65 if northbound.is_some() {
66 return Err("Found two northbound key values".to_string());
67 }
68
69 northbound = Some(northbound_values);
70 }
71
72 if let Some(southbound_values) = inner_values.remove("Southbound") {
73 if southbound.is_some() {
74 return Err("Found two southbound key values".to_string());
75 }
76
77 southbound = Some(southbound_values);
78 }
79 }
80
81 Ok(ArrivalsResponse {
82 title,
83 northbound: northbound.unwrap_or(Vec::new()),
84 southbound: southbound.unwrap_or(Vec::new()),
85 })
86 }
87}
88
89#[derive(Debug, Deserialize, Clone)]
90pub struct Arrivals {
91 pub direction: String,
92 pub path: String,
93 pub train_id: String,
94
95 #[serde(deserialize_with = "deserialize_string_enum")]
96 pub origin: RegionalRailStop,
97
98 #[serde(deserialize_with = "deserialize_string_enum")]
99 pub destination: RegionalRailStop,
100
101 #[serde(deserialize_with = "deserialize_string_enum")]
102 pub line: RegionalRailsLine,
103 pub status: String,
104
105 #[serde(deserialize_with = "deserialize_string_enum")]
106 pub service_type: ServiceType,
107
108 #[serde(deserialize_with = "deserialize_optional_string_enum")]
109 pub next_station: Option<RegionalRailStop>,
110
111 #[serde(deserialize_with = "deserialize_naive_date_time")]
112 pub sched_time: NaiveDateTime,
113
114 #[serde(deserialize_with = "deserialize_naive_date_time")]
115 pub depart_time: NaiveDateTime,
116 pub track: String,
117 pub track_change: Option<String>,
118 pub platform: String,
119 pub platform_change: Option<String>,
120}
121
122pub type TrainApiResponse = ApiResponse<TrainResponse>;
123pub type TrainResponse = Vec<Train>;
124
125#[derive(Debug, Deserialize, Clone)]
126pub struct Train {
127 #[serde(deserialize_with = "deserialize_f64")]
128 pub lat: f64,
129
130 #[serde(deserialize_with = "deserialize_f64")]
131 pub lon: f64,
132
133 #[serde(rename = "trainno")]
134 pub train_number: String,
135
136 #[serde(deserialize_with = "deserialize_string_enum")]
137 pub service: ServiceType,
138
139 #[serde(deserialize_with = "deserialize_string_enum")]
140 pub dest: RegionalRailStop,
141
142 #[serde(rename = "currentstop", deserialize_with = "deserialize_string_enum")]
143 pub current_stop: RegionalRailStop,
144
145 #[serde(rename = "nextstop", deserialize_with = "deserialize_string_enum")]
146 pub next_stop: RegionalRailStop,
147
148 #[serde(deserialize_with = "deserialize_string_enum")]
149 pub line: RegionalRailsLine,
150
151 #[serde(deserialize_with = "deserialize_option_csv_encoded_string")]
152 pub consist: Option<Vec<i32>>,
153
154 #[serde(deserialize_with = "deserialize_optional_f64")]
155 pub heading: Option<f64>,
156
157 pub late: i32,
158
159 #[serde(rename = "SOURCE", deserialize_with = "deserialize_string_enum")]
160 pub source: RegionalRailStop,
161
162 #[serde(rename = "TRACK")]
163 pub track: String,
164
165 #[serde(rename = "TRACK_CHANGE")]
166 pub track_change: String,
167}
168
169pub type NextToArriveApiResponse = ApiResponse<NextToArriveResponse>;
170pub type NextToArriveResponse = Vec<NextToArrive>;
171
172#[derive(Debug, Deserialize, Clone)]
173pub struct NextToArrive {
174 pub orig_train: String,
175
176 #[serde(deserialize_with = "deserialize_string_enum")]
177 pub orig_line: RegionalRailsLine,
178
179 #[serde(deserialize_with = "deserialize_naive_time")]
180 pub orig_departure_time: NaiveTime,
181
182 #[serde(deserialize_with = "deserialize_naive_time")]
183 pub orig_arrival_time: NaiveTime,
184
185 pub orig_delay: String,
186
187 #[serde(rename = "isdirect", deserialize_with = "deserialize_bool")]
188 pub is_direct: bool,
189}
190
191pub type RailScheduleApiResponse = ApiResponse<RailScheduleResponse>;
192pub type RailScheduleResponse = Vec<RailSchedule>;
193
194#[derive(Debug, Deserialize, Clone)]
195pub struct RailSchedule {
196 #[serde(deserialize_with = "deserialize_string_enum")]
197 pub station: RegionalRailStop,
198
199 #[serde(
200 rename = "sched_tm",
201 deserialize_with = "deserialize_naive_time_with_space"
202 )]
203 pub scheduled_time: NaiveTime,
204
205 #[serde(
206 rename = "est_tm",
207 deserialize_with = "deserialize_naive_time_with_space"
208 )]
209 pub estimated_time: NaiveTime,
210
211 #[serde(
212 rename = "act_tm",
213 deserialize_with = "deserialize_option_naive_time_with_space"
214 )]
215 pub actual_time: Option<NaiveTime>,
216}