diff --git a/JSONAPI.playground/contents.xcplayground b/JSONAPI.playground/contents.xcplayground
index a1ec109..e240eff 100644
--- a/JSONAPI.playground/contents.xcplayground
+++ b/JSONAPI.playground/contents.xcplayground
@@ -5,6 +5,5 @@
-
\ No newline at end of file
diff --git a/Sources/JSONAPIOpenAPI/JSONAPITypes+OpenAPI.swift b/Sources/JSONAPIOpenAPI/JSONAPITypes+OpenAPI.swift
index 6afd065..35fd347 100644
--- a/Sources/JSONAPIOpenAPI/JSONAPITypes+OpenAPI.swift
+++ b/Sources/JSONAPIOpenAPI/JSONAPITypes+OpenAPI.swift
@@ -118,6 +118,7 @@ extension ToManyRelationship: OpenAPINodeType {
extension Entity: OpenAPINodeType where Description.Attributes: Sampleable, Description.Relationships: Sampleable {
public static func openAPINode() throws -> JSONNode {
+ // TODO: const for json `type`
// TODO: metadata, links
let idNode = JSONNode.string(.init(format: .generic,
@@ -147,7 +148,40 @@ extension Entity: OpenAPINodeType where Description.Attributes: Sampleable, Desc
typeProperty,
attributesProperty,
relationshipsProperty
- ].compactMap { $0 }) { _, value in value }
+ ].compactMap { $0 }) { _, value in value }
+
+ return .object(.init(format: .generic,
+ required: true),
+ .init(properties: propertiesDict))
+ }
+}
+
+extension SingleResourceBody: OpenAPINodeType where Entity: OpenAPINodeType {
+ public static func openAPINode() throws -> JSONNode {
+ return try Entity.openAPINode()
+ }
+}
+
+extension ManyResourceBody: OpenAPINodeType where Entity: OpenAPINodeType {
+ public static func openAPINode() throws -> JSONNode {
+ return .array(.init(format: .generic,
+ required: true),
+ .init(items: try Entity.openAPINode()))
+ }
+}
+
+extension Document: OpenAPINodeType where PrimaryResourceBody: OpenAPINodeType {
+ public static func openAPINode() throws -> JSONNode {
+ // TODO: metadata, links, api description, includes, 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 propertiesDict = Dictionary([
+ primaryDataProperty
+ ].compactMap { $0 }) { _, value in value }
return .object(.init(format: .generic,
required: true),
diff --git a/Sources/JSONAPIOpenAPI/OpenAPITypes+Codable.swift b/Sources/JSONAPIOpenAPI/OpenAPITypes+Codable.swift
index 6d0aced..9d4d2cc 100644
--- a/Sources/JSONAPIOpenAPI/OpenAPITypes+Codable.swift
+++ b/Sources/JSONAPIOpenAPI/OpenAPITypes+Codable.swift
@@ -12,6 +12,7 @@ extension JSONNode.Context: Encodable {
case format
case allowedValues = "enum"
case nullable
+ case example
}
public func encode(to encoder: Encoder) throws {
@@ -28,6 +29,10 @@ extension JSONNode.Context: Encodable {
}
try container.encode(nullable, forKey: .nullable)
+
+ if example != nil {
+ try container.encode(example, forKey: .example)
+ }
}
}
diff --git a/Sources/JSONAPIOpenAPI/OpenAPITypes.swift b/Sources/JSONAPIOpenAPI/OpenAPITypes.swift
index 033a3bd..65ec92b 100644
--- a/Sources/JSONAPIOpenAPI/OpenAPITypes.swift
+++ b/Sources/JSONAPIOpenAPI/OpenAPITypes.swift
@@ -16,6 +16,12 @@ public protocol OpenAPINodeType {
static func openAPINode() throws -> JSONNode
}
+extension OpenAPINodeType where Self: Sampleable, Self: Encodable {
+ public static func openAPINodeWithExample() throws -> JSONNode {
+ return try openAPINode().with(example: Self.sample)
+ }
+}
+
/// Anything conforming to `RawOpenAPINodeType` can provide an
/// OpenAPI schema representing itself. This second protocol is
/// necessary so that one type can conditionally provide a
@@ -261,14 +267,18 @@ public enum JSONNode: Equatable {
/// into an allowed value.
public let allowedValues: [AnyCodable]?
+ public let example: AnyCodable?
+
public init(format: Format,
required: Bool,
nullable: Bool = false,
- allowedValues: [AnyCodable]? = nil) {
+ allowedValues: [AnyCodable]? = nil,
+ example: AnyCodable? = nil) {
self.format = format
self.required = required
self.nullable = nullable
self.allowedValues = allowedValues
+ self.example = example
}
/// Return the optional version of this Context
@@ -296,12 +306,21 @@ public enum JSONNode: Equatable {
}
/// Return this context with the given list of possible values
- public func with(allowedValues: [AnyCodable]?) -> Context {
+ public func with(allowedValues: [AnyCodable]) -> Context {
return .init(format: format,
required: required,
nullable: nullable,
allowedValues: allowedValues)
}
+
+ /// Return this context with the given example
+ public func with(example: AnyCodable) -> Context {
+ return .init(format: format,
+ required: required,
+ nullable: nullable,
+ allowedValues: allowedValues,
+ example: example)
+ }
}
public struct NumericContext: Equatable {
@@ -513,8 +532,35 @@ public enum JSONNode: Equatable {
return self
}
}
+
+ public func with(example codableExample: T) throws -> JSONNode {
+ let example: AnyCodable
+ if let goodToGo = codableExample as? AnyCodable {
+ example = goodToGo
+ } else {
+ example = AnyCodable(try JSONSerialization.jsonObject(with: JSONEncoder().encode(codableExample), options: []))
+ }
+
+ switch self {
+ case .boolean(let context):
+ return .boolean(context.with(example: example))
+ case .object(let contextA, let contextB):
+ return .object(contextA.with(example: example), contextB)
+ case .array(let contextA, let contextB):
+ return .array(contextA.with(example: example), contextB)
+ case .number(let context, let contextB):
+ return .number(context.with(example: example), contextB)
+ case .integer(let context, let contextB):
+ 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:
+ return self
+ }
+ }
}
public enum OpenAPICodableError: Swift.Error {
case allCasesArrayNotCodable
+ case exampleNotCodable
}
diff --git a/Tests/JSONAPIOpenAPITests/JSONAPIDocumentOpenAPITests.swift b/Tests/JSONAPIOpenAPITests/JSONAPIDocumentOpenAPITests.swift
new file mode 100644
index 0000000..f709f17
--- /dev/null
+++ b/Tests/JSONAPIOpenAPITests/JSONAPIDocumentOpenAPITests.swift
@@ -0,0 +1,42 @@
+//
+// JSONAPIDocumentOpenAPITests.swift
+// JSONAPIOpenAPITests
+//
+// Created by Mathew Polzin on 1/21/19.
+//
+
+import XCTest
+import JSONAPI
+import JSONAPIOpenAPI
+
+class JSONAPIDocumentOpenAPITests: XCTestCase {
+ func test_SingleResourceDocument() {
+ let node = try! SingleEntityDocument.openAPINode()
+
+ let encoder = JSONEncoder()
+ encoder.outputFormatting = .prettyPrinted
+
+ print(String(data: try! encoder.encode(node), encoding: .utf8)!)
+ }
+}
+
+// MARK: - Test Types
+extension JSONAPIDocumentOpenAPITests {
+ enum TestEntityDescription: EntityDescription {
+ static var jsonType: String { return "test" }
+
+ struct Attributes: JSONAPI.Attributes, Sampleable {
+ let name: Attribute
+
+ static var sample: Attributes {
+ return .init(name: "hello world")
+ }
+ }
+
+ typealias Relationships = NoRelationships
+ }
+
+ typealias TestEntity = BasicEntity
+
+ typealias SingleEntityDocument = Document, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>
+}