From 1bde17877d72b37696f7fb29c40acc62d50e51aa Mon Sep 17 00:00:00 2001 From: Matthew Douglass <5410142+mdouglass@users.noreply.github.com> Date: Fri, 27 Jun 2025 11:13:44 -0700 Subject: [PATCH] Map AggegateError and Error.cause to ApplicationFailureInfo - ensureApplicationFailure was not providing Error.cause to ApplicationFailure.create which meant that cause information was not being built - AggegateError.errors wasn't being handled in either ensureApplicationFailure or the DefaultFailureConverter. It is not possible to map that directly to an IFailure, so that information is added to then ApplicationFailureInfo.details field --- .../common/src/converter/failure-converter.ts | 11 ++++++++ packages/common/src/failure.ts | 25 ++++++++++++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/common/src/converter/failure-converter.ts b/packages/common/src/converter/failure-converter.ts index fe1837054..55e1320c8 100644 --- a/packages/common/src/converter/failure-converter.ts +++ b/packages/common/src/converter/failure-converter.ts @@ -329,6 +329,17 @@ export class DefaultFailureConverter implements FailureConverter { message: String(err.message) ?? '', stackTrace: cutoffStackTrace(err.stack), cause: this.optionalErrorToOptionalFailure((err as any).cause, payloadConverter), + applicationFailureInfo: + 'errors' in err && Array.isArray(err.errors) + ? { + details: { + payloads: toPayloads( + payloadConverter, + err.errors.map((errInner) => this.optionalErrorToOptionalFailure(errInner, payloadConverter)) + ), + }, + } + : undefined, }; } diff --git a/packages/common/src/failure.ts b/packages/common/src/failure.ts index f5f0e48c2..1491c3a53 100644 --- a/packages/common/src/failure.ts +++ b/packages/common/src/failure.ts @@ -1,4 +1,5 @@ import type { temporal } from '@temporalio/proto'; +import { cutoffStackTrace } from '../lib'; import { errorMessage, isRecord, SymbolBasedInstanceOfError } from './type-helpers'; import { Duration } from './time'; import { makeProtoEnumConverters } from './internal-workflow'; @@ -412,9 +413,31 @@ export function ensureApplicationFailure(error: unknown): ApplicationFailure { return error; } + function toDetails(error: unknown): Record | undefined { + if (isRecord(error)) { + return { + message: String(error.message), + type: error.constructor?.name ?? error.name, + stack: cutoffStackTrace(String(error.stack)), + cause: toDetails(error.cause), + details: Array.isArray(error.errors) ? error.errors.map(toDetails) : undefined, + }; + } else if (error != null) { + return { message: String(error) }; + } else { + return undefined; + } + } + const message = (isRecord(error) && String(error.message)) || String(error); const type = (isRecord(error) && (error.constructor?.name ?? error.name)) || undefined; - const failure = ApplicationFailure.create({ message, type, nonRetryable: false }); + const failure = ApplicationFailure.create({ + message, + type, + cause: isRecord(error) && error.cause instanceof Error ? error.cause : undefined, + details: isRecord(error) && Array.isArray(error.errors) ? error.errors.map(toDetails) : undefined, + nonRetryable: false, + }); failure.stack = (isRecord(error) && String(error.stack)) || ''; return failure; }