Validation & Hono
Shared Standard Schema validation errors and drop-in Hono middleware.
@nice-code/common-errors gives you a ready-made error domain for Standard Schema validation failures, plus Hono middleware that throws it for you.
bun add @nice-code/common-errorsPeer deps: valibot (or any Standard Schema library), hono (for the /hono subpath).
The validation error domain
Section titled “The validation error domain”err_validation is a @nice-code/error domain. Import it to match or inspect validation errors by domain. It exposes one id — EValidator.standard_schema — with context { issues }.
import { err_validation, EValidator } from "@nice-code/common-errors";
if (err_validation.isExact(caught)) { const hydrated = err_validation.hydrate(caught); const { issues } = hydrated.getContext(EValidator.standard_schema); // issues: readonly StandardSchemaV1.Issue[]}Hono middleware
Section titled “Hono middleware”Import from the /hono subpath.
niceSValidator(target, schema)
Section titled “niceSValidator(target, schema)”A drop-in replacement for @hono/standard-validator’s sValidator. When validation fails it throws a NiceError instead of returning a 400 — so all your error handling goes through one place.
import { niceSValidator } from "@nice-code/common-errors/hono";import * as v from "valibot";
const CreateUserSchema = v.object({ name: v.string(), email: v.pipe(v.string(), v.email()),});
app.post("/users", niceSValidator("json", CreateUserSchema), async (c) => { const body = c.req.valid("json"); // fully typed // ...});Convert the thrown error to a response in Hono’s onError:
import { castNiceError } from "@nice-code/error";import type { ContentfulStatusCode } from "hono/utils/http-status";
app.onError((err, c) => { const niceError = castNiceError(err); // `httpStatusCode` is a plain `number`; Hono's `c.json` only accepts its restricted // status-code union, so the cast bridges the two. The value is a real HTTP code at runtime. return c.json(niceError.toJsonObject(), niceError.httpStatusCode as ContentfulStatusCode);});If something that isn’t a NiceError crashes the request — a dropped DB connection, a thrown string, a bug deep in a dependency — you don’t need a separate 500 branch. castNiceError wraps any non-NiceError value in a generic NiceError flagged isUnhandled: true, with a default httpStatusCode of 500. So the single onError above already returns a well-formed JSON error with a sensible status for every failure path; the isUnhandled flag is what lets you tell a declared, expected error apart from an unexpected crash (e.g. to log it differently or hide its message in production).
niceCatchSValidation()
Section titled “niceCatchSValidation()”Catches the raw responses @hono/standard-validator sends back (its default { success: false, error: [...] } shape) and turns them into NiceError JSON responses — useful when you can’t swap sValidator out yourself.
import { niceCatchSValidation } from "@nice-code/common-errors/hono";
app.use(niceCatchSValidation());// Existing sValidator usage is now intercepted automaticallyapp.post("/data", sValidator("json", MySchema), handler);Full example
Section titled “Full example”import { Hono } from "hono";import type { ContentfulStatusCode } from "hono/utils/http-status";import { niceSValidator } from "@nice-code/common-errors/hono";import { castNiceError } from "@nice-code/error";import * as v from "valibot";
const app = new Hono();
app.onError((err, c) => { const niceError = castNiceError(err); return c.json(niceError.toJsonObject(), niceError.httpStatusCode as ContentfulStatusCode);});
const BodySchema = v.object({ message: v.string() });
app.post("/echo", niceSValidator("json", BodySchema), async (c) => { const { message } = c.req.valid("json"); return c.json({ echo: message });});