From 951c04ad4481f62ffc0bebb34e085f26a9347a19 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Wed, 23 Jan 2019 22:21:27 -0800 Subject: [PATCH] Add Sampleable conformances. Make blanket JSONEncoder change I am not happy with; will try to walk back requirement that encoder gets passed to all functions creating OpenAPI Nodes --- .../JSONAPI/JSONAPIInclude+OpenAPI.swift | 115 +++++++++--------- .../JSONAPI/JSONAPITypes+OpenAPI.swift | 65 +++++----- .../JSONAPIOpenAPI/OpenAPI/OpenAPITypes.swift | 30 ++--- .../OpenAPI/SwiftPrimitiveTypes+OpenAPI.swift | 39 +++--- .../Sampleable/Sampleable.swift | 88 ++++++++++++-- .../JSONAPIEntityOpenAPITests.swift | 6 +- 6 files changed, 211 insertions(+), 132 deletions(-) diff --git a/Sources/JSONAPIOpenAPI/JSONAPI/JSONAPIInclude+OpenAPI.swift b/Sources/JSONAPIOpenAPI/JSONAPI/JSONAPIInclude+OpenAPI.swift index 9b9e456..948cd0e 100644 --- a/Sources/JSONAPIOpenAPI/JSONAPI/JSONAPIInclude+OpenAPI.swift +++ b/Sources/JSONAPIOpenAPI/JSONAPI/JSONAPIInclude+OpenAPI.swift @@ -6,10 +6,11 @@ // import JSONAPI +import Foundation extension Includes: OpenAPINodeType where I: OpenAPINodeType { - public static func openAPINode() throws -> JSONNode { - let includeNode = try I.openAPINode() + public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { + let includeNode = try I.openAPINode(using: encoder) return .array(.init(format: .generic, required: true), @@ -19,113 +20,113 @@ extension Includes: OpenAPINodeType where I: OpenAPINodeType { } extension Include0: OpenAPINodeType { - public static func openAPINode() throws -> JSONNode { + public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { throw OpenAPITypeError.invalidNode } } extension Include1: OpenAPINodeType where A: OpenAPINodeType { - public static func openAPINode() throws -> JSONNode { - return try .one(of: [A.openAPINode()]) + public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { + return try .one(of: [A.openAPINode(using: encoder)]) } } extension Include2: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType { - public static func openAPINode() throws -> JSONNode { + public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { return try .one(of: [ - A.openAPINode(), - B.openAPINode() + A.openAPINode(using: encoder), + B.openAPINode(using: encoder) ]) } } extension Include3: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType { - public static func openAPINode() throws -> JSONNode { + public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { return try .one(of: [ - A.openAPINode(), - B.openAPINode(), - C.openAPINode() + A.openAPINode(using: encoder), + B.openAPINode(using: encoder), + C.openAPINode(using: encoder) ]) } } extension Include4: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType { - public static func openAPINode() throws -> JSONNode { + public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { return try .one(of: [ - A.openAPINode(), - B.openAPINode(), - C.openAPINode(), - D.openAPINode() + A.openAPINode(using: encoder), + B.openAPINode(using: encoder), + C.openAPINode(using: encoder), + D.openAPINode(using: encoder) ]) } } extension Include5: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType, E: OpenAPINodeType { - public static func openAPINode() throws -> JSONNode { + public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { return try .one(of: [ - A.openAPINode(), - B.openAPINode(), - C.openAPINode(), - D.openAPINode(), - E.openAPINode() + A.openAPINode(using: encoder), + B.openAPINode(using: encoder), + C.openAPINode(using: encoder), + D.openAPINode(using: encoder), + E.openAPINode(using: encoder) ]) } } extension Include6: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType, E: OpenAPINodeType, F: OpenAPINodeType { - public static func openAPINode() throws -> JSONNode { + public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { return try .one(of: [ - A.openAPINode(), - B.openAPINode(), - C.openAPINode(), - D.openAPINode(), - E.openAPINode(), - F.openAPINode() + A.openAPINode(using: encoder), + B.openAPINode(using: encoder), + C.openAPINode(using: encoder), + D.openAPINode(using: encoder), + E.openAPINode(using: encoder), + F.openAPINode(using: encoder) ]) } } extension Include7: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType, E: OpenAPINodeType, F: OpenAPINodeType, G: OpenAPINodeType { - public static func openAPINode() throws -> JSONNode { + public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { return try .one(of: [ - A.openAPINode(), - B.openAPINode(), - C.openAPINode(), - D.openAPINode(), - E.openAPINode(), - F.openAPINode(), - G.openAPINode() + A.openAPINode(using: encoder), + B.openAPINode(using: encoder), + C.openAPINode(using: encoder), + D.openAPINode(using: encoder), + E.openAPINode(using: encoder), + F.openAPINode(using: encoder), + G.openAPINode(using: encoder) ]) } } extension Include8: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType, E: OpenAPINodeType, F: OpenAPINodeType, G: OpenAPINodeType, H: OpenAPINodeType { - public static func openAPINode() throws -> JSONNode { + public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { return try .one(of: [ - A.openAPINode(), - B.openAPINode(), - C.openAPINode(), - D.openAPINode(), - E.openAPINode(), - F.openAPINode(), - G.openAPINode(), - H.openAPINode() + A.openAPINode(using: encoder), + B.openAPINode(using: encoder), + C.openAPINode(using: encoder), + D.openAPINode(using: encoder), + E.openAPINode(using: encoder), + F.openAPINode(using: encoder), + G.openAPINode(using: encoder), + H.openAPINode(using: encoder) ]) } } extension Include9: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType, E: OpenAPINodeType, F: OpenAPINodeType, G: OpenAPINodeType, H: OpenAPINodeType, I: OpenAPINodeType { - public static func openAPINode() throws -> JSONNode { + public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { return try .one(of: [ - A.openAPINode(), - B.openAPINode(), - C.openAPINode(), - D.openAPINode(), - E.openAPINode(), - F.openAPINode(), - G.openAPINode(), - H.openAPINode(), - I.openAPINode() + A.openAPINode(using: encoder), + B.openAPINode(using: encoder), + C.openAPINode(using: encoder), + D.openAPINode(using: encoder), + E.openAPINode(using: encoder), + F.openAPINode(using: encoder), + G.openAPINode(using: encoder), + H.openAPINode(using: encoder), + I.openAPINode(using: encoder) ]) } } diff --git a/Sources/JSONAPIOpenAPI/JSONAPI/JSONAPITypes+OpenAPI.swift b/Sources/JSONAPIOpenAPI/JSONAPI/JSONAPITypes+OpenAPI.swift index cc5efde..5e0baac 100644 --- a/Sources/JSONAPIOpenAPI/JSONAPI/JSONAPITypes+OpenAPI.swift +++ b/Sources/JSONAPIOpenAPI/JSONAPI/JSONAPITypes+OpenAPI.swift @@ -6,68 +6,69 @@ // import JSONAPI +import Foundation import AnyCodable private protocol _Optional {} extension Optional: _Optional {} extension Attribute: OpenAPINodeType where RawValue: OpenAPINodeType { - static public func openAPINode() throws -> JSONNode { + static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { // If the RawValue is not required, we actually consider it // nullable. To be not required is for the Attribute itself // to be optional. - if try !RawValue.openAPINode().required { - return try RawValue.openAPINode().requiredNode().nullableNode() + if try !RawValue.openAPINode(using: encoder).required { + return try RawValue.openAPINode(using: encoder).requiredNode().nullableNode() } - return try RawValue.openAPINode() + return try RawValue.openAPINode(using: encoder) } } extension Attribute: RawOpenAPINodeType where RawValue: RawRepresentable, RawValue.RawValue: OpenAPINodeType { - static public func rawOpenAPINode() throws -> JSONNode { + static public func rawOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode { // If the RawValue is not required, we actually consider it // nullable. To be not required is for the Attribute itself // to be optional. - if try !RawValue.RawValue.openAPINode().required { - return try RawValue.RawValue.openAPINode().requiredNode().nullableNode() + if try !RawValue.RawValue.openAPINode(using: encoder).required { + return try RawValue.RawValue.openAPINode(using: encoder).requiredNode().nullableNode() } - return try RawValue.RawValue.openAPINode() + return try RawValue.RawValue.openAPINode(using: encoder) } } extension Attribute: WrappedRawOpenAPIType where RawValue: RawOpenAPINodeType { - public static func wrappedOpenAPINode() throws -> JSONNode { + public static func wrappedOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode { // If the RawValue is not required, we actually consider it // nullable. To be not required is for the Attribute itself // to be optional. - if try !RawValue.rawOpenAPINode().required { - return try RawValue.rawOpenAPINode().requiredNode().nullableNode() + if try !RawValue.rawOpenAPINode(using: encoder).required { + return try RawValue.rawOpenAPINode(using: encoder).requiredNode().nullableNode() } - return try RawValue.rawOpenAPINode() + return try RawValue.rawOpenAPINode(using: encoder) } } extension Attribute: AnyJSONCaseIterable where RawValue: CaseIterable, RawValue: Codable { - public static var allCases: [AnyCodable] { - return (try? allCases(from: Array(RawValue.allCases))) ?? [] + public static func allCases(using encoder: JSONEncoder) -> [AnyCodable] { + return (try? allCases(from: Array(RawValue.allCases), using: encoder)) ?? [] } } extension Attribute: AnyWrappedJSONCaseIterable where RawValue: AnyJSONCaseIterable { - public static var allCases: [AnyCodable] { - return RawValue.allCases + public static func allCases(using encoder: JSONEncoder) -> [AnyCodable] { + return RawValue.allCases(using: encoder) } } extension TransformedAttribute: OpenAPINodeType where RawValue: OpenAPINodeType { - static public func openAPINode() throws -> JSONNode { + static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { // If the RawValue is not required, we actually consider it // nullable. To be not required is for the Attribute itself // to be optional. - if try !RawValue.openAPINode().required { - return try RawValue.openAPINode().requiredNode().nullableNode() + if try !RawValue.openAPINode(using: encoder).required { + return try RawValue.openAPINode(using: encoder).requiredNode().nullableNode() } - return try RawValue.openAPINode() + return try RawValue.openAPINode(using: encoder) } } @@ -95,7 +96,7 @@ extension ToOneRelationship: OpenAPINodeType { // Will use "enum" with one possible value for now. // TODO: metadata & links - static public func openAPINode() throws -> JSONNode { + static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { let nullable = Identifiable.self is _Optional.Type return .object(.init(format: .generic, required: true), @@ -110,7 +111,7 @@ extension ToManyRelationship: OpenAPINodeType { // Will use "enum" with one possible value for now. // TODO: metadata & links - static public func openAPINode() throws -> JSONNode { + static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { return .object(.init(format: .generic, required: true), .init(properties: [ @@ -122,7 +123,7 @@ extension ToManyRelationship: OpenAPINodeType { } extension Entity: OpenAPINodeType where Description.Attributes: Sampleable, Description.Relationships: Sampleable { - public static func openAPINode() throws -> JSONNode { + public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { // NOTE: const for json `type` not supported by OpenAPI 3.0 // Will use "enum" with one possible value for now. @@ -141,13 +142,13 @@ extension Entity: OpenAPINodeType where Description.Attributes: Sampleable, Desc let attributesNode: JSONNode? = Description.Attributes.self == NoAttributes.self ? nil - : try Description.Attributes.genericObjectOpenAPINode() + : try Description.Attributes.genericObjectOpenAPINode(using: encoder) let attributesProperty = attributesNode.map { ("attributes", $0) } let relationshipsNode: JSONNode? = Description.Relationships.self == NoRelationships.self ? nil - : try Description.Relationships.genericObjectOpenAPINode() + : try Description.Relationships.genericObjectOpenAPINode(using: encoder) let relationshipsProperty = relationshipsNode.map { ("relationships", $0) } @@ -165,31 +166,31 @@ extension Entity: OpenAPINodeType where Description.Attributes: Sampleable, Desc } extension SingleResourceBody: OpenAPINodeType where Entity: OpenAPINodeType { - public static func openAPINode() throws -> JSONNode { - return try Entity.openAPINode() + public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { + return try Entity.openAPINode(using: encoder) } } extension ManyResourceBody: OpenAPINodeType where Entity: OpenAPINodeType { - public static func openAPINode() throws -> JSONNode { + public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { return .array(.init(format: .generic, required: true), - .init(items: try Entity.openAPINode())) + .init(items: try Entity.openAPINode(using: encoder))) } } extension Document: OpenAPINodeType where PrimaryResourceBody: OpenAPINodeType, IncludeType: OpenAPINodeType { - public static func openAPINode() throws -> JSONNode { + public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { // TODO: metadata, links, api description, errors // TODO: represent data and errors as the two distinct possible outcomes - let primaryDataNode: JSONNode? = try PrimaryResourceBody.openAPINode() + let primaryDataNode: JSONNode? = try PrimaryResourceBody.openAPINode(using: encoder) let primaryDataProperty = primaryDataNode.map { ("data", $0) } let includeNode: JSONNode? do { - includeNode = try Includes.openAPINode() + includeNode = try Includes.openAPINode(using: encoder) } catch let err as OpenAPITypeError { guard err == .invalidNode else { throw err diff --git a/Sources/JSONAPIOpenAPI/OpenAPI/OpenAPITypes.swift b/Sources/JSONAPIOpenAPI/OpenAPI/OpenAPITypes.swift index c5ac131..d477ae6 100644 --- a/Sources/JSONAPIOpenAPI/OpenAPI/OpenAPITypes.swift +++ b/Sources/JSONAPIOpenAPI/OpenAPI/OpenAPITypes.swift @@ -13,12 +13,12 @@ import Foundation /// Anything conforming to `OpenAPINodeType` can provide an /// OpenAPI schema representing itself. public protocol OpenAPINodeType { - static func openAPINode() throws -> JSONNode + static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode } extension OpenAPINodeType where Self: Sampleable, Self: Encodable { - public static func openAPINodeWithExample() throws -> JSONNode { - return try openAPINode().with(example: Self.successSample ?? Self.sample) + public static func openAPINodeWithExample(using encoder: JSONEncoder = JSONEncoder()) throws -> JSONNode { + return try openAPINode(using: encoder).with(example: Self.successSample ?? Self.sample, using: encoder) } } @@ -29,7 +29,7 @@ extension OpenAPINodeType where Self: Sampleable, Self: Encodable { /// different schema. The "different" conditions have to do /// with Raw Representability, hence the name of this protocol. public protocol RawOpenAPINodeType { - static func rawOpenAPINode() throws -> JSONNode + static func rawOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode } /// Anything conforming to `RawOpenAPINodeType` can provide an @@ -39,7 +39,7 @@ public protocol RawOpenAPINodeType { /// different schema. The "different" conditions have to do /// with Optionality, hence the name of this protocol. public protocol WrappedRawOpenAPIType { - static func wrappedOpenAPINode() throws -> JSONNode + static func wrappedOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode } /// Anything conforming to `RawOpenAPINodeType` can provide an @@ -52,18 +52,18 @@ public protocol DoubleWrappedRawOpenAPIType { // NOTE: This is definitely a rabbit hole... hopefully I // will realize I've been missing something obvious // and dig my way back out at some point... - static func wrappedOpenAPINode() throws -> JSONNode + static func wrappedOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode } /// Anything conforming to `AnyJSONCaseIterable` can provide a /// list of its possible values. public protocol AnyJSONCaseIterable { - static var allCases: [AnyCodable] { get } + static func allCases(using encoder: JSONEncoder) -> [AnyCodable] } extension AnyJSONCaseIterable { /// Given an array of Codable values, retrieve an array of AnyCodables. - static func allCases(from input: [T]) throws -> [AnyCodable] { + static func allCases(from input: [T], using encoder: JSONEncoder) throws -> [AnyCodable] { if let alreadyGoodToGo = input as? [AnyCodable] { return alreadyGoodToGo } @@ -76,7 +76,7 @@ extension AnyJSONCaseIterable { // by AnyCodable, but AnyCodable wants it to actually BE a String // upon initialization. - guard let arrayOfCodables = try JSONSerialization.jsonObject(with: JSONEncoder().encode(input), options: []) as? [Any] else { + guard let arrayOfCodables = try JSONSerialization.jsonObject(with: encoder.encode(input), options: []) as? [Any] else { throw OpenAPICodableError.allCasesArrayNotCodable } return arrayOfCodables.map(AnyCodable.init) @@ -91,7 +91,7 @@ extension AnyJSONCaseIterable { /// The "different" conditions have to do /// with Optionality, hence the name of this protocol. public protocol AnyWrappedJSONCaseIterable { - static var allCases: [AnyCodable] { get } + static func allCases(using encoder: JSONEncoder) -> [AnyCodable] } public protocol SwiftTyped { @@ -282,14 +282,15 @@ public enum JSONNode: Equatable { nullable: Bool = false, // constantValue: Format.SwiftType? = nil, allowedValues: [AnyCodable]? = nil, - example: AnyCodable? = nil) { + example: AnyCodable? = nil, + using encoder: JSONEncoder = JSONEncoder()) { self.format = format self.required = required self.nullable = nullable // self.constantValue = constantValue self.allowedValues = allowedValues self.example = example - .flatMap { try? JSONEncoder().encode($0)} + .flatMap { try? encoder.encode($0)} .flatMap { String(data: $0, encoding: .utf8) } } @@ -550,12 +551,13 @@ public enum JSONNode: Equatable { } } - public func with(example codableExample: T) throws -> JSONNode { + public func with(example codableExample: T, + using encoder: JSONEncoder) throws -> JSONNode { let example: AnyCodable if let goodToGo = codableExample as? AnyCodable { example = goodToGo } else { - example = AnyCodable(try JSONSerialization.jsonObject(with: JSONEncoder().encode(codableExample), options: [])) + example = AnyCodable(try JSONSerialization.jsonObject(with: encoder.encode(codableExample), options: [])) } switch self { diff --git a/Sources/JSONAPIOpenAPI/OpenAPI/SwiftPrimitiveTypes+OpenAPI.swift b/Sources/JSONAPIOpenAPI/OpenAPI/SwiftPrimitiveTypes+OpenAPI.swift index ae03f6a..90a6be9 100644 --- a/Sources/JSONAPIOpenAPI/OpenAPI/SwiftPrimitiveTypes+OpenAPI.swift +++ b/Sources/JSONAPIOpenAPI/OpenAPI/SwiftPrimitiveTypes+OpenAPI.swift @@ -6,6 +6,7 @@ // import AnyCodable +import Foundation /** @@ -32,37 +33,37 @@ Any object: **/ extension Optional: OpenAPINodeType where Wrapped: OpenAPINodeType { - static public func openAPINode() throws -> JSONNode { - return try Wrapped.openAPINode().optionalNode() + static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { + return try Wrapped.openAPINode(using: encoder).optionalNode() } } extension Optional: RawOpenAPINodeType where Wrapped: RawRepresentable, Wrapped.RawValue: OpenAPINodeType { - static public func rawOpenAPINode() throws -> JSONNode { - return try Wrapped.RawValue.openAPINode().optionalNode() + static public func rawOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode { + return try Wrapped.RawValue.openAPINode(using: encoder).optionalNode() } } extension Optional: WrappedRawOpenAPIType where Wrapped: RawOpenAPINodeType { - static public func wrappedOpenAPINode() throws -> JSONNode { - return try Wrapped.rawOpenAPINode().optionalNode() + static public func wrappedOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode { + return try Wrapped.rawOpenAPINode(using: encoder).optionalNode() } } extension Optional: DoubleWrappedRawOpenAPIType where Wrapped: WrappedRawOpenAPIType { - static public func wrappedOpenAPINode() throws -> JSONNode { - return try Wrapped.wrappedOpenAPINode().optionalNode() + static public func wrappedOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode { + return try Wrapped.wrappedOpenAPINode(using: encoder).optionalNode() } } extension Optional: AnyJSONCaseIterable where Wrapped: CaseIterable, Wrapped: Codable { - public static var allCases: [AnyCodable] { - return (try? allCases(from: Array(Wrapped.allCases))) ?? [] + public static func allCases(using encoder: JSONEncoder) -> [AnyCodable] { + return (try? allCases(from: Array(Wrapped.allCases), using: encoder)) ?? [] } } extension String: OpenAPINodeType { - static public func openAPINode() throws -> JSONNode { + static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { return .string(.init(format: .generic, required: true), .init()) @@ -70,22 +71,22 @@ extension String: OpenAPINodeType { } extension Bool: OpenAPINodeType { - static public func openAPINode() throws -> JSONNode { + static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { return .boolean(.init(format: .generic, required: true)) } } extension Array: OpenAPINodeType where Element: OpenAPINodeType { - static public func openAPINode() throws -> JSONNode { + static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { return .array(.init(format: .generic, required: true), - .init(items: try Element.openAPINode())) + .init(items: try Element.openAPINode(using: encoder))) } } extension Double: OpenAPINodeType { - static public func openAPINode() throws -> JSONNode { + static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { return .number(.init(format: .double, required: true), .init()) @@ -93,7 +94,7 @@ extension Double: OpenAPINodeType { } extension Float: OpenAPINodeType { - static public func openAPINode() throws -> JSONNode { + static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { return .number(.init(format: .float, required: true), .init()) @@ -101,7 +102,7 @@ extension Float: OpenAPINodeType { } extension Int: OpenAPINodeType { - static public func openAPINode() throws -> JSONNode { + static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { return .integer(.init(format: .generic, required: true), .init()) @@ -109,7 +110,7 @@ extension Int: OpenAPINodeType { } extension Int32: OpenAPINodeType { - static public func openAPINode() throws -> JSONNode { + static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { return .integer(.init(format: .int32, required: true), .init()) @@ -117,7 +118,7 @@ extension Int32: OpenAPINodeType { } extension Int64: OpenAPINodeType { - static public func openAPINode() throws -> JSONNode { + static public func openAPINode(using encoder: JSONEncoder) throws -> JSONNode { return .integer(.init(format: .int64, required: true), .init()) diff --git a/Sources/JSONAPIOpenAPI/Sampleable/Sampleable.swift b/Sources/JSONAPIOpenAPI/Sampleable/Sampleable.swift index 0e8fe58..bbf778b 100644 --- a/Sources/JSONAPIOpenAPI/Sampleable/Sampleable.swift +++ b/Sources/JSONAPIOpenAPI/Sampleable/Sampleable.swift @@ -6,6 +6,7 @@ // import JSONAPI +import Foundation import AnyCodable /// A Sampleable type can provide a sample value. @@ -48,7 +49,7 @@ public extension Sampleable { } extension Sampleable { - public static func genericObjectOpenAPINode() throws -> JSONNode { + public static func genericObjectOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode { let mirror = Mirror(reflecting: Self.sample) let properties: [(String, JSONNode)] = try mirror.children.compactMap { child in @@ -56,9 +57,9 @@ extension Sampleable { let maybeAllCases: [AnyCodable]? = { switch type(of: child.value) { case let valType as AnyJSONCaseIterable.Type: - return valType.allCases + return valType.allCases(using: encoder) case let valType as AnyWrappedJSONCaseIterable.Type: - return valType.allCases + return valType.allCases(using: encoder) default: return nil } @@ -68,16 +69,16 @@ extension Sampleable { let maybeOpenAPINode: JSONNode? = try { switch type(of: child.value) { case let valType as OpenAPINodeType.Type: - return try valType.openAPINode() + return try valType.openAPINode(using: encoder) case let valType as RawOpenAPINodeType.Type: - return try valType.rawOpenAPINode() + return try valType.rawOpenAPINode(using: encoder) case let valType as WrappedRawOpenAPIType.Type: - return try valType.wrappedOpenAPINode() + return try valType.wrappedOpenAPINode(using: encoder) case let valType as DoubleWrappedRawOpenAPIType.Type: - return try valType.wrappedOpenAPINode() + return try valType.wrappedOpenAPINode(using: encoder) default: return nil @@ -107,6 +108,61 @@ extension Sampleable { } } +extension Sampleable { + public static func samples(using s1: S1.Type, with constructor: (S1) -> Self) -> [Self] { + return S1.samples.map(constructor) + } + + public static func samples(using s1: S1.Type, _ s2: S2.Type, with constructor: (S1, S2) -> Self) -> [Self] { + return zip(S1.samples, S2.samples).map(constructor) + } + + public static func samples(using s1: S1.Type, _ s2: S2.Type, _ s3: S3.Type, with constructor: (S1, S2, S3) -> Self) -> [Self] { + return zip3(S1.samples, S2.samples, S3.samples).map(constructor) + } + + public static func samples(using s1: S1.Type, _ s2: S2.Type, _ s3: S3.Type, _ s4: S4.Type, with constructor: (S1, S2, S3, S4) -> Self) -> [Self] { + return zip4(S1.samples, S2.samples, S3.samples, S4.samples).map(constructor) + } + + public static func samples(using s1: S1.Type, _ s2: S2.Type, _ s3: S3.Type, _ s4: S4.Type, _ s5: S5.Type, with constructor: (S1, S2, S3, S4, S5) -> Self) -> [Self] { + return zip5(S1.samples, S2.samples, S3.samples, S4.samples, S5.samples).map(constructor) + } + + public static func samples(using s1: S1.Type, _ s2: S2.Type, _ s3: S3.Type, _ s4: S4.Type, _ s5: S5.Type, _ s6: S6.Type, with constructor: (S1, S2, S3, S4, S5, S6) -> Self) -> [Self] { + // the compiler craps out at zip6. breaking it down makes the difference. + let firstZip = zip3(S1.samples, S2.samples, S3.samples) + let secondZip = zip3(S4.samples, S5.samples, S6.samples) + return zip(firstZip, secondZip).map { arg in (arg.0.0, arg.0.1, arg.0.2, arg.1.0, arg.1.1, arg.1.2) }.map(constructor) + } + + public static func samples(using s1: S1.Type, _ s2: S2.Type, _ s3: S3.Type, _ s4: S4.Type, _ s5: S5.Type, _ s6: S6.Type, _ s7: S7.Type, with constructor: (S1, S2, S3, S4, S5, S6, S7) -> Self) -> [Self] { + // the compiler craps out at zip6. breaking it down makes the difference. + let firstZip = zip3(S1.samples, S2.samples, S3.samples) + let secondZip = zip4(S4.samples, S5.samples, S6.samples, S7.samples) + return zip(firstZip, secondZip).map { arg in (arg.0.0, arg.0.1, arg.0.2, arg.1.0, arg.1.1, arg.1.2, arg.1.3) }.map(constructor) + } + + public static func samples(using s1: S1.Type, _ s2: S2.Type, _ s3: S3.Type, _ s4: S4.Type, _ s5: S5.Type, _ s6: S6.Type, _ s7: S7.Type, _ s8: S8.Type, with constructor: (S1, S2, S3, S4, S5, S6, S7, S8) -> Self) -> [Self] { + // the compiler craps out at zip6. breaking it down makes the difference. + let firstZip = zip4(S1.samples, S2.samples, S3.samples, S4.samples) + let secondZip = zip4(S5.samples, S6.samples, S7.samples, S8.samples) + return zip(firstZip, secondZip).map { arg in (arg.0.0, arg.0.1, arg.0.2, arg.0.3, arg.1.0, arg.1.1, arg.1.2, arg.1.3) }.map(constructor) + } + + @inlinable static func zip3(_ a: A, _ b: B, _ c: C) -> [(A.Element, B.Element, C.Element)] { + return zip(a, zip(b, c)).map { arg in (arg.0, arg.1.0, arg.1.1) } + } + + @inlinable static func zip4(_ a: A, _ b: B, _ c: C, _ d: D) -> [(A.Element, B.Element, C.Element, D.Element)] { + return zip(a, zip(b, zip(c, d))).map { arg in (arg.0, arg.1.0, arg.1.1.0, arg.1.1.1) } + } + + @inlinable static func zip5(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E) -> [(A.Element, B.Element, C.Element, D.Element, E.Element)] { + return zip(a, zip(b, zip(c, zip(d, e)))).map { arg in (arg.0, arg.1.0, arg.1.1.0, arg.1.1.1.0, arg.1.1.1.1) } + } +} + extension NoAttributes: Sampleable { public static var sample: NoAttributes { return .none @@ -131,8 +187,26 @@ extension NoLinks: Sampleable { } } +extension NoAPIDescription: Sampleable { + public static var sample: NoAPIDescription { + return .none + } +} + extension UnknownJSONAPIError: Sampleable { public static var sample: UnknownJSONAPIError { return .unknownError } } + +extension SingleResourceBody: Sampleable where Entity: Sampleable { + public static var sample: SingleResourceBody { + return .init(entity: Entity.sample) + } +} + +extension ManyResourceBody: Sampleable where Entity: Sampleable { + public static var sample: ManyResourceBody { + return .init(entities: Entity.samples) + } +} diff --git a/Tests/JSONAPIOpenAPITests/JSONAPIEntityOpenAPITests.swift b/Tests/JSONAPIOpenAPITests/JSONAPIEntityOpenAPITests.swift index abaf9d1..e2289ba 100644 --- a/Tests/JSONAPIOpenAPITests/JSONAPIEntityOpenAPITests.swift +++ b/Tests/JSONAPIOpenAPITests/JSONAPIEntityOpenAPITests.swift @@ -12,7 +12,7 @@ import AnyCodable class JSONAPIEntityOpenAPITests: XCTestCase { func test_EmptyEntity() { - let node = try! TestType1.openAPINode() + let node = try! TestType1.openAPINode(using: JSONEncoder()) XCTAssertTrue(node.required) XCTAssertEqual(node.jsonTypeFormat, .object(.generic)) @@ -40,7 +40,7 @@ class JSONAPIEntityOpenAPITests: XCTestCase { } func test_AttributesEntity() { - let node = try! TestType2.openAPINode() + let node = try! TestType2.openAPINode(using: JSONEncoder()) XCTAssertTrue(node.required) XCTAssertEqual(node.jsonTypeFormat, .object(.generic)) @@ -122,7 +122,7 @@ class JSONAPIEntityOpenAPITests: XCTestCase { } func test_RelationshipsEntity() { - let node = try! TestType3.openAPINode() + let node = try! TestType3.openAPINode(using: JSONEncoder()) XCTAssertTrue(node.required) XCTAssertEqual(node.jsonTypeFormat, .object(.generic))