diff --git a/Sources/OpenAPIKit/Components Object/Components.swift b/Sources/OpenAPIKit/Components Object/Components.swift index a92862d76..e90d318b1 100644 --- a/Sources/OpenAPIKit/Components Object/Components.swift +++ b/Sources/OpenAPIKit/Components Object/Components.swift @@ -320,7 +320,7 @@ extension OpenAPI.Components { } extension OpenAPI.Components { - internal mutating func externallyDereference(in context: Context.Type, depth: ExternalDereferenceDepth = .iterations(1)) async throws { + internal mutating func externallyDereference(with loader: Loader.Type, depth: ExternalDereferenceDepth = .iterations(1)) async throws { if case let .iterations(number) = depth, number <= 0 { return @@ -336,15 +336,15 @@ extension OpenAPI.Components { let oldCallbacks = callbacks let oldPathItems = pathItems - async let (newSchemas, c1) = oldSchemas.externallyDereferenced(with: context) - async let (newResponses, c2) = oldResponses.externallyDereferenced(with: context) - async let (newParameters, c3) = oldParameters.externallyDereferenced(with: context) - async let (newExamples, c4) = oldExamples.externallyDereferenced(with: context) - async let (newRequestBodies, c5) = oldRequestBodies.externallyDereferenced(with: context) - async let (newHeaders, c6) = oldHeaders.externallyDereferenced(with: context) - async let (newSecuritySchemes, c7) = oldSecuritySchemes.externallyDereferenced(with: context) - async let (newCallbacks, c8) = oldCallbacks.externallyDereferenced(with: context) - async let (newPathItems, c9) = oldPathItems.externallyDereferenced(with: context) + async let (newSchemas, c1) = oldSchemas.externallyDereferenced(with: loader) + async let (newResponses, c2) = oldResponses.externallyDereferenced(with: loader) + async let (newParameters, c3) = oldParameters.externallyDereferenced(with: loader) + async let (newExamples, c4) = oldExamples.externallyDereferenced(with: loader) + async let (newRequestBodies, c5) = oldRequestBodies.externallyDereferenced(with: loader) + async let (newHeaders, c6) = oldHeaders.externallyDereferenced(with: loader) + async let (newSecuritySchemes, c7) = oldSecuritySchemes.externallyDereferenced(with: loader) + async let (newCallbacks, c8) = oldCallbacks.externallyDereferenced(with: loader) + async let (newPathItems, c9) = oldPathItems.externallyDereferenced(with: loader) schemas = try await newSchemas responses = try await newResponses @@ -391,9 +391,9 @@ extension OpenAPI.Components { switch depth { case .iterations(let number): - try await externallyDereference(in: context, depth: .iterations(number - 1)) + try await externallyDereference(with: loader, depth: .iterations(number - 1)) case .full: - try await externallyDereference(in: context, depth: .full) + try await externallyDereference(with: loader, depth: .full) } } } diff --git a/Sources/OpenAPIKit/Content/DereferencedContent.swift b/Sources/OpenAPIKit/Content/DereferencedContent.swift index 992eea1a7..afdcd582e 100644 --- a/Sources/OpenAPIKit/Content/DereferencedContent.swift +++ b/Sources/OpenAPIKit/Content/DereferencedContent.swift @@ -77,7 +77,7 @@ extension OpenAPI.Content: LocallyDereferenceable { } extension OpenAPI.Content: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { let oldSchema = schema async let (newSchema, c1) = oldSchema.externallyDereferenced(with: loader) diff --git a/Sources/OpenAPIKit/Content/DereferencedContentEncoding.swift b/Sources/OpenAPIKit/Content/DereferencedContentEncoding.swift index f6d43cb5e..605ae7426 100644 --- a/Sources/OpenAPIKit/Content/DereferencedContentEncoding.swift +++ b/Sources/OpenAPIKit/Content/DereferencedContentEncoding.swift @@ -58,7 +58,7 @@ extension OpenAPI.Content.Encoding: LocallyDereferenceable { } extension OpenAPI.Content.Encoding: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { let newHeaders: OpenAPI.Header.Map? let newComponents: OpenAPI.Components diff --git a/Sources/OpenAPIKit/Document/Document.swift b/Sources/OpenAPIKit/Document/Document.swift index 75a703363..9864d8280 100644 --- a/Sources/OpenAPIKit/Document/Document.swift +++ b/Sources/OpenAPIKit/Document/Document.swift @@ -362,7 +362,7 @@ extension OpenAPI.Document { return try DereferencedDocument(self) } - public mutating func externallyDereference(in context: Context.Type, depth: ExternalDereferenceDepth = .iterations(1)) async throws { + public mutating func externallyDereference(with loader: Loader.Type, depth: ExternalDereferenceDepth = .iterations(1)) async throws { if case let .iterations(number) = depth, number <= 0 { return @@ -371,15 +371,15 @@ extension OpenAPI.Document { let oldPaths = paths let oldWebhooks = webhooks - async let (newPaths, c1) = oldPaths.externallyDereferenced(with: context) - async let (newWebhooks, c2) = oldWebhooks.externallyDereferenced(with: context) + async let (newPaths, c1) = oldPaths.externallyDereferenced(with: loader) + async let (newWebhooks, c2) = oldWebhooks.externallyDereferenced(with: loader) paths = try await newPaths webhooks = try await newWebhooks try await components.merge(c1) try await components.merge(c2) - try await components.externallyDereference(in: context, depth: depth) + try await components.externallyDereference(with: loader, depth: depth) } } diff --git a/Sources/OpenAPIKit/Either/Either+ExternallyDereferenceable.swift b/Sources/OpenAPIKit/Either/Either+ExternallyDereferenceable.swift index 5dfe12868..8c202bb5b 100644 --- a/Sources/OpenAPIKit/Either/Either+ExternallyDereferenceable.swift +++ b/Sources/OpenAPIKit/Either/Either+ExternallyDereferenceable.swift @@ -10,7 +10,7 @@ import OpenAPIKitCore // MARK: - ExternallyDereferenceable extension Either: ExternallyDereferenceable where A: ExternallyDereferenceable, B: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { switch self { case .a(let a): let (newA, components) = try await a.externallyDereferenced(with: loader) diff --git a/Sources/OpenAPIKit/Example.swift b/Sources/OpenAPIKit/Example.swift index d61fc8392..74c9f7226 100644 --- a/Sources/OpenAPIKit/Example.swift +++ b/Sources/OpenAPIKit/Example.swift @@ -209,7 +209,7 @@ extension OpenAPI.Example: LocallyDereferenceable { } extension OpenAPI.Example: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { return (self, .init()) } } diff --git a/Sources/OpenAPIKit/ExternalLoader.swift b/Sources/OpenAPIKit/ExternalLoader.swift index 2b62c1699..257264995 100644 --- a/Sources/OpenAPIKit/ExternalLoader.swift +++ b/Sources/OpenAPIKit/ExternalLoader.swift @@ -30,5 +30,5 @@ public protocol ExternalLoader { } public protocol ExternallyDereferenceable { - func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) + func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) } diff --git a/Sources/OpenAPIKit/Header/DereferencedHeader.swift b/Sources/OpenAPIKit/Header/DereferencedHeader.swift index 554a5b266..ec9881c71 100644 --- a/Sources/OpenAPIKit/Header/DereferencedHeader.swift +++ b/Sources/OpenAPIKit/Header/DereferencedHeader.swift @@ -84,7 +84,7 @@ extension OpenAPI.Header: LocallyDereferenceable { } extension OpenAPI.Header: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { // if not for a Swift bug, this whole next bit would just be the // next line: diff --git a/Sources/OpenAPIKit/JSONReference.swift b/Sources/OpenAPIKit/JSONReference.swift index 75621e00a..7d59430f5 100644 --- a/Sources/OpenAPIKit/JSONReference.swift +++ b/Sources/OpenAPIKit/JSONReference.swift @@ -538,7 +538,7 @@ extension JSONReference: LocallyDereferenceable where ReferenceType: LocallyDere } extension JSONReference: ExternallyDereferenceable where ReferenceType: ExternallyDereferenceable & Decodable & Equatable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { switch self { case .internal(let ref): return (.internal(ref), .init()) @@ -580,7 +580,7 @@ extension OpenAPI.Reference: LocallyDereferenceable where ReferenceType: Locally } extension OpenAPI.Reference: ExternallyDereferenceable where ReferenceType: ExternallyDereferenceable & Decodable & Equatable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { let (newRef, components) = try await jsonReference.externallyDereferenced(with: loader) return (.init(newRef), components) } diff --git a/Sources/OpenAPIKit/Link.swift b/Sources/OpenAPIKit/Link.swift index be416dcff..dde15299e 100644 --- a/Sources/OpenAPIKit/Link.swift +++ b/Sources/OpenAPIKit/Link.swift @@ -290,7 +290,7 @@ extension OpenAPI.Link: LocallyDereferenceable { } extension OpenAPI.Link: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { let (newServer, newComponents) = try await server.externallyDereferenced(with: loader) var newLink = self diff --git a/Sources/OpenAPIKit/Operation/DereferencedOperation.swift b/Sources/OpenAPIKit/Operation/DereferencedOperation.swift index dfdd14c9d..f4139fcd5 100644 --- a/Sources/OpenAPIKit/Operation/DereferencedOperation.swift +++ b/Sources/OpenAPIKit/Operation/DereferencedOperation.swift @@ -126,7 +126,7 @@ extension OpenAPI.Operation: LocallyDereferenceable { } extension OpenAPI.Operation: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { let oldParameters = parameters let oldRequestBody = requestBody let oldResponses = responses diff --git a/Sources/OpenAPIKit/Parameter/DereferencedParameter.swift b/Sources/OpenAPIKit/Parameter/DereferencedParameter.swift index b2b6b9604..e0385e1c1 100644 --- a/Sources/OpenAPIKit/Parameter/DereferencedParameter.swift +++ b/Sources/OpenAPIKit/Parameter/DereferencedParameter.swift @@ -84,7 +84,7 @@ extension OpenAPI.Parameter: LocallyDereferenceable { } extension OpenAPI.Parameter: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { // if not for a Swift bug, this whole function would just be the // next line: diff --git a/Sources/OpenAPIKit/Parameter/DereferencedSchemaContext.swift b/Sources/OpenAPIKit/Parameter/DereferencedSchemaContext.swift index cea39b921..68f9bb772 100644 --- a/Sources/OpenAPIKit/Parameter/DereferencedSchemaContext.swift +++ b/Sources/OpenAPIKit/Parameter/DereferencedSchemaContext.swift @@ -71,7 +71,7 @@ extension OpenAPI.Parameter.SchemaContext: LocallyDereferenceable { } extension OpenAPI.Parameter.SchemaContext: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { let oldSchema = schema async let (newSchema, c1) = oldSchema.externallyDereferenced(with: loader) diff --git a/Sources/OpenAPIKit/Path Item/DereferencedPathItem.swift b/Sources/OpenAPIKit/Path Item/DereferencedPathItem.swift index fc284b1df..48d9b038a 100644 --- a/Sources/OpenAPIKit/Path Item/DereferencedPathItem.swift +++ b/Sources/OpenAPIKit/Path Item/DereferencedPathItem.swift @@ -140,7 +140,7 @@ extension OpenAPI.PathItem: LocallyDereferenceable { } extension OpenAPI.PathItem: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { let oldParameters = parameters let oldServers = servers let oldGet = get @@ -154,22 +154,39 @@ extension OpenAPI.PathItem: ExternallyDereferenceable { async let (newParameters, c1) = oldParameters.externallyDereferenced(with: loader) // async let (newServers, c2) = oldServers.externallyDereferenced(with: loader) -// async let (newGet, c3) = oldGet.externallyDereferenced(with: loader) -// async let (newPut, c4) = oldPut.externallyDereferenced(with: loader) -// async let (newPost, c5) = oldPost.externallyDereferenced(with: loader) -// async let (newDelete, c6) = oldDelete.externallyDereferenced(with: loader) -// async let (newOptions, c7) = oldOptions.externallyDereferenced(with: loader) -// async let (newHead, c8) = oldHead.externallyDereferenced(with: loader) -// async let (newPatch, c9) = oldPatch.externallyDereferenced(with: loader) -// async let (newTrace, c10) = oldTrace.externallyDereferenced(with: loader) + async let (newGet, c3) = oldGet.externallyDereferenced(with: loader) + async let (newPut, c4) = oldPut.externallyDereferenced(with: loader) + async let (newPost, c5) = oldPost.externallyDereferenced(with: loader) + async let (newDelete, c6) = oldDelete.externallyDereferenced(with: loader) + async let (newOptions, c7) = oldOptions.externallyDereferenced(with: loader) + async let (newHead, c8) = oldHead.externallyDereferenced(with: loader) + async let (newPatch, c9) = oldPatch.externallyDereferenced(with: loader) + async let (newTrace, c10) = oldTrace.externallyDereferenced(with: loader) var pathItem = self var newComponents = try await c1 // ideally we would async let all of the props above and then set them here, // but for now since there seems to be some sort of compiler bug we will do - // most of them in if lets below + // newServers in an if let below pathItem.parameters = try await newParameters + pathItem.get = try await newGet + pathItem.put = try await newPut + pathItem.post = try await newPost + pathItem.delete = try await newDelete + pathItem.options = try await newOptions + pathItem.head = try await newHead + pathItem.patch = try await newPatch + pathItem.trace = try await newTrace + + try await newComponents.merge(c3) + try await newComponents.merge(c4) + try await newComponents.merge(c5) + try await newComponents.merge(c6) + try await newComponents.merge(c7) + try await newComponents.merge(c8) + try await newComponents.merge(c9) + try await newComponents.merge(c10) if let oldServers { async let (newServers, c2) = oldServers.externallyDereferenced(with: loader) @@ -177,54 +194,6 @@ extension OpenAPI.PathItem: ExternallyDereferenceable { try await newComponents.merge(c2) } - if let oldGet { - async let (newGet, c3) = oldGet.externallyDereferenced(with: loader) - pathItem.get = try await newGet - try await newComponents.merge(c3) - } - - if let oldPut { - async let (newPut, c4) = oldPut.externallyDereferenced(with: loader) - pathItem.put = try await newPut - try await newComponents.merge(c4) - } - - if let oldPost { - async let (newPost, c5) = oldPost.externallyDereferenced(with: loader) - pathItem.post = try await newPost - try await newComponents.merge(c5) - } - - if let oldDelete { - async let (newDelete, c6) = oldDelete.externallyDereferenced(with: loader) - pathItem.delete = try await newDelete - try await newComponents.merge(c6) - } - - if let oldOptions { - async let (newOptions, c7) = oldOptions.externallyDereferenced(with: loader) - pathItem.options = try await newOptions - try await newComponents.merge(c7) - } - - if let oldHead { - async let (newHead, c8) = oldHead.externallyDereferenced(with: loader) - pathItem.head = try await newHead - try await newComponents.merge(c8) - } - - if let oldPatch { - async let (newPatch, c9) = oldPatch.externallyDereferenced(with: loader) - pathItem.patch = try await newPatch - try await newComponents.merge(c9) - } - - if let oldTrace { - async let (newTrace, c10) = oldTrace.externallyDereferenced(with: loader) - pathItem.trace = try await newTrace - try await newComponents.merge(c10) - } - return (pathItem, newComponents) } } diff --git a/Sources/OpenAPIKit/Request/DereferencedRequest.swift b/Sources/OpenAPIKit/Request/DereferencedRequest.swift index a5dce43bd..39d0c2409 100644 --- a/Sources/OpenAPIKit/Request/DereferencedRequest.swift +++ b/Sources/OpenAPIKit/Request/DereferencedRequest.swift @@ -63,7 +63,7 @@ extension OpenAPI.Request: LocallyDereferenceable { } extension OpenAPI.Request: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { var newRequest = self let (newContent, components) = try await content.externallyDereferenced(with: loader) diff --git a/Sources/OpenAPIKit/Response/DereferencedResponse.swift b/Sources/OpenAPIKit/Response/DereferencedResponse.swift index 363e33448..add208773 100644 --- a/Sources/OpenAPIKit/Response/DereferencedResponse.swift +++ b/Sources/OpenAPIKit/Response/DereferencedResponse.swift @@ -78,12 +78,14 @@ extension OpenAPI.Response: LocallyDereferenceable { } extension OpenAPI.Response: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { let oldContent = content let oldLinks = links + let oldHeaders = headers async let (newContent, c1) = oldContent.externallyDereferenced(with: loader) async let (newLinks, c2) = oldLinks.externallyDereferenced(with: loader) +// async let (newHeaders, c3) = oldHeaders.externallyDereferenced(with: loader) var response = self response.content = try await newContent @@ -92,7 +94,7 @@ extension OpenAPI.Response: ExternallyDereferenceable { var components = try await c1 try await components.merge(c2) - if let oldHeaders = headers { + if let oldHeaders { let (newHeaders, c3) = try await oldHeaders.externallyDereferenced(with: loader) response.headers = newHeaders try components.merge(c3) diff --git a/Sources/OpenAPIKit/Schema Object/DereferencedJSONSchema.swift b/Sources/OpenAPIKit/Schema Object/DereferencedJSONSchema.swift index faeccde28..dfe1af9e3 100644 --- a/Sources/OpenAPIKit/Schema Object/DereferencedJSONSchema.swift +++ b/Sources/OpenAPIKit/Schema Object/DereferencedJSONSchema.swift @@ -536,7 +536,7 @@ extension JSONSchema: LocallyDereferenceable { } extension JSONSchema: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { let newSchema: JSONSchema let newComponents: OpenAPI.Components diff --git a/Sources/OpenAPIKit/Schema Object/JSONSchema.swift b/Sources/OpenAPIKit/Schema Object/JSONSchema.swift index cba7fe6d6..27564a342 100644 --- a/Sources/OpenAPIKit/Schema Object/JSONSchema.swift +++ b/Sources/OpenAPIKit/Schema Object/JSONSchema.swift @@ -13,7 +13,7 @@ import OpenAPIKitCore public struct JSONSchema: JSONSchemaContext, HasWarnings { public let warnings: [OpenAPI.Warning] - public let value: Schema + public var value: Schema internal init(warnings: [OpenAPI.Warning], schema: Schema) { self.warnings = warnings @@ -441,8 +441,8 @@ extension JSONSchema: VendorExtendable { get { coreContext.vendorExtensions } - set { - coreContext.vendorExtensions + set(extensions) { + self.value = value.with(vendorExtensions: extensions) } } diff --git a/Sources/OpenAPIKit/Security/SecurityScheme.swift b/Sources/OpenAPIKit/Security/SecurityScheme.swift index a304bccfd..c4af0ff78 100644 --- a/Sources/OpenAPIKit/Security/SecurityScheme.swift +++ b/Sources/OpenAPIKit/Security/SecurityScheme.swift @@ -275,7 +275,7 @@ extension OpenAPI.SecurityScheme: LocallyDereferenceable { } extension OpenAPI.SecurityScheme: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { return (self, .init()) } } diff --git a/Sources/OpenAPIKit/Server.swift b/Sources/OpenAPIKit/Server.swift index 4bea3607f..80f4bec07 100644 --- a/Sources/OpenAPIKit/Server.swift +++ b/Sources/OpenAPIKit/Server.swift @@ -260,7 +260,7 @@ extension OpenAPI.Server.Variable { } extension OpenAPI.Server: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { return (self, .init()) } } diff --git a/Sources/OpenAPIKit/Utility/Array+ExternallyDereferenceable.swift b/Sources/OpenAPIKit/Utility/Array+ExternallyDereferenceable.swift index 1435c2c95..a67002587 100644 --- a/Sources/OpenAPIKit/Utility/Array+ExternallyDereferenceable.swift +++ b/Sources/OpenAPIKit/Utility/Array+ExternallyDereferenceable.swift @@ -6,7 +6,7 @@ import OpenAPIKitCore extension Array where Element: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { try await withThrowingTaskGroup(of: (Int, (Element, OpenAPI.Components)).self) { group in for (idx, elem) in zip(self.indices, self) { group.addTask { diff --git a/Sources/OpenAPIKit/Utility/Dictionary+ExternallyDereferenceable.swift b/Sources/OpenAPIKit/Utility/Dictionary+ExternallyDereferenceable.swift index 2ba0a8fcb..d15b123b2 100644 --- a/Sources/OpenAPIKit/Utility/Dictionary+ExternallyDereferenceable.swift +++ b/Sources/OpenAPIKit/Utility/Dictionary+ExternallyDereferenceable.swift @@ -7,7 +7,7 @@ import OpenAPIKitCore extension Dictionary where Value: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { try await withThrowingTaskGroup(of: (Key, Value, OpenAPI.Components).self) { group in for (key, value) in self { group.addTask { diff --git a/Sources/OpenAPIKit/Utility/Optional+ExternallyDereferenceable.swift b/Sources/OpenAPIKit/Utility/Optional+ExternallyDereferenceable.swift index adf2b1816..79b055b52 100644 --- a/Sources/OpenAPIKit/Utility/Optional+ExternallyDereferenceable.swift +++ b/Sources/OpenAPIKit/Utility/Optional+ExternallyDereferenceable.swift @@ -6,7 +6,7 @@ import OpenAPIKitCore extension Optional where Wrapped: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { guard let wrapped = self else { return (nil, .init()) } return try await wrapped.externallyDereferenced(with: loader) } diff --git a/Sources/OpenAPIKit/Utility/OrderedDictionary+ExternallyDereferenceable.swift b/Sources/OpenAPIKit/Utility/OrderedDictionary+ExternallyDereferenceable.swift index 283aab817..38ab9bbc3 100644 --- a/Sources/OpenAPIKit/Utility/OrderedDictionary+ExternallyDereferenceable.swift +++ b/Sources/OpenAPIKit/Utility/OrderedDictionary+ExternallyDereferenceable.swift @@ -9,7 +9,7 @@ import OpenAPIKitCore extension OrderedDictionary where Value: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Context.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { try await withThrowingTaskGroup(of: (Key, Value, OpenAPI.Components).self) { group in for (key, value) in self { group.addTask { diff --git a/Sources/OpenAPIKit30/Callbacks.swift b/Sources/OpenAPIKit30/Callbacks.swift index f0504fa25..3e0341915 100644 --- a/Sources/OpenAPIKit30/Callbacks.swift +++ b/Sources/OpenAPIKit30/Callbacks.swift @@ -35,3 +35,9 @@ extension OpenAPI.CallbackURL: LocallyDereferenceable { self } } + +// The following conformance is theoretically unnecessary but the compiler is +// only able to find the conformance if we explicitly declare it here, though +// it is apparently able to determine the conformance is already satisfied here +// at least. +extension OpenAPI.Callbacks: ExternallyDereferenceable { } diff --git a/Sources/OpenAPIKit30/CodableVendorExtendable.swift b/Sources/OpenAPIKit30/CodableVendorExtendable.swift index 9cfa2e0e0..1c75c293e 100644 --- a/Sources/OpenAPIKit30/CodableVendorExtendable.swift +++ b/Sources/OpenAPIKit30/CodableVendorExtendable.swift @@ -18,7 +18,7 @@ public protocol VendorExtendable { /// These should be of the form: /// `[ "x-extensionKey": ]` /// where the values are anything codable. - var vendorExtensions: VendorExtensions { get } + var vendorExtensions: VendorExtensions { get set } } public enum VendorExtensionsConfiguration { diff --git a/Sources/OpenAPIKit30/Components Object/Components+Locatable.swift b/Sources/OpenAPIKit30/Components Object/Components+Locatable.swift index ed2172e2b..cd6fcfd91 100644 --- a/Sources/OpenAPIKit30/Components Object/Components+Locatable.swift +++ b/Sources/OpenAPIKit30/Components Object/Components+Locatable.swift @@ -15,59 +15,59 @@ public protocol ComponentDictionaryLocatable { /// This can be used to create a JSON path /// like `#/name1/name2/name3` static var openAPIComponentsKey: String { get } - static var openAPIComponentsKeyPath: KeyPath> { get } + static var openAPIComponentsKeyPath: WritableKeyPath> { get } } extension JSONSchema: ComponentDictionaryLocatable { public static var openAPIComponentsKey: String { "schemas" } - public static var openAPIComponentsKeyPath: KeyPath> { \.schemas } + public static var openAPIComponentsKeyPath: WritableKeyPath> { \.schemas } } extension OpenAPI.Response: ComponentDictionaryLocatable { public static var openAPIComponentsKey: String { "responses" } - public static var openAPIComponentsKeyPath: KeyPath> { \.responses } + public static var openAPIComponentsKeyPath: WritableKeyPath> { \.responses } } extension OpenAPI.Callbacks: ComponentDictionaryLocatable { public static var openAPIComponentsKey: String { "callbacks" } - public static var openAPIComponentsKeyPath: KeyPath> { \.callbacks } + public static var openAPIComponentsKeyPath: WritableKeyPath> { \.callbacks } } extension OpenAPI.Parameter: ComponentDictionaryLocatable { public static var openAPIComponentsKey: String { "parameters" } - public static var openAPIComponentsKeyPath: KeyPath> { \.parameters } + public static var openAPIComponentsKeyPath: WritableKeyPath> { \.parameters } } extension OpenAPI.Example: ComponentDictionaryLocatable { public static var openAPIComponentsKey: String { "examples" } - public static var openAPIComponentsKeyPath: KeyPath> { \.examples } + public static var openAPIComponentsKeyPath: WritableKeyPath> { \.examples } } extension OpenAPI.Request: ComponentDictionaryLocatable { public static var openAPIComponentsKey: String { "requestBodies" } - public static var openAPIComponentsKeyPath: KeyPath> { \.requestBodies } + public static var openAPIComponentsKeyPath: WritableKeyPath> { \.requestBodies } } extension OpenAPI.Header: ComponentDictionaryLocatable { public static var openAPIComponentsKey: String { "headers" } - public static var openAPIComponentsKeyPath: KeyPath> { \.headers } + public static var openAPIComponentsKeyPath: WritableKeyPath> { \.headers } } extension OpenAPI.SecurityScheme: ComponentDictionaryLocatable { public static var openAPIComponentsKey: String { "securitySchemes" } - public static var openAPIComponentsKeyPath: KeyPath> { \.securitySchemes } + public static var openAPIComponentsKeyPath: WritableKeyPath> { \.securitySchemes } } extension OpenAPI.Link: ComponentDictionaryLocatable { public static var openAPIComponentsKey: String { "links" } - public static var openAPIComponentsKeyPath: KeyPath> { \.links } + public static var openAPIComponentsKeyPath: WritableKeyPath> { \.links } } // Until OpenAPI 3.1, path items cannot actually be stored in the Components Object. This is here to facilitate path item // references, albeit in a less than ideal way. extension OpenAPI.PathItem: ComponentDictionaryLocatable { public static var openAPIComponentsKey: String { "pathItems" } - public static var openAPIComponentsKeyPath: KeyPath> { \.pathItems } + public static var openAPIComponentsKeyPath: WritableKeyPath> { \.pathItems } } /// A dereferenceable type can be recursively looked up in diff --git a/Sources/OpenAPIKit30/Components Object/Components.swift b/Sources/OpenAPIKit30/Components Object/Components.swift index 93bcad2f5..12b79fced 100644 --- a/Sources/OpenAPIKit30/Components Object/Components.swift +++ b/Sources/OpenAPIKit30/Components Object/Components.swift @@ -71,6 +71,57 @@ extension OpenAPI { } } +extension OpenAPI.Components { + public struct ComponentCollision: Swift.Error { + public let componentType: String + public let existingComponent: String + public let newComponent: String + } + + private func detectCollision(type: String) throws -> (_ old: T, _ new: T) throws -> T { + return { old, new in + // theoretically we can detect collisions here, but we would need to compare + // for equality up-to but not including the difference between an external and + // internal reference which is not supported yet. +// if(old == new) { return old } +// throw ComponentCollision(componentType: type, existingComponent: String(describing:old), newComponent: String(describing:new)) + + // Given we aren't ensuring there are no collisions, the old version is going to be + // the one more likely to have been _further_ dereferenced than the new record, so + // we keep that version. + return old + } + } + + public mutating func merge(_ other: OpenAPI.Components) throws { + try schemas.merge(other.schemas, uniquingKeysWith: detectCollision(type: "schema")) + try responses.merge(other.responses, uniquingKeysWith: detectCollision(type: "responses")) + try parameters.merge(other.parameters, uniquingKeysWith: detectCollision(type: "parameters")) + try examples.merge(other.examples, uniquingKeysWith: detectCollision(type: "examples")) + try requestBodies.merge(other.requestBodies, uniquingKeysWith: detectCollision(type: "requestBodies")) + try headers.merge(other.headers, uniquingKeysWith: detectCollision(type: "headers")) + try securitySchemes.merge(other.securitySchemes, uniquingKeysWith: detectCollision(type: "securitySchemes")) + try links.merge(other.links, uniquingKeysWith: detectCollision(type: "links")) + try callbacks.merge(other.callbacks, uniquingKeysWith: detectCollision(type: "callbacks")) + try pathItems.merge(other.pathItems, uniquingKeysWith: detectCollision(type: "pathItems")) + try vendorExtensions.merge(other.vendorExtensions, uniquingKeysWith: detectCollision(type: "vendorExtensions")) + } + + /// Sort the components within each type by the component key. + public mutating func sort() { + schemas.sortKeys() + responses.sortKeys() + parameters.sortKeys() + examples.sortKeys() + requestBodies.sortKeys() + headers.sortKeys() + securitySchemes.sortKeys() + links.sortKeys() + callbacks.sortKeys() + pathItems.sortKeys() + } +} + extension OpenAPI.Components { /// The extension name used to store a Components Object name (the key something is stored under /// within the Components Object). This is used by OpenAPIKit to store the previous Component name @@ -260,24 +311,82 @@ extension OpenAPI.Components { } } -public extension OpenAPI.Components { - struct ValueCollision: Swift.Error { - let value1: T - let value2: T - } +extension OpenAPI.Components { + internal mutating func externallyDereference(with loader: Loader.Type, depth: ExternalDereferenceDepth = .iterations(1)) async throws { + if case let .iterations(number) = depth, + number <= 0 { + return + } - mutating func merge(_ components: OpenAPI.Components) throws { - try schemas.merge(components.schemas, uniquingKeysWith: { (v1, v2) in throw OpenAPI.Components.ValueCollision(value1: v1, value2: v2) }) - try responses.merge(components.responses, uniquingKeysWith: { (v1, v2) in throw OpenAPI.Components.ValueCollision(value1: v1, value2: v2) }) - try parameters.merge(components.parameters, uniquingKeysWith: { (v1, v2) in throw OpenAPI.Components.ValueCollision(value1: v1, value2: v2) }) - try examples.merge(components.examples, uniquingKeysWith: { (v1, v2) in throw OpenAPI.Components.ValueCollision(value1: v1, value2: v2) }) - try requestBodies.merge(components.requestBodies, uniquingKeysWith: { (v1, v2) in throw OpenAPI.Components.ValueCollision(value1: v1, value2: v2) }) - try headers.merge(components.headers, uniquingKeysWith: { (v1, v2) in throw OpenAPI.Components.ValueCollision(value1: v1, value2: v2) }) - try securitySchemes.merge(components.securitySchemes, uniquingKeysWith: { (v1, v2) in throw OpenAPI.Components.ValueCollision(value1: v1, value2: v2) }) - try links.merge(components.links, uniquingKeysWith: { (v1, v2) in throw OpenAPI.Components.ValueCollision(value1: v1, value2: v2) }) - try callbacks.merge(components.callbacks, uniquingKeysWith: { (v1, v2) in throw OpenAPI.Components.ValueCollision(value1: v1, value2: v2) }) - try pathItems.merge(components.pathItems, uniquingKeysWith: { (v1, v2) in throw OpenAPI.Components.ValueCollision(value1: v1, value2: v2) }) - try vendorExtensions.merge(components.vendorExtensions, uniquingKeysWith: { (v1, v2) in throw OpenAPI.Components.ValueCollision(value1: v1, value2: v2) }) + let oldSchemas = schemas + let oldResponses = responses + let oldParameters = parameters + let oldExamples = examples + let oldRequestBodies = requestBodies + let oldHeaders = headers + let oldSecuritySchemes = securitySchemes + let oldCallbacks = callbacks + let oldPathItems = pathItems + + async let (newSchemas, c1) = oldSchemas.externallyDereferenced(with: loader) + async let (newResponses, c2) = oldResponses.externallyDereferenced(with: loader) + async let (newParameters, c3) = oldParameters.externallyDereferenced(with: loader) + async let (newExamples, c4) = oldExamples.externallyDereferenced(with: loader) + async let (newRequestBodies, c5) = oldRequestBodies.externallyDereferenced(with: loader) + async let (newHeaders, c6) = oldHeaders.externallyDereferenced(with: loader) + async let (newSecuritySchemes, c7) = oldSecuritySchemes.externallyDereferenced(with: loader) + async let (newCallbacks, c8) = oldCallbacks.externallyDereferenced(with: loader) + async let (newPathItems, c9) = oldPathItems.externallyDereferenced(with: loader) + + schemas = try await newSchemas + responses = try await newResponses + parameters = try await newParameters + examples = try await newExamples + requestBodies = try await newRequestBodies + headers = try await newHeaders + securitySchemes = try await newSecuritySchemes + callbacks = try await newCallbacks + pathItems = try await newPathItems + + let c1Resolved = try await c1 + let c2Resolved = try await c2 + let c3Resolved = try await c3 + let c4Resolved = try await c4 + let c5Resolved = try await c5 + let c6Resolved = try await c6 + let c7Resolved = try await c7 + let c8Resolved = try await c8 + let c9Resolved = try await c9 + + let noNewComponents = + c1Resolved.isEmpty + && c2Resolved.isEmpty + && c3Resolved.isEmpty + && c4Resolved.isEmpty + && c5Resolved.isEmpty + && c6Resolved.isEmpty + && c7Resolved.isEmpty + && c8Resolved.isEmpty + && c9Resolved.isEmpty + + if noNewComponents { return } + + try merge(c1Resolved) + try merge(c2Resolved) + try merge(c3Resolved) + try merge(c4Resolved) + try merge(c5Resolved) + try merge(c6Resolved) + try merge(c7Resolved) + try merge(c8Resolved) + try merge(c9Resolved) + + switch depth { + case .iterations(let number): + try await externallyDereference(with: loader, depth: .iterations(number - 1)) + case .full: + try await externallyDereference(with: loader, depth: .full) + } } } diff --git a/Sources/OpenAPIKit30/Content/DereferencedContent.swift b/Sources/OpenAPIKit30/Content/DereferencedContent.swift index e9ee1fef8..c7f256c1d 100644 --- a/Sources/OpenAPIKit30/Content/DereferencedContent.swift +++ b/Sources/OpenAPIKit30/Content/DereferencedContent.swift @@ -75,3 +75,30 @@ extension OpenAPI.Content: LocallyDereferenceable { return try DereferencedContent(self, resolvingIn: components, following: references) } } + +extension OpenAPI.Content: ExternallyDereferenceable { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + let oldSchema = schema + + async let (newSchema, c1) = oldSchema.externallyDereferenced(with: loader) + + var newContent = self + var newComponents = try await c1 + + newContent.schema = try await newSchema + + if let oldExamples = examples { + let (newExamples, c2) = try await oldExamples.externallyDereferenced(with: loader) + newContent.examples = newExamples + try newComponents.merge(c2) + } + + if let oldEncoding = encoding { + let (newEncoding, c3) = try await oldEncoding.externallyDereferenced(with: loader) + newContent.encoding = newEncoding + try newComponents.merge(c3) + } + + return (newContent, newComponents) + } +} diff --git a/Sources/OpenAPIKit30/Content/DereferencedContentEncoding.swift b/Sources/OpenAPIKit30/Content/DereferencedContentEncoding.swift index fdd0b1bbc..605ae7426 100644 --- a/Sources/OpenAPIKit30/Content/DereferencedContentEncoding.swift +++ b/Sources/OpenAPIKit30/Content/DereferencedContentEncoding.swift @@ -56,3 +56,27 @@ extension OpenAPI.Content.Encoding: LocallyDereferenceable { return try DereferencedContentEncoding(self, resolvingIn: components, following: references) } } + +extension OpenAPI.Content.Encoding: ExternallyDereferenceable { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + let newHeaders: OpenAPI.Header.Map? + let newComponents: OpenAPI.Components + + if let oldHeaders = headers { + (newHeaders, newComponents) = try await oldHeaders.externallyDereferenced(with: loader) + } else { + newHeaders = nil + newComponents = .init() + } + + let newEncoding = OpenAPI.Content.Encoding( + contentType: contentType, + headers: newHeaders, + style: style, + explode: explode, + allowReserved: allowReserved + ) + + return (newEncoding, newComponents) + } +} diff --git a/Sources/OpenAPIKit30/Document/Document.swift b/Sources/OpenAPIKit30/Document/Document.swift index 4f4baffd5..29f7b0f2f 100644 --- a/Sources/OpenAPIKit30/Document/Document.swift +++ b/Sources/OpenAPIKit30/Document/Document.swift @@ -308,6 +308,17 @@ extension OpenAPI.Document { } } +public enum ExternalDereferenceDepth { + case iterations(Int) + case full +} + +extension ExternalDereferenceDepth: ExpressibleByIntegerLiteral { + public init(integerLiteral value: Int) { + self = .iterations(value) + } +} + extension OpenAPI.Document { /// Create a locally-dereferenced OpenAPI /// Document. @@ -334,6 +345,22 @@ extension OpenAPI.Document { public func locallyDereferenced() throws -> DereferencedDocument { return try DereferencedDocument(self) } + + public mutating func externallyDereference(with loader: Loader.Type, depth: ExternalDereferenceDepth = .iterations(1)) async throws { + if case let .iterations(number) = depth, + number <= 0 { + return + } + + let oldPaths = paths + + async let (newPaths, c1) = oldPaths.externallyDereferenced(with: loader) + + paths = try await newPaths + try await components.merge(c1) + + try await components.externallyDereference(with: loader, depth: depth) + } } extension OpenAPI { diff --git a/Sources/OpenAPIKit30/Either/Either+ExternallyDereferenceable.swift b/Sources/OpenAPIKit30/Either/Either+ExternallyDereferenceable.swift new file mode 100644 index 000000000..8c202bb5b --- /dev/null +++ b/Sources/OpenAPIKit30/Either/Either+ExternallyDereferenceable.swift @@ -0,0 +1,23 @@ +// +// Either+ExternallyDereferenceable.swift +// +// +// Created by Mathew Polzin on 2/28/21. +// + +import OpenAPIKitCore + +// MARK: - ExternallyDereferenceable +extension Either: ExternallyDereferenceable where A: ExternallyDereferenceable, B: ExternallyDereferenceable { + + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + switch self { + case .a(let a): + let (newA, components) = try await a.externallyDereferenced(with: loader) + return (.a(newA), components) + case .b(let b): + let (newB, components) = try await b.externallyDereferenced(with: loader) + return (.b(newB), components) + } + } +} diff --git a/Sources/OpenAPIKit30/Example.swift b/Sources/OpenAPIKit30/Example.swift index ad0786843..2eec08ce2 100644 --- a/Sources/OpenAPIKit30/Example.swift +++ b/Sources/OpenAPIKit30/Example.swift @@ -25,7 +25,7 @@ extension OpenAPI { /// These should be of the form: /// `[ "x-extensionKey": ]` /// where the values are anything codable. - public let vendorExtensions: [String: AnyCodable] + public var vendorExtensions: [String: AnyCodable] public init( summary: String? = nil, @@ -184,4 +184,10 @@ extension OpenAPI.Example: LocallyDereferenceable { } } +extension OpenAPI.Example: ExternallyDereferenceable { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + return (self, .init()) + } +} + extension OpenAPI.Example: Validatable {} diff --git a/Sources/OpenAPIKit30/ExternalLoader.swift b/Sources/OpenAPIKit30/ExternalLoader.swift new file mode 100644 index 000000000..257264995 --- /dev/null +++ b/Sources/OpenAPIKit30/ExternalLoader.swift @@ -0,0 +1,34 @@ +// +// ExternalLoader.swift +// +// +// Created by Mathew Polzin on 7/30/2023. +// + +import OpenAPIKitCore +import Foundation + +/// An `ExternalLoader` enables `OpenAPIKit` to load external references +/// without knowing the details of what decoder is being used or how new internal +/// references should be named. +public protocol ExternalLoader { + /// Load the given URL and decode it as Type `T`. All Types `T` are `Decodable`, so + /// the only real responsibility of a `load` function is to locate and load the given + /// `URL` and pass its `Data` or `String` (depending on the decoder) to an appropriate + /// `Decoder` for the given file type. + static func load(_: URL) async throws -> T where T: Decodable + + /// Determine the next Component Key (where to store something in the + /// Components Object) for a new object of the given type that was loaded + /// at the given external URL. + /// + /// - Important: Ideally, this function returns distinct keys for all different objects + /// but the same key for all equal objects. In practice, this probably means that any + /// time the same type and URL pair are passed in the same `ComponentKey` should be + /// returned. + static func componentKey(type: T.Type, at url: URL) throws -> OpenAPI.ComponentKey +} + +public protocol ExternallyDereferenceable { + func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) +} diff --git a/Sources/OpenAPIKit30/Header/DereferencedHeader.swift b/Sources/OpenAPIKit30/Header/DereferencedHeader.swift index 18f453a6c..ec9881c71 100644 --- a/Sources/OpenAPIKit30/Header/DereferencedHeader.swift +++ b/Sources/OpenAPIKit30/Header/DereferencedHeader.swift @@ -82,3 +82,36 @@ extension OpenAPI.Header: LocallyDereferenceable { return try DereferencedHeader(self, resolvingIn: components, following: references, dereferencedFromComponentNamed: name) } } + +extension OpenAPI.Header: ExternallyDereferenceable { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + + // if not for a Swift bug, this whole next bit would just be the + // next line: +// let (newSchemaOrContent, components) = try await schemaOrContent.externallyDereferenced(with: loader) + + let newSchemaOrContent: Either + let newComponents: OpenAPI.Components + + switch schemaOrContent { + case .a(let schemaContext): + let (context, components) = try await schemaContext.externallyDereferenced(with: loader) + newSchemaOrContent = .a(context) + newComponents = components + case .b(let contentMap): + let (map, components) = try await contentMap.externallyDereferenced(with: loader) + newSchemaOrContent = .b(map) + newComponents = components + } + + let newHeader = OpenAPI.Header( + schemaOrContent: newSchemaOrContent, + description: description, + required: required, + deprecated: deprecated, + vendorExtensions: vendorExtensions + ) + + return (newHeader, newComponents) + } +} diff --git a/Sources/OpenAPIKit30/JSONReference.swift b/Sources/OpenAPIKit30/JSONReference.swift index a544ffd68..e6b425224 100644 --- a/Sources/OpenAPIKit30/JSONReference.swift +++ b/Sources/OpenAPIKit30/JSONReference.swift @@ -373,4 +373,20 @@ extension JSONReference: LocallyDereferenceable where ReferenceType: LocallyDere } } +// MARK: - ExternallyDereferenceable +extension JSONReference: ExternallyDereferenceable where ReferenceType: ExternallyDereferenceable & Decodable & Equatable { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + switch self { + case .internal(let ref): + return (.internal(ref), .init()) + case .external(let url): + let componentKey = try loader.componentKey(type: ReferenceType.self, at: url) + let component: ReferenceType = try await loader.load(url) + var components = OpenAPI.Components() + components[keyPath: ReferenceType.openAPIComponentsKeyPath][componentKey] = component + return (try components.reference(named: componentKey.rawValue, ofType: ReferenceType.self), components) + } + } +} + extension JSONReference: Validatable where ReferenceType: Validatable {} diff --git a/Sources/OpenAPIKit30/Link.swift b/Sources/OpenAPIKit30/Link.swift index b6585d98a..c39d6c219 100644 --- a/Sources/OpenAPIKit30/Link.swift +++ b/Sources/OpenAPIKit30/Link.swift @@ -20,18 +20,18 @@ extension OpenAPI { /// 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 + public var operation: Either /// 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> + public var parameters: OrderedDictionary> /// A literal value or expression to use as a request body when calling the target operation. - public let requestBody: Either? + public var requestBody: Either? public var description: String? - public let server: Server? + public var server: Server? /// Dictionary of vendor extensions. /// @@ -278,4 +278,15 @@ extension OpenAPI.Link: LocallyDereferenceable { } } +extension OpenAPI.Link: ExternallyDereferenceable { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + let (newServer, newComponents) = try await server.externallyDereferenced(with: loader) + + var newLink = self + newLink.server = newServer + + return (newLink, newComponents) + } +} + extension OpenAPI.Link: Validatable {} diff --git a/Sources/OpenAPIKit30/Operation/DereferencedOperation.swift b/Sources/OpenAPIKit30/Operation/DereferencedOperation.swift index 05ef9dd37..80cfb5590 100644 --- a/Sources/OpenAPIKit30/Operation/DereferencedOperation.swift +++ b/Sources/OpenAPIKit30/Operation/DereferencedOperation.swift @@ -124,3 +124,43 @@ extension OpenAPI.Operation: LocallyDereferenceable { return try DereferencedOperation(self, resolvingIn: components, following: references) } } + +extension OpenAPI.Operation: ExternallyDereferenceable { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + let oldParameters = parameters + let oldRequestBody = requestBody + let oldResponses = responses + + async let (newParameters, c1) = oldParameters.externallyDereferenced(with: loader) + async let (newRequestBody, c2) = oldRequestBody.externallyDereferenced(with: loader) + async let (newResponses, c3) = oldResponses.externallyDereferenced(with: loader) + async let (newCallbacks, c4) = callbacks.externallyDereferenced(with: loader) +// let (newServers, c6) = try await servers.externallyDereferenced(with: loader) + + var newOperation = self + var newComponents = try await c1 + + newOperation.parameters = try await newParameters + newOperation.requestBody = try await newRequestBody + try await newComponents.merge(c2) + newOperation.responses = try await newResponses + try await newComponents.merge(c3) + newOperation.callbacks = try await newCallbacks + try await newComponents.merge(c4) + + if let oldServers = servers { + let (newServers, c6) = try await oldServers.externallyDereferenced(with: loader) + newOperation.servers = newServers + try newComponents.merge(c6) + } + + // should not be necessary but current Swift compiler can't figure out conformance of ExternallyDereferenceable: + if let oldServers = servers { + let (newServers, c6) = try await oldServers.externallyDereferenced(with: loader) + newOperation.servers = newServers + try newComponents.merge(c6) + } + + return (newOperation, newComponents) + } +} diff --git a/Sources/OpenAPIKit30/Operation/Operation.swift b/Sources/OpenAPIKit30/Operation/Operation.swift index cea038bab..4ac2172e0 100644 --- a/Sources/OpenAPIKit30/Operation/Operation.swift +++ b/Sources/OpenAPIKit30/Operation/Operation.swift @@ -76,7 +76,7 @@ extension OpenAPI { /// The key is a unique identifier for the Callback Object. Each value in the /// map is a Callback Object that describes a request that may be initiated /// by the API provider and the expected responses. - public let callbacks: OpenAPI.CallbacksMap + public var callbacks: OpenAPI.CallbacksMap /// Indicates that the operation is deprecated or not. /// diff --git a/Sources/OpenAPIKit30/Parameter/DereferencedParameter.swift b/Sources/OpenAPIKit30/Parameter/DereferencedParameter.swift index f2cdf232a..e0385e1c1 100644 --- a/Sources/OpenAPIKit30/Parameter/DereferencedParameter.swift +++ b/Sources/OpenAPIKit30/Parameter/DereferencedParameter.swift @@ -82,3 +82,31 @@ extension OpenAPI.Parameter: LocallyDereferenceable { return try DereferencedParameter(self, resolvingIn: components, following: references, dereferencedFromComponentNamed: name) } } + +extension OpenAPI.Parameter: ExternallyDereferenceable { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + + // if not for a Swift bug, this whole function would just be the + // next line: +// let (newSchemaOrContent, components) = try await schemaOrContent.externallyDereferenced(with: loader) + + let newSchemaOrContent: Either + let newComponents: OpenAPI.Components + + switch schemaOrContent { + case .a(let schemaContext): + let (context, components) = try await schemaContext.externallyDereferenced(with: loader) + newSchemaOrContent = .a(context) + newComponents = components + case .b(let contentMap): + let (map, components) = try await contentMap.externallyDereferenced(with: loader) + newSchemaOrContent = .b(map) + newComponents = components + } + + var newParameter = self + newParameter.schemaOrContent = newSchemaOrContent + + return (newParameter, newComponents) + } +} diff --git a/Sources/OpenAPIKit30/Parameter/DereferencedSchemaContext.swift b/Sources/OpenAPIKit30/Parameter/DereferencedSchemaContext.swift index 299b728cd..3e7d357f4 100644 --- a/Sources/OpenAPIKit30/Parameter/DereferencedSchemaContext.swift +++ b/Sources/OpenAPIKit30/Parameter/DereferencedSchemaContext.swift @@ -68,3 +68,24 @@ extension OpenAPI.Parameter.SchemaContext: LocallyDereferenceable { return try DereferencedSchemaContext(self, resolvingIn: components, following: references) } } + +extension OpenAPI.Parameter.SchemaContext: ExternallyDereferenceable { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + let oldSchema = schema + + async let (newSchema, c1) = oldSchema.externallyDereferenced(with: loader) + + var newSchemaContext = self + var newComponents = try await c1 + + newSchemaContext.schema = try await newSchema + + if let oldExamples = examples { + let (newExamples, c2) = try await oldExamples.externallyDereferenced(with: loader) + newSchemaContext.examples = newExamples + try newComponents.merge(c2) + } + + return (newSchemaContext, newComponents) + } +} diff --git a/Sources/OpenAPIKit30/Parameter/ParameterSchemaContext.swift b/Sources/OpenAPIKit30/Parameter/ParameterSchemaContext.swift index e44df7313..cab0963e8 100644 --- a/Sources/OpenAPIKit30/Parameter/ParameterSchemaContext.swift +++ b/Sources/OpenAPIKit30/Parameter/ParameterSchemaContext.swift @@ -13,13 +13,13 @@ extension OpenAPI.Parameter { /// See [OpenAPI Parameter Object](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#parameter-object) /// and [OpenAPI Style Values](https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.3.md#style-values). public struct SchemaContext: Equatable { - public let style: Style - public let explode: Bool - public let allowReserved: Bool //defaults to false - public let schema: Either, JSONSchema> + public var style: Style + public var explode: Bool + public var allowReserved: Bool //defaults to false + public var schema: Either, JSONSchema> - public let example: AnyCodable? - public let examples: OpenAPI.Example.Map? + public var example: AnyCodable? + public var examples: OpenAPI.Example.Map? public init(_ schema: JSONSchema, style: Style, diff --git a/Sources/OpenAPIKit30/Path Item/DereferencedPathItem.swift b/Sources/OpenAPIKit30/Path Item/DereferencedPathItem.swift index 93767b025..9b7e5ac49 100644 --- a/Sources/OpenAPIKit30/Path Item/DereferencedPathItem.swift +++ b/Sources/OpenAPIKit30/Path Item/DereferencedPathItem.swift @@ -137,3 +137,62 @@ extension OpenAPI.PathItem: LocallyDereferenceable { return try DereferencedPathItem(self, resolvingIn: components, following: references, dereferencedFromComponentNamed: name) } } + +extension OpenAPI.PathItem: ExternallyDereferenceable { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + let oldParameters = parameters + let oldServers = servers + let oldGet = get + let oldPut = put + let oldPost = post + let oldDelete = delete + let oldOptions = options + let oldHead = head + let oldPatch = patch + let oldTrace = trace + + async let (newParameters, c1) = oldParameters.externallyDereferenced(with: loader) +// async let (newServers, c2) = oldServers.externallyDereferenced(with: loader) + async let (newGet, c3) = oldGet.externallyDereferenced(with: loader) + async let (newPut, c4) = oldPut.externallyDereferenced(with: loader) + async let (newPost, c5) = oldPost.externallyDereferenced(with: loader) + async let (newDelete, c6) = oldDelete.externallyDereferenced(with: loader) + async let (newOptions, c7) = oldOptions.externallyDereferenced(with: loader) + async let (newHead, c8) = oldHead.externallyDereferenced(with: loader) + async let (newPatch, c9) = oldPatch.externallyDereferenced(with: loader) + async let (newTrace, c10) = oldTrace.externallyDereferenced(with: loader) + + var pathItem = self + var newComponents = try await c1 + + // ideally we would async let all of the props above and then set them here, + // but for now since there seems to be some sort of compiler bug we will do + // newServers in an if let below + pathItem.parameters = try await newParameters + pathItem.get = try await newGet + pathItem.put = try await newPut + pathItem.post = try await newPost + pathItem.delete = try await newDelete + pathItem.options = try await newOptions + pathItem.head = try await newHead + pathItem.patch = try await newPatch + pathItem.trace = try await newTrace + + try await newComponents.merge(c3) + try await newComponents.merge(c4) + try await newComponents.merge(c5) + try await newComponents.merge(c6) + try await newComponents.merge(c7) + try await newComponents.merge(c8) + try await newComponents.merge(c9) + try await newComponents.merge(c10) + + if let oldServers { + async let (newServers, c2) = oldServers.externallyDereferenced(with: loader) + pathItem.servers = try await newServers + try await newComponents.merge(c2) + } + + return (pathItem, newComponents) + } +} diff --git a/Sources/OpenAPIKit30/Request/DereferencedRequest.swift b/Sources/OpenAPIKit30/Request/DereferencedRequest.swift index 454e4c7aa..e59647cde 100644 --- a/Sources/OpenAPIKit30/Request/DereferencedRequest.swift +++ b/Sources/OpenAPIKit30/Request/DereferencedRequest.swift @@ -61,3 +61,14 @@ extension OpenAPI.Request: LocallyDereferenceable { return try DereferencedRequest(self, resolvingIn: components, following: references, dereferencedFromComponentNamed: name) } } + +extension OpenAPI.Request: ExternallyDereferenceable { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + var newRequest = self + + let (newContent, components) = try await content.externallyDereferenced(with: loader) + + newRequest.content = newContent + return (newRequest, components) + } +} diff --git a/Sources/OpenAPIKit30/Response/DereferencedResponse.swift b/Sources/OpenAPIKit30/Response/DereferencedResponse.swift index c61ae5c7b..add208773 100644 --- a/Sources/OpenAPIKit30/Response/DereferencedResponse.swift +++ b/Sources/OpenAPIKit30/Response/DereferencedResponse.swift @@ -76,3 +76,30 @@ extension OpenAPI.Response: LocallyDereferenceable { return try DereferencedResponse(self, resolvingIn: components, following: references, dereferencedFromComponentNamed: name) } } + +extension OpenAPI.Response: ExternallyDereferenceable { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + let oldContent = content + let oldLinks = links + let oldHeaders = headers + + async let (newContent, c1) = oldContent.externallyDereferenced(with: loader) + async let (newLinks, c2) = oldLinks.externallyDereferenced(with: loader) +// async let (newHeaders, c3) = oldHeaders.externallyDereferenced(with: loader) + + var response = self + response.content = try await newContent + response.links = try await newLinks + + var components = try await c1 + try await components.merge(c2) + + if let oldHeaders { + let (newHeaders, c3) = try await oldHeaders.externallyDereferenced(with: loader) + response.headers = newHeaders + try components.merge(c3) + } + + return (response, components) + } +} diff --git a/Sources/OpenAPIKit30/Schema Object/DereferencedJSONSchema.swift b/Sources/OpenAPIKit30/Schema Object/DereferencedJSONSchema.swift index 07af9bfb1..57776fe42 100644 --- a/Sources/OpenAPIKit30/Schema Object/DereferencedJSONSchema.swift +++ b/Sources/OpenAPIKit30/Schema Object/DereferencedJSONSchema.swift @@ -408,3 +408,107 @@ extension JSONSchema: LocallyDereferenceable { return try? dereferenced(in: .noComponents) } } + +extension JSONSchema: ExternallyDereferenceable { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + let newSchema: JSONSchema + let newComponents: OpenAPI.Components + + switch value { + case .boolean(_): + newComponents = .noComponents + newSchema = self + case .number(_, _): + newComponents = .noComponents + newSchema = self + case .integer(_, _): + newComponents = .noComponents + newSchema = self + case .string(_, _): + newComponents = .noComponents + newSchema = self + case .object(let core, let object): + var components = OpenAPI.Components() + + let (newProperties, c1) = try await object.properties.externallyDereferenced(with: loader) + try components.merge(c1) + + let newAdditionalProperties: Either? + if case .b(let schema) = object.additionalProperties { + let (additionalProperties, c2) = try await schema.externallyDereferenced(with: loader) + try components.merge(c2) + newAdditionalProperties = .b(additionalProperties) + } else { + newAdditionalProperties = object.additionalProperties + } + newComponents = components + newSchema = .init( + schema: .object( + core, + .init( + properties: newProperties, + additionalProperties: newAdditionalProperties, + maxProperties: object.maxProperties, + minProperties: object._minProperties + ) + ), + vendorExtensions: vendorExtensions + ) + case .array(let core, let array): + let (newItems, components) = try await array.items.externallyDereferenced(with: loader) + newComponents = components + newSchema = .init( + schema: .array( + core, + .init( + items: newItems, + maxItems: array.maxItems, + minItems: array._minItems, + uniqueItems: array._uniqueItems + ) + ), + vendorExtensions: vendorExtensions + ) + case .all(let schema, let core): + let (newSubschemas, components) = try await schema.externallyDereferenced(with: loader) + newComponents = components + newSchema = .init( + schema: .all(of: newSubschemas, core: core), + vendorExtensions: vendorExtensions + ) + case .one(let schema, let core): + let (newSubschemas, components) = try await schema.externallyDereferenced(with: loader) + newComponents = components + newSchema = .init( + schema: .one(of: newSubschemas, core: core), + vendorExtensions: vendorExtensions + ) + case .any(let schema, let core): + let (newSubschemas, components) = try await schema.externallyDereferenced(with: loader) + newComponents = components + newSchema = .init( + schema: .any(of: newSubschemas, core: core), + vendorExtensions: vendorExtensions + ) + case .not(let schema, let core): + let (newSubschema, components) = try await schema.externallyDereferenced(with: loader) + newComponents = components + newSchema = .init( + schema: .not(newSubschema, core: core), + vendorExtensions: vendorExtensions + ) + case .reference(let reference, let core): + let (newReference, components) = try await reference.externallyDereferenced(with: loader) + newComponents = components + newSchema = .init( + schema: .reference(newReference, core), + vendorExtensions: vendorExtensions + ) + case .fragment(_): + newComponents = .noComponents + newSchema = self + } + + return (newSchema, newComponents) + } +} diff --git a/Sources/OpenAPIKit30/Schema Object/JSONSchema.swift b/Sources/OpenAPIKit30/Schema Object/JSONSchema.swift index 93dc1b10a..ff3fa8166 100644 --- a/Sources/OpenAPIKit30/Schema Object/JSONSchema.swift +++ b/Sources/OpenAPIKit30/Schema Object/JSONSchema.swift @@ -14,7 +14,7 @@ public struct JSONSchema: JSONSchemaContext, HasWarnings, VendorExtendable { public let warnings: [OpenAPI.Warning] public let value: Schema - public let vendorExtensions: [String: AnyCodable] + public var vendorExtensions: [String: AnyCodable] internal init(warnings: [OpenAPI.Warning], schema: Schema, vendorExtensions: [String: AnyCodable]) { self.warnings = warnings diff --git a/Sources/OpenAPIKit30/Security/SecurityScheme.swift b/Sources/OpenAPIKit30/Security/SecurityScheme.swift index 8b883491e..364196b67 100644 --- a/Sources/OpenAPIKit30/Security/SecurityScheme.swift +++ b/Sources/OpenAPIKit30/Security/SecurityScheme.swift @@ -251,4 +251,10 @@ extension OpenAPI.SecurityScheme: LocallyDereferenceable { } } +extension OpenAPI.SecurityScheme: ExternallyDereferenceable { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + return (self, .init()) + } +} + extension OpenAPI.SecurityScheme.SecurityType.Name: Validatable {} diff --git a/Sources/OpenAPIKit30/Server.swift b/Sources/OpenAPIKit30/Server.swift index f0073f827..5990e297b 100644 --- a/Sources/OpenAPIKit30/Server.swift +++ b/Sources/OpenAPIKit30/Server.swift @@ -245,5 +245,11 @@ extension OpenAPI.Server.Variable { } } +extension OpenAPI.Server: ExternallyDereferenceable { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + return (self, .init()) + } +} + extension OpenAPI.Server: Validatable {} extension OpenAPI.Server.Variable: Validatable {} diff --git a/Sources/OpenAPIKit30/Utility/Array+ExternallyDereferenceable.swift b/Sources/OpenAPIKit30/Utility/Array+ExternallyDereferenceable.swift new file mode 100644 index 000000000..a67002587 --- /dev/null +++ b/Sources/OpenAPIKit30/Utility/Array+ExternallyDereferenceable.swift @@ -0,0 +1,30 @@ +// +// Array+ExternallyDereferenceable.swift +// + +import OpenAPIKitCore + +extension Array where Element: ExternallyDereferenceable { + + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + try await withThrowingTaskGroup(of: (Int, (Element, OpenAPI.Components)).self) { group in + for (idx, elem) in zip(self.indices, self) { + group.addTask { + return try await (idx, elem.externallyDereferenced(with: loader)) + } + } + + var newElems = Array<(Int, Element)>() + var newComponents = OpenAPI.Components() + + for try await (idx, (elem, components)) in group { + newElems.append((idx, elem)) + try newComponents.merge(components) + } + // things may come in out of order because of concurrency + // so we reorder after completing all entries. + newElems.sort { left, right in left.0 < right.0 } + return (newElems.map { $0.1 }, newComponents) + } + } +} diff --git a/Sources/OpenAPIKit30/Utility/Dictionary+ExternallyDereferenceable.swift b/Sources/OpenAPIKit30/Utility/Dictionary+ExternallyDereferenceable.swift new file mode 100644 index 000000000..d15b123b2 --- /dev/null +++ b/Sources/OpenAPIKit30/Utility/Dictionary+ExternallyDereferenceable.swift @@ -0,0 +1,29 @@ +// +// Dictionary+ExternallyDereferenceable.swift +// OpenAPI +// + +import OpenAPIKitCore + +extension Dictionary where Value: ExternallyDereferenceable { + + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + try await withThrowingTaskGroup(of: (Key, Value, OpenAPI.Components).self) { group in + for (key, value) in self { + group.addTask { + let (newRef, components) = try await value.externallyDereferenced(with: loader) + return (key, newRef, components) + } + } + + var newDict = Self() + var newComponents = OpenAPI.Components() + + for try await (key, newRef, components) in group { + newDict[key] = newRef + try newComponents.merge(components) + } + return (newDict, newComponents) + } + } +} diff --git a/Sources/OpenAPIKit30/Utility/Optional+ExternallyDereferenceable.swift b/Sources/OpenAPIKit30/Utility/Optional+ExternallyDereferenceable.swift new file mode 100644 index 000000000..79b055b52 --- /dev/null +++ b/Sources/OpenAPIKit30/Utility/Optional+ExternallyDereferenceable.swift @@ -0,0 +1,13 @@ +// +// Optional+ExternallyDereferenceable.swift +// + +import OpenAPIKitCore + +extension Optional where Wrapped: ExternallyDereferenceable { + + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + guard let wrapped = self else { return (nil, .init()) } + return try await wrapped.externallyDereferenced(with: loader) + } +} diff --git a/Sources/OpenAPIKit30/Utility/OrderedDictionary+ExternallyDereferenceable.swift b/Sources/OpenAPIKit30/Utility/OrderedDictionary+ExternallyDereferenceable.swift new file mode 100644 index 000000000..38ab9bbc3 --- /dev/null +++ b/Sources/OpenAPIKit30/Utility/OrderedDictionary+ExternallyDereferenceable.swift @@ -0,0 +1,34 @@ +// +// OrderedDictionary+ExternallyDereferenceable.swift +// OpenAPI +// +// Created by Mathew Polzin on 08/05/2023. +// + +import OpenAPIKitCore + +extension OrderedDictionary where Value: ExternallyDereferenceable { + + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + try await withThrowingTaskGroup(of: (Key, Value, OpenAPI.Components).self) { group in + for (key, value) in self { + group.addTask { + let (newRef, components) = try await value.externallyDereferenced(with: loader) + return (key, newRef, components) + } + } + + var newDict = Self() + var newComponents = OpenAPI.Components() + + for try await (key, newRef, components) in group { + newDict[key] = newRef + try newComponents.merge(components) + } + // things may come in out of order because of concurrency + // so we reorder after completing all entries. + try newDict.applyOrder(self) + return (newDict, newComponents) + } + } +} diff --git a/Tests/OpenAPIKit30Tests/Document/ExternalDereferencingDocumentTests.swift b/Tests/OpenAPIKit30Tests/Document/ExternalDereferencingDocumentTests.swift new file mode 100644 index 000000000..862f47474 --- /dev/null +++ b/Tests/OpenAPIKit30Tests/Document/ExternalDereferencingDocumentTests.swift @@ -0,0 +1,249 @@ +// +// ExternalDereferencingDocumentTests.swift +// + +import Foundation +import Yams +import OpenAPIKit30 +import XCTest + +final class ExternalDereferencingDocumentTests: XCTestCase { + // temporarily test with an example of the new interface + func test_example() async throws { + + /// An example of implementing a loader context for loading external references + /// into an OpenAPI document. + struct ExampleLoader: ExternalLoader { + static func load(_ url: URL) async throws -> T where T : Decodable { + // load data from file, perhaps. we will just mock that up for the test: + let data = try await mockData(componentKey(type: T.self, at: url)) + + // We use the YAML decoder purely for order-stability. + let decoded = try YAMLDecoder().decode(T.self, from: data) + let finished: T + // while unnecessary, a loader may likely want to attatch some extra info + // to keep track of where a reference was loaded from. This test makes sure + // the following strategy of using vendor extensions works. + if var extendable = decoded as? VendorExtendable { + extendable.vendorExtensions["x-source-url"] = AnyCodable(url) + finished = extendable as! T + } else { + finished = decoded + } + return finished + } + + static func componentKey(type: T.Type, at url: URL) throws -> OpenAPIKit30.OpenAPI.ComponentKey { + // do anything you want here to determine what key the new component should be stored at. + // for the example, we will just transform the URL into a valid components key: + let urlString = url.pathComponents.dropFirst() + .joined(separator: "_") + .replacingOccurrences(of: ".", with: "_") + return try .forceInit(rawValue: urlString) + } + + /// Mock up some data, just for the example. + static func mockData(_ key: OpenAPIKit30.OpenAPI.ComponentKey) async throws -> Data { + return try XCTUnwrap(files[key.rawValue]) + } + + static let files: [String: Data] = [ + "params_name_json": """ + { + "name": "name", + "description": "a lonely parameter", + "in": "path", + "required": true, + "schema": { + "$ref": "file://./schemas/string_param.json#" + } + } + """, + "schemas_string_param_json": """ + { + "oneOf": [ + { "type": "string" }, + { "$ref": "file://./schemas/basic_object.json" } + ] + } + """, + "schemas_basic_object_json": """ + { + "type": "object" + } + """, + "paths_webhook_json": """ + { + "summary": "just a webhook", + "get": { + "requestBody": { + "$ref": "file://./requests/webhook.json" + }, + "responses": { + "200": { + "$ref": "file://./responses/webhook.json" + } + } + } + } + """, + "requests_webhook_json": """ + { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "body": { + "$ref": "file://./schemas/string_param.json" + } + } + }, + "examples": { + "good": { + "$ref": "file://./examples/good.json" + } + }, + "encoding": { + "enc1": { + "headers": { + "head1": { + "$ref": "file://./headers/webhook.json" + } + } + } + } + } + } + } + """, + "responses_webhook_json": """ + { + "description": "webhook response", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "body": { + "type": "string" + }, + "length": { + "type": "integer", + "minimum": 0 + } + } + } + } + }, + "headers": { + "X-Hello": { + "$ref": "file://./headers/webhook.json" + } + } + } + """, + "headers_webhook_json": """ + { + "schema": { + "$ref": "file://./schemas/string_param.json" + } + } + """, + "examples_good_json": """ + { + "value": "{\\"body\\": \\"request me\\"}" + } + """, + "callbacks_one_json": """ + { + "https://callback.site.com/callback": { + "summary": "just a callback" + } + } + """, + "paths_callback_json": """ + { + "summary": "just a callback", + "get": { + "responses": { + "200": { + "description": "callback response", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + }, + "links": { + "link1": { + "$ref": "file://./links/first.json" + } + } + } + } + } + } + """, + "links_first_json": """ + { + "operationId": "helloOp" + } + """ + ].mapValues { $0.data(using: .utf8)! } + } + + let document = OpenAPI.Document( + info: .init(title: "test document", version: "1.0.0"), + servers: [], + paths: [ + "/hello/{name}": .init( + parameters: [ + .reference(.external(URL(string: "file://./params/name.json")!)) + ], + get: .init( + operationId: "helloOp", + responses: [:], + callbacks: [ + "callback1": .reference(.external(URL(string: "file://./callbacks/one.json")!)) + ] + ) + ), + "/goodbye/{name}": .init( + parameters: [ + .reference(.external(URL(string: "file://./params/name.json")!)) + ] + ), + "/webhook": .reference(.external(URL(string: "file://./paths/webhook.json")!)) + ], + components: .init( + schemas: [ + "name_param": .reference(.external(URL(string: "file://./schemas/string_param.json")!)) + ], + // just to show, no parameters defined within document components : + parameters: [:] + ) + ) + + let encoder = JSONEncoder() + encoder.outputFormatting = .prettyPrinted + + var docCopy1 = document + try await docCopy1.externallyDereference(with: ExampleLoader.self) + try await docCopy1.externallyDereference(with: ExampleLoader.self) + try await docCopy1.externallyDereference(with: ExampleLoader.self) + docCopy1.components.sort() + + var docCopy2 = document + try await docCopy2.externallyDereference(with: ExampleLoader.self, depth: 3) + docCopy2.components.sort() + + var docCopy3 = document + try await docCopy3.externallyDereference(with: ExampleLoader.self, depth: .full) + docCopy3.components.sort() + + XCTAssertEqual(docCopy1, docCopy2) + XCTAssertEqual(docCopy2, docCopy3) + } +} diff --git a/Tests/OpenAPIKit30Tests/JSONReferenceTests.swift b/Tests/OpenAPIKit30Tests/JSONReferenceTests.swift index 60fee797e..5aca879c0 100644 --- a/Tests/OpenAPIKit30Tests/JSONReferenceTests.swift +++ b/Tests/OpenAPIKit30Tests/JSONReferenceTests.swift @@ -339,4 +339,47 @@ extension JSONReferenceTests { struct ReferenceWrapper: Codable, Equatable { let reference: JSONReference } + + struct SchemaLoader: ExternalLoader { + static func load(_ url: URL) -> T where T: Decodable { + return JSONSchema.string as! T + } + + static func componentKey(type: T.Type, at url: URL) throws -> OpenAPI.ComponentKey { + return try .forceInit(rawValue: url.absoluteString + .replacingOccurrences(of: "/", with: "_") + .replacingOccurrences(of: "#", with: "_") + .replacingOccurrences(of: ".", with: "_")) + } + } +} + +// MARK: - External Dereferencing +extension JSONReferenceTests { + func test_externalDerefNoFragment() async throws { + let reference: JSONReference = .external(.init(string: "./schema.json")!) + + let (newReference, components) = try await reference.externallyDereferenced(with: SchemaLoader.self) + + XCTAssertEqual(newReference, .component(named: "__schema_json")) + XCTAssertEqual(components, .init(schemas: ["__schema_json": .string])) + } + + func test_externalDerefFragment() async throws { + let reference: JSONReference = .external(.init(string: "./schema.json#/test")!) + + let (newReference, components) = try await reference.externallyDereferenced(with: SchemaLoader.self) + + XCTAssertEqual(newReference, .component(named: "__schema_json__test")) + XCTAssertEqual(components, .init(schemas: ["__schema_json__test": .string])) + } + + func test_externalDerefExternalComponents() async throws { + let reference: JSONReference = .external(.init(string: "./schema.json#/components/schemas/test")!) + + let (newReference, components) = try await reference.externallyDereferenced(with: SchemaLoader.self) + + XCTAssertEqual(newReference, .component(named: "__schema_json__components_schemas_test")) + XCTAssertEqual(components, .init(schemas: ["__schema_json__components_schemas_test": .string])) + } } diff --git a/Tests/OpenAPIKit30Tests/VendorExtendableTests.swift b/Tests/OpenAPIKit30Tests/VendorExtendableTests.swift index 44ea33cdf..944655df7 100644 --- a/Tests/OpenAPIKit30Tests/VendorExtendableTests.swift +++ b/Tests/OpenAPIKit30Tests/VendorExtendableTests.swift @@ -145,7 +145,7 @@ private struct TestStruct: Codable, CodableVendorExtendable { } } - public let vendorExtensions: Self.VendorExtensions + public var vendorExtensions: Self.VendorExtensions init(vendorExtensions: Self.VendorExtensions) { self.vendorExtensions = vendorExtensions diff --git a/Tests/OpenAPIKitTests/Document/ExternalDereferencingDocumentTests.swift b/Tests/OpenAPIKitTests/Document/ExternalDereferencingDocumentTests.swift index 914e575cc..35bf2429d 100644 --- a/Tests/OpenAPIKitTests/Document/ExternalDereferencingDocumentTests.swift +++ b/Tests/OpenAPIKitTests/Document/ExternalDereferencingDocumentTests.swift @@ -233,17 +233,17 @@ final class ExternalDereferencingDocumentTests: XCTestCase { encoder.outputFormatting = .prettyPrinted var docCopy1 = document - try await docCopy1.externallyDereference(in: ExampleLoader.self) - try await docCopy1.externallyDereference(in: ExampleLoader.self) - try await docCopy1.externallyDereference(in: ExampleLoader.self) + try await docCopy1.externallyDereference(with: ExampleLoader.self) + try await docCopy1.externallyDereference(with: ExampleLoader.self) + try await docCopy1.externallyDereference(with: ExampleLoader.self) docCopy1.components.sort() var docCopy2 = document - try await docCopy2.externallyDereference(in: ExampleLoader.self, depth: 3) + try await docCopy2.externallyDereference(with: ExampleLoader.self, depth: 3) docCopy2.components.sort() var docCopy3 = document - try await docCopy3.externallyDereference(in: ExampleLoader.self, depth: .full) + try await docCopy3.externallyDereference(with: ExampleLoader.self, depth: .full) docCopy3.components.sort() XCTAssertEqual(docCopy1, docCopy2)