diff --git a/Sources/JSONAPIOpenAPI/JSONAPIOpenAPITypes.swift b/Sources/JSONAPIOpenAPI/JSONAPIOpenAPITypes.swift index aa36007..5b2e24c 100644 --- a/Sources/JSONAPIOpenAPI/JSONAPIOpenAPITypes.swift +++ b/Sources/JSONAPIOpenAPI/JSONAPIOpenAPITypes.swift @@ -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 } } diff --git a/Sources/JSONAPIOpenAPI/OpenAPITypes.swift b/Sources/JSONAPIOpenAPI/OpenAPITypes.swift index 1be08c4..c4b410d 100644 --- a/Sources/JSONAPIOpenAPI/OpenAPITypes.swift +++ b/Sources/JSONAPIOpenAPI/OpenAPITypes.swift @@ -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(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(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 +} diff --git a/Sources/JSONAPIOpenAPI/Sampleable.swift b/Sources/JSONAPIOpenAPI/Sampleable.swift index 5e700a3..437ccfb 100644 --- a/Sources/JSONAPIOpenAPI/Sampleable.swift +++ b/Sources/JSONAPIOpenAPI/Sampleable.swift @@ -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 } diff --git a/Sources/JSONAPIOpenAPI/SwiftPrimitiveTypes.swift b/Sources/JSONAPIOpenAPI/SwiftPrimitiveTypes.swift index 1fe6559..1b09ab8 100644 --- a/Sources/JSONAPIOpenAPI/SwiftPrimitiveTypes.swift +++ b/Sources/JSONAPIOpenAPI/SwiftPrimitiveTypes.swift @@ -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))) ?? [] } } diff --git a/Tests/JSONAPIOpenAPITests/JSONAPIEntityOpenAPITests.swift b/Tests/JSONAPIOpenAPITests/JSONAPIEntityOpenAPITests.swift index c7d325f..298100c 100644 --- a/Tests/JSONAPIOpenAPITests/JSONAPIEntityOpenAPITests.swift +++ b/Tests/JSONAPIOpenAPITests/JSONAPIEntityOpenAPITests.swift @@ -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