Skip to content

Commit 5849293

Browse files
authored
refactor(errors): centralize custom errors (#1885)
1 parent 3798e2e commit 5849293

18 files changed

Lines changed: 96 additions & 86 deletions

File tree

src/core/project/manager.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import { afterEach, describe, expect, test } from "bun:test";
22
import { mkdtemp, readdir, rm } from "node:fs/promises";
33
import { join, relative } from "node:path";
44
import { tmpdir } from "node:os";
5-
import { FsProjectManager, NestedProjectError } from "./manager";
6-
import { ProjectFileExistsError } from "./tree";
5+
import { NestedProjectError, ProjectFileExistsError } from "../../errors";
6+
import { FsProjectManager } from "./manager";
77
import { PROJECT_TEMPLATES } from "../../handlers/project/types";
88
import { createSilentLogger } from "../../testing";
99

src/core/project/manager.tsx

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { existsSync } from "node:fs";
22
import { dirname, join } from "node:path";
3-
import { AgentCoreCLIError, ERROR_SOURCE } from "../../errors";
3+
import { NestedProjectError } from "../../errors";
44
import type {
55
CreateProjectInput,
66
ResolveProjectInput,
@@ -12,16 +12,6 @@ import { projectTree } from "./compose";
1212
import { defaultSource, type AssetSource } from "./source";
1313
import { writeTree } from "./tree";
1414

15-
/** Thrown when scaffolding would nest a new project inside an existing AgentCore project. */
16-
export class NestedProjectError extends AgentCoreCLIError {
17-
constructor(public readonly projectRoot: string) {
18-
super(
19-
`cannot create a project inside an existing AgentCore project (found ${join(projectRoot, "agentcore", "agentcore.json")})`,
20-
{ source: ERROR_SOURCE.USER, meta: { projectRoot } },
21-
);
22-
}
23-
}
24-
2515
/** Walks up from directory looking for the agentcore/agentcore.json project marker. */
2616
function enclosingProjectRoot(directory: string): string | undefined {
2717
for (let current = directory; ; current = dirname(current)) {

src/core/project/source.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { describe, expect, test } from "bun:test";
2-
import { EmbeddedAssetNotFoundError, EmbeddedAssetSource } from "./source";
2+
import { EmbeddedAssetNotFoundError } from "../../errors";
3+
import { EmbeddedAssetSource } from "./source";
34

45
describe("EmbeddedAssetSource", () => {
56
test("throws a modeled error when the asset is not embedded", () => {

src/core/project/source.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { existsSync } from "node:fs";
22
import { readFile, readdir } from "node:fs/promises";
33
import { dirname, join, relative, resolve } from "node:path";
44
import { fileURLToPath } from "node:url";
5-
import { AgentCoreCLIError } from "../../errors";
5+
import { EmbeddedAssetNotFoundError } from "../../errors";
66

77
/**
88
* Reads and lists asset files by path relative to the asset root.
@@ -21,13 +21,6 @@ const EMBEDDED_PREFIX = "agentcore-assets/src/assets/";
2121
// Embedded files carry a name property that Bun's types widen to Blob.
2222
type NamedBlob = Blob & { readonly name: string };
2323

24-
/** Thrown when an asset is missing from the compiled executable, indicating a packaging bug. */
25-
export class EmbeddedAssetNotFoundError extends AgentCoreCLIError {
26-
constructor(public readonly assetPath: string) {
27-
super(`Embedded asset not found: ${assetPath}`, { meta: { assetPath } });
28-
}
29-
}
30-
3124
/** Reads assets embedded in the compiled standalone executable. */
3225
export class EmbeddedAssetSource implements AssetSource {
3326
private blobs(): readonly NamedBlob[] {

src/core/project/tree.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { existsSync } from "node:fs";
22
import { mkdir } from "node:fs/promises";
33
import { join } from "node:path";
4-
import { AgentCoreCLIError, ERROR_SOURCE } from "../../errors";
4+
import { ProjectFileExistsError } from "../../errors";
55
import { atomicWrite } from "../../io";
66

77
/**
@@ -34,16 +34,6 @@ export const file = (name: string, bytes: () => Promise<string>): FileNode => ({
3434
bytes,
3535
});
3636

37-
/** Thrown when scaffolding would overwrite a file that already exists. */
38-
export class ProjectFileExistsError extends AgentCoreCLIError {
39-
constructor(public readonly path: string) {
40-
super(`Refusing to overwrite existing file: ${path}`, {
41-
source: ERROR_SOURCE.USER,
42-
meta: { path },
43-
});
44-
}
45-
}
46-
4737
/**
4838
* Writes a project tree to the destination with atomic file writes.
4939
* Refuses to overwrite an existing file so a re-run fails loudly instead of clobbering user work.

src/errors/errors.tsx

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { ServiceException } from "@smithy/core/client";
2+
import { join } from "node:path";
23
import { ERROR_SOURCE, type ErrorSource } from "./types";
34

45
export interface AgentCoreCLIErrorOptions extends ErrorOptions {
@@ -74,3 +75,70 @@ export class NotImplementedError extends AgentCoreCLIError {
7475
super(message ?? "not implemented yet", { ...options, source: ERROR_SOURCE.INTERNAL });
7576
}
7677
}
78+
79+
/** Error raised when detecting an invalid environment */
80+
export class InvalidEnvironmentError extends AgentCoreCLIError {
81+
constructor(message?: string, options?: Omit<AgentCoreCLIErrorOptions, "source">) {
82+
super(message, { ...options, source: ERROR_SOURCE.USER });
83+
}
84+
}
85+
86+
export class SourceResolutionError extends InputValidationError {
87+
constructor(message: string, options?: ErrorOptions) {
88+
super(message, options);
89+
this.name = "SourceResolutionError";
90+
}
91+
}
92+
93+
// TODO: attach telemetry metadata to this error class.
94+
export class DeserializationError extends Error {
95+
constructor(path: string, options?: { cause?: unknown }) {
96+
super(`Failed to deserialize JSON at "${path}"`, options);
97+
this.name = "DeserializationError";
98+
}
99+
}
100+
101+
/** Thrown when scaffolding would overwrite a file that already exists. */
102+
export class ProjectFileExistsError extends AgentCoreCLIError {
103+
constructor(public readonly path: string) {
104+
super(`Refusing to overwrite existing file: ${path}`, {
105+
source: ERROR_SOURCE.USER,
106+
meta: { path },
107+
});
108+
}
109+
}
110+
111+
/** Thrown when scaffolding would nest a new project inside an existing AgentCore project. */
112+
export class NestedProjectError extends AgentCoreCLIError {
113+
constructor(public readonly projectRoot: string) {
114+
super(
115+
`cannot create a project inside an existing AgentCore project (found ${join(projectRoot, "agentcore", "agentcore.json")})`,
116+
{ source: ERROR_SOURCE.USER, meta: { projectRoot } },
117+
);
118+
}
119+
}
120+
121+
/** Thrown when an asset is missing from the compiled executable, indicating a packaging bug. */
122+
export class EmbeddedAssetNotFoundError extends AgentCoreCLIError {
123+
constructor(public readonly assetPath: string) {
124+
super(`Embedded asset not found: ${assetPath}`, { meta: { assetPath } });
125+
}
126+
}
127+
128+
export class RuntimeInvokeInterruptedError extends AgentCoreCLIError {
129+
readonly reported: boolean;
130+
131+
constructor(cause?: unknown, reported = false) {
132+
super("The operation was aborted", { cause, exitCode: 130 });
133+
this.name = "AbortError";
134+
this.reported = reported;
135+
}
136+
}
137+
138+
export class RuntimeInvokeResponseError extends AgentCoreCLIError {
139+
readonly reported = true;
140+
141+
constructor(message: string, cause?: unknown) {
142+
super(message, { cause });
143+
}
144+
}

src/errors/index.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
export {
22
AgentCoreCLIError,
3+
DeserializationError,
4+
EmbeddedAssetNotFoundError,
35
InputValidationError,
6+
InvalidEnvironmentError,
7+
NestedProjectError,
48
NotImplementedError,
9+
ProjectFileExistsError,
10+
RuntimeInvokeInterruptedError,
11+
RuntimeInvokeResponseError,
12+
SourceResolutionError,
513
type AgentCoreCLIErrorOptions,
614
} from "./errors";
715
export { ERROR_SOURCE } from "./types";

src/handlers/runtime/invoke/errors.ts

Lines changed: 0 additions & 19 deletions
This file was deleted.

src/handlers/runtime/invoke/index.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import z from "zod";
2-
import { InputValidationError } from "../../../errors";
2+
import { InputValidationError, RuntimeInvokeInterruptedError } from "../../../errors";
33
import { createHandler, flag } from "../../../router";
44
import type { AppIO } from "../../../io";
55
import type { Core } from "../../types";
66
import { coreOptsFromCtx } from "../../utils";
77
import { JsonKey } from "../../keys";
88
import { ExitCode } from "../../../runnable";
9-
import { RuntimeInvokeInterruptedError } from "./errors";
109
import {
1110
normalizeRuntimeInvokeRequest,
1211
parseRuntimeInvokeHeaders,

src/handlers/runtime/invoke/request.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import { describe, expect, test } from "bun:test";
22
import { Readable } from "node:stream";
33
import type { GetAgentRuntimeResponse } from "@aws-sdk/client-bedrock-agentcore-control";
4-
import { InputValidationError } from "../../../errors";
5-
import { SourceResolutionError } from "../../../io";
4+
import { InputValidationError, SourceResolutionError } from "../../../errors";
65
import {
76
normalizeRuntimeInvokeRequest,
87
parseRuntimeInvokeHeaders,

0 commit comments

Comments
 (0)