import * as Api from "common/Api"; import * as Cbor from "common/Cbor"; import * as Client from "common/Client"; import { Cause, Effect, Either, HashMap, Inspectable, Option, pipe } from "effect"; /* NOTE We shouldn't need to extract this to a separate type, but if we don't do * it the TypeScript parser in VS Code sort of blows up and the syntax colors * turn wrong. */ type Return> = Effect.Effect>>; const catchToResponse = Effect.catchAll((error: unknown) => pipe( error, Inspectable.toJSON, Cbor.encode, Effect.map(Client.cborBody), Effect.matchEffect({ onSuccess: ({ body, headers }) => Effect.fail(new Response(body, { status: 500, headers })), onFailure: () => Effect.fail(new Response(null, { status: 500 })), }), )); export const implement = < const Record extends { readonly [_: string]: Api.ApiAny }, const Impl extends Api.ApiBundleImpl, >( bundle: Api.ApiBundle, impl: Impl, ): (key: string, request: Request) => Return => { return (key, requestObject) => { /* Force both return types to be `Response`. We can use the error route * for it's short-circuit capabilities. */ const effect: Effect.Effect = Effect.gen(function* () { const maybeApi = HashMap.get(bundle.map, key); if (Option.isNone(maybeApi)) { return RESPONSE_API_NOT_FOUND; } const { value: api } = maybeApi; const fn = impl[key]; if (fn === undefined) { return RESPONSE_API_NOT_IMPLEMENTED; } const request = yield* pipe( requestObject, Client.decodeBody(api.request), catchToResponse, ); const result = yield* pipe( request, fn, Effect.sandbox, Effect.either, ); const { status, response } = pipe( result, Either.match({ onLeft: (cause) => pipe( cause, Cause.failureOrCause, Either.match({ onLeft: (error) => ({ status: 400, response: pipe( error, Client.encodeBody(api.error), catchToResponse, ), }), onRight: (die) => ({ status: 500, response: pipe( die, Inspectable.toJSON, Cbor.encode, Effect.map(Client.cborBody), catchToResponse, ), }), }), ), onRight: (response) => ({ status: 200, response: pipe( response, Client.encodeBody(api.response), catchToResponse, ), }), }), ); const { body, headers } = yield* response; return new Response(body, { status, headers }); }); return Effect.catchAll(effect, Effect.succeed); }; }; const RESPONSE_API_NOT_FOUND = new Response(null, { status: 404 }); const RESPONSE_API_NOT_IMPLEMENTED = new Response(null, { status: 501 });