Skip to content

Commit d90fa2b

Browse files
authored
refactor(runtime): propagate invoke SDK errors (#1937)
1 parent 4d509bb commit d90fa2b

3 files changed

Lines changed: 24 additions & 113 deletions

File tree

src/core/core.test.ts

Lines changed: 13 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -591,67 +591,21 @@ test("invokeRuntime aborts an established IAM response stream", async () => {
591591
expect(result).toBe("AbortError");
592592
});
593593

594-
test("IAM invoke failures preserve safe SDK diagnostics without arbitrary causes", async () => {
595-
const { logger, logs } = captureLogs();
596-
const core = coreWithDataSend(async () => {
597-
throw Object.assign(new Error("failed with secret request content"), {
598-
name: "AccessDeniedException",
599-
$metadata: {
600-
httpStatusCode: 403,
601-
requestId: "request-123",
602-
},
603-
});
604-
}, logger);
605-
606-
const caught = await core.runtime
607-
.invokeRuntime(
608-
{
609-
runtimeId: "runtime-123",
610-
accountId: "123456789012",
611-
qualifier: "DEFAULT",
612-
payload: new TextEncoder().encode("secret payload"),
613-
contentType: "application/json",
614-
applicationHeaders: [["X-Secret", "secret-header-value"]],
615-
},
616-
{ region: "us-east-1" },
617-
)
618-
.catch((caught: Error) => caught);
619-
const error = caught as Error;
620-
621-
expect(error.message).toBe(
622-
"Runtime invocation failed (AccessDeniedException, HTTP 403, request ID request-123)",
623-
);
624-
expect(error.message).not.toContain("secret request content");
625-
expectSafeDebugLog(
626-
logs,
627-
"Runtime invocation SDK request failed",
628-
{
629-
authMode: "IAM",
630-
runtimeId: "runtime-123",
631-
qualifier: "DEFAULT",
632-
region: "us-east-1",
633-
errorName: "AccessDeniedException",
634-
httpStatusCode: 403,
635-
requestId: "request-123",
594+
test("IAM invoke preserves modeled AWS service errors", async () => {
595+
const failure = new ValidationException({
596+
message: "Runtime session ID must contain at least 33 characters",
597+
reason: "FieldValidationFailed",
598+
$metadata: {
599+
httpStatusCode: 400,
600+
requestId: "request-456",
636601
},
637-
["secret request content", "secret payload", "secret-header-value"],
638-
);
639-
});
640-
641-
test("IAM invoke failures include messages from modeled AWS service errors", async () => {
602+
});
642603
const core = coreWithDataSend(async () => {
643-
throw new ValidationException({
644-
message: "Runtime session ID must contain at least 33 characters",
645-
reason: "FieldValidationFailed",
646-
$metadata: {
647-
httpStatusCode: 400,
648-
requestId: "request-456",
649-
},
650-
});
604+
throw failure;
651605
});
652606

653-
const caught = await core.runtime
654-
.invokeRuntime(
607+
await expect(
608+
core.runtime.invokeRuntime(
655609
{
656610
runtimeId: "runtime-123",
657611
accountId: "123456789012",
@@ -660,14 +614,8 @@ test("IAM invoke failures include messages from modeled AWS service errors", asy
660614
contentType: "application/json",
661615
},
662616
{ region: "us-east-1" },
663-
)
664-
.catch((caught: Error) => caught);
665-
const error = caught as Error;
666-
667-
expect(error.message).toBe(
668-
"Runtime invocation failed: Runtime session ID must contain at least 33 characters " +
669-
"(ValidationException, HTTP 400, request ID request-456)",
670-
);
617+
),
618+
).rejects.toBe(failure);
671619
});
672620

673621
test("CUSTOM_JWT invoke uses the generated endpoint and exact fetch request", async () => {

src/core/runtime.tsx

Lines changed: 9 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { randomUUID } from "node:crypto";
2-
import { ServiceException } from "@smithy/core/client";
32
import {
43
GetAgentRuntimeCommand,
54
GetAgentRuntimeEndpointCommand,
@@ -38,17 +37,17 @@ export class RuntimeClient implements CoreRuntimeClient {
3837
signal?: AbortSignal,
3938
): Promise<RuntimeInvokeResponse> {
4039
const { runtimeId, bearerToken } = request;
41-
const logger = this.logger.child({
42-
operation: "invokeRuntime",
43-
authMode: bearerToken === undefined ? "IAM" : "CUSTOM_JWT",
44-
runtimeId,
45-
qualifier: request.qualifier,
46-
region: options.region,
47-
});
4840
if (bearerToken !== undefined) {
41+
const logger = this.logger.child({
42+
operation: "invokeRuntime",
43+
authMode: "CUSTOM_JWT",
44+
runtimeId,
45+
qualifier: request.qualifier,
46+
region: options.region,
47+
});
4948
return this.invokeRuntimeWithCustomJwt(request, bearerToken, options, logger, signal);
5049
}
51-
return this.invokeRuntimeWithIam(request, options, logger, signal);
50+
return this.invokeRuntimeWithIam(request, options, signal);
5251
}
5352

5453
private async invokeRuntimeWithCustomJwt(
@@ -147,7 +146,6 @@ export class RuntimeClient implements CoreRuntimeClient {
147146
private async invokeRuntimeWithIam(
148147
request: RuntimeInvokeRequest,
149148
options: CoreOptions,
150-
logger: Logger,
151149
signal?: AbortSignal,
152150
): Promise<RuntimeInvokeResponse> {
153151
const { runtimeId, applicationHeaders, bearerToken: _bearerToken, ...input } = request;
@@ -169,41 +167,7 @@ export class RuntimeClient implements CoreRuntimeClient {
169167
});
170168
} catch (error) {
171169
if (signal?.aborted) throw signal.reason ?? error;
172-
const sdkError = error as {
173-
name?: unknown;
174-
$metadata?: { httpStatusCode?: unknown; requestId?: unknown };
175-
};
176-
const serviceMessage =
177-
ServiceException.isInstance(error) && error.message.trim()
178-
? error.message.trim()
179-
: undefined;
180-
const diagnostics: string[] = [];
181-
if (typeof sdkError?.name === "string" && sdkError.name !== "Error") {
182-
diagnostics.push(sdkError.name);
183-
}
184-
if (typeof sdkError?.$metadata?.httpStatusCode === "number") {
185-
diagnostics.push(`HTTP ${sdkError.$metadata.httpStatusCode}`);
186-
}
187-
if (typeof sdkError?.$metadata?.requestId === "string") {
188-
diagnostics.push(`request ID ${sdkError.$metadata.requestId}`);
189-
}
190-
logger
191-
.child({
192-
...(typeof sdkError?.name === "string" && { errorName: sdkError.name }),
193-
...(typeof sdkError?.$metadata?.httpStatusCode === "number" && {
194-
httpStatusCode: sdkError.$metadata.httpStatusCode,
195-
}),
196-
...(typeof sdkError?.$metadata?.requestId === "string" && {
197-
requestId: sdkError.$metadata.requestId,
198-
}),
199-
})
200-
.debug("Runtime invocation SDK request failed");
201-
throw new Error(
202-
`Runtime invocation failed${serviceMessage ? `: ${serviceMessage}` : ""}${
203-
diagnostics.length ? ` (${diagnostics.join(", ")})` : ""
204-
}`,
205-
{ cause: error },
206-
);
170+
throw error;
207171
}
208172

209173
const body = (response.response as AsyncIterable<Uint8Array> | undefined) ?? emptyBody();

src/handlers/runtime/invoke/invoke.screen.test.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -500,13 +500,13 @@ describe("Runtime invoke JSON console", () => {
500500
test("formats modeled AWS service errors with their diagnostics", async () => {
501501
const core = new TestCoreClient();
502502
core.runtime.setGetResponse({ agentRuntimeArn: RUNTIME_ARN } as GetAgentRuntimeResponse);
503-
const cause = new ValidationException({
503+
const error = new ValidationException({
504504
message: "Runtime session ID must contain at least 33 characters",
505505
reason: "FieldValidationFailed",
506506
$metadata: { httpStatusCode: 400, requestId: "request-456" },
507507
});
508508
core.runtime.invokeRuntime = async () => {
509-
throw new Error("flattened wrapper", { cause });
509+
throw error;
510510
};
511511
const screen = renderScreen(CONSOLE_PATH, { core });
512512

@@ -517,7 +517,6 @@ describe("Runtime invoke JSON console", () => {
517517
await waitForText(screen.lastFrame, "ValidationException · HTTP 400");
518518
expect(screen.lastFrame()).toContain("Runtime session ID must contain at least 33 characters");
519519
expect(screen.lastFrame()).toContain("Request ID: request-456");
520-
expect(screen.lastFrame()).not.toContain("flattened wrapper");
521520
});
522521

523522
test.each([

0 commit comments

Comments
 (0)