Skip to content

Commit

Permalink
Add ValidatorSchemaOptions and SerializerSchemaOptions (#103)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: Use named type arguments and also pass ValidatorSchemaOptions and SerializerSchemaOptions to their respective type provider.
  • Loading branch information
bcomnes authored Dec 23, 2024
1 parent 919cfe0 commit b98efe7
Show file tree
Hide file tree
Showing 5 changed files with 463 additions and 58 deletions.
186 changes: 166 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ A Type Provider for [json-schema-to-ts](https://github.com/ThomasAribart/json-sc

## Install

```
```bash
npm i @fastify/type-provider-json-schema-to-ts
```

Expand All @@ -18,8 +18,7 @@ It is required to use `[email protected]` or above with
[`strict`](https://www.typescriptlang.org/tsconfig#strict)
mode enabled and
[`noStrictGenericChecks`](https://www.typescriptlang.org/tsconfig#noStrictGenericChecks)
disabled. You may take the below configuration (`tsconfig.json`)
as an example.
disabled. You may take the following configuration (`tsconfig.json`) as an example:

```json
{
Expand All @@ -33,7 +32,7 @@ as an example.
## Plugin definition

> **Note**
> When using plugin types, withTypeProvider is not required in order to register the plugin
> When using plugin types, `withTypeProvider` is not required to register the plugin.
```ts
const plugin: FastifyPluginAsyncJsonSchemaToTs = async function (
Expand All @@ -56,17 +55,17 @@ const plugin: FastifyPluginAsyncJsonSchemaToTs = async function (
},
},
(req) => {
/// The `x`, `y`, and `z` types are automatically inferred
// The `x`, `y`, and `z` types are automatically inferred
const { x, y, z } = req.body;
}
);
};
```

## Using References from a Shared Schema
## Setting FromSchema for the validator and serializer

JsonSchemaToTsProvider takes a generic that can be passed in the Shared Schema
as shown in the following example
You can set the `FromSchema` settings for things like [`references`](https://github.com/ThomasAribart/json-schema-to-ts#references) and [`deserialization`](https://github.com/ThomasAribart/json-schema-to-ts#deserialization) for the validation and serialization schema by setting `ValidatorSchemaOptions` and `SerializerSchemaOptions` type parameters.
You can use the `deserialize` option in `SerializerSchemaOptions` to allow Date objects in place of date-time strings or other special serialization rules handled by [fast-json-stringify](https://github.com/fastify/fast-json-stringify?tab=readme-ov-file#specific-use-cases).

```ts
const userSchema = {
Expand All @@ -77,23 +76,53 @@ const userSchema = {
familyName: { type: "string" },
},
required: ["givenName", "familyName"],
} as const;
} as const satisfies JSONSchema;

const sharedSchema = {
$id: "shared-schema",
definitions: {
user: userSchema,
},
} as const;
} as const satisfies JSONSchema;

const userProfileSchema = {
$id: "userProfile",
type: "object",
additionalProperties: false,
properties: {
user: {
$ref: "shared-schema#/definitions/user",
},
joinedAt: { type: "string", format: "date-time" },
},
required: ["user", "joinedAt"],
} as const satisfies JSONSchema


// then when using JsonSchemaToTsProvider, the shared schema is passed through the generic
// references takes an array so can pass in multiple shared schema
const fastify =
Fastify().withTypeProvider<
JsonSchemaToTsProvider<{ references: [typeof sharedSchema] }>
>();
type UserProfile = FromSchema<typeof userProfileSchema, {
references: [typeof sharedSchema]
deserialize: [{ pattern: { type: "string"; format: "date-time" }; output: Date }]
}>;

// Use JsonSchemaToTsProvider with shared schema references
const fastify = Fastify().withTypeProvider<
JsonSchemaToTsProvider<{
ValidatorSchemaOptions: {
references: [typeof sharedSchema]
}
}>
>();

const fastify = Fastify().withTypeProvider<
JsonSchemaToTsProvider<{
ValidatorSchemaOptions: { references: [typeof sharedSchema] }
SerializerSchemaOptions: {
references: [typeof userProfileSchema]
deserialize: [{ pattern: { type: "string"; format: "date-time" }; output: Date }]
}
}>
>()

// now reference the shared schema like the following
fastify.get(
"/profile",
{
Expand All @@ -107,11 +136,128 @@ fastify.get(
},
required: ['user'],
},
response: {
200: { $ref: "userProfile#" },
},
} as const,
},
(req) => {
// givenName and familyName will be correctly typed as strings!
(req, reply) => {
// `givenName` and `familyName` are correctly typed as strings
const { givenName, familyName } = req.body.user;

// Construct a compatible response type
const profile: UserProfile = {
user: { givenName: "John", familyName: "Doe" },
joinedAt: new Date(), // Returning a Date object
};

// A type error is surfaced if profile doesn't match the serialization schema
reply.send(profile)
}
);
)
```

## Using References in a Plugin Definition

When defining a plugin, shared schema references and deserialization options can also be used with `FastifyPluginAsyncJsonSchemaToTs` and `FastifyPluginCallbackJsonSchemaToTs`.

### Example

```ts
const schemaPerson = {
$id: "schema:person",
type: "object",
additionalProperties: false,
properties: {
givenName: { type: "string" },
familyName: { type: "string" },
joinedAt: { type: "string", format: "date-time" },
},
required: ["givenName", "familyName"],
} as const satisfies JSONSchema;

const plugin: FastifyPluginAsyncJsonSchemaToTs<{
ValidatorSchemaOptions: { references: [typeof schemaPerson] }
SerializerSchemaOptions: {
references: [typeof schemaPerson]
deserialize: [{ pattern: { type: "string"; format: "date-time" }; output: Date }]
};
}> = async function (fastify, _opts) {
fastify.addSchema(schemaPerson)

fastify.get(
"/profile",
{
schema: {
body: {
type: "object",
properties: {
user: {
$ref: "schema:person",
},
},
required: ['user'],
},
response: {
200: { $ref: "schema:person" },
},
}, // as const satisfies JSONSchema is not required thanks to FastifyPluginAsyncJsonSchemaToTs
},
(req, reply) => {
// `givenName`, `familyName`, and `joinedAt` are correctly typed as strings and validated for format.
const { givenName, familyName, joinedAt } = req.body.user;

// Send a serialized response
reply.send({
givenName: "John",
familyName: "Doe",
// Date objects form DB queries can be returned directly and transformed to string by fast-json-stringify
joinedAt: new Date(),
})
}
)
}

const callbackPlugin: FastifyPluginCallbackJsonSchemaToTs<{
ValidatorSchemaOptions: { references: [typeof schemaPerson] }
SerializerSchemaOptions: {
references: [typeof schemaPerson]
deserialize: [{ pattern: { type: "string"; format: "date-time" }; output: Date }]
};
}> = (fastify, options, done) => {
// Type check for custom options
expectType<string>(options.optionA)

// Schema is already added above
// fastify.addSchema(schemaPerson);

fastify.get(
"/callback-profile",
{
schema: {
body: {
type: "object",
properties: {
user: { $ref: "schema:person" },
},
required: ["user"],
},
response: {
200: { $ref: "schema:person" },
},
},
},
(req, reply) => {
const { givenName, familyName, joinedAt } = req.body.user

reply.send({
givenName,
familyName,
joinedAt: new Date(),
});
}
);

done()
};
```
103 changes: 89 additions & 14 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,42 +10,117 @@ import {

import { FromSchema, FromSchemaDefaultOptions, FromSchemaOptions, JSONSchema } from 'json-schema-to-ts'

export interface JsonSchemaToTsProvider<Options extends FromSchemaOptions = FromSchemaDefaultOptions> extends FastifyTypeProvider {
validator: this['schema'] extends JSONSchema ? FromSchema<this['schema'], Options> : unknown;
serializer: this['schema'] extends JSONSchema ? FromSchema<this['schema'], Options> : unknown;
export interface JsonSchemaToTsProviderOptions {
ValidatorSchemaOptions?: FromSchemaOptions;
SerializerSchemaOptions?: FromSchemaOptions;
}

export interface JsonSchemaToTsProvider<
Options extends JsonSchemaToTsProviderOptions = {}
> extends FastifyTypeProvider {
validator: this['schema'] extends JSONSchema
? FromSchema<
this['schema'],
Options['ValidatorSchemaOptions'] extends FromSchemaOptions
? Options['ValidatorSchemaOptions']
: FromSchemaDefaultOptions
>
: unknown;
serializer: this['schema'] extends JSONSchema
? FromSchema<
this['schema'],
Options['SerializerSchemaOptions'] extends FromSchemaOptions
? Options['SerializerSchemaOptions']
: FromSchemaDefaultOptions
>
: unknown;
}

export interface FastifyPluginJsonSchemaToTsOptions extends JsonSchemaToTsProviderOptions {
Options?: FastifyPluginOptions;
Server?: RawServerBase;
Logger?: FastifyBaseLogger;
};

/**
* FastifyPluginCallback with JSON Schema to Typescript automatic type inference
*
* @example
* ```typescript
* import { FastifyPluginCallbackJsonSchemaToTs } from "@fastify/type-provider-json-schema-to-ts"
*
* const plugin: FastifyPluginCallbackJsonSchemaToTs = (fastify, options, done) => {
* const plugin: FastifyPluginCallbackJsonSchemaToTs<{
* ValidatorSchemaOptions: {
* references: [ SchemaWrite ],
* },
* SerializerSchemaOptions: {
* references: [ SchemaRead ],
* deserialize: [{ pattern: { type: 'string'; format: 'date-time'; }; output: Date; }]
* }
* }> = (fastify, options, done) => {
* done()
* }
* ```
*/
export type FastifyPluginCallbackJsonSchemaToTs<
Options extends FastifyPluginOptions = Record<never, never>,
Server extends RawServerBase = RawServerDefault,
Logger extends FastifyBaseLogger = FastifyBaseLogger
> = FastifyPluginCallback<Options, Server, JsonSchemaToTsProvider, Logger>
Options extends FastifyPluginJsonSchemaToTsOptions = {}
> = FastifyPluginCallback<
Options['Options'] extends FastifyPluginOptions
? Options['Options']
: Record<never, never>,
Options['Server'] extends RawServerBase
? Options['Server']
: RawServerDefault,
JsonSchemaToTsProvider<{
ValidatorSchemaOptions: Options['ValidatorSchemaOptions'] extends FromSchemaOptions
? Options['ValidatorSchemaOptions']
: FromSchemaDefaultOptions,
SerializerSchemaOptions: Options['SerializerSchemaOptions'] extends FromSchemaOptions
? Options['SerializerSchemaOptions']
: FromSchemaDefaultOptions
}>,
Options['Logger'] extends FastifyBaseLogger
? Options['Logger']
: FastifyBaseLogger
>

/**
* FastifyPluginAsync with JSON Schema to Typescript automatic type inference
*
* @example
* ```typescript
* import { FastifyPluginAsyncJsonSchemaToTs } fromg "@fastify/type-provider-json-schema-to-ts"
* import { FastifyPluginAsyncJsonSchemaToTs } from "@fastify/type-provider-json-schema-to-ts"
*
* const plugin: FastifyPluginAsyncJsonSchemaToTs = async (fastify, options) => {
* const plugin: FastifyPluginAsyncJsonSchemaToTs<{
* ValidatorSchemaOptions: {
* references: [ SchemaWrite ],
* },
* SerializerSchemaOptions: {
* references: [ SchemaRead ],
* deserialize: [{ pattern: { type: 'string'; format: 'date-time'; }; output: Date; }]
* }
* }> = async (fastify, options) => {
* }
* ```
*/
export type FastifyPluginAsyncJsonSchemaToTs<
Options extends FastifyPluginOptions = Record<never, never>,
Server extends RawServerBase = RawServerDefault,
Logger extends FastifyBaseLogger = FastifyBaseLogger
> = FastifyPluginAsync<Options, Server, JsonSchemaToTsProvider, Logger>
Options extends FastifyPluginJsonSchemaToTsOptions = {}
> = FastifyPluginAsync<
Options['Options'] extends FastifyPluginOptions
? Options['Options']
: Record<never, never>,
Options['Server'] extends RawServerBase
? Options['Server']
: RawServerDefault,
JsonSchemaToTsProvider<{
ValidatorSchemaOptions: Options['ValidatorSchemaOptions'] extends FromSchemaOptions
? Options['ValidatorSchemaOptions']
: FromSchemaDefaultOptions,
SerializerSchemaOptions: Options['SerializerSchemaOptions'] extends FromSchemaOptions
? Options['SerializerSchemaOptions']
: FromSchemaDefaultOptions
}>,
Options['Logger'] extends FastifyBaseLogger
? Options['Logger']
: FastifyBaseLogger
>
2 changes: 1 addition & 1 deletion types/index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ fastify.get('/', {
z: { type: 'boolean' }
},
required: ['x', 'y', 'z']
} as const
}
}
}, (req) => {
expectType<boolean>(req.body.z)
Expand Down
Loading

0 comments on commit b98efe7

Please sign in to comment.