Skip to content

Commit 043e07c

Browse files
committed
Add handling for unexpected array of stops
This closes issue #10.
1 parent 98e6216 commit 043e07c

File tree

3 files changed

+349
-5
lines changed

3 files changed

+349
-5
lines changed

src/commands/times.rs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,15 @@ pub async fn handle_times_request(
4040

4141
let parsed_response = serde_json::from_str::<StopScheduleResponse>(&api_response_text).unwrap();
4242

43-
let mut response_text = format!(
44-
"{} {}\n",
45-
parsed_response.stop_schedule.stop.number, parsed_response.stop_schedule.stop.name
46-
);
43+
let mut response_text = match &parsed_response.stop_schedule.stop_data {
44+
StopData::Single { stop } => {
45+
format!("{} {}\n", stop.number, stop.name)
46+
}
47+
StopData::Multiple { stop } => {
48+
let stop = &stop[0];
49+
format!("{} {}\n", stop.number, stop.name)
50+
}
51+
};
4752

4853
let mut schedule_lines: Vec<(NaiveDateTime, String)> = Vec::new();
4954

@@ -143,10 +148,19 @@ struct StopScheduleResponse {
143148
#[derive(Deserialize)]
144149
#[serde(rename_all = "kebab-case")]
145150
pub struct StopSchedule {
146-
stop: Stop,
151+
#[serde(flatten)]
152+
stop_data: StopData,
147153
route_schedules: Vec<RouteSchedule>,
148154
}
149155

156+
// Issue #10, the API doesn’t say this request can return multiple stops, but it did.
157+
#[derive(Deserialize)]
158+
#[serde(untagged)]
159+
enum StopData {
160+
Single { stop: Stop },
161+
Multiple { stop: Vec<Stop> },
162+
}
163+
150164
#[derive(Deserialize)]
151165
pub struct Stop {
152166
name: String,
Lines changed: 286 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,286 @@
1+
{
2+
"stop-schedule": {
3+
"stop": [
4+
{
5+
"key": 10620,
6+
"name": "WB St Mary@Fort",
7+
"number": 10620,
8+
"direction": "Westbound",
9+
"side": "Nearside",
10+
"street": {
11+
"key": 3452,
12+
"name": "St MaryAve",
13+
"type": "Avenue"
14+
},
15+
"cross-street": {
16+
"key": 1365,
17+
"name": "FortSt",
18+
"type": "Street"
19+
},
20+
"centre": {
21+
"utm": {
22+
"zone": "14U",
23+
"x": 633773,
24+
"y": 5528255
25+
},
26+
"geographic": {
27+
"latitude": "49.89172",
28+
"longitude": "-97.13753"
29+
}
30+
}
31+
},
32+
{
33+
"key": 10620,
34+
"name": "WB St Mary@Fort",
35+
"number": 10620,
36+
"direction": "Westbound",
37+
"side": "Nearside",
38+
"street": {
39+
"key": 3452,
40+
"name": "St MaryAve",
41+
"type": "Avenue"
42+
},
43+
"cross-street": {
44+
"key": 1365,
45+
"name": "FortSt",
46+
"type": "Street"
47+
},
48+
"centre": {
49+
"utm": {
50+
"zone": "14U",
51+
"x": 633773,
52+
"y": 5528255
53+
},
54+
"geographic": {
55+
"latitude": "49.89172",
56+
"longitude": "-97.13753"
57+
}
58+
}
59+
}
60+
],
61+
"route-schedules": [
62+
{
63+
"route": {
64+
"key": 55,
65+
"number": 55,
66+
"name": "St. Anne's",
67+
"customer-type": "regular",
68+
"coverage": "regular",
69+
"badge-label": 55,
70+
"badge-style": {
71+
"class-names": {
72+
"class-name": ["badge-label", "regular"]
73+
},
74+
"background-color": "#ffffff",
75+
"border-color": "#d9d9d9",
76+
"color": "#000000"
77+
}
78+
},
79+
"scheduled-stops": [
80+
{
81+
"key": "27998793-43",
82+
"cancelled": "false",
83+
"times": {
84+
"arrival": {
85+
"scheduled": "2024-12-15T00:54:22",
86+
"estimated": "2024-12-15T00:58:31"
87+
},
88+
"departure": {
89+
"scheduled": "2024-12-15T00:54:22",
90+
"estimated": "2024-12-15T00:58:31"
91+
}
92+
},
93+
"variant": {
94+
"key": "55-1-D",
95+
"name": "UofW via Dakota"
96+
},
97+
"bus": {
98+
"key": 331,
99+
"bike-rack": "false",
100+
"wifi": "false"
101+
}
102+
},
103+
{
104+
"key": "27998768-54",
105+
"cancelled": "false",
106+
"times": {
107+
"arrival": {
108+
"scheduled": "2024-12-15T01:19:22",
109+
"estimated": "2024-12-15T01:19:22"
110+
},
111+
"departure": {
112+
"scheduled": "2024-12-15T01:19:22",
113+
"estimated": "2024-12-15T01:19:22"
114+
}
115+
},
116+
"variant": {
117+
"key": "55-1-M",
118+
"name": "UofW via Meadow'd"
119+
},
120+
"bus": {
121+
"key": 811,
122+
"bike-rack": "false",
123+
"wifi": "false"
124+
}
125+
}
126+
]
127+
},
128+
{
129+
"route": {
130+
"key": 14,
131+
"number": 14,
132+
"name": "Ellice-St. Mary's",
133+
"customer-type": "regular",
134+
"coverage": "regular",
135+
"badge-label": 14,
136+
"badge-style": {
137+
"class-names": {
138+
"class-name": ["badge-label", "regular"]
139+
},
140+
"background-color": "#ffffff",
141+
"border-color": "#d9d9d9",
142+
"color": "#000000"
143+
}
144+
},
145+
"scheduled-stops": [
146+
{
147+
"key": "27997493-58",
148+
"cancelled": "false",
149+
"times": {
150+
"arrival": {
151+
"scheduled": "2024-12-15T00:47:05",
152+
"estimated": "2024-12-15T00:50:25"
153+
},
154+
"departure": {
155+
"scheduled": "2024-12-15T00:47:05",
156+
"estimated": "2024-12-15T00:50:25"
157+
}
158+
},
159+
"variant": {
160+
"key": "14-1-E",
161+
"name": "Ferry Rd"
162+
},
163+
"bus": {
164+
"key": 343,
165+
"bike-rack": "false",
166+
"wifi": "false"
167+
}
168+
},
169+
{
170+
"key": "27997494-59",
171+
"cancelled": "false",
172+
"times": {
173+
"arrival": {
174+
"scheduled": "2024-12-15T01:11:05",
175+
"estimated": "2024-12-15T01:11:05"
176+
},
177+
"departure": {
178+
"scheduled": "2024-12-15T01:11:05",
179+
"estimated": "2024-12-15T01:11:05"
180+
}
181+
},
182+
"variant": {
183+
"key": "14-1-E",
184+
"name": "Ferry Rd"
185+
},
186+
"bus": {
187+
"key": 420,
188+
"bike-rack": "false",
189+
"wifi": "false"
190+
}
191+
},
192+
{
193+
"key": "27997495-58",
194+
"cancelled": "false",
195+
"times": {
196+
"arrival": {
197+
"scheduled": "2024-12-15T01:36:05",
198+
"estimated": "2024-12-15T01:36:05"
199+
},
200+
"departure": {
201+
"scheduled": "2024-12-15T01:36:05",
202+
"estimated": "2024-12-15T01:36:05"
203+
}
204+
},
205+
"variant": {
206+
"key": "14-1-E",
207+
"name": "Ferry Rd"
208+
},
209+
"bus": {
210+
"key": 808,
211+
"bike-rack": "false",
212+
"wifi": "false"
213+
}
214+
}
215+
]
216+
},
217+
{
218+
"route": {
219+
"key": 19,
220+
"number": 19,
221+
"name": "Marion-Logan-Notre Dame",
222+
"customer-type": "regular",
223+
"coverage": "regular",
224+
"badge-label": 19,
225+
"badge-style": {
226+
"class-names": {
227+
"class-name": ["badge-label", "regular"]
228+
},
229+
"background-color": "#ffffff",
230+
"border-color": "#d9d9d9",
231+
"color": "#000000"
232+
}
233+
},
234+
"scheduled-stops": [
235+
{
236+
"key": "27998030-33",
237+
"cancelled": "false",
238+
"times": {
239+
"arrival": {
240+
"scheduled": "2024-12-15T01:10:09",
241+
"estimated": "2024-12-15T01:10:09"
242+
},
243+
"departure": {
244+
"scheduled": "2024-12-15T01:10:09",
245+
"estimated": "2024-12-15T01:10:09"
246+
}
247+
},
248+
"variant": {
249+
"key": "19-0-L",
250+
"name": "Via Logan"
251+
},
252+
"bus": {
253+
"key": 452,
254+
"bike-rack": "false",
255+
"wifi": "false"
256+
}
257+
},
258+
{
259+
"key": "27998028-40",
260+
"cancelled": "false",
261+
"times": {
262+
"arrival": {
263+
"scheduled": "2024-12-15T01:36:09",
264+
"estimated": "2024-12-15T01:36:09"
265+
},
266+
"departure": {
267+
"scheduled": "2024-12-15T01:36:09",
268+
"estimated": "2024-12-15T01:36:09"
269+
}
270+
},
271+
"variant": {
272+
"key": "19-0-N",
273+
"name": "Via Notre Dame"
274+
},
275+
"bus": {
276+
"key": 455,
277+
"bike-rack": "false",
278+
"wifi": "false"
279+
}
280+
}
281+
]
282+
}
283+
]
284+
},
285+
"query-time": "2024-12-15T00:46:41"
286+
}

tests/times.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,3 +330,47 @@ async fn stop_number_returns_stop_schedule_via_raw_endpoint(db: PgPool) {
330330
"/v3/stops/10619/schedule.json?usage=short"
331331
);
332332
}
333+
334+
#[sqlx::test(fixtures("numbers-approved"))]
335+
async fn stop_number_returns_stop_schedule_issue_10(db: PgPool) {
336+
let mock_winnipeg_transit_api = MockServer::start().await;
337+
let mock_stop_schedule_response =
338+
fs::read_to_string("tests/fixtures/times/stop_schedule_issue_10.json")
339+
.expect("Failed to read stop schedule fixture");
340+
341+
Mock::given(method("GET"))
342+
.and(path_regex(r"^/v3/stops/.*/schedule.json$"))
343+
.respond_with(
344+
ResponseTemplate::new(200).set_body_string(mock_stop_schedule_response.clone()),
345+
)
346+
.expect(1)
347+
.mount(&mock_winnipeg_transit_api)
348+
.await;
349+
350+
let response = get(
351+
"/twilio?Body= 10620&From=approved&To=textabus&MessageSid=SM1849",
352+
InjectableServices {
353+
db: db.clone(),
354+
twilio_address: None,
355+
winnipeg_transit_api_address: Some(mock_winnipeg_transit_api.uri()),
356+
},
357+
)
358+
.await
359+
.expect("Failed to execute request");
360+
361+
assert!(response.status().is_success());
362+
assert_eq!(response.headers()["content-type"], "text/xml");
363+
364+
let document = Document::from(response.text().await.unwrap().as_str());
365+
let body = &document.find(Name("body")).next().unwrap().text();
366+
367+
let expected_body = indoc! {"
368+
10620 WB St Mary@Fort
369+
12:50a 14 Ferry Rd (3min late)
370+
12:58a 55 UofW via Dakota (4min late)
371+
1:10a 19 Via Logan
372+
1:11a 14 Ferry Rd
373+
"};
374+
375+
assert_that(body).contains(expected_body);
376+
}

0 commit comments

Comments
 (0)