UNPKG

fastify

Version:

Fast and low overhead web framework, for Node.js

619 lines (547 loc) 13.1 kB
'use strict' const { test } = require('tap') const localize = require('ajv-i18n') const Fastify = require('..') test('Example - URI $id', t => { t.plan(1) const fastify = Fastify() fastify.addSchema({ $id: 'http://example.com/', type: 'object', properties: { hello: { type: 'string' } } }) fastify.post('/', { handler () { }, schema: { body: { type: 'array', items: { $ref: 'http://example.com#/properties/hello' } } } }) fastify.ready(err => t.error(err)) }) test('Example - string $id', t => { t.plan(1) const fastify = Fastify() fastify.addSchema({ $id: 'commonSchema', type: 'object', properties: { hello: { type: 'string' } } }) fastify.post('/', { handler () { }, schema: { body: { $ref: 'commonSchema#' }, headers: { $ref: 'commonSchema#' } } }) fastify.ready(err => t.error(err)) }) test('Example - get schema', t => { t.plan(1) const fastify = Fastify() fastify.addSchema({ $id: 'schemaId', type: 'object', properties: { hello: { type: 'string' } } }) const mySchemas = fastify.getSchemas() const mySchema = fastify.getSchema('schemaId') t.same(mySchemas.schemaId, mySchema) }) test('Example - get schema encapsulated', async t => { const fastify = Fastify() fastify.addSchema({ $id: 'one', my: 'hello' }) // will return only `one` schema fastify.get('/', (request, reply) => { reply.send(fastify.getSchemas()) }) fastify.register((instance, opts, done) => { instance.addSchema({ $id: 'two', my: 'ciao' }) // will return `one` and `two` schemas instance.get('/sub', (request, reply) => { reply.send(instance.getSchemas()) }) instance.register((subinstance, opts, done) => { subinstance.addSchema({ $id: 'three', my: 'hola' }) // will return `one`, `two` and `three` subinstance.get('/deep', (request, reply) => { reply.send(subinstance.getSchemas()) }) done() }) done() }) const r1 = await fastify.inject('/') const r2 = await fastify.inject('/sub') const r3 = await fastify.inject('/deep') t.same(Object.keys(r1.json()), ['one']) t.same(Object.keys(r2.json()), ['one', 'two']) t.same(Object.keys(r3.json()), ['one', 'two', 'three']) }) test('Example - validation', t => { t.plan(1) const fastify = Fastify({ ajv: { customOptions: { allowUnionTypes: true } } }) const handler = () => { } 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', nullable: true } 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 = { 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) fastify.ready(err => t.error(err)) }) test('Example - ajv config', t => { t.plan(1) const fastify = Fastify({ ajv: { plugins: [ require('ajv-merge-patch') ] } }) fastify.post('/', { handler (req, reply) { reply.send({ ok: 1 }) }, schema: { body: { $patch: { source: { type: 'object', properties: { q: { type: 'string' } } }, with: [ { op: 'add', path: '/properties/q', value: { type: 'number' } } ] } } } }) fastify.post('/foo', { handler (req, reply) { reply.send({ ok: 1 }) }, schema: { body: { $merge: { source: { type: 'object', properties: { q: { type: 'string' } } }, with: { required: ['q'] } } } } }) fastify.ready(err => t.error(err)) }) test('Example Joi', t => { t.plan(1) const fastify = Fastify() const handler = () => { } const Joi = require('joi') fastify.post('/the/url', { schema: { body: Joi.object().keys({ hello: Joi.string().required() }).required() }, validatorCompiler: ({ schema, method, url, httpPart }) => { return data => schema.validate(data) } }, handler) fastify.ready(err => t.error(err)) }) test('Example yup', t => { t.plan(1) const fastify = Fastify() const handler = () => { } const yup = require('yup') // Validation options to match ajv's baseline options used in Fastify const yupOptions = { strict: false, abortEarly: false, // return all errors stripUnknown: true, // remove additional properties recursive: true } fastify.post('/the/url', { schema: { body: yup.object({ age: yup.number().integer().required(), sub: yup.object().shape({ name: yup.string().required() }).required() }) }, validatorCompiler: ({ schema, method, url, httpPart }) => { return function (data) { // with option strict = false, yup `validateSync` function returns the coerced value if validation was successful, or throws if validation failed try { const result = schema.validateSync(data, yupOptions) return { value: result } } catch (e) { return { error: e } } } } }, handler) fastify.ready(err => t.error(err)) }) test('Example - serialization', t => { t.plan(1) const fastify = Fastify() const handler = () => { } const schema = { response: { 200: { type: 'object', properties: { value: { type: 'string' }, otherValue: { type: 'boolean' } } } } } fastify.post('/the/url', { schema }, handler) fastify.ready(err => t.error(err)) }) test('Example - serialization 2', t => { t.plan(1) const fastify = Fastify() const handler = () => { } const schema = { response: { '2xx': { type: 'object', properties: { value: { type: 'string' }, otherValue: { type: 'boolean' } } }, 201: { // the contract syntax value: { type: 'string' } } } } fastify.post('/the/url', { schema }, handler) fastify.ready(err => t.error(err)) }) test('Example - serializator', t => { t.plan(1) const fastify = Fastify() fastify.setSerializerCompiler(({ schema, method, url, httpStatus }) => { return data => JSON.stringify(data) }) fastify.get('/user', { handler (req, reply) { reply.send({ id: 1, name: 'Foo', image: 'BIG IMAGE' }) }, schema: { response: { '2xx': { id: { type: 'number' }, name: { type: 'string' } } } } }) fastify.ready(err => t.error(err)) }) test('Example - schemas examples', t => { t.plan(1) const fastify = Fastify() const handler = () => { } fastify.addSchema({ $id: 'http://foo/common.json', type: 'object', definitions: { foo: { $id: '#address', type: 'object', properties: { city: { type: 'string' } } } } }) fastify.addSchema({ $id: 'http://foo/shared.json', type: 'object', definitions: { foo: { type: 'object', properties: { city: { type: 'string' } } } } }) const refToId = { type: 'object', definitions: { foo: { $id: '#address', type: 'object', properties: { city: { type: 'string' } } } }, properties: { home: { $ref: '#address' }, work: { $ref: '#address' } } } const refToDefinitions = { type: 'object', definitions: { foo: { $id: '#address', type: 'object', properties: { city: { type: 'string' } } } }, properties: { home: { $ref: '#/definitions/foo' }, work: { $ref: '#/definitions/foo' } } } const refToSharedSchemaId = { type: 'object', properties: { home: { $ref: 'http://foo/common.json#address' }, work: { $ref: 'http://foo/common.json#address' } } } const refToSharedSchemaDefinitions = { type: 'object', properties: { home: { $ref: 'http://foo/shared.json#/definitions/foo' }, work: { $ref: 'http://foo/shared.json#/definitions/foo' } } } fastify.post('/', { handler, schema: { body: refToId, headers: refToDefinitions, params: refToSharedSchemaId, query: refToSharedSchemaDefinitions } }) fastify.ready(err => t.error(err)) }) test('should return custom error messages with ajv-errors', t => { t.plan(3) const fastify = Fastify({ ajv: { customOptions: { allErrors: true }, plugins: [ require('ajv-errors') ] } }) const schema = { body: { type: 'object', properties: { name: { type: 'string' }, work: { type: 'string' }, age: { type: 'number', errorMessage: { type: 'bad age - should be num' } } }, required: ['name', 'work'], errorMessage: { required: { name: 'name please', work: 'work please', age: 'age please' } } } } fastify.post('/', { schema }, function (req, reply) { reply.code(200).send(req.body.name) }) fastify.inject({ method: 'POST', payload: { hello: 'salman', age: 'bad' }, url: '/' }, (err, res) => { t.error(err) t.same(JSON.parse(res.payload), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'body/age bad age - should be num, body name please, body work please' }) t.equal(res.statusCode, 400) }) }) test('should be able to handle formats of ajv-formats when added by plugins option', t => { t.plan(3) const fastify = Fastify({ ajv: { plugins: [ require('ajv-formats') ] } }) const schema = { body: { type: 'object', properties: { id: { type: 'string', format: 'uuid' }, email: { type: 'string', format: 'email' } }, required: ['id', 'email'] } } fastify.post('/', { schema }, function (req, reply) { reply.code(200).send(req.body.id) }) fastify.inject({ method: 'POST', payload: { id: '254381a5-888c-4b41-8116-e3b1a54980bd', email: 'info@fastify.dev' }, url: '/' }, (_err, res) => { t.equal(res.body, '254381a5-888c-4b41-8116-e3b1a54980bd') t.equal(res.statusCode, 200) }) fastify.inject({ method: 'POST', payload: { id: 'invalid', email: 'info@fastify.dev' }, url: '/' }, (_err, res) => { t.same(JSON.parse(res.payload), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: 'body/id must match format "uuid"' }) }) }) test('should return localized error messages with ajv-i18n', t => { t.plan(3) const schema = { body: { type: 'object', properties: { name: { type: 'string' }, work: { type: 'string' } }, required: ['name', 'work'] } } const fastify = Fastify({ ajv: { customOptions: { allErrors: true } } }) fastify.setErrorHandler(function (error, request, reply) { if (error.validation) { localize.ru(error.validation) reply.status(400).send(error.validation) return } reply.send(error) }) fastify.post('/', { schema }, function (req, reply) { reply.code(200).send(req.body.name) }) fastify.inject({ method: 'POST', payload: { name: 'salman' }, url: '/' }, (err, res) => { t.error(err) t.same(JSON.parse(res.payload), [{ instancePath: '', keyword: 'required', message: 'должно иметь обязательное поле work', params: { missingProperty: 'work' }, schemaPath: '#/required' }]) t.equal(res.statusCode, 400) }) })