import type { FastifyContextConfig, FastifyReply, FastifyRequest, FastifyRequestContext, RouteHandler } from 'fastify'; /** * Settings used to determine prefix from servers section of OAS */ export interface PrefixExtractingSettings { urlRegex?: RegExp; descriptionRegex?: RegExp; prefixVariable?: string; } /** * Options used to setup the plugin */ export interface Options { securityHandlers?: SecurityHandlers; operationHandlers: OperationHandlers | OperationHandlersUntyped; openApiSpecification: OpenAPISpec; settings?: { initializePaths?: boolean; initializeWebhooks?: boolean; prefix?: string | PrefixExtractingSettings; useXSecurity?: boolean; validateResponses?: boolean; contentTypes?: string[]; dereferenceOAS?: boolean; }; } /** * Type for security handler function that will be used to check if user has access to specific route */ export type SecurityHandler = (req: FastifyRequest, scopes?: string[]) => boolean | Promise; /** * Dictionary of security handlers, where key is securitySchema name from OAS */ export interface SecurityHandlers { [resolverName: string]: SecurityHandler | undefined; } /** * Dictionary of operation handlers, where key is operationId from OAS */ export type OperationHandlers, Content = 'application/json'> = { [K in keyof Ops]: TypedHandlerBase; }; export interface OperationHandlersUntyped { [resolverName: string]: ((req: FastifyRequest, reply: FastifyReply) => any) | undefined; } /** * Typed security section of OAS */ export type SecuritySpecification = { [securityHandlerName: string]: string[] | undefined; }[]; /** * Typed components section of OAS */ export interface Components { parameters?: Record; schemas?: Record>; } /** * Typed paths section of OAS */ export interface PathsMap { [name: string]: unknown; } /** * Typed server variable object of OAS */ export interface ServerVariableObject { enum?: string[]; default: string; description?: string; } /** * Typed sever object of OAS */ export interface ServerObject { url: string; description?: string; variables?: Record; } /** * Typed OAS */ export interface OpenAPISpec { components?: Components; security?: SecuritySpecification; paths?: PathsMap; webhooks?: PathsMap; servers?: ServerObject[]; } /** * Typed response object of OAS */ export interface SpecResponse { [statusCode: string]: { description: string; type?: string; content?: unknown; }; } /** * Typed operation object of OAS */ export interface PathOperation { operationId?: string; parameters?: (SchemaParameter | ReferenceObject)[]; requestBody: unknown; security?: SecuritySpecification; responses?: SpecResponse; 'x-fastify-config'?: Omit['config'], 'url' | 'method'>; } /** * Typed paths object of OAS */ export type Paths = { parameters?: SchemaParameter[]; 'x-security'?: unknown; } & { [method: string]: PathOperation; }; /** * Typed possible in paremeter values of OAS */ export type SchemaParametersIn = 'query' | 'path' | 'header' | 'cookie'; /** * Typed schema parameter object of OAS */ export interface SchemaParameter { name: string; description?: string; required?: boolean; schema: { type: string; }; in: SchemaParametersIn; } /** * Parameters handling */ export interface ParsedParameter { type: string; properties: { [name: string]: { type: string; description?: string; }; }; required?: string[]; } /** * Typed reference object of OAS */ export interface ReferenceObject { $ref: string; summary?: string; description?: string; } type OperationWithParams = { parameters: any; }; type OperationWithBody = { requestBody: any; }; type OperationWithResponse = { responses: any; }; type RecordToTuple = [keyof T] extends [never] ? [] : T extends Record ? { [K in keyof T]: [T[K], ...RecordToTuple>]; }[keyof T] : []; type evaluate = { [K in keyof T]: T[K]; } & unknown; /** * XOR type for two types. */ type xor = evaluate | evaluate; /** * XOR type for multiple types. * Recursively applies `xor2` to combine all types into a mutually exclusive type. */ type ArrayToXor = T extends [infer First, ...infer Rest] ? Rest extends unknown[] ? xor> : First : unknown; type TransformOperationsToReply = Ops[T] extends { responses: infer Responses; } ? { [StatusCode in keyof Responses]: Responses[StatusCode] extends { content: infer Content; } ? ArrayToXor> : never; } : never; type TypedFastifyReply = FastifyReply<{ Reply: TransformOperationsToReply; }>; /** * First argument is interface with operations, second is name of operation we want to get request type for */ export type TypedRequestBase = FastifyRequest<{ Params: Ops[T] extends OperationWithParams ? Ops[T]['parameters']['path'] : never; Querystring: Ops[T] extends OperationWithParams ? Ops[T]['parameters']['query'] : never; Body: Ops[T] extends OperationWithBody ? Content extends keyof Ops[T]['requestBody']['content'] ? Ops[T]['requestBody']['content'][Content] : never : never; }>; export type TypedRouteHandler = RouteHandler<{ Params: Ops[T] extends OperationWithParams ? Ops[T]['parameters']['path'] : never; Querystring: Ops[T] extends OperationWithParams ? Ops[T]['parameters']['query'] : never; Body: Ops[T] extends OperationWithBody ? Content extends keyof Ops[T]['requestBody']['content'] ? Ops[T]['requestBody']['content'][Content] : never : never; Reply: TransformOperationsToReply; }>; /** First argument is interface with operations, second is name of operation we want to get response type for * As what ever you retunr in fastify will be treated as 200 response, we want to restrict this to only valid responses * If operation has 200 response with content application/json, we set it as type (or FastifyReply), otherwise we set FastifyReply * That way we can wither return strongly typed response or just FastifyReply where we can set any additional information like code, headers, etc. */ export type TypedResponseBaseSync = Ops[T] extends OperationWithResponse ? TypedFastifyReply | (Content extends keyof Ops[T]['responses']['200']['content'] ? Ops[T]['responses']['200']['content'][Content] : never) : TypedFastifyReply; export type TypedResponseBaseAsync = Promise>; export type TypedResponseBase = TypedResponseBaseSync | Promise>; /** * Base type for operation handlers functions * !!! IMPORTANT !!! * As TypeScript does not enforce return type of function (but provides suggestions) you should define your handles as follows: * `const myHandler: TypedHandlerBase = (req, reply): TypedResponseBase => {` */ export type TypedHandlerBase, T extends keyof Ops = keyof Ops, Content = 'application/json'> = (req: TypedRequestBase, reply: TypedFastifyReply) => TypedResponseBase; export {};