-
-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ValidatorSchemaOptions and SerializerSchemaOptions (#103)
BREAKING CHANGE: Use named type arguments and also pass ValidatorSchemaOptions and SerializerSchemaOptions to their respective type provider.
- Loading branch information
Showing
5 changed files
with
463 additions
and
58 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
``` | ||
|
||
|
@@ -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 | ||
{ | ||
|
@@ -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 ( | ||
|
@@ -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 = { | ||
|
@@ -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", | ||
{ | ||
|
@@ -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() | ||
}; | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.