From 41683b2f53682b8f23a9d93238599ebd65a18142 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Mon, 4 May 2026 20:03:56 -0400 Subject: [PATCH] refactor: centralize typed GSDToolsError construction --- sdk/src/gsd-tools-error.test.ts | 16 +++++++++++++ sdk/src/gsd-tools-error.ts | 36 +++++++++++++++++++++++++++++ sdk/src/query-gsd-tools-runtime.ts | 10 ++++---- sdk/src/query-tools-error-mapper.ts | 21 +++++------------ 4 files changed, 63 insertions(+), 20 deletions(-) create mode 100644 sdk/src/gsd-tools-error.test.ts diff --git a/sdk/src/gsd-tools-error.test.ts b/sdk/src/gsd-tools-error.test.ts new file mode 100644 index 000000000..704de685a --- /dev/null +++ b/sdk/src/gsd-tools-error.test.ts @@ -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); + }); +}); diff --git a/sdk/src/gsd-tools-error.ts b/sdk/src/gsd-tools-error.ts index bc5d79880..1d54e26d3 100644 --- a/sdk/src/gsd-tools-error.ts +++ b/sdk/src/gsd-tools-error.ts @@ -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; } diff --git a/sdk/src/query-gsd-tools-runtime.ts b/sdk/src/query-gsd-tools-runtime.ts index 3a9f72d92..07a9e9063 100644 --- a/sdk/src/query-gsd-tools-runtime.ts +++ b/sdk/src/query-gsd-tools-runtime.ts @@ -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; @@ -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, { diff --git a/sdk/src/query-tools-error-mapper.ts b/sdk/src/query-tools-error-mapper.ts index ee58ec0a8..06f54b6eb 100644 --- a/sdk/src/query-tools-error-mapper.ts +++ b/sdk/src/query-tools-error-mapper.ts @@ -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); }