From 7d0bb2ec059e87f6c26f6a062398ef0f8ced1bcf Mon Sep 17 00:00:00 2001 From: Marcos Candeia Date: Sat, 19 Oct 2024 18:00:49 -0300 Subject: [PATCH] Add actor error handling Signed-off-by: Marcos Candeia --- src/actors/proxy.ts | 8 ++++++-- src/actors/proxyutil.ts | 24 ++++++++++++++++++++++-- src/actors/runtime.ts | 15 +++++++++++++++ 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/actors/proxy.ts b/src/actors/proxy.ts index 8703fb3..d2e8f6e 100644 --- a/src/actors/proxy.ts +++ b/src/actors/proxy.ts @@ -6,15 +6,19 @@ export interface ActorsServer { deploymentId?: string; } +export interface ActorsOptions { + server?: ActorsServer; + errorHandling?: Record Error>; +} /** * utilities to create and manage actors. */ export const actors = { proxy: ( actor: ActorConstructor | string, - server?: ActorsServer | undefined, + options?: ActorsOptions | undefined, ): { id: (id: string) => Promisify } => { - const factory = (id: string) => createHttpInvoker(id, server); + const factory = (id: string) => createHttpInvoker(id, options); return create(actor, factory); }, }; diff --git a/src/actors/proxyutil.ts b/src/actors/proxyutil.ts index 7c77c38..39a6ce4 100644 --- a/src/actors/proxyutil.ts +++ b/src/actors/proxyutil.ts @@ -1,4 +1,4 @@ -import type { ActorsServer } from "./proxy.ts"; +import type { ActorsOptions, ActorsServer } from "./proxy.ts"; import type { Actor, ActorConstructor } from "./runtime.ts"; import { EVENT_STREAM_RESPONSE_HEADER, readFromStream } from "./stream.ts"; import { @@ -9,6 +9,7 @@ import { export const ACTOR_ID_HEADER_NAME = "x-deno-isolate-instance-id"; export const ACTOR_ID_QS_NAME = "deno_isolate_instance_id"; +export const ACTOR_CONSTRUCTOR_NAME_HEADER = "x-error-constructor-name"; /** * Promise.prototype.then onfufilled callback type. */ @@ -177,8 +178,9 @@ export const createHttpInvoker = < TChannel extends DuplexChannel, >( actorId: string, - server?: ActorsServer, + options?: ActorsOptions, ): ActorInvoker => { + const server = options?.server; if (!server) { _server ??= initServer(); } @@ -214,6 +216,24 @@ export const createHttpInvoker = < }), }, ); + if (!resp.ok) { + const constructorName = resp.headers.get(ACTOR_CONSTRUCTOR_NAME_HEADER); + const ErrorConstructor = + options?.errorHandling?.[constructorName ?? "Error"] ?? Error; + const errorParameters = + resp.headers.get("content-type")?.includes("application/json") + ? await resp.json() + : { + message: await resp.text().catch(() => + `HTTP Error: ${resp.statusText}` + ), + }; + const deserializedError = Object.assign( + new ErrorConstructor(), + errorParameters, + ); + throw deserializedError; + } if ( resp.headers.get("content-type") === EVENT_STREAM_RESPONSE_HEADER diff --git a/src/actors/runtime.ts b/src/actors/runtime.ts index 87a9970..fb7c7de 100644 --- a/src/actors/runtime.ts +++ b/src/actors/runtime.ts @@ -1,6 +1,7 @@ import { type ServerSentEventMessage, ServerSentEventStream } from "@std/http"; import { ActorError } from "./errors.ts"; import { + ACTOR_CONSTRUCTOR_NAME_HEADER, ACTOR_ID_HEADER_NAME, ACTOR_ID_QS_NAME, type ActorInvoker, @@ -278,6 +279,20 @@ export class ActorRuntime { }[err.code] ?? 500, }); } + const constructorName = err?.constructor?.name; + if (constructorName) { + const serializedError = JSON.stringify( + err, + Object.getOwnPropertyNames(err), + ); + return new Response(serializedError, { + status: 500, + headers: { + [ACTOR_CONSTRUCTOR_NAME_HEADER]: constructorName, + "content-type": "application/json", + }, + }); + } throw err; } }