Skip to content

Commit

Permalink
Merge pull request #13 from mattpolzin/feature/OpenAPISchema
Browse files Browse the repository at this point in the history
Add Include support to OpenAPI schema of JSONAPI Document.
  • Loading branch information
mattpolzin authored Jan 22, 2019
2 parents 6dd14da + e3c637a commit 850a713
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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("====")
19 changes: 17 additions & 2 deletions JSONAPI.playground/Sources/OpenAPISupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,28 @@ import JSONAPIOpenAPI
import SwiftCheck
import JSONAPIArbitrary

extension PersonDescription.Attributes: Sampleable {
extension PersonDescription.Attributes: Arbitrary, Sampleable {
public static var arbitrary: Gen<PersonDescription.Attributes> {
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<PersonDescription.Relationships> {
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")
}
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
131 changes: 131 additions & 0 deletions Sources/JSONAPIOpenAPI/JSONAPI/JSONAPIInclude+OpenAPI.swift
Original file line number Diff line number Diff line change
@@ -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()
])
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Include>.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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,9 @@ public enum JSONNode: Equatable {
case number(Context<JSONTypeFormat.NumberFormat>, NumericContext)
case integer(Context<JSONTypeFormat.IntegerFormat>, NumericContext)
case string(Context<JSONTypeFormat.StringFormat>, 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<Format: OpenAPIFormat>: JSONNodeContext, Equatable {
Expand Down Expand Up @@ -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
}
}
Expand All @@ -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
}
}
Expand All @@ -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
}
}
Expand All @@ -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
}
}
Expand All @@ -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
}
}
Expand All @@ -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
}
}
Expand All @@ -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
}

0 comments on commit 850a713

Please sign in to comment.