61 lines · 2.0 KB · TypeScript
RawDownload
import { HttpStatus } from "../../core/errors";import type { LaunchFailure } from "./contracts"; /** * The only two renderings a LaunchFailure has. Every HTTP status and every SSE stage is * derived here from the tagged union — never from substring-matching an error message, * which is how the old path turned any message containing "cancelled" into a 400. */ const messageOf = (failure: LaunchFailure): string => {  switch (failure.kind) {    case "unsupported":      return `${failure.engine} cannot run here: ${failure.reason}`;    case "already-running":      return `${failure.name} is already running`;    case "no-capacity":      return `needs ${failure.need} device(s); only ${failure.free} free`;    case "install-failed":      return `${failure.engine} install failed: ${failure.detail}`;    case "spawn-failed":      return failure.detail;    case "exited-early":      return `process exited (code ${failure.exitCode ?? "?"}${        failure.signal ? `, signal ${failure.signal}` : ""      })\n${failure.logTail}`.trim();    case "unhealthy-timeout":      return `not healthy after ${Math.round(failure.waitedMs / 1000)}s\n${failure.logTail}`.trim();    case "cancelled":      return "launch cancelled";  }}; const statusOf = (failure: LaunchFailure): number => {  switch (failure.kind) {    case "unsupported":      return 422;    case "already-running":    case "no-capacity":      return 409;    case "cancelled":      return 400;    case "install-failed":    case "spawn-failed":    case "exited-early":    case "unhealthy-timeout":      return 503;  }}; export const toHttp = (failure: LaunchFailure): HttpStatus =>  new HttpStatus({ status: statusOf(failure), detail: messageOf(failure) }); export interface LaunchEvent {  readonly stage: "error" | "cancelled";  readonly message: string;} export const toEvent = (failure: LaunchFailure): LaunchEvent => ({  stage: failure.kind === "cancelled" ? "cancelled" : "error",  message: messageOf(failure),});