Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Emit a warning when validating undefined schemas #4647

Merged
merged 10 commits into from
Apr 7, 2023
10 changes: 6 additions & 4 deletions lib/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const {
FST_ERR_SCH_RESPONSE_SCHEMA_NOT_NESTED_2XX
} = require('./errors')

const warning = require('./warnings')
franher marked this conversation as resolved.
Show resolved Hide resolved

function compileSchemasForSerialization (context, compile) {
if (!context.schema || !context.schema.response) {
return
Expand Down Expand Up @@ -81,25 +83,25 @@ function compileSchemasForValidation (context, compile, isCustom) {
}
context[headersSchema] = compile({ schema: headersSchemaLowerCase, method, url, httpPart: 'headers' })
} else if (Object.hasOwnProperty.call(schema, 'headers')) {
throw new Error('headers schema is undefined')
warning.emit('FSTDEP015', 'headers', method, url)
franher marked this conversation as resolved.
Show resolved Hide resolved
}

if (schema.body) {
context[bodySchema] = compile({ schema: schema.body, method, url, httpPart: 'body' })
} else if (Object.hasOwnProperty.call(schema, 'body')) {
throw new Error('body schema is undefined')
warning.emit('FSTDEP015', 'body', method, url)
franher marked this conversation as resolved.
Show resolved Hide resolved
}

if (schema.querystring) {
context[querystringSchema] = compile({ schema: schema.querystring, method, url, httpPart: 'querystring' })
} else if (Object.hasOwnProperty.call(schema, 'querystring')) {
throw new Error('querystring schema is undefined')
warning.emit('FSTDEP015', 'querystring', method, url)
franher marked this conversation as resolved.
Show resolved Hide resolved
}

if (schema.params) {
context[paramsSchema] = compile({ schema: schema.params, method, url, httpPart: 'params' })
} else if (Object.hasOwnProperty.call(schema, 'params')) {
franher marked this conversation as resolved.
Show resolved Hide resolved
throw new Error('params schema is undefined')
warning.emit('FSTDEP015', 'params', method, url)
franher marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down
2 changes: 2 additions & 0 deletions lib/warnings.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ warning.create('FastifyDeprecation', 'FSTDEP013', 'Direct return of "trailers" f

warning.create('FastifyDeprecation', 'FSTDEP014', 'You are trying to set/access the default route. This property is deprecated. Please, use setNotFoundHandler if you want to custom a 404 handler or the wildcard (*) to match all routes.')

warning.create('FastifyDeprecation', 'FSTDEP015', 'The %s schema for %s: %s is missed. This may indicate the schema is not well specified.', { unlimited: true })
Fdawgs marked this conversation as resolved.
Show resolved Hide resolved

module.exports = warning
163 changes: 128 additions & 35 deletions test/schema-feature.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const fp = require('fastify-plugin')
const deepClone = require('rfdc')({ circles: true, proto: false })
const Ajv = require('ajv')
const { kSchemaController } = require('../lib/symbols.js')
const warning = require('../lib/warnings')

const echoParams = (req, reply) => { reply.send(req.params) }
const echoBody = (req, reply) => { reply.send(req.body) }
Expand Down Expand Up @@ -253,88 +254,180 @@ test('Should not change the input schemas', t => {
})
})

test('Should throw if the schema body is undefined', t => {
t.plan(2)
test('Should emit warning if the schema headers is undefined', t => {
t.plan(4)
const fastify = Fastify()

fastify.get('/:id', {
process.on('warning', onWarning)
function onWarning (warning) {
t.equal(warning.name, 'FastifyDeprecation')
t.equal(warning.code, 'FSTDEP015')
franher marked this conversation as resolved.
Show resolved Hide resolved
}

t.teardown(() => {
process.removeListener('warning', onWarning)
warning.emitted.set('FSTDEP015', false)
franher marked this conversation as resolved.
Show resolved Hide resolved
})

fastify.post('/:id', {
handler: echoParams,
schema: {
body: undefined
headers: undefined
}
})

fastify.ready(err => {
t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD')
t.equal(err.message, 'Failed building the validation schema for GET: /:id, due to error body schema is undefined')
fastify.inject({
method: 'POST',
url: '/123'
}, (error, res) => {
t.error(error)
t.equal(res.statusCode, 200)
})
})

test('Should throw if the schema headers is undefined', t => {
t.plan(2)
test('Should emit warning if the schema body is undefined', t => {
t.plan(4)
const fastify = Fastify()

fastify.get('/:id', {
process.on('warning', onWarning)
function onWarning (warning) {
t.equal(warning.name, 'FastifyDeprecation')
t.equal(warning.code, 'FSTDEP015')
franher marked this conversation as resolved.
Show resolved Hide resolved
}

t.teardown(() => {
process.removeListener('warning', onWarning)
warning.emitted.set('FSTDEP015', false)
franher marked this conversation as resolved.
Show resolved Hide resolved
})

fastify.post('/:id', {
handler: echoParams,
schema: {
headers: undefined
body: undefined
}
})

fastify.ready(err => {
t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD')
t.equal(err.message, 'Failed building the validation schema for GET: /:id, due to error headers schema is undefined')
fastify.inject({
method: 'POST',
url: '/123'
}, (error, res) => {
t.error(error)
t.equal(res.statusCode, 200)
})
})

test('Should throw if the schema params is undefined', t => {
t.plan(2)
test('Should emit warning if the schema query is undefined', t => {
t.plan(4)
const fastify = Fastify()

fastify.get('/:id', {
process.on('warning', onWarning)
function onWarning (warning) {
t.equal(warning.name, 'FastifyDeprecation')
t.equal(warning.code, 'FSTDEP015')
franher marked this conversation as resolved.
Show resolved Hide resolved
}

t.teardown(() => {
process.removeListener('warning', onWarning)
warning.emitted.set('FSTDEP015', false)
franher marked this conversation as resolved.
Show resolved Hide resolved
})

fastify.post('/:id', {
handler: echoParams,
schema: {
params: undefined
querystring: undefined
}
})

fastify.ready(err => {
t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD')
t.equal(err.message, 'Failed building the validation schema for GET: /:id, due to error params schema is undefined')
fastify.inject({
method: 'POST',
url: '/123'
}, (error, res) => {
t.error(error)
t.equal(res.statusCode, 200)
})
})

test('Should throw if the schema query is undefined', t => {
t.plan(2)
test('Should emit warning if the schema params is undefined', t => {
t.plan(4)
const fastify = Fastify()

fastify.get('/:id', {
process.on('warning', onWarning)
function onWarning (warning) {
t.equal(warning.name, 'FastifyDeprecation')
t.equal(warning.code, 'FSTDEP015')
franher marked this conversation as resolved.
Show resolved Hide resolved
}

t.teardown(() => {
process.removeListener('warning', onWarning)
warning.emitted.set('FSTDEP015', false)
franher marked this conversation as resolved.
Show resolved Hide resolved
})

fastify.post('/:id', {
handler: echoParams,
schema: {
querystring: undefined
params: undefined
}
})

fastify.ready(err => {
t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD')
t.equal(err.message, 'Failed building the validation schema for GET: /:id, due to error querystring schema is undefined')
fastify.inject({
method: 'POST',
url: '/123'
}, (error, res) => {
t.error(error)
t.equal(res.statusCode, 200)
})
})

test('Should throw if the schema query is undefined', t => {
t.plan(2)
test('Should emit a warning for every route with undefined schema', t => {
t.plan(16)
const fastify = Fastify()

fastify.get('/:id', {
let runs = 0
const expectedWarningEmitted = [0, 1, 2, 3]
// It emits 4 warnings:
// - 2 - GET and HEAD for /undefinedParams/:id
// - 2 - GET and HEAD for /undefinedBody/:id
// => 3 x 4 assertions = 12 assertions
function onWarning (warning) {
t.equal(warning.name, 'FastifyDeprecation')
t.equal(warning.code, 'FSTDEP015')
franher marked this conversation as resolved.
Show resolved Hide resolved
t.equal(runs++, expectedWarningEmitted.shift())
}

process.on('warning', onWarning)
t.teardown(() => {
process.removeListener('warning', onWarning)
warning.emitted.set('FSTDEP015', false)
franher marked this conversation as resolved.
Show resolved Hide resolved
})

fastify.get('/undefinedParams/:id', {
handler: echoParams,
schema: {
querystring: undefined
params: undefined
}
})

fastify.ready(err => {
t.equal(err.code, 'FST_ERR_SCH_VALIDATION_BUILD')
t.equal(err.message, 'Failed building the validation schema for GET: /:id, due to error querystring schema is undefined')
fastify.get('/undefinedBody/:id', {
handler: echoParams,
schema: {
body: undefined
}
})

fastify.inject({
method: 'GET',
url: '/undefinedParams/123'
}, (error, res) => {
t.error(error)
t.equal(res.statusCode, 200)
})

fastify.inject({
method: 'GET',
url: '/undefinedBody/123'
}, (error, res) => {
t.error(error)
t.equal(res.statusCode, 200)
})
})

Expand Down