diff --git a/.changeset/fix-invalid-value-doubled-expected.md b/.changeset/fix-invalid-value-doubled-expected.md new file mode 100644 index 00000000000..a8e9deaeedd --- /dev/null +++ b/.changeset/fix-invalid-value-doubled-expected.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Fix doubled `Expected: Expected ...` prefixes in CLI `InvalidValue` error messages, closes #6312. diff --git a/packages/effect/src/unstable/cli/CliError.ts b/packages/effect/src/unstable/cli/CliError.ts index ae58094514c..2ff929399d3 100644 --- a/packages/effect/src/unstable/cli/CliError.ts +++ b/packages/effect/src/unstable/cli/CliError.ts @@ -362,13 +362,16 @@ export class InvalidValue extends Schema.TaggedErrorClass( * @since 4.0.0 */ override get message() { + const expectation = this.expected.startsWith("Expected ") || this.expected.startsWith("Expected:") + ? this.expected + : `Expected: ${this.expected}` if (this.kind === "argument") { - return `Invalid value for argument <${this.option}>: "${this.value}". Expected: ${this.expected}` + return `Invalid value for argument <${this.option}>: "${this.value}". ${expectation}` } if (this.value.length === 0) { - return `Missing value for flag --${this.option}. Expected: ${this.expected}` + return `Missing value for flag --${this.option}. ${expectation}` } - return `Invalid value for flag --${this.option}: "${this.value}". Expected: ${this.expected}` + return `Invalid value for flag --${this.option}: "${this.value}". ${expectation}` } } diff --git a/packages/effect/src/unstable/cli/Primitive.ts b/packages/effect/src/unstable/cli/Primitive.ts index a4c59201f14..4526a81af1e 100644 --- a/packages/effect/src/unstable/cli/Primitive.ts +++ b/packages/effect/src/unstable/cli/Primitive.ts @@ -304,7 +304,7 @@ export const choice = ( if (choiceMap.has(value)) { return Effect.succeed(choiceMap.get(value)!) } - return Effect.fail(`Expected ${validChoices}, got ${format(value)}`) + return Effect.fail(validChoices) }) return Object.assign(primitive, { choiceKeys: choices.map(([key]) => key) }) } diff --git a/packages/effect/test/unstable/cli/Arguments.test.ts b/packages/effect/test/unstable/cli/Arguments.test.ts index 7314bf37551..5c0f37788dc 100644 --- a/packages/effect/test/unstable/cli/Arguments.test.ts +++ b/packages/effect/test/unstable/cli/Arguments.test.ts @@ -156,7 +156,7 @@ describe("Command arguments", () => { expect(errorText).toMatchInlineSnapshot(` " ERROR - Invalid value for argument : "not-a-number". Expected: Failed to parse integer: Expected an integer, got NaN" + Invalid value for argument : "not-a-number". Expected a string representing a finite number, got "not-a-number"" `) }).pipe(Effect.provide(TestLayer))) diff --git a/packages/effect/test/unstable/cli/Command.test.ts b/packages/effect/test/unstable/cli/Command.test.ts index 756ae223aaf..5dac07ac127 100644 --- a/packages/effect/test/unstable/cli/Command.test.ts +++ b/packages/effect/test/unstable/cli/Command.test.ts @@ -271,6 +271,27 @@ describe("Command", () => { }).pipe(Effect.provide(TestLayer))) } + it.effect("should render invalid choice values without doubling the Expected prefix", () => + Effect.gen(function*() { + let invoked = false + const command = Command.make("demo", { + size: Flag.choice("size", ["small", "medium", "large"]) + }, () => + Effect.sync(() => { + invoked = true + })) + + yield* Command.runWith(command, { version: "1.0.0" })(["--size", "bogus"]).pipe(Effect.ignore) + + const stderr = yield* TestConsole.errorLines + assert.isFalse(invoked) + assert.isTrue( + stderr.some((line) => + String(line).includes(`Invalid value for flag --size: "bogus". Expected: "small" | "medium" | "large"`) + ) + ) + }).pipe(Effect.provide(TestLayer))) + it.effect("should execute handler with parsed config", () => Effect.gen(function*() { const path = yield* Path.Path diff --git a/packages/effect/test/unstable/cli/Errors.test.ts b/packages/effect/test/unstable/cli/Errors.test.ts index 933502c431b..2fcb127de2a 100644 --- a/packages/effect/test/unstable/cli/Errors.test.ts +++ b/packages/effect/test/unstable/cli/Errors.test.ts @@ -147,4 +147,60 @@ describe("Command errors", () => { assert.strictEqual(output, "") }) }) + + describe("InvalidValue", () => { + it("labels a bare expected description", () => { + const error = new CliError.InvalidValue({ + option: "size", + value: "bogus", + expected: `"small" | "medium" | "large"`, + kind: "flag" + }) + + assert.strictEqual( + error.message, + `Invalid value for flag --size: "bogus". Expected: "small" | "medium" | "large"` + ) + }) + + it("does not double the prefix for an Expected sentence", () => { + const error = new CliError.InvalidValue({ + option: "count", + value: "3.14", + expected: "Expected an integer, got 3.14", + kind: "argument" + }) + + assert.strictEqual( + error.message, + `Invalid value for argument : "3.14". Expected an integer, got 3.14` + ) + + const labeled = new CliError.InvalidValue({ + option: "count", + value: "x", + expected: "Expected: an integer", + kind: "flag" + }) + + assert.strictEqual( + labeled.message, + `Invalid value for flag --count: "x". Expected: an integer` + ) + }) + + it("does not double the prefix for a missing flag value", () => { + const error = new CliError.InvalidValue({ + option: "count", + value: "", + expected: `Expected a string representing a finite number, got ""`, + kind: "flag" + }) + + assert.strictEqual( + error.message, + `Missing value for flag --count. Expected a string representing a finite number, got ""` + ) + }) + }) }) diff --git a/packages/effect/test/unstable/cli/Primitive.test.ts b/packages/effect/test/unstable/cli/Primitive.test.ts index 0b83b1ee630..f3e3706c5fc 100644 --- a/packages/effect/test/unstable/cli/Primitive.test.ts +++ b/packages/effect/test/unstable/cli/Primitive.test.ts @@ -214,9 +214,9 @@ describe("Primitive", () => { colorChoice, ["yellow", "purple", ""], [ - `Expected "red" | "green" | "blue", got "yellow"`, - `Expected "red" | "green" | "blue", got "purple"`, - `Expected "red" | "green" | "blue", got ""` + `"red" | "green" | "blue"`, + `"red" | "green" | "blue"`, + `"red" | "green" | "blue"` ] ))