Skip to content

Commit

Permalink
List valid options in error messages
Browse files Browse the repository at this point in the history
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 apple#344.
  • Loading branch information
dduan committed Jan 4, 2022
1 parent e73578d commit baebf3e
Show file tree
Hide file tree
Showing 2 changed files with 18 additions and 6 deletions.
17 changes: 14 additions & 3 deletions Sources/ArgumentParser/Usage/UsageGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -394,8 +394,9 @@ extension ErrorMessageGenerator {
}

func unableToParseValueMessage(origin: InputOrigin, name: Name?, value: String, key: InputKey, error: Error?) -> String {
let valueName = arguments(for: key).first?.valueName

let argumentValue = arguments(for: key).first
let valueName = argumentValue?.valueName

// We want to make the "best effort" in producing a custom error message.
// We favour `LocalizedError.errorDescription` and fall back to
// `CustomStringConvertible`. To opt in, return your custom error message
Expand All @@ -407,10 +408,20 @@ extension ErrorMessageGenerator {
case let err?:
return ": " + String(describing: err)
default:
if let help = argumentValue?.help, !help.allValues.isEmpty {
let quotedValues = help.allValues.map { "'\($0)'" }
let validList: String
if quotedValues.count <= 2 {
validList = quotedValues.joined(separator: " and ")
} else {
validList = quotedValues.dropLast().joined(separator: ", ") + " and \(quotedValues.last!)"
}
return ". Choose from \(validList)."
}
return ""
}
}()

switch (name, valueName) {
case let (n?, v?):
return "The value '\(value)' is invalid for '\(n.synopsisString) <\(v)>'\(customErrorMessage)"
Expand Down
7 changes: 4 additions & 3 deletions Tests/ArgumentParserUnitTests/ErrorMessageTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,19 @@ extension ErrorMessageTests {
}

fileprivate struct Foo: ParsableArguments {
enum Format: String, Equatable, Decodable, ExpressibleByArgument {
enum Format: String, Equatable, Decodable, ExpressibleByArgument, CaseIterable {
case text
case json
case csv
}
@Option(name: [.short, .long])
var format: Format
}

extension ErrorMessageTests {
func testWrongEnumValue() {
AssertErrorMessage(Foo.self, ["--format", "png"], "The value 'png' is invalid for '--format <format>'")
AssertErrorMessage(Foo.self, ["-f", "png"], "The value 'png' is invalid for '-f <format>'")
AssertErrorMessage(Foo.self, ["--format", "png"], "The value 'png' is invalid for '--format <format>'. Choose from 'text', 'json' and 'csv'.")
AssertErrorMessage(Foo.self, ["-f", "png"], "The value 'png' is invalid for '-f <format>'. Choose from 'text', 'json' and 'csv'.")
}
}

Expand Down

0 comments on commit baebf3e

Please sign in to comment.