How to distinguish response validation error from other validation errors in Elysia.js

1k Views Asked by At

I would like to validate response of request in silent (for example only log to console) because I don't want to interrupt user with this information (also response schema is used in elysiajs swagger plugin so it is useful to have it), but I still want to know when this happens. Also I want to validate body strictly and return 400.

This is far as I got, I can use onError and catch an error, but can't differentiate between body validation and response validation.

import { Elysia, t } from "elysia";

export const PostLoginRoute = new Elysia()
  .onError((error) => {
    console.log("error", error);
    return new Response(error.toString());
  })
  .post("/login", ({ body }) => body, {
    body: t.Object(
      {
        username: t.String(),
        password: t.String(),
      },
      {
        description: "Expected an username and password",
      }
    ),
    response: t.Object({
      username: t.String(),
      password: t.String(),
      id: t.String(),
    }),

    detail: {
      summary: "Sign in the user",
      tags: ["authentication"],
    },
  });

error content

error: Invalid response, 'id': Expected string

Expected: {
  "username": "",
  "password": "",
  "id": ""
}

Found: {
  "username": "user",
  "password": "pass"
}
 code: "VALIDATION"

      at new K0 (/hi-elysia/node_modules/elysia/dist/bun/index.js:8:25881)
,
  set: {
    headers: {},
    status: 500
  },
  code: "VALIDATION"
}

I can parse message of error and find Invalid response substring, but I kind of dislike this idea. Is there any better approach?

2

There are 2 best solutions below

2
Tim Rus On

Take a look at this page: https://elysiajs.com/patterns/error-handling.html

There is an example showing exactly how to do that.

In short, add local error handler, switch on an error code, than map through errors looking into path property.

new Elysia()
.post('/', ({ body }) => body, {
    body: t.Object({
        name: t.String(),
        age: t.Number()
    }),
    error({ code, error }) {
        switch (code) {
            case 'VALIDATION':
                console.log(error.all)

                // Find a specific error name (path is OpenAPI Schema compliance)
                const name = error.all.find((x) => x.path === '/name')

                // If has validation error, then log it
                if(name)
                    console.log(name)
        }
    }
})

X - is an object containing info you need.

Something like this:

E {
  type: 53,
  schema: {
    type: "string",
    [Symbol(TypeBox.Kind)]: "String"
  },
  path: "/name",
  value: undefined,
  message: "Expected string"
}
0
Magnus Bull On

This is how I do it.

onError(({ error, code, set }) => {
  switch (code) {
    // Other cases...
    
    case "VALIDATION": {
      if (error.type === "response") {
        set.status = "Internal Server Error";

        if (process.env.NODE_ENV === "development") {
          return error.message;
        }

        return { error: "Server validation error" }
      }
    }
  }
})

I have this as a global error middleware, but you can also attach it directly to a specific route. The case will be VALIDATION both when the request body is invalid and when the response body is invalid. You can check whether it's request or response with error.type after narrowing down the type on code.

Here, I've set the status code to be 500 if the validation error happened on the server output, because default code for any validation error is 400 (bad request), but I consider it a server error if I didn't properly respond according to the schema. I'm also omitting some details when running in production.