Compare commits

...

4 Commits

6 changed files with 475 additions and 140 deletions
+1 -1
View File
@@ -843,4 +843,4 @@ This Framework is currently undocumented, but familiarity with `SwiftCheck` will
# JSONAPI+OpenAPI
The `JSONAPIOpenAPI` framework adds the ability to generate OpenAPI compliant JSON documentation of a JSONAPI Document.
This library is in its infancy. The documentation will grow as the framework becomes more complete.
*This library is in its infancy. The documentation will grow as the framework becomes more complete.*
@@ -8,9 +8,9 @@
import JSONAPI
import Foundation
extension Includes: OpenAPINodeType where I: OpenAPINodeType {
public static func openAPINode() throws -> JSONNode {
let includeNode = try I.openAPINode()
extension Includes: OpenAPIEncodedNodeType where I: OpenAPIEncodedNodeType {
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
let includeNode = try I.openAPINode(using: encoder)
return .array(.init(format: .generic,
required: true),
@@ -19,114 +19,114 @@ extension Includes: OpenAPINodeType where I: OpenAPINodeType {
}
}
extension Include0: OpenAPINodeType {
public static func openAPINode() throws -> JSONNode {
extension Include0: OpenAPIEncodedNodeType {
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()])
extension Include1: OpenAPIEncodedNodeType where A: OpenAPIEncodedNodeType {
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
return try A.openAPINode(using: encoder)
}
}
extension Include2: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType {
public static func openAPINode() throws -> JSONNode {
extension Include2: OpenAPIEncodedNodeType where A: OpenAPIEncodedNodeType, B: OpenAPIEncodedNodeType {
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 {
extension Include3: OpenAPIEncodedNodeType where A: OpenAPIEncodedNodeType, B: OpenAPIEncodedNodeType, C: OpenAPIEncodedNodeType {
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 {
extension Include4: OpenAPIEncodedNodeType where A: OpenAPIEncodedNodeType, B: OpenAPIEncodedNodeType, C: OpenAPIEncodedNodeType, D: OpenAPIEncodedNodeType {
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 {
extension Include5: OpenAPIEncodedNodeType where A: OpenAPIEncodedNodeType, B: OpenAPIEncodedNodeType, C: OpenAPIEncodedNodeType, D: OpenAPIEncodedNodeType, E: OpenAPIEncodedNodeType {
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 {
extension Include6: OpenAPIEncodedNodeType where A: OpenAPIEncodedNodeType, B: OpenAPIEncodedNodeType, C: OpenAPIEncodedNodeType, D: OpenAPIEncodedNodeType, E: OpenAPIEncodedNodeType, F: OpenAPIEncodedNodeType {
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 {
extension Include7: OpenAPIEncodedNodeType where A: OpenAPIEncodedNodeType, B: OpenAPIEncodedNodeType, C: OpenAPIEncodedNodeType, D: OpenAPIEncodedNodeType, E: OpenAPIEncodedNodeType, F: OpenAPIEncodedNodeType, G: OpenAPIEncodedNodeType {
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 {
extension Include8: OpenAPIEncodedNodeType where A: OpenAPIEncodedNodeType, B: OpenAPIEncodedNodeType, C: OpenAPIEncodedNodeType, D: OpenAPIEncodedNodeType, E: OpenAPIEncodedNodeType, F: OpenAPIEncodedNodeType, G: OpenAPIEncodedNodeType, H: OpenAPIEncodedNodeType {
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 {
extension Include9: OpenAPIEncodedNodeType where A: OpenAPIEncodedNodeType, B: OpenAPIEncodedNodeType, C: OpenAPIEncodedNodeType, D: OpenAPIEncodedNodeType, E: OpenAPIEncodedNodeType, F: OpenAPIEncodedNodeType, G: OpenAPIEncodedNodeType, H: OpenAPIEncodedNodeType, I: OpenAPIEncodedNodeType {
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)
])
}
}
@@ -53,6 +53,31 @@ extension Attribute: WrappedRawOpenAPIType where RawValue: RawOpenAPINodeType {
}
}
extension Attribute: GenericOpenAPINodeType where RawValue: GenericOpenAPINodeType {
public static func genericOpenAPINode(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.genericOpenAPINode(using: encoder).required {
return try RawValue.genericOpenAPINode(using: encoder).requiredNode().nullableNode()
}
return try RawValue.genericOpenAPINode(using: encoder)
}
}
extension Attribute: DateOpenAPINodeType where RawValue: DateOpenAPINodeType {
public static func dateOpenAPINodeGuess(using encoder: JSONEncoder) -> 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
!(RawValue.dateOpenAPINodeGuess(using: encoder)?.required ?? true) {
return RawValue.dateOpenAPINodeGuess(using: encoder)?.requiredNode().nullableNode()
}
return RawValue.dateOpenAPINodeGuess(using: encoder)
}
}
extension Attribute: AnyJSONCaseIterable where RawValue: CaseIterable, RawValue: Codable {
public static func allCases(using encoder: JSONEncoder) -> [AnyCodable] {
return (try? allCases(from: Array(RawValue.allCases), using: encoder)) ?? []
@@ -65,18 +90,6 @@ extension Attribute: AnyWrappedJSONCaseIterable where RawValue: AnyJSONCaseItera
}
}
extension Attribute: GenericOpenAPINodeType where RawValue: GenericOpenAPINodeType {
public static func genericOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode {
return try RawValue.genericOpenAPINode(using: encoder)
}
}
extension Attribute: DateOpenAPINodeType where RawValue: DateOpenAPINodeType {
public static func dateOpenAPINodeGuess(using encoder: JSONEncoder) -> JSONNode? {
return RawValue.dateOpenAPINodeGuess(using: encoder)
}
}
extension TransformedAttribute: OpenAPINodeType where RawValue: OpenAPINodeType {
static public func openAPINode() throws -> JSONNode {
// If the RawValue is not required, we actually consider it
@@ -200,7 +213,7 @@ extension ManyResourceBody: OpenAPIEncodedNodeType where Entity: OpenAPIEncodedN
}
}
extension Document: OpenAPIEncodedNodeType where PrimaryResourceBody: OpenAPIEncodedNodeType, IncludeType: OpenAPINodeType {
extension Document: OpenAPIEncodedNodeType where PrimaryResourceBody: OpenAPIEncodedNodeType, IncludeType: OpenAPIEncodedNodeType {
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
@@ -211,7 +224,7 @@ extension Document: OpenAPIEncodedNodeType where PrimaryResourceBody: OpenAPIEnc
let includeNode: JSONNode?
do {
includeNode = try Includes<Include>.openAPINode()
includeNode = try Includes<Include>.openAPINode(using: encoder)
} catch let err as OpenAPITypeError {
guard case .invalidNode = err else {
throw err
@@ -62,6 +62,12 @@ extension Optional: AnyJSONCaseIterable where Wrapped: CaseIterable, Wrapped: Co
}
}
extension Optional: DateOpenAPINodeType where Wrapped: DateOpenAPINodeType {
static public func dateOpenAPINodeGuess(using encoder: JSONEncoder) -> JSONNode? {
return Wrapped.dateOpenAPINodeGuess(using: encoder)?.optionalNode()
}
}
extension String: OpenAPINodeType {
static public func openAPINode() throws -> JSONNode {
return .string(.init(format: .generic,
@@ -802,62 +802,80 @@ extension JSONAPIAttributeOpenAPITests {
XCTAssertEqual(numberContext, .init())
}
// func test_NullableEnumAttribute() {
// let node = try! Attribute<EnumAttribute?>.wrappedOpenAPINode()
//
// XCTAssertTrue(node.required)
// XCTAssertEqual(node.jsonTypeFormat, .string(.generic))
//
// guard case .string(let contextA, let stringContext) = node else {
// XCTFail("Expected string Node")
// return
// }
//
// XCTAssertEqual(contextA, .init(format: .generic,
// required: true,
// nullable: true,
// allowedValues: nil))
//
// XCTAssertEqual(stringContext, .init())
// }
//
// func test_OptionalEnumAttribute() {
// let node = try! Attribute<EnumAttribute>?.wrappedOpenAPINode()
//
// XCTAssertFalse(node.required)
// XCTAssertEqual(node.jsonTypeFormat, .string(.generic))
//
// guard case .string(let contextA, let stringContext) = node else {
// XCTFail("Expected string Node")
// return
// }
//
// XCTAssertEqual(contextA, .init(format: .generic,
// required: false,
// nullable: false,
// allowedValues: nil))
//
// XCTAssertEqual(stringContext, .init())
// }
//
// func test_OptionalNullableEnumAttribute() {
// let node = try! Attribute<EnumAttribute?>?.wrappedOpenAPINode()
//
// XCTAssertFalse(node.required)
// XCTAssertEqual(node.jsonTypeFormat, .string(.generic))
//
// guard case .string(let contextA, let stringContext) = node else {
// XCTFail("Expected string Node")
// return
// }
//
// XCTAssertEqual(contextA, .init(format: .generic,
// required: false,
// nullable: true,
// allowedValues: nil))
//
// XCTAssertEqual(stringContext, .init())
// }
func test_NullableDateAttribute() {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.dateEncodingStrategy = .secondsSince1970
let node = Attribute<Date?>.dateOpenAPINodeGuess(using: encoder)
XCTAssertNotNil(node)
XCTAssertTrue(node?.required ?? false)
XCTAssertEqual(node?.jsonTypeFormat, .number(.double))
guard case .number(let contextA, let numberContext)? = node else {
XCTFail("Expected string Node")
return
}
XCTAssertEqual(contextA, .init(format: .double,
required: true,
nullable: true,
allowedValues: nil))
XCTAssertEqual(numberContext, .init())
}
func test_OptionalDateAttribute() {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.dateEncodingStrategy = .secondsSince1970
let node = Attribute<Date>?.dateOpenAPINodeGuess(using: encoder)
XCTAssertNotNil(node)
XCTAssertFalse(node?.required ?? true)
XCTAssertEqual(node?.jsonTypeFormat, .number(.double))
guard case .number(let contextA, let numberContext)? = node else {
XCTFail("Expected string Node")
return
}
XCTAssertEqual(contextA, .init(format: .double,
required: false,
nullable: false,
allowedValues: nil))
XCTAssertEqual(numberContext, .init())
}
func test_OptionalNullableDateAttribute() {
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.dateEncodingStrategy = .secondsSince1970
let node = Attribute<Date?>?.dateOpenAPINodeGuess(using: encoder)
XCTAssertNotNil(node)
XCTAssertFalse(node?.required ?? true)
XCTAssertEqual(node?.jsonTypeFormat, .number(.double))
guard case .number(let contextA, let numberContext)? = node else {
XCTFail("Expected string Node")
return
}
XCTAssertEqual(contextA, .init(format: .double,
required: false,
nullable: true,
allowedValues: nil))
XCTAssertEqual(numberContext, .init())
}
}
// MARK: - Test Types
@@ -24,7 +24,289 @@ class JSONAPIDocumentOpenAPITests: XCTestCase {
let node = try! SingleEntityDocument.openAPINodeWithExample(using: encoder)
print(String(data: try! encoder.encode(node), encoding: .utf8)!)
XCTAssertTrue(node.required)
XCTAssertEqual(node.jsonTypeFormat, .object(.generic))
guard case let .object(contextA, objectContext1) = node else {
XCTFail("Expected JSON Document to be an Object Node")
return
}
XCTAssertNotNil(contextA.example)
XCTAssertFalse(contextA.nullable)
XCTAssertEqual(contextA.format, .generic)
XCTAssertTrue(contextA.required)
XCTAssertEqual(objectContext1.minProperties, 1)
XCTAssertEqual(Set(objectContext1.requiredProperties), Set(["data"]))
XCTAssertEqual(Set(objectContext1.properties.keys), Set(["data"]))
guard case let .object(contextB, objectContext2)? = objectContext1.properties["data"] else {
XCTFail("Expected Data field of JSON Document to be an Object Node")
return
}
XCTAssertFalse(contextB.nullable)
XCTAssertEqual(contextB.format, .generic)
XCTAssertTrue(contextB.required)
XCTAssertEqual(objectContext2.minProperties, 3)
XCTAssertEqual(Set(objectContext2.requiredProperties), Set(["id", "attributes", "type"]))
XCTAssertEqual(Set(objectContext2.properties.keys), Set(["id", "attributes", "type"]))
XCTAssertEqual(objectContext2.properties["type"],
JSONNode.string(.init(format: .generic,
required: true,
allowedValues: [.init("test")]),
.init()))
}
func test_ManyResourceDocument() {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
dateFormatter.locale = Locale(identifier: "en_US")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.dateEncodingStrategy = .formatted(dateFormatter)
let node = try! ManyEntityDocument.openAPINodeWithExample(using: encoder)
XCTAssertTrue(node.required)
XCTAssertEqual(node.jsonTypeFormat, .object(.generic))
guard case let .object(contextA, objectContext1) = node else {
XCTFail("Expected JSON Document to be an Object Node")
return
}
XCTAssertNotNil(contextA.example)
XCTAssertFalse(contextA.nullable)
XCTAssertEqual(contextA.format, .generic)
XCTAssertTrue(contextA.required)
XCTAssertEqual(objectContext1.minProperties, 1)
XCTAssertEqual(Set(objectContext1.requiredProperties), Set(["data"]))
XCTAssertEqual(Set(objectContext1.properties.keys), Set(["data"]))
guard case let .array(contextB, arrayContext)? = objectContext1.properties["data"] else {
XCTFail("Expected Data field of JSON Document to be an Array Node")
return
}
XCTAssertFalse(contextB.nullable)
XCTAssertEqual(contextB.format, .generic)
XCTAssertTrue(contextB.required)
XCTAssertFalse(arrayContext.uniqueItems)
XCTAssertEqual(arrayContext.minItems, 0)
guard case let .object(contextC, objectContext2) = arrayContext.items else {
XCTFail("Expected Items of Array under Data to be an Object Node")
return
}
XCTAssertFalse(contextC.nullable)
XCTAssertEqual(contextC.format, .generic)
XCTAssertTrue(contextC.required)
XCTAssertEqual(objectContext2.minProperties, 3)
XCTAssertEqual(Set(objectContext2.requiredProperties), Set(["id", "attributes", "type"]))
XCTAssertEqual(Set(objectContext2.properties.keys), Set(["id", "attributes", "type"]))
XCTAssertEqual(objectContext2.properties["type"],
JSONNode.string(.init(format: .generic,
required: true,
allowedValues: [.init("test")]),
.init()))
}
func test_DocumentWithOneIncludeType() {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
dateFormatter.locale = Locale(identifier: "en_US")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.dateEncodingStrategy = .formatted(dateFormatter)
let node = try! DocumentWithIncludes.openAPINodeWithExample(using: encoder)
XCTAssertTrue(node.required)
XCTAssertEqual(node.jsonTypeFormat, .object(.generic))
guard case let .object(contextA, objectContext1) = node else {
XCTFail("Expected JSON Document to be an Object Node")
return
}
XCTAssertNotNil(contextA.example)
XCTAssertFalse(contextA.nullable)
XCTAssertEqual(contextA.format, .generic)
XCTAssertTrue(contextA.required)
XCTAssertEqual(objectContext1.minProperties, 2)
XCTAssertEqual(Set(objectContext1.requiredProperties), Set(["data", "included"]))
XCTAssertEqual(Set(objectContext1.properties.keys), Set(["data", "included"]))
guard case let .object(contextB, objectContext2)? = objectContext1.properties["data"] else {
XCTFail("Expected Data field of JSON Document to be an Object Node")
return
}
XCTAssertFalse(contextB.nullable)
XCTAssertEqual(contextB.format, .generic)
XCTAssertTrue(contextB.required)
XCTAssertEqual(objectContext2.minProperties, 3)
XCTAssertEqual(Set(objectContext2.requiredProperties), Set(["id", "attributes", "type"]))
XCTAssertEqual(Set(objectContext2.properties.keys), Set(["id", "attributes", "type"]))
XCTAssertEqual(objectContext2.properties["type"],
JSONNode.string(.init(format: .generic,
required: true,
allowedValues: [.init("test")]),
.init()))
guard case let .array(contextC, arrayContext)? = objectContext1.properties["included"] else {
XCTFail("Expected Includes field of JSON Document to be an Array Node")
return
}
XCTAssertFalse(contextC.nullable)
XCTAssertEqual(contextC.format, .generic)
XCTAssertTrue(contextC.required)
XCTAssertTrue(arrayContext.uniqueItems)
XCTAssertEqual(arrayContext.minItems, 0)
guard case let .object(contextD, objectContext3) = arrayContext.items else {
XCTFail("Expected Items of Array under Data to be an Object Node")
return
}
XCTAssertFalse(contextD.nullable)
XCTAssertEqual(contextD.format, .generic)
XCTAssertTrue(contextD.required)
XCTAssertEqual(objectContext3.minProperties, 3)
XCTAssertEqual(Set(objectContext3.requiredProperties), Set(["id", "attributes", "type"]))
XCTAssertEqual(Set(objectContext3.properties.keys), Set(["id", "attributes", "type"]))
XCTAssertEqual(objectContext3.properties["type"],
JSONNode.string(.init(format: .generic,
required: true,
allowedValues: [.init("test")]),
.init()))
}
func test_DocumentWithTwoIncludeTypes() {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .none
dateFormatter.locale = Locale(identifier: "en_US")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
encoder.dateEncodingStrategy = .formatted(dateFormatter)
let node = try! DocumentWithMultipleTypesOfIncludes.openAPINodeWithExample(using: encoder)
XCTAssertTrue(node.required)
XCTAssertEqual(node.jsonTypeFormat, .object(.generic))
guard case let .object(contextA, objectContext1) = node else {
XCTFail("Expected JSON Document to be an Object Node")
return
}
XCTAssertNotNil(contextA.example)
XCTAssertFalse(contextA.nullable)
XCTAssertEqual(contextA.format, .generic)
XCTAssertTrue(contextA.required)
XCTAssertEqual(objectContext1.minProperties, 2)
XCTAssertEqual(Set(objectContext1.requiredProperties), Set(["data", "included"]))
XCTAssertEqual(Set(objectContext1.properties.keys), Set(["data", "included"]))
guard case let .object(contextB, objectContext2)? = objectContext1.properties["data"] else {
XCTFail("Expected Data field of JSON Document to be an Object Node")
return
}
XCTAssertFalse(contextB.nullable)
XCTAssertEqual(contextB.format, .generic)
XCTAssertTrue(contextB.required)
XCTAssertEqual(objectContext2.minProperties, 3)
XCTAssertEqual(Set(objectContext2.requiredProperties), Set(["id", "attributes", "type"]))
XCTAssertEqual(Set(objectContext2.properties.keys), Set(["id", "attributes", "type"]))
XCTAssertEqual(objectContext2.properties["type"],
JSONNode.string(.init(format: .generic,
required: true,
allowedValues: [.init("test")]),
.init()))
guard case let .array(contextC, arrayContext)? = objectContext1.properties["included"] else {
XCTFail("Expected Includes field of JSON Document to be an Array Node")
return
}
XCTAssertFalse(contextC.nullable)
XCTAssertEqual(contextC.format, .generic)
XCTAssertTrue(contextC.required)
XCTAssertTrue(arrayContext.uniqueItems)
XCTAssertEqual(arrayContext.minItems, 0)
guard case let .one(of: includeNodes) = arrayContext.items else {
XCTFail("Expected Included to contain multiple types of items.")
return
}
XCTAssertEqual(includeNodes.count, 2)
guard case let .object(contextD, objectContext3) = includeNodes[0] else {
XCTFail("Expected Items of OneOf under Array under Data to be an Object Node")
return
}
XCTAssertFalse(contextD.nullable)
XCTAssertEqual(contextD.format, .generic)
XCTAssertTrue(contextD.required)
XCTAssertEqual(objectContext3.minProperties, 3)
XCTAssertEqual(Set(objectContext3.requiredProperties), Set(["id", "attributes", "type"]))
XCTAssertEqual(Set(objectContext3.properties.keys), Set(["id", "attributes", "type"]))
XCTAssertEqual(objectContext3.properties["type"],
JSONNode.string(.init(format: .generic,
required: true,
allowedValues: [.init("test")]),
.init()))
guard case let .object(contextE, objectContext4) = includeNodes[1] else {
XCTFail("Expected Items of OneOf under Array under Data to be an Object Node")
return
}
XCTAssertFalse(contextE.nullable)
XCTAssertEqual(contextE.format, .generic)
XCTAssertTrue(contextE.required)
XCTAssertEqual(objectContext4.minProperties, 2)
XCTAssertEqual(Set(objectContext4.requiredProperties), Set(["id", "type"]))
XCTAssertEqual(Set(objectContext4.properties.keys), Set(["id", "type"]))
XCTAssertEqual(objectContext4.properties["type"],
JSONNode.string(.init(format: .generic,
required: true,
allowedValues: [.init("test2")]),
.init()))
}
}
@@ -49,6 +331,22 @@ extension JSONAPIDocumentOpenAPITests {
typealias TestEntity = BasicEntity<TestEntityDescription>
typealias SingleEntityDocument = Document<SingleResourceBody<TestEntity>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>
typealias ManyEntityDocument = Document<ManyResourceBody<TestEntity>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>
typealias DocumentWithIncludes = Document<SingleResourceBody<TestEntity>, NoMetadata, NoLinks, Include1<TestEntity>, NoAPIDescription, UnknownJSONAPIError>
enum TestEntityDescription2: EntityDescription {
static var jsonType: String { return "test2" }
typealias Attributes = NoAttributes
typealias Relationships = NoRelationships
}
typealias TestEntity2 = BasicEntity<TestEntityDescription2>
typealias DocumentWithMultipleTypesOfIncludes = Document<SingleResourceBody<TestEntity>, NoMetadata, NoLinks, Include2<TestEntity, TestEntity2>, NoAPIDescription, UnknownJSONAPIError>
}
extension Id: Sampleable where RawType == String {