Skip to content

Commit b98efe7

Browse files
authored
Add ValidatorSchemaOptions and SerializerSchemaOptions (#103)
BREAKING CHANGE: Use named type arguments and also pass ValidatorSchemaOptions and SerializerSchemaOptions to their respective type provider.
1 parent 919cfe0 commit b98efe7

File tree

5 files changed

+463
-58
lines changed

5 files changed

+463
-58
lines changed

README.md

Lines changed: 166 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ A Type Provider for [json-schema-to-ts](https://github.com/ThomasAribart/json-sc
88

99
## Install
1010

11-
```
11+
```bash
1212
npm i @fastify/type-provider-json-schema-to-ts
1313
```
1414

@@ -18,8 +18,7 @@ It is required to use `TypeScript@4.3` or above with
1818
[`strict`](https://www.typescriptlang.org/tsconfig#strict)
1919
mode enabled and
2020
[`noStrictGenericChecks`](https://www.typescriptlang.org/tsconfig#noStrictGenericChecks)
21-
disabled. You may take the below configuration (`tsconfig.json`)
22-
as an example.
21+
disabled. You may take the following configuration (`tsconfig.json`) as an example:
2322

2423
```json
2524
{
@@ -33,7 +32,7 @@ as an example.
3332
## Plugin definition
3433

3534
> **Note**
36-
> When using plugin types, withTypeProvider is not required in order to register the plugin
35+
> When using plugin types, `withTypeProvider` is not required to register the plugin.
3736
3837
```ts
3938
const plugin: FastifyPluginAsyncJsonSchemaToTs = async function (
@@ -56,17 +55,17 @@ const plugin: FastifyPluginAsyncJsonSchemaToTs = async function (
5655
},
5756
},
5857
(req) => {
59-
/// The `x`, `y`, and `z` types are automatically inferred
58+
// The `x`, `y`, and `z` types are automatically inferred
6059
const { x, y, z } = req.body;
6160
}
6261
);
6362
};
6463
```
6564

66-
## Using References from a Shared Schema
65+
## Setting FromSchema for the validator and serializer
6766

68-
JsonSchemaToTsProvider takes a generic that can be passed in the Shared Schema
69-
as shown in the following example
67+
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.
68+
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).
7069

7170
```ts
7271
const userSchema = {
@@ -77,23 +76,53 @@ const userSchema = {
7776
familyName: { type: "string" },
7877
},
7978
required: ["givenName", "familyName"],
80-
} as const;
79+
} as const satisfies JSONSchema;
8180

8281
const sharedSchema = {
8382
$id: "shared-schema",
8483
definitions: {
8584
user: userSchema,
8685
},
87-
} as const;
86+
} as const satisfies JSONSchema;
87+
88+
const userProfileSchema = {
89+
$id: "userProfile",
90+
type: "object",
91+
additionalProperties: false,
92+
properties: {
93+
user: {
94+
$ref: "shared-schema#/definitions/user",
95+
},
96+
joinedAt: { type: "string", format: "date-time" },
97+
},
98+
required: ["user", "joinedAt"],
99+
} as const satisfies JSONSchema
100+
88101

89-
// then when using JsonSchemaToTsProvider, the shared schema is passed through the generic
90-
// references takes an array so can pass in multiple shared schema
91-
const fastify =
92-
Fastify().withTypeProvider<
93-
JsonSchemaToTsProvider<{ references: [typeof sharedSchema] }>
94-
>();
102+
type UserProfile = FromSchema<typeof userProfileSchema, {
103+
references: [typeof sharedSchema]
104+
deserialize: [{ pattern: { type: "string"; format: "date-time" }; output: Date }]
105+
}>;
106+
107+
// Use JsonSchemaToTsProvider with shared schema references
108+
const fastify = Fastify().withTypeProvider<
109+
JsonSchemaToTsProvider<{
110+
ValidatorSchemaOptions: {
111+
references: [typeof sharedSchema]
112+
}
113+
}>
114+
>();
115+
116+
const fastify = Fastify().withTypeProvider<
117+
JsonSchemaToTsProvider<{
118+
ValidatorSchemaOptions: { references: [typeof sharedSchema] }
119+
SerializerSchemaOptions: {
120+
references: [typeof userProfileSchema]
121+
deserialize: [{ pattern: { type: "string"; format: "date-time" }; output: Date }]
122+
}
123+
}>
124+
>()
95125

96-
// now reference the shared schema like the following
97126
fastify.get(
98127
"/profile",
99128
{
@@ -107,11 +136,128 @@ fastify.get(
107136
},
108137
required: ['user'],
109138
},
139+
response: {
140+
200: { $ref: "userProfile#" },
141+
},
110142
} as const,
111143
},
112-
(req) => {
113-
// givenName and familyName will be correctly typed as strings!
144+
(req, reply) => {
145+
// `givenName` and `familyName` are correctly typed as strings
114146
const { givenName, familyName } = req.body.user;
147+
148+
// Construct a compatible response type
149+
const profile: UserProfile = {
150+
user: { givenName: "John", familyName: "Doe" },
151+
joinedAt: new Date(), // Returning a Date object
152+
};
153+
154+
// A type error is surfaced if profile doesn't match the serialization schema
155+
reply.send(profile)
115156
}
116-
);
157+
)
158+
```
159+
160+
## Using References in a Plugin Definition
161+
162+
When defining a plugin, shared schema references and deserialization options can also be used with `FastifyPluginAsyncJsonSchemaToTs` and `FastifyPluginCallbackJsonSchemaToTs`.
163+
164+
### Example
165+
166+
```ts
167+
const schemaPerson = {
168+
$id: "schema:person",
169+
type: "object",
170+
additionalProperties: false,
171+
properties: {
172+
givenName: { type: "string" },
173+
familyName: { type: "string" },
174+
joinedAt: { type: "string", format: "date-time" },
175+
},
176+
required: ["givenName", "familyName"],
177+
} as const satisfies JSONSchema;
178+
179+
const plugin: FastifyPluginAsyncJsonSchemaToTs<{
180+
ValidatorSchemaOptions: { references: [typeof schemaPerson] }
181+
SerializerSchemaOptions: {
182+
references: [typeof schemaPerson]
183+
deserialize: [{ pattern: { type: "string"; format: "date-time" }; output: Date }]
184+
};
185+
}> = async function (fastify, _opts) {
186+
fastify.addSchema(schemaPerson)
187+
188+
fastify.get(
189+
"/profile",
190+
{
191+
schema: {
192+
body: {
193+
type: "object",
194+
properties: {
195+
user: {
196+
$ref: "schema:person",
197+
},
198+
},
199+
required: ['user'],
200+
},
201+
response: {
202+
200: { $ref: "schema:person" },
203+
},
204+
}, // as const satisfies JSONSchema is not required thanks to FastifyPluginAsyncJsonSchemaToTs
205+
},
206+
(req, reply) => {
207+
// `givenName`, `familyName`, and `joinedAt` are correctly typed as strings and validated for format.
208+
const { givenName, familyName, joinedAt } = req.body.user;
209+
210+
// Send a serialized response
211+
reply.send({
212+
givenName: "John",
213+
familyName: "Doe",
214+
// Date objects form DB queries can be returned directly and transformed to string by fast-json-stringify
215+
joinedAt: new Date(),
216+
})
217+
}
218+
)
219+
}
220+
221+
const callbackPlugin: FastifyPluginCallbackJsonSchemaToTs<{
222+
ValidatorSchemaOptions: { references: [typeof schemaPerson] }
223+
SerializerSchemaOptions: {
224+
references: [typeof schemaPerson]
225+
deserialize: [{ pattern: { type: "string"; format: "date-time" }; output: Date }]
226+
};
227+
}> = (fastify, options, done) => {
228+
// Type check for custom options
229+
expectType<string>(options.optionA)
230+
231+
// Schema is already added above
232+
// fastify.addSchema(schemaPerson);
233+
234+
fastify.get(
235+
"/callback-profile",
236+
{
237+
schema: {
238+
body: {
239+
type: "object",
240+
properties: {
241+
user: { $ref: "schema:person" },
242+
},
243+
required: ["user"],
244+
},
245+
response: {
246+
200: { $ref: "schema:person" },
247+
},
248+
},
249+
},
250+
(req, reply) => {
251+
const { givenName, familyName, joinedAt } = req.body.user
252+
253+
reply.send({
254+
givenName,
255+
familyName,
256+
joinedAt: new Date(),
257+
});
258+
}
259+
);
260+
261+
done()
262+
};
117263
```

index.ts

Lines changed: 89 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,42 +10,117 @@ import {
1010

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

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

18+
export interface JsonSchemaToTsProvider<
19+
Options extends JsonSchemaToTsProviderOptions = {}
20+
> extends FastifyTypeProvider {
21+
validator: this['schema'] extends JSONSchema
22+
? FromSchema<
23+
this['schema'],
24+
Options['ValidatorSchemaOptions'] extends FromSchemaOptions
25+
? Options['ValidatorSchemaOptions']
26+
: FromSchemaDefaultOptions
27+
>
28+
: unknown;
29+
serializer: this['schema'] extends JSONSchema
30+
? FromSchema<
31+
this['schema'],
32+
Options['SerializerSchemaOptions'] extends FromSchemaOptions
33+
? Options['SerializerSchemaOptions']
34+
: FromSchemaDefaultOptions
35+
>
36+
: unknown;
37+
}
38+
39+
export interface FastifyPluginJsonSchemaToTsOptions extends JsonSchemaToTsProviderOptions {
40+
Options?: FastifyPluginOptions;
41+
Server?: RawServerBase;
42+
Logger?: FastifyBaseLogger;
43+
};
44+
1845
/**
1946
* FastifyPluginCallback with JSON Schema to Typescript automatic type inference
2047
*
2148
* @example
2249
* ```typescript
2350
* import { FastifyPluginCallbackJsonSchemaToTs } from "@fastify/type-provider-json-schema-to-ts"
2451
*
25-
* const plugin: FastifyPluginCallbackJsonSchemaToTs = (fastify, options, done) => {
52+
* const plugin: FastifyPluginCallbackJsonSchemaToTs<{
53+
* ValidatorSchemaOptions: {
54+
* references: [ SchemaWrite ],
55+
* },
56+
* SerializerSchemaOptions: {
57+
* references: [ SchemaRead ],
58+
* deserialize: [{ pattern: { type: 'string'; format: 'date-time'; }; output: Date; }]
59+
* }
60+
* }> = (fastify, options, done) => {
2661
* done()
2762
* }
2863
* ```
2964
*/
3065
export type FastifyPluginCallbackJsonSchemaToTs<
31-
Options extends FastifyPluginOptions = Record<never, never>,
32-
Server extends RawServerBase = RawServerDefault,
33-
Logger extends FastifyBaseLogger = FastifyBaseLogger
34-
> = FastifyPluginCallback<Options, Server, JsonSchemaToTsProvider, Logger>
66+
Options extends FastifyPluginJsonSchemaToTsOptions = {}
67+
> = FastifyPluginCallback<
68+
Options['Options'] extends FastifyPluginOptions
69+
? Options['Options']
70+
: Record<never, never>,
71+
Options['Server'] extends RawServerBase
72+
? Options['Server']
73+
: RawServerDefault,
74+
JsonSchemaToTsProvider<{
75+
ValidatorSchemaOptions: Options['ValidatorSchemaOptions'] extends FromSchemaOptions
76+
? Options['ValidatorSchemaOptions']
77+
: FromSchemaDefaultOptions,
78+
SerializerSchemaOptions: Options['SerializerSchemaOptions'] extends FromSchemaOptions
79+
? Options['SerializerSchemaOptions']
80+
: FromSchemaDefaultOptions
81+
}>,
82+
Options['Logger'] extends FastifyBaseLogger
83+
? Options['Logger']
84+
: FastifyBaseLogger
85+
>
3586

3687
/**
3788
* FastifyPluginAsync with JSON Schema to Typescript automatic type inference
3889
*
3990
* @example
4091
* ```typescript
41-
* import { FastifyPluginAsyncJsonSchemaToTs } fromg "@fastify/type-provider-json-schema-to-ts"
92+
* import { FastifyPluginAsyncJsonSchemaToTs } from "@fastify/type-provider-json-schema-to-ts"
4293
*
43-
* const plugin: FastifyPluginAsyncJsonSchemaToTs = async (fastify, options) => {
94+
* const plugin: FastifyPluginAsyncJsonSchemaToTs<{
95+
* ValidatorSchemaOptions: {
96+
* references: [ SchemaWrite ],
97+
* },
98+
* SerializerSchemaOptions: {
99+
* references: [ SchemaRead ],
100+
* deserialize: [{ pattern: { type: 'string'; format: 'date-time'; }; output: Date; }]
101+
* }
102+
* }> = async (fastify, options) => {
44103
* }
45104
* ```
46105
*/
47106
export type FastifyPluginAsyncJsonSchemaToTs<
48-
Options extends FastifyPluginOptions = Record<never, never>,
49-
Server extends RawServerBase = RawServerDefault,
50-
Logger extends FastifyBaseLogger = FastifyBaseLogger
51-
> = FastifyPluginAsync<Options, Server, JsonSchemaToTsProvider, Logger>
107+
Options extends FastifyPluginJsonSchemaToTsOptions = {}
108+
> = FastifyPluginAsync<
109+
Options['Options'] extends FastifyPluginOptions
110+
? Options['Options']
111+
: Record<never, never>,
112+
Options['Server'] extends RawServerBase
113+
? Options['Server']
114+
: RawServerDefault,
115+
JsonSchemaToTsProvider<{
116+
ValidatorSchemaOptions: Options['ValidatorSchemaOptions'] extends FromSchemaOptions
117+
? Options['ValidatorSchemaOptions']
118+
: FromSchemaDefaultOptions,
119+
SerializerSchemaOptions: Options['SerializerSchemaOptions'] extends FromSchemaOptions
120+
? Options['SerializerSchemaOptions']
121+
: FromSchemaDefaultOptions
122+
}>,
123+
Options['Logger'] extends FastifyBaseLogger
124+
? Options['Logger']
125+
: FastifyBaseLogger
126+
>

types/index.test-d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ fastify.get('/', {
1616
z: { type: 'boolean' }
1717
},
1818
required: ['x', 'y', 'z']
19-
} as const
19+
}
2020
}
2121
}, (req) => {
2222
expectType<boolean>(req.body.z)

0 commit comments

Comments
 (0)