Saltar al contenido
Documentación en español

Traducción de la página original en inglés. Si algo no cuadra, prevalece la versión en inglés.

Errores y problem+json

En DaloyJS los errores son ciudadanos de primera clase. Cada HttpError lanzado se serializa como problem+json de la RFC 9457 con una URI type estable, un id de petición y el Content-Type adecuado.

Del error lanzado a problem+json
  1. 01handlerthrow new NotFoundError(...)cualquier subclase de HttpError
  2. 02frameworkMapear a los campos de la RFC 9457type · title · status · detail
  3. 03producciónOcultar el detalle de los 5xxsolo se muestra si el entorno es development o test
  4. 04salidaRespuesta serializadaapplication/problem+json + x-request-id
Un HttpError lanzado se convierte automáticamente en un documento problem+json. Salvo que el entorno sea explícitamente development o test, el campo detail de las respuestas 5xx se oculta para que las trazas de pila y los fragmentos de SQL nunca lleguen al cliente, mientras que el error completo sigue llegando a tu hook onError.

Clases de error integradas

ts
import {
  HttpError,                         // clase base: cualquier status, type/title/headers personalizados
  BadRequestError,                   // 400
  UnauthorizedError,                 // 401 + cache-control: no-store
  ForbiddenError,                    // 403 + cache-control: no-store
  NotFoundError,                     // 404
  MethodNotAllowedError,             // 405 + cabecera Allow
  RequestTimeoutError,               // 408 / 503 / 504 + cache-control: no-store
  ConflictError,                     // 409 + cache-control: no-store
  PayloadTooLargeError,              // 413
  UnsupportedMediaTypeError,         // 415
  ValidationError,                   // 422
  TooManyRequestsError,              // 429 + Retry-After + cache-control: no-store
  RequestHeaderFieldsTooLargeError,  // 431
  InternalError,                     // 500 (detail oculto fuera de dev/test)
  ServiceUnavailableError,           // 503 + Retry-After + cache-control: no-store
} from "@daloyjs/core";

TooManyRequestsError y ServiceUnavailableError validan retryAfterSeconds de la misma forma: cualquier valor que no sea un entero seguro no negativo lanza un RangeError al construirlo, así que un NaN procedente de una variable de entorno ausente nunca se convierte en una cabecera Retry-After no válida. RequestTimeoutError (el plazo de requestTimeoutMs, consulta requestTimeoutStatus, en inglés) es no-store en todas sus variantes de estado desde la 1.16.0, así que una caché compartida nunca sigue sirviendo un 503/504 después de que el backend se recupere. Un new HttpError(status, …) a secas recibe un type de problem https://httpstatuses.io/<status>; las clases integradas llevan URIs estables https://daloyjs.dev/errors/<slug>.

Lanzar errores en un handler

ts
import { NotFoundError } from "@daloyjs/core";

app.get(
  "/users/:id",
  {
    operationId: "getUser",
    responses: { 200: { description: "ok" }, 404: { description: "missing" } },
  },
  async ({ params }) => {
    const user = await db.find(params.id);
    if (!user) throw new NotFoundError(`user ${params.id} not found`);
    return { status: 200, body: user };
  },
);

Formato en la red

El id de petición se devuelve al cliente en dos sitios: la cabecera de respuesta x-request-id y (según la RFC 9457 §3.1) el campo instance del documento problem, como una URN urn:request:<uuid>. No hay una propiedad requestId de primer nivel: los clientes deben leer la cabecera o extraer la URN de instance.

json
HTTP/1.1 404 Not Found
content-type: application/problem+json
x-request-id: c9aa8e1c-7a6e-4f1e-9f44-c2e5d2c4a431

{
  "type": "https://daloyjs.dev/errors/not-found",
  "title": "Not Found",
  "status": 404,
  "detail": "user 42 not found",
  "instance": "urn:request:c9aa8e1c-7a6e-4f1e-9f44-c2e5d2c4a431"
}

Ocultación en producción

DaloyJS elimina el campo detail de cualquier respuesta 5xx salvo que el entorno sea explícitamente development o test, para que los mensajes de error internos y los fragmentos de SQL no lleguen a los clientes. El error completo se sigue enviando a tu logger y al hook onError.

Configuracióndetail en 5xx
env: "development" o env: "test"se muestra
sin env, NODE_ENV=development o testse muestra
sin env, NODE_ENV sin definir, vacío o con cualquier otro valorse oculta
env: "production", production: true o NODE_ENV=productionse oculta
production: false con NODE_ENV sin definirse oculta

Un entorno sin definir falla en modo cerrado a propósito. Los runtimes edge como Cloudflare Workers y Deno Deploy no tienen NODE_ENV, y tratar esa situación como desarrollo enviaría los mensajes de las excepciones a los clientes. daloy dev define NODE_ENV=development en su proceso hijo cuando no lo has definido tú, así que el desarrollo en local sigue mostrando el detalle. Los errores que serializas tú mismo (por ejemplo, un cuerpo de respuesta construido a partir de err.message en un bloque catch) no se ocultan. Lánzalos en su lugar, o lanza un HttpError con un detail seguro.

Clases de error personalizadas

ts
import { HttpError } from "@daloyjs/core";

export class QuotaExceededError extends HttpError {
  constructor(resource: string) {
    super(429, {
      title: "Quota exceeded",
      type: "https://api.example.com/errors/quota-exceeded",
      detail: `Quota exceeded for ${resource}`,
    });
  }
}

Todas las clases integradas de arriba definen un type https://daloyjs.dev/errors/<slug>. Un HttpError simple lanzado sin type recurre a https://httpstatuses.io/<status>. El middleware propio que responde 503 (loadShedding(), concurrencyLimit(), requestTimeoutStatus: 503) define su propio type de daloyjs.dev, así que la alternativa solo se aplica a los errores que construyes tú. Define type cuando los clientes dependan de él, o usa una clase integrada:

ts
throw new HttpError(503, { title: "Service Unavailable" });
// "type": "https://httpstatuses.io/503"

throw new HttpError(503, {
  title: "Service Unavailable",
  type: "https://api.example.com/errors/service-unavailable",
});

Servicio no disponible

Para el modo de mantenimiento, la caída de una dependencia o una instancia que se está vaciando, lanza ServiceUnavailableError. Responde 503 con un type estable https://daloyjs.dev/errors/service-unavailable, un Retry-After opcional y cache-control: no-store, para que una CDN nunca siga sirviendo la caída después de que te recuperes.

ts
import { ServiceUnavailableError } from "@daloyjs/core";

if (await maintenance.isOn()) {
  // Retry-After: 120. El detail se oculta fuera de development/test,
  // así que la cabecera es lo que el cliente puede usar de verdad.
  throw new ServiceUnavailableError(120, "scheduled database maintenance");
}

retryAfterSeconds debe ser un entero no negativo. Cualquier otra cosa (NaN por una variable de entorno ausente, -1, 1.5) lanza un RangeError en lugar de enviar una cabecera Retry-After mal formada.

onError personalizado

ts
app.use({
  onError: async (error, ctx) => {
    logger.error({ err: error, requestId: ctx?.state.requestId }, "request failed");
    // devuelve una Response para sustituirla; si no, DaloyJS serializa problem+json
  },
});

Última actualización: