mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-07-10 12:18:40 -07:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d057b4398 | |||
| c8421cdd58 | |||
| cde10a8491 | |||
| ad05d3908a | |||
| 23b2b2e04f | |||
| 2988503d7d | |||
| 5ea83b07c1 | |||
| 2b59f54067 | |||
| d1cf19f9fe | |||
| 59835fbe11 |
@@ -3,6 +3,7 @@
|
||||
import Foundation
|
||||
import JSONAPI
|
||||
import JSONAPIOpenAPI
|
||||
import Poly
|
||||
|
||||
// print Entity Schema
|
||||
let encoder = JSONEncoder()
|
||||
@@ -28,3 +29,18 @@ print("Batch Person Document Schema")
|
||||
print("====")
|
||||
print(batchPersonSchemaData.map { String(data: $0, encoding: .utf8)! } ?? "Schema Construction Failed")
|
||||
print("====")
|
||||
|
||||
let tmp: [String: OpenAPIComponents.SchemasDict.RefType] = [
|
||||
"BatchPerson": try! BatchPeopleDocument.openAPINodeWithExample()
|
||||
]
|
||||
|
||||
let components = OpenAPIComponents(schemas: tmp)
|
||||
|
||||
let batchPeopleRef = JSONReference(type: \OpenAPIComponents.schemas, selector: "BatchPerson")
|
||||
|
||||
let tmp2 = JSONNode.reference(batchPeopleRef)
|
||||
|
||||
print("====")
|
||||
print("====")
|
||||
//print(String(data: try! encoder.encode(components), encoding: .utf8)!)
|
||||
print(String(data: try! encoder.encode(tmp2), encoding: .utf8)!)
|
||||
|
||||
@@ -141,7 +141,7 @@ extension ToManyRelationship: OpenAPINodeType {
|
||||
}
|
||||
}
|
||||
|
||||
extension Entity: OpenAPIEncodedNodeType, OpenAPINodeType where Description.Attributes: Sampleable, Description.Relationships: Sampleable {
|
||||
extension Entity: OpenAPIEncodedNodeType where Description.Attributes: Sampleable, Description.Relationships: Sampleable {
|
||||
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
|
||||
// NOTE: const for json `type` not supported by OpenAPI 3.0
|
||||
// Will use "enum" with one possible value for now.
|
||||
@@ -184,13 +184,13 @@ extension Entity: OpenAPIEncodedNodeType, OpenAPINodeType where Description.Attr
|
||||
}
|
||||
}
|
||||
|
||||
extension SingleResourceBody: OpenAPIEncodedNodeType, OpenAPINodeType where Entity: OpenAPIEncodedNodeType {
|
||||
extension SingleResourceBody: OpenAPIEncodedNodeType where Entity: OpenAPIEncodedNodeType {
|
||||
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
|
||||
return try Entity.openAPINode(using: encoder)
|
||||
}
|
||||
}
|
||||
|
||||
extension ManyResourceBody: OpenAPIEncodedNodeType, OpenAPINodeType where Entity: OpenAPIEncodedNodeType {
|
||||
extension ManyResourceBody: OpenAPIEncodedNodeType where Entity: OpenAPIEncodedNodeType {
|
||||
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
|
||||
return .array(.init(format: .generic,
|
||||
required: true),
|
||||
@@ -198,7 +198,7 @@ extension ManyResourceBody: OpenAPIEncodedNodeType, OpenAPINodeType where Entity
|
||||
}
|
||||
}
|
||||
|
||||
extension Document: OpenAPIEncodedNodeType, OpenAPINodeType where PrimaryResourceBody: OpenAPIEncodedNodeType, IncludeType: OpenAPINodeType {
|
||||
extension Document: OpenAPIEncodedNodeType where PrimaryResourceBody: OpenAPIEncodedNodeType, IncludeType: OpenAPINodeType {
|
||||
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
|
||||
|
||||
@@ -155,6 +155,7 @@ extension JSONNode: Encodable {
|
||||
case oneOf
|
||||
case anyOf
|
||||
case not
|
||||
case reference = "$ref"
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
@@ -189,6 +190,172 @@ extension JSONNode: Encodable {
|
||||
var container = encoder.container(keyedBy: SubschemaCodingKeys.self)
|
||||
|
||||
try container.encode(node, forKey: .not)
|
||||
|
||||
case .reference(let reference):
|
||||
var container = encoder.container(keyedBy: SubschemaCodingKeys.self)
|
||||
|
||||
try container.encode(reference, forKey: .reference)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension JSONReference: Encodable {
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
|
||||
let referenceString: String = {
|
||||
switch self {
|
||||
case .file(let reference):
|
||||
return reference
|
||||
case .node(let reference):
|
||||
return "#/\(Root.refName)/\(reference.refName)/\(reference.selector)"
|
||||
}
|
||||
}()
|
||||
|
||||
try container.encode(referenceString)
|
||||
}
|
||||
}
|
||||
|
||||
extension RefDict: Encodable {
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
|
||||
try container.encode(dict)
|
||||
}
|
||||
}
|
||||
|
||||
extension OpenAPIResponse.Code: Encodable {
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
|
||||
let string: String
|
||||
switch self {
|
||||
case .`default`:
|
||||
string = "default"
|
||||
|
||||
case .status(code: let code):
|
||||
string = String(code)
|
||||
}
|
||||
|
||||
try container.encode(string)
|
||||
}
|
||||
}
|
||||
|
||||
extension OpenAPIPathItem.PathProperties.Operation: Encodable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case tags
|
||||
case summary
|
||||
case description
|
||||
case externalDocs
|
||||
case operationId
|
||||
case parameters
|
||||
case requestBody
|
||||
case responses
|
||||
case callbacks
|
||||
case deprecated
|
||||
case security
|
||||
case servers
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
if tags != nil {
|
||||
try container.encode(tags, forKey: .tags)
|
||||
}
|
||||
|
||||
if summary != nil {
|
||||
try container.encode(summary, forKey: .summary)
|
||||
}
|
||||
|
||||
if description != nil {
|
||||
try container.encode(description, forKey: .description)
|
||||
}
|
||||
|
||||
try container.encode(operationId, forKey: .operationId)
|
||||
|
||||
try container.encode(parameters, forKey: .parameters)
|
||||
|
||||
try container.encode(responses, forKey: .responses)
|
||||
|
||||
try container.encode(deprecated, forKey: .deprecated)
|
||||
}
|
||||
}
|
||||
|
||||
extension OpenAPIPathItem.PathProperties: Encodable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case summary
|
||||
case description
|
||||
case servers
|
||||
case parameters
|
||||
|
||||
case get
|
||||
case put
|
||||
case post
|
||||
case delete
|
||||
case options
|
||||
case head
|
||||
case patch
|
||||
case trace
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
if summary != nil {
|
||||
try container.encode(summary, forKey: .summary)
|
||||
}
|
||||
|
||||
if description != nil {
|
||||
try container.encode(description, forKey: .description)
|
||||
}
|
||||
|
||||
try container.encode(parameters, forKey: .parameters)
|
||||
|
||||
if get != nil {
|
||||
try container.encode(get, forKey: .get)
|
||||
}
|
||||
|
||||
if put != nil {
|
||||
try container.encode(put, forKey: .put)
|
||||
}
|
||||
|
||||
if post != nil {
|
||||
try container.encode(post, forKey: .post)
|
||||
}
|
||||
|
||||
if delete != nil {
|
||||
try container.encode(delete, forKey: .delete)
|
||||
}
|
||||
|
||||
if options != nil {
|
||||
try container.encode(options, forKey: .options)
|
||||
}
|
||||
|
||||
if head != nil {
|
||||
try container.encode(head, forKey: .head)
|
||||
}
|
||||
|
||||
if patch != nil {
|
||||
try container.encode(patch, forKey: .patch)
|
||||
}
|
||||
|
||||
if trace != nil {
|
||||
try container.encode(trace, forKey: .trace)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension OpenAPIPathItem: Encodable {
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
|
||||
switch self {
|
||||
case .reference(let reference):
|
||||
try container.encode(reference)
|
||||
|
||||
case .operations(let operations):
|
||||
try container.encode(operations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import AnyCodable
|
||||
import Foundation
|
||||
import Poly
|
||||
|
||||
// MARK: Node (i.e. schema) Protocols
|
||||
|
||||
@@ -16,22 +17,16 @@ public protocol OpenAPINodeType {
|
||||
static func openAPINode() throws -> JSONNode
|
||||
}
|
||||
|
||||
extension OpenAPINodeType where Self: Sampleable, Self: Encodable {
|
||||
public static func openAPINodeWithExample(using encoder: JSONEncoder = JSONEncoder()) throws -> JSONNode {
|
||||
return try openAPINode().with(example: Self.successSample ?? Self.sample, using: encoder)
|
||||
}
|
||||
}
|
||||
|
||||
/// Anything conforming to `OpenAPIEncodedNodeType` can provide an
|
||||
/// OpenAPI schema representing itself but it may need an Encoder
|
||||
/// to do its job.
|
||||
public protocol OpenAPIEncodedNodeType: OpenAPINodeType {
|
||||
public protocol OpenAPIEncodedNodeType {
|
||||
static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode
|
||||
}
|
||||
|
||||
extension OpenAPIEncodedNodeType {
|
||||
public static func openAPINode() throws -> JSONNode {
|
||||
return try openAPINode(using: JSONEncoder())
|
||||
extension OpenAPIEncodedNodeType where Self: Sampleable, Self: Encodable {
|
||||
public static func openAPINodeWithExample(using encoder: JSONEncoder = JSONEncoder()) throws -> JSONNode {
|
||||
return try openAPINode(using: encoder).with(example: Self.successSample ?? Self.sample, using: encoder)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -274,6 +269,7 @@ public enum JSONNode: Equatable {
|
||||
indirect case one(of: [JSONNode])
|
||||
indirect case any(of: [JSONNode])
|
||||
indirect case not(JSONNode)
|
||||
case reference(JSONReference<OpenAPIComponents, JSONNode>)
|
||||
|
||||
public struct Context<Format: OpenAPIFormat>: JSONNodeContext, Equatable {
|
||||
public let format: Format
|
||||
@@ -476,7 +472,7 @@ public enum JSONNode: Equatable {
|
||||
return .integer(context.format)
|
||||
case .string(let context, _):
|
||||
return .string(context.format)
|
||||
case .all, .one, .any, .not:
|
||||
case .all, .one, .any, .not, .reference:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -490,7 +486,7 @@ public enum JSONNode: Equatable {
|
||||
.integer(let contextA as JSONNodeContext, _),
|
||||
.string(let contextA as JSONNodeContext, _):
|
||||
return contextA.required
|
||||
case .all, .one, .any, .not:
|
||||
case .all, .one, .any, .not, .reference:
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -510,7 +506,7 @@ public enum JSONNode: Equatable {
|
||||
return .integer(context.optionalContext(), contextB)
|
||||
case .string(let context, let contextB):
|
||||
return .string(context.optionalContext(), contextB)
|
||||
case .all, .one, .any, .not:
|
||||
case .all, .one, .any, .not, .reference:
|
||||
return self
|
||||
}
|
||||
}
|
||||
@@ -530,7 +526,7 @@ public enum JSONNode: Equatable {
|
||||
return .integer(context.requiredContext(), contextB)
|
||||
case .string(let context, let contextB):
|
||||
return .string(context.requiredContext(), contextB)
|
||||
case .all, .one, .any, .not:
|
||||
case .all, .one, .any, .not, .reference:
|
||||
return self
|
||||
}
|
||||
}
|
||||
@@ -550,7 +546,7 @@ public enum JSONNode: Equatable {
|
||||
return .integer(context.nullableContext(), contextB)
|
||||
case .string(let context, let contextB):
|
||||
return .string(context.nullableContext(), contextB)
|
||||
case .all, .one, .any, .not:
|
||||
case .all, .one, .any, .not, .reference:
|
||||
return self
|
||||
}
|
||||
}
|
||||
@@ -570,7 +566,7 @@ public enum JSONNode: Equatable {
|
||||
return .integer(context.with(allowedValues: allowedValues), contextB)
|
||||
case .string(let context, let contextB):
|
||||
return .string(context.with(allowedValues: allowedValues), contextB)
|
||||
case .all, .one, .any, .not:
|
||||
case .all, .one, .any, .not, .reference:
|
||||
return self
|
||||
}
|
||||
}
|
||||
@@ -597,7 +593,7 @@ public enum JSONNode: Equatable {
|
||||
return .integer(context.with(example: example, using: encoder), contextB)
|
||||
case .string(let context, let contextB):
|
||||
return .string(context.with(example: example, using: encoder), contextB)
|
||||
case .all, .one, .any, .not:
|
||||
case .all, .one, .any, .not, .reference:
|
||||
return self
|
||||
}
|
||||
}
|
||||
@@ -613,3 +609,316 @@ public enum OpenAPITypeError: Swift.Error {
|
||||
case invalidNode
|
||||
case unknownNodeType(Any.Type)
|
||||
}
|
||||
|
||||
/// Anything conforming to RefName knows what to call itself
|
||||
/// in the context of JSON References.
|
||||
public protocol RefName {
|
||||
static var refName: String { get }
|
||||
}
|
||||
|
||||
public protocol ReferenceRoot: RefName {}
|
||||
|
||||
public protocol ReferenceDict: RefName {
|
||||
associatedtype Value
|
||||
}
|
||||
|
||||
/// A RefDict knows what to call itself (Name) and where to
|
||||
/// look for itself (Root) and it stores a dictionary of
|
||||
/// JSONNodes (some of which might be other references).
|
||||
public struct RefDict<Root: ReferenceRoot, Name: RefName, RefType: Equatable & Encodable>: ReferenceDict, Equatable {
|
||||
public static var refName: String { return Name.refName }
|
||||
|
||||
public typealias Value = RefType
|
||||
public typealias Key = String
|
||||
|
||||
let dict: [String: RefType]
|
||||
|
||||
public init(_ dict: [String: RefType]) {
|
||||
self.dict = dict
|
||||
}
|
||||
|
||||
public subscript(_ key: String) -> RefType? {
|
||||
return dict[key]
|
||||
}
|
||||
}
|
||||
|
||||
/// A Reference is the combination of
|
||||
/// a path to a reference dictionary
|
||||
/// and a selector that the dictionary is keyed off of.
|
||||
public enum JSONReference<Root: ReferenceRoot, RefType: Equatable>: Equatable {
|
||||
|
||||
case node(InternalReference)
|
||||
case file(FileReference)
|
||||
|
||||
public typealias FileReference = String
|
||||
|
||||
public struct InternalReference: Equatable {
|
||||
public let path: PartialKeyPath<Root>
|
||||
public let selector: String
|
||||
|
||||
public var refName: String {
|
||||
// we require RD be a RefName in the initializer
|
||||
// so it is safe to force cast here.
|
||||
return (type(of: path).valueType as! RefName.Type).refName
|
||||
}
|
||||
|
||||
public init<RD: RefName & ReferenceDict>(type: KeyPath<Root, RD>,
|
||||
selector: String) where RD.Value == RefType {
|
||||
self.path = type
|
||||
self.selector = selector
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An OpenAPI Path Item
|
||||
/// This type describes the endpoints a server has
|
||||
/// bound to a particular path.
|
||||
public enum OpenAPIPathItem: Equatable {
|
||||
case reference(JSONReference<OpenAPIComponents, OpenAPIPathItem>)
|
||||
case operations(PathProperties)
|
||||
|
||||
public struct PathProperties: Equatable {
|
||||
public let summary: String?
|
||||
public let description: String?
|
||||
// public let servers:
|
||||
public let parameters: ParameterArray
|
||||
|
||||
public let get: Operation?
|
||||
public let put: Operation?
|
||||
public let post: Operation?
|
||||
public let delete: Operation?
|
||||
public let options: Operation?
|
||||
public let head: Operation?
|
||||
public let patch: Operation?
|
||||
public let trace: Operation?
|
||||
|
||||
public init(summary: String? = nil,
|
||||
description: String? = nil,
|
||||
parameters: ParameterArray,
|
||||
get: Operation? = nil,
|
||||
put: Operation? = nil,
|
||||
post: Operation? = nil,
|
||||
delete: Operation? = nil,
|
||||
options: Operation? = nil,
|
||||
head: Operation? = nil,
|
||||
patch: Operation? = nil,
|
||||
trace: Operation? = nil) {
|
||||
self.summary = summary
|
||||
self.description = description
|
||||
self.parameters = parameters
|
||||
|
||||
self.get = get
|
||||
self.put = put
|
||||
self.post = post
|
||||
self.delete = delete
|
||||
self.options = options
|
||||
self.head = head
|
||||
self.patch = patch
|
||||
self.trace = trace
|
||||
}
|
||||
|
||||
public typealias ParameterArray = [Either<Parameter, JSONReference<OpenAPIComponents, Parameter>>]
|
||||
|
||||
public struct Parameter: Equatable, Encodable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
// case parameterLocation = "in"
|
||||
case description
|
||||
case deprecated
|
||||
}
|
||||
|
||||
public let name: String
|
||||
// public let parameterLocation: Location
|
||||
public let description: String?
|
||||
public let deprecated: Bool // default is false
|
||||
// TODO: serialization rules
|
||||
/*
|
||||
Serialization Rules
|
||||
*/
|
||||
|
||||
public init(name: String,
|
||||
description: String? = nil,
|
||||
deprecated: Bool = false) {
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.deprecated = deprecated
|
||||
}
|
||||
|
||||
// public enum Location: Encodable {
|
||||
// case query(required: Bool?)
|
||||
// case header(required: Bool?)
|
||||
// case path
|
||||
// case cookie(required: Bool?)
|
||||
// }
|
||||
}
|
||||
|
||||
public struct Operation: Equatable {
|
||||
public let tags: [String]?
|
||||
public let summary: String?
|
||||
public let description: String?
|
||||
// public let externalDocs:
|
||||
public let operationId: String
|
||||
public let parameters: ParameterArray
|
||||
// public let requestBody:
|
||||
public let responses: ResponseMap
|
||||
// public let callbacks:
|
||||
public let deprecated: Bool // default is false
|
||||
// public let security:
|
||||
// public let servers:
|
||||
|
||||
public init(tags: [String]? = nil,
|
||||
summary: String? = nil,
|
||||
description: String? = nil,
|
||||
operationId: String,
|
||||
parameters: ParameterArray,
|
||||
responses: ResponseMap,
|
||||
deprecated: Bool = false) {
|
||||
self.tags = tags
|
||||
self.summary = summary
|
||||
self.description = description
|
||||
self.operationId = operationId
|
||||
self.parameters = parameters
|
||||
self.responses = responses
|
||||
self.deprecated = deprecated
|
||||
}
|
||||
|
||||
public typealias ResponseMap = [OpenAPIResponse.Code: Either<OpenAPIResponse, JSONReference<OpenAPIComponents, OpenAPIResponse>>]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct OpenAPIResponse: Encodable, Equatable {
|
||||
public let description: String
|
||||
// public let headers:
|
||||
public let content: ContentMap
|
||||
// public let links:
|
||||
|
||||
public init(description: String,
|
||||
content: ContentMap) {
|
||||
self.description = description
|
||||
self.content = content
|
||||
}
|
||||
|
||||
public typealias ContentMap = [ContentType: Content]
|
||||
|
||||
public enum Code: Equatable, Hashable {
|
||||
case `default`
|
||||
case status(code: Int)
|
||||
}
|
||||
|
||||
public enum ContentType: String, Encodable, Equatable, Hashable {
|
||||
case json = "application/json"
|
||||
}
|
||||
|
||||
public struct Content: Encodable, Equatable {
|
||||
public let schema: Either<JSONNode, JSONReference<OpenAPIComponents, JSONNode>>
|
||||
// public let example:
|
||||
// public let examples:
|
||||
// public let encoding:
|
||||
|
||||
public init(schema: Either<JSONNode, JSONReference<OpenAPIComponents, JSONNode>>) {
|
||||
self.schema = schema
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// What the spec calls the "Components Object".
|
||||
/// This is a place to put reusable components to
|
||||
/// be referenced from other parts of the spec.
|
||||
public struct OpenAPIComponents: Equatable, Encodable, ReferenceRoot {
|
||||
public static var refName: String { return "components" }
|
||||
|
||||
public let schemas: SchemasDict
|
||||
// public let responses:
|
||||
public let parameters: ParametersDict
|
||||
// public let examples:
|
||||
// public let requestBodies:
|
||||
// public let headers:
|
||||
// public let headers:
|
||||
// public let securitySchemas:
|
||||
// public let links:
|
||||
// public let callbacks:
|
||||
|
||||
public init(schemas: [String: SchemasDict.Value], parameters: [String: ParametersDict.Value]) {
|
||||
self.schemas = SchemasDict(schemas)
|
||||
self.parameters = ParametersDict(parameters)
|
||||
}
|
||||
|
||||
public enum SchemasName: RefName {
|
||||
public static var refName: String { return "schemas" }
|
||||
}
|
||||
|
||||
public typealias SchemasDict = RefDict<OpenAPIComponents, SchemasName, JSONNode>
|
||||
|
||||
public enum ParametersName: RefName {
|
||||
public static var refName: String { return "parameters" }
|
||||
}
|
||||
|
||||
public typealias ParametersDict = RefDict<OpenAPIComponents, ParametersName, OpenAPIPathItem.PathProperties.Parameter>
|
||||
}
|
||||
|
||||
/// The root of an OpenAPI 3.0 document.
|
||||
public struct OpenAPISchema: Encodable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case openAPIVersion = "openapi"
|
||||
case info
|
||||
case paths
|
||||
case components
|
||||
}
|
||||
|
||||
public let openAPIVersion: Version
|
||||
public let info: Info
|
||||
// public let servers:
|
||||
public let paths: [PathComponents: OpenAPIPathItem]
|
||||
public let components: OpenAPIComponents
|
||||
// public let security:
|
||||
// public let tags:
|
||||
// public let externalDocs:
|
||||
|
||||
public init(openAPIVersion: Version = .v3_0_0,
|
||||
info: Info,
|
||||
paths: [PathComponents: OpenAPIPathItem],
|
||||
components: OpenAPIComponents) {
|
||||
self.openAPIVersion = openAPIVersion
|
||||
self.info = info
|
||||
self.paths = paths
|
||||
self.components = components
|
||||
}
|
||||
|
||||
public enum Version: String, Encodable {
|
||||
case v3_0_0 = "3.0.0"
|
||||
}
|
||||
|
||||
public struct Info: Encodable {
|
||||
public let title: String
|
||||
public let description: String?
|
||||
public let termsOfService: URL?
|
||||
// public let contact:
|
||||
// public let license:
|
||||
public let version: String
|
||||
|
||||
public init(title: String,
|
||||
description: String? = nil,
|
||||
termsOfService: URL? = nil,
|
||||
version: String) {
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.termsOfService = termsOfService
|
||||
self.version = version
|
||||
}
|
||||
}
|
||||
|
||||
public struct PathComponents: Encodable, Equatable, Hashable {
|
||||
public let components: [String]
|
||||
|
||||
public init(_ components: [String]) {
|
||||
self.components = components
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
|
||||
try container.encode(components.joined(separator: "/"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-1
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// Sampleable+JSONAPI.swift
|
||||
// JSONAPI+Sampleable.swift
|
||||
// JSONAPIOpenAPI
|
||||
//
|
||||
// Created by Mathew Polzin on 1/24/19.
|
||||
@@ -43,6 +43,12 @@ extension UnknownJSONAPIError: Sampleable {
|
||||
}
|
||||
}
|
||||
|
||||
extension Unidentified: Sampleable {
|
||||
public static var sample: Unidentified {
|
||||
return Unidentified()
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: Sampleable where RawValue: Sampleable {
|
||||
public static var sample: Attribute<RawValue> {
|
||||
return .init(value: RawValue.sample)
|
||||
@@ -40,7 +40,16 @@ class JSONAPIEntityOpenAPITests: XCTestCase {
|
||||
}
|
||||
|
||||
func test_AttributesEntity() {
|
||||
let node = try! TestType2.openAPINode(using: JSONEncoder())
|
||||
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .medium
|
||||
dateFormatter.timeStyle = .short
|
||||
dateFormatter.locale = Locale(identifier: "en_US")
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.dateEncodingStrategy = .formatted(dateFormatter)
|
||||
|
||||
let node = try! TestType2.openAPINode(using: encoder)
|
||||
|
||||
XCTAssertTrue(node.required)
|
||||
XCTAssertEqual(node.jsonTypeFormat, .object(.generic))
|
||||
@@ -83,9 +92,9 @@ class JSONAPIEntityOpenAPITests: XCTestCase {
|
||||
nullable: false,
|
||||
allowedValues: nil))
|
||||
|
||||
XCTAssertEqual(attributesContext.minProperties, 3)
|
||||
XCTAssertEqual(Set(attributesContext.requiredProperties), Set(["stringProperty", "enumProperty", "nullableProperty"]))
|
||||
XCTAssertEqual(Set(attributesContext.properties.keys), Set(["stringProperty", "enumProperty", "optionalProperty", "nullableProperty", "nullableOptionalProperty"]))
|
||||
XCTAssertEqual(attributesContext.minProperties, 4)
|
||||
XCTAssertEqual(Set(attributesContext.requiredProperties), Set(["stringProperty", "enumProperty", "dateProperty", "nullableProperty"]))
|
||||
XCTAssertEqual(Set(attributesContext.properties.keys), Set(["stringProperty", "enumProperty", "dateProperty", "optionalProperty", "nullableProperty", "nullableOptionalProperty"]))
|
||||
|
||||
XCTAssertEqual(attributesContext.properties["stringProperty"],
|
||||
.string(.init(format: .generic,
|
||||
@@ -99,6 +108,13 @@ class JSONAPIEntityOpenAPITests: XCTestCase {
|
||||
allowedValues: ["one", "two"].map(AnyCodable.init)),
|
||||
.init()))
|
||||
|
||||
XCTAssertEqual(attributesContext.properties["dateProperty"],
|
||||
.string(.init(format: .dateTime,
|
||||
required: true,
|
||||
nullable: false,
|
||||
allowedValues: nil),
|
||||
.init()))
|
||||
|
||||
XCTAssertEqual(attributesContext.properties["optionalProperty"],
|
||||
.string(.init(format: .generic,
|
||||
required: false,
|
||||
@@ -268,6 +284,7 @@ extension JSONAPIEntityOpenAPITests {
|
||||
public struct Attributes: JSONAPI.Attributes, Sampleable {
|
||||
let stringProperty: Attribute<String>
|
||||
let enumProperty: Attribute<EnumType>
|
||||
let dateProperty: Attribute<Date>
|
||||
let optionalProperty: Attribute<String>?
|
||||
let nullableProperty: Attribute<String?>
|
||||
let nullableOptionalProperty: Attribute<String?>?
|
||||
@@ -278,6 +295,7 @@ extension JSONAPIEntityOpenAPITests {
|
||||
public static var sample: Attributes {
|
||||
return Attributes(stringProperty: .init(value: "hello"),
|
||||
enumProperty: .init(value: .one),
|
||||
dateProperty: .init(value: Date()),
|
||||
optionalProperty: nil,
|
||||
nullableProperty: .init(value: nil),
|
||||
nullableOptionalProperty: nil)
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
//
|
||||
// OpenAPITests.swift
|
||||
// JSONAPIOpenAPITests
|
||||
//
|
||||
// Created by Mathew Polzin on 1/25/19.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import JSONAPI
|
||||
import JSONAPIOpenAPI
|
||||
|
||||
class OpenAPITests: XCTestCase {
|
||||
|
||||
func test_placeholder() {
|
||||
|
||||
let schemaInfo = OpenAPISchema.Info(title: "Cool API", version: "0.1.0")
|
||||
|
||||
let personResponse = OpenAPIResponse(description: "Successfully created a Person",
|
||||
content: [
|
||||
.json: .init(schema: .init(JSONReference.node(.init(type: \.schemas, selector: "person"))))
|
||||
])
|
||||
|
||||
let schemaPaths: [OpenAPISchema.PathComponents: OpenAPIPathItem] = [
|
||||
.init(["api","people"]):
|
||||
.operations(
|
||||
.init(parameters: [],
|
||||
post: OpenAPIPathItem.PathProperties.Operation(
|
||||
summary: "",
|
||||
operationId: "createPerson",
|
||||
parameters: [],
|
||||
responses: [
|
||||
.status(code: 200): .init(personResponse)
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
let schemaComponents = OpenAPIComponents(schemas: ["person": .reference(.file("person.json"))],
|
||||
parameters: [:])
|
||||
|
||||
let openAPISchema = OpenAPISchema(info: schemaInfo,
|
||||
paths: schemaPaths,
|
||||
components: schemaComponents)
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
|
||||
print(String(data: try! encoder.encode(openAPISchema), encoding: .utf8)!)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user