diff --git a/src/application/FunctionAnalysisEvidence.ts b/src/application/FunctionAnalysisEvidence.ts index 88269805fb..17154edfde 100644 --- a/src/application/FunctionAnalysisEvidence.ts +++ b/src/application/FunctionAnalysisEvidence.ts @@ -3,13 +3,12 @@ import type { ExecutionOptions, } from "./AnalysisProvider.js"; import { toolContract } from "../contracts/toolContracts.js"; -import { AnalysisInputError } from "../domain/analysisErrorCore.js"; import type { BinaryTarget } from "../domain/binaryTarget.js"; import { createEvidence } from "../domain/evidence.js"; import { parseFunctionDossier } from "../domain/hopperValues.js"; import { jsonObjectSchema, type JsonValue } from "../domain/jsonValue.js"; import { err, ok } from "../domain/result.js"; -import { projectInputIssues } from "../domain/inputIssueProjection.js"; +import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; /** Preserve the provider's observations when presenting a validated function dossier. */ export const executeFunctionAnalysisEvidence = async ( @@ -22,10 +21,11 @@ export const executeFunctionAnalysisEvidence = async ( toolContract("analyze_function").inputSchema.safeParse(parameters); if (!input.success) return err( - new AnalysisInputError( + analysisInputErrorFromIssues( "analyze_function", + input.error.issues, + parameters, { cause: input.error }, - projectInputIssues(input.error.issues, parameters), ), ); const arguments_ = jsonObjectSchema.parse(input.data); diff --git a/src/application/WebModuleTraceService.ts b/src/application/WebModuleTraceService.ts index 88e322f695..8561856164 100644 --- a/src/application/WebModuleTraceService.ts +++ b/src/application/WebModuleTraceService.ts @@ -9,7 +9,7 @@ import { } from "../domain/analysisErrorCore.js"; import { createEvidence, type Evidence } from "../domain/evidence.js"; import { jsonObjectSchema } from "../domain/jsonValue.js"; -import { projectInputIssues } from "../domain/inputIssueProjection.js"; +import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; import { err, ok, type Result } from "../domain/result.js"; import { collectWebModuleImports, @@ -78,11 +78,9 @@ export class WebModuleTraceService { const parsed = webModuleTraceInputSchema.safeParse(rawInput); if (!parsed.success) return err( - new AnalysisInputError( - OPERATION, - { cause: parsed.error }, - projectInputIssues(parsed.error.issues, rawInput), - ), + analysisInputErrorFromIssues(OPERATION, parsed.error.issues, rawInput, { + cause: parsed.error, + }), ); const input = parsed.data; for (const [path, value] of [ diff --git a/src/application/WebNetworkCaptureService.ts b/src/application/WebNetworkCaptureService.ts index ca86b72880..326016a269 100644 --- a/src/application/WebNetworkCaptureService.ts +++ b/src/application/WebNetworkCaptureService.ts @@ -11,7 +11,7 @@ import { AnalysisOutputError, } from "../domain/analysisErrorCore.js"; import { createEvidence, type Evidence } from "../domain/evidence.js"; -import { projectInputIssues } from "../domain/inputIssueProjection.js"; +import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; import { jsonObjectSchema } from "../domain/jsonValue.js"; import { err, ok, type Result } from "../domain/result.js"; import { @@ -50,11 +50,9 @@ export class WebNetworkCaptureService { const parsed = inspectWebNetworkCaptureInputSchema.safeParse(rawInput); if (!parsed.success) return failure( - new AnalysisInputError( - OPERATION, - { cause: parsed.error }, - projectInputIssues(parsed.error.issues, rawInput), - ), + analysisInputErrorFromIssues(OPERATION, parsed.error.issues, rawInput, { + cause: parsed.error, + }), ); const input = parsed.data; if (!isAbsolute(input.capture_path)) diff --git a/src/application/WebRuntimeService.ts b/src/application/WebRuntimeService.ts index 69f95f1924..da402ba49b 100644 --- a/src/application/WebRuntimeService.ts +++ b/src/application/WebRuntimeService.ts @@ -4,12 +4,11 @@ import type { WebRuntimePort } from "./WebRuntimePort.js"; import type { AnalysisError } from "../domain/analysisErrorBase.js"; import { AnalysisCancelledError, - AnalysisInputError, AnalysisOutputError, } from "../domain/analysisErrorCore.js"; import { createEvidence, type Evidence } from "../domain/evidence.js"; import { jsonObjectSchema, jsonValueSchema } from "../domain/jsonValue.js"; -import { projectInputIssues } from "../domain/inputIssueProjection.js"; +import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; import { err, ok, type Result } from "../domain/result.js"; import { observeWebExecutionInputSchema, @@ -137,10 +136,8 @@ const parseInput = ( return parsed.success ? ok(parsed.data) : err( - new AnalysisInputError( - operation, - { cause: parsed.error }, - projectInputIssues(parsed.error.issues, rawInput), - ), + analysisInputErrorFromIssues(operation, parsed.error.issues, rawInput, { + cause: parsed.error, + }), ); }; diff --git a/src/application/WebScriptExportService.ts b/src/application/WebScriptExportService.ts index c0bfa496b6..e8529ea181 100644 --- a/src/application/WebScriptExportService.ts +++ b/src/application/WebScriptExportService.ts @@ -14,7 +14,7 @@ import { } from "../domain/analysisErrorCore.js"; import type { AnalysisError } from "../domain/analysisErrorBase.js"; import { createEvidence, type Evidence } from "../domain/evidence.js"; -import { projectInputIssues } from "../domain/inputIssueProjection.js"; +import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; import { jsonValueSchema } from "../domain/jsonValue.js"; import { ProviderAdapterError } from "../domain/providerAdapterError.js"; import { err, ok, type Result } from "../domain/result.js"; @@ -38,11 +38,7 @@ export const exportWebScripts = async ( return input.success ? exportWebScriptsValidated(input.data, options) : err( - new AnalysisInputError( - OPERATION, - undefined, - projectInputIssues(input.error.issues, rawInput), - ), + analysisInputErrorFromIssues(OPERATION, input.error.issues, rawInput), ); }; diff --git a/src/application/WebSourceLocationService.ts b/src/application/WebSourceLocationService.ts index 2510b3c667..2b52556104 100644 --- a/src/application/WebSourceLocationService.ts +++ b/src/application/WebSourceLocationService.ts @@ -8,7 +8,7 @@ import { AnalysisOutputError, } from "../domain/analysisErrorCore.js"; import { createEvidence, type Evidence } from "../domain/evidence.js"; -import { projectInputIssues } from "../domain/inputIssueProjection.js"; +import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; import { jsonObjectSchema } from "../domain/jsonValue.js"; import { err, ok, type Result } from "../domain/result.js"; import { @@ -69,11 +69,9 @@ export class WebSourceLocationService { const parsed = webSourceLocationInputSchema.safeParse(rawInput); if (!parsed.success) return err( - new AnalysisInputError( - OPERATION, - { cause: parsed.error }, - projectInputIssues(parsed.error.issues, rawInput), - ), + analysisInputErrorFromIssues(OPERATION, parsed.error.issues, rawInput, { + cause: parsed.error, + }), ); const input = parsed.data; const admitted = admitContext(input); diff --git a/src/application/binaryDiagnostics/BinaryLayoutService.ts b/src/application/binaryDiagnostics/BinaryLayoutService.ts index 7fb1d308ea..11bfeb76e9 100644 --- a/src/application/binaryDiagnostics/BinaryLayoutService.ts +++ b/src/application/binaryDiagnostics/BinaryLayoutService.ts @@ -12,7 +12,7 @@ import { inspectBinaryLayoutInputSchema, binaryLayoutSchema, } from "../../domain/native/binaryLayout.js"; -import { projectInputIssues } from "../../domain/inputIssueProjection.js"; +import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js"; import { err, ok, type Result } from "../../domain/result.js"; const OPERATION = "inspect_binary_layout"; @@ -31,11 +31,9 @@ export class BinaryLayoutService { const input = inspectBinaryLayoutInputSchema.safeParse(rawInput); if (!input.success) return err( - new AnalysisInputError( - OPERATION, - { cause: input.error }, - projectInputIssues(input.error.issues, rawInput), - ), + analysisInputErrorFromIssues(OPERATION, input.error.issues, rawInput, { + cause: input.error, + }), ); if (!isAbsolute(input.data.path)) return err( diff --git a/src/application/binaryDiagnostics/RecordedCrashService.ts b/src/application/binaryDiagnostics/RecordedCrashService.ts index 69307498a6..cfc9278dc1 100644 --- a/src/application/binaryDiagnostics/RecordedCrashService.ts +++ b/src/application/binaryDiagnostics/RecordedCrashService.ts @@ -12,7 +12,7 @@ import { inspectRecordedCrashInputSchema, recordedCrashSchema, } from "../../domain/native/recordedCrash.js"; -import { projectInputIssues } from "../../domain/inputIssueProjection.js"; +import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js"; import { err, ok, type Result } from "../../domain/result.js"; const OPERATION = "inspect_recorded_crash"; @@ -31,11 +31,9 @@ export class RecordedCrashService { const input = inspectRecordedCrashInputSchema.safeParse(rawInput); if (!input.success) return err( - new AnalysisInputError( - OPERATION, - { cause: input.error }, - projectInputIssues(input.error.issues, rawInput), - ), + analysisInputErrorFromIssues(OPERATION, input.error.issues, rawInput, { + cause: input.error, + }), ); if (!isAbsolute(input.data.path)) return err( diff --git a/src/application/evm/EvmInterfaceService.ts b/src/application/evm/EvmInterfaceService.ts index 9769255c00..9295e86454 100644 --- a/src/application/evm/EvmInterfaceService.ts +++ b/src/application/evm/EvmInterfaceService.ts @@ -13,7 +13,7 @@ import { inspectEvmInterfaceInputSchema, evmInterfaceSchema, } from "../../domain/evm/evmInterface.js"; -import { projectInputIssues } from "../../domain/inputIssueProjection.js"; +import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js"; import { err, ok, type Result } from "../../domain/result.js"; const OPERATION = "inspect_evm_interface"; @@ -35,11 +35,9 @@ export class EvmInterfaceService { const input = inspectEvmInterfaceInputSchema.safeParse(rawInput); if (!input.success) return err( - new AnalysisInputError( - OPERATION, - { cause: input.error }, - projectInputIssues(input.error.issues, rawInput), - ), + analysisInputErrorFromIssues(OPERATION, input.error.issues, rawInput, { + cause: input.error, + }), ); if (!isAbsolute(input.data.path)) return err( diff --git a/src/application/javascript/JavaScriptApplicationService.ts b/src/application/javascript/JavaScriptApplicationService.ts index 76583e4760..7766653b94 100644 --- a/src/application/javascript/JavaScriptApplicationService.ts +++ b/src/application/javascript/JavaScriptApplicationService.ts @@ -5,14 +5,11 @@ import { analyzeJavaScriptApplicationInputSchema, javascriptApplicationAnalysisResultSchema, } from "../../domain/javascript/javascriptApplicationAnalysis.js"; -import { - AnalysisInputError, - AnalysisOutputError, -} from "../../domain/analysisErrorCore.js"; +import { AnalysisOutputError } from "../../domain/analysisErrorCore.js"; import { ArtifactOperationError } from "../../domain/artifactOperationError.js"; import { type AnalysisError } from "../../domain/analysisErrorBase.js"; import type { Evidence } from "../../domain/evidence.js"; -import { projectInputIssues } from "../../domain/inputIssueProjection.js"; +import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js"; import { err, ok, type Result } from "../../domain/result.js"; import { ProviderAdapterError } from "../../domain/providerAdapterError.js"; import type { ExecutionOptions } from "../AnalysisProvider.js"; @@ -30,11 +27,7 @@ export const analyzeJavaScriptApplication = async ( const parsed = analyzeJavaScriptApplicationInputSchema.safeParse(rawInput); if (!parsed.success) return err( - new AnalysisInputError( - OPERATION, - undefined, - projectInputIssues(parsed.error.issues, rawInput), - ), + analysisInputErrorFromIssues(OPERATION, parsed.error.issues, rawInput), ); return analyzeJavaScriptApplicationValidated(parsed.data, options); }; diff --git a/src/application/javascript/JavaScriptApplicationWorkflowService.ts b/src/application/javascript/JavaScriptApplicationWorkflowService.ts index e41ec5bfbd..a9c31ed12a 100644 --- a/src/application/javascript/JavaScriptApplicationWorkflowService.ts +++ b/src/application/javascript/JavaScriptApplicationWorkflowService.ts @@ -6,7 +6,7 @@ import { } from "../../domain/analysisErrorCore.js"; import { type AnalysisError } from "../../domain/analysisErrorBase.js"; import type { Evidence } from "../../domain/evidence.js"; -import { projectInputIssues } from "../../domain/inputIssueProjection.js"; +import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js"; import { compareJavaScriptApplicationVersions } from "../../domain/javascript/javascriptApplicationVersionComparison.js"; import { compareApplicationVersionsInputSchema } from "../../domain/javascript/javascriptApplicationVersionComparisonSchemas.js"; import { compareJavaScriptExportShapes } from "../../domain/javascript/javascriptExportShapeComparison.js"; @@ -35,11 +35,7 @@ export const traceApplicationFeatureEvidence = ( const parsed = traceApplicationFeatureInputSchema.safeParse(rawInput); if (!parsed.success) return err( - new AnalysisInputError( - operation, - undefined, - projectInputIssues(parsed.error.issues, rawInput), - ), + analysisInputErrorFromIssues(operation, parsed.error.issues, rawInput), ); return traceApplicationFeatureEvidenceValidated(parsed.data); }; @@ -85,11 +81,7 @@ export const compareApplicationVersionsEvidence = ( const parsed = compareApplicationVersionsInputSchema.safeParse(rawInput); if (!parsed.success) return err( - new AnalysisInputError( - operation, - undefined, - projectInputIssues(parsed.error.issues, rawInput), - ), + analysisInputErrorFromIssues(operation, parsed.error.issues, rawInput), ); return compareApplicationVersionsEvidenceValidated(parsed.data); }; @@ -180,11 +172,7 @@ export const compareJavaScriptExportShapesEvidence = ( const parsed = compareJavaScriptExportShapesInputSchema.safeParse(rawInput); if (!parsed.success) return err( - new AnalysisInputError( - operation, - undefined, - projectInputIssues(parsed.error.issues, rawInput), - ), + analysisInputErrorFromIssues(operation, parsed.error.issues, rawInput), ); return compareJavaScriptExportShapesEvidenceValidated(parsed.data); }; @@ -235,13 +223,7 @@ const workflowFailure = ( input: unknown, ): Result => cause instanceof z.ZodError - ? err( - new AnalysisInputError( - operation, - undefined, - projectInputIssues(cause.issues, input), - ), - ) + ? err(analysisInputErrorFromIssues(operation, cause.issues, input)) : cause instanceof TypeError && SAFE_APPLICATION_INPUT_CONSTRAINTS.has(cause.message) ? err( diff --git a/src/application/javascript/JavaScriptRecoveryService.ts b/src/application/javascript/JavaScriptRecoveryService.ts index 16a536c646..c21e9e1e3f 100644 --- a/src/application/javascript/JavaScriptRecoveryService.ts +++ b/src/application/javascript/JavaScriptRecoveryService.ts @@ -12,7 +12,7 @@ import { javascriptRecoveryResultSchema, } from "../../domain/javascript/javascriptRecovery.js"; import { jsonObjectSchema } from "../../domain/jsonValue.js"; -import { projectInputIssues } from "../../domain/inputIssueProjection.js"; +import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js"; import { err, ok, type Result } from "../../domain/result.js"; import type { ExecutionOptions } from "../AnalysisProvider.js"; import type { JavaScriptRecoveryPort } from "./JavaScriptRecoveryPort.js"; @@ -33,11 +33,9 @@ export class JavaScriptRecoveryService { const parsed = javascriptRecoveryInputSchema.safeParse(input); if (!parsed.success) return err( - new AnalysisInputError( - OPERATION, - { cause: parsed.error }, - projectInputIssues(parsed.error.issues, input), - ), + analysisInputErrorFromIssues(OPERATION, parsed.error.issues, input, { + cause: parsed.error, + }), ); const invalidPaths = (["path", "output_directory"] as const).filter( (field) => !isAbsolute(parsed.data[field]), diff --git a/src/application/javascript/JavaScriptRuntimeReconciliationService.ts b/src/application/javascript/JavaScriptRuntimeReconciliationService.ts index b7e8ef166b..bc44855521 100644 --- a/src/application/javascript/JavaScriptRuntimeReconciliationService.ts +++ b/src/application/javascript/JavaScriptRuntimeReconciliationService.ts @@ -7,7 +7,7 @@ import { } from "../../domain/analysisErrorCore.js"; import { type AnalysisError } from "../../domain/analysisErrorBase.js"; import type { Evidence } from "../../domain/evidence.js"; -import { projectInputIssues } from "../../domain/inputIssueProjection.js"; +import { analysisInputErrorFromIssues } from "../../domain/inputIssueProjection.js"; import { reconcileJavaScriptRuntime } from "../../domain/javascript/javascriptRuntimeReconciliation.js"; import { reconcileJavaScriptRuntimeInputSchema } from "../../domain/javascript/javascriptRuntimeReconciliationSchemas.js"; import { err, ok, type Result } from "../../domain/result.js"; @@ -22,11 +22,7 @@ export const reconcileJavaScriptRuntimeEvidence = ( const parsed = reconcileJavaScriptRuntimeInputSchema.safeParse(rawInput); if (!parsed.success) return err( - new AnalysisInputError( - OPERATION, - undefined, - projectInputIssues(parsed.error.issues, rawInput), - ), + analysisInputErrorFromIssues(OPERATION, parsed.error.issues, rawInput), ); return reconcileJavaScriptRuntimeEvidenceValidated(parsed.data); }; @@ -44,11 +40,7 @@ export const reconcileJavaScriptRuntimeEvidenceValidated = ( } catch (cause: unknown) { if (cause instanceof z.ZodError) return err( - new AnalysisInputError( - OPERATION, - undefined, - projectInputIssues(cause.issues, parsedInput), - ), + analysisInputErrorFromIssues(OPERATION, cause.issues, parsedInput), ); if ( cause instanceof TypeError && diff --git a/src/application/process/ProcessCli.ts b/src/application/process/ProcessCli.ts index 7c60613ef2..560866a596 100644 --- a/src/application/process/ProcessCli.ts +++ b/src/application/process/ProcessCli.ts @@ -7,7 +7,10 @@ import { projectAnalysisError } from "../../domain/analysisErrorProjection.js"; import { createEvidence, parseEvidence } from "../../domain/evidence.js"; import { describeValidationFailure } from "../../domain/evidenceBundle.js"; import { jsonValueSchema } from "../../domain/jsonValue.js"; -import { projectInputIssues } from "../../domain/inputIssueProjection.js"; +import { + analysisInputErrorFromIssues, + projectInputIssues, +} from "../../domain/inputIssueProjection.js"; import { processTraceSpecificationSchema } from "../../domain/process/processTraceComparison.js"; import { processScenarioSchema } from "../../domain/process/processScenario.js"; import { @@ -49,10 +52,11 @@ export const captureProcessScenarioFile = async ( const input = await readJson(path); const parsed = processScenarioSchema.safeParse(input); if (!parsed.success) - throw new AnalysisInputError( + throw analysisInputErrorFromIssues( "capture_process_scenario", + parsed.error.issues, + input, { cause: parsed.error }, - projectInputIssues(parsed.error.issues, input), ); const captured = await captureProcessScenario( parsed.data, diff --git a/src/application/workflowInputError.ts b/src/application/workflowInputError.ts index 33150be22e..8fe6eecafa 100644 --- a/src/application/workflowInputError.ts +++ b/src/application/workflowInputError.ts @@ -1,7 +1,7 @@ import { z } from "zod"; import { AnalysisInputError } from "../domain/analysisErrorCore.js"; -import { projectInputIssues } from "../domain/inputIssueProjection.js"; +import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; /** Project schema issues of the raw request onto their request paths. */ export const requestInputError = ( @@ -9,11 +9,7 @@ export const requestInputError = ( cause: z.ZodError, input: unknown, ): AnalysisInputError => - new AnalysisInputError( - operation, - { cause }, - projectInputIssues(cause.issues, input), - ); + analysisInputErrorFromIssues(operation, cause.issues, input, { cause }); /** * Keep the failed constraint when a parsed request is rejected later. Nested diff --git a/src/cli/applicationCommands.ts b/src/cli/applicationCommands.ts index f4a6fb33b0..6edbc70474 100644 --- a/src/cli/applicationCommands.ts +++ b/src/cli/applicationCommands.ts @@ -15,7 +15,6 @@ import { buildReconstructionObligationLedgerEvidenceValidated } from "../applica import { CLI_COMMANDS } from "../cliCommandNames.js"; import { parseCliJsonInput } from "../cliJsonInput.js"; import { logCliCommand } from "../cliLogging.js"; -import { AnalysisInputError } from "../domain/analysisErrorCore.js"; import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; import { type AnalysisError } from "../domain/analysisErrorBase.js"; import { androidApplicationProjectionInputSchema } from "../domain/android/androidApplication.js"; @@ -27,7 +26,7 @@ import { traceJavaScriptSemanticsInputSchema } from "../domain/javascript/javasc import { compareApplicationVersionsInputSchema } from "../domain/javascript/javascriptApplicationVersionComparisonSchemas.js"; import { compareSourceToBundleInputSchema } from "../domain/javascript/sourceToBundleComparisonSchemas.js"; import { compareJavaScriptExportShapesInputSchema } from "../domain/javascript/javascriptExportShapeComparisonSchemas.js"; -import { projectInputIssues } from "../domain/inputIssueProjection.js"; +import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; import { reconstructionObligationLedgerInputSchema } from "../domain/reconstructionObligationLedgerSchemas.js"; type CliInstance = ReturnType; @@ -184,10 +183,10 @@ const registerJsonCommand = ({ return { error: "Application workflow failed", ...projectAnalysisError( - new AnalysisInputError( + analysisInputErrorFromIssues( name, - undefined, - projectInputIssues(parsed.error.issues, input.value), + parsed.error.issues, + input.value, ), ), }; diff --git a/src/cli/nativeCallCommands.ts b/src/cli/nativeCallCommands.ts index 8a796a2dfe..33a25c0cda 100644 --- a/src/cli/nativeCallCommands.ts +++ b/src/cli/nativeCallCommands.ts @@ -4,9 +4,8 @@ import { runProviderAnalysis } from "../composition/directAnalysis.js"; import { CLI_COMMANDS } from "../cliCommandNames.js"; import { parseCliJsonInput } from "../cliJsonInput.js"; import { logCliCommand } from "../cliLogging.js"; -import { AnalysisInputError } from "../domain/analysisErrorCore.js"; import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; -import { projectInputIssues } from "../domain/inputIssueProjection.js"; +import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js"; import { jsonValueSchema } from "../domain/jsonValue.js"; import { nativeCallObservationInputSchema } from "../domain/native/nativeCallObservation.js"; import type { Logger } from "../logger.js"; @@ -39,10 +38,10 @@ export const registerNativeCallCommands = ( return { error: "Analysis failed", ...projectAnalysisError( - new AnalysisInputError( + analysisInputErrorFromIssues( OPERATION, - undefined, - projectInputIssues(parsed.error.issues, input.value), + parsed.error.issues, + input.value, ), ), }; diff --git a/src/domain/inputIssueProjection.test.ts b/src/domain/inputIssueProjection.test.ts index 45a241d9ee..2543d97b48 100644 --- a/src/domain/inputIssueProjection.test.ts +++ b/src/domain/inputIssueProjection.test.ts @@ -1,10 +1,38 @@ import { describe, expect, it } from "vitest"; import { z } from "zod"; -import { projectInputIssues } from "./inputIssueProjection.js"; +import { + analysisInputErrorFromIssues, + projectInputIssues, +} from "./inputIssueProjection.js"; import { processScenarioSchema } from "./process/processScenario.js"; describe("input issue projection", () => { + it("builds application input errors with projected and semantic issues", () => { + const schema = z.object({ count: z.number().int() }); + const input = { count: "invalid" }; + const parsed = schema.safeParse(input); + if (parsed.success) throw new Error("expected invalid input"); + const cause = parsed.error; + const error = analysisInputErrorFromIssues( + "inspect_fixture", + parsed.error.issues, + input, + { + cause, + additionalIssues: [ + { path: ["count"], reason: "out_of_range", minimum: 0 }, + ], + }, + ); + + expect(error.cause).toBe(cause); + expect(error.issues).toEqual([ + { path: ["count"], reason: "invalid_type", expected: "number" }, + { path: ["count"], reason: "out_of_range", minimum: 0 }, + ]); + }); + it("preserves static regex guidance without echoing the rejected value", () => { const schema = z.object({ value: z.string().regex(/^[^\0]*$/u, "Values cannot contain NUL"), diff --git a/src/domain/inputIssueProjection.ts b/src/domain/inputIssueProjection.ts index 31d158743d..25798721c9 100644 --- a/src/domain/inputIssueProjection.ts +++ b/src/domain/inputIssueProjection.ts @@ -1,5 +1,24 @@ import type { z } from "zod"; -import type { AnalysisInputIssue } from "./analysisErrorCore.js"; +import { + AnalysisInputError, + type AnalysisInputIssue, +} from "./analysisErrorCore.js"; + +/** Build the standard application input error while retaining schema causes and extra semantic issues. */ +export const analysisInputErrorFromIssues = ( + operation: string, + issues: readonly z.core.$ZodIssue[], + input: unknown, + options: { + readonly cause?: unknown; + readonly additionalIssues?: readonly AnalysisInputIssue[]; + } = {}, +): AnalysisInputError => + new AnalysisInputError( + operation, + options.cause === undefined ? undefined : { cause: options.cause }, + [...projectInputIssues(issues, input), ...(options.additionalIssues ?? [])], + ); /** Project Zod failures to secret-safe caller correction metadata. */ export const projectInputIssues = (