Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a validation message for an invalid decoder #487

Merged
merged 1 commit into from Sep 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -146,14 +146,29 @@ struct ParsableArgumentsCodingKeyValidator: ParsableArgumentsValidator {

/// This error indicates that an option, a flag, or an argument of
/// a `ParsableArguments` is defined without a corresponding `CodingKey`.
struct Error: ParsableArgumentsValidatorError, CustomStringConvertible {
struct MissingKeysError: ParsableArgumentsValidatorError, CustomStringConvertible {
let missingCodingKeys: [String]

var description: String {
let resolution = """
To resolve this error, make sure that all properties have corresponding
cases in your custom `CodingKey` enumeration.
"""

if missingCodingKeys.count > 1 {
return "Arguments \(missingCodingKeys.map({ "`\($0)`" }).joined(separator: ",")) are defined without corresponding `CodingKey`s."
return """
Arguments \(missingCodingKeys.map({ "`\($0)`" }).joined(separator: ",")) \
are defined without corresponding `CodingKey`s.

\(resolution)
"""
} else {
return "Argument `\(missingCodingKeys[0])` is defined without a corresponding `CodingKey`."
return """
Argument `\(missingCodingKeys[0])` is defined without a corresponding \
`CodingKey`.

\(resolution)
"""
}
}

Expand All @@ -162,6 +177,23 @@ struct ParsableArgumentsCodingKeyValidator: ParsableArgumentsValidator {
}
}

struct InvalidDecoderError: ParsableArgumentsValidatorError, CustomStringConvertible {
let type: ParsableArguments.Type

var description: String {
"""
The implementation of `init(from:)` for `\(type)`
is not compatible with ArgumentParser. To resolve this issue, make sure
that `init(from:)` calls the `container(keyedBy:)` method on the given
decoder and decodes each of its properties using the returned decoder.
"""
}

var kind: ValidatorErrorKind {
.failure
}
}

static func validate(_ type: ParsableArguments.Type) -> ParsableArgumentsValidatorError? {
let argumentKeys: [String] = Mirror(reflecting: type.init())
.children
Expand All @@ -179,11 +211,11 @@ struct ParsableArgumentsCodingKeyValidator: ParsableArgumentsValidator {
}
do {
let _ = try type.init(from: Validator(argumentKeys: argumentKeys))
fatalError("The validator should always throw.")
return InvalidDecoderError(type: type)
} catch let result as Validator.ValidationResult {
switch result {
case .missingCodingKeys(let keys):
return Error(missingCodingKeys: keys)
return MissingKeysError(missingCodingKeys: keys)
case .success:
return nil
}
Expand Down Expand Up @@ -244,9 +276,11 @@ struct NonsenseFlagsValidator: ParsableArgumentsValidator {
"""
One or more Boolean flags is declared with an initial value of `true`.
This results in the flag always being `true`, no matter whether the user
specifies the flag or not. To resolve this error, change the default to
`false`, provide a value for the `inversion:` parameter, or remove the
`@Flag` property wrapper altogether.
specifies the flag or not.

To resolve this error, change the default to `false`, provide a value
for the `inversion:` parameter, or remove the `@Flag` property wrapper
altogether.

Affected flag(s):
\(names.joined(separator: "\n"))
Expand Down
Expand Up @@ -85,29 +85,53 @@ final class ParsableArgumentsValidationTests: XCTestCase {
XCTAssertNil(ParsableArgumentsCodingKeyValidator.validate(B.self))

if let error = ParsableArgumentsCodingKeyValidator.validate(C.self)
as? ParsableArgumentsCodingKeyValidator.Error
as? ParsableArgumentsCodingKeyValidator.MissingKeysError
{
XCTAssert(error.missingCodingKeys == ["count"])
} else {
XCTFail()
}

if let error = ParsableArgumentsCodingKeyValidator.validate(D.self)
as? ParsableArgumentsCodingKeyValidator.Error
as? ParsableArgumentsCodingKeyValidator.MissingKeysError
{
XCTAssert(error.missingCodingKeys == ["phrase"])
} else {
XCTFail()
}

if let error = ParsableArgumentsCodingKeyValidator.validate(E.self)
as? ParsableArgumentsCodingKeyValidator.Error
as? ParsableArgumentsCodingKeyValidator.MissingKeysError
{
XCTAssert(error.missingCodingKeys == ["phrase", "includeCounter"])
} else {
XCTFail()
}
}

private struct TypeWithInvalidDecoder: ParsableArguments {
@Argument(help: "The phrase to repeat.")
var phrase: String = ""

@Option(help: "The number of times to repeat 'phrase'.")
var count: Int = 0

init() {}

init(from decoder: Decoder) throws {
self.init()
}
}

func testCustomDecoderValidation() throws {
if let error = ParsableArgumentsCodingKeyValidator.validate(TypeWithInvalidDecoder.self)
as? ParsableArgumentsCodingKeyValidator.InvalidDecoderError
{
XCTAssert(error.type == TypeWithInvalidDecoder.self)
} else {
XCTFail()
}
}

private struct F: ParsableArguments {
@Argument()
Expand Down Expand Up @@ -413,9 +437,11 @@ final class ParsableArgumentsValidationTests: XCTestCase {
"""
One or more Boolean flags is declared with an initial value of `true`.
This results in the flag always being `true`, no matter whether the user
specifies the flag or not. To resolve this error, change the default to
`false`, provide a value for the `inversion:` parameter, or remove the
`@Flag` property wrapper altogether.
specifies the flag or not.

To resolve this error, change the default to `false`, provide a value
for the `inversion:` parameter, or remove the `@Flag` property wrapper
altogether.

Affected flag(s):
--nonsense
Expand Down Expand Up @@ -448,9 +474,11 @@ final class ParsableArgumentsValidationTests: XCTestCase {
"""
One or more Boolean flags is declared with an initial value of `true`.
This results in the flag always being `true`, no matter whether the user
specifies the flag or not. To resolve this error, change the default to
`false`, provide a value for the `inversion:` parameter, or remove the
`@Flag` property wrapper altogether.
specifies the flag or not.

To resolve this error, change the default to `false`, provide a value
for the `inversion:` parameter, or remove the `@Flag` property wrapper
altogether.

Affected flag(s):
--stuff
Expand Down