mirror of
https://github.com/encounter/JSONAPI.git
synced 2026-07-10 12:18:40 -07:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| cb2800abd4 | |||
| 85d5fef3c8 | |||
| e4ef61fd56 | |||
| 75ec4f156e | |||
| b45fc73188 | |||
| 3b73dc9989 | |||
| b2c81026f4 |
@@ -148,10 +148,12 @@ extension Entity: OpenAPIEncodedNodeType where Description.Attributes: Sampleabl
|
||||
|
||||
// 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,
|
||||
|
||||
@@ -155,7 +155,6 @@ extension JSONNode: Encodable {
|
||||
case oneOf
|
||||
case anyOf
|
||||
case not
|
||||
case reference = "$ref"
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
@@ -192,16 +191,20 @@ extension JSONNode: Encodable {
|
||||
try container.encode(node, forKey: .not)
|
||||
|
||||
case .reference(let reference):
|
||||
var container = encoder.container(keyedBy: SubschemaCodingKeys.self)
|
||||
var container = encoder.singleValueContainer()
|
||||
|
||||
try container.encode(reference, forKey: .reference)
|
||||
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.singleValueContainer()
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
let referenceString: String = {
|
||||
switch self {
|
||||
@@ -212,7 +215,7 @@ extension JSONReference: Encodable {
|
||||
}
|
||||
}()
|
||||
|
||||
try container.encode(referenceString)
|
||||
try container.encode(referenceString, forKey: .ref)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,6 +244,32 @@ extension OpenAPIResponse.Code: Encodable {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
@@ -276,7 +305,17 @@ extension OpenAPIPathItem.PathProperties.Operation: Encodable {
|
||||
|
||||
try container.encode(parameters, forKey: .parameters)
|
||||
|
||||
try container.encode(responses, forKey: .responses)
|
||||
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)
|
||||
}
|
||||
@@ -346,6 +385,29 @@ extension OpenAPIPathItem.PathProperties: Encodable {
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
@@ -359,3 +421,34 @@ extension OpenAPIPathItem: Encodable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -759,7 +759,7 @@ public enum OpenAPIPathItem: Equatable {
|
||||
// public let externalDocs:
|
||||
public let operationId: String
|
||||
public let parameters: ParameterArray
|
||||
// public let requestBody:
|
||||
public let requestBody: OpenAPIRequestBody?
|
||||
public let responses: ResponseMap
|
||||
// public let callbacks:
|
||||
public let deprecated: Bool // default is false
|
||||
@@ -771,6 +771,7 @@ public enum OpenAPIPathItem: Equatable {
|
||||
description: String? = nil,
|
||||
operationId: String,
|
||||
parameters: ParameterArray,
|
||||
requestBody: OpenAPIRequestBody? = nil,
|
||||
responses: ResponseMap,
|
||||
deprecated: Bool = false) {
|
||||
self.tags = tags
|
||||
@@ -778,47 +779,82 @@ public enum OpenAPIPathItem: Equatable {
|
||||
self.description = description
|
||||
self.operationId = operationId
|
||||
self.parameters = parameters
|
||||
self.requestBody = requestBody
|
||||
self.responses = responses
|
||||
self.deprecated = deprecated
|
||||
}
|
||||
|
||||
public typealias ResponseMap = [OpenAPIResponse.Code: Either<OpenAPIResponse, JSONReference<OpenAPIComponents, OpenAPIResponse>>]
|
||||
|
||||
public typealias ContentMap = [OpenAPIContentType: OpenAPIContent]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct OpenAPIResponse: Encodable, Equatable {
|
||||
public struct OpenAPIRequestBody: Equatable {
|
||||
public let description: String?
|
||||
public let content: OpenAPIPathItem.PathProperties.Operation.ContentMap
|
||||
public let required: Bool
|
||||
|
||||
public init(description: String? = nil,
|
||||
content: OpenAPIPathItem.PathProperties.Operation.ContentMap,
|
||||
required: Bool = true) {
|
||||
self.description = description
|
||||
self.content = content
|
||||
self.required = required
|
||||
}
|
||||
}
|
||||
|
||||
public struct OpenAPIResponse: Equatable {
|
||||
public let description: String
|
||||
// public let headers:
|
||||
public let content: ContentMap
|
||||
public let content: OpenAPIPathItem.PathProperties.Operation.ContentMap
|
||||
// public let links:
|
||||
|
||||
public init(description: String,
|
||||
content: ContentMap) {
|
||||
content: OpenAPIPathItem.PathProperties.Operation.ContentMap) {
|
||||
self.description = description
|
||||
self.content = content
|
||||
}
|
||||
|
||||
public typealias ContentMap = [ContentType: Content]
|
||||
public enum Code: RawRepresentable, Equatable, Hashable {
|
||||
public typealias RawValue = String
|
||||
|
||||
public enum Code: Equatable, Hashable {
|
||||
case `default`
|
||||
case status(code: Int)
|
||||
}
|
||||
|
||||
public enum ContentType: String, Encodable, Equatable, Hashable {
|
||||
case json = "application/json"
|
||||
}
|
||||
public var rawValue: String {
|
||||
switch self {
|
||||
case .default:
|
||||
return "default"
|
||||
|
||||
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
|
||||
case .status(code: let code):
|
||||
return String(code)
|
||||
}
|
||||
}
|
||||
|
||||
public init?(rawValue: String) {
|
||||
if let val = Int(rawValue) {
|
||||
self = .status(code: val)
|
||||
} else {
|
||||
self = .default
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum OpenAPIContentType: String, Encodable, Equatable, Hashable {
|
||||
case json = "application/json"
|
||||
}
|
||||
|
||||
public struct OpenAPIContent: 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -858,14 +894,7 @@ public struct OpenAPIComponents: Equatable, Encodable, ReferenceRoot {
|
||||
}
|
||||
|
||||
/// 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 struct OpenAPISchema {
|
||||
public let openAPIVersion: Version
|
||||
public let info: Info
|
||||
// public let servers:
|
||||
@@ -908,17 +937,25 @@ public struct OpenAPISchema: Encodable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct PathComponents: Encodable, Equatable, Hashable {
|
||||
public struct PathComponents: RawRepresentable, Encodable, Equatable, Hashable {
|
||||
public let components: [String]
|
||||
|
||||
public init(_ components: [String]) {
|
||||
self.components = components
|
||||
}
|
||||
|
||||
public init?(rawValue: String) {
|
||||
components = rawValue.split(separator: "/").map(String.init)
|
||||
}
|
||||
|
||||
public var rawValue: String {
|
||||
return "/\(components.joined(separator: "/"))"
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
|
||||
try container.encode(components.joined(separator: "/"))
|
||||
try container.encode(rawValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,31 @@ 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 dateFormatter = DateFormatter()
|
||||
@@ -272,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" }
|
||||
|
||||
Reference in New Issue
Block a user