Comparing version 4.28.1 to 4.29.0
@@ -9,2 +9,30 @@ # V4 Migration Guide | ||
## Codemods | ||
### Fastify v4 Codemods | ||
To help with the upgrade, we’ve worked with the team at | ||
[Codemod](https://github.com/codemod-com/codemod) to | ||
publish codemods that will automatically update your code to many of | ||
the new APIs and patterns in Fastify v4. | ||
Run the following | ||
[migration recipe](https://go.codemod.com/fastify-4-migration-recipe) to | ||
automatically update your code to Fastify v4: | ||
``` | ||
npx codemod@latest fastify/4/migration-recipe | ||
``` | ||
This will run the following codemods: | ||
- [`fastify/4/remove-app-use`](https://go.codemod.com/fastify-4-remove-app-use) | ||
- [`fastify/4/reply-raw-access`](https://go.codemod.com/fastify-4-reply-raw-access) | ||
- [`fastify/4/wrap-routes-plugin`](https://go.codemod.com/fastify-4-wrap-routes-plugin) | ||
- [`fastify/4/await-register-calls`](https://go.codemod.com/fastify-4-await-register-calls) | ||
Each of these codemods automates the changes listed in the v4 migration guide. | ||
For a complete list of available Fastify codemods and further details, | ||
see [Codemod Registry](https://go.codemod.com/fastify). | ||
## Breaking Changes | ||
@@ -59,2 +87,8 @@ | ||
> **Note**: Codemod remove `app.use()` with: | ||
> | ||
> ```bash | ||
> npx codemod@latest fastify/4/remove-app-use | ||
> ``` | ||
### `reply.res` moved to `reply.raw` | ||
@@ -65,2 +99,8 @@ | ||
> **Note**: Codemod `reply.res` to `reply.raw` with: | ||
> | ||
> ```bash | ||
> npx codemod@latest fastify/4/reply-raw-access | ||
> ``` | ||
### Need to `return reply` to signal a "fork" of the promise chain | ||
@@ -111,2 +151,7 @@ | ||
``` | ||
> **Note**: Codemod synchronous route definitions with: | ||
> | ||
> ```bash | ||
> npx codemod@latest fastify/4/wrap-routes-plugin | ||
> ``` | ||
@@ -137,2 +182,9 @@ * use `await register(...)` | ||
> **Note**: Codemod 'await register(...)' with: | ||
> | ||
> ```bash | ||
> npx codemod@latest fastify/4/await-register-calls | ||
> ``` | ||
### Optional URL parameters | ||
@@ -139,0 +191,0 @@ |
@@ -46,3 +46,4 @@ <h1 align="center">Fastify</h1> | ||
* `body`: validates the body of the request if it is a POST, PUT, PATCH, | ||
TRACE, SEARCH, PROPFIND, PROPPATCH or LOCK method. | ||
TRACE, SEARCH, PROPFIND, PROPPATCH, COPY, MOVE, MKCOL, REPORT, MKCALENDAR | ||
or LOCK method. | ||
* `querystring` or `query`: validates the querystring. This can be a complete | ||
@@ -49,0 +50,0 @@ JSON Schema object, with the property `type` of `object` and `properties` |
@@ -238,2 +238,24 @@ <h1 align="center">Fastify</h1> | ||
For `body` schema, it is further possible to differentiate the schema per content | ||
type by nesting the schemas inside `content` property. The schema validation | ||
will be applied based on the `Content-Type` header in the request. | ||
```js | ||
fastify.post('/the/url', { | ||
schema: { | ||
body: { | ||
content: { | ||
'application/json': { | ||
schema: { type: 'object' } | ||
}, | ||
'text/plain': { | ||
schema: { type: 'string' } | ||
} | ||
// Other content types will not be validated | ||
} | ||
} | ||
} | ||
}, handler) | ||
``` | ||
*Note that Ajv will try to [coerce](https://ajv.js.org/coercion.html) the values | ||
@@ -240,0 +262,0 @@ to the types specified in your schema `type` keywords, both to pass the |
@@ -28,2 +28,3 @@ | ||
- [FSTDEP021](#FSTDEP021) | ||
- [FSTDEP022](#FSTDEP022) | ||
@@ -96,1 +97,2 @@ | ||
| <a id="FSTDEP021">FSTDEP021</a> | The `reply.redirect()` method has a new signature: `reply.redirect(url: string, code?: number)`. It will be enforced in `fastify@v5`'. | [#5483](https://github.com/fastify/fastify/pull/5483) | | ||
| <a id="FSTDEP022">FSTDEP022</a> | You are using the deprecated json shorthand schema on route %s. Specify full object schema instead. It will be removed in `fastify@v5` | [#5483](https://github.com/fastify/fastify/pull/0000) | |
'use strict' | ||
const VERSION = '4.28.1' | ||
const VERSION = '4.29.0' | ||
@@ -674,3 +674,3 @@ const Avvio = require('avvio') | ||
if (name === 'onClose') { | ||
this.onClose(fn) | ||
this.onClose(fn.bind(this)) | ||
} else if (name === 'onReady' || name === 'onListen' || name === 'onRoute') { | ||
@@ -677,0 +677,0 @@ this[kHooks].add(name, fn) |
@@ -31,3 +31,4 @@ 'use strict' | ||
if (method === 'POST' || method === 'PUT' || method === 'PATCH' || method === 'TRACE' || method === 'SEARCH' || | ||
method === 'PROPFIND' || method === 'PROPPATCH' || method === 'LOCK' || method === 'REPORT' || method === 'MKCALENDAR') { | ||
method === 'PROPFIND' || method === 'PROPPATCH' || method === 'LOCK' || method === 'COPY' || method === 'MOVE' || | ||
method === 'MKCOL' || method === 'REPORT' || method === 'MKCALENDAR') { | ||
if (contentType === undefined) { | ||
@@ -34,0 +35,0 @@ if ( |
@@ -605,6 +605,6 @@ 'use strict' | ||
// since Response contain status code, we need to update before | ||
// any action that used statusCode | ||
const isResponse = toString.call(payload) === '[object Response]' | ||
if (isResponse) { | ||
// since Response contain status code, headers and body, | ||
// we need to update the status, add the headers and use it's body as payload | ||
// before continuing | ||
if (toString.call(payload) === '[object Response]') { | ||
// https://developer.mozilla.org/en-US/docs/Web/API/Response/status | ||
@@ -614,2 +614,18 @@ if (typeof payload.status === 'number') { | ||
} | ||
// https://developer.mozilla.org/en-US/docs/Web/API/Response/headers | ||
if (typeof payload.headers === 'object' && typeof payload.headers.forEach === 'function') { | ||
for (const [headerName, headerValue] of payload.headers) { | ||
reply.header(headerName, headerValue) | ||
} | ||
} | ||
// https://developer.mozilla.org/en-US/docs/Web/API/Response/body | ||
if (payload.body !== null) { | ||
if (payload.bodyUsed) { | ||
throw new FST_ERR_REP_RESPONSE_BODY_CONSUMED() | ||
} | ||
} | ||
// Keep going, body is either null or ReadableStream | ||
payload = payload.body | ||
} | ||
@@ -661,22 +677,2 @@ const statusCode = res.statusCode | ||
// Response | ||
if (isResponse) { | ||
// https://developer.mozilla.org/en-US/docs/Web/API/Response/headers | ||
if (typeof payload.headers === 'object' && typeof payload.headers.forEach === 'function') { | ||
for (const [headerName, headerValue] of payload.headers) { | ||
reply.header(headerName, headerValue) | ||
} | ||
} | ||
// https://developer.mozilla.org/en-US/docs/Web/API/Response/body | ||
if (payload.body != null) { | ||
if (payload.bodyUsed) { | ||
throw new FST_ERR_REP_RESPONSE_BODY_CONSUMED() | ||
} | ||
// Response.body always a ReadableStream | ||
sendWebStream(payload.body, res, reply) | ||
} | ||
return | ||
} | ||
if (typeof payload !== 'string' && !Buffer.isBuffer(payload)) { | ||
@@ -732,3 +728,3 @@ throw new FST_ERR_REP_INVALID_PAYLOAD_TYPE(typeof payload) | ||
errorLogged = true | ||
logStreamError(reply.log, err, res) | ||
logStreamError(reply.log, err, reply) | ||
} | ||
@@ -735,0 +731,0 @@ res.destroy() |
@@ -369,3 +369,3 @@ 'use strict' | ||
if (!context[kRouteByFastify]) { | ||
const isDuplicatedRoute = error.message.includes(`Method '${opts.method}' already declared for route '${opts.url}'`) | ||
const isDuplicatedRoute = error.message.includes(`Method '${opts.method}' already declared for route`) | ||
if (isDuplicatedRoute) { | ||
@@ -411,3 +411,3 @@ throw new FST_ERR_DUPLICATED_ROUTE(opts.method, opts.url) | ||
if (opts.schema) { | ||
context.schema = normalizeSchema(context.schema, this.initialConfig) | ||
context.schema = normalizeSchema(opts, context.schema, this.initialConfig) | ||
@@ -414,0 +414,0 @@ const schemaController = this[kSchemaController] |
@@ -8,2 +8,5 @@ 'use strict' | ||
const { | ||
FSTDEP022 | ||
} = require('./warnings') | ||
const { | ||
FST_ERR_SCH_MISSING_ID, | ||
@@ -58,3 +61,3 @@ FST_ERR_SCH_ALREADY_PRESENT, | ||
function normalizeSchema (routeSchemas, serverOptions) { | ||
function normalizeSchema (opts, routeSchemas, serverOptions) { | ||
if (routeSchemas[kSchemaVisited]) { | ||
@@ -78,3 +81,16 @@ return routeSchemas | ||
if (schema && !isCustomSchemaPrototype(schema)) { | ||
routeSchemas[key] = getSchemaAnyway(schema, serverOptions.jsonShorthand) | ||
if (key === 'body' && schema.content) { | ||
const contentProperty = schema.content | ||
const keys = Object.keys(contentProperty) | ||
for (let i = 0; i < keys.length; i++) { | ||
const contentType = keys[i] | ||
const contentSchema = contentProperty[contentType].schema | ||
if (!contentSchema) { | ||
throw new FST_ERR_SCH_CONTENT_MISSING_SCHEMA(contentType) | ||
} | ||
routeSchemas.body.content[contentType].schema = getSchemaAnyway(opts.url, contentSchema, serverOptions.jsonShorthand) | ||
} | ||
continue | ||
} | ||
routeSchemas[key] = getSchemaAnyway(opts.url, schema, serverOptions.jsonShorthand) | ||
} | ||
@@ -101,3 +117,3 @@ } | ||
} | ||
routeSchemas.response[code].content[mediaName].schema = getSchemaAnyway(contentProperty[mediaName].schema, serverOptions.jsonShorthand) | ||
routeSchemas.response[code].content[mediaName].schema = getSchemaAnyway(opts.url, contentProperty[mediaName].schema, serverOptions.jsonShorthand) | ||
if (i === keys.length - 1) { | ||
@@ -110,3 +126,3 @@ hasContentMultipleContentTypes = true | ||
if (!hasContentMultipleContentTypes) { | ||
routeSchemas.response[code] = getSchemaAnyway(routeSchemas.response[code], serverOptions.jsonShorthand) | ||
routeSchemas.response[code] = getSchemaAnyway(opts.url, routeSchemas.response[code], serverOptions.jsonShorthand) | ||
} | ||
@@ -137,5 +153,6 @@ } | ||
function getSchemaAnyway (schema, jsonShorthand) { | ||
function getSchemaAnyway (url, schema, jsonShorthand) { | ||
if (!jsonShorthand || schema.$ref || schema.oneOf || schema.allOf || schema.anyOf || schema.$merge || schema.$patch) return schema | ||
if (!schema.type && !schema.properties) { | ||
FSTDEP022(url) | ||
return { | ||
@@ -142,0 +159,0 @@ type: 'object', |
@@ -75,3 +75,4 @@ 'use strict' | ||
} | ||
if (Object.prototype.hasOwnProperty.call(listenOptions, 'host') === false) { | ||
if (Object.prototype.hasOwnProperty.call(listenOptions, 'host') === false || | ||
listenOptions.host == null) { | ||
listenOptions.host = host | ||
@@ -78,0 +79,0 @@ } |
@@ -90,3 +90,13 @@ 'use strict' | ||
if (schema.body) { | ||
context[bodySchema] = compile({ schema: schema.body, method, url, httpPart: 'body' }) | ||
const contentProperty = schema.body.content | ||
if (contentProperty) { | ||
const contentTypeSchemas = {} | ||
for (const contentType of Object.keys(contentProperty)) { | ||
const contentSchema = contentProperty[contentType].schema | ||
contentTypeSchemas[contentType] = compile({ schema: contentSchema, method, url, httpPart: 'body', contentType }) | ||
} | ||
context[bodySchema] = contentTypeSchemas | ||
} else { | ||
context[bodySchema] = compile({ schema: schema.body, method, url, httpPart: 'body' }) | ||
} | ||
} else if (Object.prototype.hasOwnProperty.call(schema, 'body')) { | ||
@@ -144,3 +154,14 @@ FSTWRN001('body', method, url) | ||
if (runExecution || !execution.skipBody) { | ||
const body = validateParam(context[bodySchema], request, 'body') | ||
let validatorFunction = null | ||
if (typeof context[bodySchema] === 'function') { | ||
validatorFunction = context[bodySchema] | ||
} else if (context[bodySchema]) { | ||
// TODO: add request.contentType and reuse it here | ||
const contentType = request.headers['content-type']?.split(';', 1)[0] | ||
const contentSchema = context[bodySchema][contentType] | ||
if (contentSchema) { | ||
validatorFunction = contentSchema | ||
} | ||
} | ||
const body = validateParam(validatorFunction, request, 'body') | ||
if (body) { | ||
@@ -147,0 +168,0 @@ if (typeof body.then !== 'function') { |
@@ -90,2 +90,7 @@ 'use strict' | ||
const FSTDEP022 = createDeprecation({ | ||
code: 'FSTDEP021', | ||
message: 'You are using the deprecated json shorthand schema on route %s. Specify full object schema instead. It will be removed in `fastify@v5`' | ||
}) | ||
const FSTWRN001 = createWarning({ | ||
@@ -123,4 +128,5 @@ name: 'FastifyWarning', | ||
FSTDEP021, | ||
FSTDEP022, | ||
FSTWRN001, | ||
FSTWRN002 | ||
} |
{ | ||
"name": "fastify", | ||
"version": "4.28.1", | ||
"version": "4.29.0", | ||
"description": "Fast and low overhead web framework, for Node.js", | ||
@@ -5,0 +5,0 @@ "main": "fastify.js", |
@@ -17,2 +17,4 @@ # Sponsors | ||
- [Mercedes-Benz Group](https://github.com/mercedes-benz) | ||
- [Val Town, Inc.](https://opencollective.com/valtown) | ||
- [Handsontable - JavaScript Data Grid](https://handsontable.com/docs/react-data-grid/?utm_source=Fastify_GH&utm_medium=sponsorship&utm_campaign=library_sponsorship_2024) | ||
@@ -19,0 +21,0 @@ ## Tier 2 |
@@ -13,7 +13,10 @@ 'use strict' | ||
test('close callback', t => { | ||
t.plan(4) | ||
t.plan(7) | ||
const fastify = Fastify() | ||
fastify.addHook('onClose', onClose) | ||
function onClose (instance, done) { | ||
t.type(fastify, this) | ||
t.type(fastify, instance) | ||
t.equal(fastify, this) | ||
t.equal(fastify, instance) | ||
done() | ||
@@ -20,0 +23,0 @@ } |
@@ -15,3 +15,6 @@ 'use strict' | ||
handler: function (req, reply) { | ||
reply.code(204).send() | ||
reply.code(204) | ||
.header('location', req.headers.destination) | ||
.header('body', req.body.toString()) | ||
.send() | ||
} | ||
@@ -30,3 +33,3 @@ }) | ||
test('request - copy', t => { | ||
t.plan(2) | ||
t.plan(4) | ||
sget({ | ||
@@ -36,6 +39,10 @@ url: `http://localhost:${fastify.server.address().port}/test.txt`, | ||
headers: { | ||
Destination: '/test2.txt' | ||
} | ||
}, (err, response, body) => { | ||
destination: '/test2.txt', | ||
'Content-Type': 'text/plain' | ||
}, | ||
body: '/test3.txt' | ||
}, (err, response) => { | ||
t.error(err) | ||
t.equal(response.headers.location, '/test2.txt') | ||
t.equal(response.headers.body, '/test3.txt') | ||
t.equal(response.statusCode, 204) | ||
@@ -42,0 +49,0 @@ }) |
@@ -66,3 +66,3 @@ 'use strict' | ||
test('build schema - payload schema', t => { | ||
test('build schema - body schema', t => { | ||
t.plan(1) | ||
@@ -83,2 +83,28 @@ const opts = { | ||
test('build schema - body with multiple content type schemas', t => { | ||
t.plan(2) | ||
const opts = { | ||
schema: { | ||
body: { | ||
content: { | ||
'application/json': { | ||
schema: { | ||
type: 'object', | ||
properties: { | ||
hello: { type: 'string' } | ||
} | ||
} | ||
}, | ||
'text/plain': { | ||
schema: { type: 'string' } | ||
} | ||
} | ||
} | ||
} | ||
} | ||
validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) | ||
t.type(opts[symbols.bodySchema]['application/json'], 'function') | ||
t.type(opts[symbols.bodySchema]['text/plain'], 'function') | ||
}) | ||
test('build schema - avoid repeated normalize schema', t => { | ||
@@ -99,6 +125,6 @@ t.plan(3) | ||
} | ||
opts.schema = normalizeSchema(opts.schema, serverConfig) | ||
opts.schema = normalizeSchema({}, opts.schema, serverConfig) | ||
t.not(kSchemaVisited, undefined) | ||
t.equal(opts.schema[kSchemaVisited], true) | ||
t.equal(opts.schema, normalizeSchema(opts.schema, serverConfig)) | ||
t.equal(opts.schema, normalizeSchema({}, opts.schema, serverConfig)) | ||
}) | ||
@@ -121,3 +147,3 @@ | ||
} | ||
opts.schema = normalizeSchema(opts.schema, serverConfig) | ||
opts.schema = normalizeSchema({}, opts.schema, serverConfig) | ||
validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) | ||
@@ -140,3 +166,3 @@ t.type(opts[symbols.querystringSchema].schema.type, 'string') | ||
} | ||
opts.schema = normalizeSchema(opts.schema, serverConfig) | ||
opts.schema = normalizeSchema({}, opts.schema, serverConfig) | ||
validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) | ||
@@ -176,3 +202,3 @@ t.type(opts[symbols.querystringSchema].schema.type, 'string') | ||
} | ||
opts.schema = normalizeSchema(opts.schema, serverConfig) | ||
opts.schema = normalizeSchema({}, opts.schema, serverConfig) | ||
validation.compileSchemasForValidation(opts, ({ schema, method, url, httpPart }) => ajv.compile(schema)) | ||
@@ -205,3 +231,3 @@ t.type(opts[symbols.querystringSchema].schema.type, 'string') | ||
} | ||
opts.schema = normalizeSchema(opts.schema, serverConfig) | ||
opts.schema = normalizeSchema({}, opts.schema, serverConfig) | ||
} catch (err) { | ||
@@ -364,5 +390,5 @@ t.equal(err.code, 'FST_ERR_SCH_DUPLICATE') | ||
testCases.forEach((testCase) => { | ||
const result = normalizeSchema(testCase.schema, { jsonShorthand: true }) | ||
const result = normalizeSchema({}, testCase.schema, { jsonShorthand: true }) | ||
testCase.assertions(result) | ||
}) | ||
}) |
@@ -102,1 +102,37 @@ 'use strict' | ||
}) | ||
test('listen works with undefined host', async t => { | ||
const doNotWarn = () => { | ||
t.fail('should not be deprecated') | ||
} | ||
process.on('warning', doNotWarn) | ||
const fastify = Fastify() | ||
t.teardown(fastify.close.bind(fastify)) | ||
t.teardown(() => { | ||
fastify.close() | ||
process.removeListener('warning', doNotWarn) | ||
}) | ||
await fastify.listen({ host: undefined, port: 0 }) | ||
const address = fastify.server.address() | ||
t.equal(address.address, localhost) | ||
t.ok(address.port > 0) | ||
}) | ||
test('listen works with null host', async t => { | ||
const doNotWarn = () => { | ||
t.fail('should not be deprecated') | ||
} | ||
process.on('warning', doNotWarn) | ||
const fastify = Fastify() | ||
t.teardown(fastify.close.bind(fastify)) | ||
t.teardown(() => { | ||
fastify.close() | ||
process.removeListener('warning', doNotWarn) | ||
}) | ||
await fastify.listen({ host: null, port: 0 }) | ||
const address = fastify.server.address() | ||
t.equal(address.address, localhost) | ||
t.ok(address.port > 0) | ||
}) |
@@ -15,3 +15,3 @@ 'use strict' | ||
handler: function (req, reply) { | ||
reply.code(201).send() | ||
reply.code(201).header('body', req.body.toString()).send() | ||
} | ||
@@ -30,8 +30,11 @@ }) | ||
test('request - mkcol', t => { | ||
t.plan(2) | ||
t.plan(3) | ||
sget({ | ||
url: `http://localhost:${fastify.server.address().port}/test/`, | ||
method: 'MKCOL' | ||
}, (err, response, body) => { | ||
method: 'MKCOL', | ||
headers: { 'Content-Type': 'text/plain' }, | ||
body: '/test.txt' | ||
}, (err, response) => { | ||
t.error(err) | ||
t.equal(response.headers.body, '/test.txt') | ||
t.equal(response.statusCode, 201) | ||
@@ -38,0 +41,0 @@ }) |
@@ -15,5 +15,5 @@ 'use strict' | ||
handler: function (req, reply) { | ||
const destination = req.headers.destination | ||
reply.code(201) | ||
.header('location', destination) | ||
.header('location', req.headers.destination) | ||
.header('body', req.body.toString()) | ||
.send() | ||
@@ -33,3 +33,3 @@ } | ||
test('request - move', t => { | ||
t.plan(3) | ||
t.plan(4) | ||
sget({ | ||
@@ -39,10 +39,13 @@ url: `http://localhost:${fastify.server.address().port}/test.txt`, | ||
headers: { | ||
Destination: '/test2.txt' | ||
} | ||
}, (err, response, body) => { | ||
destination: '/test2.txt', | ||
'Content-Type': 'text/plain' | ||
}, | ||
body: '/test3.txt' | ||
}, (err, response) => { | ||
t.error(err) | ||
t.equal(response.statusCode, 201) | ||
t.equal(response.headers.location, '/test2.txt') | ||
t.equal(response.headers.body, '/test3.txt') | ||
}) | ||
}) | ||
}) |
@@ -143,1 +143,28 @@ 'use strict' | ||
}) | ||
test('Adding manually HEAD route after GET with the same path throws Fastify duplicated route instance error', t => { | ||
t.plan(1) | ||
const fastify = Fastify() | ||
fastify.route({ | ||
method: 'GET', | ||
path: '/:param1', | ||
handler: (req, reply) => { | ||
reply.send({ hello: 'world' }) | ||
} | ||
}) | ||
try { | ||
fastify.route({ | ||
method: 'HEAD', | ||
path: '/:param2', | ||
handler: (req, reply) => { | ||
reply.send({ hello: 'world' }) | ||
} | ||
}) | ||
t.fail('Should throw fastify duplicated route declaration') | ||
} catch (error) { | ||
t.equal(error.code, 'FST_ERR_DUPLICATED_ROUTE') | ||
} | ||
}) |
@@ -193,2 +193,23 @@ 'use strict' | ||
test('Should throw if schema is missing for content type', t => { | ||
t.plan(2) | ||
const fastify = Fastify() | ||
fastify.post('/', { | ||
handler: echoBody, | ||
schema: { | ||
body: { | ||
content: { | ||
'application/json': {} | ||
} | ||
} | ||
} | ||
}) | ||
fastify.ready(err => { | ||
t.equal(err.code, 'FST_ERR_SCH_CONTENT_MISSING_SCHEMA') | ||
t.equal(err.message, "Schema is missing for the content type 'application/json'") | ||
}) | ||
}) | ||
test('Should throw of the schema does not exists in output', t => { | ||
@@ -195,0 +216,0 @@ t.plan(2) |
@@ -108,2 +108,138 @@ 'use strict' | ||
test('Different schema per content type', t => { | ||
t.plan(12) | ||
const fastify = Fastify() | ||
fastify.addContentTypeParser('application/octet-stream', { | ||
parseAs: 'buffer' | ||
}, async function (_, payload) { | ||
return payload | ||
}) | ||
fastify.post('/', { | ||
schema: { | ||
body: { | ||
content: { | ||
'application/json': { | ||
schema: schemaArtist | ||
}, | ||
'application/octet-stream': { | ||
schema: {} // Skip validation | ||
}, | ||
'text/plain': { | ||
schema: { type: 'string' } | ||
} | ||
} | ||
} | ||
} | ||
}, async function (req, reply) { | ||
return reply.send(req.body) | ||
}) | ||
fastify.inject({ | ||
url: '/', | ||
method: 'POST', | ||
headers: { 'Content-Type': 'application/json' }, | ||
body: { | ||
name: 'michelangelo', | ||
work: 'sculptor, painter, architect and poet' | ||
} | ||
}, (err, res) => { | ||
t.error(err) | ||
t.same(JSON.parse(res.payload).name, 'michelangelo') | ||
t.equal(res.statusCode, 200) | ||
}) | ||
fastify.inject({ | ||
url: '/', | ||
method: 'POST', | ||
headers: { 'Content-Type': 'application/json' }, | ||
body: { name: 'michelangelo' } | ||
}, (err, res) => { | ||
t.error(err) | ||
t.same(res.json(), { statusCode: 400, code: 'FST_ERR_VALIDATION', error: 'Bad Request', message: "body must have required property 'work'" }) | ||
t.equal(res.statusCode, 400) | ||
}) | ||
fastify.inject({ | ||
url: '/', | ||
method: 'POST', | ||
headers: { 'Content-Type': 'application/octet-stream' }, | ||
body: Buffer.from('AAAAAAAA') | ||
}, (err, res) => { | ||
t.error(err) | ||
t.same(res.payload, 'AAAAAAAA') | ||
t.equal(res.statusCode, 200) | ||
}) | ||
fastify.inject({ | ||
url: '/', | ||
method: 'POST', | ||
headers: { 'Content-Type': 'text/plain' }, | ||
body: 'AAAAAAAA' | ||
}, (err, res) => { | ||
t.error(err) | ||
t.same(res.payload, 'AAAAAAAA') | ||
t.equal(res.statusCode, 200) | ||
}) | ||
}) | ||
test('Skip validation if no schema for content type', t => { | ||
t.plan(3) | ||
const fastify = Fastify() | ||
fastify.post('/', { | ||
schema: { | ||
body: { | ||
content: { | ||
'application/json': { | ||
schema: schemaArtist | ||
} | ||
// No schema for 'text/plain' | ||
} | ||
} | ||
} | ||
}, async function (req, reply) { | ||
return reply.send(req.body) | ||
}) | ||
fastify.inject({ | ||
url: '/', | ||
method: 'POST', | ||
headers: { 'Content-Type': 'text/plain' }, | ||
body: 'AAAAAAAA' | ||
}, (err, res) => { | ||
t.error(err) | ||
t.same(res.payload, 'AAAAAAAA') | ||
t.equal(res.statusCode, 200) | ||
}) | ||
}) | ||
test('Skip validation if no content type schemas', t => { | ||
t.plan(3) | ||
const fastify = Fastify() | ||
fastify.post('/', { | ||
schema: { | ||
body: { | ||
content: { | ||
// No schemas | ||
} | ||
} | ||
} | ||
}, async function (req, reply) { | ||
return reply.send(req.body) | ||
}) | ||
fastify.inject({ | ||
url: '/', | ||
method: 'POST', | ||
headers: { 'Content-Type': 'text/plain' }, | ||
body: 'AAAAAAAA' | ||
}, (err, res) => { | ||
t.error(err) | ||
t.same(res.payload, 'AAAAAAAA') | ||
t.equal(res.statusCode, 200) | ||
}) | ||
}) | ||
test('External AJV instance', t => { | ||
@@ -110,0 +246,0 @@ t.plan(5) |
@@ -25,5 +25,5 @@ 'use strict' | ||
fastify.get('/', () => {}) | ||
t.fail('Should throw on duplicated route declaration') | ||
t.fail('Should throw fastify duplicated route declaration') | ||
} catch (error) { | ||
t.equal(error.message, "Method 'GET' already declared for route '/'") | ||
t.equal(error.code, 'FST_ERR_DUPLICATED_ROUTE') | ||
} | ||
@@ -30,0 +30,0 @@ }) |
@@ -154,3 +154,4 @@ import { FastifyError } from '@fastify/error' | ||
server.addHook('onClose', (instance, done) => { | ||
server.addHook('onClose', function (instance, done) { | ||
expectType<FastifyInstance>(this) | ||
expectType<FastifyInstance>(instance) | ||
@@ -240,3 +241,4 @@ expectAssignable<(err?: FastifyError) => void>(done) | ||
server.addHook('onClose', async (instance) => { | ||
server.addHook('onClose', async function (instance) { | ||
expectType<FastifyInstance>(this) | ||
expectType<FastifyInstance>(instance) | ||
@@ -243,0 +245,0 @@ }) |
@@ -22,3 +22,10 @@ import { FastifyError } from '@fastify/error' | ||
bar: number; | ||
includeMessage?: boolean; | ||
} | ||
interface FastifyRequest<RouteGeneric, RawServer, RawRequest, SchemaCompiler, TypeProvider, ContextConfig, Logger, RequestType> { | ||
message: ContextConfig extends { includeMessage: true } | ||
? string | ||
: null; | ||
} | ||
} | ||
@@ -40,2 +47,18 @@ | ||
fastify().get( | ||
'/', | ||
{ config: { foo: 'bar', bar: 100, includeMessage: true } }, | ||
(req) => { | ||
expectType<string>(req.message) | ||
} | ||
) | ||
fastify().get( | ||
'/', | ||
{ config: { foo: 'bar', bar: 100, includeMessage: false } }, | ||
(req) => { | ||
expectType<null>(req.message) | ||
} | ||
) | ||
type LowerCaseHTTPMethods = 'get' | 'post' | 'put' | 'patch' | 'head' | 'delete' | 'options'; | ||
@@ -42,0 +65,0 @@ |
@@ -23,2 +23,21 @@ import { expectAssignable } from 'tsd' | ||
expectAssignable<FastifyInstance>(server.post( | ||
'/multiple-content-schema', | ||
{ | ||
schema: { | ||
body: { | ||
content: { | ||
'application/json': { | ||
schema: { type: 'object' } | ||
}, | ||
'text/plain': { | ||
schema: { type: 'string' } | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
() => { } | ||
)) | ||
expectAssignable<FastifyInstance>(server.get( | ||
@@ -25,0 +44,0 @@ '/empty-schema', |
@@ -65,2 +65,77 @@ 'use strict' | ||
test('should response with a Response 204', async (t) => { | ||
t.plan(3) | ||
const fastify = Fastify() | ||
fastify.get('/', function (request, reply) { | ||
reply.send(new Response(null, { | ||
status: 204, | ||
headers: { | ||
hello: 'world' | ||
} | ||
})) | ||
}) | ||
const { | ||
statusCode, | ||
headers, | ||
body | ||
} = await fastify.inject({ method: 'GET', path: '/' }) | ||
t.equal(statusCode, 204) | ||
t.equal(body, '') | ||
t.equal(headers.hello, 'world') | ||
}) | ||
test('should response with a Response 304', async (t) => { | ||
t.plan(3) | ||
const fastify = Fastify() | ||
fastify.get('/', function (request, reply) { | ||
reply.send(new Response(null, { | ||
status: 304, | ||
headers: { | ||
hello: 'world' | ||
} | ||
})) | ||
}) | ||
const { | ||
statusCode, | ||
headers, | ||
body | ||
} = await fastify.inject({ method: 'GET', path: '/' }) | ||
t.equal(statusCode, 304) | ||
t.equal(body, '') | ||
t.equal(headers.hello, 'world') | ||
}) | ||
test('should response with a Response without body', async (t) => { | ||
t.plan(3) | ||
const fastify = Fastify() | ||
fastify.get('/', function (request, reply) { | ||
reply.send(new Response(null, { | ||
status: 200, | ||
headers: { | ||
hello: 'world' | ||
} | ||
})) | ||
}) | ||
const { | ||
statusCode, | ||
headers, | ||
body | ||
} = await fastify.inject({ method: 'GET', path: '/' }) | ||
t.equal(statusCode, 200) | ||
t.equal(body, '') | ||
t.equal(headers.hello, 'world') | ||
}) | ||
test('able to use in onSend hook - ReadableStream', async (t) => { | ||
@@ -67,0 +142,0 @@ t.plan(4) |
@@ -785,2 +785,3 @@ import { Readable } from 'stream' | ||
( | ||
this: FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>, | ||
instance: FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>, | ||
@@ -799,2 +800,3 @@ done: HookHandlerDoneFunction | ||
( | ||
this: FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider>, | ||
instance: FastifyInstance<RawServer, RawRequest, RawReply, Logger, TypeProvider> | ||
@@ -801,0 +803,0 @@ ): Promise<unknown>; |
import { FastifyError } from '@fastify/error' | ||
import { ConstraintStrategy } from 'find-my-way' | ||
import { FastifyRequestContext } from './context' | ||
import { FastifyContextConfig } from './context' | ||
import { onErrorMetaHookHandler, onRequestAbortMetaHookHandler, onRequestMetaHookHandler, onResponseMetaHookHandler, onSendMetaHookHandler, onTimeoutMetaHookHandler, preHandlerMetaHookHandler, preParsingMetaHookHandler, preSerializationMetaHookHandler, preValidationMetaHookHandler } from './hooks' | ||
@@ -55,3 +55,3 @@ import { FastifyInstance } from './instance' | ||
logLevel?: LogLevel; | ||
config?: Omit<FastifyRequestContext<ContextConfig>['config'], 'url' | 'method'>; | ||
config?: FastifyContextConfig & ContextConfig; | ||
version?: string; | ||
@@ -58,0 +58,0 @@ constraints?: RouteConstraint, |
Sorry, the diff of this file is too big to display
2459906
336
59245