Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/unrecognized-arguments.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"effect": patch
---

Added UnrecognizedArgument error to effect/cli for excess positional arguments.
31 changes: 31 additions & 0 deletions packages/effect/src/unstable/cli/CliError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export type CliError =
| UnknownSubcommand
| ShowHelp
| UserError
| UnrecognizedArgument

/**
* Error thrown when an unrecognized option is encountered.
Expand Down Expand Up @@ -489,6 +490,34 @@ export class UserError extends Schema.TaggedErrorClass<UserError>(
readonly [TypeId] = TypeId
}

/**
* Error thrown when an unrecognized argument is encountered.
*
* @category models
* @since 4.0.0
*/
export class UnrecognizedArgument extends Schema.TaggedErrorClass<UnrecognizedArgument>(
`${TypeId}/UnrecognizedArgument`
)("UnrecognizedArgument", {
error: Schema.String
}) {
/**
* Marks this value as a CLI parsing error for runtime guards.
*
* @since 4.0.0
*/
readonly [TypeId] = TypeId

/**
* Retrieves the underlying error.
*
* @since 4.0.0
*/
override get message() {
return this.error
}
}

/**
* Schema for concrete CLI errors that can be reported together with help output.
*
Expand All @@ -508,6 +537,7 @@ export const NonShowHelpErrors: Schema.Union<
typeof MissingArgument,
typeof InvalidValue,
typeof UnknownSubcommand,
typeof UnrecognizedArgument,
typeof UserError
]
> = Schema.Union([
Expand All @@ -517,6 +547,7 @@ export const NonShowHelpErrors: Schema.Union<
MissingArgument,
InvalidValue,
UnknownSubcommand,
UnrecognizedArgument,
UserError
])

Expand Down
6 changes: 6 additions & 0 deletions packages/effect/src/unstable/cli/internal/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,12 @@ const parseParams: (parsedArgs: Param.ParsedArgs, params: ReadonlyArray<Param.An
currentArguments = remainingArguments
}

if (currentArguments.length > 0) {
return yield* new CliError.UnrecognizedArgument({
error: `Excess arguments: ${currentArguments.join(", ")}`
})
}

return results
})

Expand Down
28 changes: 28 additions & 0 deletions packages/effect/test/unstable/cli/Command.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -722,6 +722,34 @@ describe("Command", () => {
const result = yield* Effect.flip(Cli.run(["test-failing", "--input", "test"]))
assert.strictEqual(result, "Handler error")
}).pipe(Effect.provide(TestLayer)))
it.effect("should fail with UnrecognizedArgument when excess positional arguments are provided to a command with no arguments", () =>
Effect.gen(function*() {
const command = Command.make("test", {}, () => Effect.void)
const runCommand = Command.runWith(command, { version: "1.0.0" })
const error = yield* Effect.flip(runCommand(["bogus1", "bogus2"]))
assert.strictEqual((error as any)._tag, "UnrecognizedArgument")
assert.isTrue(String((error as any).error).includes("Excess arguments: bogus1, bogus2"))
}).pipe(Effect.provide(TestLayer)))

it.effect("should fail with UnrecognizedArgument when excess positional arguments are provided to a command with fixed arguments", () =>
Effect.gen(function*() {
const command = Command.make("test", {
arg1: Argument.string("arg1")
}, () => Effect.void)
const runCommand = Command.runWith(command, { version: "1.0.0" })
const error = yield* Effect.flip(runCommand(["valid", "bogus1", "bogus2"]))
assert.strictEqual((error as any)._tag, "UnrecognizedArgument")
assert.isTrue(String((error as any).error).includes("Excess arguments: bogus1, bogus2"))
}).pipe(Effect.provide(TestLayer)))

it.effect("should not fail with UnrecognizedArgument when a command has variadic arguments", () =>
Effect.gen(function*() {
const command = Command.make("test", {
args: Argument.string("args").pipe(Argument.repeated)
}, () => Effect.void)
const runCommand = Command.runWith(command, { version: "1.0.0" })
yield* runCommand(["valid1", "valid2", "valid3"])
}).pipe(Effect.provide(TestLayer)))
})

describe("withSubcommands", () => {
Expand Down