DeesseJS Errors

Message Rendering

Render error messages from validated fields in @deessejs/errors.

When you attach a fields schema to an error factory, the message is computed from the parsed data, not the raw input. This lets you format fields that have been transformed, validated, or branded by your schema.

Pass a function that takes the parsed fields and returns the message. The data you receive is the Standard Schema output, with all transforms already applied:

function.ts
import { z } from "zod";
import { error } from "@deessejs/errors";

const ValidationError = error({
  name: "ValidationError",
  fields: z.object({
    field: z.string(),
    reason: z.string(),
  }),
  message: (data) => `Field "${data.field}" is invalid: ${data.reason}`,
});

const err = ValidationError({ field: "email", reason: "invalid format" });
console.log(err.message); // -> Field "email" is invalid: invalid format

The function receives the Standard Schema output type. After a z.coerce.number(), z.brand(), or any transform, you see the final shape.

With Valibot

valibot.ts
import * as v from "valibot";
import { error } from "@deessejs/errors";

const FormatError = error({
  name: "FormatError",
  fields: v.object({
    expected: v.string(),
    actual: v.string(),
  }),
  message: (data) => `Expected ${data.expected}, got ${data.actual}`,
});

const err = FormatError({ expected: "number", actual: "string" });
console.log(err.message); // -> Expected number, got string

With ArkType

arktype.ts
import { type } from "@ark/type";
import { error } from "@deessejs/errors";

const ApiError = error({
  name: "ApiError",
  fields: type({
    method: "string",
    endpoint: "string",
    statusCode: "number",
  }),
  message: (data) => `${data.method.toUpperCase()} ${data.endpoint} failed with status ${data.statusCode}`,
});

const err = ApiError({ method: "post", endpoint: "/users", statusCode: 500 });
console.log(err.message); // -> POST /users failed with status 500

Modifiers via the function body

The function form replaces the legacy :upper / :lower / :json modifiers. Implement them inline with the JS string APIs you already know:

modifiers.ts
import { z } from "zod";
import { error } from "@deessejs/errors";

const UserCreatedError = error({
  name: "UserCreatedError",
  fields: z.object({ userId: z.string() }),
  message: (data) => `Created user: ${data.userId.toUpperCase()}`,
});

const err = UserCreatedError({ userId: "usr_abc" });
console.log(err.message); // -> Created user: USR_ABC

JSON.stringify replaces :json:

json.ts
import { z } from "zod";
import { error } from "@deessejs/errors";

const DataError = error({
  name: "DataError",
  fields: z.object({ payload: z.object({ id: z.number(), name: z.string() }) }),
  message: (data) => `Invalid data: ${JSON.stringify(data.payload)}`,
});

const err = DataError({ payload: { id: 1, name: "Ada" } });
console.log(err.message); // -> Invalid data: {"id":1,"name":"Ada"}

Deprecated: string template (removed in 2.0.0)

The string-template form (message: "Field {field} is invalid") was deprecated in 1.4.0 and removed in 2.0.0 for the schema overload. A factory declared with a string message is no longer accepted alongside a schema; the schema overload requires a function-form message. The legacy no-schema form (no fields schema) still accepts a string message, and renders it without placeholder substitution:

legacy.ts
import { error } from "@deessejs/errors";

const ValidationError = error({
  name: "ValidationError",
  message: "Field {field} is invalid",
});

const err = ValidationError();
console.log(err.message); // -> Field {field} is invalid

Behavior summary:

  • The function form is the only path that produces type-checked, post-transform fields. New code should use the function form.
  • For no-schema factories, a string message is rendered verbatim. The function form is the recommended path.
  • Schema validation, when supplied, runs before rendering; failed validation throws ArgsValidationError.

See Also

On this page