Files

52 lines
3.7 KiB
TypeScript

import { caseRunAccepted } from "./transport.ts";
export function createHarnessRLHttpApp(options: { service: ReturnType<typeof import("./service.ts")["createHarnessRLService"]> }) {
return {
async fetch(request: Request) {
const url = new URL(request.url);
try {
if (url.pathname === "/healthz" || url.pathname === "/health/live") return json(200, { ok: true, serviceId: "hwlab-harnessrl-api" });
if (url.pathname === "/readyz" || url.pathname === "/health/ready") return json(200, await options.service.health());
if (url.pathname === "/v1/caserun" && request.method === "GET") return json(200, { ok: true, contractVersion: "hwlab-caserun-web-v1", casesRoute: "/v1/caserun/cases", runsRoute: "/v1/caserun/runs", executionAuthority: "temporal", registryAuthority: "postgresql" });
if (url.pathname === "/v1/caserun/cases" && request.method === "GET") {
const cases = await options.service.listCases();
return json(200, { ok: true, contractVersion: "hwlab-caserun-cases-v1", count: cases.length, cases });
}
if (url.pathname === "/v1/caserun/runs" && request.method === "POST") {
const body = await bodyObject(request);
const record = await options.service.submit({ caseId: String(body.caseId ?? ""), runId: body.runId ? String(body.runId) : undefined, runtimeApiUrl: runtimeApiUrl(request) });
return json(202, caseRunAccepted(record));
}
if (url.pathname === "/v1/caserun/runs" && request.method === "GET") {
return json(200, await options.service.listRuns({
status: url.searchParams.get("status") ?? undefined,
limit: url.searchParams.get("limit") ?? undefined,
cursor: url.searchParams.get("cursor") ?? undefined
}));
}
const match = /^\/v1\/caserun\/runs\/([^/]+)(?:\/(events|aggregate|cancel))?$/u.exec(url.pathname);
if (match) {
const runId = decodeURIComponent(match[1]);
const suffix = match[2] ?? "";
if (!suffix && request.method === "GET") return json(200, await options.service.getRun(runId));
if (suffix === "events" && request.method === "GET") return json(200, await options.service.events(runId));
if (suffix === "aggregate" && request.method === "GET") return json(200, await options.service.aggregate(runId));
if (suffix === "cancel" && request.method === "POST") return json(202, await options.service.cancel(runId));
return json(405, errorBody("method_not_allowed", suffix === "cancel" ? "POST required" : "GET required"));
}
return json(404, errorBody("caserun_route_not_found", "CaseRun REST route is not implemented"));
} catch (error: any) {
const code = error?.code ?? "harnessrl_error";
const status = code.includes("not_found") ? 404 : code.startsWith("invalid_") ? 400 : 500;
return json(status, errorBody(code, error?.message ?? String(error), error?.details));
}
},
close: () => options.service.close()
};
}
async function bodyObject(request: Request) { const body = await request.json().catch(() => null); if (!body || typeof body !== "object" || Array.isArray(body)) throw Object.assign(new Error("JSON object body is required"), { code: "invalid_json" }); return body as Record<string, unknown>; }
function runtimeApiUrl(request: Request) { const url = new URL(request.url); return `${url.protocol}//${url.host}`; }
function errorBody(code: string, message: string, details?: unknown) { return { ok: false, error: { code, message, details } }; }
function json(status: number, body: unknown) { return Response.json(body, { status, headers: { "cache-control": "no-store" } }); }