UNPKG

fastify

Version:

Fast and low overhead web framework, for Node.js

579 lines (530 loc) 24.6 kB
import * as http from 'node:http' import { FastifyError } from '@fastify/error' import { expect } from 'tstyche' import fastify, { ContextConfigDefault, FastifyContextConfig, FastifyInstance, FastifyPluginOptions, FastifyReply, FastifyRequest, FastifySchema, FastifyTypeProviderDefault, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerDefault, RegisterOptions, RouteOptions, preCloseAsyncHookHandler, preCloseHookHandler } from '../../fastify.js' import { DoneFuncWithErrOrRes, HookHandlerDoneFunction, RequestPayload, preHandlerAsyncHookHandler } from '../../types/hooks.js' import { FastifyRouteConfig, RouteGenericInterface } from '../../types/route.js' const server = fastify() // Test payload generic pass through for preSerialization and onSend type TestPayloadType = { foo: string; bar: number; } // Synchronous Tests server.addHook('onRequest', function (request, reply, done) { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() expect(done).type.toBeAssignableTo<(err?: FastifyError) => void>() expect(done).type.toBeAssignableTo<(err?: NodeJS.ErrnoException) => void>() expect(done(new Error())).type.toBe<void>() }) server.addHook('preParsing', function (request, reply, payload, done) { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() expect(payload).type.toBe<RequestPayload>() expect(done).type.toBeAssignableTo<(err?: FastifyError) => void>() expect(done).type.toBeAssignableTo<(err?: NodeJS.ErrnoException) => void>() expect(done(new Error())).type.toBe<void>() }) server.addHook('preValidation', function (request, reply, done) { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() expect(done).type.toBeAssignableTo<(err?: FastifyError) => void>() expect(done).type.toBeAssignableTo<(err?: NodeJS.ErrnoException) => void>() expect(done(new Error())).type.toBe<void>() }) server.addHook('preHandler', function (request, reply, done) { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() expect(done).type.toBeAssignableTo<(err?: FastifyError) => void>() expect(done).type.toBeAssignableTo<(err?: NodeJS.ErrnoException) => void>() expect(done(new Error())).type.toBe<void>() }) server.addHook<TestPayloadType>('preSerialization', function (request, reply, payload, done) { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() expect(payload).type.toBe<TestPayloadType>() // we expect this to be unknown when not specified like in the previous test expect(done(new Error())).type.toBe<void>() expect(done(null, 'foobar')).type.toBe<void>() expect(done()).type.toBe<void>() expect(done).type.not.toBeCallableWith(new Error(), 'foobar') }) server.addHook<TestPayloadType>('onSend', function (request, reply, payload, done) { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() expect(payload).type.toBe<TestPayloadType>() expect(done(new Error())).type.toBe<void>() expect(done(null, 'foobar')).type.toBe<void>() expect(done()).type.toBe<void>() expect(done).type.not.toBeCallableWith(new Error(), 'foobar') }) server.addHook('onResponse', function (request, reply, done) { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() expect(done).type.toBeAssignableTo<(err?: FastifyError) => void>() expect(done).type.toBeAssignableTo<(err?: NodeJS.ErrnoException) => void>() expect(done(new Error())).type.toBe<void>() }) server.addHook('onTimeout', function (request, reply, done) { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() expect(done).type.toBeAssignableTo<(err?: FastifyError) => void>() expect(done).type.toBeAssignableTo<(err?: NodeJS.ErrnoException) => void>() expect(done(new Error())).type.toBe<void>() }) server.addHook('onError', function (request, reply, error, done) { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() expect(error).type.toBe<FastifyError>() expect(done).type.toBe<() => void>() expect(done()).type.toBe<void>() }) server.addHook('onRequestAbort', function (request, done) { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() expect(done).type.toBeAssignableTo<(err?: FastifyError) => void>() expect(done).type.toBeAssignableTo<(err?: NodeJS.ErrnoException) => void>() expect(done(new Error())).type.toBe<void>() }) server.addHook('onRoute', function (opts) { expect(this).type.toBe<FastifyInstance>() expect(opts).type.toBe<RouteOptions & { routePath: string; path: string; prefix: string }>() }) server.addHook('onRegister', function (instance, opts) { expect(this).type.toBe<FastifyInstance>() expect(instance).type.toBe<FastifyInstance>() expect(opts).type.toBe<RegisterOptions & FastifyPluginOptions>() }) server.addHook('onReady', function (done) { expect(this).type.toBe<FastifyInstance>() expect(done).type.toBeAssignableTo<(err?: FastifyError) => void>() expect(done).type.toBeAssignableTo<(err?: NodeJS.ErrnoException) => void>() expect(done(new Error())).type.toBe<void>() }) server.addHook('onListen', function (done) { expect(this).type.toBe<FastifyInstance>() expect(done).type.toBeAssignableTo<(err?: FastifyError) => void>() expect(done).type.toBeAssignableTo<(err?: NodeJS.ErrnoException) => void>() }) server.addHook('onClose', function (instance, done) { expect(this).type.toBe<FastifyInstance>() expect(instance).type.toBe<FastifyInstance>() expect(done).type.toBeAssignableTo<(err?: FastifyError) => void>() expect(done).type.toBeAssignableTo<(err?: NodeJS.ErrnoException) => void>() expect(done(new Error())).type.toBe<void>() }) // Asynchronous server.addHook('onRequest', async function (request, reply) { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() }) server.addHook('preParsing', async function (request, reply, payload) { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() expect(payload).type.toBe<RequestPayload>() }) server.addHook('preValidation', async function (request, reply) { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() }) server.addHook('preHandler', async function (request, reply) { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() }) server.addHook<TestPayloadType>('preSerialization', async function (request, reply, payload) { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() expect(payload).type.toBe<TestPayloadType>() // we expect this to be unknown when not specified like in the previous test }) server.addHook<TestPayloadType>('onSend', async function (request, reply, payload) { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() expect(payload).type.toBe<TestPayloadType>() }) server.addHook('onResponse', async function (request, reply) { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() }) server.addHook('onTimeout', async function (request, reply) { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() }) server.addHook('onError', async function (request, reply, error) { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() expect(error).type.toBe<FastifyError>() }) server.addHook('onRequestAbort', async function (request) { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() }) server.addHook('onRegister', async (instance, opts) => { expect(instance).type.toBe<FastifyInstance>() expect(opts).type.toBe<RegisterOptions & FastifyPluginOptions>() }) server.addHook('onReady', async function () { expect(this).type.toBe<FastifyInstance>() }) server.addHook('onListen', async function () { expect(this).type.toBe<FastifyInstance>() }) server.addHook('onClose', async function (instance) { expect(this).type.toBe<FastifyInstance>() expect(instance).type.toBe<FastifyInstance>() }) // Use case to monitor any regression on issue #3620 // ref.: https://github.com/fastify/fastify/issues/3620 const customTypedHook: preHandlerAsyncHookHandler< RawServerDefault, RawRequestDefaultExpression, RawReplyDefaultExpression, RouteGenericInterface, ContextConfigDefault, FastifySchema, FastifyTypeProviderDefault > = async function (request, reply): Promise<void> { expect(this).type.toBe<FastifyInstance>() expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() } server.register(async (instance) => { instance.addHook('preHandler', customTypedHook) }) // Test custom Context Config types for hooks type CustomContextConfig = FastifyContextConfig & { foo: string; bar: number; } type CustomContextConfigWithDefault = CustomContextConfig & FastifyRouteConfig server.route<RouteGenericInterface, CustomContextConfig>({ method: 'GET', url: '/', handler: () => { }, onRequest: (request, reply, done) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, preParsing: (request, reply, payload, done) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, preValidation: (request, reply, done) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, preHandler: (request, reply, done) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, preSerialization: (request, reply, payload, done) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, onSend: (request, reply, payload, done) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, onResponse: (request, reply, done) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, onTimeout: (request, reply, done) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, onError: (request, reply, error, done) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() } }) server.get<RouteGenericInterface, CustomContextConfig>('/', { onRequest: async (request, reply) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, preParsing: async (request, reply) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, preValidation: async (request, reply) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, preHandler: async (request, reply) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, preSerialization: async (request, reply) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, onSend: async (request, reply) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, onResponse: async (request, reply) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, onTimeout: async (request, reply) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, onError: async (request, reply) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() } }, async (request, reply) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }) type CustomContextRequest = FastifyRequest<any, any, any, any, any, CustomContextConfig, any> type CustomContextReply = FastifyReply<any, any, any, any, CustomContextConfig, any, any> server.route<RouteGenericInterface, CustomContextConfig>({ method: 'GET', url: '/', handler: () => { }, onRequest: async (request: CustomContextRequest, reply: CustomContextReply) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, preParsing: async (request: CustomContextRequest, reply: CustomContextReply, payload: RequestPayload) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, preValidation: async (request: CustomContextRequest, reply: CustomContextReply) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, preHandler: async (request: CustomContextRequest, reply: CustomContextReply) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, preSerialization: async (request: CustomContextRequest, reply: CustomContextReply, payload: any) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, onSend: async (request: CustomContextRequest, reply: CustomContextReply, payload: any) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, onResponse: async (request: CustomContextRequest, reply: CustomContextReply) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, onTimeout: async (request: CustomContextRequest, reply: CustomContextReply) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() }, onError: async (request: CustomContextRequest, reply: CustomContextReply, error: FastifyError) => { expect(request.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() expect(reply.routeOptions.config).type.toBe<CustomContextConfigWithDefault>() } }) type NoInferRequest = FastifyRequest<RouteGenericInterface, http.Server, http.IncomingMessage, NoInfer<FastifySchema>> type NoInferReply = FastifyReply< RouteGenericInterface, http.Server, http.IncomingMessage, http.ServerResponse, unknown, NoInfer<FastifySchema> > server.route({ method: 'GET', url: '/', handler: (request, reply) => { expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() }, onRequest: (request, reply, done) => { expect(request).type.toBe<NoInferRequest>() expect(reply).type.toBe<NoInferReply>() expect(done).type.toBe<HookHandlerDoneFunction>() }, onRequestAbort: (request, done) => { expect(request).type.toBe<NoInferRequest>() expect(done).type.toBe<HookHandlerDoneFunction>() }, preParsing: (request, reply, payload, done) => { expect(request).type.toBe<NoInferRequest>() expect(reply).type.toBe<NoInferReply>() expect(payload).type.toBe<RequestPayload>() expect(done).type.toBe< <TError extends Error = FastifyError>( err?: TError | null | undefined, res?: RequestPayload | undefined ) => void >() }, preValidation: (request, reply, done) => { expect(request).type.toBe<NoInferRequest>() expect(reply).type.toBe<NoInferReply>() expect(done).type.toBe<HookHandlerDoneFunction>() }, preHandler: (request, reply, done) => { expect(request).type.toBe<NoInferRequest>() expect(reply).type.toBe<NoInferReply>() expect(done).type.toBe<HookHandlerDoneFunction>() }, preSerialization: (request, reply, payload, done) => { expect(request).type.toBe<NoInferRequest>() expect(reply).type.toBe<NoInferReply>() expect(payload).type.toBe<unknown>() expect(done).type.toBe<DoneFuncWithErrOrRes>() }, onSend: (request, reply, payload, done) => { expect(request).type.toBe<NoInferRequest>() expect(reply).type.toBe<NoInferReply>() expect(payload).type.toBe<unknown>() expect(done).type.toBe<DoneFuncWithErrOrRes>() }, onResponse: (request, reply, done) => { expect(request).type.toBe<NoInferRequest>() expect(reply).type.toBe<NoInferReply>() expect(done).type.toBe<HookHandlerDoneFunction>() }, onTimeout: (request, reply, done) => { expect(request).type.toBe<NoInferRequest>() expect(reply).type.toBe<NoInferReply>() expect(done).type.toBe<HookHandlerDoneFunction>() }, onError: (request, reply, error, done) => { expect(request).type.toBe<NoInferRequest>() expect(reply).type.toBe<NoInferReply>() expect(error).type.toBe<FastifyError>() expect(done).type.toBe<() => void>() } }) server.get('/', { onRequest: async (request, reply) => { expect(request).type.toBe<NoInferRequest>() expect(reply).type.toBe<NoInferReply>() }, onRequestAbort: async (request, done) => { expect(request).type.toBe<NoInferRequest>() expect(done).type.toBe<HookHandlerDoneFunction>() }, preParsing: async (request, reply, payload) => { expect(request).type.toBe<NoInferRequest>() expect(reply).type.toBe<NoInferReply>() expect(payload).type.toBe<RequestPayload>() }, preValidation: async (request, reply) => { expect(request).type.toBe<NoInferRequest>() expect(reply).type.toBe<NoInferReply>() }, preHandler: async (request, reply) => { expect(request).type.toBe<NoInferRequest>() expect(reply).type.toBe<NoInferReply>() }, preSerialization: async (request, reply, payload) => { expect(request).type.toBe<NoInferRequest>() expect(reply).type.toBe<NoInferReply>() expect(payload).type.toBe<unknown>() }, onSend: async (request, reply, payload) => { expect(request).type.toBe<NoInferRequest>() expect(reply).type.toBe<NoInferReply>() expect(payload).type.toBe<unknown>() }, onResponse: async (request, reply) => { expect(request).type.toBe<NoInferRequest>() expect(reply).type.toBe<NoInferReply>() }, onTimeout: async (request, reply) => { expect(request).type.toBe<NoInferRequest>() expect(reply).type.toBe<NoInferReply>() }, onError: async (request, reply, error) => { expect(request).type.toBe<NoInferRequest>() expect(reply).type.toBe<NoInferReply>() expect(error).type.toBe<FastifyError>() } }, async (request, reply) => { expect(request).type.toBe<FastifyRequest>() expect(reply).type.toBe<FastifyReply>() }) // TODO: Should throw errors // server.get('/', { onRequest: async (request, reply, done) => {} }, async (request, reply) => {}) // server.get('/', { onRequestAbort: async (request, done) => {} }, async (request, reply) => {}) // server.get('/', { preParsing: async (request, reply, payload, done) => {} }, async (request, reply) => {}) // server.get('/', { preValidation: async (request, reply, done) => {} }, async (request, reply) => {}) // server.get('/', { preHandler: async (request, reply, done) => {} }, async (request, reply) => {}) // server.get('/', { preSerialization: async (request, reply, payload, done) => {} }, async (request, reply) => {}) // server.get('/', { onSend: async (request, reply, payload, done) => {} }, async (request, reply) => {}) // server.get('/', { onResponse: async (request, reply, done) => {} }, async (request, reply) => {}) // server.get('/', { onTimeout: async (request, reply, done) => {} }, async (request, reply) => {}) // server.get('/', { onError: async (request, reply, error, done) => {} }, async (request, reply) => {}) server.addHook('preClose', function (done) { expect(this).type.toBe<FastifyInstance>() expect(done).type.toBeAssignableTo<(err?: FastifyError) => void>() expect(done).type.toBeAssignableTo<(err?: NodeJS.ErrnoException) => void>() expect(done(new Error())).type.toBe<void>() }) const preCloseHandler: preCloseHookHandler = function (done) { expect(this).type.toBe<FastifyInstance>() expect(done).type.toBe<HookHandlerDoneFunction>() } server.addHook('preClose', preCloseHandler) server.addHook('preClose', async function () { expect(this).type.toBe<FastifyInstance>() }) const preCloseAsyncHandler: preCloseAsyncHookHandler = async function () { expect(this).type.toBe<FastifyInstance>() } server.addHook('preClose', preCloseAsyncHandler) // @ts-expect-error No overload matches this call. server.addHook('onClose', async function (instance, done) {}) // @ts-expect-error No overload matches this call. server.addHook('onError', async function (request, reply, error, done) {}) // @ts-expect-error No overload matches this call. server.addHook('onReady', async function (done) {}) // @ts-expect-error No overload matches this call. server.addHook('onListen', async function (done) {}) // @ts-expect-error No overload matches this call. server.addHook('onRequest', async function (request, reply, done) {}) // @ts-expect-error No overload matches this call. server.addHook('onRequestAbort', async function (request, done) {}) // @ts-expect-error No overload matches this call. server.addHook('onResponse', async function (request, reply, done) {}) // @ts-expect-error No overload matches this call. server.addHook('onSend', async function (request, reply, payload, done) {}) // @ts-expect-error No overload matches this call. server.addHook('onTimeout', async function (request, reply, done) {}) // @ts-expect-error No overload matches this call. server.addHook('preClose', async function (done) {}) // @ts-expect-error No overload matches this call. server.addHook('preHandler', async function (request, reply, done) {}) // @ts-expect-error No overload matches this call. server.addHook('preSerialization', async function (request, reply, payload, done) {}) // @ts-expect-error No overload matches this call. server.addHook('preValidation', async function (request, reply, done) {})