Comments (6)
@mcollina Hi!
Automatic type coercion can be achieved with TypeBox Transforms + overriding the Fastify preSerialization and validationCompiler request/response phases. This hasn't been integrated into the provider yet (but there has been a few PR's attempted, see #99 and #127).
If it's helpful, I've updated the provider submitted on #99 to work with the latest versions of Fastify. I'll post this below.
Experimental TypeProvider
The provider is implemented as a single file that can be copied into a project as a standalone module. This code may constitute a future TypeProvider (requiring a major semver)
Expand for provider.ts source code
import { FastifyInstance, FastifySchemaCompiler, FastifyTypeProvider, preValidationHookHandler } from 'fastify'
import { StaticDecode, TSchema } from '@sinclair/typebox'
import { TypeCompiler, TypeCheck } from '@sinclair/typebox/compiler'
import { Value } from '@sinclair/typebox/value'
export * from '@sinclair/typebox'
/** Functions used to transform values during validation and preSerialization phases */
namespace TransformFunctions {
const checks = new Map<TSchema, TypeCheck<TSchema>>()
function ResolveCheck (schema: TSchema): TypeCheck<TSchema> {
if (checks.has(schema)) return checks.get(schema)!
checks.set(schema, TypeCompiler.Compile(schema))
return checks.get(schema)!
}
/* Converts request params, querystrings and headers into their target type */
export function ProcessValue<T extends TSchema> (httpPart: string | undefined, schema: T, value: unknown) {
const converted = httpPart === 'body' ? value : Value.Convert(schema as TSchema, value)
const defaulted = Value.Default(schema, converted)
const cleaned = Value.Clean(schema, defaulted)
return cleaned
}
/* Generates errors for the given value. This function is only called on decode error. */
export function Errors<T extends TSchema> (schema: T, value: unknown) {
return [...ResolveCheck(schema).Errors(value)].map((error) => {
return { message: `${error.message}`, instancePath: error.path }
})
}
/** Decodes a value or returns undefined if error */
export function Decode<T extends TSchema> (schema: T, value: unknown): unknown {
try {
return ResolveCheck(schema).Decode(value)
} catch {
return undefined
}
}
/** Encodes a value or throws if error */
export function Encode<T extends TSchema> (schema: T, value: unknown) {
return ResolveCheck(schema).Encode(value)
}
}
const TypeBoxValidatorCompiler: FastifySchemaCompiler<TSchema> = ({ schema, httpPart }) => {
return (value): any => {
const processed = TransformFunctions.ProcessValue(httpPart, schema, value)
const decoded = TransformFunctions.Decode(schema, processed)
return decoded ? { value: decoded } : { error: TransformFunctions.Errors(schema, processed) }
}
}
const TypeBoxPreSerializationHook: preValidationHookHandler = (...args: any[]) => {
const [request, reply, payload, done] = args
const response = request.routeOptions.schema.response
const schema = response ? response[reply.statusCode] : undefined
try {
return schema !== undefined
? done(null, TransformFunctions.Encode(schema, payload))
: done(null, payload) // no schema to encode
} catch (error) {
done(error, null)
}
}
/** `[Experimental]` Specialized Type Provider that supports Transform type decoding */
export interface TypeBoxTransformDecodeProvider extends FastifyTypeProvider {
output: this['input'] extends TSchema ? StaticDecode<this['input']> : unknown
}
/** `[Experimental]` Configures a Fastify instance to use the TypeBox Transform infrastructure. */
export function TypeProvider<T extends FastifyInstance> (instance: T) {
return instance.withTypeProvider<TypeBoxTransformDecodeProvider>()
.addHook('preSerialization', TypeBoxPreSerializationHook)
.setValidatorCompiler(TypeBoxValidatorCompiler)
}
Experimental TypeProvider Usage
This is an example implementation using the provider above. It automatically converts numeric timestamps into JS Date objects by way of Transform types. Note that the configuration of the preSerialization and validationCompiler is handled by way of the TypeProvider function. Also note that this function also sets up the useTypeProvider inference.
import Fastify from 'fastify'
import { Type, TypeProvider } from './provider' // from above
// Function to configure validationCompiler and preSerialization & TypeProvider
const fastify = TypeProvider(Fastify())
// Transforms Number timestamp into a Date object.
const Timestamp = Type.Transform(Type.Number())
.Decode(value => new Date(value)) // runs on -> validationCompiler phase
.Encode(value => value.getTime()) // runs on -> preSerialization phase
// Route: Note that the timestamp is observed as Date object within the route.
fastify.post('/', {
schema: {
body: Timestamp,
response: {
200: Timestamp
}
}
}, (req, reply) => {
console.log('server body:', req.body) // ... receive Date object
reply.send(new Date()) // ... send Date object
})
// Client: Note that client must send Number for the request.
fastify
.inject()
.post('/').headers({ 'Content-Type': 'application/json' })
.body(new Date().getTime()) // ... client sends number timestamps
.then(response => {
console.log(response.payload) // ... client receives number timestamps
})
I may be able to take another look at pushing this functionality through later in the year, but it may be a good opportunity for someone in the community to have a go also (I'd certainly be happy to review a PR if someone wants to take the above provider and integrate it). In the short term though, I would recommend a bit of experimentation with the provider implementation submitted here (just to make sure it ticks all the boxes, and to check that the overriding of request/response phases doesn't have unintended breakage in plugins)
Also, need to compare the implementation submitted here with future proposals to update the provider inference, just referencing this PR fastify/fastify#5315 which would have implication for this and other providers.
Let me know if the above helps. Happy to field any additional questions on the implementation :)
Cheers!
S
from fastify-type-provider-typebox.
Struggle with that problem too, using this solution for now.
from fastify-type-provider-typebox.
@sinclairzx81 wdyt? How can we solve this?
from fastify-type-provider-typebox.
cc @ehaynes99
from fastify-type-provider-typebox.
Why is preSerialization hook used instead of setSerializerCompiler?
from fastify-type-provider-typebox.
@Bram-dc Hi,
Why is preSerialization hook used instead of setSerializerCompiler?
I believe the intent here was to apply the transform / encode "before" the value was sent for serialization. The preSerialization
hook seemed the best way to achieve this.
from fastify-type-provider-typebox.
Related Issues (20)
- Error `forgotten to call 'done'` since version 3.4 HOT 9
- `Type.Void` / `Type.Undefined` throws `FST_ERR_SCH_SERIALIZATION_BUILD` since typebox 0.30.0 HOT 3
- Type error: TypeBoxTypeProvider does not satisfy the constraint FastifyTypeProvider HOT 1
- Reply type check improvement HOT 3
- Support for transform types HOT 2
- Fastify fails to work with FormatRegistry from TypeBox HOT 1
- Type.Record Generating Incorrect Model in Fastify HOT 2
- Request types resolving to unknown when setting route prehandlers HOT 3
- Support for responses HOT 2
- Does not type check if withTypeProvider is not fluent HOT 6
- Widen typebox peerDependency to allow 0.26 HOT 7
- Support typebox 0.27.8 HOT 2
- Release 3.1.0 broke type inference on fastify route handler request parameter HOT 18
- string auto convert to number HOT 2
- Mention in Typebox Readme.md under "Ecosystem" HOT 1
- Type.String({ format: "date" }) doesn't work in body validator HOT 7
- Allow providing `references: Types.TSchema[]` to TypeboxCompiler
- incorrect (and potentially broken) dependency resolution error when installing latest via NPM HOT 2
- Is there way to create wrapper function for route handler inferring schema type? HOT 5
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from fastify-type-provider-typebox.