Not crazy about how I got there, but now it is relatively easy to print arbitrary enum's allCases as the list of possible values in the format specced out by OpenAPI.

This commit is contained in:
Mathew Polzin
2019-01-20 15:39:54 -08:00
parent cf746e182f
commit dc42ec27fc
5 changed files with 77 additions and 92 deletions
@@ -6,6 +6,7 @@
//
import JSONAPI
import AnyCodable
private protocol _Optional {}
extension Optional: _Optional {}
@@ -32,14 +33,14 @@ extension Attribute: RawOpenAPINodeType where RawValue: RawRepresentable, RawVal
}
}
extension Attribute: AnyJSONCaseIterable where RawValue: CaseIterable {
public static var allCases: [Any] {
return Array(RawValue.allCases)
extension Attribute: AnyJSONCaseIterable where RawValue: CaseIterable, RawValue: Codable {
public static var allCases: [AnyCodable] {
return (try? allCases(from: Array(RawValue.allCases))) ?? []
}
}
extension Attribute: AnyWrappedJSONCaseIterable where RawValue: AnyJSONCaseIterable {
public static var allCases: [Any] {
public static var allCases: [AnyCodable] {
return RawValue.allCases
}
}
+40 -47
View File
@@ -6,6 +6,7 @@
//
import AnyCodable
import Foundation
// MARK: Node (i.e. schema) Protocols
@@ -28,7 +29,20 @@ public protocol RawOpenAPINodeType {
/// Anything conforming to `AnyJSONCaseIterable` can provide a
/// list of its possible values.
public protocol AnyJSONCaseIterable {
static var allCases: [Any] { get }
static var allCases: [AnyCodable] { get }
}
extension AnyJSONCaseIterable {
static func allCases<T: Codable>(from input: [T]) throws -> [AnyCodable] {
if let alreadyGoodToGo = input as? [AnyCodable] {
return alreadyGoodToGo
}
guard let arrayOfCodables = try JSONSerialization.jsonObject(with: JSONEncoder().encode(input), options: []) as? [Any] else {
throw CodableError.allCasesArrayNotCodable
}
return arrayOfCodables.map(AnyCodable.init)
}
}
/// Anything conforming to `AnyJSONCaseIterable` can provide a
@@ -39,7 +53,7 @@ public protocol AnyJSONCaseIterable {
/// The "different" conditions have to do
/// with Optionality, hence the name of this protocol.
public protocol AnyWrappedJSONCaseIterable {
static var allCases: [Any] { get }
static var allCases: [AnyCodable] { get }
}
public protocol SwiftTyped {
@@ -206,12 +220,12 @@ public enum JSONNode {
/// The OpenAPI spec calls this "enum"
/// If not specified, it is assumed that any
/// value of the given format is allowed.
public let allowedValues: [Format.SwiftType]?
public let allowedValues: [AnyCodable]?
public init(format: Format,
required: Bool,
nullable: Bool = false,
allowedValues: [Format.SwiftType]? = nil) {
allowedValues: [AnyCodable]? = nil) {
self.format = format
self.required = required
self.nullable = nullable
@@ -243,7 +257,7 @@ public enum JSONNode {
}
/// Return this context with the given list of possible values
public func with(allowedValues: [Format.SwiftType]?) -> Context {
public func with(allowedValues: [AnyCodable]?) -> Context {
return .init(format: format,
required: required,
nullable: nullable,
@@ -433,50 +447,24 @@ public enum JSONNode {
}
}
public func with<T>(allowedValues: [T]) throws -> JSONNode where T: RawRepresentable, T.RawValue == String {
return try with(allowedValues: allowedValues.map { $0.rawValue })
}
public func with(allowedValues: [AnyCodable]) throws -> JSONNode {
public func with(allowedValues: [JSONTypeFormat.BooleanFormat.SwiftType]) throws -> JSONNode {
guard case let .boolean(contextA) = self else {
throw AllowedValueError(expectation: jsonTypeFormat?.jsonType, receivedType: JSONTypeFormat.BooleanFormat.SwiftType.self)
switch self {
case .boolean(let context):
return .boolean(context.with(allowedValues: allowedValues))
case .object(let contextA, let contextB):
return .object(contextA.with(allowedValues: allowedValues), contextB)
case .array(let contextA, let contextB):
return .array(contextA.with(allowedValues: allowedValues), contextB)
case .number(let context, let contextB):
return .number(context.with(allowedValues: allowedValues), contextB)
case .integer(let context, let contextB):
return .integer(context.with(allowedValues: allowedValues), contextB)
case .string(let context, let contextB):
return .string(context.with(allowedValues: allowedValues), contextB)
case .allOf, .oneOf, .anyOf, .not:
return self
}
return .boolean(contextA.with(allowedValues: allowedValues))
}
public func with(allowedValues: [JSONTypeFormat.ObjectFormat.SwiftType]) throws -> JSONNode {
guard case let .object(contextA, contextB) = self else {
throw AllowedValueError(expectation: jsonTypeFormat?.jsonType, receivedType: JSONTypeFormat.ObjectFormat.SwiftType.self)
}
return .object(contextA.with(allowedValues: allowedValues), contextB)
}
public func with(allowedValues: [JSONTypeFormat.ArrayFormat.SwiftType]) throws -> JSONNode {
guard case let .array(contextA, contextB) = self else {
throw AllowedValueError(expectation: jsonTypeFormat?.jsonType, receivedType: JSONTypeFormat.ArrayFormat.SwiftType.self)
}
return .array(contextA.with(allowedValues: allowedValues), contextB)
}
public func with(allowedValues: [JSONTypeFormat.NumberFormat.SwiftType]) throws -> JSONNode {
guard case let .number(contextA, contextB) = self else {
throw AllowedValueError(expectation: jsonTypeFormat?.jsonType, receivedType: JSONTypeFormat.NumberFormat.SwiftType.self)
}
return .number(contextA.with(allowedValues: allowedValues), contextB)
}
public func with(allowedValues: [JSONTypeFormat.IntegerFormat.SwiftType]) throws -> JSONNode {
guard case let .integer(contextA, contextB) = self else {
throw AllowedValueError(expectation: jsonTypeFormat?.jsonType, receivedType: JSONTypeFormat.IntegerFormat.SwiftType.self)
}
return .integer(contextA.with(allowedValues: allowedValues), contextB)
}
public func with(allowedValues: [JSONTypeFormat.StringFormat.SwiftType]) throws -> JSONNode {
guard case let .string(contextA, contextB) = self else {
throw AllowedValueError(expectation: jsonTypeFormat?.jsonType, receivedType: JSONTypeFormat.StringFormat.SwiftType.self)
}
return .string(contextA.with(allowedValues: allowedValues), contextB)
}
}
@@ -493,3 +481,8 @@ public struct AllowedValueError: Swift.Error, CustomStringConvertible {
return "Expected type compatible with JSON Type \(String(describing: expectation)) but found \(receivedType)"
}
}
public enum CodableError: Swift.Error {
case codableNotAnyCodable(Any.Type)
case allCasesArrayNotCodable
}
+27 -27
View File
@@ -20,7 +20,7 @@ extension Sampleable {
let properties: [(String, JSONNode)] = try mirror.children.compactMap { child in
// see if we can enumerate the possible values
let maybeAllCases: [Any]? = {
let maybeAllCases: [AnyCodable]? = {
switch type(of: child.value) {
case let valType as AnyJSONCaseIterable.Type:
return valType.allCases
@@ -49,32 +49,32 @@ extension Sampleable {
let newNode: JSONNode?
if let allCases = maybeAllCases,
let openAPINode = maybeOpenAPINode {
newNode = try {
if let cases = allCases as? [JSONTypeFormat.BooleanFormat.SwiftType] {
return try openAPINode.with(allowedValues: cases)
} else if let cases = allCases as? [JSONTypeFormat.ArrayFormat.SwiftType] {
return try openAPINode.with(allowedValues: cases)
} else if let cases = allCases as? [JSONTypeFormat.ObjectFormat.SwiftType] {
return try openAPINode.with(allowedValues: cases)
} else if let cases = allCases as? [JSONTypeFormat.NumberFormat.SwiftType] {
return try openAPINode.with(allowedValues: cases)
} else if let cases = allCases as? [JSONTypeFormat.IntegerFormat.SwiftType] {
return try openAPINode.with(allowedValues: cases)
} else if let cases = allCases as? [JSONTypeFormat.StringFormat.SwiftType] {
return try openAPINode.with(allowedValues: cases)
} else if allCases.compactMap({ $0 as? RawStringRepresentable }).count == allCases.count {
return try openAPINode.with(allowedValues: allCases.compactMap { ($0 as? RawStringRepresentable)?.rawValue })
} else {
throw SampleableError.allowedValuesNotOfExpectedType(forNode: openAPINode, allowedValues: allCases)
}
}()
newNode = try openAPINode.with(allowedValues: allCases) // try {
// if let cases = allCases as? [JSONTypeFormat.BooleanFormat.SwiftType] {
// return try openAPINode.with(allowedValues: cases)
//
// } else if let cases = allCases as? [JSONTypeFormat.ArrayFormat.SwiftType] {
// return try openAPINode.with(allowedValues: cases)
//
// } else if let cases = allCases as? [JSONTypeFormat.ObjectFormat.SwiftType] {
// return try openAPINode.with(allowedValues: cases)
//
// } else if let cases = allCases as? [JSONTypeFormat.NumberFormat.SwiftType] {
// return try openAPINode.with(allowedValues: cases)
//
// } else if let cases = allCases as? [JSONTypeFormat.IntegerFormat.SwiftType] {
// return try openAPINode.with(allowedValues: cases)
//
// } else if let cases = allCases as? [JSONTypeFormat.StringFormat.SwiftType] {
// return try openAPINode.with(allowedValues: cases)
//
// } else if allCases.compactMap({ $0 as? RawStringRepresentable }).count == allCases.count {
// return try openAPINode.with(allowedValues: allCases.compactMap { ($0 as? RawStringRepresentable)?.rawValue })
//
// } else {
// throw SampleableError.allowedValuesNotOfExpectedType(forNode: openAPINode, allowedValues: allCases)
// }
// }()
} else {
newNode = maybeOpenAPINode
}
@@ -5,6 +5,8 @@
// Created by Mathew Polzin on 01/13/19.
//
import AnyCodable
/**
Notable omissions in this library's default offerings:
@@ -41,9 +43,9 @@ extension Optional: RawOpenAPINodeType where Wrapped: RawRepresentable, Wrapped.
}
}
extension Optional: AnyJSONCaseIterable where Wrapped: CaseIterable {
public static var allCases: [Any] {
return Array(Wrapped.allCases)
extension Optional: AnyJSONCaseIterable where Wrapped: CaseIterable, Wrapped: Codable {
public static var allCases: [AnyCodable] {
return (try? allCases(from: Array(Wrapped.allCases))) ?? []
}
}
@@ -22,17 +22,6 @@ class JSONAPIEntityOpenAPITests: XCTestCase {
}
func test_AttributesEntity() {
let tmp = ["hello"] as [Any]
let tmp2 = tmp as! [String]
let tmp3 = tmp as? RawStringArrayRepresentable
let tmp4 = tmp2 as? RawStringArrayRepresentable
let y = TestType2Description.EnumType.one
let z = y as Any
let x = [y as? TestType2Description.EnumType]
let node = try! TestType2.openAPINode()
// TODO: Write test