Skip to content

Commit

Permalink
Merge pull request #235 from mattpolzin/link-object
Browse files Browse the repository at this point in the history
Add Link Object
  • Loading branch information
mattpolzin authored Dec 19, 2021
2 parents 3e99653 + 78c27f1 commit 96b8c43
Show file tree
Hide file tree
Showing 19 changed files with 1,122 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ extension OpenAPI.SecurityScheme: ComponentDictionaryLocatable {
public static var openAPIComponentsKeyPath: KeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>> { \.securitySchemes }
}

extension OpenAPI.Link: ComponentDictionaryLocatable {
public static var openAPIComponentsKey: String { "links" }
public static var openAPIComponentsKeyPath: KeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>> { \.links }
}

extension OpenAPI.PathItem: ComponentDictionaryLocatable {
public static var openAPIComponentsKey: String { "pathItems" }
public static var openAPIComponentsKeyPath: KeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>> { \.pathItems }
Expand Down
10 changes: 9 additions & 1 deletion Sources/OpenAPIKit/Components Object/Components.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ extension OpenAPI {
public var requestBodies: ComponentDictionary<Request>
public var headers: ComponentDictionary<Header>
public var securitySchemes: ComponentDictionary<SecurityScheme>
public var links: ComponentDictionary<Link>
public var callbacks: ComponentDictionary<Callbacks>
// public var links:

public var pathItems: ComponentDictionary<PathItem>

Expand All @@ -44,6 +44,7 @@ extension OpenAPI {
requestBodies: ComponentDictionary<Request> = [:],
headers: ComponentDictionary<Header> = [:],
securitySchemes: ComponentDictionary<SecurityScheme> = [:],
links: ComponentDictionary<Link> = [:],
callbacks: ComponentDictionary<Callbacks> = [:],
pathItems: ComponentDictionary<PathItem> = [:],
vendorExtensions: [String: AnyCodable] = [:]
Expand All @@ -55,6 +56,7 @@ extension OpenAPI {
self.requestBodies = requestBodies
self.headers = headers
self.securitySchemes = securitySchemes
self.links = links
self.callbacks = callbacks
self.pathItems = pathItems
self.vendorExtensions = vendorExtensions
Expand Down Expand Up @@ -165,6 +167,10 @@ extension OpenAPI.Components: Encodable {
try container.encode(securitySchemes, forKey: .securitySchemes)
}

if !links.isEmpty {
try container.encode(links, forKey: .links)
}

if !callbacks.isEmpty {
try container.encode(callbacks, forKey: .callbacks)
}
Expand Down Expand Up @@ -202,6 +208,8 @@ extension OpenAPI.Components: Decodable {

securitySchemes = try container.decodeIfPresent(OpenAPI.ComponentDictionary<OpenAPI.SecurityScheme>.self, forKey: .securitySchemes) ?? [:]

links = try container.decodeIfPresent(OpenAPI.ComponentDictionary<OpenAPI.Link>.self, forKey: .links) ?? [:]

callbacks = try container.decodeIfPresent(OpenAPI.ComponentDictionary<OpenAPI.Callbacks>.self, forKey: .callbacks) ?? [:]

pathItems = try container.decodeIfPresent(OpenAPI.ComponentDictionary<OpenAPI.PathItem>.self, forKey: .pathItems) ?? [:]
Expand Down
5 changes: 5 additions & 0 deletions Sources/OpenAPIKit/Either/Either+Convenience.swift
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ extension Either where B == OpenAPI.Response {
public var responseValue: B? { b }
}

extension Either where B == OpenAPI.Link {
/// Retrieve the link if that is what this property contains.
public var linkValue: B? { b }
}

extension Either where B == OpenAPI.Content.Map {
/// Retrieve the content map if that is what this property contains.
public var contentValue: B? { b }
Expand Down
259 changes: 255 additions & 4 deletions Sources/OpenAPIKit/Link.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,263 @@
// Created by Mathew Polzin on 1/23/20.
//

// TODO: create validation that operationIds in Link objects
// refer to Operation objects in the document that have the
// given ids.

import OpenAPIKitCore
import Foundation

extension OpenAPI {
// TODO: requires runtime expression to be built first.
/// OpenAPI Spec "Link Object"
///
/// See [OpenAPI Link Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#link-object).
public struct Link: Equatable, CodableVendorExtendable {
/// The **OpenAPI**` `operationRef` or `operationId` field, depending on whether
/// a `URL` of a remote or local Operation Object or a `operationId` (String) of an
/// operation defined in the same document is given.
public let operation: Either<URL, String>
/// A map from parameter names to either runtime expressions that evaluate to values or
/// constant values (`AnyCodable`).
///
/// See the docuemntation for the [OpenAPI Link Object](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#link-object) for more details.
///
/// Empty dictionaries will be omitted from encoding.
public let parameters: OrderedDictionary<String, Either<RuntimeExpression, AnyCodable>>
/// A literal value or expression to use as a request body when calling the target operation.
public let requestBody: Either<RuntimeExpression, AnyCodable>?
public var description: String?
public let server: Server?

// public struct Link: Equatable {
//
// }
/// Dictionary of vendor extensions.
///
/// These should be of the form:
/// `[ "x-extensionKey": <anything>]`
/// where the values are anything codable.
public var vendorExtensions: [String: AnyCodable]

public init(
operation: Either<URL, String>,
parameters: OrderedDictionary<String, Either<RuntimeExpression, AnyCodable>> = [:],
requestBody: Either<RuntimeExpression, AnyCodable>? = nil,
description: String? = nil,
server: Server? = nil,
vendorExtensions: [String: AnyCodable] = [:]
) {
self.operation = operation
self.parameters = parameters
self.requestBody = requestBody
self.description = description
self.server = server
self.vendorExtensions = vendorExtensions
}

/// Create a Link by referring to an `operationId` of some `Operation` elsewhere
/// in the same document.
public init(
operationId: String,
parameters: OrderedDictionary<String, Either<RuntimeExpression, AnyCodable>> = [:],
requestBody: Either<RuntimeExpression, AnyCodable>? = nil,
description: String? = nil,
server: Server? = nil,
vendorExtensions: [String: AnyCodable] = [:]
) {
self.init(
operation: .b(operationId),
parameters: parameters,
requestBody: requestBody,
description: description,
server: server,
vendorExtensions: vendorExtensions
)
}

/// Create a Link by referring to an `operationRef` pointing to an `Operation`
/// either in the same document or elsewhere.
public init(
operationRef: URL,
parameters: OrderedDictionary<String, Either<RuntimeExpression, AnyCodable>> = [:],
requestBody: Either<RuntimeExpression, AnyCodable>? = nil,
description: String? = nil,
server: Server? = nil,
vendorExtensions: [String: AnyCodable] = [:]
) {
self.init(
operation: .a(operationRef),
parameters: parameters,
requestBody: requestBody,
description: description,
server: server,
vendorExtensions: vendorExtensions
)
}
}
}

extension OpenAPI.Link {
public typealias Map = OrderedDictionary<String, Either<OpenAPI.Reference<OpenAPI.Link>, OpenAPI.Link>>
}

// MARK: `Either` convenience methods
extension Either where A == OpenAPI.Reference<OpenAPI.Link>, B == OpenAPI.Link {

public static func link(
operationId: String,
parameters: OrderedDictionary<String, Either<OpenAPI.RuntimeExpression, AnyCodable>> = [:],
requestBody: Either<OpenAPI.RuntimeExpression, AnyCodable>? = nil,
description: String? = nil,
server: OpenAPI.Server? = nil
) -> Self {
return .b(
.init(
operationId: operationId,
parameters: parameters,
requestBody: requestBody,
description: description,
server: server
)
)
}

public static func link(
operationRef: URL,
parameters: OrderedDictionary<String, Either<OpenAPI.RuntimeExpression, AnyCodable>> = [:],
requestBody: Either<OpenAPI.RuntimeExpression, AnyCodable>? = nil,
description: String? = nil,
server: OpenAPI.Server? = nil
) -> Self {
return .b(
.init(
operationRef: operationRef,
parameters: parameters,
requestBody: requestBody,
description: description,
server: server
)
)
}
}

// MARK: - Describable

extension OpenAPI.Link: OpenAPIDescribable {
public func overriddenNonNil(description: String?) -> OpenAPI.Link {
guard let description = description else { return self }
var link = self
link.description = description
return link
}
}

// MARK: - Codable

extension OpenAPI.Link: Encodable {
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)

switch operation {
case .a(let url):
try container.encode(url.absoluteString, forKey: .operationRef)
case .b(let id):
try container.encode(id, forKey: .operationId)
}

if !parameters.isEmpty {
try container.encode(parameters, forKey: .parameters)
}

try container.encodeIfPresent(requestBody, forKey: .requestBody)
try container.encodeIfPresent(description, forKey: .description)
try container.encodeIfPresent(server, forKey: .server)

try encodeExtensions(to: &container)
}
}

extension OpenAPI.Link: Decodable {
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)

if let operationString = try container.decodeIfPresent(String.self, forKey: .operationId) {
operation = .b(operationString)
} else {
operation = .a(try container.decodeURLAsString(forKey: .operationRef))
}

parameters = try container.decodeIfPresent(OrderedDictionary<String, Either<OpenAPI.RuntimeExpression, AnyCodable>>.self, forKey: .parameters) ?? [:]

requestBody = try container.decodeIfPresent(Either<OpenAPI.RuntimeExpression, AnyCodable>.self, forKey: .requestBody)
description = try container.decodeIfPresent(String.self, forKey: .description)
server = try container.decodeIfPresent(OpenAPI.Server.self, forKey: .server)

vendorExtensions = try Self.extensions(from: decoder)
}
}

extension OpenAPI.Link {
internal enum CodingKeys: ExtendableCodingKey {
case operationId
case operationRef
case parameters
case requestBody
case description
case server

case extended(String)

static var allBuiltinKeys: [CodingKeys] {
return [
.operationId,
.operationRef,
.parameters,
.requestBody,
.description,
.server
]
}

static func extendedKey(for value: String) -> CodingKeys {
return .extended(value)
}

init?(stringValue: String) {
switch stringValue {
case "operationId":
self = .operationId
case "operationRef":
self = .operationRef
case "parameters":
self = .parameters
case "requestBody":
self = .requestBody
case "description":
self = .description
case "server":
self = .server
default:
self = .extendedKey(for: stringValue)
}
}

var stringValue: String {
switch self {
case .operationId:
return "operationId"
case .operationRef:
return "operationRef"
case .parameters:
return "parameters"
case .requestBody:
return "requestBody"
case .description:
return "description"
case .server:
return "server"
case .extended(let key):
return key
}
}
}
}

extension OpenAPI.Link: Validatable {}
2 changes: 1 addition & 1 deletion Sources/OpenAPIKit/Operation/Operation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ extension OpenAPI.Operation {

// MARK: - Describable & Summarizable

extension OpenAPI.Operation : OpenAPISummarizable {
extension OpenAPI.Operation: OpenAPISummarizable {
public func overriddenNonNil(summary: String?) -> OpenAPI.Operation {
guard let summary = summary else { return self }
var operation = self
Expand Down
Loading

0 comments on commit 96b8c43

Please sign in to comment.