refactor: centralize query tools error construction in factory

This commit is contained in:
Tom Boucher
2026-05-04 20:09:20 -04:00
parent abf7779088
commit 9a469fa05c
4 changed files with 64 additions and 26 deletions

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 { GSDToolsError } from './gsd-tools-error.js';
import { failureToolsError, timeoutToolsError } from './query-tools-error-factory.js';
export interface GSDToolsRuntime {
registry: ReturnType<typeof createRegistry>;
@@ -34,18 +34,18 @@ export function createGSDToolsRuntime(opts: {
timeoutMs: opts.timeoutMs,
workstream: opts.workstream,
createTimeoutError: (message, command, args, stderr, timeoutMs) =>
GSDToolsError.timeout(message, command, args, stderr, timeoutMs),
timeoutToolsError(message, command, args, stderr, timeoutMs),
createFailureError: (message, command, args, exitCode, stderr) =>
GSDToolsError.failure(message, command, args, exitCode, stderr),
failureToolsError(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) =>
GSDToolsError.timeout(message, command, args, '', opts.timeoutMs),
timeoutToolsError(message, command, args, '', opts.timeoutMs),
createFailureError: (message, command, args, cause) =>
GSDToolsError.failure(message, command, args, 1, '', { cause }),
failureToolsError(message, command, args, 1, '', cause),
});
const transport = new GSDTransport(registry, {

View File

@@ -0,0 +1,25 @@
import { describe, expect, it } from 'vitest';
import { ErrorClassification, GSDError } from './errors.js';
import {
failureToolsError,
timeoutToolsError,
toToolsErrorFromUnknown,
} from './query-tools-error-factory.js';
describe('query tools error factory', () => {
it('builds timeout and failure tools errors', () => {
expect(timeoutToolsError('t', 'state', ['load'], '', 10).classification).toEqual({ kind: 'timeout', timeoutMs: 10 });
expect(failureToolsError('f', 'state', ['load'], 1).classification).toEqual({ kind: 'failure' });
});
it('maps GSDError to failure with semantic exit code', () => {
const err = toToolsErrorFromUnknown('state', ['load'], new GSDError('bad', ErrorClassification.Validation));
expect(err.exitCode).toBe(10);
expect(err.classification).toEqual({ kind: 'failure' });
});
it('maps timeout-like unknown errors to timeout classification', () => {
const err = toToolsErrorFromUnknown('state', ['load'], new Error('gsd-tools timed out after 50ms: state load'));
expect(err.classification).toEqual({ kind: 'timeout', timeoutMs: 50 });
});
});

View File

@@ -0,0 +1,31 @@
import { GSDError, exitCodeFor } from './errors.js';
import { GSDToolsError } from './gsd-tools-error.js';
import { errorMessage, toFailureSignal } from './query-failure-classification.js';
export function timeoutToolsError(message: string, command: string, args: string[], stderr = '', timeoutMs?: number): GSDToolsError {
return GSDToolsError.timeout(message, command, args, stderr, timeoutMs);
}
export function failureToolsError(
message: string,
command: string,
args: string[],
exitCode: number | null,
stderr = '',
cause?: unknown,
): GSDToolsError {
return GSDToolsError.failure(message, command, args, exitCode, stderr, cause === undefined ? undefined : { cause });
}
export function toToolsErrorFromUnknown(command: string, args: string[], err: unknown): GSDToolsError {
if (err instanceof GSDError) {
return failureToolsError(err.message, command, args, exitCodeFor(err.classification), '', err);
}
const msg = errorMessage(err);
const signal = toFailureSignal(err);
if (signal.kind === 'timeout') {
return timeoutToolsError(msg, command, args, '', signal.timeoutMs);
}
return failureToolsError(msg, command, args, 1, '', err instanceof Error ? err : undefined);
}

View File

@@ -1,27 +1,9 @@
import { GSDError, exitCodeFor } from './errors.js';
import { GSDToolsError } from './gsd-tools-error.js';
import { errorMessage, toFailureSignal } from './query-failure-classification.js';
import { toToolsErrorFromUnknown } from './query-tools-error-factory.js';
import type { GSDToolsError } from './gsd-tools-error.js';
/**
* Module owning projection of internal errors to GSDToolsError contract.
*/
export function toGSDToolsError(command: string, args: string[], err: unknown): GSDToolsError {
if (err instanceof GSDError) {
return GSDToolsError.failure(
err.message,
command,
args,
exitCodeFor(err.classification),
'',
{ cause: err },
);
}
const msg = errorMessage(err);
const signal = toFailureSignal(err);
if (signal.kind === 'timeout') {
return GSDToolsError.timeout(msg, command, args, '', signal.timeoutMs, err instanceof Error ? { cause: err } : undefined);
}
return GSDToolsError.failure(msg, command, args, 1, '', err instanceof Error ? { cause: err } : undefined);
return toToolsErrorFromUnknown(command, args, err);
}