strong-tie/inbound-calls
0
1'use strict'2 3const { test } = require('tap')4const Fastify = require('..')5 6const AJV = require('ajv')7const Schema = require('fluent-json-schema')8 9const customSchemaCompilers = {10 body: new AJV({11 coerceTypes: false12 }),13 params: new AJV({14 coerceTypes: true15 }),16 querystring: new AJV({17 coerceTypes: true18 })19}20 21const customValidatorCompiler = req => {22 if (!req.httpPart) {23 throw new Error('Missing httpPart')24 }25 26 const compiler = customSchemaCompilers[req.httpPart]27 28 if (!compiler) {29 throw new Error(`Missing compiler for ${req.httpPart}`)30 }31 32 return compiler.compile(req.schema)33}34 35const schemaA = {36 $id: 'urn:schema:foo',37 type: 'object',38 definitions: {39 foo: { type: 'integer' }40 },41 properties: {42 foo: { $ref: '#/definitions/foo' }43 }44}45const schemaBRefToA = {46 $id: 'urn:schema:response',47 type: 'object',48 required: ['foo'],49 properties: {50 foo: { $ref: 'urn:schema:foo#/definitions/foo' }51 }52}53 54const schemaCRefToB = {55 $id: 'urn:schema:request',56 type: 'object',57 required: ['foo'],58 properties: {59 foo: { $ref: 'urn:schema:response#/properties/foo' }60 }61}62 63const schemaArtist = {64 type: 'object',65 properties: {66 name: { type: 'string' },67 work: { type: 'string' }68 },69 required: ['name', 'work']70}71 72test('Basic validation test', t => {73 t.plan(6)74 75 const fastify = Fastify()76 fastify.post('/', {77 schema: {78 body: schemaArtist79 }80 }, function (req, reply) {81 reply.code(200).send(req.body.name)82 })83 84 fastify.inject({85 method: 'POST',86 payload: {87 name: 'michelangelo',88 work: 'sculptor, painter, architect and poet'89 },90 url: '/'91 }, (err, res) => {92 t.error(err)93 t.same(res.payload, 'michelangelo')94 t.equal(res.statusCode, 200)95 })96 97 fastify.inject({98 method: 'POST',99 payload: { name: 'michelangelo' },100 url: '/'101 }, (err, res) => {102 t.error(err)103 t.same(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: "body must have required property 'work'" })104 t.equal(res.statusCode, 400)105 })106})107 108test('Different schema per content type', t => {109 t.plan(12)110 111 const fastify = Fastify()112 fastify.addContentTypeParser('application/octet-stream', {113 parseAs: 'buffer'114 }, async function (_, payload) {115 return payload116 })117 fastify.post('/', {118 schema: {119 body: {120 content: {121 'application/json': {122 schema: schemaArtist123 },124 'application/octet-stream': {125 schema: {} // Skip validation126 },127 'text/plain': {128 schema: { type: 'string' }129 }130 }131 }132 }133 }, async function (req, reply) {134 return reply.send(req.body)135 })136 137 fastify.inject({138 url: '/',139 method: 'POST',140 headers: { 'Content-Type': 'application/json' },141 body: {142 name: 'michelangelo',143 work: 'sculptor, painter, architect and poet'144 }145 }, (err, res) => {146 t.error(err)147 t.same(JSON.parse(res.payload).name, 'michelangelo')148 t.equal(res.statusCode, 200)149 })150 151 fastify.inject({152 url: '/',153 method: 'POST',154 headers: { 'Content-Type': 'application/json' },155 body: { name: 'michelangelo' }156 }, (err, res) => {157 t.error(err)158 t.same(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: "body must have required property 'work'" })159 t.equal(res.statusCode, 400)160 })161 162 fastify.inject({163 url: '/',164 method: 'POST',165 headers: { 'Content-Type': 'application/octet-stream' },166 body: Buffer.from('AAAAAAAA')167 }, (err, res) => {168 t.error(err)169 t.same(res.payload, 'AAAAAAAA')170 t.equal(res.statusCode, 200)171 })172 173 fastify.inject({174 url: '/',175 method: 'POST',176 headers: { 'Content-Type': 'text/plain' },177 body: 'AAAAAAAA'178 }, (err, res) => {179 t.error(err)180 t.same(res.payload, 'AAAAAAAA')181 t.equal(res.statusCode, 200)182 })183})184 185test('Skip validation if no schema for content type', t => {186 t.plan(3)187 188 const fastify = Fastify()189 fastify.post('/', {190 schema: {191 body: {192 content: {193 'application/json': {194 schema: schemaArtist195 }196 // No schema for 'text/plain'197 }198 }199 }200 }, async function (req, reply) {201 return reply.send(req.body)202 })203 204 fastify.inject({205 url: '/',206 method: 'POST',207 headers: { 'Content-Type': 'text/plain' },208 body: 'AAAAAAAA'209 }, (err, res) => {210 t.error(err)211 t.same(res.payload, 'AAAAAAAA')212 t.equal(res.statusCode, 200)213 })214})215 216test('Skip validation if no content type schemas', t => {217 t.plan(3)218 219 const fastify = Fastify()220 fastify.post('/', {221 schema: {222 body: {223 content: {224 // No schemas225 }226 }227 }228 }, async function (req, reply) {229 return reply.send(req.body)230 })231 232 fastify.inject({233 url: '/',234 method: 'POST',235 headers: { 'Content-Type': 'text/plain' },236 body: 'AAAAAAAA'237 }, (err, res) => {238 t.error(err)239 t.same(res.payload, 'AAAAAAAA')240 t.equal(res.statusCode, 200)241 })242})243 244test('External AJV instance', t => {245 t.plan(5)246 247 const fastify = Fastify()248 const ajv = new AJV()249 ajv.addSchema(schemaA)250 ajv.addSchema(schemaBRefToA)251 252 // the user must provide the schemas to fastify also253 fastify.addSchema(schemaA)254 fastify.addSchema(schemaBRefToA)255 256 fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => {257 t.pass('custom validator compiler called')258 return ajv.compile(schema)259 })260 261 fastify.post('/', {262 handler (req, reply) { reply.send({ foo: 1 }) },263 schema: {264 body: schemaCRefToB,265 response: {266 '2xx': ajv.getSchema('urn:schema:response').schema267 }268 }269 })270 271 fastify.inject({272 method: 'POST',273 url: '/',274 payload: { foo: 42 }275 }, (err, res) => {276 t.error(err)277 t.equal(res.statusCode, 200)278 })279 280 fastify.inject({281 method: 'POST',282 url: '/',283 payload: { foo: 'not a number' }284 }, (err, res) => {285 t.error(err)286 t.equal(res.statusCode, 400)287 })288})289 290test('Encapsulation', t => {291 t.plan(21)292 293 const fastify = Fastify()294 const ajv = new AJV()295 ajv.addSchema(schemaA)296 ajv.addSchema(schemaBRefToA)297 298 // the user must provide the schemas to fastify also299 fastify.addSchema(schemaA)300 fastify.addSchema(schemaBRefToA)301 302 fastify.register((instance, opts, done) => {303 const validator = ({ schema, method, url, httpPart }) => {304 t.pass('custom validator compiler called')305 return ajv.compile(schema)306 }307 instance.setValidatorCompiler(validator)308 instance.post('/one', {309 handler (req, reply) { reply.send({ foo: 'one' }) },310 schema: {311 body: ajv.getSchema('urn:schema:response').schema312 }313 })314 315 instance.register((instance, opts, done) => {316 instance.post('/two', {317 handler (req, reply) {318 t.same(instance.validatorCompiler, validator)319 reply.send({ foo: 'two' })320 },321 schema: {322 body: ajv.getSchema('urn:schema:response').schema323 }324 })325 326 const anotherValidator = ({ schema, method, url, httpPart }) => {327 return () => { return true } // always valid328 }329 instance.post('/three', {330 validatorCompiler: anotherValidator,331 handler (req, reply) {332 t.same(instance.validatorCompiler, validator, 'the route validator does not change the instance one')333 reply.send({ foo: 'three' })334 },335 schema: {336 body: ajv.getSchema('urn:schema:response').schema337 }338 })339 done()340 })341 done()342 })343 344 fastify.register((instance, opts, done) => {345 instance.post('/clean', function (req, reply) {346 t.equal(instance.validatorCompiler, undefined)347 reply.send({ foo: 'bar' })348 })349 done()350 })351 352 fastify.inject({353 method: 'POST',354 url: '/one',355 payload: { foo: 1 }356 }, (err, res) => {357 t.error(err)358 t.equal(res.statusCode, 200)359 t.same(res.json(), { foo: 'one' })360 })361 362 fastify.inject({363 method: 'POST',364 url: '/one',365 payload: { wrongFoo: 'bar' }366 }, (err, res) => {367 t.error(err)368 t.equal(res.statusCode, 400)369 })370 371 fastify.inject({372 method: 'POST',373 url: '/two',374 payload: { foo: 2 }375 }, (err, res) => {376 t.error(err)377 t.equal(res.statusCode, 200)378 t.same(res.json(), { foo: 'two' })379 })380 381 fastify.inject({382 method: 'POST',383 url: '/two',384 payload: { wrongFoo: 'bar' }385 }, (err, res) => {386 t.error(err)387 t.equal(res.statusCode, 400)388 })389 390 fastify.inject({391 method: 'POST',392 url: '/three',393 payload: { wrongFoo: 'but works' }394 }, (err, res) => {395 t.error(err)396 t.equal(res.statusCode, 200)397 t.same(res.json(), { foo: 'three' })398 })399 400 fastify.inject({401 method: 'POST',402 url: '/clean',403 payload: { wrongFoo: 'bar' }404 }, (err, res) => {405 t.error(err)406 t.equal(res.statusCode, 200)407 t.same(res.json(), { foo: 'bar' })408 })409})410 411test('Triple $ref with a simple $id', t => {412 t.plan(7)413 414 const fastify = Fastify()415 const ajv = new AJV()416 ajv.addSchema(schemaA)417 ajv.addSchema(schemaBRefToA)418 ajv.addSchema(schemaCRefToB)419 420 // the user must provide the schemas to fastify also421 fastify.addSchema(schemaA)422 fastify.addSchema(schemaBRefToA)423 fastify.addSchema(schemaCRefToB)424 425 fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => {426 t.pass('custom validator compiler called')427 return ajv.compile(schema)428 })429 430 fastify.post('/', {431 handler (req, reply) { reply.send({ foo: 105, bar: 'foo' }) },432 schema: {433 body: ajv.getSchema('urn:schema:request').schema,434 response: {435 '2xx': ajv.getSchema('urn:schema:response').schema436 }437 }438 })439 440 fastify.inject({441 method: 'POST',442 url: '/',443 payload: { foo: 43 }444 }, (err, res) => {445 t.error(err)446 t.equal(res.statusCode, 200)447 t.same(res.json(), { foo: 105 })448 })449 450 fastify.inject({451 method: 'POST',452 url: '/',453 payload: { fool: 'bar' }454 }, (err, res) => {455 t.error(err)456 t.equal(res.statusCode, 400)457 t.same(res.json().message, "body must have required property 'foo'")458 })459})460 461test('Extending schema', t => {462 t.plan(4)463 const fastify = Fastify()464 465 fastify.addSchema({466 $id: 'address.id',467 type: 'object',468 definitions: {469 address: {470 type: 'object',471 properties: {472 city: { type: 'string' },473 state: { type: 'string' }474 },475 required: ['city', 'state']476 }477 }478 })479 480 fastify.post('/', {481 handler (req, reply) { reply.send('works') },482 schema: {483 body: {484 type: 'object',485 properties: {486 billingAddress: { $ref: 'address.id#/definitions/address' },487 shippingAddress: {488 allOf: [489 { $ref: 'address.id#/definitions/address' },490 {491 type: 'object',492 properties: { type: { enum: ['residential', 'business'] } },493 required: ['type']494 }495 ]496 }497 }498 }499 }500 })501 502 fastify.inject({503 method: 'POST',504 url: '/',505 payload: {506 shippingAddress: {507 city: 'Forlì',508 state: 'FC'509 }510 }511 }, (err, res) => {512 t.error(err)513 t.equal(res.statusCode, 400)514 })515 516 fastify.inject({517 method: 'POST',518 url: '/',519 payload: {520 shippingAddress: {521 city: 'Forlì',522 state: 'FC',523 type: 'business'524 }525 }526 }, (err, res) => {527 t.error(err)528 t.equal(res.statusCode, 200)529 })530})531 532test('Should work with nested ids', t => {533 t.plan(6)534 const fastify = Fastify()535 536 fastify.addSchema({537 $id: 'test',538 type: 'object',539 properties: {540 id: { type: 'number' }541 }542 })543 544 fastify.addSchema({545 $id: 'greetings',546 type: 'string'547 })548 549 fastify.post('/:id', {550 handler (req, reply) { reply.send(typeof req.params.id) },551 schema: {552 params: { $ref: 'test#' },553 body: {554 type: 'object',555 properties: {556 hello: { $ref: 'greetings#' }557 }558 }559 }560 })561 562 fastify.inject({563 method: 'POST',564 url: '/123',565 payload: {566 hello: 'world'567 }568 }, (err, res) => {569 t.error(err)570 t.equal(res.statusCode, 200)571 t.equal(res.payload, 'number')572 })573 574 fastify.inject({575 method: 'POST',576 url: '/abc',577 payload: {578 hello: 'world'579 }580 }, (err, res) => {581 t.error(err)582 t.equal(res.statusCode, 400)583 t.equal(res.json().message, 'params/id must be number')584 })585})586 587test('Use the same schema across multiple routes', t => {588 t.plan(8)589 const fastify = Fastify()590 591 fastify.addSchema({592 $id: 'test',593 type: 'object',594 properties: {595 id: { type: 'number' }596 }597 })598 599 fastify.get('/first/:id', {600 handler (req, reply) { reply.send(typeof req.params.id) },601 schema: {602 params: { $ref: 'test#' }603 }604 })605 606 fastify.get('/second/:id', {607 handler (req, reply) { reply.send(typeof req.params.id) },608 schema: {609 params: { $ref: 'test#' }610 }611 })612 613 ;[614 '/first/123',615 '/second/123'616 ].forEach(url => {617 fastify.inject({618 url,619 method: 'GET'620 }, (err, res) => {621 t.error(err)622 t.equal(res.payload, 'number')623 })624 })625 626 ;[627 '/first/abc',628 '/second/abc'629 ].forEach(url => {630 fastify.inject({631 url,632 method: 'GET'633 }, (err, res) => {634 t.error(err)635 t.equal(res.statusCode, 400)636 })637 })638})639 640test('JSON Schema validation keywords', t => {641 t.plan(6)642 const fastify = Fastify()643 644 fastify.addSchema({645 $id: 'test',646 type: 'object',647 properties: {648 ip: {649 type: 'string',650 format: 'ipv4'651 }652 }653 })654 655 fastify.get('/:ip', {656 handler (req, reply) { reply.send(typeof req.params.ip) },657 schema: {658 params: { $ref: 'test#' }659 }660 })661 662 fastify.inject({663 method: 'GET',664 url: '/127.0.0.1'665 }, (err, res) => {666 t.error(err)667 t.equal(res.statusCode, 200)668 t.equal(res.payload, 'string')669 })670 671 fastify.inject({672 method: 'GET',673 url: '/localhost'674 }, (err, res) => {675 t.error(err)676 t.equal(res.statusCode, 400)677 t.same(res.json(), {678 statusCode: 400,679 code: 'FST_ERR_VALIDATION',680 error: 'Bad Request',681 message: 'params/ip must match format "ipv4"'682 })683 })684})685 686test('Nested id calls', t => {687 t.plan(6)688 const fastify = Fastify()689 690 fastify.addSchema({691 $id: 'test',692 type: 'object',693 properties: {694 ip: {695 type: 'string',696 format: 'ipv4'697 }698 }699 })700 701 fastify.addSchema({702 $id: 'hello',703 type: 'object',704 properties: {705 host: { $ref: 'test#' }706 }707 })708 709 fastify.post('/', {710 handler (req, reply) { reply.send(typeof req.body.host.ip) },711 schema: {712 body: { $ref: 'hello#' }713 }714 })715 716 fastify.inject({717 method: 'POST',718 url: '/',719 payload: { host: { ip: '127.0.0.1' } }720 }, (err, res) => {721 t.error(err)722 t.equal(res.statusCode, 200)723 t.equal(res.payload, 'string')724 })725 726 fastify.inject({727 method: 'POST',728 url: '/',729 payload: { host: { ip: 'localhost' } }730 }, (err, res) => {731 t.error(err)732 t.equal(res.statusCode, 400)733 t.same(res.json(), {734 error: 'Bad Request',735 message: 'body/host/ip must match format "ipv4"',736 statusCode: 400,737 code: 'FST_ERR_VALIDATION'738 })739 })740})741 742test('Use the same schema id in different places', t => {743 t.plan(2)744 const fastify = Fastify()745 746 fastify.addSchema({747 $id: 'test',748 type: 'object',749 properties: {750 id: { type: 'number' }751 }752 })753 754 fastify.post('/', {755 handler (req, reply) { reply.send({ id: req.body.id / 2 }) },756 schema: {757 body: { $ref: 'test#' },758 response: {759 200: { $ref: 'test#' }760 }761 }762 })763 764 fastify.inject({765 method: 'POST',766 url: '/',767 payload: { id: 42 }768 }, (err, res) => {769 t.error(err)770 t.same(res.json(), { id: 21 })771 })772})773 774test('Use shared schema and $ref with $id ($ref to $id)', t => {775 t.plan(5)776 const fastify = Fastify()777 778 fastify.addSchema({779 $id: 'http://foo/test',780 type: 'object',781 properties: {782 id: { type: 'number' }783 }784 })785 786 const body = {787 $id: 'http://foo/user',788 $schema: 'http://json-schema.org/draft-07/schema#',789 type: 'object',790 definitions: {791 address: {792 $id: '#address',793 type: 'object',794 properties: {795 city: { type: 'string' }796 }797 }798 },799 required: ['address'],800 properties: {801 test: { $ref: 'http://foo/test#' }, // to external802 address: { $ref: '#address' } // to local803 }804 }805 806 fastify.post('/', {807 handler (req, reply) { reply.send(req.body.test) },808 schema: {809 body,810 response: {811 200: { $ref: 'http://foo/test#' }812 }813 }814 })815 816 const id = Date.now()817 fastify.inject({818 method: 'POST',819 url: '/',820 payload: {821 address: { city: 'New Node' },822 test: { id }823 }824 }, (err, res) => {825 t.error(err)826 t.same(res.json(), { id })827 })828 829 fastify.inject({830 method: 'POST',831 url: '/',832 payload: { test: { id } }833 }, (err, res) => {834 t.error(err)835 t.equal(res.statusCode, 400)836 t.same(res.json(), {837 error: 'Bad Request',838 message: "body must have required property 'address'",839 statusCode: 400,840 code: 'FST_ERR_VALIDATION'841 })842 })843})844 845test('Use items with $ref', t => {846 t.plan(4)847 const fastify = Fastify()848 849 fastify.addSchema({850 $id: 'http://example.com/ref-to-external-validator.json',851 type: 'object',852 properties: {853 hello: { type: 'string' }854 }855 })856 857 const body = {858 type: 'array',859 items: { $ref: 'http://example.com/ref-to-external-validator.json#' }860 }861 862 fastify.post('/', {863 schema: { body },864 handler: (_, r) => { r.send('ok') }865 })866 867 fastify.inject({868 method: 'POST',869 url: '/',870 payload: [{ hello: 'world' }]871 }, (err, res) => {872 t.error(err)873 t.equal(res.payload, 'ok')874 })875 876 fastify.inject({877 method: 'POST',878 url: '/',879 payload: { hello: 'world' }880 }, (err, res) => {881 t.error(err)882 t.equal(res.statusCode, 400)883 })884})885 886test('Use $ref to /definitions', t => {887 t.plan(6)888 const fastify = Fastify()889 890 fastify.addSchema({891 $id: 'test',892 type: 'object',893 properties: {894 id: { type: 'number' }895 }896 })897 898 const body = {899 type: 'object',900 definitions: {901 address: {902 $id: '#otherId',903 type: 'object',904 properties: {905 city: { type: 'string' }906 }907 }908 },909 properties: {910 test: { $ref: 'test#' },911 address: { $ref: '#/definitions/address' }912 },913 required: ['address', 'test']914 }915 916 fastify.post('/', {917 schema: {918 body,919 response: {920 200: body921 }922 },923 handler: (req, reply) => {924 req.body.removeThis = 'it should not be serialized'925 reply.send(req.body)926 }927 })928 929 const payload = {930 address: { city: 'New Node' },931 test: { id: Date.now() }932 }933 fastify.inject({934 method: 'POST',935 url: '/',936 payload937 }, (err, res) => {938 t.error(err)939 t.equal(res.statusCode, 200)940 t.same(res.json(), payload)941 })942 943 fastify.inject({944 method: 'POST',945 url: '/',946 payload: {947 address: { city: 'New Node' },948 test: { id: 'wrong' }949 }950 }, (err, res) => {951 t.error(err)952 t.equal(res.statusCode, 400)953 t.same(res.json(), {954 error: 'Bad Request',955 message: 'body/test/id must be number',956 statusCode: 400,957 code: 'FST_ERR_VALIDATION'958 })959 })960})961 962test('Custom AJV settings - pt1', t => {963 t.plan(4)964 const fastify = Fastify()965 966 fastify.post('/', {967 schema: {968 body: {969 type: 'object',970 properties: {971 num: { type: 'integer' }972 }973 }974 },975 handler: (req, reply) => {976 t.equal(req.body.num, 12)977 reply.send(req.body)978 }979 })980 981 fastify.inject({982 method: 'POST',983 url: '/',984 payload: {985 num: '12'986 }987 }, (err, res) => {988 t.error(err)989 t.equal(res.statusCode, 200)990 t.same(res.json(), { num: 12 })991 })992})993 994test('Custom AJV settings - pt2', t => {995 t.plan(2)996 const fastify = Fastify({997 ajv: {998 customOptions: {999 coerceTypes: false1000 }1001 }1002 })1003 1004 fastify.post('/', {1005 schema: {1006 body: {1007 type: 'object',1008 properties: {1009 num: { type: 'integer' }1010 }1011 }1012 },1013 handler: (req, reply) => {1014 t.fail('the handler is not called because the "12" is not coerced to number')1015 }1016 })1017 1018 fastify.inject({1019 method: 'POST',1020 url: '/',1021 payload: {1022 num: '12'1023 }1024 }, (err, res) => {1025 t.error(err)1026 t.equal(res.statusCode, 400)1027 })1028})1029 1030test('Custom AJV settings on different parameters - pt1', t => {1031 t.plan(2)1032 const fastify = Fastify()1033 1034 fastify.setValidatorCompiler(customValidatorCompiler)1035 1036 fastify.post('/api/:id', {1037 schema: {1038 querystring: {1039 type: 'object',1040 properties: {1041 id: { type: 'integer' }1042 }1043 },1044 body: {1045 type: 'object',1046 properties: {1047 num: { type: 'number' }1048 },1049 required: ['num']1050 }1051 },1052 handler: (req, reply) => {1053 t.fail('the handler is not called because the "12" is not coerced to number')1054 }1055 })1056 1057 fastify.inject({1058 method: 'POST',1059 url: '/api/42',1060 payload: {1061 num: '12'1062 }1063 }, (err, res) => {1064 t.error(err)1065 t.equal(res.statusCode, 400)1066 })1067})1068 1069test('Custom AJV settings on different parameters - pt2', t => {1070 t.plan(4)1071 const fastify = Fastify()1072 1073 fastify.setValidatorCompiler(customValidatorCompiler)1074 1075 fastify.post('/api/:id', {1076 schema: {1077 params: {1078 type: 'object',1079 properties: {1080 id: { type: 'number' }1081 },1082 required: ['id']1083 },1084 body: {1085 type: 'object',1086 properties: {1087 num: { type: 'number' }1088 },1089 required: ['num']1090 }1091 },1092 handler: (req, reply) => {1093 t.same(typeof req.params.id, 'number')1094 t.same(typeof req.body.num, 'number')1095 t.same(req.params.id, 42)1096 t.same(req.body.num, 12)1097 }1098 })1099 1100 fastify.inject({1101 method: 'POST',1102 url: '/api/42',1103 payload: {1104 num: 121105 }1106 })1107})1108 1109test("The same $id in route's schema must not overwrite others", t => {1110 t.plan(4)1111 const fastify = Fastify()1112 1113 const UserSchema = Schema.object()1114 .id('http://mydomain.com/user')1115 .title('User schema')1116 .description('Contains all user fields')1117 .prop('id', Schema.integer())1118 .prop('username', Schema.string().minLength(4))1119 .prop('firstName', Schema.string().minLength(1))1120 .prop('lastName', Schema.string().minLength(1))1121 .prop('fullName', Schema.string().minLength(1))1122 .prop('email', Schema.string())1123 .prop('password', Schema.string().minLength(6))1124 .prop('bio', Schema.string())1125 1126 const userCreateSchema = UserSchema.only([1127 'username',1128 'firstName',1129 'lastName',1130 'email',1131 'bio',1132 'password',1133 'password_confirm'1134 ])1135 .required([1136 'username',1137 'firstName',1138 'lastName',1139 'email',1140 'bio',1141 'password'1142 ])1143 1144 const userPatchSchema = UserSchema.only([1145 'firstName',1146 'lastName',1147 'bio'1148 ])1149 1150 fastify1151 .patch('/user/:id', {1152 schema: { body: userPatchSchema },1153 handler: () => { return 'ok' }1154 })1155 .post('/user', {1156 schema: { body: userCreateSchema },1157 handler: () => { return 'ok' }1158 })1159 1160 fastify.inject({1161 method: 'POST',1162 url: '/user',1163 body: {}1164 }, (err, res) => {1165 t.error(err)1166 t.same(res.json().message, "body must have required property 'username'")1167 })1168 1169 fastify.inject({1170 url: '/user/1',1171 method: 'PATCH',1172 body: {}1173 }, (err, res) => {1174 t.error(err)1175 t.same(res.payload, 'ok')1176 })1177})1178 1179test('Custom validator compiler should not mutate schema', async t => {1180 t.plan(2)1181 class Headers { }1182 const fastify = Fastify()1183 1184 fastify.setValidatorCompiler(({ schema, method, url, httpPart }) => {1185 t.type(schema, Headers)1186 return () => { }1187 })1188 1189 fastify.get('/', {1190 schema: {1191 headers: new Headers()1192 }1193 }, () => { })1194 1195 await fastify.ready()1196})1197 1198test('Custom validator builder override by custom validator compiler', async t => {1199 t.plan(3)1200 const ajvDefaults = {