Skip to content

Commit 3b3a045

Browse files
committed
refactor(backend): return 404 instead 400 status when postcode fails to resolve to location
1 parent 12903fb commit 3b3a045

File tree

7 files changed

+80
-32
lines changed

7 files changed

+80
-32
lines changed

backend/api.d.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,15 @@ export interface paths {
274274
"application/json": components["schemas"]["Error"];
275275
};
276276
};
277+
/** @description No location found for one or more addresses. */
278+
404: {
279+
headers: {
280+
[name: string]: unknown;
281+
};
282+
content: {
283+
"application/json": components["schemas"]["Error"];
284+
};
285+
};
277286
/** @description Internal server error */
278287
500: {
279288
headers: {
@@ -323,7 +332,7 @@ export interface paths {
323332
"application/json": components["schemas"]["Location"];
324333
};
325334
};
326-
/** @description Invalid postcode. */
335+
/** @description Invalid request parameters. */
327336
400: {
328337
headers: {
329338
[name: string]: unknown;
@@ -332,6 +341,15 @@ export interface paths {
332341
"application/json": components["schemas"]["Error"];
333342
};
334343
};
344+
/** @description No Location for postcode found. */
345+
404: {
346+
headers: {
347+
[name: string]: unknown;
348+
};
349+
content: {
350+
"application/json": components["schemas"]["Error"];
351+
};
352+
};
335353
/** @description Internal server error */
336354
500: {
337355
headers: {

backend/error.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,27 @@ export class ValidationError extends Error {
2727
this.status = 400;
2828
}
2929
}
30+
31+
/**
32+
* Represents an error that occurs when a requested resource is not found.
33+
* Extends the standard JavaScript `Error` class with an additional `status` property, indicating
34+
* an HTTP status code for "Not Found".
35+
*
36+
* @property {number} status - HTTP status code associated with the error, set to 404 (Not Found).
37+
*
38+
* @constructor
39+
* @param {string} message - A descriptive message explaining the missing resource error.
40+
*
41+
* @example
42+
* throw new NotFoundError("Resource not found");
43+
*/
44+
export class NotFoundError extends Error {
45+
status: number;
46+
47+
constructor(message: string) {
48+
super(message);
49+
this.name = "NotFoundError";
50+
// not found status code
51+
this.status = 404;
52+
}
53+
}

backend/index.ts

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Request, Response } from "express";
1010
import * as OpenApiValidator from "express-openapi-validator";
1111
import { ROUTING_API, RoutingSvc } from "./services/routing";
1212
import { paths } from "./api";
13+
import { ValidationError } from "./error";
1314

1415
type GeoLocation = {
1516
latitude: number;
@@ -49,37 +50,31 @@ app.post("/route", async (req: Request, res: Response) => {
4950
const r =
5051
req.body as paths["/route"]["post"]["requestBody"]["content"]["application/json"];
5152

52-
try {
53-
let srcLocation: GeoLocation;
54-
let destLocation: GeoLocation;
53+
let srcLocation: GeoLocation;
54+
let destLocation: GeoLocation;
5555

56-
if (r.src.kind === "address") {
57-
const postcode = r.src.address?.postcode?.trim();
58-
if (!postcode) {
59-
throw new Error("Source address must include a postcode.");
60-
}
61-
srcLocation = await routing_service.geolookup(postcode);
62-
} else {
63-
srcLocation = r.src.location!;
56+
if (r.src.kind === "address") {
57+
const postcode = r.src.address?.postcode?.trim();
58+
if (!postcode) {
59+
throw new ValidationError("Source address must include a postcode.");
6460
}
61+
srcLocation = await routing_service.geolookup(postcode);
62+
} else {
63+
srcLocation = r.src.location!;
64+
}
6565

66-
if (r.dest.kind === "address") {
67-
const postcode = r.dest.address?.postcode?.trim();
68-
if (!postcode) {
69-
throw new Error("Destination address must include a postcode.");
70-
}
71-
destLocation = await routing_service.geolookup(postcode);
72-
} else {
73-
destLocation = r.dest.location!;
66+
if (r.dest.kind === "address") {
67+
const postcode = r.dest.address?.postcode?.trim();
68+
if (!postcode) {
69+
throw new ValidationError("Destination address must include a postcode.");
7470
}
75-
76-
const routes = await routing_service.route(srcLocation, destLocation);
77-
res.json({ routes });
78-
} catch (error) {
79-
console.error(error);
80-
const err = error as Error;
81-
res.status(500).json({ message: err.message });
71+
destLocation = await routing_service.geolookup(postcode);
72+
} else {
73+
destLocation = r.dest.location!;
8274
}
75+
76+
const routes = await routing_service.route(srcLocation, destLocation);
77+
res.json({ routes });
8378
});
8479

8580
app.get("/geocode/:postcode", async (req: Request, res: Response) => {

backend/services/routing.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import * as path from "path";
99
import csv from "csv-parser";
1010
import { components, paths } from "../api";
1111
import { CongestionSvc } from "./congestion";
12-
import { ValidationError } from "../error";
12+
import { NotFoundError } from "../error";
1313

1414
// OSRMText instructions
1515
// eslint-disable-next-line @typescript-eslint/no-require-imports
@@ -54,7 +54,7 @@ export class RoutingSvc {
5454
const location = this.postcodeLookup.get(normalizedPostcode);
5555

5656
if (!location) {
57-
throw new ValidationError(`No location found for postcode: ${postcode}`);
57+
throw new NotFoundError(`No location found for postcode: ${postcode}`);
5858
}
5959

6060
return location;

lib/api_codegen.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Openapi Generator last run: : 2024-10-31T22:41:50.903620
1+
// Openapi Generator last run: : 2024-10-31T22:55:43.313382
22
import 'package:openapi_generator_annotations/openapi_generator_annotations.dart';
33

44
@Openapi(

packages/flowmotion_api/.openapi-generator/FILES

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,3 @@ lib/src/model/route_post_request_dest.dart
4343
lib/src/model/route_post_request_src.dart
4444
lib/src/serializers.dart
4545
pubspec.yaml
46-
test/geocoding_api_test.dart

schema/flowmotion_api.yaml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,12 @@ paths:
305305
application/json:
306306
schema:
307307
$ref: "#/components/schemas/Error"
308+
"404":
309+
description: No location found for one or more addresses.
310+
content:
311+
application/json:
312+
schema:
313+
$ref: "#/components/schemas/Error"
308314
"500":
309315
description: Internal server error
310316
content:
@@ -332,7 +338,13 @@ paths:
332338
schema:
333339
$ref: "#/components/schemas/Location"
334340
"400":
335-
description: Invalid postcode.
341+
description: Invalid request parameters.
342+
content:
343+
application/json:
344+
schema:
345+
$ref: "#/components/schemas/Error"
346+
"404":
347+
description: No Location for postcode found.
336348
content:
337349
application/json:
338350
schema:

0 commit comments

Comments
 (0)