fastify
Version:
Fast and low overhead web framework, for Node.js
598 lines (538 loc) • 22 kB
text/typescript
import { AddressInfo } from 'node:net'
import { ConstraintStrategy, Config as FindMyWayConfig } from 'find-my-way'
import { expect } from 'tstyche'
import fastify, {
FastifyBaseLogger,
FastifyBodyParser,
FastifyError,
FastifyInstance,
FastifyListenOptions,
FastifyRouterOptions,
RawReplyDefaultExpression,
RawRequestDefaultExpression,
RawServerDefault,
RouteGenericInterface
} from '../../fastify.js'
import { HookHandlerDoneFunction } from '../../types/hooks.js'
import { FindMyWayVersion } from '../../types/instance.js'
import { Bindings, ChildLoggerOptions } from '../../types/logger.js'
import { FastifyReply } from '../../types/reply.js'
import { FastifyRequest } from '../../types/request.js'
import { FastifySchemaCompiler, FastifySchemaControllerOptions, FastifySerializerCompiler } from '../../types/schema.js'
const server = fastify()
expect(server.addSchema({ type: 'null' })).type.toBe<FastifyInstance>()
expect(server.addSchema({ schemaId: 'id' })).type.toBe<FastifyInstance>()
expect(server.addSchema({ schemas: [] })).type.toBe<FastifyInstance>()
expect(server.pluginName).type.toBe<string>()
expect(server.getSchemas()).type.toBe<Record<string, unknown>>()
expect(server.addresses()).type.toBe<AddressInfo[]>()
expect(server.getSchema('SchemaId')).type.toBe<unknown>()
expect(server.printRoutes()).type.toBe<string>()
expect(server.printPlugins()).type.toBe<string>()
expect(server.listeningOrigin).type.toBe<string>()
expect(server.supportedMethods).type.toBe<string[]>()
expect(
server.setErrorHandler(function (error, request, reply) {
expect(error).type.toBe<unknown>()
expect(this).type.toBe<FastifyInstance>()
})
).type.toBe<FastifyInstance>()
expect(
server.setErrorHandler<FastifyError>(function (error, request, reply) {
expect(error).type.toBe<FastifyError>()
})
).type.toBe<FastifyInstance>()
expect(
server.setGenReqId(function (req) {
expect(req).type.toBe<RawRequestDefaultExpression>()
return 'foo'
})
).type.toBe<FastifyInstance>()
function fastifySetGenReqId (req: RawRequestDefaultExpression) {
return 'foo'
}
server.setGenReqId(fastifySetGenReqId)
function fastifyErrorHandler (this: FastifyInstance, error: FastifyError) { }
server.setErrorHandler(fastifyErrorHandler)
async function asyncFastifyErrorHandler (this: FastifyInstance, error: FastifyError) { }
server.setErrorHandler(asyncFastifyErrorHandler)
function nodeJSErrorHandler (error: NodeJS.ErrnoException) {
if (error) { throw error }
}
server.setErrorHandler(nodeJSErrorHandler)
function asyncNodeJSErrorHandler (error: NodeJS.ErrnoException) {
if (error) { throw error }
}
server.setErrorHandler(asyncNodeJSErrorHandler)
class CustomError extends Error {
private __brand: any
}
interface ReplyPayload {
Reply: {
test: boolean;
};
}
// typed sync error handler
server.setErrorHandler<CustomError, ReplyPayload>((error, request, reply) => {
expect(error).type.toBe<CustomError>()
expect(reply.send).type.toBe<((...args: [payload: ReplyPayload['Reply']]) => FastifyReply<ReplyPayload, RawServerDefault, RawRequestDefaultExpression<RawServerDefault>, RawReplyDefaultExpression<RawServerDefault>>)>()
})
// typed async error handler send
server.setErrorHandler<CustomError, ReplyPayload>(async (error, request, reply) => {
expect(error).type.toBe<CustomError>()
expect(reply.send).type.toBe<((...args: [payload: ReplyPayload['Reply']]) => FastifyReply<ReplyPayload, RawServerDefault, RawRequestDefaultExpression<RawServerDefault>, RawReplyDefaultExpression<RawServerDefault>>)>()
})
// typed async error handler return
server.setErrorHandler<CustomError, ReplyPayload>(async (error, request, reply) => {
expect(error).type.toBe<CustomError>()
return { test: true }
})
// typed sync error handler send error
server.setErrorHandler<CustomError, ReplyPayload>((error, request, reply) => {
expect(error).type.toBe<CustomError>()
// @ts-expect-error Type 'string' is not assignable to type 'boolean'.
reply.send({ test: 'foo' })
})
// typed sync error handler return error
server.setErrorHandler<CustomError, ReplyPayload>((error, request, reply) => {
expect(error).type.toBe<CustomError>()
return { test: 'foo' }
})
// typed async error handler send error
server.setErrorHandler<CustomError, ReplyPayload>(async (error, request, reply) => {
expect(error).type.toBe<CustomError>()
// @ts-expect-error Type 'string' is not assignable to type 'boolean'.
reply.send({ test: 'foo' })
})
// typed async error handler return error
server.setErrorHandler<CustomError, ReplyPayload>(async (error, request, reply) => {
expect(error).type.toBe<CustomError>()
return { test: 'foo' }
})
function notFoundHandler (request: FastifyRequest, reply: FastifyReply) { }
async function notFoundAsyncHandler (request: FastifyRequest, reply: FastifyReply) { }
function notFoundpreHandlerHandler (
request: FastifyRequest,
reply: FastifyReply,
done: HookHandlerDoneFunction
) { done() }
async function notFoundpreHandlerAsyncHandler (
request: FastifyRequest,
reply: FastifyReply
) { }
function notFoundpreValidationHandler (
request: FastifyRequest,
reply: FastifyReply,
done: HookHandlerDoneFunction
) { done() }
async function notFoundpreValidationAsyncHandler (
request: FastifyRequest,
reply: FastifyReply
) { }
server.setNotFoundHandler(notFoundHandler)
server.setNotFoundHandler({ preHandler: notFoundpreHandlerHandler }, notFoundHandler)
server.setNotFoundHandler({ preHandler: notFoundpreHandlerAsyncHandler }, notFoundHandler)
server.setNotFoundHandler({ preValidation: notFoundpreValidationHandler }, notFoundHandler)
server.setNotFoundHandler({ preValidation: notFoundpreValidationAsyncHandler }, notFoundHandler)
server.setNotFoundHandler(
{ preHandler: notFoundpreHandlerHandler, preValidation: notFoundpreValidationHandler },
notFoundHandler
)
server.setNotFoundHandler(notFoundAsyncHandler)
server.setNotFoundHandler({ preHandler: notFoundpreHandlerHandler }, notFoundAsyncHandler)
server.setNotFoundHandler({ preHandler: notFoundpreHandlerAsyncHandler }, notFoundAsyncHandler)
server.setNotFoundHandler({ preValidation: notFoundpreValidationHandler }, notFoundAsyncHandler)
server.setNotFoundHandler({ preValidation: notFoundpreValidationAsyncHandler }, notFoundAsyncHandler)
server.setNotFoundHandler(
{ preHandler: notFoundpreHandlerHandler, preValidation: notFoundpreValidationHandler },
notFoundAsyncHandler
)
server.setNotFoundHandler(function (_, reply) {
return reply.send('')
})
server.setSchemaController({
bucket: (parentSchemas) => {
expect(parentSchemas).type.toBe<unknown>()
return {
add (schema) {
expect(schema).type.toBe<unknown>()
expect(server.addSchema({ type: 'null' })).type.toBe<FastifyInstance>()
return server.addSchema({ type: 'null' })
},
getSchema (schemaId) {
expect(schemaId).type.toBe<string>()
return server.getSchema('SchemaId')
},
getSchemas () {
expect(server.getSchemas()).type.toBe<Record<string, unknown>>()
return server.getSchemas()
}
}
}
})
function invalidSchemaController (schemaControllerOptions: FastifySchemaControllerOptions) { }
// @ts-expect-error Type '(schemaControllerOptions: FastifySchemaControllerOptions) => void' has no properties in common with type 'FastifySchemaControllerOptions'.
server.setSchemaController(invalidSchemaController)
server.setReplySerializer(function (payload, statusCode) {
expect(payload).type.toBe<unknown>()
expect(statusCode).type.toBe<number>()
return 'serialized'
})
function invalidReplySerializer (payload: number, statusCode: string) { }
// @ts-expect-error Argument of type '(payload: number, statusCode: string) => void' is not assignable to parameter of type '(payload: unknown, statusCode: number) => string'.
server.setReplySerializer(invalidReplySerializer)
function serializerWithInvalidReturn (payload: unknown, statusCode: number) { }
// @ts-expect-error Argument of type '(payload: unknown, statusCode: number) => void' is not assignable to parameter of type '(payload: unknown, statusCode: number) => string'.
server.setReplySerializer(serializerWithInvalidReturn)
function invalidSchemaErrorFormatter (err: Error) {
if (err) { throw err }
}
// @ts-expect-error Argument of type '(err: Error) => void' is not assignable to parameter of type 'SchemaErrorFormatter'.
server.setSchemaErrorFormatter(invalidSchemaErrorFormatter)
expect(server.addHttpMethod('SEARCH', { hasBody: true })).type.toBe<FastifyInstance>()
// test listen opts objects
const options: FastifyListenOptions = {}
expect(server.listen()).type.toBe<Promise<string>>()
expect(server.listen(options)).type.toBe<Promise<string>>()
expect(server.listen({ port: 3000 })).type.toBe<Promise<string>>()
expect(server.listen({ port: 3000, listenTextResolver: (address) => { return `address: ${address}` } })).type.toBe<Promise<string>>()
expect(server.listen({ port: 3000, host: '0.0.0.0' })).type.toBe<Promise<string>>()
expect(server.listen({ port: 3000, host: '0.0.0.0', backlog: 42 })).type.toBe<Promise<string>>()
expect(server.listen({ port: 3000, host: '0.0.0.0', backlog: 42, exclusive: true })).type.toBe<Promise<string>>()
expect(server.listen({ port: 3000, host: '::/0', ipv6Only: true })).type.toBe<Promise<string>>()
expect(server.listen(() => { })).type.toBe<void>()
expect(server.listen({ port: 3000 }, () => { })).type.toBe<void>()
expect(server.listen({ port: 3000, listenTextResolver: (address) => { return `address: ${address}` } }, () => { })).type.toBe<void>()
expect(server.listen({ port: 3000, host: '0.0.0.0' }, () => { })).type.toBe<void>()
expect(server.listen({ port: 3000, host: '0.0.0.0', backlog: 42 }, () => { })).type.toBe<void>()
expect(server.listen({ port: 3000, host: '0.0.0.0', backlog: 42, exclusive: true }, () => { })).type.toBe<void>()
expect(server.listen({ port: 3000, host: '::/0', ipv6Only: true }, () => { })).type.toBe<void>()
// test after method
expect(server.after()).type.toBeAssignableTo<FastifyInstance>()
expect(server.after((err) => {
expect(err).type.toBe<Error | null>()
})).type.toBeAssignableTo<FastifyInstance>()
// test ready method
expect(server.ready()).type.toBeAssignableTo<FastifyInstance>()
expect(server.ready((err) => {
expect(err).type.toBe<Error | null>()
})).type.toBeAssignableTo<FastifyInstance>()
expect(server.ready(async (err) => {
expect(err).type.toBe<Error | null>()
})).type.toBeAssignableTo<FastifyInstance>()
expect(server.routing({} as RawRequestDefaultExpression, {} as RawReplyDefaultExpression)).type.toBe<void>()
expect(fastify().get<RouteGenericInterface, { contextKey: string }>('/', {
handler: () => { },
errorHandler: (error, request, reply) => {
expect(error).type.toBe<FastifyError>()
expect(request).type.toBeAssignableTo<FastifyRequest>()
expect(request.routeOptions.config).type.toBeAssignableTo<{ contextKey: string }>()
expect(reply).type.toBeAssignableTo<FastifyReply>()
expect(server.errorHandler(error, request, reply)).type.toBe<void>()
}
})).type.toBe<FastifyInstance>()
expect(fastify().get('/', {
handler: () => { },
childLoggerFactory: (logger, bindings, opts, req) => {
expect(server.childLoggerFactory(logger, bindings, opts, req)).type.toBe<FastifyBaseLogger>()
return server.childLoggerFactory(logger, bindings, opts, req)
}
})).type.toBe<FastifyInstance>()
expect(
server.setChildLoggerFactory(function (logger, bindings, opts, req) {
expect(logger).type.toBe<FastifyBaseLogger>()
expect(bindings).type.toBe<Bindings>()
expect(opts).type.toBe<ChildLoggerOptions>()
expect(req).type.toBe<RawRequestDefaultExpression>()
expect(this).type.toBe<FastifyInstance>()
return logger.child(bindings, opts)
})
).type.toBe<FastifyInstance>()
expect(
server.setErrorHandler<FastifyError>(function (error, request, reply) {
expect(error).type.toBe<FastifyError>()
})
).type.toBe<FastifyInstance>()
function childLoggerFactory (
this: FastifyInstance,
logger: FastifyBaseLogger,
bindings: Bindings,
opts: ChildLoggerOptions,
req: RawRequestDefaultExpression
) {
return logger.child(bindings, opts)
}
server.setChildLoggerFactory(childLoggerFactory)
server.setChildLoggerFactory(server.childLoggerFactory)
type InitialConfig = Readonly<{
connectionTimeout?: number,
keepAliveTimeout?: number,
bodyLimit?: number,
caseSensitive?: boolean,
allowUnsafeRegex?: boolean,
forceCloseConnections?: boolean,
http2?: boolean,
https?: boolean | Readonly<{ allowHTTP1: boolean }>,
ignoreTrailingSlash?: boolean,
ignoreDuplicateSlashes?: boolean,
disableRequestLogging?: boolean | ((req: FastifyRequest) => boolean),
maxParamLength?: number,
onProtoPoisoning?: 'error' | 'remove' | 'ignore',
onConstructorPoisoning?: 'error' | 'remove' | 'ignore',
pluginTimeout?: number,
requestIdHeader?: string | false,
requestIdLogLabel?: string,
http2SessionTimeout?: number,
useSemicolonDelimiter?: boolean,
routerOptions?: FastifyRouterOptions<RawServerDefault>
}>
expect(fastify().initialConfig).type.toBe<InitialConfig>()
expect<FastifyRouterOptions<RawServerDefault>>().type.toBeAssignableTo<
FindMyWayConfig<FindMyWayVersion<RawServerDefault>>
>()
fastify({
routerOptions: {
defaultRoute: (req, res) => {
expect(req).type.toBe<RawRequestDefaultExpression<RawServerDefault>>()
expect(res).type.toBe<RawReplyDefaultExpression<RawServerDefault>>()
expect(res).type.not.toBeAssignableTo<FastifyReply>()
res.end('foo')
},
onBadUrl: (path, req, res) => {
expect(path).type.toBe<string>()
expect(req).type.toBe<RawRequestDefaultExpression<RawServerDefault>>()
expect(res).type.toBe<RawReplyDefaultExpression<RawServerDefault>>()
expect(res).type.not.toBeAssignableTo<FastifyReply>()
res.end('foo')
},
onMaxParamLength (path, req, res) {
expect(path).type.toBe<string>()
expect(req).type.toBe<RawRequestDefaultExpression<RawServerDefault>>()
expect(res).type.toBe<RawReplyDefaultExpression<RawServerDefault>>()
expect(res).type.not.toBeAssignableTo<FastifyReply>()
res.end('foo')
}
}
})
expect(server.defaultTextParser).type.toBe<FastifyBodyParser<string>>()
expect(server.getDefaultJsonParser('ignore', 'error')).type.toBe<FastifyBodyParser<string>>()
expect(server.printRoutes({ includeHooks: true, commonPrefix: false, includeMeta: true })).type.toBe<string>()
expect(server.printRoutes({ includeMeta: ['key1', Symbol('key2')] })).type.toBe<string>()
expect(server.printRoutes({ method: 'GET' })).type.toBe<string>()
expect(server.printRoutes()).type.toBe<string>()
server.decorate<(x: string) => void>('test', function (x: string): void {
expect(this).type.toBe<FastifyInstance>()
})
server.decorate('test', function (x: string): void {
expect(this).type.toBe<FastifyInstance>()
})
server.decorate<string>('test', {
getter () {
expect(this).type.toBe<FastifyInstance>()
return 'foo'
}
})
server.decorate<string>('test', {
getter () {
expect(this).type.toBe<FastifyInstance>()
return 'foo'
},
setter (x) {
expect(x).type.toBe<string>()
expect(this).type.toBe<FastifyInstance>()
}
})
server.decorate('test')
server.decorate('test', null, ['foo'])
server.decorateRequest<(x: string, y: number) => void>('test', function (x: string, y: number): void {
expect(this).type.toBe<FastifyRequest>()
})
server.decorateRequest('test', function (x: string, y: number): void {
expect(this).type.toBe<FastifyRequest>()
})
server.decorateRequest('test')
server.decorateRequest('test', null, ['foo'])
server.decorateReply<(x: string) => void>('test', function (x: string): void {
expect(this).type.toBe<FastifyReply>()
})
server.decorateReply('test', function (x: string): void {
expect(this).type.toBe<FastifyReply>()
})
server.decorateReply('test')
server.decorateReply('test', null, ['foo'])
// @ts-expect-error Argument of type 'boolean' is not assignable to parameter of type 'GetterSetter...'.
server.decorate<string>('test', true)
// @ts-expect-error Type 'string' is not assignable to type 'number'.
server.decorate<(myNumber: number) => number>('test', function (myNumber: number): string {
return ''
})
server.decorate<string>('test', {
// @ts-expect-error Type 'boolean' is not assignable to type 'string'.
getter () {
return true
}
})
// @ts-expect-error Property 'getter' is missing in type
server.decorate<string>('test', {
setter (x) { }
})
declare module '../../fastify' {
interface FastifyInstance {
typedTestProperty: boolean
typedTestPropertyGetterSetter: string
typedTestMethod(x: string): string
}
interface FastifyRequest {
typedTestRequestProperty: boolean
typedTestRequestPropertyGetterSetter: string
typedTestRequestMethod(x: string): string
}
interface FastifyReply {
typedTestReplyProperty: boolean
typedTestReplyPropertyGetterSetter: string
typedTestReplyMethod(x: string): string
}
}
server.decorate('typedTestProperty', false)
server.decorate('typedTestProperty', {
getter () {
return false
}
})
server.decorate('typedTestProperty', {
getter (): boolean {
return true
},
setter (x) {
expect(x).type.toBe<boolean>()
expect(this).type.toBe<FastifyInstance>()
}
})
server.decorate('typedTestProperty')
server.decorate('typedTestProperty', null, ['foo'])
expect(server.decorate).type.not.toBeCallableWith('typedTestProperty', null)
expect(server.decorate).type.not.toBeCallableWith('typedTestProperty', 'foo')
expect(server.decorate).type.not.toBeCallableWith('typedTestProperty', {
getter () {
return 'foo'
}
})
server.decorate('typedTestMethod', function (x) {
expect(x).type.toBe<string>()
expect(this).type.toBe<FastifyInstance>()
return 'foo'
})
server.decorate('typedTestMethod', x => x)
// @ts-expect-error Type 'string' is not assignable to type 'boolean'.
server.decorate('typedTestMethod', function (x: boolean) {
return 'foo'
})
// @ts-expect-error Type 'boolean' is not assignable to type 'string'.
server.decorate('typedTestMethod', function () {
return true
})
// @ts-expect-error Type 'Promise<string>' is not assignable to type 'string'.
server.decorate('typedTestMethod', async function () {
return 'foo'
})
server.decorateRequest('typedTestRequestProperty', false)
server.decorateRequest('typedTestRequestProperty', {
getter () {
return false
}
})
server.decorateRequest('typedTestRequestProperty', {
getter (): boolean {
return true
},
setter (x) {
expect(x).type.toBe<boolean>()
expect(this).type.toBe<FastifyRequest>()
}
})
server.decorateRequest('typedTestRequestProperty')
server.decorateRequest('typedTestRequestProperty', null, ['foo'])
expect(server.decorateRequest).type.not.toBeCallableWith('typedTestRequestProperty', null)
expect(server.decorateRequest).type.not.toBeCallableWith('typedTestRequestProperty', 'foo')
expect(server.decorateRequest).type.not.toBeCallableWith('typedTestRequestProperty', {
getter () {
return 'foo'
}
})
server.decorateRequest('typedTestRequestMethod', function (x) {
expect(x).type.toBe<string>()
expect(this).type.toBe<FastifyRequest>()
return 'foo'
})
server.decorateRequest('typedTestRequestMethod', x => x)
// @ts-expect-error Type 'string' is not assignable to type 'boolean'.
server.decorateRequest('typedTestRequestMethod', function (x: boolean) {
return 'foo'
})
// @ts-expect-error Type 'boolean' is not assignable to type 'string'.
server.decorateRequest('typedTestRequestMethod', function () {
return true
})
// @ts-expect-error Type 'Promise<string>' is not assignable to type 'string'.
server.decorateRequest('typedTestRequestMethod', async function () {
return 'foo'
})
server.decorateReply('typedTestReplyProperty', false)
server.decorateReply('typedTestReplyProperty', {
getter () {
return false
}
})
server.decorateReply('typedTestReplyProperty', {
getter (): boolean {
return true
},
setter (x) {
expect(x).type.toBe<boolean>()
expect(this).type.toBe<FastifyReply>()
}
})
server.decorateReply('typedTestReplyProperty')
server.decorateReply('typedTestReplyProperty', null, ['foo'])
expect(server.decorateReply).type.not.toBeCallableWith('typedTestReplyProperty', null)
expect(server.decorateReply).type.not.toBeCallableWith('typedTestReplyProperty', 'foo')
expect(server.decorateReply).type.not.toBeCallableWith('typedTestReplyProperty', {
getter () {
return 'foo'
}
})
server.decorateReply('typedTestReplyMethod', function (x) {
expect(x).type.toBe<string>()
expect(this).type.toBe<FastifyReply>()
return 'foo'
})
server.decorateReply('typedTestReplyMethod', x => x)
// @ts-expect-error Type 'string' is not assignable to type 'boolean'
server.decorateReply('typedTestReplyMethod', function (x: boolean) {
return 'foo'
})
// @ts-expect-error Type 'boolean' is not assignable to type 'string'
server.decorateReply('typedTestReplyMethod', function (x) {
return true
})
// @ts-expect-error Type 'Promise<string>' is not assignable to type 'string'.
server.decorateReply('typedTestReplyMethod', async function () {
return 'foo'
})
const foo = server.getDecorator<string>('foo')
expect(foo).type.toBe<string>()
const versionConstraintStrategy: ConstraintStrategy<FindMyWayVersion<RawServerDefault>> = {
name: 'version',
storage: () => ({
get: () => () => { },
set: () => { },
del: () => { },
empty: () => { }
}),
validate () { },
deriveConstraint: () => 'foo'
}
expect(server.addConstraintStrategy(versionConstraintStrategy)).type.toBe<void>()
expect(server.hasConstraintStrategy(versionConstraintStrategy.name)).type.toBe<boolean>()
expect(server.validatorCompiler).type.toBe<FastifySchemaCompiler<any> | undefined>()
expect(server.serializerCompiler).type.toBe<FastifySerializerCompiler<any> | undefined>()