mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-07-10 12:18:40 -07:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 11a7727ac9 | |||
| e8bfbc881b | |||
| af757a2fac | |||
| 84955872f8 | |||
| cb2800abd4 | |||
| 85d5fef3c8 | |||
| e4ef61fd56 | |||
| 75ec4f156e | |||
| b45fc73188 | |||
| 3b73dc9989 | |||
| b2c81026f4 | |||
| 8d057b4398 | |||
| c8421cdd58 | |||
| cde10a8491 | |||
| ad05d3908a | |||
| 23b2b2e04f | |||
| 2988503d7d | |||
| 5ea83b07c1 | |||
| 2b59f54067 | |||
| 58a7c82436 | |||
| dc30cb3b9e | |||
| 7045373708 | |||
| 952fe8ba7e | |||
| 951c04ad44 | |||
| d1cf19f9fe | |||
| 59835fbe11 |
@@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import JSONAPI
|
||||
import Poly
|
||||
|
||||
// MARK: - Preamble (setup)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import Foundation
|
||||
import JSONAPI
|
||||
import JSONAPIOpenAPI
|
||||
import Poly
|
||||
|
||||
// print Entity Schema
|
||||
let encoder = JSONEncoder()
|
||||
@@ -15,16 +16,31 @@ print("====")
|
||||
print(personSchemaData.map { String(data: $0, encoding: .utf8)! } ?? "Schema Construction Failed")
|
||||
print("====")
|
||||
|
||||
let dogDocumentSchemaData = try? encoder.encode(SingleDogDocument.openAPINodeWithExample())
|
||||
let dogDocumentSchemaData = try? encoder.encode(SingleDogDocument.openAPINodeWithExample(using: encoder))
|
||||
|
||||
print("Dog Document Schema")
|
||||
print("====")
|
||||
print(dogDocumentSchemaData.map { String(data: $0, encoding: .utf8)! } ?? "Schema Construction Failed")
|
||||
print("====")
|
||||
|
||||
let batchPersonSchemaData = try? encoder.encode(BatchPeopleDocument.openAPINodeWithExample())
|
||||
let batchPersonSchemaData = try? encoder.encode(BatchPeopleDocument.openAPINodeWithExample(using: encoder))
|
||||
|
||||
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)!)
|
||||
|
||||
@@ -843,4 +843,4 @@ This Framework is currently undocumented, but familiarity with `SwiftCheck` will
|
||||
# JSONAPI+OpenAPI
|
||||
The `JSONAPIOpenAPI` framework adds the ability to generate OpenAPI compliant JSON documentation of a JSONAPI Document.
|
||||
|
||||
This library is in its infancy. The documentation will grow as the framework becomes more complete.
|
||||
*This library is in its infancy. The documentation will grow as the framework becomes more complete.*
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
//
|
||||
|
||||
import JSONAPI
|
||||
import Foundation
|
||||
|
||||
extension Includes: OpenAPINodeType where I: OpenAPINodeType {
|
||||
public static func openAPINode() throws -> JSONNode {
|
||||
let includeNode = try I.openAPINode()
|
||||
extension Includes: OpenAPIEncodedNodeType where I: OpenAPIEncodedNodeType {
|
||||
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
|
||||
let includeNode = try I.openAPINode(using: encoder)
|
||||
|
||||
return .array(.init(format: .generic,
|
||||
required: true),
|
||||
@@ -18,114 +19,114 @@ extension Includes: OpenAPINodeType where I: OpenAPINodeType {
|
||||
}
|
||||
}
|
||||
|
||||
extension Include0: OpenAPINodeType {
|
||||
public static func openAPINode() throws -> JSONNode {
|
||||
extension Include0: OpenAPIEncodedNodeType {
|
||||
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
|
||||
throw OpenAPITypeError.invalidNode
|
||||
}
|
||||
}
|
||||
|
||||
extension Include1: OpenAPINodeType where A: OpenAPINodeType {
|
||||
public static func openAPINode() throws -> JSONNode {
|
||||
return try .one(of: [A.openAPINode()])
|
||||
extension Include1: OpenAPIEncodedNodeType where A: OpenAPIEncodedNodeType {
|
||||
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
|
||||
return try A.openAPINode(using: encoder)
|
||||
}
|
||||
}
|
||||
|
||||
extension Include2: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType {
|
||||
public static func openAPINode() throws -> JSONNode {
|
||||
extension Include2: OpenAPIEncodedNodeType where A: OpenAPIEncodedNodeType, B: OpenAPIEncodedNodeType {
|
||||
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
|
||||
return try .one(of: [
|
||||
A.openAPINode(),
|
||||
B.openAPINode()
|
||||
A.openAPINode(using: encoder),
|
||||
B.openAPINode(using: encoder)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
extension Include3: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType {
|
||||
public static func openAPINode() throws -> JSONNode {
|
||||
extension Include3: OpenAPIEncodedNodeType where A: OpenAPIEncodedNodeType, B: OpenAPIEncodedNodeType, C: OpenAPIEncodedNodeType {
|
||||
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
|
||||
return try .one(of: [
|
||||
A.openAPINode(),
|
||||
B.openAPINode(),
|
||||
C.openAPINode()
|
||||
A.openAPINode(using: encoder),
|
||||
B.openAPINode(using: encoder),
|
||||
C.openAPINode(using: encoder)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
extension Include4: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType {
|
||||
public static func openAPINode() throws -> JSONNode {
|
||||
extension Include4: OpenAPIEncodedNodeType where A: OpenAPIEncodedNodeType, B: OpenAPIEncodedNodeType, C: OpenAPIEncodedNodeType, D: OpenAPIEncodedNodeType {
|
||||
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
|
||||
return try .one(of: [
|
||||
A.openAPINode(),
|
||||
B.openAPINode(),
|
||||
C.openAPINode(),
|
||||
D.openAPINode()
|
||||
A.openAPINode(using: encoder),
|
||||
B.openAPINode(using: encoder),
|
||||
C.openAPINode(using: encoder),
|
||||
D.openAPINode(using: encoder)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
extension Include5: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType, E: OpenAPINodeType {
|
||||
public static func openAPINode() throws -> JSONNode {
|
||||
extension Include5: OpenAPIEncodedNodeType where A: OpenAPIEncodedNodeType, B: OpenAPIEncodedNodeType, C: OpenAPIEncodedNodeType, D: OpenAPIEncodedNodeType, E: OpenAPIEncodedNodeType {
|
||||
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
|
||||
return try .one(of: [
|
||||
A.openAPINode(),
|
||||
B.openAPINode(),
|
||||
C.openAPINode(),
|
||||
D.openAPINode(),
|
||||
E.openAPINode()
|
||||
A.openAPINode(using: encoder),
|
||||
B.openAPINode(using: encoder),
|
||||
C.openAPINode(using: encoder),
|
||||
D.openAPINode(using: encoder),
|
||||
E.openAPINode(using: encoder)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
extension Include6: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType, E: OpenAPINodeType, F: OpenAPINodeType {
|
||||
public static func openAPINode() throws -> JSONNode {
|
||||
extension Include6: OpenAPIEncodedNodeType where A: OpenAPIEncodedNodeType, B: OpenAPIEncodedNodeType, C: OpenAPIEncodedNodeType, D: OpenAPIEncodedNodeType, E: OpenAPIEncodedNodeType, F: OpenAPIEncodedNodeType {
|
||||
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
|
||||
return try .one(of: [
|
||||
A.openAPINode(),
|
||||
B.openAPINode(),
|
||||
C.openAPINode(),
|
||||
D.openAPINode(),
|
||||
E.openAPINode(),
|
||||
F.openAPINode()
|
||||
A.openAPINode(using: encoder),
|
||||
B.openAPINode(using: encoder),
|
||||
C.openAPINode(using: encoder),
|
||||
D.openAPINode(using: encoder),
|
||||
E.openAPINode(using: encoder),
|
||||
F.openAPINode(using: encoder)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
extension Include7: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType, E: OpenAPINodeType, F: OpenAPINodeType, G: OpenAPINodeType {
|
||||
public static func openAPINode() throws -> JSONNode {
|
||||
extension Include7: OpenAPIEncodedNodeType where A: OpenAPIEncodedNodeType, B: OpenAPIEncodedNodeType, C: OpenAPIEncodedNodeType, D: OpenAPIEncodedNodeType, E: OpenAPIEncodedNodeType, F: OpenAPIEncodedNodeType, G: OpenAPIEncodedNodeType {
|
||||
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
|
||||
return try .one(of: [
|
||||
A.openAPINode(),
|
||||
B.openAPINode(),
|
||||
C.openAPINode(),
|
||||
D.openAPINode(),
|
||||
E.openAPINode(),
|
||||
F.openAPINode(),
|
||||
G.openAPINode()
|
||||
A.openAPINode(using: encoder),
|
||||
B.openAPINode(using: encoder),
|
||||
C.openAPINode(using: encoder),
|
||||
D.openAPINode(using: encoder),
|
||||
E.openAPINode(using: encoder),
|
||||
F.openAPINode(using: encoder),
|
||||
G.openAPINode(using: encoder)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
extension Include8: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType, E: OpenAPINodeType, F: OpenAPINodeType, G: OpenAPINodeType, H: OpenAPINodeType {
|
||||
public static func openAPINode() throws -> JSONNode {
|
||||
extension Include8: OpenAPIEncodedNodeType where A: OpenAPIEncodedNodeType, B: OpenAPIEncodedNodeType, C: OpenAPIEncodedNodeType, D: OpenAPIEncodedNodeType, E: OpenAPIEncodedNodeType, F: OpenAPIEncodedNodeType, G: OpenAPIEncodedNodeType, H: OpenAPIEncodedNodeType {
|
||||
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
|
||||
return try .one(of: [
|
||||
A.openAPINode(),
|
||||
B.openAPINode(),
|
||||
C.openAPINode(),
|
||||
D.openAPINode(),
|
||||
E.openAPINode(),
|
||||
F.openAPINode(),
|
||||
G.openAPINode(),
|
||||
H.openAPINode()
|
||||
A.openAPINode(using: encoder),
|
||||
B.openAPINode(using: encoder),
|
||||
C.openAPINode(using: encoder),
|
||||
D.openAPINode(using: encoder),
|
||||
E.openAPINode(using: encoder),
|
||||
F.openAPINode(using: encoder),
|
||||
G.openAPINode(using: encoder),
|
||||
H.openAPINode(using: encoder)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
extension Include9: OpenAPINodeType where A: OpenAPINodeType, B: OpenAPINodeType, C: OpenAPINodeType, D: OpenAPINodeType, E: OpenAPINodeType, F: OpenAPINodeType, G: OpenAPINodeType, H: OpenAPINodeType, I: OpenAPINodeType {
|
||||
public static func openAPINode() throws -> JSONNode {
|
||||
extension Include9: OpenAPIEncodedNodeType where A: OpenAPIEncodedNodeType, B: OpenAPIEncodedNodeType, C: OpenAPIEncodedNodeType, D: OpenAPIEncodedNodeType, E: OpenAPIEncodedNodeType, F: OpenAPIEncodedNodeType, G: OpenAPIEncodedNodeType, H: OpenAPIEncodedNodeType, I: OpenAPIEncodedNodeType {
|
||||
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
|
||||
return try .one(of: [
|
||||
A.openAPINode(),
|
||||
B.openAPINode(),
|
||||
C.openAPINode(),
|
||||
D.openAPINode(),
|
||||
E.openAPINode(),
|
||||
F.openAPINode(),
|
||||
G.openAPINode(),
|
||||
H.openAPINode(),
|
||||
I.openAPINode()
|
||||
A.openAPINode(using: encoder),
|
||||
B.openAPINode(using: encoder),
|
||||
C.openAPINode(using: encoder),
|
||||
D.openAPINode(using: encoder),
|
||||
E.openAPINode(using: encoder),
|
||||
F.openAPINode(using: encoder),
|
||||
G.openAPINode(using: encoder),
|
||||
H.openAPINode(using: encoder),
|
||||
I.openAPINode(using: encoder)
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,11 +6,17 @@
|
||||
//
|
||||
|
||||
import JSONAPI
|
||||
import Foundation
|
||||
import AnyCodable
|
||||
|
||||
private protocol _Optional {}
|
||||
extension Optional: _Optional {}
|
||||
|
||||
private protocol Wrapper {
|
||||
associatedtype Wrapped
|
||||
}
|
||||
extension Optional: Wrapper {}
|
||||
|
||||
extension Attribute: OpenAPINodeType where RawValue: OpenAPINodeType {
|
||||
static public func openAPINode() throws -> JSONNode {
|
||||
// If the RawValue is not required, we actually consider it
|
||||
@@ -47,15 +53,40 @@ extension Attribute: WrappedRawOpenAPIType where RawValue: RawOpenAPINodeType {
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: GenericOpenAPINodeType where RawValue: GenericOpenAPINodeType {
|
||||
public static func genericOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode {
|
||||
// If the RawValue is not required, we actually consider it
|
||||
// nullable. To be not required is for the Attribute itself
|
||||
// to be optional.
|
||||
if try !RawValue.genericOpenAPINode(using: encoder).required {
|
||||
return try RawValue.genericOpenAPINode(using: encoder).requiredNode().nullableNode()
|
||||
}
|
||||
return try RawValue.genericOpenAPINode(using: encoder)
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: DateOpenAPINodeType where RawValue: DateOpenAPINodeType {
|
||||
public static func dateOpenAPINodeGuess(using encoder: JSONEncoder) -> JSONNode? {
|
||||
// If the RawValue is not required, we actually consider it
|
||||
// nullable. To be not required is for the Attribute itself
|
||||
// to be optional.
|
||||
if
|
||||
!(RawValue.dateOpenAPINodeGuess(using: encoder)?.required ?? true) {
|
||||
return RawValue.dateOpenAPINodeGuess(using: encoder)?.requiredNode().nullableNode()
|
||||
}
|
||||
return RawValue.dateOpenAPINodeGuess(using: encoder)
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: AnyJSONCaseIterable where RawValue: CaseIterable, RawValue: Codable {
|
||||
public static var allCases: [AnyCodable] {
|
||||
return (try? allCases(from: Array(RawValue.allCases))) ?? []
|
||||
public static func allCases(using encoder: JSONEncoder) -> [AnyCodable] {
|
||||
return (try? allCases(from: Array(RawValue.allCases), using: encoder)) ?? []
|
||||
}
|
||||
}
|
||||
|
||||
extension Attribute: AnyWrappedJSONCaseIterable where RawValue: AnyJSONCaseIterable {
|
||||
public static var allCases: [AnyCodable] {
|
||||
return RawValue.allCases
|
||||
public static func allCases(using encoder: JSONEncoder) -> [AnyCodable] {
|
||||
return RawValue.allCases(using: encoder)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +102,8 @@ extension TransformedAttribute: OpenAPINodeType where RawValue: OpenAPINodeType
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: conform TransformedAttribute to all of the above protocols that Attribute conforms to.
|
||||
|
||||
extension RelationshipType {
|
||||
static func relationshipNode(nullable: Bool, jsonType: String) -> JSONNode {
|
||||
let propertiesDict: [String: JSONNode] = [
|
||||
@@ -121,17 +154,19 @@ extension ToManyRelationship: OpenAPINodeType {
|
||||
}
|
||||
}
|
||||
|
||||
extension Entity: OpenAPINodeType where Description.Attributes: Sampleable, Description.Relationships: Sampleable {
|
||||
public static func openAPINode() throws -> JSONNode {
|
||||
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.
|
||||
|
||||
// TODO: metadata, links
|
||||
|
||||
let idNode = JSONNode.string(.init(format: .generic,
|
||||
required: true),
|
||||
.init())
|
||||
let idProperty = ("id", idNode)
|
||||
let idNode: JSONNode? = Id.RawType.self != Unidentified.self
|
||||
? JSONNode.string(.init(format: .generic,
|
||||
required: true),
|
||||
.init())
|
||||
: nil
|
||||
let idProperty = idNode.map { ("id", $0) }
|
||||
|
||||
let typeNode = JSONNode.string(.init(format: .generic,
|
||||
required: true,
|
||||
@@ -141,13 +176,13 @@ extension Entity: OpenAPINodeType where Description.Attributes: Sampleable, Desc
|
||||
|
||||
let attributesNode: JSONNode? = Description.Attributes.self == NoAttributes.self
|
||||
? nil
|
||||
: try Description.Attributes.genericObjectOpenAPINode()
|
||||
: try Description.Attributes.genericOpenAPINode(using: encoder)
|
||||
|
||||
let attributesProperty = attributesNode.map { ("attributes", $0) }
|
||||
|
||||
let relationshipsNode: JSONNode? = Description.Relationships.self == NoRelationships.self
|
||||
? nil
|
||||
: try Description.Relationships.genericObjectOpenAPINode()
|
||||
: try Description.Relationships.genericOpenAPINode(using: encoder)
|
||||
|
||||
let relationshipsProperty = relationshipsNode.map { ("relationships", $0) }
|
||||
|
||||
@@ -164,34 +199,34 @@ extension Entity: OpenAPINodeType where Description.Attributes: Sampleable, Desc
|
||||
}
|
||||
}
|
||||
|
||||
extension SingleResourceBody: OpenAPINodeType where Entity: OpenAPINodeType {
|
||||
public static func openAPINode() throws -> JSONNode {
|
||||
return try Entity.openAPINode()
|
||||
extension SingleResourceBody: OpenAPIEncodedNodeType where Entity: OpenAPIEncodedNodeType {
|
||||
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
|
||||
return try Entity.openAPINode(using: encoder)
|
||||
}
|
||||
}
|
||||
|
||||
extension ManyResourceBody: OpenAPINodeType where Entity: OpenAPINodeType {
|
||||
public static func openAPINode() throws -> JSONNode {
|
||||
extension ManyResourceBody: OpenAPIEncodedNodeType where Entity: OpenAPIEncodedNodeType {
|
||||
public static func openAPINode(using encoder: JSONEncoder) throws -> JSONNode {
|
||||
return .array(.init(format: .generic,
|
||||
required: true),
|
||||
.init(items: try Entity.openAPINode()))
|
||||
.init(items: try Entity.openAPINode(using: encoder)))
|
||||
}
|
||||
}
|
||||
|
||||
extension Document: OpenAPINodeType where PrimaryResourceBody: OpenAPINodeType, IncludeType: OpenAPINodeType {
|
||||
public static func openAPINode() throws -> JSONNode {
|
||||
extension Document: OpenAPIEncodedNodeType where PrimaryResourceBody: OpenAPIEncodedNodeType, IncludeType: OpenAPIEncodedNodeType {
|
||||
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
|
||||
|
||||
let primaryDataNode: JSONNode? = try PrimaryResourceBody.openAPINode()
|
||||
let primaryDataNode: JSONNode? = try PrimaryResourceBody.openAPINode(using: encoder)
|
||||
|
||||
let primaryDataProperty = primaryDataNode.map { ("data", $0) }
|
||||
|
||||
let includeNode: JSONNode?
|
||||
do {
|
||||
includeNode = try Includes<Include>.openAPINode()
|
||||
includeNode = try Includes<Include>.openAPINode(using: encoder)
|
||||
} catch let err as OpenAPITypeError {
|
||||
guard err == .invalidNode else {
|
||||
guard case .invalidNode = err else {
|
||||
throw err
|
||||
}
|
||||
includeNode = nil
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// Date+OpenAPI.swift
|
||||
// JSONAPIOpenAPI
|
||||
//
|
||||
// Created by Mathew Polzin on 1/24/19.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension Date: DateOpenAPINodeType {
|
||||
public static func dateOpenAPINodeGuess(using encoder: JSONEncoder) -> JSONNode? {
|
||||
|
||||
switch encoder.dateEncodingStrategy {
|
||||
case .deferredToDate, .custom:
|
||||
// I don't know if we can say anything about this case without
|
||||
// encoding the Date and looking at it, which is what `primitiveGuess()`
|
||||
// does.
|
||||
return nil
|
||||
|
||||
case .secondsSince1970,
|
||||
.millisecondsSince1970:
|
||||
return .number(.init(format: .double,
|
||||
required: true),
|
||||
.init())
|
||||
|
||||
case .iso8601:
|
||||
return .string(.init(format: .dateTime,
|
||||
required: true),
|
||||
.init())
|
||||
|
||||
case .formatted(let formatter):
|
||||
let hasTime = formatter.timeStyle != .none
|
||||
let format: JSONTypeFormat.StringFormat = hasTime ? .dateTime : .date
|
||||
|
||||
return .string(.init(format: format,
|
||||
required: true),
|
||||
.init())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -189,6 +189,266 @@ extension JSONNode: Encodable {
|
||||
var container = encoder.container(keyedBy: SubschemaCodingKeys.self)
|
||||
|
||||
try container.encode(node, forKey: .not)
|
||||
|
||||
case .reference(let reference):
|
||||
var container = encoder.singleValueContainer()
|
||||
|
||||
try container.encode(reference)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension JSONReference: Encodable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case ref = "$ref"
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
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, forKey: .ref)
|
||||
}
|
||||
}
|
||||
|
||||
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 OpenAPIRequestBody: Encodable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case description
|
||||
case content
|
||||
case required
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
if description != nil {
|
||||
try container.encode(description, forKey: .description)
|
||||
}
|
||||
|
||||
// Hack to work around Dictionary encoding
|
||||
// itself as an array in this case:
|
||||
let stringKeyedDict = Dictionary(
|
||||
content.map { ($0.key.rawValue, $0.value) },
|
||||
uniquingKeysWith: { $1 }
|
||||
)
|
||||
try container.encode(stringKeyedDict, forKey: .content)
|
||||
|
||||
try container.encode(required, forKey: .required)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
if requestBody != nil {
|
||||
try container.encode(requestBody, forKey: .requestBody)
|
||||
}
|
||||
|
||||
// Hack to work around Dictionary encoding
|
||||
// itself as an array in this case:
|
||||
let stringKeyedDict = Dictionary(
|
||||
responses.map { ($0.key.rawValue, $0.value) },
|
||||
uniquingKeysWith: { $1 }
|
||||
)
|
||||
try container.encode(stringKeyedDict, 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 OpenAPIResponse: Encodable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case description
|
||||
case headers
|
||||
case content
|
||||
case links
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
try container.encode(description, forKey: .description)
|
||||
|
||||
// Hack to work around Dictionary encoding
|
||||
// itself as an array in this case:
|
||||
let stringKeyedDict = Dictionary(
|
||||
content.map { ($0.key.rawValue, $0.value) },
|
||||
uniquingKeysWith: { $1 }
|
||||
)
|
||||
try container.encode(stringKeyedDict, forKey: .content)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension OpenAPISchema: Encodable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case openAPIVersion = "openapi"
|
||||
case info
|
||||
case servers
|
||||
case paths
|
||||
case components
|
||||
case security
|
||||
case tags
|
||||
case externalDocs
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
try container.encode(openAPIVersion, forKey: .openAPIVersion)
|
||||
|
||||
try container.encode(info, forKey: .info)
|
||||
|
||||
// Hack to work around Dictionary encoding
|
||||
// itself as an array in this case:
|
||||
let stringKeyedDict = Dictionary(
|
||||
paths.map { ($0.key.rawValue, $0.value) },
|
||||
uniquingKeysWith: { $1 }
|
||||
)
|
||||
try container.encode(stringKeyedDict, forKey: .paths)
|
||||
|
||||
try container.encode(components, forKey: .components)
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,6 +6,7 @@
|
||||
//
|
||||
|
||||
import AnyCodable
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
|
||||
@@ -56,8 +57,14 @@ extension Optional: DoubleWrappedRawOpenAPIType where Wrapped: WrappedRawOpenAPI
|
||||
}
|
||||
|
||||
extension Optional: AnyJSONCaseIterable where Wrapped: CaseIterable, Wrapped: Codable {
|
||||
public static var allCases: [AnyCodable] {
|
||||
return (try? allCases(from: Array(Wrapped.allCases))) ?? []
|
||||
public static func allCases(using encoder: JSONEncoder) -> [AnyCodable] {
|
||||
return (try? allCases(from: Array(Wrapped.allCases), using: encoder)) ?? []
|
||||
}
|
||||
}
|
||||
|
||||
extension Optional: DateOpenAPINodeType where Wrapped: DateOpenAPINodeType {
|
||||
static public func dateOpenAPINodeGuess(using encoder: JSONEncoder) -> JSONNode? {
|
||||
return Wrapped.dateOpenAPINodeGuess(using: encoder)?.optionalNode()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
//
|
||||
// JSONAPI+Sampleable.swift
|
||||
// JSONAPIOpenAPI
|
||||
//
|
||||
// Created by Mathew Polzin on 1/24/19.
|
||||
//
|
||||
|
||||
import JSONAPI
|
||||
|
||||
extension NoAttributes: Sampleable {
|
||||
public static var sample: NoAttributes {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
extension NoRelationships: Sampleable {
|
||||
public static var sample: NoRelationships {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
extension NoMetadata: Sampleable {
|
||||
public static var sample: NoMetadata {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
extension NoLinks: Sampleable {
|
||||
public static var sample: NoLinks {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
extension NoAPIDescription: Sampleable {
|
||||
public static var sample: NoAPIDescription {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
extension UnknownJSONAPIError: Sampleable {
|
||||
public static var sample: UnknownJSONAPIError {
|
||||
return .unknownError
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
extension SingleResourceBody: Sampleable where Entity: Sampleable {
|
||||
public static var sample: SingleResourceBody<Entity> {
|
||||
return .init(entity: Entity.sample)
|
||||
}
|
||||
}
|
||||
|
||||
extension ManyResourceBody: Sampleable where Entity: Sampleable {
|
||||
public static var sample: ManyResourceBody<Entity> {
|
||||
return .init(entities: Entity.samples)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
//
|
||||
// Sampleable+OpenAPI.swift
|
||||
// JSONAPIOpenAPI
|
||||
//
|
||||
// Created by Mathew Polzin on 1/24/19.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import AnyCodable
|
||||
|
||||
public typealias SampleableOpenAPIType = Sampleable & GenericOpenAPINodeType
|
||||
|
||||
extension Sampleable where Self: Encodable {
|
||||
public static func genericOpenAPINode(using encoder: JSONEncoder) throws -> JSONNode {
|
||||
|
||||
// short circuit for dates
|
||||
if let dateType = self as? Date.Type,
|
||||
let node = try dateType.dateOpenAPINodeGuess(using: encoder) ?? primitiveGuess(using: encoder) {
|
||||
return node
|
||||
}
|
||||
|
||||
let mirror = Mirror(reflecting: Self.sample)
|
||||
let properties: [(String, JSONNode)] = try mirror.children.compactMap { child in
|
||||
|
||||
// see if we can enumerate the possible values
|
||||
let maybeAllCases: [AnyCodable]? = {
|
||||
switch type(of: child.value) {
|
||||
case let valType as AnyJSONCaseIterable.Type:
|
||||
return valType.allCases(using: encoder)
|
||||
case let valType as AnyWrappedJSONCaseIterable.Type:
|
||||
return valType.allCases(using: encoder)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
|
||||
// try to snag an OpenAPI Node
|
||||
let maybeOpenAPINode: JSONNode? = try {
|
||||
switch type(of: child.value) {
|
||||
case let valType as OpenAPINodeType.Type:
|
||||
return try valType.openAPINode()
|
||||
|
||||
case let valType as RawOpenAPINodeType.Type:
|
||||
return try valType.rawOpenAPINode()
|
||||
|
||||
case let valType as WrappedRawOpenAPIType.Type:
|
||||
return try valType.wrappedOpenAPINode()
|
||||
|
||||
case let valType as DoubleWrappedRawOpenAPIType.Type:
|
||||
return try valType.wrappedOpenAPINode()
|
||||
|
||||
case let valType as GenericOpenAPINodeType.Type:
|
||||
return try valType.genericOpenAPINode(using: encoder)
|
||||
|
||||
case let valType as DateOpenAPINodeType.Type:
|
||||
return valType.dateOpenAPINodeGuess(using: encoder)
|
||||
|
||||
default:
|
||||
throw OpenAPITypeError.unknownNodeType(self)
|
||||
// return nil
|
||||
}
|
||||
}()
|
||||
|
||||
// put it all together
|
||||
let newNode: JSONNode?
|
||||
if let allCases = maybeAllCases,
|
||||
let openAPINode = maybeOpenAPINode {
|
||||
newNode = try openAPINode.with(allowedValues: allCases)
|
||||
} else {
|
||||
newNode = maybeOpenAPINode
|
||||
}
|
||||
|
||||
return zip(child.label, newNode) { ($0, $1) }
|
||||
}
|
||||
|
||||
// if there are no properties, let's see if we are dealing
|
||||
// with a primitive.
|
||||
if properties.count == 0,
|
||||
let primitive = try primitiveGuess(using: encoder) {
|
||||
return primitive
|
||||
}
|
||||
|
||||
// There should not be any duplication of keys since these are
|
||||
// property names, but rather than risk runtime exception, we just
|
||||
// fail to the newer value arbitrarily
|
||||
let propertiesDict = Dictionary(properties) { _, value2 in value2 }
|
||||
|
||||
return .object(.init(format: .generic,
|
||||
required: true),
|
||||
.init(properties: propertiesDict))
|
||||
}
|
||||
|
||||
private static func primitiveGuess(using encoder: JSONEncoder) throws -> JSONNode? {
|
||||
|
||||
let data = try encoder.encode(PrimitiveWrapper(primitive: Self.sample))
|
||||
let wrappedValue = try JSONSerialization.jsonObject(with: data, options: [.allowFragments])
|
||||
|
||||
guard let wrapperDict = wrappedValue as? [String: Any],
|
||||
wrapperDict.contains(where: { $0.key == "primitive" }) else {
|
||||
throw OpenAPICodableError.primitiveGuessFailed
|
||||
}
|
||||
|
||||
let value = (wrappedValue as! [String: Any])["primitive"]!
|
||||
|
||||
return try {
|
||||
switch type(of: value) {
|
||||
case let valType as OpenAPINodeType.Type:
|
||||
return try valType.openAPINode()
|
||||
|
||||
case let valType as RawOpenAPINodeType.Type:
|
||||
return try valType.rawOpenAPINode()
|
||||
|
||||
case let valType as WrappedRawOpenAPIType.Type:
|
||||
return try valType.wrappedOpenAPINode()
|
||||
|
||||
case let valType as DoubleWrappedRawOpenAPIType.Type:
|
||||
return try valType.wrappedOpenAPINode()
|
||||
|
||||
case let valType as GenericOpenAPINodeType.Type:
|
||||
return try valType.genericOpenAPINode(using: encoder)
|
||||
|
||||
case let valType as DateOpenAPINodeType.Type:
|
||||
return valType.dateOpenAPINodeGuess(using: encoder)
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}() ?? {
|
||||
switch value {
|
||||
case is String:
|
||||
return .string(.init(format: .generic,
|
||||
required: true),
|
||||
.init())
|
||||
|
||||
case is Int:
|
||||
return .integer(.init(format: .generic,
|
||||
required: true),
|
||||
.init())
|
||||
|
||||
case is Double:
|
||||
return .number(.init(format: .double,
|
||||
required: true),
|
||||
.init())
|
||||
|
||||
case is Bool:
|
||||
return .boolean(.init(format: .generic,
|
||||
required: true))
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
// The following wrapper is only needed because JSONEncoder cannot yet encode
|
||||
// JSON fragments. It is a very unfortunate limitation that requires silly
|
||||
// workarounds in edge cases like this.
|
||||
private struct PrimitiveWrapper<Wrapped: Encodable>: Encodable {
|
||||
let primitive: Wrapped
|
||||
}
|
||||
@@ -5,8 +5,7 @@
|
||||
// Created by Mathew Polzin on 1/15/19.
|
||||
//
|
||||
|
||||
import JSONAPI
|
||||
import AnyCodable
|
||||
import Foundation
|
||||
|
||||
/// A Sampleable type can provide a sample value.
|
||||
/// This is useful for reflection.
|
||||
@@ -48,91 +47,56 @@ public extension Sampleable {
|
||||
}
|
||||
|
||||
extension Sampleable {
|
||||
public static func genericObjectOpenAPINode() throws -> JSONNode {
|
||||
let mirror = Mirror(reflecting: Self.sample)
|
||||
let properties: [(String, JSONNode)] = try mirror.children.compactMap { child in
|
||||
public static func samples<S1: Sampleable>(using s1: S1.Type, with constructor: (S1) -> Self) -> [Self] {
|
||||
return S1.samples.map(constructor)
|
||||
}
|
||||
|
||||
// see if we can enumerate the possible values
|
||||
let maybeAllCases: [AnyCodable]? = {
|
||||
switch type(of: child.value) {
|
||||
case let valType as AnyJSONCaseIterable.Type:
|
||||
return valType.allCases
|
||||
case let valType as AnyWrappedJSONCaseIterable.Type:
|
||||
return valType.allCases
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
public static func samples<S1: Sampleable, S2: Sampleable>(using s1: S1.Type, _ s2: S2.Type, with constructor: (S1, S2) -> Self) -> [Self] {
|
||||
return zip(S1.samples, S2.samples).map(constructor)
|
||||
}
|
||||
|
||||
// try to snag an OpenAPI Node
|
||||
let maybeOpenAPINode: JSONNode? = try {
|
||||
switch type(of: child.value) {
|
||||
case let valType as OpenAPINodeType.Type:
|
||||
return try valType.openAPINode()
|
||||
public static func samples<S1: Sampleable, S2: Sampleable, S3: Sampleable>(using s1: S1.Type, _ s2: S2.Type, _ s3: S3.Type, with constructor: (S1, S2, S3) -> Self) -> [Self] {
|
||||
return zip3(S1.samples, S2.samples, S3.samples).map(constructor)
|
||||
}
|
||||
|
||||
case let valType as RawOpenAPINodeType.Type:
|
||||
return try valType.rawOpenAPINode()
|
||||
public static func samples<S1: Sampleable, S2: Sampleable, S3: Sampleable, S4: Sampleable>(using s1: S1.Type, _ s2: S2.Type, _ s3: S3.Type, _ s4: S4.Type, with constructor: (S1, S2, S3, S4) -> Self) -> [Self] {
|
||||
return zip4(S1.samples, S2.samples, S3.samples, S4.samples).map(constructor)
|
||||
}
|
||||
|
||||
case let valType as WrappedRawOpenAPIType.Type:
|
||||
return try valType.wrappedOpenAPINode()
|
||||
public static func samples<S1: Sampleable, S2: Sampleable, S3: Sampleable, S4: Sampleable, S5: Sampleable>(using s1: S1.Type, _ s2: S2.Type, _ s3: S3.Type, _ s4: S4.Type, _ s5: S5.Type, with constructor: (S1, S2, S3, S4, S5) -> Self) -> [Self] {
|
||||
return zip5(S1.samples, S2.samples, S3.samples, S4.samples, S5.samples).map(constructor)
|
||||
}
|
||||
|
||||
case let valType as DoubleWrappedRawOpenAPIType.Type:
|
||||
return try valType.wrappedOpenAPINode()
|
||||
public static func samples<S1: Sampleable, S2: Sampleable, S3: Sampleable, S4: Sampleable, S5: Sampleable, S6: Sampleable>(using s1: S1.Type, _ s2: S2.Type, _ s3: S3.Type, _ s4: S4.Type, _ s5: S5.Type, _ s6: S6.Type, with constructor: (S1, S2, S3, S4, S5, S6) -> Self) -> [Self] {
|
||||
// the compiler craps out at zip6. breaking it down makes the difference.
|
||||
let firstZip = zip3(S1.samples, S2.samples, S3.samples)
|
||||
let secondZip = zip3(S4.samples, S5.samples, S6.samples)
|
||||
return zip(firstZip, secondZip).map { arg in (arg.0.0, arg.0.1, arg.0.2, arg.1.0, arg.1.1, arg.1.2) }.map(constructor)
|
||||
}
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}()
|
||||
public static func samples<S1: Sampleable, S2: Sampleable, S3: Sampleable, S4: Sampleable, S5: Sampleable, S6: Sampleable, S7: Sampleable>(using s1: S1.Type, _ s2: S2.Type, _ s3: S3.Type, _ s4: S4.Type, _ s5: S5.Type, _ s6: S6.Type, _ s7: S7.Type, with constructor: (S1, S2, S3, S4, S5, S6, S7) -> Self) -> [Self] {
|
||||
// the compiler craps out at zip6. breaking it down makes the difference.
|
||||
let firstZip = zip3(S1.samples, S2.samples, S3.samples)
|
||||
let secondZip = zip4(S4.samples, S5.samples, S6.samples, S7.samples)
|
||||
return zip(firstZip, secondZip).map { arg in (arg.0.0, arg.0.1, arg.0.2, arg.1.0, arg.1.1, arg.1.2, arg.1.3) }.map(constructor)
|
||||
}
|
||||
|
||||
// put it all together
|
||||
let newNode: JSONNode?
|
||||
if let allCases = maybeAllCases,
|
||||
let openAPINode = maybeOpenAPINode {
|
||||
newNode = try openAPINode.with(allowedValues: allCases)
|
||||
} else {
|
||||
newNode = maybeOpenAPINode
|
||||
}
|
||||
public static func samples<S1: Sampleable, S2: Sampleable, S3: Sampleable, S4: Sampleable, S5: Sampleable, S6: Sampleable, S7: Sampleable, S8: Sampleable>(using s1: S1.Type, _ s2: S2.Type, _ s3: S3.Type, _ s4: S4.Type, _ s5: S5.Type, _ s6: S6.Type, _ s7: S7.Type, _ s8: S8.Type, with constructor: (S1, S2, S3, S4, S5, S6, S7, S8) -> Self) -> [Self] {
|
||||
// the compiler craps out at zip6. breaking it down makes the difference.
|
||||
let firstZip = zip4(S1.samples, S2.samples, S3.samples, S4.samples)
|
||||
let secondZip = zip4(S5.samples, S6.samples, S7.samples, S8.samples)
|
||||
return zip(firstZip, secondZip).map { arg in (arg.0.0, arg.0.1, arg.0.2, arg.0.3, arg.1.0, arg.1.1, arg.1.2, arg.1.3) }.map(constructor)
|
||||
}
|
||||
|
||||
return zip(child.label, newNode) { ($0, $1) }
|
||||
}
|
||||
@inlinable static func zip3<A: Sequence, B: Sequence, C: Sequence>(_ a: A, _ b: B, _ c: C) -> [(A.Element, B.Element, C.Element)] {
|
||||
return zip(a, zip(b, c)).map { arg in (arg.0, arg.1.0, arg.1.1) }
|
||||
}
|
||||
|
||||
// There should not be any duplication of keys since these are
|
||||
// property names, but rather than risk runtime exception, we just
|
||||
// fail to the newer value arbitrarily
|
||||
let propertiesDict = Dictionary(properties) { _, value2 in value2 }
|
||||
@inlinable static func zip4<A: Sequence, B: Sequence, C: Sequence, D: Sequence>(_ a: A, _ b: B, _ c: C, _ d: D) -> [(A.Element, B.Element, C.Element, D.Element)] {
|
||||
return zip(a, zip(b, zip(c, d))).map { arg in (arg.0, arg.1.0, arg.1.1.0, arg.1.1.1) }
|
||||
}
|
||||
|
||||
return .object(.init(format: .generic,
|
||||
required: true),
|
||||
.init(properties: propertiesDict))
|
||||
}
|
||||
}
|
||||
|
||||
extension NoAttributes: Sampleable {
|
||||
public static var sample: NoAttributes {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
extension NoRelationships: Sampleable {
|
||||
public static var sample: NoRelationships {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
extension NoMetadata: Sampleable {
|
||||
public static var sample: NoMetadata {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
extension NoLinks: Sampleable {
|
||||
public static var sample: NoLinks {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
extension UnknownJSONAPIError: Sampleable {
|
||||
public static var sample: UnknownJSONAPIError {
|
||||
return .unknownError
|
||||
@inlinable static func zip5<A: Sequence, B: Sequence, C: Sequence, D: Sequence, E: Sequence>(_ a: A, _ b: B, _ c: C, _ d: D, _ e: E) -> [(A.Element, B.Element, C.Element, D.Element, E.Element)] {
|
||||
return zip(a, zip(b, zip(c, zip(d, e)))).map { arg in (arg.0, arg.1.0, arg.1.1.0, arg.1.1.1.0, arg.1.1.1.1) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import XCTest
|
||||
import JSONAPI
|
||||
import JSONAPIOpenAPI
|
||||
import SwiftCheck
|
||||
import AnyCodable
|
||||
|
||||
class JSONAPIAttributeOpenAPITests: XCTestCase {
|
||||
@@ -504,6 +505,379 @@ extension JSONAPIAttributeOpenAPITests {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Date
|
||||
extension JSONAPIAttributeOpenAPITests {
|
||||
func test_DateStringAttribute() {
|
||||
// TEST:
|
||||
// Encoder is set to use
|
||||
// formatter with date
|
||||
// with no time.
|
||||
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .medium
|
||||
dateFormatter.timeStyle = .none
|
||||
dateFormatter.locale = Locale(identifier: "en_US")
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
encoder.dateEncodingStrategy = .formatted(dateFormatter)
|
||||
|
||||
let node = Attribute<Date>.dateOpenAPINodeGuess(using: encoder)
|
||||
|
||||
XCTAssertNotNil(node)
|
||||
|
||||
XCTAssertTrue(node?.required ?? false)
|
||||
XCTAssertEqual(node?.jsonTypeFormat, .string(.date))
|
||||
|
||||
guard case .string(let contextA, let stringContext)? = node else {
|
||||
XCTFail("Expected string Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(contextA, .init(format: .date,
|
||||
required: true,
|
||||
nullable: false,
|
||||
allowedValues: nil))
|
||||
|
||||
XCTAssertEqual(stringContext, .init())
|
||||
}
|
||||
|
||||
func test_DateStringAttribute_Sampleable() {
|
||||
// TEST:
|
||||
// Encoder is set to use
|
||||
// formatter with date
|
||||
// with no time.
|
||||
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .medium
|
||||
dateFormatter.timeStyle = .none
|
||||
dateFormatter.locale = Locale(identifier: "en_US")
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
encoder.dateEncodingStrategy = .formatted(dateFormatter)
|
||||
|
||||
let node = try! Attribute<Date>.genericOpenAPINode(using: encoder)
|
||||
|
||||
XCTAssertTrue(node.required)
|
||||
XCTAssertEqual(node.jsonTypeFormat, .string(.date))
|
||||
|
||||
guard case .string(let contextA, let stringContext) = node else {
|
||||
XCTFail("Expected string Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(contextA, .init(format: .date,
|
||||
required: true,
|
||||
nullable: false,
|
||||
allowedValues: nil))
|
||||
|
||||
XCTAssertEqual(stringContext, .init())
|
||||
}
|
||||
|
||||
func test_DateTimeStringAttribute() {
|
||||
// TEST:
|
||||
// Encoder is set to use
|
||||
// formatter with date
|
||||
// with time.
|
||||
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .medium
|
||||
dateFormatter.timeStyle = .short
|
||||
dateFormatter.locale = Locale(identifier: "en_US")
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
encoder.dateEncodingStrategy = .formatted(dateFormatter)
|
||||
|
||||
let node = Attribute<Date>.dateOpenAPINodeGuess(using: encoder)
|
||||
|
||||
XCTAssertNotNil(node)
|
||||
|
||||
XCTAssertTrue(node?.required ?? false)
|
||||
XCTAssertEqual(node?.jsonTypeFormat, .string(.dateTime))
|
||||
|
||||
guard case .string(let contextA, let stringContext)? = node else {
|
||||
XCTFail("Expected string Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(contextA, .init(format: .dateTime,
|
||||
required: true,
|
||||
nullable: false,
|
||||
allowedValues: nil))
|
||||
|
||||
XCTAssertEqual(stringContext, .init())
|
||||
}
|
||||
|
||||
func test_DateTimeStringAttribute_Sampleable() {
|
||||
// TEST:
|
||||
// Encoder is set to use
|
||||
// formatter with date
|
||||
// with time.
|
||||
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .medium
|
||||
dateFormatter.timeStyle = .short
|
||||
dateFormatter.locale = Locale(identifier: "en_US")
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
encoder.dateEncodingStrategy = .formatted(dateFormatter)
|
||||
|
||||
let node = try! Attribute<Date>.genericOpenAPINode(using: encoder)
|
||||
|
||||
XCTAssertTrue(node.required)
|
||||
XCTAssertEqual(node.jsonTypeFormat, .string(.dateTime))
|
||||
|
||||
guard case .string(let contextA, let stringContext) = node else {
|
||||
XCTFail("Expected string Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(contextA, .init(format: .dateTime,
|
||||
required: true,
|
||||
nullable: false,
|
||||
allowedValues: nil))
|
||||
|
||||
XCTAssertEqual(stringContext, .init())
|
||||
}
|
||||
|
||||
func test_8601DateStringAttribute() {
|
||||
if #available(OSX 10.12, *) {
|
||||
// TEST:
|
||||
// Encoder is set to use
|
||||
// iso8601 date format
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
encoder.dateEncodingStrategy = .iso8601
|
||||
|
||||
let node = Attribute<Date>.dateOpenAPINodeGuess(using: encoder)
|
||||
|
||||
XCTAssertNotNil(node)
|
||||
|
||||
XCTAssertTrue(node?.required ?? false)
|
||||
XCTAssertEqual(node?.jsonTypeFormat, .string(.dateTime))
|
||||
|
||||
guard case .string(let contextA, let stringContext)? = node else {
|
||||
XCTFail("Expected string Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(contextA, .init(format: .dateTime,
|
||||
required: true,
|
||||
nullable: false,
|
||||
allowedValues: nil))
|
||||
|
||||
XCTAssertEqual(stringContext, .init())
|
||||
}
|
||||
}
|
||||
|
||||
func test_8601DateStringAttribute_Sampleable() {
|
||||
if #available(OSX 10.12, *) {
|
||||
// TEST:
|
||||
// Encoder is set to use
|
||||
// iso8601 date format
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
encoder.dateEncodingStrategy = .iso8601
|
||||
|
||||
let node = try! Attribute<Date>.genericOpenAPINode(using: encoder)
|
||||
|
||||
XCTAssertTrue(node.required)
|
||||
XCTAssertEqual(node.jsonTypeFormat, .string(.dateTime))
|
||||
|
||||
guard case .string(let contextA, let stringContext) = node else {
|
||||
XCTFail("Expected string Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(contextA, .init(format: .dateTime,
|
||||
required: true,
|
||||
nullable: false,
|
||||
allowedValues: nil))
|
||||
|
||||
XCTAssertEqual(stringContext, .init())
|
||||
}
|
||||
}
|
||||
|
||||
func test_DateNumberAttribute() {
|
||||
// TEST:
|
||||
// Encoder is set to use
|
||||
// seconds since 1970 as
|
||||
// date format
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
encoder.dateEncodingStrategy = .secondsSince1970
|
||||
|
||||
let node = Attribute<Date>.dateOpenAPINodeGuess(using: encoder)
|
||||
|
||||
XCTAssertNotNil(node)
|
||||
|
||||
XCTAssertTrue(node?.required ?? false)
|
||||
XCTAssertEqual(node?.jsonTypeFormat, .number(.double))
|
||||
|
||||
guard case .number(let contextA, let numberContext)? = node else {
|
||||
XCTFail("Expected string Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(contextA, .init(format: .double,
|
||||
required: true,
|
||||
nullable: false,
|
||||
allowedValues: nil))
|
||||
|
||||
XCTAssertEqual(numberContext, .init())
|
||||
}
|
||||
|
||||
func test_DateNumberAttribute_Sampleable() {
|
||||
// TEST:
|
||||
// Encoder is set to use
|
||||
// seconds since 1970 as
|
||||
// date format
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
encoder.dateEncodingStrategy = .secondsSince1970
|
||||
|
||||
let node = try! Attribute<Date>.genericOpenAPINode(using: encoder)
|
||||
|
||||
XCTAssertTrue(node.required)
|
||||
XCTAssertEqual(node.jsonTypeFormat, .number(.double))
|
||||
|
||||
guard case .number(let contextA, let numberContext) = node else {
|
||||
XCTFail("Expected string Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(contextA, .init(format: .double,
|
||||
required: true,
|
||||
nullable: false,
|
||||
allowedValues: nil))
|
||||
|
||||
XCTAssertEqual(numberContext, .init())
|
||||
}
|
||||
|
||||
func test_DateDeferredAttribute() {
|
||||
// TEST:
|
||||
// Encoder is set to use
|
||||
// Date default encoding
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
encoder.dateEncodingStrategy = .deferredToDate
|
||||
|
||||
let node = Attribute<Date>.dateOpenAPINodeGuess(using: encoder)
|
||||
|
||||
XCTAssertNil(node)
|
||||
}
|
||||
|
||||
func test_DateDeferredAttribute_Sampleable() {
|
||||
// TEST:
|
||||
// Encoder is set to use
|
||||
// Date default encoding
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
encoder.dateEncodingStrategy = .deferredToDate
|
||||
|
||||
let node = try! Attribute<Date>.genericOpenAPINode(using: encoder)
|
||||
|
||||
XCTAssertTrue(node.required)
|
||||
XCTAssertEqual(node.jsonTypeFormat, .number(.double))
|
||||
|
||||
guard case .number(let contextA, let numberContext) = node else {
|
||||
XCTFail("Expected string Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(contextA, .init(format: .double,
|
||||
required: true,
|
||||
nullable: false,
|
||||
allowedValues: nil))
|
||||
|
||||
XCTAssertEqual(numberContext, .init())
|
||||
}
|
||||
|
||||
func test_NullableDateAttribute() {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
encoder.dateEncodingStrategy = .secondsSince1970
|
||||
|
||||
let node = Attribute<Date?>.dateOpenAPINodeGuess(using: encoder)
|
||||
|
||||
XCTAssertNotNil(node)
|
||||
|
||||
XCTAssertTrue(node?.required ?? false)
|
||||
XCTAssertEqual(node?.jsonTypeFormat, .number(.double))
|
||||
|
||||
guard case .number(let contextA, let numberContext)? = node else {
|
||||
XCTFail("Expected string Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(contextA, .init(format: .double,
|
||||
required: true,
|
||||
nullable: true,
|
||||
allowedValues: nil))
|
||||
|
||||
XCTAssertEqual(numberContext, .init())
|
||||
}
|
||||
|
||||
func test_OptionalDateAttribute() {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
encoder.dateEncodingStrategy = .secondsSince1970
|
||||
|
||||
let node = Attribute<Date>?.dateOpenAPINodeGuess(using: encoder)
|
||||
|
||||
XCTAssertNotNil(node)
|
||||
|
||||
XCTAssertFalse(node?.required ?? true)
|
||||
XCTAssertEqual(node?.jsonTypeFormat, .number(.double))
|
||||
|
||||
guard case .number(let contextA, let numberContext)? = node else {
|
||||
XCTFail("Expected string Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(contextA, .init(format: .double,
|
||||
required: false,
|
||||
nullable: false,
|
||||
allowedValues: nil))
|
||||
|
||||
XCTAssertEqual(numberContext, .init())
|
||||
}
|
||||
|
||||
func test_OptionalNullableDateAttribute() {
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
encoder.dateEncodingStrategy = .secondsSince1970
|
||||
|
||||
let node = Attribute<Date?>?.dateOpenAPINodeGuess(using: encoder)
|
||||
|
||||
XCTAssertNotNil(node)
|
||||
|
||||
XCTAssertFalse(node?.required ?? true)
|
||||
XCTAssertEqual(node?.jsonTypeFormat, .number(.double))
|
||||
|
||||
guard case .number(let contextA, let numberContext)? = node else {
|
||||
XCTFail("Expected string Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(contextA, .init(format: .double,
|
||||
required: false,
|
||||
nullable: true,
|
||||
allowedValues: nil))
|
||||
|
||||
XCTAssertEqual(numberContext, .init())
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Test Types
|
||||
extension JSONAPIAttributeOpenAPITests {
|
||||
enum EnumAttribute: String, Codable, CaseIterable {
|
||||
@@ -511,3 +885,9 @@ extension JSONAPIAttributeOpenAPITests {
|
||||
case two
|
||||
}
|
||||
}
|
||||
|
||||
extension Date: SampleableOpenAPIType {
|
||||
public static var sample: Date {
|
||||
return TimeInterval.arbitrary.map { Date(timeIntervalSince1970: $0) }.generate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,17 +6,307 @@
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import SwiftCheck
|
||||
import JSONAPI
|
||||
import JSONAPIOpenAPI
|
||||
|
||||
class JSONAPIDocumentOpenAPITests: XCTestCase {
|
||||
func test_SingleResourceDocument() {
|
||||
let node = try! SingleEntityDocument.openAPINode()
|
||||
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .medium
|
||||
dateFormatter.timeStyle = .none
|
||||
dateFormatter.locale = Locale(identifier: "en_US")
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
encoder.dateEncodingStrategy = .formatted(dateFormatter)
|
||||
|
||||
print(String(data: try! encoder.encode(node), encoding: .utf8)!)
|
||||
let node = try! SingleEntityDocument.openAPINodeWithExample(using: encoder)
|
||||
|
||||
XCTAssertTrue(node.required)
|
||||
XCTAssertEqual(node.jsonTypeFormat, .object(.generic))
|
||||
|
||||
guard case let .object(contextA, objectContext1) = node else {
|
||||
XCTFail("Expected JSON Document to be an Object Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertNotNil(contextA.example)
|
||||
XCTAssertFalse(contextA.nullable)
|
||||
XCTAssertEqual(contextA.format, .generic)
|
||||
XCTAssertTrue(contextA.required)
|
||||
|
||||
XCTAssertEqual(objectContext1.minProperties, 1)
|
||||
XCTAssertEqual(Set(objectContext1.requiredProperties), Set(["data"]))
|
||||
XCTAssertEqual(Set(objectContext1.properties.keys), Set(["data"]))
|
||||
|
||||
guard case let .object(contextB, objectContext2)? = objectContext1.properties["data"] else {
|
||||
XCTFail("Expected Data field of JSON Document to be an Object Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertFalse(contextB.nullable)
|
||||
XCTAssertEqual(contextB.format, .generic)
|
||||
XCTAssertTrue(contextB.required)
|
||||
|
||||
XCTAssertEqual(objectContext2.minProperties, 3)
|
||||
XCTAssertEqual(Set(objectContext2.requiredProperties), Set(["id", "attributes", "type"]))
|
||||
XCTAssertEqual(Set(objectContext2.properties.keys), Set(["id", "attributes", "type"]))
|
||||
|
||||
XCTAssertEqual(objectContext2.properties["type"],
|
||||
JSONNode.string(.init(format: .generic,
|
||||
required: true,
|
||||
allowedValues: [.init("test")]),
|
||||
.init()))
|
||||
}
|
||||
|
||||
func test_ManyResourceDocument() {
|
||||
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .medium
|
||||
dateFormatter.timeStyle = .none
|
||||
dateFormatter.locale = Locale(identifier: "en_US")
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
encoder.dateEncodingStrategy = .formatted(dateFormatter)
|
||||
|
||||
let node = try! ManyEntityDocument.openAPINodeWithExample(using: encoder)
|
||||
|
||||
XCTAssertTrue(node.required)
|
||||
XCTAssertEqual(node.jsonTypeFormat, .object(.generic))
|
||||
|
||||
guard case let .object(contextA, objectContext1) = node else {
|
||||
XCTFail("Expected JSON Document to be an Object Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertNotNil(contextA.example)
|
||||
XCTAssertFalse(contextA.nullable)
|
||||
XCTAssertEqual(contextA.format, .generic)
|
||||
XCTAssertTrue(contextA.required)
|
||||
|
||||
XCTAssertEqual(objectContext1.minProperties, 1)
|
||||
XCTAssertEqual(Set(objectContext1.requiredProperties), Set(["data"]))
|
||||
XCTAssertEqual(Set(objectContext1.properties.keys), Set(["data"]))
|
||||
|
||||
guard case let .array(contextB, arrayContext)? = objectContext1.properties["data"] else {
|
||||
XCTFail("Expected Data field of JSON Document to be an Array Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertFalse(contextB.nullable)
|
||||
XCTAssertEqual(contextB.format, .generic)
|
||||
XCTAssertTrue(contextB.required)
|
||||
|
||||
XCTAssertFalse(arrayContext.uniqueItems)
|
||||
XCTAssertEqual(arrayContext.minItems, 0)
|
||||
|
||||
guard case let .object(contextC, objectContext2) = arrayContext.items else {
|
||||
XCTFail("Expected Items of Array under Data to be an Object Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertFalse(contextC.nullable)
|
||||
XCTAssertEqual(contextC.format, .generic)
|
||||
XCTAssertTrue(contextC.required)
|
||||
|
||||
XCTAssertEqual(objectContext2.minProperties, 3)
|
||||
XCTAssertEqual(Set(objectContext2.requiredProperties), Set(["id", "attributes", "type"]))
|
||||
XCTAssertEqual(Set(objectContext2.properties.keys), Set(["id", "attributes", "type"]))
|
||||
|
||||
XCTAssertEqual(objectContext2.properties["type"],
|
||||
JSONNode.string(.init(format: .generic,
|
||||
required: true,
|
||||
allowedValues: [.init("test")]),
|
||||
.init()))
|
||||
}
|
||||
|
||||
func test_DocumentWithOneIncludeType() {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .medium
|
||||
dateFormatter.timeStyle = .none
|
||||
dateFormatter.locale = Locale(identifier: "en_US")
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
encoder.dateEncodingStrategy = .formatted(dateFormatter)
|
||||
|
||||
let node = try! DocumentWithIncludes.openAPINodeWithExample(using: encoder)
|
||||
|
||||
XCTAssertTrue(node.required)
|
||||
XCTAssertEqual(node.jsonTypeFormat, .object(.generic))
|
||||
|
||||
guard case let .object(contextA, objectContext1) = node else {
|
||||
XCTFail("Expected JSON Document to be an Object Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertNotNil(contextA.example)
|
||||
XCTAssertFalse(contextA.nullable)
|
||||
XCTAssertEqual(contextA.format, .generic)
|
||||
XCTAssertTrue(contextA.required)
|
||||
|
||||
XCTAssertEqual(objectContext1.minProperties, 2)
|
||||
XCTAssertEqual(Set(objectContext1.requiredProperties), Set(["data", "included"]))
|
||||
XCTAssertEqual(Set(objectContext1.properties.keys), Set(["data", "included"]))
|
||||
|
||||
guard case let .object(contextB, objectContext2)? = objectContext1.properties["data"] else {
|
||||
XCTFail("Expected Data field of JSON Document to be an Object Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertFalse(contextB.nullable)
|
||||
XCTAssertEqual(contextB.format, .generic)
|
||||
XCTAssertTrue(contextB.required)
|
||||
|
||||
XCTAssertEqual(objectContext2.minProperties, 3)
|
||||
XCTAssertEqual(Set(objectContext2.requiredProperties), Set(["id", "attributes", "type"]))
|
||||
XCTAssertEqual(Set(objectContext2.properties.keys), Set(["id", "attributes", "type"]))
|
||||
|
||||
XCTAssertEqual(objectContext2.properties["type"],
|
||||
JSONNode.string(.init(format: .generic,
|
||||
required: true,
|
||||
allowedValues: [.init("test")]),
|
||||
.init()))
|
||||
|
||||
guard case let .array(contextC, arrayContext)? = objectContext1.properties["included"] else {
|
||||
XCTFail("Expected Includes field of JSON Document to be an Array Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertFalse(contextC.nullable)
|
||||
XCTAssertEqual(contextC.format, .generic)
|
||||
XCTAssertTrue(contextC.required)
|
||||
|
||||
XCTAssertTrue(arrayContext.uniqueItems)
|
||||
XCTAssertEqual(arrayContext.minItems, 0)
|
||||
|
||||
guard case let .object(contextD, objectContext3) = arrayContext.items else {
|
||||
XCTFail("Expected Items of Array under Data to be an Object Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertFalse(contextD.nullable)
|
||||
XCTAssertEqual(contextD.format, .generic)
|
||||
XCTAssertTrue(contextD.required)
|
||||
|
||||
XCTAssertEqual(objectContext3.minProperties, 3)
|
||||
XCTAssertEqual(Set(objectContext3.requiredProperties), Set(["id", "attributes", "type"]))
|
||||
XCTAssertEqual(Set(objectContext3.properties.keys), Set(["id", "attributes", "type"]))
|
||||
|
||||
XCTAssertEqual(objectContext3.properties["type"],
|
||||
JSONNode.string(.init(format: .generic,
|
||||
required: true,
|
||||
allowedValues: [.init("test")]),
|
||||
.init()))
|
||||
}
|
||||
|
||||
func test_DocumentWithTwoIncludeTypes() {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .medium
|
||||
dateFormatter.timeStyle = .none
|
||||
dateFormatter.locale = Locale(identifier: "en_US")
|
||||
|
||||
let encoder = JSONEncoder()
|
||||
encoder.outputFormatting = .prettyPrinted
|
||||
encoder.dateEncodingStrategy = .formatted(dateFormatter)
|
||||
|
||||
let node = try! DocumentWithMultipleTypesOfIncludes.openAPINodeWithExample(using: encoder)
|
||||
|
||||
XCTAssertTrue(node.required)
|
||||
XCTAssertEqual(node.jsonTypeFormat, .object(.generic))
|
||||
|
||||
guard case let .object(contextA, objectContext1) = node else {
|
||||
XCTFail("Expected JSON Document to be an Object Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertNotNil(contextA.example)
|
||||
XCTAssertFalse(contextA.nullable)
|
||||
XCTAssertEqual(contextA.format, .generic)
|
||||
XCTAssertTrue(contextA.required)
|
||||
|
||||
XCTAssertEqual(objectContext1.minProperties, 2)
|
||||
XCTAssertEqual(Set(objectContext1.requiredProperties), Set(["data", "included"]))
|
||||
XCTAssertEqual(Set(objectContext1.properties.keys), Set(["data", "included"]))
|
||||
|
||||
guard case let .object(contextB, objectContext2)? = objectContext1.properties["data"] else {
|
||||
XCTFail("Expected Data field of JSON Document to be an Object Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertFalse(contextB.nullable)
|
||||
XCTAssertEqual(contextB.format, .generic)
|
||||
XCTAssertTrue(contextB.required)
|
||||
|
||||
XCTAssertEqual(objectContext2.minProperties, 3)
|
||||
XCTAssertEqual(Set(objectContext2.requiredProperties), Set(["id", "attributes", "type"]))
|
||||
XCTAssertEqual(Set(objectContext2.properties.keys), Set(["id", "attributes", "type"]))
|
||||
|
||||
XCTAssertEqual(objectContext2.properties["type"],
|
||||
JSONNode.string(.init(format: .generic,
|
||||
required: true,
|
||||
allowedValues: [.init("test")]),
|
||||
.init()))
|
||||
|
||||
guard case let .array(contextC, arrayContext)? = objectContext1.properties["included"] else {
|
||||
XCTFail("Expected Includes field of JSON Document to be an Array Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertFalse(contextC.nullable)
|
||||
XCTAssertEqual(contextC.format, .generic)
|
||||
XCTAssertTrue(contextC.required)
|
||||
|
||||
XCTAssertTrue(arrayContext.uniqueItems)
|
||||
XCTAssertEqual(arrayContext.minItems, 0)
|
||||
|
||||
guard case let .one(of: includeNodes) = arrayContext.items else {
|
||||
XCTFail("Expected Included to contain multiple types of items.")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(includeNodes.count, 2)
|
||||
|
||||
guard case let .object(contextD, objectContext3) = includeNodes[0] else {
|
||||
XCTFail("Expected Items of OneOf under Array under Data to be an Object Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertFalse(contextD.nullable)
|
||||
XCTAssertEqual(contextD.format, .generic)
|
||||
XCTAssertTrue(contextD.required)
|
||||
|
||||
XCTAssertEqual(objectContext3.minProperties, 3)
|
||||
XCTAssertEqual(Set(objectContext3.requiredProperties), Set(["id", "attributes", "type"]))
|
||||
XCTAssertEqual(Set(objectContext3.properties.keys), Set(["id", "attributes", "type"]))
|
||||
|
||||
XCTAssertEqual(objectContext3.properties["type"],
|
||||
JSONNode.string(.init(format: .generic,
|
||||
required: true,
|
||||
allowedValues: [.init("test")]),
|
||||
.init()))
|
||||
|
||||
guard case let .object(contextE, objectContext4) = includeNodes[1] else {
|
||||
XCTFail("Expected Items of OneOf under Array under Data to be an Object Node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertFalse(contextE.nullable)
|
||||
XCTAssertEqual(contextE.format, .generic)
|
||||
XCTAssertTrue(contextE.required)
|
||||
|
||||
XCTAssertEqual(objectContext4.minProperties, 2)
|
||||
XCTAssertEqual(Set(objectContext4.requiredProperties), Set(["id", "type"]))
|
||||
XCTAssertEqual(Set(objectContext4.properties.keys), Set(["id", "type"]))
|
||||
|
||||
XCTAssertEqual(objectContext4.properties["type"],
|
||||
JSONNode.string(.init(format: .generic,
|
||||
required: true,
|
||||
allowedValues: [.init("test2")]),
|
||||
.init()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +317,11 @@ extension JSONAPIDocumentOpenAPITests {
|
||||
|
||||
struct Attributes: JSONAPI.Attributes, Sampleable {
|
||||
let name: Attribute<String>
|
||||
let date: Attribute<Date>
|
||||
|
||||
static var sample: Attributes {
|
||||
return .init(name: "hello world")
|
||||
return .init(name: "hello world",
|
||||
date: .init(value: Date()))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,4 +331,46 @@ extension JSONAPIDocumentOpenAPITests {
|
||||
typealias TestEntity = BasicEntity<TestEntityDescription>
|
||||
|
||||
typealias SingleEntityDocument = Document<SingleResourceBody<TestEntity>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>
|
||||
|
||||
typealias ManyEntityDocument = Document<ManyResourceBody<TestEntity>, NoMetadata, NoLinks, NoIncludes, NoAPIDescription, UnknownJSONAPIError>
|
||||
|
||||
typealias DocumentWithIncludes = Document<SingleResourceBody<TestEntity>, NoMetadata, NoLinks, Include1<TestEntity>, NoAPIDescription, UnknownJSONAPIError>
|
||||
|
||||
enum TestEntityDescription2: EntityDescription {
|
||||
static var jsonType: String { return "test2" }
|
||||
|
||||
typealias Attributes = NoAttributes
|
||||
|
||||
typealias Relationships = NoRelationships
|
||||
}
|
||||
|
||||
typealias TestEntity2 = BasicEntity<TestEntityDescription2>
|
||||
|
||||
typealias DocumentWithMultipleTypesOfIncludes = Document<SingleResourceBody<TestEntity>, NoMetadata, NoLinks, Include2<TestEntity, TestEntity2>, NoAPIDescription, UnknownJSONAPIError>
|
||||
}
|
||||
|
||||
extension Id: Sampleable where RawType == String {
|
||||
public static var sample: Id<RawType, IdentifiableType> {
|
||||
return .init(rawValue: String.arbitrary.generate)
|
||||
}
|
||||
}
|
||||
|
||||
extension JSONAPI.Entity: Sampleable where Description.Attributes: Sampleable, Description.Relationships: Sampleable, MetaType: Sampleable, LinksType: Sampleable, EntityRawIdType == String {
|
||||
public static var sample: JSONAPI.Entity<Description, MetaType, LinksType, EntityRawIdType> {
|
||||
return JSONAPI.Entity(id: .sample,
|
||||
attributes: .sample,
|
||||
relationships: .sample,
|
||||
meta: .sample,
|
||||
links: .sample)
|
||||
}
|
||||
}
|
||||
|
||||
extension Document: Sampleable where PrimaryResourceBody: Sampleable, MetaType: Sampleable, LinksType: Sampleable, IncludeType: Sampleable, APIDescription: Sampleable, Error: Sampleable {
|
||||
public static var sample: Document<PrimaryResourceBody, MetaType, LinksType, IncludeType, APIDescription, Error> {
|
||||
return Document(apiDescription: .sample,
|
||||
body: .sample,
|
||||
includes: .sample,
|
||||
meta: .sample,
|
||||
links: .sample)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import AnyCodable
|
||||
|
||||
class JSONAPIEntityOpenAPITests: XCTestCase {
|
||||
func test_EmptyEntity() {
|
||||
let node = try! TestType1.openAPINode()
|
||||
let node = try! TestType1.openAPINode(using: JSONEncoder())
|
||||
|
||||
XCTAssertTrue(node.required)
|
||||
XCTAssertEqual(node.jsonTypeFormat, .object(.generic))
|
||||
@@ -39,8 +39,42 @@ class JSONAPIEntityOpenAPITests: XCTestCase {
|
||||
.init()))
|
||||
}
|
||||
|
||||
func test_UnidentifiedEmptyEntity() {
|
||||
let node = try! UnidentifiedTestType1.openAPINode(using: JSONEncoder())
|
||||
|
||||
XCTAssertTrue(node.required)
|
||||
XCTAssertEqual(node.jsonTypeFormat, .object(.generic))
|
||||
|
||||
guard case let .object(contextA, objectContext1) = node else {
|
||||
XCTFail("Expected Object node")
|
||||
return
|
||||
}
|
||||
|
||||
XCTAssertEqual(contextA, .init(format: .generic,
|
||||
required: true,
|
||||
nullable: false,
|
||||
allowedValues: nil))
|
||||
|
||||
XCTAssertEqual(objectContext1.minProperties, 1)
|
||||
XCTAssertEqual(Set(objectContext1.requiredProperties), Set(["type"]))
|
||||
XCTAssertEqual(Set(objectContext1.properties.keys), Set(["type"]))
|
||||
XCTAssertEqual(objectContext1.properties["type"], .string(.init(format: .generic,
|
||||
required: true,
|
||||
allowedValues: [.init(TestType1.jsonType)]),
|
||||
.init()))
|
||||
}
|
||||
|
||||
func test_AttributesEntity() {
|
||||
let node = try! TestType2.openAPINode()
|
||||
|
||||
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 +117,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 +133,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,
|
||||
@@ -122,7 +163,7 @@ class JSONAPIEntityOpenAPITests: XCTestCase {
|
||||
}
|
||||
|
||||
func test_RelationshipsEntity() {
|
||||
let node = try! TestType3.openAPINode()
|
||||
let node = try! TestType3.openAPINode(using: JSONEncoder())
|
||||
|
||||
XCTAssertTrue(node.required)
|
||||
XCTAssertEqual(node.jsonTypeFormat, .object(.generic))
|
||||
@@ -256,6 +297,7 @@ extension JSONAPIEntityOpenAPITests {
|
||||
}
|
||||
|
||||
typealias TestType1 = BasicEntity<TestType1Description>
|
||||
typealias UnidentifiedTestType1 = JSONAPI.Entity<TestType1Description, NoMetadata, NoLinks, Unidentified>
|
||||
|
||||
enum TestType2Description: EntityDescription {
|
||||
public static var jsonType: String { return "test2" }
|
||||
@@ -268,6 +310,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 +321,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