Add example to JSONNode

This commit is contained in:
Mathew Polzin
2019-01-21 20:57:54 -08:00
parent 9972d13a4e
commit d6911f170c
5 changed files with 130 additions and 4 deletions
-1
View File
@@ -5,6 +5,5 @@
<page name='Usage'/>
<page name='Full Client &amp; Server Example'/>
<page name='Full Document Verbose Generation'/>
<page name='OpenAPI Documentation'/>
</pages>
</playground>
@@ -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),
@@ -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)
}
}
}
+48 -2
View File
@@ -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<T: Encodable>(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
}
@@ -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<String>
static var sample: Attributes {
return .init(name: "hello world")
}
}
typealias Relationships = NoRelationships
}
typealias TestEntity = BasicEntity<TestEntityDescription>
typealias SingleEntityDocument = Document<SingleResourceBody<TestEntity>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>
}