mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-03-30 11:18:38 -07:00
Add Include support to OpenAPI schema of JSONAPI Document.
This commit is contained in:
@@ -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("====")
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
])
|
||||
}
|
||||
}
|
||||
+16
-3
@@ -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,
|
||||
+23
-11
@@ -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(let nodes):
|
||||
var container = encoder.container(keyedBy: SubschemaCodingKeys.self)
|
||||
|
||||
case .oneOf(let nodes):
|
||||
// TODO
|
||||
print("TODO")
|
||||
try container.encode(nodes, forKey: .allOf)
|
||||
|
||||
case .anyOf(let nodes):
|
||||
// TODO
|
||||
print("TODO")
|
||||
case .one(let nodes):
|
||||
var container = encoder.container(keyedBy: SubschemaCodingKeys.self)
|
||||
|
||||
try container.encode(nodes, forKey: .oneOf)
|
||||
|
||||
case .any(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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-11
@@ -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 {
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user