Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 19 additions & 10 deletions src/commands/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ const BRACKET_CONTENTS_REGEX = /\[([^[\]]*)\]/g;
export function parseFieldKey(key: string): string[] {
const match = key.match(FIELD_KEY_REGEX);
if (!match?.[1]) {
throw new Error(`Invalid field key format: ${key}`);
throw new ValidationError(`Invalid field key format: ${key}`, "field");
}

const baseKey = match[1];
Expand All @@ -190,14 +190,18 @@ function validatePathSegments(path: string[]): void {

// Check for prototype pollution
if (DANGEROUS_KEYS.has(segment)) {
throw new Error(`Invalid field key: "${segment}" is not allowed`);
throw new ValidationError(
`Invalid field key: "${segment}" is not allowed`,
"field"
);
}

// Empty brackets ("") are only valid at the end of the path (array push syntax)
// Reject patterns like a[][b] which would silently lose data
if (segment === "" && i < path.length - 1) {
throw new Error(
"Invalid field key: empty brackets [] can only appear at the end of a key"
throw new ValidationError(
"Invalid field key: empty brackets [] can only appear at the end of a key",
"field"
);
}
}
Expand Down Expand Up @@ -249,14 +253,16 @@ function validateTypeCompatibility(
const pathStr = formatPathForError(path, index);

if (expectsArray && !Array.isArray(existing)) {
throw new Error(
`expected array type under "${pathStr}", got ${getTypeName(existing)}`
throw new ValidationError(
`expected array type under "${pathStr}", got ${getTypeName(existing)}`,
"field"
);
}

if (!(expectsArray || isTraversableObject(existing))) {
throw new Error(
`expected map type under "${pathStr}", got ${getTypeName(existing)}`
throw new ValidationError(
`expected map type under "${pathStr}", got ${getTypeName(existing)}`,
"field"
);
}
}
Expand Down Expand Up @@ -628,7 +634,10 @@ export function parseHeaders(headers: string[]): Record<string, string> {
for (const header of headers) {
const colonIndex = header.indexOf(":");
if (colonIndex === -1) {
throw new Error(`Invalid header format: ${header}. Expected Key: Value`);
throw new ValidationError(
`Invalid header format: ${header}. Expected Key: Value`,
"header"
);
}

const key = header.substring(0, colonIndex).trim();
Expand Down Expand Up @@ -821,7 +830,7 @@ export async function buildBodyFromInput(
} else {
const file = Bun.file(inputPath);
if (!(await file.exists())) {
throw new Error(`File not found: ${inputPath}`);
throw new ValidationError(`File not found: ${inputPath}`, "input");
}
content = await file.text();
}
Expand Down
4 changes: 2 additions & 2 deletions test/commands/api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1039,12 +1039,12 @@ describe("buildBodyFromInput", () => {
}
});

test("throws for non-existent file", async () => {
test("throws ValidationError for non-existent file", async () => {
const mockStdin = createMockStdin("");

await expect(
buildBodyFromInput("/nonexistent/path/file.json", mockStdin)
).rejects.toThrow(/File not found/);
).rejects.toBeInstanceOf(ValidationError);
});
});

Expand Down
Loading