CodingKeyValidator.swift 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. //===----------------------------------------------------------------------===//
  2. //
  3. // This source file is part of the Swift Argument Parser open source project
  4. //
  5. // Copyright (c) 2020 Apple Inc. and the Swift project authors
  6. // Licensed under Apache License v2.0 with Runtime Library Exception
  7. //
  8. // See https://swift.org/LICENSE.txt for license information
  9. //
  10. //===----------------------------------------------------------------------===//
  11. /// A validator that ensures that all arguments have corresponding coding keys.
  12. struct CodingKeyValidator: ParsableArgumentsValidator {
  13. private struct Validator: Decoder {
  14. let argumentKeys: [InputKey]
  15. enum ValidationResult: Swift.Error {
  16. case success
  17. case missingCodingKeys([InputKey])
  18. }
  19. let codingPath: [CodingKey] = []
  20. let userInfo: [CodingUserInfoKey: Any] = [:]
  21. func unkeyedContainer() throws -> UnkeyedDecodingContainer {
  22. fatalError()
  23. }
  24. func singleValueContainer() throws -> SingleValueDecodingContainer {
  25. fatalError()
  26. }
  27. func container<Key>(keyedBy type: Key.Type) throws
  28. -> KeyedDecodingContainer<Key> where Key: CodingKey
  29. {
  30. let missingKeys = argumentKeys.filter { Key(stringValue: $0.name) == nil }
  31. if missingKeys.isEmpty {
  32. throw ValidationResult.success
  33. } else {
  34. throw ValidationResult.missingCodingKeys(missingKeys)
  35. }
  36. }
  37. }
  38. /// This error indicates that an option, a flag, or an argument of
  39. /// a `ParsableArguments` is defined without a corresponding `CodingKey`.
  40. struct MissingKeysError: ParsableArgumentsValidatorError,
  41. CustomStringConvertible
  42. {
  43. let missingCodingKeys: [InputKey]
  44. var description: String {
  45. let resolution = """
  46. To resolve this error, make sure that all properties have \
  47. corresponding cases in your custom `CodingKey` enumeration.
  48. """
  49. if missingCodingKeys.count > 1 {
  50. return """
  51. Arguments \(missingCodingKeys.map({ "`\($0)`" }).joined(separator: ",")) \
  52. are defined without corresponding `CodingKey`s.
  53. \(resolution)
  54. """
  55. } else {
  56. return """
  57. Argument `\(missingCodingKeys[0])` is defined without a \
  58. corresponding `CodingKey`.
  59. \(resolution)
  60. """
  61. }
  62. }
  63. var kind: ValidatorErrorKind {
  64. .failure
  65. }
  66. }
  67. struct InvalidDecoderError: ParsableArgumentsValidatorError,
  68. CustomStringConvertible
  69. {
  70. let type: ParsableArguments.Type
  71. var description: String {
  72. """
  73. The implementation of `init(from:)` for `\(type)` \
  74. is not compatible with ArgumentParser. To resolve this issue, make sure \
  75. that `init(from:)` calls the `container(keyedBy:)` method on the given \
  76. decoder and decodes each of its properties using the returned decoder.
  77. """
  78. }
  79. var kind: ValidatorErrorKind {
  80. .failure
  81. }
  82. }
  83. static func validate(_ type: ParsableArguments.Type, parent: InputKey?)
  84. -> ParsableArgumentsValidatorError?
  85. {
  86. let argumentKeys: [InputKey] = Mirror(reflecting: type.init())
  87. .children
  88. .compactMap { child in
  89. guard
  90. let codingKey = child.label,
  91. child.value as? ArgumentSetProvider != nil
  92. else { return nil }
  93. // Property wrappers have underscore-prefixed names
  94. return InputKey(name: codingKey, parent: parent)
  95. }
  96. guard argumentKeys.count > 0 else {
  97. return nil
  98. }
  99. do {
  100. let _ = try type.init(from: Validator(argumentKeys: argumentKeys))
  101. return InvalidDecoderError(type: type)
  102. } catch let result as Validator.ValidationResult {
  103. switch result {
  104. case .missingCodingKeys(let keys):
  105. return MissingKeysError(missingCodingKeys: keys)
  106. case .success:
  107. return nil
  108. }
  109. } catch {
  110. fatalError("Unexpected validation error: \(error)")
  111. }
  112. }
  113. }