Skip to content

Commit f67267d

Browse files
authored
Merge pull request #4 from ShogunPanda/undeclared-response
feat: Add allowUndeclaredResponses option.
2 parents 6f4991f + ded5eda commit f67267d

File tree

8 files changed

+59
-6
lines changed

8 files changed

+59
-6
lines changed

README.md

Lines changed: 2 additions & 1 deletion
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

@@ -105,7 +106,7 @@ const createError = require('http-errors')
105106
server.register(require('fastify-http-errors-enhanced'), { hideUnhandledErrors: false })
106107

107108
server.get('/invalid', {
108-
handler: function (request, reply) {
109+
handler(request, reply) {
109110
const error = new Error('This was not supposed to happen.')
110111
error.id = 123
111112
throw error

lib/index.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,17 @@ Object.defineProperty(exports, "convertValidationErrors", { enumerable: true, ge
2525
Object.defineProperty(exports, "niceJoin", { enumerable: true, get: function () { return validation_2.niceJoin; } });
2626
Object.defineProperty(exports, "validationMessagesFormatters", { enumerable: true, get: function () { return validation_2.validationMessagesFormatters; } });
2727
exports.plugin = fastify_plugin_1.default(function (instance, options, done) {
28-
var _a, _b, _c;
28+
var _a, _b, _c, _d;
2929
const isProduction = process.env.NODE_ENV === 'production';
3030
const hideUnhandledErrors = (_a = options.hideUnhandledErrors) !== null && _a !== void 0 ? _a : isProduction;
3131
const convertValidationErrors = (_b = options.convertValidationErrors) !== null && _b !== void 0 ? _b : true;
3232
const convertResponsesValidationErrors = (_c = options.convertResponsesValidationErrors) !== null && _c !== void 0 ? _c : !isProduction;
33-
instance.decorateRequest('errorProperties', { hideUnhandledErrors, convertValidationErrors });
33+
const allowUndeclaredResponses = (_d = options.allowUndeclaredResponses) !== null && _d !== void 0 ? _d : false;
34+
instance.decorateRequest('errorProperties', {
35+
hideUnhandledErrors,
36+
convertValidationErrors,
37+
allowUndeclaredResponses
38+
});
3439
instance.setErrorHandler(handlers_1.handleErrors);
3540
instance.setNotFoundHandler(handlers_1.handleNotFoundError);
3641
if (convertResponsesValidationErrors) {

lib/validation.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ function addResponseValidation(route) {
170170
validators[code] = this.responseValidatorSchemaCompiler.compile(schema);
171171
}
172172
// Note that this hook is not called for non JSON payloads therefore validation is not possible in such cases
173-
route.preSerialization = async function (_request, reply, payload) {
173+
route.preSerialization = async function (request, reply, payload) {
174174
const statusCode = reply.raw.statusCode;
175175
// Never validate error 500
176176
if (statusCode === http_errors_enhanced_1.INTERNAL_SERVER_ERROR) {
@@ -179,6 +179,9 @@ function addResponseValidation(route) {
179179
// No validator, it means the HTTP status is not allowed
180180
const validator = validators[statusCode];
181181
if (!validator) {
182+
if (request.errorProperties.allowUndeclaredResponses) {
183+
return payload;
184+
}
182185
throw new http_errors_enhanced_1.InternalServerError(exports.validationMessagesFormatters.invalidResponseCode(statusCode));
183186
}
184187
// Now validate the payload

src/index.ts

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

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

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
@@ -201,7 +201,7 @@ export function addResponseValidation(this: FastifyInstance, route: RouteOptions
201201
// Note that this hook is not called for non JSON payloads therefore validation is not possible in such cases
202202
route.preSerialization = async function (
203203
this: FastifyInstance,
204-
_request: FastifyRequest,
204+
request: FastifyRequest,
205205
reply: FastifyReply,
206206
payload: any
207207
): Promise<any> {
@@ -216,6 +216,10 @@ export function addResponseValidation(this: FastifyInstance, route: RouteOptions
216216
const validator = validators[statusCode]
217217

218218
if (!validator) {
219+
if (request.errorProperties!.allowUndeclaredResponses) {
220+
return payload
221+
}
222+
219223
throw new InternalServerError(validationMessagesFormatters.invalidResponseCode(statusCode))
220224
}
221225

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+
[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(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, 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
}

0 commit comments

Comments
 (0)