import * as Cause from "effect/Cause" import * as Effect from "effect/Effect" import type * as Exit from "effect/Exit" import * as FiberId from "effect/FiberId" import { globalValue } from "effect/GlobalValue" import * as Option from "effect/Option" import * as Predicate from "effect/Predicate" import type * as Error from "../HttpServerError.js" import * as Respondable from "../HttpServerRespondable.js" import type { HttpServerResponse } from "../HttpServerResponse.js" import * as internalServerResponse from "./httpServerResponse.js" /** @internal */ export const TypeId: Error.TypeId = Symbol.for( "@effect/platform/HttpServerError" ) as Error.TypeId /** @internal */ export const isServerError = (u: unknown): u is Error.HttpServerError => Predicate.hasProperty(u, TypeId) /** @internal */ export const clientAbortFiberId = globalValue( "@effect/platform/HttpServerError/clientAbortFiberId", () => FiberId.runtime(-499, 0) ) /** @internal */ export const causeResponse = ( cause: Cause.Cause ): Effect.Effect]> => { let effect: Effect.Effect = Effect.succeed(internalServerError) let stripped: Cause.Cause = Cause.empty as Cause.Cause let isClientInterrupt = false let hasResponse = false Cause.reduce(cause, void 0 as void, (_, current) => { const withoutInterrupt = Cause.isInterruptType(stripped) ? Cause.empty : stripped switch (current._tag) { case "Fail": { effect = Respondable.toResponseOrElse(current.error, internalServerError) stripped = combineCauses(withoutInterrupt, current) break } case "Die": { const isResponse = internalServerResponse.isServerResponse(current.defect) effect = Respondable.toResponseOrElseDefect(current.defect, internalServerError) stripped = isResponse ? withoutInterrupt : combineCauses(withoutInterrupt, current) hasResponse = hasResponse || isResponse break } case "Interrupt": { isClientInterrupt = isClientInterrupt || current.fiberId === clientAbortFiberId if (Cause.isEmptyType(stripped) && !hasResponse) { stripped = current } break } } return Option.none() }) const responseEffect = !hasResponse && Cause.isInterruptType(stripped) ? Effect.succeed(isClientInterrupt ? clientAbortError : serverAbortError) : effect const strippedCause: Cause.Cause = !hasResponse && Cause.isInterruptType(stripped) && isClientInterrupt ? Cause.interrupt(clientAbortFiberId) as Cause.Cause : stripped return Effect.map(responseEffect, (response) => { if (Cause.isEmptyType(strippedCause)) { return [response, Cause.empty] as const } return [response, Cause.sequential(strippedCause, Cause.die(response))] as const }) } const combineCauses = (left: Cause.Cause, right: Cause.Cause): Cause.Cause => { if (Cause.isEmptyType(left)) { return right } else if (Cause.isEmptyType(right)) { return left } return Cause.sequential(left, right) } /** @internal */ export const causeResponseStripped = ( cause: Cause.Cause ): readonly [response: HttpServerResponse, cause: Option.Option>] => { let response: HttpServerResponse | undefined const stripped = Cause.stripSomeDefects(cause, (defect) => { if (internalServerResponse.isServerResponse(defect)) { response = defect return Option.some(Cause.empty) } return Option.none() }) return [response ?? internalServerError, stripped] } const internalServerError = internalServerResponse.empty({ status: 500 }) const clientAbortError = internalServerResponse.empty({ status: 499 }) const serverAbortError = internalServerResponse.empty({ status: 503 }) /** @internal */ export const exitResponse = (exit: Exit.Exit): HttpServerResponse => { if (exit._tag === "Success") { return exit.value } return causeResponseStripped(exit.cause)[0] }