Skip to content

Commit 90f76c1

Browse files
authored
List valid options in error messages (#382)
When an option value fails to parse, no custom error message is provided, and a list of valid candidate values is available, include the list as part of the error message. Addresses #344.
1 parent e73578d commit 90f76c1

File tree

2 files changed

+65
-7
lines changed

2 files changed

+65
-7
lines changed

Sources/ArgumentParser/Usage/UsageGenerator.swift

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -394,8 +394,9 @@ extension ErrorMessageGenerator {
394394
}
395395

396396
func unableToParseValueMessage(origin: InputOrigin, name: Name?, value: String, key: InputKey, error: Error?) -> String {
397-
let valueName = arguments(for: key).first?.valueName
398-
397+
let argumentValue = arguments(for: key).first
398+
let valueName = argumentValue?.valueName
399+
399400
// We want to make the "best effort" in producing a custom error message.
400401
// We favour `LocalizedError.errorDescription` and fall back to
401402
// `CustomStringConvertible`. To opt in, return your custom error message
@@ -407,10 +408,10 @@ extension ErrorMessageGenerator {
407408
case let err?:
408409
return ": " + String(describing: err)
409410
default:
410-
return ""
411+
return argumentValue?.formattedValueList ?? ""
411412
}
412413
}()
413-
414+
414415
switch (name, valueName) {
415416
case let (n?, v?):
416417
return "The value '\(value)' is invalid for '\(n.synopsisString) <\(v)>'\(customErrorMessage)"
@@ -423,3 +424,25 @@ extension ErrorMessageGenerator {
423424
}
424425
}
425426
}
427+
428+
private extension ArgumentDefinition {
429+
var formattedValueList: String {
430+
if help.allValues.isEmpty {
431+
return ""
432+
}
433+
434+
if help.allValues.count < 6 {
435+
let quotedValues = help.allValues.map { "'\($0)'" }
436+
let validList: String
437+
if quotedValues.count <= 2 {
438+
validList = quotedValues.joined(separator: " and ")
439+
} else {
440+
validList = quotedValues.dropLast().joined(separator: ", ") + " or \(quotedValues.last!)"
441+
}
442+
return ". Please provide one of \(validList)."
443+
} else {
444+
let bulletValueList = help.allValues.map { " - \($0)" }.joined(separator: "\n")
445+
return ". Please provide one of the following:\n\(bulletValueList)"
446+
}
447+
}
448+
}

Tests/ArgumentParserUnitTests/ErrorMessageTests.swift

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,18 +65,53 @@ extension ErrorMessageTests {
6565
}
6666

6767
fileprivate struct Foo: ParsableArguments {
68-
enum Format: String, Equatable, Decodable, ExpressibleByArgument {
68+
enum Format: String, Equatable, Decodable, ExpressibleByArgument, CaseIterable {
6969
case text
7070
case json
71+
case csv
72+
}
73+
74+
enum Name: String, Equatable, Decodable, ExpressibleByArgument, CaseIterable {
75+
case bruce
76+
case clint
77+
case hulk
78+
case natasha
79+
case steve
80+
case thor
81+
case tony
7182
}
7283
@Option(name: [.short, .long])
7384
var format: Format
85+
@Option(name: [.short, .long])
86+
var name: Name?
7487
}
7588

7689
extension ErrorMessageTests {
7790
func testWrongEnumValue() {
78-
AssertErrorMessage(Foo.self, ["--format", "png"], "The value 'png' is invalid for '--format <format>'")
79-
AssertErrorMessage(Foo.self, ["-f", "png"], "The value 'png' is invalid for '-f <format>'")
91+
AssertErrorMessage(Foo.self, ["--format", "png"], "The value 'png' is invalid for '--format <format>'. Please provide one of 'text', 'json' or 'csv'.")
92+
AssertErrorMessage(Foo.self, ["-f", "png"], "The value 'png' is invalid for '-f <format>'. Please provide one of 'text', 'json' or 'csv'.")
93+
AssertErrorMessage(Foo.self, ["-f", "text", "--name", "loki"],
94+
"""
95+
The value 'loki' is invalid for '--name <name>'. Please provide one of the following:
96+
- bruce
97+
- clint
98+
- hulk
99+
- natasha
100+
- steve
101+
- thor
102+
- tony
103+
""")
104+
AssertErrorMessage(Foo.self, ["-f", "text", "-n", "loki"],
105+
"""
106+
The value 'loki' is invalid for '-n <name>'. Please provide one of the following:
107+
- bruce
108+
- clint
109+
- hulk
110+
- natasha
111+
- steve
112+
- thor
113+
- tony
114+
""")
80115
}
81116
}
82117

0 commit comments

Comments
 (0)