Skip to content

Commit 9d755ff

Browse files
committed
feat: Add allowUndeclaredResponses option.
1 parent bcdaf40 commit 9d755ff

File tree

8 files changed

+60
-7
lines changed

8 files changed

+60
-7
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Register as a plugin, optional providing any of the following options:
2424
- `hideUnhandledErrors`: If to hide unhandled server errors or returning to the client including stack information. Default is to hide errors when `NODE_ENV` environment variable is `production`.
2525
- `convertValidationErrors`: Convert validation errors to a structured human readable object. Default is `true`.
2626
- `convertResponsesValidationErrors`: Convert response validation errors to a structured human readable object. Default is to enable when `NODE_ENV` environment variable is different from `production`.
27+
- `allowUndeclaredResponses`: When converting response validation errors, allow responses that have no schema defined instead of throwing an error.
2728

2829
Once registered, the server will use the plugin handlers for all errors (basically, both `setErrorHandler` and `setNotFoundHandler` are called).
2930

@@ -49,7 +50,7 @@ const createError = require('http-errors')
4950
server.register(require('fastify-errors-properties'))
5051

5152
server.get('/invalid', {
52-
handler: async function(request, reply) {
53+
async handler(request, reply) {
5354
throw createError(404, 'You are not supposed to reach this.', { header: { 'X-Req-Id': request.id, id: 123 } })
5455
}
5556
})
@@ -101,7 +102,7 @@ const createError = require('http-errors')
101102
server.register(require('fastify-errors-properties'), { hideUnhandledErrors: false })
102103

103104
server.get('/invalid', {
104-
handler: function(request, reply) {
105+
handler(request, reply) {
105106
const error = new Error('This was not supposed to happen.')
106107
error.id = 123
107108
throw error

lib/index.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,17 @@ Object.defineProperty(exports, "convertValidationErrors", { enumerable: true, ge
2828
Object.defineProperty(exports, "niceJoin", { enumerable: true, get: function () { return validation_2.niceJoin; } });
2929
Object.defineProperty(exports, "validationMessagesFormatters", { enumerable: true, get: function () { return validation_2.validationMessagesFormatters; } });
3030
exports.plugin = fastify_plugin_1.default(function (instance, options, done) {
31-
var _a, _b, _c;
31+
var _a, _b, _c, _d;
3232
const isProduction = process.env.NODE_ENV === 'production';
3333
const hideUnhandledErrors = (_a = options.hideUnhandledErrors) !== null && _a !== void 0 ? _a : isProduction;
3434
const convertValidationErrors = (_b = options.convertValidationErrors) !== null && _b !== void 0 ? _b : true;
3535
const convertResponsesValidationErrors = (_c = options.convertResponsesValidationErrors) !== null && _c !== void 0 ? _c : !isProduction;
36-
instance.decorateRequest('errorProperties', { hideUnhandledErrors, convertValidationErrors });
36+
const allowUndeclaredResponses = (_d = options.allowUndeclaredResponses) !== null && _d !== void 0 ? _d : false;
37+
instance.decorateRequest('errorProperties', {
38+
hideUnhandledErrors,
39+
convertValidationErrors,
40+
allowUndeclaredResponses
41+
});
3742
instance.setErrorHandler(handlers_1.handleErrors);
3843
instance.setNotFoundHandler(handlers_1.handleNotFoundError);
3944
if (convertResponsesValidationErrors) {

lib/validation.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,7 @@ function addResponseValidation(route) {
174174
validators[code] = this.responseValidatorSchemaCompiler.compile(schema);
175175
}
176176
// Note that this hook is not called for non JSON payloads therefore validation is not possible in such cases
177-
route.preSerialization = async function (_request, reply, payload) {
177+
route.preSerialization = async function (request, reply, payload) {
178178
const statusCode = reply.raw.statusCode;
179179
// Never validate error 500
180180
if (statusCode === http_status_codes_1.default.INTERNAL_SERVER_ERROR) {
@@ -183,6 +183,9 @@ function addResponseValidation(route) {
183183
// No validator, it means the HTTP status is not allowed
184184
const validator = validators[statusCode];
185185
if (!validator) {
186+
if (request.errorProperties.allowUndeclaredResponses) {
187+
return payload;
188+
}
186189
throw http_errors_1.default(http_status_codes_1.default.INTERNAL_SERVER_ERROR, exports.validationMessagesFormatters.invalidResponseCode(statusCode));
187190
}
188191
// Now validate the payload

src/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,13 @@ export const plugin = fastifyPlugin(
1515
const hideUnhandledErrors = options.hideUnhandledErrors ?? isProduction
1616
const convertValidationErrors = options.convertValidationErrors ?? true
1717
const convertResponsesValidationErrors = options.convertResponsesValidationErrors ?? !isProduction
18+
const allowUndeclaredResponses = options.allowUndeclaredResponses ?? false
1819

19-
instance.decorateRequest('errorProperties', { hideUnhandledErrors, convertValidationErrors })
20+
instance.decorateRequest('errorProperties', {
21+
hideUnhandledErrors,
22+
convertValidationErrors,
23+
allowUndeclaredResponses
24+
})
2025
instance.setErrorHandler(handleErrors)
2126
instance.setNotFoundHandler(handleNotFoundError)
2227

src/interfaces.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ declare module 'fastify' {
99
errorProperties?: {
1010
hideUnhandledErrors?: boolean
1111
convertValidationErrors?: boolean
12+
allowUndeclaredResponses?: boolean
1213
}
1314
}
1415

src/validation.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ export function addResponseValidation(this: FastifyInstance, route: RouteOptions
202202
// Note that this hook is not called for non JSON payloads therefore validation is not possible in such cases
203203
route.preSerialization = async function (
204204
this: FastifyInstance,
205-
_request: FastifyRequest,
205+
request: FastifyRequest,
206206
reply: FastifyReply,
207207
payload: any
208208
): Promise<any> {
@@ -217,6 +217,10 @@ export function addResponseValidation(this: FastifyInstance, route: RouteOptions
217217
const validator = validators[statusCode]
218218

219219
if (!validator) {
220+
if (request.errorProperties!.allowUndeclaredResponses) {
221+
return payload
222+
}
223+
220224
throw createError(StatusCodes.INTERNAL_SERVER_ERROR, validationMessagesFormatters.invalidResponseCode(statusCode))
221225
}
222226

test/validation.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,30 @@ async function buildServer(options: FastifyPluginOptions = {}): Promise<FastifyI
8080
}
8181
})
8282

83+
server.get('/undeclared-response', {
84+
schema: {
85+
response: {
86+
[StatusCodes.OK]: {
87+
type: 'object',
88+
properties: {
89+
a: {
90+
type: 'string'
91+
},
92+
b: {
93+
type: 'string'
94+
}
95+
},
96+
required: ['b'],
97+
additionalProperties: false
98+
}
99+
}
100+
},
101+
async handler(_r: FastifyRequest, reply: FastifyReply): Promise<object> {
102+
reply.code(StatusCodes.ACCEPTED)
103+
return { a: 1 }
104+
}
105+
})
106+
83107
server.get('/no-json', {
84108
schema: {
85109
response: {
@@ -411,6 +435,15 @@ t.test('Response Validation', (t: Test) => {
411435
t.deepEqual(JSON.parse(response.payload), { a: 1, c: 2 })
412436
})
413437

438+
t.test('should allow responses which are missing in the schema if explicitily enabled', async (t: Test) => {
439+
await buildServer({ allowUndeclaredResponses: true })
440+
441+
const response = await server!.inject({ method: 'GET', url: '/undeclared-response' })
442+
443+
t.equal(response.statusCode, StatusCodes.ACCEPTED)
444+
t.deepEqual(JSON.parse(response.payload), { a: 1 })
445+
})
446+
414447
t.test('should allow everything if the payload is not JSON', async (t: Test) => {
415448
await buildServer()
416449

types/interfaces.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ declare module 'fastify' {
88
errorProperties?: {
99
hideUnhandledErrors?: boolean;
1010
convertValidationErrors?: boolean;
11+
allowUndeclaredResponses?: boolean;
1112
};
1213
}
1314
interface FastifyReply {

0 commit comments

Comments
 (0)