mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-03-30 11:18:38 -07:00
Add example to JSONNode
This commit is contained in:
@@ -5,6 +5,5 @@
|
||||
<page name='Usage'/>
|
||||
<page name='Full Client & 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
Reference in New Issue
Block a user