Skip to content

Commit 32d0077

Browse files
committed
feat: more useful error messages
1 parent cd3c833 commit 32d0077

File tree

5 files changed

+169
-12
lines changed

5 files changed

+169
-12
lines changed

src/data/stop-ids.ts

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
export const stopIDs = [
2+
"U1040Z101P",
3+
"U1040Z102P",
4+
"U157Z101P",
5+
"U157Z102P",
6+
"U50Z101P",
7+
"U50Z102P",
8+
"U897Z101P",
9+
"U510Z101P",
10+
"U510Z102P",
11+
"U321Z101P",
12+
"U321Z102P",
13+
"U1071Z101P",
14+
"U1071Z102P",
15+
"U118Z101P",
16+
"U118Z102P",
17+
"U689Z101P",
18+
"U689Z102P",
19+
"U689Z121P",
20+
"U689Z122P",
21+
"U286Z101P",
22+
"U142Z101P",
23+
"U142Z102P",
24+
"U135Z101P",
25+
"U135Z102P",
26+
"U163Z101P",
27+
"U163Z102P",
28+
"U1154Z101P",
29+
"U1154Z102P",
30+
"U52Z102P",
31+
"U52Z101P",
32+
"U190Z101P",
33+
"U190Z102P",
34+
"U655Z101P",
35+
"U655Z102P",
36+
"U685Z101P",
37+
"U685Z102P",
38+
"U209Z101P",
39+
"U209Z102P",
40+
"U228Z101P",
41+
"U228Z102P",
42+
"U237Z101P",
43+
"U237Z102P",
44+
"U675Z101P",
45+
"U675Z102P",
46+
"U75Z101P",
47+
"U75Z102P",
48+
"U758Z101P",
49+
"U758Z102P",
50+
"U78Z101P",
51+
"U78Z102P",
52+
"U1000Z102P",
53+
"U1007Z101P",
54+
"U1007Z102P",
55+
"U258Z101P",
56+
"U258Z102P",
57+
"U360Z101P",
58+
"U360Z102P",
59+
"U1072Z101P",
60+
"U1072Z102P",
61+
"U1072Z121P",
62+
"U1072Z122P",
63+
"U400Z101P",
64+
"U400Z102P",
65+
"U400Z121P",
66+
"U400Z122P",
67+
"U115Z101P",
68+
"U115Z102P",
69+
"U462Z101P",
70+
"U462Z102P",
71+
"U476Z101P",
72+
"U476Z102P",
73+
"U480Z101P",
74+
"U480Z102P",
75+
"U539Z101P",
76+
"U539Z102P",
77+
"U306Z101P",
78+
"U602Z101P",
79+
"U602Z102P",
80+
"U106Z101P",
81+
"U106Z102P",
82+
"U529Z101P",
83+
"U529Z102P",
84+
"U385Z101P",
85+
"U385Z102P",
86+
"U507Z101P",
87+
"U507Z102P",
88+
"U597Z101P",
89+
"U597Z102P",
90+
"U603Z101P",
91+
"U603Z102P",
92+
"U957Z101P",
93+
"U957Z102P",
94+
"U818Z101P",
95+
"U818Z102P",
96+
"U601Z101P",
97+
"U601Z102P",
98+
"U953Z101P",
99+
"U953Z102P",
100+
"U458Z101P",
101+
"U458Z102P",
102+
"U703Z101P",
103+
"U703Z102P",
104+
"U1140Z101P",
105+
"U1140Z102P",
106+
"U713Z101P",
107+
"U713Z102P",
108+
"U332Z101P",
109+
"U332Z102P",
110+
"U100Z101P",
111+
"U100Z102P",
112+
"U474Z101P",
113+
"U474Z102P",
114+
"U527Z101P",
115+
"U527Z102P",
116+
"U1141Z102P",
117+
"U921Z101P",
118+
"U921Z102P",
119+
] as const satisfies `U${number}Z${number}P`[];

src/fetch-metro/fetch-metro.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@ export const fetchApiData = async (stopIDs: string[]) => {
2222
}
2323

2424
const body = await res.json();
25-
const parsed = ApiResponseSchema.parse(body);
25+
const parsed = ApiResponseSchema.safeParse(body);
26+
if (!parsed.success) {
27+
console.error("Golemio API response:", body);
28+
throw new Error("Golemio API response is in unexpected format");
29+
}
2630

27-
return parsed;
31+
return parsed.data;
2832
};

src/schemas.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { z } from "zod";
22
import { MetroLine } from "./types";
3+
import { stopIDs } from "./data/stop-ids";
34

4-
export const StopIDsSchema = z.array(z.string()).nonempty().max(10);
5+
export const StopIDsSchema = z.array(z.enum(stopIDs)).nonempty().max(10);
56

67
export const SubscribeSchema = z.object({
78
subscribe: StopIDsSchema,

src/server.ts

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import { group } from "radash";
77
import { STOP_IDS_HEADER, INTERVAL } from "./server/server.const";
88
import { fetchApiData } from "./fetch-metro/fetch-metro";
99
import { StopIDsSchema, SubscribeSchema, type ApiResponse } from "./schemas";
10-
import { getParsedDeparture } from "./server/server.utils";
10+
import { getErrorResponse, getParsedDeparture } from "./server/server.utils";
1111

1212
if (!process.env.GOLEMIO_API_KEY) {
13-
throw new Error("GOLEMIO_API_KEY is not set");
13+
throw new Error("GOLEMIO_API_KEY is not set in .env");
1414
}
1515

1616
let intervalId: number | null = null;
@@ -99,17 +99,27 @@ const fetchData = async (clientID?: string) => {
9999
const server = Bun.serve<ClientData>({
100100
fetch(req, server) {
101101
const stopIDsHeaderRaw = req.headers.get(STOP_IDS_HEADER);
102-
const StopIDsHeaderParsed = JSON.parse(stopIDsHeaderRaw ?? "");
103-
const res = StopIDsSchema.safeParse(StopIDsHeaderParsed);
102+
if (!stopIDsHeaderRaw)
103+
return getErrorResponse(`"${STOP_IDS_HEADER}" header is missing`);
104+
105+
let StopIDsHeaderParsed: unknown;
106+
try {
107+
StopIDsHeaderParsed = JSON.parse(stopIDsHeaderRaw);
108+
} catch (error) {
109+
return getErrorResponse(`"${STOP_IDS_HEADER}" header ${error}`);
110+
}
104111

105-
if (!res.success) return new Response("Invalid request", { status: 400 });
112+
const res = StopIDsSchema.safeParse(StopIDsHeaderParsed);
113+
if (!res.success)
114+
return getErrorResponse(
115+
`"${STOP_IDS_HEADER}" error: ${res.error.errors[0].message}`
116+
);
106117

107118
const clientID = uuid();
108119
subscribedStopIDsByClientID.set(clientID, res.data);
109120
const success = server.upgrade(req, { data: { clientID } });
110121

111-
if (!success)
112-
return new Response("Failed to upgrade connection", { status: 500 });
122+
if (!success) return getErrorResponse("Failed to upgrade connection");
113123
},
114124
websocket: {
115125
open(ws) {
@@ -125,8 +135,24 @@ const server = Bun.serve<ClientData>({
125135
intervalId = intervalObj[Symbol.toPrimitive]();
126136
},
127137
message(ws, message) {
128-
const res = SubscribeSchema.safeParse(message);
129-
if (!res.success) return;
138+
if (typeof message !== "string") {
139+
ws.close(1011, "Message has to be string");
140+
return;
141+
}
142+
143+
let StopIDsHeaderParsed: unknown;
144+
try {
145+
StopIDsHeaderParsed = JSON.parse(message);
146+
} catch (error) {
147+
ws.close(1011, String(error));
148+
return;
149+
}
150+
151+
const res = SubscribeSchema.safeParse(StopIDsHeaderParsed);
152+
if (!res.success) {
153+
ws.close(1011, res.error.errors[0].message);
154+
return;
155+
}
130156

131157
subscribedStopIDsByClientID.set(ws.data.clientID, res.data.subscribe);
132158
},

src/server/server.utils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import type { Departure } from "../types";
22
import type { ApiResponse } from "../schemas";
33

4+
export const getErrorResponse = (message: string): Response => {
5+
return new Response(message, {
6+
status: 500,
7+
headers: [["error", message]], // Postman doesn't show response body when testing websocket
8+
});
9+
};
10+
411
export const getParsedDeparture = (
512
departure: ApiResponse["departures"][0]
613
): Departure => {

0 commit comments

Comments
 (0)