Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ OpenAPIKit follows semantic versioning despite the fact that the OpenAPI specifi
|------------|-------|--------------------|-----------------------------------|--------------|
| v3.x | 5.1+ | ✅ | | |
| v4.x | 5.8+ | ✅ | ✅ | |
| v4.x | 5.8+ | ✅ | ✅ | ✅ |
| v5.x | 5.10+ | ✅ | ✅ | ✅ |

- [Usage](#usage)
- [Migration](#migration)
Expand Down
267 changes: 234 additions & 33 deletions Sources/OpenAPIKit/Components Object/Components+JSONReference.swift

Large diffs are not rendered by default.

22 changes: 11 additions & 11 deletions Sources/OpenAPIKit/Components Object/Components+Locatable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,57 +16,57 @@ public protocol ComponentDictionaryLocatable: SummaryOverridable {
/// This can be used to create a JSON path
/// like `#/name1/name2/name3`
static var openAPIComponentsKey: String { get }
static var openAPIComponentsKeyPath: WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>> { get }
static var openAPIComponentsKeyPath: Either<WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>>, WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentReferenceDictionary<Self>>> { get }
}

extension JSONSchema: ComponentDictionaryLocatable {
public static var openAPIComponentsKey: String { "schemas" }
public static var openAPIComponentsKeyPath: WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>> { \.schemas }
public static var openAPIComponentsKeyPath: Either<WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>>, WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentReferenceDictionary<Self>>> { .a(\.schemas) }
}

extension OpenAPI.Response: ComponentDictionaryLocatable {
public static var openAPIComponentsKey: String { "responses" }
public static var openAPIComponentsKeyPath: WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>> { \.responses }
public static var openAPIComponentsKeyPath: Either<WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>>, WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentReferenceDictionary<Self>>> { .b(\.responses) }
}

extension OpenAPI.Callbacks: ComponentDictionaryLocatable & SummaryOverridable {
public static var openAPIComponentsKey: String { "callbacks" }
public static var openAPIComponentsKeyPath: WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>> { \.callbacks }
public static var openAPIComponentsKeyPath: Either<WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>>, WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentReferenceDictionary<Self>>> { .b(\.callbacks) }
}

extension OpenAPI.Parameter: ComponentDictionaryLocatable {
public static var openAPIComponentsKey: String { "parameters" }
public static var openAPIComponentsKeyPath: WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>> { \.parameters }
public static var openAPIComponentsKeyPath: Either<WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>>, WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentReferenceDictionary<Self>>> { .b(\.parameters) }
}

extension OpenAPI.Example: ComponentDictionaryLocatable {
public static var openAPIComponentsKey: String { "examples" }
public static var openAPIComponentsKeyPath: WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>> { \.examples }
public static var openAPIComponentsKeyPath: Either<WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>>, WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentReferenceDictionary<Self>>> { .b(\.examples) }
}

extension OpenAPI.Request: ComponentDictionaryLocatable {
public static var openAPIComponentsKey: String { "requestBodies" }
public static var openAPIComponentsKeyPath: WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>> { \.requestBodies }
public static var openAPIComponentsKeyPath: Either<WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>>, WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentReferenceDictionary<Self>>> { .b(\.requestBodies) }
}

extension OpenAPI.Header: ComponentDictionaryLocatable {
public static var openAPIComponentsKey: String { "headers" }
public static var openAPIComponentsKeyPath: WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>> { \.headers }
public static var openAPIComponentsKeyPath: Either<WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>>, WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentReferenceDictionary<Self>>> { .b(\.headers) }
}

extension OpenAPI.SecurityScheme: ComponentDictionaryLocatable {
public static var openAPIComponentsKey: String { "securitySchemes" }
public static var openAPIComponentsKeyPath: WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>> { \.securitySchemes }
public static var openAPIComponentsKeyPath: Either<WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>>, WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentReferenceDictionary<Self>>> { .b(\.securitySchemes) }
}

extension OpenAPI.Link: ComponentDictionaryLocatable {
public static var openAPIComponentsKey: String { "links" }
public static var openAPIComponentsKeyPath: WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>> { \.links }
public static var openAPIComponentsKeyPath: Either<WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>>, WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentReferenceDictionary<Self>>> { .b(\.links) }
}

extension OpenAPI.PathItem: ComponentDictionaryLocatable {
public static var openAPIComponentsKey: String { "pathItems" }
public static var openAPIComponentsKeyPath: WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>> { \.pathItems }
public static var openAPIComponentsKeyPath: Either<WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentDictionary<Self>>, WritableKeyPath<OpenAPI.Components, OpenAPI.ComponentReferenceDictionary<Self>>> { .a(\.pathItems) }
}

/// A dereferenceable type can be recursively looked up in
Expand Down
126 changes: 97 additions & 29 deletions Sources/OpenAPIKit/Components Object/Components.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,47 @@ extension OpenAPI {
///
/// This is a place to put reusable components to
/// be referenced from other parts of the spec.
///
/// Most of the components dictionaries can contain either the component
/// directly or a $ref to the component. This distinction can be seen in
/// the types as either `ComponentDictionary<T>` (direct) or
/// `ComponentReferenceDictionary<T>` (direct or by-reference).
///
/// If you are building a Components Object in Swift you may choose to make
/// all of your components direct in which case the
/// `OpenAPI.Components.direct()` convenience constructor will save you
/// some typing and verbosity.
///
/// **Example**
/// OpenAPI.Components(
/// parameters: [ "my_param": .parameter(.cookie(name: "my_param", schema: .string)) ]
/// )
///
/// // The above value is the same as the below value
///
/// OpenAPI.Components.direct(
/// parameters: [ "my_param": .cookie(name: "my_param", schema: .string) ]
/// )
///
/// // However, the `init()` initializer does allow you to use references where desired
///
/// OpenAPI.Components(
/// parameters: [
/// "my_direct_param": .parameter(.cookie(name: "my_param", schema: .string)),
/// "my_param": .reference(.component(named: "my_direct_param"))
/// ]
/// )
public struct Components: Equatable, CodableVendorExtendable, Sendable {

public var schemas: ComponentDictionary<JSONSchema>
public var responses: ComponentDictionary<Response>
public var parameters: ComponentDictionary<Parameter>
public var examples: ComponentDictionary<Example>
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 responses: ComponentReferenceDictionary<Response>
public var parameters: ComponentReferenceDictionary<Parameter>
public var examples: ComponentReferenceDictionary<Example>
public var requestBodies: ComponentReferenceDictionary<Request>
public var headers: ComponentReferenceDictionary<Header>
public var securitySchemes: ComponentReferenceDictionary<SecurityScheme>
public var links: ComponentReferenceDictionary<Link>
public var callbacks: ComponentReferenceDictionary<Callbacks>

public var pathItems: ComponentDictionary<PathItem>

Expand All @@ -38,14 +68,14 @@ extension OpenAPI {

public init(
schemas: ComponentDictionary<JSONSchema> = [:],
responses: ComponentDictionary<Response> = [:],
parameters: ComponentDictionary<Parameter> = [:],
examples: ComponentDictionary<Example> = [:],
requestBodies: ComponentDictionary<Request> = [:],
headers: ComponentDictionary<Header> = [:],
securitySchemes: ComponentDictionary<SecurityScheme> = [:],
links: ComponentDictionary<Link> = [:],
callbacks: ComponentDictionary<Callbacks> = [:],
responses: ComponentReferenceDictionary<Response> = [:],
parameters: ComponentReferenceDictionary<Parameter> = [:],
examples: ComponentReferenceDictionary<Example> = [:],
requestBodies: ComponentReferenceDictionary<Request> = [:],
headers: ComponentReferenceDictionary<Header> = [:],
securitySchemes: ComponentReferenceDictionary<SecurityScheme> = [:],
links: ComponentReferenceDictionary<Link> = [:],
callbacks: ComponentReferenceDictionary<Callbacks> = [:],
pathItems: ComponentDictionary<PathItem> = [:],
vendorExtensions: [String: AnyCodable] = [:]
) {
Expand All @@ -62,6 +92,37 @@ extension OpenAPI {
self.vendorExtensions = vendorExtensions
}

/// Construct components as "direct" entries (no references). When
/// building a document in Swift code, this is often sufficient and it
/// means you don't need to wrap every entry in an `Either`.
public static func direct(
schemas: ComponentDictionary<JSONSchema> = [:],
responses: ComponentDictionary<Response> = [:],
parameters: ComponentDictionary<Parameter> = [:],
examples: ComponentDictionary<Example> = [:],
requestBodies: ComponentDictionary<Request> = [:],
headers: ComponentDictionary<Header> = [:],
securitySchemes: ComponentDictionary<SecurityScheme> = [:],
links: ComponentDictionary<Link> = [:],
callbacks: ComponentDictionary<Callbacks> = [:],
pathItems: ComponentDictionary<PathItem> = [:],
vendorExtensions: [String: AnyCodable] = [:]
) -> Self {
.init(
schemas: schemas,
responses: responses.mapValues { .b($0) },
parameters: parameters.mapValues { .b($0) },
examples: examples.mapValues { .b($0) },
requestBodies: requestBodies.mapValues { .b($0) },
headers: headers.mapValues { .b($0) },
securitySchemes: securitySchemes.mapValues { .b($0) },
links: links.mapValues { .b($0) },
callbacks: callbacks.mapValues { .b($0) },
pathItems: pathItems,
vendorExtensions: vendorExtensions
)
}

/// An empty OpenAPI Components Object.
public static let noComponents: Components = .init()

Expand All @@ -71,6 +132,12 @@ extension OpenAPI {
}
}

extension OpenAPI {

public typealias ComponentDictionary<T> = OrderedDictionary<ComponentKey, T>
public typealias ComponentReferenceDictionary<T: ComponentDictionaryLocatable> = OrderedDictionary<ComponentKey, Either<OpenAPI.Reference<T>, T>>
}

extension OpenAPI.Components {
public struct ComponentCollision: Swift.Error {
public let componentType: String
Expand Down Expand Up @@ -130,11 +197,6 @@ extension OpenAPI.Components {
public static let componentNameExtension: String = "x-component-name"
}

extension OpenAPI {

public typealias ComponentDictionary<T> = OrderedDictionary<ComponentKey, T>
}

// MARK: - Codable
extension OpenAPI.Components: Encodable {
public func encode(to encoder: Encoder) throws {
Expand Down Expand Up @@ -194,30 +256,36 @@ extension OpenAPI.Components: Decodable {
schemas = try container.decodeIfPresent(OpenAPI.ComponentDictionary<JSONSchema>.self, forKey: .schemas)
?? [:]

responses = try container.decodeIfPresent(OpenAPI.ComponentDictionary<OpenAPI.Response>.self, forKey: .responses)
responses = try container.decodeIfPresent(OpenAPI.ComponentReferenceDictionary<OpenAPI.Response>.self, forKey: .responses)
?? [:]

parameters = try container.decodeIfPresent(OpenAPI.ComponentDictionary<OpenAPI.Parameter>.self, forKey: .parameters)
parameters = try container.decodeIfPresent(OpenAPI.ComponentReferenceDictionary<OpenAPI.Parameter>.self, forKey: .parameters)
?? [:]

examples = try container.decodeIfPresent(OpenAPI.ComponentDictionary<OpenAPI.Example>.self, forKey: .examples)
examples = try container.decodeIfPresent(OpenAPI.ComponentReferenceDictionary<OpenAPI.Example>.self, forKey: .examples)
?? [:]

requestBodies = try container.decodeIfPresent(OpenAPI.ComponentDictionary<OpenAPI.Request>.self, forKey: .requestBodies)
requestBodies = try container.decodeIfPresent(OpenAPI.ComponentReferenceDictionary<OpenAPI.Request>.self, forKey: .requestBodies)
?? [:]

headers = try container.decodeIfPresent(OpenAPI.ComponentDictionary<OpenAPI.Header>.self, forKey: .headers)
headers = try container.decodeIfPresent(OpenAPI.ComponentReferenceDictionary<OpenAPI.Header>.self, forKey: .headers)
?? [:]

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

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

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

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

vendorExtensions = try Self.extensions(from: decoder)
} catch let error as EitherDecodeNoTypesMatchedError {
if let underlyingError = OpenAPI.Error.Decoding.Document.eitherBranchToDigInto(error) {
throw (underlyingError.underlyingError ?? underlyingError)
}

throw error
} catch let error as DecodingError {
if let underlyingError = error.underlyingError as? KeyDecodingError {
throw GenericError(
Expand Down
25 changes: 25 additions & 0 deletions Sources/OpenAPIKit/Either/Either+Convenience.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,16 @@ extension Either where B == OpenAPI.Header {
public var headerValue: B? { b }
}

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

extension Either where B == OpenAPI.SecurityScheme {
/// Retrieve the security scheme if that is what this property contains.
public var securitySchemeValue: B? { b }
}

// MARK: - Convenience constructors
extension Either where A == Bool {
/// Construct a boolean value.
Expand Down Expand Up @@ -220,7 +230,22 @@ extension Either where B == OpenAPI.Response {
public static func response(_ response: OpenAPI.Response) -> Self { .b(response) }
}

extension Either where B == OpenAPI.Link {
/// Construct a link value.
public static func link(_ link: OpenAPI.Link) -> Self { .b(link) }
}

extension Either where B == OpenAPI.Header {
/// Construct a header value.
public static func header(_ header: OpenAPI.Header) -> Self { .b(header) }
}

extension Either where B == OpenAPI.Callbacks {
/// Construct a callbacks value.
public static func callbacks(_ callbacks: OpenAPI.Callbacks) -> Self { .b(callbacks) }
}

extension Either where B == OpenAPI.SecurityScheme {
/// Construct a security scheme value.
public static func securityScheme(_ securityScheme: OpenAPI.SecurityScheme) -> Self { .b(securityScheme) }
}
21 changes: 20 additions & 1 deletion Sources/OpenAPIKit/JSONReference.swift
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,20 @@ public protocol OpenAPISummarizable: OpenAPIDescribable {
func overriddenNonNil(summary: String?) -> Self
}

extension OpenAPI.Reference: OpenAPISummarizable {
public func overriddenNonNil(summary: String?) -> Self {
guard let summary else { return self }

return .init(jsonReference, summary: summary, description: description)
}

public func overriddenNonNil(description: String?) -> Self {
guard let description else { return self }

return .init(jsonReference, summary: summary, description: description)
}
}

// MARK: - Codable

extension JSONReference {
Expand Down Expand Up @@ -558,7 +572,12 @@ extension JSONReference: ExternallyDereferenceable where ReferenceType: External
let componentKey = try loader.componentKey(type: ReferenceType.self, at: url)
let (component, messages): (ReferenceType, [Loader.Message]) = try await loader.load(url)
var components = OpenAPI.Components()
components[keyPath: ReferenceType.openAPIComponentsKeyPath][componentKey] = component
switch ReferenceType.openAPIComponentsKeyPath {
case .a(let directPath):
components[keyPath: directPath][componentKey] = component
case .b(let referencePath):
components[keyPath: referencePath][componentKey] = .b(component)
}
return (try components.reference(named: componentKey.rawValue, ofType: ReferenceType.self).jsonReference, components, messages)
}
}
Expand Down
16 changes: 8 additions & 8 deletions Sources/OpenAPIKitCompat/Compat30To31.swift
Original file line number Diff line number Diff line change
Expand Up @@ -652,14 +652,14 @@ extension OpenAPIKit30.OpenAPI.Components: To31 {
fileprivate func to31() -> OpenAPIKit.OpenAPI.Components {
OpenAPIKit.OpenAPI.Components(
schemas: schemas.mapValues { $0.to31() },
responses: responses.mapValues { $0.to31() },
parameters: parameters.mapValues { $0.to31() },
examples: examples.mapValues { $0.to31() },
requestBodies: requestBodies.mapValues { $0.to31() },
headers: headers.mapValues { $0.to31() },
securitySchemes: securitySchemes.mapValues { $0.to31() },
links: links.mapValues { $0.to31() },
callbacks: callbacks.mapValues { $0.to31() },
responses: responses.mapValues { .b($0.to31()) },
parameters: parameters.mapValues { .b($0.to31()) },
examples: examples.mapValues { .b($0.to31()) },
requestBodies: requestBodies.mapValues { .b($0.to31()) },
headers: headers.mapValues { .b($0.to31()) },
securitySchemes: securitySchemes.mapValues { .b($0.to31()) },
links: links.mapValues { .b($0.to31()) },
callbacks: callbacks.mapValues { .b($0.to31()) },
vendorExtensions: vendorExtensions
)
}
Expand Down
Loading