You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							557 lines
						
					
					
						
							12 KiB
						
					
					
				
			
		
		
	
	
							557 lines
						
					
					
						
							12 KiB
						
					
					
				'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()
 | 
						|
  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('@hapi/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 sintax
 | 
						|
        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.get('/', {
 | 
						|
    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, jsonPointers: 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,
 | 
						|
      error: 'Bad Request',
 | 
						|
      message: 'body/age bad age - should be num, body name please, body work please'
 | 
						|
    })
 | 
						|
    t.equal(res.statusCode, 400)
 | 
						|
  })
 | 
						|
})
 | 
						|
 | 
						|
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), [{
 | 
						|
      dataPath: '',
 | 
						|
      keyword: 'required',
 | 
						|
      message: 'должно иметь обязательное поле work',
 | 
						|
      params: { missingProperty: 'work' },
 | 
						|
      schemaPath: '#/required'
 | 
						|
    }])
 | 
						|
    t.equal(res.statusCode, 400)
 | 
						|
  })
 | 
						|
})
 |