80 lines · 2.8 KB · TypeScript
RawDownload
import { Cause, Data, Effect, Predicate, Result } from "effect";import { HttpServerRespondable, HttpServerResponse } from "effect/unstable/http"; import { captureCause, captureCauseEffect } from "../observability"; // Implements `Respondable` so the framework's default cause→response// pipeline (`HttpServerRespondable.toResponseOrElse`) renders this as the// declared JSON body + status code without an explicit `catchCause` at// every error boundary.export class HttpResponseError extends Data.TaggedError("HttpResponseError")<{  readonly status: number;  readonly code: string;  readonly message: string;}> {  [HttpServerRespondable.symbol](): Effect.Effect<HttpServerResponse.HttpServerResponse> {    const self = this;    return Effect.gen(function* () {      if (self.status >= 500) yield* captureCauseEffect(self);      return HttpServerResponse.jsonUnsafe(        { error: self.message, code: self.code },        { status: self.status },      );    });  }} const unwrapCause = (error: unknown): unknown => {  if (!Cause.isCause(error)) return error;   const failure = Cause.findError(error);  if (Result.isSuccess(failure)) return failure.success;   const defect = Cause.findDefect(error);  if (Result.isSuccess(defect)) return defect.success;   return error;}; const isHttpResponseError = (error: unknown): error is HttpResponseError =>  Predicate.isTagged(error, "HttpResponseError"); const toHttpResponseError = (error: unknown): HttpResponseError => {  const unwrapped = unwrapCause(error);  return isHttpResponseError(unwrapped)    ? unwrapped    : new HttpResponseError({        status: 500,        code: "internal_server_error",        message: "Internal server error",      });}; export const isServerError = (error: unknown): boolean => toHttpResponseError(error).status >= 500; export const toErrorResponse = (error: unknown): Response => {  const mapped = toHttpResponseError(error);  if (mapped.status >= 500) captureCause(mapped);  return Response.json({ error: mapped.message, code: mapped.code }, { status: mapped.status });}; export const toErrorServerResponse = (error: unknown): HttpServerResponse.HttpServerResponse => {  const mapped = toHttpResponseError(error);  if (mapped.status >= 500) captureCause(mapped);  return HttpServerResponse.jsonUnsafe(    { error: mapped.message, code: mapped.code },    { status: mapped.status },  );}; export const toErrorServerResponseEffect = (  error: unknown,): Effect.Effect<HttpServerResponse.HttpServerResponse> =>  Effect.gen(function* () {    const mapped = toHttpResponseError(error);    if (mapped.status >= 500) yield* captureCauseEffect(mapped);    return HttpServerResponse.jsonUnsafe(      { error: mapped.message, code: mapped.code },      { status: mapped.status },    );  });