From 7eee54842ded3c44f8511d05f6f36ffe542e02f6 Mon Sep 17 00:00:00 2001 From: Thomas Kosiewski Date: Mon, 11 May 2026 12:04:44 +0200 Subject: [PATCH] refactor: share result validation parser Change-Id: I07e8948ddfd483949ca05ced26639a902eedf9b1 Signed-off-by: Thomas Kosiewski --- src/protocol/validation.ts | 22 ++++++++++++ src/screenshot/capture.ts | 12 ++----- src/snapshot/capture.ts | 11 ++---- test/unit/protocol/validation.test.ts | 49 +++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 19 deletions(-) create mode 100644 src/protocol/validation.ts create mode 100644 test/unit/protocol/validation.test.ts diff --git a/src/protocol/validation.ts b/src/protocol/validation.ts new file mode 100644 index 0000000..11beb2a --- /dev/null +++ b/src/protocol/validation.ts @@ -0,0 +1,22 @@ +import type { z } from 'zod'; + +import type { ProtocolErrorCode } from './errors.js'; + +import { ERROR_CODES, makeCliError } from './errors.js'; + +export function parseValidatedResult( + schema: TSchema, + rawValue: unknown, + message: string, + errorCode: ProtocolErrorCode = ERROR_CODES.PROTOCOL_ERROR, +): z.infer { + const parsedResult = schema.safeParse(rawValue); + if (!parsedResult.success) { + throw makeCliError(errorCode, { + message, + details: { issues: parsedResult.error.issues }, + }); + } + + return parsedResult.data; +} diff --git a/src/screenshot/capture.ts b/src/screenshot/capture.ts index 84d3282..3f30f68 100644 --- a/src/screenshot/capture.ts +++ b/src/screenshot/capture.ts @@ -5,8 +5,8 @@ import { ulid } from 'ulid'; import type { ScreenshotResult } from '../protocol/messages.js'; import type { RendererBackend } from '../renderer/backend.js'; -import { ERROR_CODES, makeCliError } from '../protocol/errors.js'; import { ScreenshotResultSchema } from '../protocol/schemas.js'; +import { parseValidatedResult } from '../protocol/validation.js'; import { appendArtifact, createArtifactEntry, @@ -46,15 +46,7 @@ export function parseScreenshotResult( rawResult: unknown, message = 'Unexpected response from host', ): ScreenshotResult { - const parsedResult = ScreenshotResultSchema.safeParse(rawResult); - if (!parsedResult.success) { - throw makeCliError(ERROR_CODES.PROTOCOL_ERROR, { - message, - details: { issues: parsedResult.error.issues }, - }); - } - - return parsedResult.data; + return parseValidatedResult(ScreenshotResultSchema, rawResult, message); } export async function captureScreenshotResult( diff --git a/src/snapshot/capture.ts b/src/snapshot/capture.ts index 1f87871..3c82efa 100644 --- a/src/snapshot/capture.ts +++ b/src/snapshot/capture.ts @@ -3,6 +3,7 @@ import type { SemanticSnapshot } from '../renderer/types.js'; import { ERROR_CODES, makeCliError } from '../protocol/errors.js'; import { SnapshotResultSchema } from '../protocol/schemas.js'; +import { parseValidatedResult } from '../protocol/validation.js'; import { appendArtifact, createArtifactEntry, @@ -21,15 +22,7 @@ export function parseSnapshotResult( rawResult: unknown, message = 'Unexpected response from host', ): SnapshotResult { - const parsedResult = SnapshotResultSchema.safeParse(rawResult); - if (!parsedResult.success) { - throw makeCliError(ERROR_CODES.PROTOCOL_ERROR, { - message, - details: { issues: parsedResult.error.issues }, - }); - } - - return parsedResult.data; + return parseValidatedResult(SnapshotResultSchema, rawResult, message); } export function createSnapshotResult( diff --git a/test/unit/protocol/validation.test.ts b/test/unit/protocol/validation.test.ts new file mode 100644 index 0000000..f6a89b8 --- /dev/null +++ b/test/unit/protocol/validation.test.ts @@ -0,0 +1,49 @@ +import { z } from 'zod'; + +import { describe, expect, it } from 'vitest'; + +import { ERROR_CODES } from '../../../src/protocol/errors.js'; +import { parseValidatedResult } from '../../../src/protocol/validation.js'; + +describe('parseValidatedResult', () => { + const ResultSchema = z + .object({ + id: z.string().min(1), + count: z.number().int().positive(), + }) + .strict(); + + it('returns parsed data on success', () => { + expect( + parseValidatedResult(ResultSchema, { id: 'result-01', count: 2 }, 'bad'), + ).toEqual({ id: 'result-01', count: 2 }); + }); + + it('throws PROTOCOL_ERROR with zod issues by default', () => { + expect(() => + parseValidatedResult(ResultSchema, { id: '', count: 0 }, 'invalid'), + ).toThrow( + expect.objectContaining({ + code: ERROR_CODES.PROTOCOL_ERROR, + message: 'invalid', + details: { issues: expect.any(Array) as unknown }, + }) as object, + ); + }); + + it('uses a supplied error code', () => { + expect(() => + parseValidatedResult( + ResultSchema, + {}, + 'invalid input', + ERROR_CODES.INVALID_INPUT, + ), + ).toThrow( + expect.objectContaining({ + code: ERROR_CODES.INVALID_INPUT, + message: 'invalid input', + }) as object, + ); + }); +});