fix(browser): report an out-of-range script_index as a selection fault (#6979)

* fix(browser): report an out-of-range script_index as a selection fault

A valid export_web_scripts manifest without the selected script_index was reported as an invalid_format input issue, as if the manifest file were malformed. It is now an out_of_range issue on script_index with the valid bounds, or invalid_value when the manifest has no scripts.

* test(browser): keep the out-of-range index case in its own block

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
This commit is contained in:
Adolaniumandmorluto authored and GitHub committed 2026-10-25 10:06:34 +08:00
1 parent e9b21aadc5
commit 046ec56d16
2 files changed
+76 -4

No files matched your search

+21 -4
View File
@@ -6,7 +6,10 @@ import { readStableArtifact } from "../../artifacts/readStableArtifact.js";
import { normalizeArtifactPath } from "../../artifacts/ArtifactPaths.js"; import { normalizeArtifactPath } from "../../artifacts/ArtifactPaths.js";
import type { WebScriptArtifactPort } from "../../application/WebScriptArtifactPort.js"; import type { WebScriptArtifactPort } from "../../application/WebScriptArtifactPort.js";
import type { ExecutionOptions } from "../../application/AnalysisProvider.js"; import type { ExecutionOptions } from "../../application/AnalysisProvider.js";
import { AnalysisCapabilityUnavailableError } from "../../domain/analysisErrorCore.js"; import {
AnalysisCapabilityUnavailableError,
AnalysisInputError,
} from "../../domain/analysisErrorCore.js";
import { isPathWithinRoot } from "../../domain/localPath.js"; import { isPathWithinRoot } from "../../domain/localPath.js";
import { err, ok } from "../../domain/result.js"; import { err, ok } from "../../domain/result.js";
import type { SelectedWebScriptInput } from "../../domain/webScriptArtifacts.js"; import type { SelectedWebScriptInput } from "../../domain/webScriptArtifacts.js";
@@ -15,7 +18,6 @@ import {
decodeWebArtifact, decodeWebArtifact,
readWebArtifactJson, readWebArtifactJson,
webArtifactReadError, webArtifactReadError,
WebArtifactFormatFailure,
type WebArtifactReadContext, type WebArtifactReadContext,
} from "./WebArtifactReads.js"; } from "./WebArtifactReads.js";
@@ -64,9 +66,24 @@ export class LocalWebScriptArtifacts implements WebScriptArtifactPort {
const manifest = webScriptExportManifestSchema.parse(manifestBytes.value); const manifest = webScriptExportManifestSchema.parse(manifestBytes.value);
field = ["script_index"]; field = ["script_index"];
const selected = manifest.scripts[input.script_index]; const selected = manifest.scripts[input.script_index];
// A valid manifest without this index is a caller selection fault.
if (selected === undefined) if (selected === undefined)
throw new WebArtifactFormatFailure( return err(
`No script at index ${String(input.script_index)}; manifest contains ${String(manifest.scripts.length)} scripts.`, new AnalysisInputError(this.operation, undefined, [
manifest.scripts.length === 0
? {
path: ["script_index"],
reason: "invalid_value",
message: "The selected manifest contains no scripts.",
}
: {
path: ["script_index"],
reason: "out_of_range",
minimum: 0,
maximum: manifest.scripts.length - 1,
message: `No script at index ${String(input.script_index)}; the manifest contains ${String(manifest.scripts.length)} scripts.`,
},
]),
); );
if (selected.content.state !== "exported") if (selected.content.state !== "exported")
return err( return err(
@@ -294,3 +294,58 @@ describe("caller-selected web module files", () => {
); );
}); });
}); });
describe("selected captured script index", () => {
it("reports an index past the manifest's scripts as an out-of-range selection", async () => {
const f = await fixture();
const response = await new LocalWebModuleArtifacts().load({
manifest_path: f.manifestPath,
script_index: 5,
});
if (response.ok) throw new Error("expected an out-of-range index");
expect(projectAnalysisError(response.error)).toMatchObject({
code: "invalid_request",
details: {
issues: [
{
path: ["script_index"],
reason: "out_of_range",
minimum: 0,
maximum: 0,
},
],
},
});
});
it("distinguishes an empty valid manifest from a malformed manifest", async () => {
const f = await fixture();
const parsed: unknown = JSON.parse(await readFile(f.manifestPath, "utf8"));
const manifest = webScriptExportResultSchema
.omit({ manifest: true })
.parse(parsed);
manifest.scripts = [];
await writeFile(f.manifestPath, JSON.stringify(manifest));
const artifacts = new LocalWebModuleArtifacts();
const input = { manifest_path: f.manifestPath, script_index: 0 };
const empty = await artifacts.load(input);
if (empty.ok) throw new Error("expected an empty-manifest selection fault");
expect(projectAnalysisError(empty.error)).toMatchObject({
code: "invalid_request",
details: {
issues: [{ path: ["script_index"], reason: "invalid_value" }],
},
});
await writeFile(f.manifestPath, "{");
const malformed = await artifacts.load(input);
if (malformed.ok) throw new Error("expected a malformed manifest fault");
expect(projectAnalysisError(malformed.error)).toMatchObject({
code: "invalid_request",
details: {
issues: [{ path: ["manifest_path"], reason: "invalid_format" }],
},
});
});
});