diff --git a/JSONAPI.playground/Sources/OpenAPISupport.swift b/JSONAPI.playground/Sources/OpenAPISupport.swift index 9a1bb9a..ac267dd 100644 --- a/JSONAPI.playground/Sources/OpenAPISupport.swift +++ b/JSONAPI.playground/Sources/OpenAPISupport.swift @@ -45,4 +45,12 @@ extension Document: Sampleable where PrimaryResourceBody: Arbitrary, IncludeType public static var sample: Document { return Document.arbitrary.generate } + + public static var successSample: Document? { + return Document.arbitraryData.generate + } + + public static var failureSample: Document? { + return Document.arbitraryErrors.generate + } } diff --git a/Sources/JSONAPIOpenAPI/JSONAPITypes+OpenAPI.swift b/Sources/JSONAPIOpenAPI/JSONAPITypes+OpenAPI.swift index 35fd347..11934be 100644 --- a/Sources/JSONAPIOpenAPI/JSONAPITypes+OpenAPI.swift +++ b/Sources/JSONAPIOpenAPI/JSONAPITypes+OpenAPI.swift @@ -72,13 +72,14 @@ extension TransformedAttribute: OpenAPINodeType where RawValue: OpenAPINodeType } extension RelationshipType { - static func relationshipNode(nullable: Bool) -> JSONNode { + static func relationshipNode(nullable: Bool, jsonType: String) -> JSONNode { let propertiesDict: [String: JSONNode] = [ "id": .string(.init(format: .generic, required: true), .init()), "type": .string(.init(format: .generic, - required: true), + required: true, + allowedValues: [.init(jsonType)]), .init()) ] @@ -97,7 +98,7 @@ extension ToOneRelationship: OpenAPINodeType { return .object(.init(format: .generic, required: true), .init(properties: [ - "data": ToOneRelationship.relationshipNode(nullable: nullable) + "data": ToOneRelationship.relationshipNode(nullable: nullable, jsonType: Identifiable.jsonType) ])) } } @@ -111,14 +112,16 @@ extension ToManyRelationship: OpenAPINodeType { .init(properties: [ "data": .array(.init(format: .generic, required: true), - .init(items: ToManyRelationship.relationshipNode(nullable: false))) + .init(items: ToManyRelationship.relationshipNode(nullable: false, jsonType: Relatable.jsonType))) ])) } } extension Entity: OpenAPINodeType where Description.Attributes: Sampleable, Description.Relationships: Sampleable { public static func openAPINode() throws -> JSONNode { - // TODO: const for json `type` + // NOTE: const for json `type` not supported by OpenAPI 3.0 + // Will use "enum" with one possible value for now. + // TODO: metadata, links let idNode = JSONNode.string(.init(format: .generic, @@ -127,7 +130,8 @@ extension Entity: OpenAPINodeType where Description.Attributes: Sampleable, Desc let idProperty = ("id", idNode) let typeNode = JSONNode.string(.init(format: .generic, - required: true), + required: true, + allowedValues: [.init(Entity.jsonType)]), .init()) let typeProperty = ("type", typeNode) diff --git a/Sources/JSONAPIOpenAPI/OpenAPITypes+Codable.swift b/Sources/JSONAPIOpenAPI/OpenAPITypes+Codable.swift index 9d4d2cc..fa6d93b 100644 --- a/Sources/JSONAPIOpenAPI/OpenAPITypes+Codable.swift +++ b/Sources/JSONAPIOpenAPI/OpenAPITypes+Codable.swift @@ -13,6 +13,7 @@ extension JSONNode.Context: Encodable { case allowedValues = "enum" case nullable case example +// case constantValue = "const" } public func encode(to encoder: Encoder) throws { @@ -28,6 +29,10 @@ extension JSONNode.Context: Encodable { try container.encode(allowedValues, forKey: .allowedValues) } +// if constantValue != nil { +// try container.encode(constantValue, forKey: .constantValue) +// } + try container.encode(nullable, forKey: .nullable) if example != nil { diff --git a/Sources/JSONAPIOpenAPI/OpenAPITypes.swift b/Sources/JSONAPIOpenAPI/OpenAPITypes.swift index 30a5338..2a65d9e 100644 --- a/Sources/JSONAPIOpenAPI/OpenAPITypes.swift +++ b/Sources/JSONAPIOpenAPI/OpenAPITypes.swift @@ -18,7 +18,7 @@ public protocol OpenAPINodeType { extension OpenAPINodeType where Self: Sampleable, Self: Encodable { public static func openAPINodeWithExample() throws -> JSONNode { - return try openAPINode().with(example: Self.sample) + return try openAPINode().with(example: Self.successSample ?? Self.sample) } } @@ -255,6 +255,10 @@ public enum JSONNode: Equatable { public let required: Bool public let nullable: Bool + // NOTE: "const" is supported by the newest JSON Schema spec but not + // yet by OpenAPI. Instead, will use "enum" with one possible value for now. +// public let constantValue: Format.SwiftType? + /// The OpenAPI spec calls this "enum" /// If not specified, it is assumed that any /// value of the given format is allowed. @@ -276,11 +280,13 @@ public enum JSONNode: Equatable { public init(format: Format, required: Bool, nullable: Bool = false, +// constantValue: Format.SwiftType? = nil, allowedValues: [AnyCodable]? = nil, example: AnyCodable? = nil) { self.format = format self.required = required self.nullable = nullable +// self.constantValue = constantValue self.allowedValues = allowedValues self.example = example .flatMap { try? JSONEncoder().encode($0)} @@ -292,6 +298,7 @@ public enum JSONNode: Equatable { return .init(format: format, required: false, nullable: nullable, +// constantValue: constantValue, allowedValues: allowedValues) } @@ -300,6 +307,7 @@ public enum JSONNode: Equatable { return .init(format: format, required: true, nullable: nullable, +// constantValue: constantValue, allowedValues: allowedValues) } @@ -308,6 +316,7 @@ public enum JSONNode: Equatable { return .init(format: format, required: required, nullable: true, +// constantValue: constantValue, allowedValues: allowedValues) } @@ -316,6 +325,7 @@ public enum JSONNode: Equatable { return .init(format: format, required: required, nullable: nullable, +// constantValue: constantValue, allowedValues: allowedValues) } @@ -324,6 +334,7 @@ public enum JSONNode: Equatable { return .init(format: format, required: required, nullable: nullable, +// constantValue: constantValue, allowedValues: allowedValues, example: example) } diff --git a/Sources/JSONAPIOpenAPI/Sampleable.swift b/Sources/JSONAPIOpenAPI/Sampleable.swift index 328080e..c730791 100644 --- a/Sources/JSONAPIOpenAPI/Sampleable.swift +++ b/Sources/JSONAPIOpenAPI/Sampleable.swift @@ -15,6 +15,20 @@ public protocol Sampleable { /// same value every time, or it can be an arbitrarily random /// value each time. static var sample: Self { get } + + /// Get an example of success, if that is meaningful and + /// available. If not, will be nil. + static var successSample: Self? { get } + + /// Get an example of failure, if that is meaningful and + /// available. If not, will be nil. + static var failureSample: Self? { get } +} + +public extension Sampleable { + public static var successSample: Self? { return nil } + + public static var failureSample: Self? { return nil } } extension Sampleable { diff --git a/Tests/JSONAPIOpenAPITests/JSONAPIEntityOpenAPITests.swift b/Tests/JSONAPIOpenAPITests/JSONAPIEntityOpenAPITests.swift index 1fc31ae..abaf9d1 100644 --- a/Tests/JSONAPIOpenAPITests/JSONAPIEntityOpenAPITests.swift +++ b/Tests/JSONAPIOpenAPITests/JSONAPIEntityOpenAPITests.swift @@ -34,7 +34,8 @@ class JSONAPIEntityOpenAPITests: XCTestCase { required: true), .init())) XCTAssertEqual(objectContext1.properties["type"], .string(.init(format: .generic, - required: true), + required: true, + allowedValues: [.init(TestType1.jsonType)]), .init())) } @@ -62,7 +63,8 @@ class JSONAPIEntityOpenAPITests: XCTestCase { required: true), .init())) XCTAssertEqual(objectContext1.properties["type"], .string(.init(format: .generic, - required: true), + required: true, + allowedValues: [.init(TestType2.jsonType)]), .init())) let attributesNode = objectContext1.properties["attributes"] @@ -143,7 +145,8 @@ class JSONAPIEntityOpenAPITests: XCTestCase { required: true), .init())) XCTAssertEqual(objectContext1.properties["type"], .string(.init(format: .generic, - required: true), + required: true, + allowedValues: [.init(TestType3.jsonType)]), .init())) let relationshipsNode = objectContext1.properties["relationships"] @@ -170,7 +173,8 @@ class JSONAPIEntityOpenAPITests: XCTestCase { required: true), .init()), "type": .string(.init(format: .generic, - required: true), + required: true, + allowedValues: [.init(TestType1.jsonType)]), .init())]) let pointerContext = JSONNode.ObjectContext(properties: ["data": .object(.init(format: .generic, @@ -225,8 +229,6 @@ class JSONAPIEntityOpenAPITests: XCTestCase { nullable: false, allowedValues: nil), manyPointerContext)) - - let tmpData = try! JSONEncoder().encode(node) } func test_AttributesAndRelationshipsEntity() {