turkerdev / fastify-type-provider-zod

MIT License
386 stars 24 forks source link

custom error handler unable to check for instanceof ResponseValidationError #64

Open nickramsbottom opened 1 year ago

nickramsbottom commented 1 year ago

I am writing my own error handler for Fastify and want to intercept ResponseValidationErrors similar to the code example here: https://github.com/turkerdev/fastify-type-provider-zod/issues/26#issuecomment-1516147720. I'm using TypeScript.

When I try to check for the error using error instanceof ResponseValidationError I get false.

error.name === 'ResponseValidationError' is true so it is an instance of ResponseValidationError: https://github.com/turkerdev/fastify-type-provider-zod/blob/85675fad44c7679622d80fb1925f5901cc2f57f8/src/index.ts#L122

Am I missing something here? If the name is set correctly I'd expect the instanceof check to work.

My overall goal is to respond normally on response validation errors and log instead of returning errors to the client.

Code to reproduce below.

import { FastifyInstance } from 'fastify'
import {
    ResponseValidationError,
    ZodTypeProvider,
} from 'fastify-type-provider-zod'
import z, { ZodError } from 'zod'

type Params = {
    id: string
}

export default async function routes(fastify: FastifyInstance) {
    fastify.get('/', async () => ({ hello: 'world' }))
    fastify.withTypeProvider<ZodTypeProvider>().get<{ Params: Params }>(
        '/session/:id',
        {
            schema: {
                params: z.object({
                    id: z.string().max(5),
                }),
                response: {
                    200: z.number(),
                },
            },
        },
        async (request, reply) => {
            const { id } = request.params
            reply.send({ hello: id })
        },
    )

    fastify.setErrorHandler(function (error, request, reply) {
        fastify.log.warn(error)
        console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
        if (error instanceof ZodError) {
            console.log('ZodError')
        }

        if (error instanceof ResponseValidationError) {
            console.log('ResponseValidationError')
        }

        if (error.name === 'ResponseValidationError') {
            console.log('name is ResponseValidationError')
        }

        if (error instanceof Error) {
            console.log('Error')
        }
        console.log('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')

        reply.status(500).send({
            statusCode: 500,
            error: 'Failed successfully',
        })
    })
}
kibertoad commented 4 months ago

I'll add a typeguard for these cases

nickramsbottom commented 4 months ago

@kibertoad am I missing something fundamental with how instanceof works with classes? It looks like it's creating an instance of the class I linked so I'd expect error instanceof ResponseValidationError to be true. I'm starting to doubt I understand what's going on!

kibertoad commented 4 months ago

@nickramsbottom instanceof is tricky in JavaScript, it is dependent on classes being from the same realm, see https://stackoverflow.com/questions/68564010/is-it-possible-several-javascript-realms-share-one-single-global-object

If you import a library A, and another library B imports a library A, and you use a class instantiated by library B using class from library A, there is a high chance they will be of a different realm, and won't pass instanceof check. for this reason I highly advise never to rely on instanceof for classes coming from external libraries, but use custom typeguards instead.

nickramsbottom commented 4 months ago

@kibertoad Interesting! That's my something new learnt today, thank you