Você está na página 1de 16

Dispensar

Adira ao GitHub hoje


O GitHub é o lar de mais de 40 milhões de
desenvolvedores trabalhando juntos para
hospedar e revisar códigos, gerenciar projetos e
criar software juntos.

inscrever-se

Filial: mestre Achar arquivo Caminho de cópia

fastify / docs / Validation-and-Serialization.md

recurso icrotz : opções personalizadas do Ajv ( # 1946 )

a8f79d3
5 dias atrás

16 colaboradores

Raw Blame História

668 linhas (575 sloc) 18 KB

Fastify

Validação e serialização

O Fastify usa uma abordagem baseada em esquema e, mesmo que não seja
obrigatório, recomendamos o uso do Esquema JSON para validar suas rotas e
serializar suas saídas. Internamente, o Fastify compila o esquema em uma função
de alto desempenho.

Notice Aviso de segurança


Trate a definição do esquema como código do aplicativo. Como os recursos
de validação e serialização avaliam dinamicamente o código new
Function() , não é seguro usar esquemas fornecidos pelo usuário. Veja Ajv e
fast-json-stringify para mais detalhes.

Validação
A validação de rota depende internamente do Ajv , que é um validador de esquema
JSON de alto desempenho. A validação da entrada é muito fácil: basta adicionar os
campos que você precisa dentro do esquema de rota e pronto! As validações
suportadas são:

body : valida o corpo da solicitação se for um POST ou PUT.

querystring ou query : valida a string de consulta. Pode ser um objeto JSON


Schema completo (com uma type propriedade 'object' e um
'properties' objeto que contêm parâmetros) ou uma variação mais simples
na qual os atributos type e properties são perdoados e os parâmetros de
consulta são listados no nível superior (veja o exemplo abaixo).
params : valida os parâmetros de rota.

headers : valida os cabeçalhos da solicitação.

Exemplo:

const bodyJsonSchema = {
type: 'object',
required: ['requiredKey'],
properties: {
someKey: { type: 'string' },
someOtherKey: { type: 'number' },
requiredKey: {
type: 'array',
maxItems: 3,
items: { type: 'integer' }
},
nullableKey: { type: ['number', 'null'] }, // or { type: 'number',
multipleTypesKey: { type: ['boolean', 'number'] },
multipleRestrictedTypesKey: {
oneOf: [
{ type: 'string', maxLength: 5 },
{ type: 'number', minimum: 10 }
]
},
enumKey: {
type: 'string',
enum: ['John', 'Foo']
},
notTypeKey: {
not: { type: 'array' }
}
}
}

const queryStringJsonSchema = {
name: { type: 'string' },
excitement: { type: 'integer' }
}

const paramsJsonSchema = {
type: 'object',
properties: {
par1: { type: 'string' },
par2: { type: 'number' }
}
}

const headersJsonSchema = {
type: 'object',
properties: {
'x-foo': { type: 'string' }
},
required: ['x-foo']
}

const schema = {
body: bodyJsonSchema,

querystring: queryStringJsonSchema,

params: paramsJsonSchema,

headers: headersJsonSchema
}

fastify.post('/the/url', { schema }, handler)

Note that Ajv will try to coerce the values to the types specified in your schema type
keywords, both to pass the validation and to use the correctly typed data afterwards.

Adding a shared schema

Thanks to the addSchema API, you can add multiple schemas to the Fastify
instance and then reuse them in multiple parts of your application. As usual, this
API is encapsulated.
There are two ways to reuse your shared schemas:

$ref-way : as described in the standard, you can refer to an external schema.


To use it you have to addSchema with a valid $id absolute URI.
replace-way : this is a Fastify utility that lets you to substitute some fields
with a shared schema. To use it you have to addSchema with an $id having a
relative URI fragment which is a simple string that applies only to alphanumeric
chars [A-Za-z0-9] .

Here an overview on how to set an $id and how references to it:

replace-way
myField: 'foobar#' will search for a shared schema added with $id:
'foobar'

$ref-way
myField: { $ref: '#foo'} will search for field with $id: '#foo' inside
the current schema
myField: { $ref: '#/definitions/foo'} will search for field
definitions.foo inside the current schema

myField: { $ref: 'http://url.com/sh.json#'} will search for a


shared schema added with $id: 'http://url.com/sh.json'
myField: { $ref: 'http://url.com/sh.json#/definitions/foo'} will
search for a shared schema added with $id:
'http://url.com/sh.json' and will use the field definitions.foo

myField: { $ref: 'http://url.com/sh.json#foo'} will search for a


shared schema added with $id: 'http://url.com/sh.json' and it will
look inside of it for object with $id: '#foo'

More examples:

$ref-way usage examples:

fastify.addSchema({
$id: 'http://example.com/common.json',
type: 'object',
properties: {
hello: { type: 'string' }
}
})

fastify.route({
method: 'POST',
url: '/',
schema: {
body: {
type: 'array',
items: { $ref: 'http://example.com/common.json#/properties/hello'
}
},
handler: () => {}
})

replace-way usage examples:

const fastify = require('fastify')()

fastify.addSchema({
$id: 'greetings',
type: 'object',
properties: {
hello: { type: 'string' }
}
})

fastify.route({
method: 'POST',
url: '/',
schema: {
body: 'greetings#'
},
handler: () => {}
})

fastify.register((instance, opts, done) => {

/**
* In children's scope can use schemas defined in upper scope like 'g
* Parent scope can't use the children schemas.
*/
instance.addSchema({
$id: 'framework',
type: 'object',
properties: {
fastest: { type: 'string' },
hi: 'greetings#'
}
})

instance.route({
method: 'POST',
url: '/sub',
schema: {
body: 'framework#'
},
handler: () => {}
})

done()
})

You can use the shared schema everywhere, as top level schema or nested inside
other schemas:

const fastify = require('fastify')()

fastify.addSchema({
$id: 'greetings',
type: 'object',
properties: {
hello: { type: 'string' }
}
})

fastify.route({
method: 'POST',
url: '/',
schema: {
body: {
type: 'object',
properties: {
greeting: 'greetings#',
timestamp: { type: 'number' }
}
}
},
handler: () => {}
})

Retrieving a copy of shared schemas

The function getSchemas returns the shared schemas available in the selected
scope:

fastify.addSchema({ $id: 'one', my: 'hello' })


fastify.get('/', (request, reply) => { reply.send(fastify.getSchemas())

fastify.register((instance, opts, done) => {


instance.addSchema({ $id: 'two', my: 'ciao' })
instance.get('/sub', (request, reply) => { reply.send(instance.getSch
instance.register((subinstance, opts, done) => {
subinstance.addSchema({ $id: 'three', my: 'hola' })
subinstance.get('/deep', (request, reply) => { reply.send(subinstan
done()
})
done()
})

This example will returns:

URL Schemas

/ one

/sub one, two

/deep one, two, three

Ajv Plugins

You can provide a list of plugins you want to use with Ajv:

Refer to ajv options to check plugins format

const fastify = require('fastify')({


ajv: {
plugins: [
require('ajv-merge-patch')
]
}
})

fastify.route({
method: 'POST',
url: '/',
schema: {
body: {
$patch: {
source: {
type: 'object',
properties: {
q: {
type: 'string'
}
}
},
with: [
{
op: 'add',
path: '/properties/q',
value: { type: 'number' }
}
]
}
}
},
handler (req, reply) {
reply.send({ ok: 1 })
}
})

fastify.route({
method: 'POST',
url: '/',
schema: {
body: {
$merge: {
source: {
type: 'object',
properties: {
q: {
type: 'string'
}
}
},
with: {
required: ['q']
}
}
}
},
handler (req, reply) {
reply.send({ ok: 1 })
}
})

Schema Compiler

The schemaCompiler is a function that returns a function that validates the body,
url parameters, headers, and query string. The default schemaCompiler returns a
function that implements the ajv validation interface. Fastify uses it internally to
speed the validation up.

Fastify's baseline ajv configuration is:


{
removeAdditional: true, // remove additional properties
useDefaults: true, // replace missing properties and items with the v
coerceTypes: true, // change data type of data to match type keyword
allErrors: true, // check for all errors
nullable: true // support keyword "nullable" from Open API 3 spec
}

This baseline configuration can be modified by providing ajv.customOptions to


your Fastify factory.

If you want to change or set additional config options, you will need to create your
own instance and override the existing one like:

const fastify = require('fastify')()


const Ajv = require('ajv')
const ajv = new Ajv({
// the fastify defaults (if needed)
removeAdditional: true,
useDefaults: true,
coerceTypes: true,
allErrors: true,
nullable: true,
// any other options
// ...
})
fastify.setSchemaCompiler(function (schema) {
return ajv.compile(schema)
})

// -------
// Alternatively, you can set the schema compiler using the setter prop
fastify.schemaCompiler = function (schema) { return ajv.compile(schema)

Note: If you use a custom instance of any validator (even Ajv), you have to add
schemas to the validator instead of fastify, since fastify's default validator is no longer
used, and fastify's addSchema method has no idea what validator you are using.

But maybe you want to change the validation library. Perhaps you like Joi . In this
case, you can use it to validate the url parameters, body, and query string!

const Joi = require('joi')

fastify.post('/the/url', {
schema: {
body: Joi.object().keys({
hello: Joi.string().required()
}).required()
},
schemaCompiler: schema => data => Joi.validate(data, schema)
}, handler)

In that case the function returned by schemaCompiler returns an object like:

error : filled with an instance of Error or a string that describes the


validation error
value : the coerced value that passed the validation

Schema Resolver

The schemaResolver is a function that works together with the schemaCompiler :


you can't use it with the default schema compiler. This feature is useful when you
use complex schemas with $ref keyword in your routes and a custom validator.

This is needed because all the schemas you add to your custom compiler are
unknown to Fastify but it need to resolve the $ref paths.

const fastify = require('fastify')()


const Ajv = require('ajv')
const ajv = new Ajv()

ajv.addSchema({
$id: 'urn:schema:foo',
definitions: {
foo: { type: 'string' }
},
type: 'object',
properties: {
foo: { $ref: '#/definitions/foo' }
}
})
ajv.addSchema({
$id: 'urn:schema:response',
type: 'object',
required: ['foo'],
properties: {
foo: { $ref: 'urn:schema:foo#/definitions/foo' }
}
})
ajv.addSchema({
$id: 'urn:schema:request',
type: 'object',
required: ['foo'],
properties: {
foo: { $ref: 'urn:schema:foo#/definitions/foo' }
}
})

fastify.setSchemaCompiler(schema => ajv.compile(schema))


fastify.setSchemaResolver((ref) => {
return ajv.getSchema(ref).schema
})

fastify.route({
method: 'POST',
url: '/',
schema: {
body: ajv.getSchema('urn:schema:request').schema,
response: {
'2xx': ajv.getSchema('urn:schema:response').schema
}
},
handler (req, reply) {
reply.send({ foo: 'bar' })
}
})

Serialization
Usually you will send your data to the clients via JSON, and Fastify has a powerful
tool to help you, fast-json-stringify, which is used if you have provided an output
schema in the route options. We encourage you to use an output schema, as it will
increase your throughput by 100-400% depending on your payload and will prevent
accidental disclosure of sensitive information.

Example:

const schema = {
response: {
200: {
type: 'object',
properties: {
value: { type: 'string' },
otherValue: { type: 'boolean' }
}
}
}
}

fastify.post('/the/url', { schema }, handler)


As you can see, the response schema is based on the status code. If you want to
use the same schema for multiple status codes, you can use '2xx' , for example:

const schema = {
response: {
'2xx': {
type: 'object',
properties: {
value: { type: 'string' },
otherValue: { type: 'boolean' }
}
},
201: {
type: 'object',
properties: {
value: { type: 'string' }
}
}
}
}

fastify.post('/the/url', { schema }, handler)

If you need a custom serializer in a very specific part of your code, you can set one
with reply.serializer(...) .

Error Handling
When schema validation fails for a request, Fastify will automtically return a status
400 response including the result from the validator in the payload. As an example,
if you have the following schema for your route

const schema = {
body: {
type: 'object',
properties: {
name: { type: 'string' }
},
required: ['name']
}
}

and fail to satisfy it, the route will immediately return a response with the following
payload
{
"statusCode": 400,
"error": "Bad Request",
"message": "body should have required property 'name'"
}

If you want to handle errors inside the route, you can specify the
attachValidation option for your route. If there is a validation error, the
validationError property of the request will contain the Error object with the
raw validation result as shown below

const fastify = Fastify()

fastify.post('/', { schema, attachValidation: true }, function (req, re


if (req.validationError) {
// `req.validationError.validation` contains the raw validation err
reply.code(400).send(req.validationError)
}
})

You can also use setErrorHandler to define a custom response for validation errors
such as

fastify.setErrorHandler(function (error, request, reply) {


if (error.validation) {
reply.status(422).send(new Error('validation failed'))
}
})

If you want custom error response in schema without headaches and quickly, you
can take a look at here

JSON Schema and Shared Schema support


JSON Schema has some type of utilities in order to optimize your schemas that, in
conjuction with the Fastify's shared schema, let you reuse all your schemas easily.

Use Case Validator Serializer

shared schema

$ref to $id
Use Case Validator Serializer

$ref to /definitions

$ref to shared schema $id

$ref to shared schema /definitions

Examples

// Usage of the Shared Schema feature


fastify.addSchema({
$id: 'sharedAddress',
type: 'object',
properties: {
city: { 'type': 'string' }
}
})

const sharedSchema = {
type: 'object',
properties: {
home: 'sharedAddress#',
work: 'sharedAddress#'
}
}

// Usage of $ref to $id in same JSON Schema


const refToId = {
type: 'object',
definitions: {
foo: {
$id: '#address',
type: 'object',
properties: {
city: { 'type': 'string' }
}
}
},
properties: {
home: { $ref: '#address' },
work: { $ref: '#address' }
}
}
// Usage of $ref to /definitions in same JSON Schema
const refToDefinitions = {
type: 'object',
definitions: {
foo: {
$id: '#address',
type: 'object',
properties: {
city: { 'type': 'string' }
}
}
},
properties: {
home: { $ref: '#/definitions/foo' },
work: { $ref: '#/definitions/foo' }
}
}

// Usage $ref to a shared schema $id as external schema


fastify.addSchema({
$id: 'http://foo/common.json',
type: 'object',
definitions: {
foo: {
$id: '#address',
type: 'object',
properties: {
city: { 'type': 'string' }
}
}
}
})

const refToSharedSchemaId = {
type: 'object',
properties: {
home: { $ref: 'http://foo/common.json#address' },
work: { $ref: 'http://foo/common.json#address' }
}
}

// Usage $ref to a shared schema /definitions as external schema


fastify.addSchema({
$id: 'http://foo/common.json',
type: 'object',
definitions: {
foo: {
type: 'object',
properties: {
city: { 'type': 'string' }
}
}
}
})

const refToSharedSchemaDefinitions = {
type: 'object',
properties: {
home: { $ref: 'http://foo/common.json#/definitions/foo' },
work: { $ref: 'http://foo/common.json#/definitions/foo' }
}
}

Resources
JSON Schema
Understanding JSON schema
fast-json-stringify documentation
Ajv documentation
Ajv i18n
Ajv custom errors
Custom error handling with core methods with error file dumping example