From f6c6e68c41bd72ec9fd667686518a3bd3aa20fe6 Mon Sep 17 00:00:00 2001 From: Jack Chuma Date: Fri, 30 Oct 2026 09:45:07 -0400 Subject: [PATCH] refactor(parser): flatten parse result and drop dead config guards J-Engine-Session: unknown --- .../upgrade-config/__tests__/route.test.ts | 4 +- src/app/api/upgrade-config/route.ts | 2 +- src/lib/__tests__/parser.test.ts | 47 +++++++++++++++++++ src/lib/parser.ts | 45 +++++------------- src/lib/types/index.ts | 1 - src/lib/types/parser.ts | 21 --------- src/lib/validation-service.ts | 8 +--- 7 files changed, 64 insertions(+), 64 deletions(-) create mode 100644 src/lib/__tests__/parser.test.ts delete mode 100644 src/lib/types/parser.ts diff --git a/src/app/api/upgrade-config/__tests__/route.test.ts b/src/app/api/upgrade-config/__tests__/route.test.ts index 5a3aaf9..fcf3ff6 100644 --- a/src/app/api/upgrade-config/__tests__/route.test.ts +++ b/src/app/api/upgrade-config/__tests__/route.test.ts @@ -43,7 +43,7 @@ describe('GET /api/upgrade-config', () => { mockReaddir.mockResolvedValue(['config1.json']); mockReadFile.mockResolvedValue('{}'); mockParseFromString.mockReturnValue({ - result: { success: true }, + success: true, config: { ledgerId: 1 }, }); }); @@ -107,7 +107,7 @@ describe('GET /api/upgrade-config', () => { }); it('returns the default config option when parsing fails', async () => { - mockParseFromString.mockReturnValue({ result: { success: false } }); + mockParseFromString.mockReturnValue({ success: false }); const res = await GET(createRequest({ network: 'mainnet', upgradeId: 'test' })); expect(res.status).toBe(200); const body = await res.json(); diff --git a/src/app/api/upgrade-config/route.ts b/src/app/api/upgrade-config/route.ts index ebd72cd..d9bb44e 100644 --- a/src/app/api/upgrade-config/route.ts +++ b/src/app/api/upgrade-config/route.ts @@ -69,7 +69,7 @@ export async function GET(req: NextRequest) { const configContent = await fs.readFile(filePath, 'utf-8'); const parsedConfig = parseFromString(configContent); - if (parsedConfig.result.success && 'config' in parsedConfig) { + if (parsedConfig.success) { ledgerId = parsedConfig.config.ledgerId; } else { console.warn(`Failed to parse ${configFile}, using default ledgerId: 0`); diff --git a/src/lib/__tests__/parser.test.ts b/src/lib/__tests__/parser.test.ts new file mode 100644 index 0000000..a3ac8cf --- /dev/null +++ b/src/lib/__tests__/parser.test.ts @@ -0,0 +1,47 @@ +import { z } from 'zod'; +import { getValidationSummary, parseFromString } from '../parser'; + +const config = { + cmd: 'forge script Test', + ledgerId: 0, + rpcUrl: 'http://localhost:8545', + expectedDomainAndMessageHashes: { + address: '0x0000000000000000000000000000000000000001', + domainHash: '0x' + 'a'.repeat(64), + messageHash: '0x' + 'b'.repeat(64), + }, + stateOverrides: [], + stateChanges: [], +}; + +it('parses valid config JSON', () => { + expect(parseFromString(JSON.stringify(config))).toEqual({ success: true, config }); +}); + +it('returns schema issues for invalid config JSON', () => { + const result = parseFromString(JSON.stringify({ ...config, ledgerId: -1 })); + expect(result.success).toBe(false); + if (result.success) throw new Error('Expected parsing to fail'); + expect(result.zodError.issues).toEqual([ + expect.objectContaining({ path: ['ledgerId'], code: 'too_small' }), + ]); +}); + +it('returns one invalid JSON issue for malformed JSON', () => { + const result = parseFromString('{'); + expect(result.success).toBe(false); + if (result.success) throw new Error('Expected parsing to fail'); + expect(result.zodError.issues).toEqual([ + { code: 'custom', path: [], message: expect.stringMatching(/^Invalid JSON:/) }, + ]); +}); + +it('formats the exact failure summary', () => { + const failure = { + success: false as const, + zodError: new z.ZodError([{ code: 'custom', path: ['task', 'name'], message: 'Required' }]), + }; + expect(getValidationSummary(failure)).toBe( + '❌ Configuration has errors\n\nErrors (1):\n • task.name: Required' + ); +}); diff --git a/src/lib/parser.ts b/src/lib/parser.ts index 7929bc1..3908de8 100644 --- a/src/lib/parser.ts +++ b/src/lib/parser.ts @@ -1,45 +1,24 @@ import { z } from 'zod'; import { TaskConfigSchema } from './config-schemas'; -import { ParsedConfig, ParseResult } from './types/index'; +import type { TaskConfig } from './types/index'; -function parseConfig(jsonData: unknown): ParsedConfig { - const parsed = TaskConfigSchema.safeParse(jsonData); +type ParsingOutcome = + | { success: true; config: TaskConfig } + | { success: false; zodError: z.ZodError }; - if (parsed.success) { - return { - config: parsed.data, - result: { - success: true, - }, - }; - } - - return { - result: { - success: false, - zodError: parsed.error, - }, - }; -} - -export function parseFromString(jsonString: string): ParsedConfig { +export function parseFromString(jsonString: string): ParsingOutcome { try { - return parseConfig(JSON.parse(jsonString)); + const parsed = TaskConfigSchema.safeParse(JSON.parse(jsonString)); + if (parsed.success) { + return { success: true, config: parsed.data }; + } + return { success: false, zodError: parsed.error }; } catch (error) { - return { - result: { - success: false, - zodError: createJsonParseError(error), - }, - }; + return { success: false, zodError: createJsonParseError(error) }; } } -export function getValidationSummary(result: ParseResult): string { - if (result.success) { - return '✅ Configuration is valid'; - } - +export function getValidationSummary(result: Extract): string { const parts: string[] = [ '❌ Configuration has errors', `\nErrors (${result.zodError.issues.length}):`, diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts index 8d7bbff..d95ec77 100644 --- a/src/lib/types/index.ts +++ b/src/lib/types/index.ts @@ -1,5 +1,4 @@ export * from './validation-config'; -export * from './parser'; export * from './diff'; export * from './validation-data'; export * from './comparison'; diff --git a/src/lib/types/parser.ts b/src/lib/types/parser.ts deleted file mode 100644 index 0d612d2..0000000 --- a/src/lib/types/parser.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { ZodError } from 'zod'; - -import type { TaskConfig } from './validation-config'; - -type ParseSuccess = { - config: TaskConfig; - result: { - success: true; - zodError?: never; - }; -}; - -type ParseFailure = { - result: { - success: false; - zodError: ZodError; - }; -}; - -export type ParsedConfig = ParseSuccess | ParseFailure; -export type ParseResult = ParsedConfig['result']; diff --git a/src/lib/validation-service.ts b/src/lib/validation-service.ts index e1a2f2d..c0999ed 100644 --- a/src/lib/validation-service.ts +++ b/src/lib/validation-service.ts @@ -70,15 +70,11 @@ async function getConfigData(opts: ValidationServiceOpts): Promise<{ const parsedConfig = parseFromString(configContent); - if (!parsedConfig.result.success) { - console.error('❌ Failed to parse config:', getValidationSummary(parsedConfig.result)); + if (!parsedConfig.success) { + console.error('❌ Failed to parse config:', getValidationSummary(parsedConfig)); throw new Error('ValidationService::getConfigData: Failed to parse config file'); } - if (!('config' in parsedConfig)) { - throw new Error('ValidationService::getConfigData: Parsed config missing config data'); - } - console.log(`✅ Loaded config data from ${configFileName}`); return { cfg: parsedConfig.config,