fastify / fastify-type-provider-typebox

A Type Provider for Typebox
MIT License
145 stars 25 forks source link

Request types resolving to unknown when setting route prehandlers #138

Open zacharynoble opened 4 months ago

zacharynoble commented 4 months ago

Prerequisites

Fastify version

4.26.2

Plugin version

4.0.0

Node.js version

21.4.0

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

13.1

Description

Without passing preHandlers to to a route, I get automatic typesafety on request objects based on the defined schema.

When I do pass preHandlers which are using the request or reply objects, the types disappear, resolving to unknown and causing TS errors.

Steps to Reproduce

This code works:

const fastify = Fastify().withTypeProvider<TypeBoxTypeProvider>()

fastify.post('/', {
  schema: {
    body: Type.Object({
      x: Type.String(),
      y: Type.Number(),
      z: Type.Boolean()
    })
  }
}, (req) => {
  const { x, y, z } = req.body // x,y,z are typed properly
})

However, If I try to pass a preHandler, i.e. to verify authentication, all of the request values lose their typing and become 'unknown', yielding TS type errors when I try to reference those variables.

This code causes TypeScript errors:

export const authenticateUser = (req: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction) => {
    ....
};

...

const fastify = Fastify().withTypeProvider<TypeBoxTypeProvider>()

fastify.post('/', {
  schema: {
    body: Type.Object({
      x: Type.String(),
      y: Type.Number(),
      z: Type.Boolean()
    })
   },
   preHandler: authenticateUser // this is what causes the problems
  }
}, (req) => {
  const { x, y, z } = req.body // ERROR: Property 'x' does not exist on type 'unknown', ...same for y and z
})

The strangest thing is that I can fix this error and get back type safety by passing an explicit callback to the preHandler as such:

preHandler: (req, reply, done) => authenticateUser(req, reply, done)

or

preHandler: (...args) => authenticateUser(...args)

Another way to fix this is to set the types for the req / reply params in the plugin to any. Which is certainly wrong, albeit potentially illuminating.

This same exact error occurs with the Zod type provider as well. There is an open issue on this, a couple months old, with many comments but no resolution.

Thank you for any help you can direct at this, I'm sure it will help the many people who I have seen stuck on this issue!

kburdett commented 4 months ago

I have encountered this issue as well. I don't think that this is an issue with the TypeBox provider though. I found the best solution for me was to allow for generic inputs into the authenticate function itself.

export function authenticateUser<T extends FastifyRequest, U extends FastifyReply>(req: T, reply: U, done: HookHandlerDoneFunction) {
    ...
}

Oddly, I had to also modify the preHandler to be in array format. This one left me a bit baffled. I wasn't able to trace the relevant types through Fastify's massive generics chaining to explain it. I gave up and just put it in an array.

preHandler: [authenticateUser],
zacharynoble commented 4 months ago

I actually tried that exact same thing, but I didn't try it as an array so I wrote it off as not an option. Good find, I think that's a bit better than the solution I found. Just gonna do that for now.

m4rvr commented 4 months ago

Yeah, this has been an issue for a long time. It also works if you just put an as any after like

preHandler: authenticateUser as any

Then you don't even need the generics in the function. But the array + generics is a great catch too, using it now 😆