refactor: centralize typed GSDToolsError construction

This commit is contained in:
Tom Boucher
2026-05-04 20:03:56 -04:00
parent 7dcafbc211
commit 41683b2f53
4 changed files with 63 additions and 20 deletions

View File

@@ -0,0 +1,16 @@
import { describe, expect, it } from 'vitest';
import { GSDToolsError } from './gsd-tools-error.js';
describe('GSDToolsError constructors', () => {
it('builds timeout-classified errors', () => {
const err = GSDToolsError.timeout('timeout', 'state', ['load'], '', 1000);
expect(err.classification).toEqual({ kind: 'timeout', timeoutMs: 1000 });
expect(err.exitCode).toBeNull();
});
it('builds failure-classified errors', () => {
const err = GSDToolsError.failure('boom', 'state', ['load'], 1);
expect(err.classification).toEqual({ kind: 'failure' });
expect(err.exitCode).toBe(1);
});
});

View File

@@ -25,5 +25,41 @@ export class GSDToolsError extends Error {
this.classification = options?.classification;
}
static timeout(
message: string,
command: string,
args: string[],
stderr = '',
timeoutMs?: number,
options?: { cause?: unknown; exitCode?: number | null },
): GSDToolsError {
return new GSDToolsError(
message,
command,
args,
options?.exitCode ?? null,
stderr,
{ cause: options?.cause, classification: timeoutClassification(timeoutMs) },
);
}
static failure(
message: string,
command: string,
args: string[],
exitCode: number | null,
stderr = '',
options?: { cause?: unknown },
): GSDToolsError {
return new GSDToolsError(
message,
command,
args,
exitCode,
stderr,
{ cause: options?.cause, classification: failureClassification() },
);
}
public readonly classification?: GSDToolsErrorClassification;
}

View File

@@ -7,7 +7,7 @@ import { QuerySubprocessAdapter } from './query-subprocess-adapter.js';
import { QueryNativeDirectAdapter } from './query-native-direct-adapter.js';
import { QueryNativeHotpathAdapter } from './query-native-hotpath-adapter.js';
import { formatQueryRawOutput } from './query-raw-output-projection.js';
import { failureClassification, GSDToolsError, timeoutClassification } from './gsd-tools-error.js';
import { GSDToolsError } from './gsd-tools-error.js';
export interface GSDToolsRuntime {
registry: ReturnType<typeof createRegistry>;
@@ -34,16 +34,16 @@ export function createGSDToolsRuntime(opts: {
timeoutMs: opts.timeoutMs,
workstream: opts.workstream,
createToolsError: (message, command, args, exitCode, stderr, classification) =>
new GSDToolsError(message, command, args, exitCode, stderr, {
classification: classification ?? failureClassification(),
}),
classification?.kind === 'timeout'
? GSDToolsError.timeout(message, command, args, stderr, classification.timeoutMs, { exitCode })
: GSDToolsError.failure(message, command, args, exitCode, stderr),
});
const nativeDirectAdapter = new QueryNativeDirectAdapter({
timeoutMs: opts.timeoutMs,
dispatch: (registryCommand, registryArgs) => registry.dispatch(registryCommand, registryArgs, opts.projectDir),
createTimeoutError: (message, command, args) =>
new GSDToolsError(message, command, args, null, '', { classification: timeoutClassification(opts.timeoutMs) }),
GSDToolsError.timeout(message, command, args, '', opts.timeoutMs),
});
const transport = new GSDTransport(registry, {

View File

@@ -1,5 +1,5 @@
import { GSDError, exitCodeFor } from './errors.js';
import { failureClassification, GSDToolsError, timeoutClassification } from './gsd-tools-error.js';
import { GSDToolsError } from './gsd-tools-error.js';
import { errorMessage, toFailureSignal } from './query-failure-classification.js';
/**
@@ -7,7 +7,7 @@ import { errorMessage, toFailureSignal } from './query-failure-classification.js
*/
export function toGSDToolsError(command: string, args: string[], err: unknown): GSDToolsError {
if (err instanceof GSDError) {
return new GSDToolsError(
return GSDToolsError.failure(
err.message,
command,
args,
@@ -19,18 +19,9 @@ export function toGSDToolsError(command: string, args: string[], err: unknown):
const msg = errorMessage(err);
const signal = toFailureSignal(err);
const classification = signal.kind === 'timeout'
? timeoutClassification(signal.timeoutMs)
: failureClassification();
if (signal.kind === 'timeout') {
return GSDToolsError.timeout(msg, command, args, '', signal.timeoutMs, err instanceof Error ? { cause: err } : undefined);
}
return new GSDToolsError(
msg,
command,
args,
1,
'',
err instanceof Error
? { cause: err, classification }
: { classification },
);
return GSDToolsError.failure(msg, command, args, 1, '', err instanceof Error ? { cause: err } : undefined);
}