septa_api/
responses.rs

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            // SEPTA's API is inconsistent and will return an empty array when the are
51            // no results.
52            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}