diff --git a/JSONAPI.playground/Pages/OpenAPI Documentation.xcplaygroundpage/Contents.swift b/JSONAPI.playground/Pages/OpenAPI Documentation.xcplaygroundpage/Contents.swift index e9c018a..01cff69 100644 --- a/JSONAPI.playground/Pages/OpenAPI Documentation.xcplaygroundpage/Contents.swift +++ b/JSONAPI.playground/Pages/OpenAPI Documentation.xcplaygroundpage/Contents.swift @@ -21,3 +21,10 @@ print("Dog Document Schema") print("====") print(dogDocumentSchemaData.map { String(data: $0, encoding: .utf8)! } ?? "Schema Construction Failed") print("====") + +let batchPersonSchemaData = try? encoder.encode(BatchPeopleDocument.openAPINodeWithExample()) + +print("Batch Person Document Schema") +print("====") +print(batchPersonSchemaData.map { String(data: $0, encoding: .utf8)! } ?? "Schema Construction Failed") +print("====") diff --git a/JSONAPI.playground/Sources/OpenAPISupport.swift b/JSONAPI.playground/Sources/OpenAPISupport.swift index ac267dd..ff2471a 100644 --- a/JSONAPI.playground/Sources/OpenAPISupport.swift +++ b/JSONAPI.playground/Sources/OpenAPISupport.swift @@ -5,13 +5,28 @@ import JSONAPIOpenAPI import SwiftCheck import JSONAPIArbitrary -extension PersonDescription.Attributes: Sampleable { +extension PersonDescription.Attributes: Arbitrary, Sampleable { + public static var arbitrary: Gen { + return Gen.compose { c in + return PersonDescription.Attributes(name: c.generate(), + favoriteColor: c.generate()) + } + } + public static var sample: PersonDescription.Attributes { return .init(name: ["Abbie", "Eibba"], favoriteColor: "Blue") } } -extension PersonDescription.Relationships: Sampleable { +extension PersonDescription.Relationships: Arbitrary, Sampleable { + public static var arbitrary: Gen { + return Gen.compose { c in + return PersonDescription.Relationships(friends: c.generate(), + dogs: c.generate(), + home: c.generate()) + } + } + public static var sample: PersonDescription.Relationships { return .init(friends: ["1", "2"], dogs: ["2"], home: "1") } diff --git a/README.md b/README.md index 6ffe3e1..a550311 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ Note that Playground support for importing non-system Frameworks is still a bit - `included` - [x] Encoding/Decoding - [x] Arbitrary - - [ ] OpenAPI + - [x] OpenAPI - `errors` - [x] Encoding/Decoding - [x] Arbitrary diff --git a/Sources/JSONAPIOpenAPI/JSONAPI/JSONAPIInclude+OpenAPI.swift b/Sources/JSONAPIOpenAPI/JSONAPI/JSONAPIInclude+OpenAPI.swift new file mode 100644 index 0000000..9b9e456 --- /dev/null +++ b/Sources/JSONAPIOpenAPI/JSONAPI/JSONAPIInclude+OpenAPI.swift @@ -0,0 +1,131 @@ +// +// JSONAPIInclude+OpenAPI.swift +// JSONAPIOpenAPI +// +// Created by Mathew Polzin on 1/22/19. +// + +import JSONAPI + +extension Includes: OpenAPINodeType where I: OpenAPINodeType { + public static func openAPINode() throws -> JSONNode { + let includeNode = try I.openAPINode() + + return .array(.init(format: .generic, + required: true), + .init(items: includeNode, + uniqueItems: true)) + } +} + +extension Include0: OpenAPINodeType { + public static func openAPINode() throws -> JSONNode { + throw OpenAPITypeError.invalidNode + } +} + +extension Include1: OpenAPINodeType where A: OpenAPINodeType { + public static func openAPINode() throws -> JSONNode { + return try .one(of: [A.openAPINode()]) + } +} + +extension Include2: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType { + public static func openAPINode() throws -> JSONNode { + return try .one(of: [ + A.openAPINode(), + B.openAPINode() + ]) + } +} + +extension Include3: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType { + public static func openAPINode() throws -> JSONNode { + return try .one(of: [ + A.openAPINode(), + B.openAPINode(), + C.openAPINode() + ]) + } +} + +extension Include4: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType { + public static func openAPINode() throws -> JSONNode { + return try .one(of: [ + A.openAPINode(), + B.openAPINode(), + C.openAPINode(), + D.openAPINode() + ]) + } +} + +extension Include5: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType, E: OpenAPINodeType { + public static func openAPINode() throws -> JSONNode { + return try .one(of: [ + A.openAPINode(), + B.openAPINode(), + C.openAPINode(), + D.openAPINode(), + E.openAPINode() + ]) + } +} + +extension Include6: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType, E: OpenAPINodeType, F: OpenAPINodeType { + public static func openAPINode() throws -> JSONNode { + return try .one(of: [ + A.openAPINode(), + B.openAPINode(), + C.openAPINode(), + D.openAPINode(), + E.openAPINode(), + F.openAPINode() + ]) + } +} + +extension Include7: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType, E: OpenAPINodeType, F: OpenAPINodeType, G: OpenAPINodeType { + public static func openAPINode() throws -> JSONNode { + return try .one(of: [ + A.openAPINode(), + B.openAPINode(), + C.openAPINode(), + D.openAPINode(), + E.openAPINode(), + F.openAPINode(), + G.openAPINode() + ]) + } +} + +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 { + return try .one(of: [ + A.openAPINode(), + B.openAPINode(), + C.openAPINode(), + D.openAPINode(), + E.openAPINode(), + F.openAPINode(), + G.openAPINode(), + H.openAPINode() + ]) + } +} + +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 { + return try .one(of: [ + A.openAPINode(), + B.openAPINode(), + C.openAPINode(), + D.openAPINode(), + E.openAPINode(), + F.openAPINode(), + G.openAPINode(), + H.openAPINode(), + I.openAPINode() + ]) + } +} diff --git a/Sources/JSONAPIOpenAPI/JSONAPITypes+OpenAPI.swift b/Sources/JSONAPIOpenAPI/JSONAPI/JSONAPITypes+OpenAPI.swift similarity index 93% rename from Sources/JSONAPIOpenAPI/JSONAPITypes+OpenAPI.swift rename to Sources/JSONAPIOpenAPI/JSONAPI/JSONAPITypes+OpenAPI.swift index 619e296..cc5efde 100644 --- a/Sources/JSONAPIOpenAPI/JSONAPITypes+OpenAPI.swift +++ b/Sources/JSONAPIOpenAPI/JSONAPI/JSONAPITypes+OpenAPI.swift @@ -178,17 +178,30 @@ extension ManyResourceBody: OpenAPINodeType where Entity: OpenAPINodeType { } } -extension Document: OpenAPINodeType where PrimaryResourceBody: OpenAPINodeType { +extension Document: OpenAPINodeType where PrimaryResourceBody: OpenAPINodeType, IncludeType: OpenAPINodeType { public static func openAPINode() throws -> JSONNode { - // TODO: metadata, links, api description, includes, errors + // TODO: metadata, links, api description, errors // TODO: represent data and errors as the two distinct possible outcomes let primaryDataNode: JSONNode? = try PrimaryResourceBody.openAPINode() let primaryDataProperty = primaryDataNode.map { ("data", $0) } + let includeNode: JSONNode? + do { + includeNode = try Includes.openAPINode() + } catch let err as OpenAPITypeError { + guard err == .invalidNode else { + throw err + } + includeNode = nil + } + + let includeProperty = includeNode.map { ("included", $0) } + let propertiesDict = Dictionary([ - primaryDataProperty + primaryDataProperty, + includeProperty ].compactMap { $0 }) { _, value in value } return .object(.init(format: .generic, diff --git a/Sources/JSONAPIOpenAPI/OpenAPITypes+Codable.swift b/Sources/JSONAPIOpenAPI/OpenAPI/OpenAPITypes+Codable.swift similarity index 85% rename from Sources/JSONAPIOpenAPI/OpenAPITypes+Codable.swift rename to Sources/JSONAPIOpenAPI/OpenAPI/OpenAPITypes+Codable.swift index fa6d93b..9322035 100644 --- a/Sources/JSONAPIOpenAPI/OpenAPITypes+Codable.swift +++ b/Sources/JSONAPIOpenAPI/OpenAPI/OpenAPITypes+Codable.swift @@ -149,6 +149,14 @@ extension JSONNode.ObjectContext : Encodable { } extension JSONNode: Encodable { + + private enum SubschemaCodingKeys: String, CodingKey { + case allOf + case oneOf + case anyOf + case not + } + public func encode(to encoder: Encoder) throws { switch self { case .boolean(let context): @@ -162,21 +170,25 @@ extension JSONNode: Encodable { try contextA.encode(to: encoder) try contextB.encode(to: encoder) - case .allOf(let nodes): - // TODO - print("TODO") + case .all(of: let nodes): + var container = encoder.container(keyedBy: SubschemaCodingKeys.self) + + try container.encode(nodes, forKey: .allOf) - case .oneOf(let nodes): - // TODO - print("TODO") + case .one(of: let nodes): + var container = encoder.container(keyedBy: SubschemaCodingKeys.self) - case .anyOf(let nodes): - // TODO - print("TODO") + try container.encode(nodes, forKey: .oneOf) + + case .any(of: let nodes): + var container = encoder.container(keyedBy: SubschemaCodingKeys.self) + + try container.encode(nodes, forKey: .anyOf) case .not(let node): - // TODO - print("TODO") + var container = encoder.container(keyedBy: SubschemaCodingKeys.self) + + try container.encode(node, forKey: .not) } } } diff --git a/Sources/JSONAPIOpenAPI/OpenAPITypes.swift b/Sources/JSONAPIOpenAPI/OpenAPI/OpenAPITypes.swift similarity index 97% rename from Sources/JSONAPIOpenAPI/OpenAPITypes.swift rename to Sources/JSONAPIOpenAPI/OpenAPI/OpenAPITypes.swift index 2a65d9e..c5ac131 100644 --- a/Sources/JSONAPIOpenAPI/OpenAPITypes.swift +++ b/Sources/JSONAPIOpenAPI/OpenAPI/OpenAPITypes.swift @@ -245,9 +245,9 @@ public enum JSONNode: Equatable { case number(Context, NumericContext) case integer(Context, NumericContext) case string(Context, StringContext) - indirect case allOf([JSONNode]) - indirect case oneOf([JSONNode]) - indirect case anyOf([JSONNode]) + indirect case all(of: [JSONNode]) + indirect case one(of: [JSONNode]) + indirect case any(of: [JSONNode]) indirect case not(JSONNode) public struct Context: JSONNodeContext, Equatable { @@ -451,7 +451,7 @@ public enum JSONNode: Equatable { return .integer(context.format) case .string(let context, _): return .string(context.format) - case .allOf, .oneOf, .anyOf, .not: + case .all, .one, .any, .not: return nil } } @@ -465,7 +465,7 @@ public enum JSONNode: Equatable { .integer(let contextA as JSONNodeContext, _), .string(let contextA as JSONNodeContext, _): return contextA.required - case .allOf, .oneOf, .anyOf, .not: + case .all, .one, .any, .not: return true } } @@ -485,7 +485,7 @@ public enum JSONNode: Equatable { return .integer(context.optionalContext(), contextB) case .string(let context, let contextB): return .string(context.optionalContext(), contextB) - case .allOf, .oneOf, .anyOf, .not: + case .all, .one, .any, .not: return self } } @@ -505,7 +505,7 @@ public enum JSONNode: Equatable { return .integer(context.requiredContext(), contextB) case .string(let context, let contextB): return .string(context.requiredContext(), contextB) - case .allOf, .oneOf, .anyOf, .not: + case .all, .one, .any, .not: return self } } @@ -525,7 +525,7 @@ public enum JSONNode: Equatable { return .integer(context.nullableContext(), contextB) case .string(let context, let contextB): return .string(context.nullableContext(), contextB) - case .allOf, .oneOf, .anyOf, .not: + case .all, .one, .any, .not: return self } } @@ -545,7 +545,7 @@ public enum JSONNode: Equatable { 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: + case .all, .one, .any, .not: return self } } @@ -571,13 +571,17 @@ public enum JSONNode: Equatable { return .integer(context.with(example: example), contextB) case .string(let context, let contextB): return .string(context.with(example: example), contextB) - case .allOf, .oneOf, .anyOf, .not: + case .all, .one, .any, .not: return self } } } -public enum OpenAPICodableError: Swift.Error { +public enum OpenAPICodableError: Swift.Error, Equatable { case allCasesArrayNotCodable case exampleNotCodable } + +public enum OpenAPITypeError: Swift.Error, Equatable { + case invalidNode +} diff --git a/Sources/JSONAPIOpenAPI/SwiftPrimitiveTypes+OpenAPI.swift b/Sources/JSONAPIOpenAPI/OpenAPI/SwiftPrimitiveTypes+OpenAPI.swift similarity index 100% rename from Sources/JSONAPIOpenAPI/SwiftPrimitiveTypes+OpenAPI.swift rename to Sources/JSONAPIOpenAPI/OpenAPI/SwiftPrimitiveTypes+OpenAPI.swift