Skip to content

Commit

Permalink
feat: type definitions and documentation for separated type provider.
Browse files Browse the repository at this point in the history
  • Loading branch information
Bram-dc committed May 22, 2024
1 parent 9ac27e0 commit 6e2caa7
Show file tree
Hide file tree
Showing 5 changed files with 60 additions and 21 deletions.
6 changes: 4 additions & 2 deletions docs/Guides/Write-Type-Provider.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ For example, `FastifyTypeProviderDefault` will not be assignable to the followin
```ts
export interface NotSubstitutableTypeProvider extends FastifyTypeProvider {
// bad, nothing is assignable to `never` (except for itself)
output: this['input'] extends /** custom check here**/ ? /** narrowed type here **/ : never;
validator: this['schema'] extends /** custom check here**/ ? /** narrowed type here **/ : never;
serializer: this['schema'] extends /** custom check here**/ ? /** narrowed type here **/ : never;
}
```

Unless changed to:
```ts
export interface SubstitutableTypeProvider extends FastifyTypeProvider {
// good, anything can be assigned to `unknown`
output: this['input'] extends /** custom check here**/ ? /** narrowed type here **/ : unknown;
validator: this['schema'] extends /** custom check here**/ ? /** narrowed type here **/ : unknown;
serializer: this['schema'] extends /** custom check here**/ ? /** narrowed type here **/ : unknown;
}
```
2 changes: 1 addition & 1 deletion test/types/register.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ expectAssignable<ServerWithHttp2>(serverWithHttp2.register(async (instance: Serv
}))

// With Type Provider
type TestTypeProvider = { input: 'test', output: 'test' }
type TestTypeProvider = { schema: 'test', validator: 'test', serializer: 'test' }
const serverWithTypeProvider = fastify().withTypeProvider<TestTypeProvider>()
type ServerWithTypeProvider = FastifyInstance<Server, IncomingMessage, ServerResponse, FastifyLoggerInstance, TestTypeProvider>
const testPluginWithTypeProvider: FastifyPluginCallback<TestOptions, RawServerDefault, TestTypeProvider> = function (instance, opts, done) { }
Expand Down
49 changes: 42 additions & 7 deletions test/types/type-provider.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ expectAssignable(server.get('/', (req) => expectType<unknown>(req.body)))
// Remapping
// -------------------------------------------------------------------

interface NumberProvider extends FastifyTypeProvider { output: number } // remap all schemas to numbers
interface NumberProvider extends FastifyTypeProvider {
validator: number
serializer: number
} // remap all schemas to numbers

expectAssignable(server.withTypeProvider<NumberProvider>().get(
'/',
Expand All @@ -47,7 +50,7 @@ expectAssignable(server.withTypeProvider<NumberProvider>().get(
// Override
// -------------------------------------------------------------------

interface OverriddenProvider extends FastifyTypeProvider { output: 'inferenced' }
interface OverriddenProvider extends FastifyTypeProvider { validator: 'inferenced' }

expectAssignable(server.withTypeProvider<OverriddenProvider>().get<{ Body: 'override' }>(
'/',
Expand All @@ -69,7 +72,10 @@ expectAssignable(server.withTypeProvider<OverriddenProvider>().get<{ Body: 'over
// TypeBox
// -------------------------------------------------------------------

interface TypeBoxProvider extends FastifyTypeProvider { output: this['input'] extends TSchema ? Static<this['input']> : unknown }
interface TypeBoxProvider extends FastifyTypeProvider {
validator: this['schema'] extends TSchema ? Static<this['schema']> : unknown
serializer: this['schema'] extends TSchema ? Static<this['schema']> : unknown
}

expectAssignable(server.withTypeProvider<TypeBoxProvider>().get(
'/',
Expand Down Expand Up @@ -103,7 +109,10 @@ expectAssignable<FastifyInstance>(server.withTypeProvider<TypeBoxProvider>())
// JsonSchemaToTs
// -------------------------------------------------------------------

interface JsonSchemaToTsProvider extends FastifyTypeProvider { output: this['input'] extends JSONSchema ? FromSchema<this['input']> : unknown }
interface JsonSchemaToTsProvider extends FastifyTypeProvider {
validator: this['schema'] extends JSONSchema ? FromSchema<this['schema']> : unknown
serializer: this['schema'] extends JSONSchema ? FromSchema<this['schema']> : unknown
}

// explicitly setting schema `as const`

Expand Down Expand Up @@ -1003,7 +1012,7 @@ expectAssignable(server.withTypeProvider<JsonSchemaToTsProvider>().get<{Reply: b
// FastifyPlugin: Auxiliary
// -------------------------------------------------------------------

interface AuxiliaryPluginProvider extends FastifyTypeProvider { output: 'plugin-auxiliary' }
interface AuxiliaryPluginProvider extends FastifyTypeProvider { validator: 'plugin-auxiliary' }

// Auxiliary plugins may have varying server types per application. Recommendation would be to explicitly remap instance provider context within plugin if required.
function plugin<T extends FastifyInstance> (instance: T) {
Expand Down Expand Up @@ -1032,7 +1041,7 @@ expectAssignable(server.withTypeProvider<AuxiliaryPluginProvider>().register(plu
// Handlers: Inline
// -------------------------------------------------------------------

interface InlineHandlerProvider extends FastifyTypeProvider { output: 'handler-inline' }
interface InlineHandlerProvider extends FastifyTypeProvider { validator: 'handler-inline' }

// Inline handlers should infer for the request parameters (non-shared)
expectAssignable(server.withTypeProvider<InlineHandlerProvider>().get(
Expand All @@ -1052,7 +1061,7 @@ expectAssignable(server.withTypeProvider<InlineHandlerProvider>().get(
// Handlers: Auxiliary
// -------------------------------------------------------------------

interface AuxiliaryHandlerProvider extends FastifyTypeProvider { output: 'handler-auxiliary' }
interface AuxiliaryHandlerProvider extends FastifyTypeProvider { validator: 'handler-auxiliary' }

// Auxiliary handlers are likely shared for multiple routes and thus should infer as unknown due to potential varying parameters
function auxiliaryHandler (request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction): void {
Expand All @@ -1069,3 +1078,29 @@ expectAssignable(server.withTypeProvider<AuxiliaryHandlerProvider>().get(
expectType<'handler-auxiliary'>(req.body)
}
))

// -------------------------------------------------------------------
// Separate Providers
// -------------------------------------------------------------------

interface SeparateProvider extends FastifyTypeProvider {
validator: string
serializer: Date
}

expectAssignable(server.withTypeProvider<SeparateProvider>().get(
'/',
{
schema: {
body: null,
response: {
200: { type: 'string' }
}
}
},
(req, res) => {
expectType<string>(req.body)

res.send(new Date())
}
))
4 changes: 2 additions & 2 deletions types/reply.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { FastifyBaseLogger } from './logger'
import { FastifyRequest } from './request'
import { RouteGenericInterface } from './route'
import { FastifySchema } from './schema'
import { FastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyReplyType, CallTypeProvider } from './type-provider'
import { CallSerializerTypeProvider, FastifyReplyType, FastifyTypeProvider, FastifyTypeProviderDefault, ResolveFastifyReplyType } from './type-provider'
import { CodeToReplyKey, ContextConfigDefault, HttpKeys, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerBase, RawServerDefault, ReplyDefault, ReplyKeysToCodes, HttpHeader } from './utils'

export interface ReplyGenericInterface {
Expand All @@ -24,7 +24,7 @@ export type ResolveReplyTypeWithRouteGeneric<RouteGenericReply, Code extends Rep
SchemaCompiler extends FastifySchema = FastifySchema,
TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault> =
Code extends keyof SchemaCompiler['response'] ?
CallTypeProvider<TypeProvider, SchemaCompiler['response'][Code]> :
CallSerializerTypeProvider<TypeProvider, SchemaCompiler['response'][Code]> :
ResolveFastifyReplyType<TypeProvider, SchemaCompiler, { Reply: ReplyTypeConstrainer<RouteGenericReply, Code> }>
/**
* FastifyReply is an instance of the standard http or http2 reply types.
Expand Down
20 changes: 11 additions & 9 deletions types/type-provider.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ import { RecordKeysToLowercase } from './utils'
// -----------------------------------------------------------------------------------------------

export interface FastifyTypeProvider {
readonly input: unknown,
readonly output: unknown,
readonly schema: unknown,
readonly validator: unknown,
readonly serializer: unknown,
}

// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface FastifyTypeProviderDefault extends FastifyTypeProvider {}

export type CallTypeProvider<F extends FastifyTypeProvider, I> = (F & { input: I })['output']
export type CallValidatorTypeProvider<F extends FastifyTypeProvider, S> = (F & { schema: S })['validator']
export type CallSerializerTypeProvider<F extends FastifyTypeProvider, S> = (F & { schema: S })['serializer']

// -----------------------------------------------------------------------------------------------
// FastifyRequestType
Expand All @@ -32,13 +34,13 @@ type KeysOf<T> = T extends any ? keyof T : never

// Resolves Request types either from generic argument or Type Provider.
type ResolveRequestParams<TypeProvider extends FastifyTypeProvider, SchemaCompiler extends FastifySchema, RouteGeneric extends RouteGenericInterface> =
UndefinedToUnknown<KeysOf<RouteGeneric['Params']> extends never ? CallTypeProvider<TypeProvider, SchemaCompiler['params']> : RouteGeneric['Params']>
UndefinedToUnknown<KeysOf<RouteGeneric['Params']> extends never ? CallValidatorTypeProvider<TypeProvider, SchemaCompiler['params']> : RouteGeneric['Params']>
type ResolveRequestQuerystring<TypeProvider extends FastifyTypeProvider, SchemaCompiler extends FastifySchema, RouteGeneric extends RouteGenericInterface> =
UndefinedToUnknown<KeysOf<RouteGeneric['Querystring']> extends never ? CallTypeProvider<TypeProvider, SchemaCompiler['querystring']> : RouteGeneric['Querystring']>
UndefinedToUnknown<KeysOf<RouteGeneric['Querystring']> extends never ? CallValidatorTypeProvider<TypeProvider, SchemaCompiler['querystring']> : RouteGeneric['Querystring']>
type ResolveRequestHeaders<TypeProvider extends FastifyTypeProvider, SchemaCompiler extends FastifySchema, RouteGeneric extends RouteGenericInterface> =
UndefinedToUnknown<KeysOf<RouteGeneric['Headers']> extends never ? CallTypeProvider<TypeProvider, SchemaCompiler['headers']> : RouteGeneric['Headers']>
UndefinedToUnknown<KeysOf<RouteGeneric['Headers']> extends never ? CallValidatorTypeProvider<TypeProvider, SchemaCompiler['headers']> : RouteGeneric['Headers']>
type ResolveRequestBody<TypeProvider extends FastifyTypeProvider, SchemaCompiler extends FastifySchema, RouteGeneric extends RouteGenericInterface> =
UndefinedToUnknown<KeysOf<RouteGeneric['Body']> extends never ? CallTypeProvider<TypeProvider, SchemaCompiler['body']> : RouteGeneric['Body']>
UndefinedToUnknown<KeysOf<RouteGeneric['Body']> extends never ? CallValidatorTypeProvider<TypeProvider, SchemaCompiler['body']> : RouteGeneric['Body']>

// The target request type. This type is inferenced on fastify 'requests' via generic argument assignment
export interface FastifyRequestType<Params = unknown, Querystring = unknown, Headers = unknown, Body = unknown> {
Expand All @@ -63,8 +65,8 @@ export interface ResolveFastifyRequestType<TypeProvider extends FastifyTypeProvi
// Resolves the Reply type by taking a union of response status codes and content-types
type ResolveReplyFromSchemaCompiler<TypeProvider extends FastifyTypeProvider, SchemaCompiler extends FastifySchema> = {
[K1 in keyof SchemaCompiler['response']]: SchemaCompiler['response'][K1] extends { content: { [keyof: string]: { schema: unknown } } } ? ({
[K2 in keyof SchemaCompiler['response'][K1]['content']]: CallTypeProvider<TypeProvider, SchemaCompiler['response'][K1]['content'][K2]['schema']>
} extends infer Result ? Result[keyof Result] : unknown) : CallTypeProvider<TypeProvider, SchemaCompiler['response'][K1]>
[K2 in keyof SchemaCompiler['response'][K1]['content']]: CallSerializerTypeProvider<TypeProvider, SchemaCompiler['response'][K1]['content'][K2]['schema']>
} extends infer Result ? Result[keyof Result] : unknown) : CallSerializerTypeProvider<TypeProvider, SchemaCompiler['response'][K1]>
} extends infer Result ? Result[keyof Result] : unknown;

// The target reply type. This type is inferenced on fastify 'replies' via generic argument assignment
Expand Down

0 comments on commit 6e2caa7

Please sign in to comment.