Skip to content

Commit 1aafdaf

Browse files
authored
Make 404 errors consistent with other responses (#305)
1 parent 8a5e49b commit 1aafdaf

File tree

5 files changed

+61
-18
lines changed

5 files changed

+61
-18
lines changed

.changeset/fifty-pens-hammer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/service-errors': patch
3+
---
4+
5+
Report HTTP method in `RouteNotFound` error

.changeset/sharp-bees-serve.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/service-core': patch
3+
---
4+
5+
Make 404 error body consistent with other error responses.

packages/service-core/src/routes/configure-fastify.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type fastify from 'fastify';
22
import * as uuid from 'uuid';
33

4-
import { registerFastifyRoutes } from './route-register.js';
4+
import { registerFastifyNotFoundHandler, registerFastifyRoutes } from './route-register.js';
55

66
import * as system from '../system/system-index.js';
77

@@ -76,6 +76,8 @@ export function configureFastifyServer(server: fastify.FastifyInstance, options:
7676
*/
7777
server.register(async function (childContext) {
7878
registerFastifyRoutes(childContext, generateContext, routes.api?.routes ?? DEFAULT_ROUTE_OPTIONS.api.routes);
79+
registerFastifyNotFoundHandler(childContext);
80+
7981
// Limit the active concurrent requests
8082
childContext.addHook(
8183
'onRequest',

packages/service-core/src/routes/route-register.ts

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
import type fastify from 'fastify';
22
import * as uuid from 'uuid';
33

4-
import { errors, HTTPMethod, logger, router } from '@powersync/lib-services-framework';
4+
import {
5+
ErrorCode,
6+
errors,
7+
HTTPMethod,
8+
logger,
9+
RouteNotFound,
10+
router,
11+
ServiceError
12+
} from '@powersync/lib-services-framework';
513
import { Context, ContextProvider, RequestEndpoint, RequestEndpointHandlerPayload } from './router.js';
14+
import { FastifyReply } from 'fastify';
615

716
export type FastifyEndpoint<I, O, C> = RequestEndpoint<I, O, C> & {
817
parse?: boolean;
@@ -69,23 +78,11 @@ export function registerFastifyRoutes(
6978
const serviceError = errors.asServiceError(ex);
7079
requestLogger.error(`Request failed`, serviceError);
7180

72-
response = new router.RouterResponse({
73-
status: serviceError.errorData.status || 500,
74-
headers: {
75-
'Content-Type': 'application/json'
76-
},
77-
data: {
78-
error: serviceError.errorData
79-
}
80-
});
81+
response = serviceErrorToResponse(serviceError);
8182
}
8283

83-
Object.keys(response.headers).forEach((key) => {
84-
reply.header(key, response.headers[key]);
85-
});
86-
reply.status(response.status);
8784
try {
88-
await reply.send(response.data);
85+
await respond(reply, response);
8986
} finally {
9087
await response.afterSend?.({ clientClosed: request.socket.closed });
9188
requestLogger.info(`${e.method} ${request.url}`, {
@@ -106,3 +103,32 @@ export function registerFastifyRoutes(
106103
});
107104
}
108105
}
106+
107+
/**
108+
* Registers a custom not-found handler to ensure 404 error responses have the same schema as other service errors.
109+
*/
110+
export function registerFastifyNotFoundHandler(app: fastify.FastifyInstance) {
111+
app.setNotFoundHandler(async (request, reply) => {
112+
await respond(reply, serviceErrorToResponse(new RouteNotFound(request.originalUrl, request.method)));
113+
});
114+
}
115+
116+
function serviceErrorToResponse(error: ServiceError): router.RouterResponse {
117+
return new router.RouterResponse({
118+
status: error.errorData.status || 500,
119+
headers: {
120+
'Content-Type': 'application/json'
121+
},
122+
data: {
123+
error: error.errorData
124+
}
125+
});
126+
}
127+
128+
async function respond(reply: FastifyReply, response: router.RouterResponse) {
129+
Object.keys(response.headers).forEach((key) => {
130+
reply.header(key, response.headers[key]);
131+
});
132+
reply.status(response.status);
133+
await reply.send(response.data);
134+
}

packages/service-errors/src/errors.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,12 +218,17 @@ export class InternalServerError extends ServiceError {
218218
export class RouteNotFound extends ServiceError {
219219
static readonly CODE = ErrorCode.PSYNC_S2002;
220220

221-
constructor(path: string) {
221+
constructor(path: string, method?: string) {
222+
let pathDescription = JSON.stringify(path);
223+
if (method != null) {
224+
pathDescription = `${method} ${pathDescription}`;
225+
}
226+
222227
super({
223228
code: RouteNotFound.CODE,
224229
status: 404,
225230
description: 'The path does not exist on this server',
226-
details: `The path ${JSON.stringify(path)} does not exist on this server`,
231+
details: `The path ${pathDescription} does not exist on this server`,
227232
severity: ErrorSeverity.INFO
228233
});
229234
}

0 commit comments

Comments
 (0)