Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Provide correct error message on the validation error. #115

Merged
merged 7 commits into from
Sep 28, 2024

Conversation

Bram-dc
Copy link
Contributor

@Bram-dc Bram-dc commented Sep 28, 2024

The previous error message for each validation error was the entire zod error message instead of the issue message.

@kibertoad
Copy link
Collaborator

@Bram-dc somewhat related - could you please add a type for request validation errors? structure changed, and it's hard to map response in a custom error handler type for an error now

@Bram-dc
Copy link
Contributor Author

Bram-dc commented Sep 28, 2024

@Bram-dc somewhat related - could you please add a type for request validation errors? structure changed, and it's hard to map response in a custom error handler type for an error now

Do you mean something like this?

@kibertoad
Copy link
Collaborator

@Bram-dc almost, although it is not the structure that we are actually getting in the error handler. I've pushed the test for this case.

See screenshot for the debugger:
image

Wonder if we need to adjust the type or the error

@kibertoad
Copy link
Collaborator

@Bram-dc I think the new type is for the issue entry, not for the error per se

@Bram-dc
Copy link
Contributor Author

Bram-dc commented Sep 28, 2024

Yes it is indeed.

@Bram-dc
Copy link
Contributor Author

Bram-dc commented Sep 28, 2024

if (error.validation && isZodFastifySchemaValidationError(error.validation))

@kibertoad
Copy link
Collaborator

@Bram-dc And for these we actually do not need to provide method and url for every entry, as they are going to be repeated for every single entry; and as you have said, they are available on the request itself anyway. original idea was to have them on the parent error, but it seems that we don't actually need them there

@Bram-dc
Copy link
Contributor Author

Bram-dc commented Sep 28, 2024

if (error.validation && isZodFastifySchemaValidationError(error.validation))

error.validation is an array

@Bram-dc
Copy link
Contributor Author

Bram-dc commented Sep 28, 2024

Should I remove them?

@kibertoad
Copy link
Collaborator

@Bram-dc What I'm looking for is a typeguard that could accurately narrow down error to what it actually is. Value of error.validation should be correctly derived from that.

And yeah, let's drop the url and method, but instead provide type guards for both request and response errors, so that these could be correctly mapped by hand in the custom error handler.

@Bram-dc
Copy link
Contributor Author

Bram-dc commented Sep 28, 2024

I have a solution, give me a few minutes

@Bram-dc
Copy link
Contributor Author

Bram-dc commented Sep 28, 2024

export class ZodFastifySchemaValidationError implements FastifySchemaValidationError {
  constructor(
    public message: string,
    public keyword: ZodIssueCode,
    public instancePath: string,
    public schemaPath: string,
    public params: {
      issue: ZodIssue;
      zodError: ZodError;
    },
  ) {}
}

export const isFastifyErrorZodFastiySchemaValidationError = (
  error: FastifyError,
): error is FastifyError & { validation: ZodFastifySchemaValidationError[] } =>
  error.validation &&
  error.validation.every((issue) => issue instanceof ZodFastifySchemaValidationError);

@Bram-dc
Copy link
Contributor Author

Bram-dc commented Sep 28, 2024

Do we want both isZodFastiySchemaValidationError and isFastifyErrorZodFastiySchemaValidationError?

I also need some help with naming of these 2 functions.

edit: no its not only isFastifyErrorZodFastiySchemaValidationError will do, still no like the naming yet.

@Bram-dc
Copy link
Contributor Author

Bram-dc commented Sep 28, 2024

maybe name it hasZodSchemaValidationErrors and only allow FastifyError as input?

@kibertoad
Copy link
Collaborator

@Bram-dc I would avoid instanceof checks, they do not work reliably across realms. Very liberal check that checks for presence of a few fields by name should be sufficient, probability of a collision with a similarly shaped error is pretty low. Alternatively we can put a simple is[ErrorName] field within the error for a quick check.

How about isFastifyRequestValidationError and isFastifyResponseValidationError?

@kibertoad
Copy link
Collaborator

only allow FastifyError as input?

That would work if fastify already narrows down all the errors it receives within the errorHandler to FastifyError. We need to accept as input what it gives us there

@Bram-dc
Copy link
Contributor Author

Bram-dc commented Sep 28, 2024

@Bram-dc I would avoid instanceof checks, they do not work reliably across realms.

Can I do error instanceof Error or is that also not reliable?
And from there continue narrowing

@kibertoad
Copy link
Collaborator

kibertoad commented Sep 28, 2024

instanceof Error is not reliable either. If you want a more reliable check, use this:

export function isError(maybeError: unknown): maybeError is Error {
  return (
    maybeError instanceof Error || Object.prototype.toString.call(maybeError) === '[object Error]'
  )
}

(this is how Node.js itself used to do the check)

@Bram-dc
Copy link
Contributor Author

Bram-dc commented Sep 28, 2024

What about this, (I removed it, my bad)

export class ZodFastifySchemaValidationError implements FastifySchemaValidationError {
  public name = 'ZodFastifySchemaValidationError';

  constructor(
    public message: string,
    public keyword: ZodIssueCode,
    public instancePath: string,
    public schemaPath: string,
    public params: {
      issue: ZodIssue
      zodError: ZodError
    },
  ) {}
}


export const isZodFastifySchemaValidationError = (error: unknown): error is ZodFastifySchemaValidationError =>
    error instanceof Error && error.name === 'ZodFastifySchemaValidationError';

export const hasZodFastifySchemaValidationErrors = (
  error: unknown,
): error is FastifyError & { validation: ZodFastifySchemaValidationError[] } =>
    error instanceof Error &&
  'validation' in error &&
  Array.isArray(error.validation) &&
  error.validation.every(isZodFastifySchemaValidationError);

@Bram-dc
Copy link
Contributor Author

Bram-dc commented Sep 28, 2024

Than this?

export class ZodFastifySchemaValidationError implements FastifySchemaValidationError {
  public name = 'ZodFastifySchemaValidationError';

  constructor(
    public message: string,
    public keyword: ZodIssueCode,
    public instancePath: string,
    public schemaPath: string,
    public params: {
      issue: ZodIssue;
      zodError: ZodError;
    },
  ) {}
}

export const isZodFastifySchemaValidationError = (
  error: unknown,
): error is ZodFastifySchemaValidationError =>
  typeof error === 'object' &&
  error !== null &&
  'name' in error &&
  error.name === 'ZodFastifySchemaValidationError';

export const hasZodFastifySchemaValidationErrors = (
  error: unknown,
): error is FastifyError & { validation: ZodFastifySchemaValidationError[] } =>
  typeof error === 'object' &&
  error !== null &&
  'validation' in error &&
  Array.isArray(error.validation) &&
  error.validation.every(isZodFastifySchemaValidationError);

@Bram-dc
Copy link
Contributor Author

Bram-dc commented Sep 28, 2024

I was looking at ZodError and saw this. Is this reliable?

image

@kibertoad
Copy link
Collaborator

@Bram-dc instanceof always relies on being in the same realm. if an error is thrown from a package imported from another package, it will not work reliably, so I wouldn't trust that. Latest version that you've suggested looks good, although I'm not sure we need to iterate entire array, probability of someone mixing different entities there is very low

@Bram-dc
Copy link
Contributor Author

Bram-dc commented Sep 28, 2024

Last thing, do you think it is better if ZodFastifySchemaValidationError was a type or a class? FastifySchemaValidationError itself is also just a type.

@Bram-dc
Copy link
Contributor Author

Bram-dc commented Sep 28, 2024

If it is a class we should extend Error and add the ZodError as a cause instead of a param.

@kibertoad
Copy link
Collaborator

No strong opinion, either works

@Bram-dc
Copy link
Contributor Author

Bram-dc commented Sep 28, 2024

🚀

@kibertoad kibertoad merged commit 6f7ebed into turkerdev:main Sep 28, 2024
6 checks passed
@kibertoad
Copy link
Collaborator

thank you! I will release a new version tomorrow

@Bram-dc Bram-dc deleted the error-message-fix branch September 29, 2024 18:48
@kibertoad
Copy link
Collaborator

kibertoad commented Sep 29, 2024

@Bram-dc In a hindsight I see that the error structure that we have is not ideal, we are leaking implementation details:

      {
        "details": {
          "issues": [
            {
              "instancePath": "/name",
              "keyword": "invalid_type",
              "message": "Required",
              "name": "ZodFastifySchemaValidationError",
              "params": {
                "issue": {
                  "code": "invalid_type",
                  "expected": "string",
                  "message": "Required",
                  "path": [
                    "name",
                  ],
                  "received": "undefined",
                },
                "zodError": {
                  "issues": [
                    {
                      "code": "invalid_type",
                      "expected": "string",
                      "message": "Required",
                      "path": [
                        "name",
                      ],
                      "received": "undefined",
                    },
                  ],
                  "name": "ZodError",
                },
              },
              "schemaPath": "#/name/invalid_type",
            },
          ],
          "method": "POST",
          "url": "/",
        },
        "error": "Response Validation Error",
        "message": "Request doesn't match the schema",
        "statusCode": 400,
      }

Ideally neither ZodFastifySchemaValidationError, nor zodError, nor ZodError should be included in the exposed entity. Wonder if we could hide the name (e. g. by making it non-enumerable, or at least rename it to remove reference to Zod). Probably also rename zodError to validationError; but do we actually need it? doesn't issue field already include all the same data?

@Bram-dc
Copy link
Contributor Author

Bram-dc commented Sep 29, 2024

@kibertoad What about this? #120

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants