diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 0d392008d..01689c62d 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -5,7 +5,7 @@ on: [pull_request] jobs: codecov: container: - image: swift:5.8 + image: swift:5.10 runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/README.md b/README.md index 5f0eccb65..db2363de1 100644 --- a/README.md +++ b/README.md @@ -330,23 +330,25 @@ OpenAPIKit leaves it to you to decide how to load external files and where to st ```swift struct ExampleLoader: ExternalLoader { - static func load(_ url: URL) async throws -> T where T : Decodable { + typealias Message = Void + + static func load(_ url: URL) async throws -> (T, [Message]) 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 mostly for order-stability in this case but it is - // also nice that it will handle both YAML and JSON data. + // 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. + // 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 + return (finished, []) } static func componentKey(type: T.Type, at url: URL) throws -> OpenAPIKit.OpenAPI.ComponentKey { @@ -362,6 +364,8 @@ struct ExampleLoader: ExternalLoader { Once you have an `ExternalLoader`, you can call an `OpenAPI.Document`'s `externallyDereference()` method to externally dereference it. You get to choose whether to only load references to a certain depth or to fully resolve references until you run out of them; any given referenced document may itself contain references and these references may point back to things loaded into the Document previously so dereferencing is done recursively up to a given depth (or until fully dereferenced if you use the `.full` depth). +If you have some information that you want to pass back to yourself from the `load()` function, you can specify any type you want as the `Message` type and return any number of messages from each `load()` function execution. These messages could be warnings, additional information about the files that each newly loaded Component came from, etc. If you want to tie some information about file loading to new Components in your messages, you can use the `componentKey()` function to get the key the new Component will be found under once external dereferencing is complete. + #### Internal References In addition to looking something up in the `Components` object, you can entirely derefererence many OpenAPIKit types. A dereferenced type has had all of its references looked up (and all of its properties' references, all the way down). diff --git a/Sources/OpenAPIKit/Components Object/Components.swift b/Sources/OpenAPIKit/Components Object/Components.swift index e90d318b1..cc1996b68 100644 --- a/Sources/OpenAPIKit/Components Object/Components.swift +++ b/Sources/OpenAPIKit/Components Object/Components.swift @@ -320,12 +320,16 @@ extension OpenAPI.Components { } extension OpenAPI.Components { - internal mutating func externallyDereference(with loader: Loader.Type, depth: ExternalDereferenceDepth = .iterations(1)) async throws { + internal mutating func externallyDereference(with loader: Loader.Type, depth: ExternalDereferenceDepth = .iterations(1), context: [Loader.Message] = []) async throws -> [Loader.Message] { if case let .iterations(number) = depth, number <= 0 { - return + return context } + // NOTE: The links and callbacks related code commented out below pushes Swift 5.8 and 5.9 + // over the edge and you get exit code 137 crashes in CI. + // Swift 5.10 handles it fine. + let oldSchemas = schemas let oldResponses = responses let oldParameters = parameters @@ -333,18 +337,20 @@ extension OpenAPI.Components { let oldRequestBodies = requestBodies let oldHeaders = headers let oldSecuritySchemes = securitySchemes + let oldLinks = links 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) + async let (newSchemas, c1, m1) = oldSchemas.externallyDereferenced(with: loader) + async let (newResponses, c2, m2) = oldResponses.externallyDereferenced(with: loader) + async let (newParameters, c3, m3) = oldParameters.externallyDereferenced(with: loader) + async let (newExamples, c4, m4) = oldExamples.externallyDereferenced(with: loader) + async let (newRequestBodies, c5, m5) = oldRequestBodies.externallyDereferenced(with: loader) + async let (newHeaders, c6, m6) = oldHeaders.externallyDereferenced(with: loader) + async let (newSecuritySchemes, c7, m7) = oldSecuritySchemes.externallyDereferenced(with: loader) +// async let (newLinks, c8, m8) = oldLinks.externallyDereferenced(with: loader) +// async let (newCallbacks, c9, m9) = oldCallbacks.externallyDereferenced(with: loader) + async let (newPathItems, c10, m10) = oldPathItems.externallyDereferenced(with: loader) schemas = try await newSchemas responses = try await newResponses @@ -353,7 +359,8 @@ extension OpenAPI.Components { requestBodies = try await newRequestBodies headers = try await newHeaders securitySchemes = try await newSecuritySchemes - callbacks = try await newCallbacks +// links = try await newLinks +// callbacks = try await newCallbacks pathItems = try await newPathItems let c1Resolved = try await c1 @@ -363,8 +370,18 @@ extension OpenAPI.Components { 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 c8Resolved = try await c8 +// let c9Resolved = try await c9 + let c10Resolved = try await c10 + + // For Swift 5.10+ we can delete the following links and callbacks code and uncomment the + // preferred code above. + let (newLinks, c8, m8) = try await oldLinks.externallyDereferenced(with: loader) + links = newLinks + let c8Resolved = c8 + let (newCallbacks, c9, m9) = try await oldCallbacks.externallyDereferenced(with: loader) + callbacks = newCallbacks + let c9Resolved = c9 let noNewComponents = c1Resolved.isEmpty @@ -376,8 +393,11 @@ extension OpenAPI.Components { && c7Resolved.isEmpty && c8Resolved.isEmpty && c9Resolved.isEmpty + && c10Resolved.isEmpty + + let newMessages = try await context + m1 + m2 + m3 + m4 + m5 + m6 + m7 + m8 + m9 + m10 - if noNewComponents { return } + if noNewComponents { return newMessages } try merge(c1Resolved) try merge(c2Resolved) @@ -388,12 +408,13 @@ extension OpenAPI.Components { try merge(c7Resolved) try merge(c8Resolved) try merge(c9Resolved) - + try merge(c10Resolved) + switch depth { case .iterations(let number): - try await externallyDereference(with: loader, depth: .iterations(number - 1)) + return try await externallyDereference(with: loader, depth: .iterations(number - 1), context: newMessages) case .full: - try await externallyDereference(with: loader, depth: .full) + return try await externallyDereference(with: loader, depth: .full, context: newMessages) } } } diff --git a/Sources/OpenAPIKit/Content/DereferencedContent.swift b/Sources/OpenAPIKit/Content/DereferencedContent.swift index afdcd582e..5350b55ee 100644 --- a/Sources/OpenAPIKit/Content/DereferencedContent.swift +++ b/Sources/OpenAPIKit/Content/DereferencedContent.swift @@ -77,28 +77,31 @@ extension OpenAPI.Content: LocallyDereferenceable { } extension OpenAPI.Content: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { let oldSchema = schema - async let (newSchema, c1) = oldSchema.externallyDereferenced(with: loader) + async let (newSchema, c1, m1) = oldSchema.externallyDereferenced(with: loader) var newContent = self var newComponents = try await c1 + var newMessages = try await m1 newContent.schema = try await newSchema if let oldExamples = examples { - let (newExamples, c2) = try await oldExamples.externallyDereferenced(with: loader) + let (newExamples, c2, m2) = try await oldExamples.externallyDereferenced(with: loader) newContent.examples = newExamples try newComponents.merge(c2) + newMessages += m2 } if let oldEncoding = encoding { - let (newEncoding, c3) = try await oldEncoding.externallyDereferenced(with: loader) + let (newEncoding, c3, m3) = try await oldEncoding.externallyDereferenced(with: loader) newContent.encoding = newEncoding try newComponents.merge(c3) + newMessages += m3 } - return (newContent, newComponents) + return (newContent, newComponents, newMessages) } } diff --git a/Sources/OpenAPIKit/Content/DereferencedContentEncoding.swift b/Sources/OpenAPIKit/Content/DereferencedContentEncoding.swift index 605ae7426..aaa9a1fd5 100644 --- a/Sources/OpenAPIKit/Content/DereferencedContentEncoding.swift +++ b/Sources/OpenAPIKit/Content/DereferencedContentEncoding.swift @@ -58,15 +58,17 @@ extension OpenAPI.Content.Encoding: LocallyDereferenceable { } extension OpenAPI.Content.Encoding: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { let newHeaders: OpenAPI.Header.Map? let newComponents: OpenAPI.Components + let newMessages: [Loader.Message] if let oldHeaders = headers { - (newHeaders, newComponents) = try await oldHeaders.externallyDereferenced(with: loader) + (newHeaders, newComponents, newMessages) = try await oldHeaders.externallyDereferenced(with: loader) } else { newHeaders = nil newComponents = .init() + newMessages = [] } let newEncoding = OpenAPI.Content.Encoding( @@ -77,6 +79,6 @@ extension OpenAPI.Content.Encoding: ExternallyDereferenceable { allowReserved: allowReserved ) - return (newEncoding, newComponents) + return (newEncoding, newComponents, newMessages) } } diff --git a/Sources/OpenAPIKit/Document/Document.swift b/Sources/OpenAPIKit/Document/Document.swift index 9864d8280..33546f086 100644 --- a/Sources/OpenAPIKit/Document/Document.swift +++ b/Sources/OpenAPIKit/Document/Document.swift @@ -362,24 +362,44 @@ extension OpenAPI.Document { return try DereferencedDocument(self) } - public mutating func externallyDereference(with loader: Loader.Type, depth: ExternalDereferenceDepth = .iterations(1)) async throws { + /// Load all remote references into the document. A remote reference is one + /// that points to another file rather than a location within the + /// same file. + /// + /// This function will load remote references into the Components object + /// and replace the remote reference with a local reference to that component. + /// No local references are modified or resolved by this function. You can + /// call `locallyDereferenced()` on the externally dereferenced document if + /// you want to also remove local references by inlining all of them. + /// + /// Externally dereferencing a document requires that you provide both a + /// function that produces a `OpenAPI.ComponentKey` for any given remote + /// file URI and also a function that loads and decodes the data found in + /// that remote file. The latter is less work than it may sound like because + /// the function is told what Decodable thing it wants, so you really just + /// need to decide what decoder to use and provide the file data to that + /// decoder. See `ExternalLoader` documentation for details. + @discardableResult + public mutating func externallyDereference(with loader: Loader.Type, depth: ExternalDereferenceDepth = .iterations(1), context: [Loader.Message] = []) async throws -> [Loader.Message] { if case let .iterations(number) = depth, number <= 0 { - return + return context } let oldPaths = paths let oldWebhooks = webhooks - async let (newPaths, c1) = oldPaths.externallyDereferenced(with: loader) - async let (newWebhooks, c2) = oldWebhooks.externallyDereferenced(with: loader) + async let (newPaths, c1, m1) = oldPaths.externallyDereferenced(with: loader) + async let (newWebhooks, c2, m2) = 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(with: loader, depth: depth) + let m3 = try await components.externallyDereference(with: loader, depth: depth, context: context) + + return try await context + m1 + m2 + m3 } } diff --git a/Sources/OpenAPIKit/Either/Either+ExternallyDereferenceable.swift b/Sources/OpenAPIKit/Either/Either+ExternallyDereferenceable.swift index 8c202bb5b..62c919355 100644 --- a/Sources/OpenAPIKit/Either/Either+ExternallyDereferenceable.swift +++ b/Sources/OpenAPIKit/Either/Either+ExternallyDereferenceable.swift @@ -10,14 +10,14 @@ import OpenAPIKitCore // MARK: - ExternallyDereferenceable extension Either: ExternallyDereferenceable where A: ExternallyDereferenceable, B: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { switch self { case .a(let a): - let (newA, components) = try await a.externallyDereferenced(with: loader) - return (.a(newA), components) + let (newA, components, messages) = try await a.externallyDereferenced(with: loader) + return (.a(newA), components, messages) case .b(let b): - let (newB, components) = try await b.externallyDereferenced(with: loader) - return (.b(newB), components) + let (newB, components, messages) = try await b.externallyDereferenced(with: loader) + return (.b(newB), components, messages) } } } diff --git a/Sources/OpenAPIKit/Example.swift b/Sources/OpenAPIKit/Example.swift index 74c9f7226..e9c904f7c 100644 --- a/Sources/OpenAPIKit/Example.swift +++ b/Sources/OpenAPIKit/Example.swift @@ -209,8 +209,8 @@ extension OpenAPI.Example: LocallyDereferenceable { } extension OpenAPI.Example: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { - return (self, .init()) + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { + return (self, .init(), []) } } diff --git a/Sources/OpenAPIKit/ExternalLoader.swift b/Sources/OpenAPIKit/ExternalLoader.swift index 257264995..28cfa8d39 100644 --- a/Sources/OpenAPIKit/ExternalLoader.swift +++ b/Sources/OpenAPIKit/ExternalLoader.swift @@ -12,11 +12,17 @@ import Foundation /// without knowing the details of what decoder is being used or how new internal /// references should be named. public protocol ExternalLoader { + /// This can be anything that an implementor of this protocol wants to pass back from + /// the `load()` function and have available after all external loading has been done. + /// + /// A trivial type if no Messages are needed would be Void. + associatedtype Message + /// 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 + static func load(_: URL) async throws -> (T, [Message]) 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 @@ -30,5 +36,5 @@ public protocol ExternalLoader { } public protocol ExternallyDereferenceable { - func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) + func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) } diff --git a/Sources/OpenAPIKit/Header/DereferencedHeader.swift b/Sources/OpenAPIKit/Header/DereferencedHeader.swift index ec9881c71..40509234a 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: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { // if not for a Swift bug, this whole next bit would just be the // next line: @@ -92,16 +92,19 @@ extension OpenAPI.Header: ExternallyDereferenceable { let newSchemaOrContent: Either let newComponents: OpenAPI.Components + let newMessages: [Loader.Message] switch schemaOrContent { case .a(let schemaContext): - let (context, components) = try await schemaContext.externallyDereferenced(with: loader) + let (context, components, messages) = try await schemaContext.externallyDereferenced(with: loader) newSchemaOrContent = .a(context) newComponents = components + newMessages = messages case .b(let contentMap): - let (map, components) = try await contentMap.externallyDereferenced(with: loader) + let (map, components, messages) = try await contentMap.externallyDereferenced(with: loader) newSchemaOrContent = .b(map) newComponents = components + newMessages = messages } let newHeader = OpenAPI.Header( @@ -112,6 +115,6 @@ extension OpenAPI.Header: ExternallyDereferenceable { vendorExtensions: vendorExtensions ) - return (newHeader, newComponents) + return (newHeader, newComponents, newMessages) } } diff --git a/Sources/OpenAPIKit/JSONReference.swift b/Sources/OpenAPIKit/JSONReference.swift index 7d59430f5..1509f029f 100644 --- a/Sources/OpenAPIKit/JSONReference.swift +++ b/Sources/OpenAPIKit/JSONReference.swift @@ -538,16 +538,16 @@ extension JSONReference: LocallyDereferenceable where ReferenceType: LocallyDere } extension JSONReference: ExternallyDereferenceable where ReferenceType: ExternallyDereferenceable & Decodable & Equatable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { switch self { case .internal(let ref): - return (.internal(ref), .init()) + 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) + let (component, messages): (ReferenceType, [Loader.Message]) = 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).jsonReference, components) + return (try components.reference(named: componentKey.rawValue, ofType: ReferenceType.self).jsonReference, components, messages) } } } @@ -580,9 +580,9 @@ extension OpenAPI.Reference: LocallyDereferenceable where ReferenceType: Locally } extension OpenAPI.Reference: ExternallyDereferenceable where ReferenceType: ExternallyDereferenceable & Decodable & Equatable { - 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) + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { + let (newRef, components, messages) = try await jsonReference.externallyDereferenced(with: loader) + return (.init(newRef), components, messages) } } diff --git a/Sources/OpenAPIKit/Link.swift b/Sources/OpenAPIKit/Link.swift index dde15299e..39a1da918 100644 --- a/Sources/OpenAPIKit/Link.swift +++ b/Sources/OpenAPIKit/Link.swift @@ -290,13 +290,13 @@ 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) + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { + let (newServer, newComponents, newMessages) = try await server.externallyDereferenced(with: loader) var newLink = self newLink.server = newServer - return (newLink, newComponents) + return (newLink, newComponents, newMessages) } } diff --git a/Sources/OpenAPIKit/Operation/DereferencedOperation.swift b/Sources/OpenAPIKit/Operation/DereferencedOperation.swift index f4139fcd5..f2019842f 100644 --- a/Sources/OpenAPIKit/Operation/DereferencedOperation.swift +++ b/Sources/OpenAPIKit/Operation/DereferencedOperation.swift @@ -126,41 +126,40 @@ extension OpenAPI.Operation: LocallyDereferenceable { } extension OpenAPI.Operation: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { 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) + async let (newParameters, c1, m1) = oldParameters.externallyDereferenced(with: loader) + async let (newRequestBody, c2, m2) = oldRequestBody.externallyDereferenced(with: loader) + async let (newResponses, c3, m3) = oldResponses.externallyDereferenced(with: loader) + async let (newCallbacks, c4, m4) = callbacks.externallyDereferenced(with: loader) +// let (newServers, c5, m5) = try await servers.externallyDereferenced(with: loader) var newOperation = self var newComponents = try await c1 + var newMessages = try await m1 newOperation.parameters = try await newParameters newOperation.requestBody = try await newRequestBody try await newComponents.merge(c2) + try await newMessages += m2 newOperation.responses = try await newResponses try await newComponents.merge(c3) + try await newMessages += m3 newOperation.callbacks = try await newCallbacks try await newComponents.merge(c4) + try await newMessages += m4 - 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) + let (newServers, c5, m5) = try await oldServers.externallyDereferenced(with: loader) newOperation.servers = newServers - try newComponents.merge(c6) + try newComponents.merge(c5) + newMessages += m5 } - return (newOperation, newComponents) + return (newOperation, newComponents, newMessages) } } diff --git a/Sources/OpenAPIKit/Parameter/DereferencedParameter.swift b/Sources/OpenAPIKit/Parameter/DereferencedParameter.swift index e0385e1c1..4b5002ca3 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: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { // if not for a Swift bug, this whole function would just be the // next line: @@ -92,21 +92,24 @@ extension OpenAPI.Parameter: ExternallyDereferenceable { let newSchemaOrContent: Either let newComponents: OpenAPI.Components + let newMessages: [Loader.Message] switch schemaOrContent { case .a(let schemaContext): - let (context, components) = try await schemaContext.externallyDereferenced(with: loader) + let (context, components, messages) = try await schemaContext.externallyDereferenced(with: loader) newSchemaOrContent = .a(context) newComponents = components + newMessages = messages case .b(let contentMap): - let (map, components) = try await contentMap.externallyDereferenced(with: loader) + let (map, components, messages) = try await contentMap.externallyDereferenced(with: loader) newSchemaOrContent = .b(map) newComponents = components + newMessages = messages } var newParameter = self newParameter.schemaOrContent = newSchemaOrContent - return (newParameter, newComponents) + return (newParameter, newComponents, newMessages) } } diff --git a/Sources/OpenAPIKit/Parameter/DereferencedSchemaContext.swift b/Sources/OpenAPIKit/Parameter/DereferencedSchemaContext.swift index 68f9bb772..a101c1653 100644 --- a/Sources/OpenAPIKit/Parameter/DereferencedSchemaContext.swift +++ b/Sources/OpenAPIKit/Parameter/DereferencedSchemaContext.swift @@ -71,22 +71,24 @@ extension OpenAPI.Parameter.SchemaContext: LocallyDereferenceable { } extension OpenAPI.Parameter.SchemaContext: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { let oldSchema = schema - async let (newSchema, c1) = oldSchema.externallyDereferenced(with: loader) + async let (newSchema, c1, m1) = oldSchema.externallyDereferenced(with: loader) var newSchemaContext = self var newComponents = try await c1 + var newMessages = try await m1 newSchemaContext.schema = try await newSchema if let oldExamples = examples { - let (newExamples, c2) = try await oldExamples.externallyDereferenced(with: loader) + let (newExamples, c2, m2) = try await oldExamples.externallyDereferenced(with: loader) newSchemaContext.examples = newExamples try newComponents.merge(c2) + newMessages += m2 } - return (newSchemaContext, newComponents) + return (newSchemaContext, newComponents, newMessages) } } diff --git a/Sources/OpenAPIKit/Path Item/DereferencedPathItem.swift b/Sources/OpenAPIKit/Path Item/DereferencedPathItem.swift index 48d9b038a..d9f25538f 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: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { let oldParameters = parameters let oldServers = servers let oldGet = get @@ -152,19 +152,20 @@ extension OpenAPI.PathItem: ExternallyDereferenceable { 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) + async let (newParameters, c1, m1) = oldParameters.externallyDereferenced(with: loader) +// async let (newServers, c2, m2) = oldServers.externallyDereferenced(with: loader) + async let (newGet, c3, m3) = oldGet.externallyDereferenced(with: loader) + async let (newPut, c4, m4) = oldPut.externallyDereferenced(with: loader) + async let (newPost, c5, m5) = oldPost.externallyDereferenced(with: loader) + async let (newDelete, c6, m6) = oldDelete.externallyDereferenced(with: loader) + async let (newOptions, c7, m7) = oldOptions.externallyDereferenced(with: loader) + async let (newHead, c8, m8) = oldHead.externallyDereferenced(with: loader) + async let (newPatch, c9, m9) = oldPatch.externallyDereferenced(with: loader) + async let (newTrace, c10, m10) = oldTrace.externallyDereferenced(with: loader) var pathItem = self var newComponents = try await c1 + var newMessages = try await m1 // 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 @@ -188,12 +189,22 @@ extension OpenAPI.PathItem: ExternallyDereferenceable { try await newComponents.merge(c9) try await newComponents.merge(c10) + try await newMessages += m3 + try await newMessages += m4 + try await newMessages += m5 + try await newMessages += m6 + try await newMessages += m7 + try await newMessages += m8 + try await newMessages += m9 + try await newMessages += m10 + if let oldServers { - async let (newServers, c2) = oldServers.externallyDereferenced(with: loader) + async let (newServers, c2, m2) = oldServers.externallyDereferenced(with: loader) pathItem.servers = try await newServers try await newComponents.merge(c2) + try await newMessages += m2 } - return (pathItem, newComponents) + return (pathItem, newComponents, newMessages) } } diff --git a/Sources/OpenAPIKit/Request/DereferencedRequest.swift b/Sources/OpenAPIKit/Request/DereferencedRequest.swift index 39d0c2409..36d802767 100644 --- a/Sources/OpenAPIKit/Request/DereferencedRequest.swift +++ b/Sources/OpenAPIKit/Request/DereferencedRequest.swift @@ -63,12 +63,12 @@ extension OpenAPI.Request: LocallyDereferenceable { } extension OpenAPI.Request: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { var newRequest = self - let (newContent, components) = try await content.externallyDereferenced(with: loader) + let (newContent, components, messages) = try await content.externallyDereferenced(with: loader) newRequest.content = newContent - return (newRequest, components) + return (newRequest, components, messages) } } diff --git a/Sources/OpenAPIKit/Response/DereferencedResponse.swift b/Sources/OpenAPIKit/Response/DereferencedResponse.swift index add208773..702da8765 100644 --- a/Sources/OpenAPIKit/Response/DereferencedResponse.swift +++ b/Sources/OpenAPIKit/Response/DereferencedResponse.swift @@ -78,28 +78,31 @@ extension OpenAPI.Response: LocallyDereferenceable { } extension OpenAPI.Response: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { 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) + async let (newContent, c1, m1) = oldContent.externallyDereferenced(with: loader) + async let (newLinks, c2, m2) = oldLinks.externallyDereferenced(with: loader) +// async let (newHeaders, c3, m3) = oldHeaders.externallyDereferenced(with: loader) var response = self + var messages = try await m1 response.content = try await newContent response.links = try await newLinks var components = try await c1 try await components.merge(c2) + try await messages += m2 if let oldHeaders { - let (newHeaders, c3) = try await oldHeaders.externallyDereferenced(with: loader) + let (newHeaders, c3, m3) = try await oldHeaders.externallyDereferenced(with: loader) response.headers = newHeaders try components.merge(c3) + messages += m3 } - return (response, components) + return (response, components, messages) } } diff --git a/Sources/OpenAPIKit/Schema Object/DereferencedJSONSchema.swift b/Sources/OpenAPIKit/Schema Object/DereferencedJSONSchema.swift index dfe1af9e3..f3b061f5c 100644 --- a/Sources/OpenAPIKit/Schema Object/DereferencedJSONSchema.swift +++ b/Sources/OpenAPIKit/Schema Object/DereferencedJSONSchema.swift @@ -536,41 +536,51 @@ extension JSONSchema: LocallyDereferenceable { } extension JSONSchema: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { let newSchema: JSONSchema let newComponents: OpenAPI.Components + let newMessages: [Loader.Message] switch value { case .null(_): newComponents = .noComponents newSchema = self + newMessages = [] case .boolean(_): newComponents = .noComponents newSchema = self + newMessages = [] case .number(_, _): newComponents = .noComponents newSchema = self + newMessages = [] case .integer(_, _): newComponents = .noComponents newSchema = self + newMessages = [] case .string(_, _): newComponents = .noComponents newSchema = self + newMessages = [] case .object(let core, let object): var components = OpenAPI.Components() + var messages = [Loader.Message]() - let (newProperties, c1) = try await object.properties.externallyDereferenced(with: loader) + let (newProperties, c1, m1) = try await object.properties.externallyDereferenced(with: loader) try components.merge(c1) + messages += m1 let newAdditionalProperties: Either? if case .b(let schema) = object.additionalProperties { - let (additionalProperties, c2) = try await schema.externallyDereferenced(with: loader) + let (additionalProperties, c2, m2) = try await schema.externallyDereferenced(with: loader) try components.merge(c2) + messages += m2 newAdditionalProperties = .b(additionalProperties) } else { newAdditionalProperties = object.additionalProperties } newComponents = components + newMessages = messages newSchema = .init( schema: .object( core, @@ -583,8 +593,9 @@ extension JSONSchema: ExternallyDereferenceable { ) ) case .array(let core, let array): - let (newItems, components) = try await array.items.externallyDereferenced(with: loader) + let (newItems, components, messages) = try await array.items.externallyDereferenced(with: loader) newComponents = components + newMessages = messages newSchema = .init( schema: .array( core, @@ -597,40 +608,46 @@ extension JSONSchema: ExternallyDereferenceable { ) ) case .all(let schema, let core): - let (newSubschemas, components) = try await schema.externallyDereferenced(with: loader) + let (newSubschemas, components, messages) = try await schema.externallyDereferenced(with: loader) newComponents = components + newMessages = messages newSchema = .init( schema: .all(of: newSubschemas, core: core) ) case .one(let schema, let core): - let (newSubschemas, components) = try await schema.externallyDereferenced(with: loader) + let (newSubschemas, components, messages) = try await schema.externallyDereferenced(with: loader) newComponents = components + newMessages = messages newSchema = .init( schema: .one(of: newSubschemas, core: core) ) case .any(let schema, let core): - let (newSubschemas, components) = try await schema.externallyDereferenced(with: loader) + let (newSubschemas, components, messages) = try await schema.externallyDereferenced(with: loader) newComponents = components + newMessages = messages newSchema = .init( schema: .any(of: newSubschemas, core: core) ) case .not(let schema, let core): - let (newSubschema, components) = try await schema.externallyDereferenced(with: loader) + let (newSubschema, components, messages) = try await schema.externallyDereferenced(with: loader) newComponents = components + newMessages = messages newSchema = .init( schema: .not(newSubschema, core: core) ) case .reference(let reference, let core): - let (newReference, components) = try await reference.externallyDereferenced(with: loader) + let (newReference, components, messages) = try await reference.externallyDereferenced(with: loader) newComponents = components + newMessages = messages newSchema = .init( schema: .reference(newReference, core) ) case .fragment(_): newComponents = .noComponents newSchema = self + newMessages = [] } - return (newSchema, newComponents) + return (newSchema, newComponents, newMessages) } } diff --git a/Sources/OpenAPIKit/Security/SecurityScheme.swift b/Sources/OpenAPIKit/Security/SecurityScheme.swift index c4af0ff78..8e487bba7 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: Loader.Type) async throws -> (Self, OpenAPI.Components) { - return (self, .init()) + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { + return (self, .init(), []) } } diff --git a/Sources/OpenAPIKit/Server.swift b/Sources/OpenAPIKit/Server.swift index 80f4bec07..d21a1b5e7 100644 --- a/Sources/OpenAPIKit/Server.swift +++ b/Sources/OpenAPIKit/Server.swift @@ -260,8 +260,8 @@ extension OpenAPI.Server.Variable { } extension OpenAPI.Server: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { - return (self, .init()) + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { + return (self, .init(), []) } } diff --git a/Sources/OpenAPIKit/Utility/Array+ExternallyDereferenceable.swift b/Sources/OpenAPIKit/Utility/Array+ExternallyDereferenceable.swift index a67002587..3a959b7ce 100644 --- a/Sources/OpenAPIKit/Utility/Array+ExternallyDereferenceable.swift +++ b/Sources/OpenAPIKit/Utility/Array+ExternallyDereferenceable.swift @@ -6,8 +6,8 @@ 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 + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { + try await withThrowingTaskGroup(of: (Int, (Element, OpenAPI.Components, [Loader.Message])).self) { group in for (idx, elem) in zip(self.indices, self) { group.addTask { return try await (idx, elem.externallyDereferenced(with: loader)) @@ -16,15 +16,17 @@ extension Array where Element: ExternallyDereferenceable { var newElems = Array<(Int, Element)>() var newComponents = OpenAPI.Components() + var newMessages = [Loader.Message]() - for try await (idx, (elem, components)) in group { + for try await (idx, (elem, components, messages)) in group { newElems.append((idx, elem)) try newComponents.merge(components) + newMessages += messages } // 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) + return (newElems.map { $0.1 }, newComponents, newMessages) } } } diff --git a/Sources/OpenAPIKit/Utility/Dictionary+ExternallyDereferenceable.swift b/Sources/OpenAPIKit/Utility/Dictionary+ExternallyDereferenceable.swift index d15b123b2..ae0696e24 100644 --- a/Sources/OpenAPIKit/Utility/Dictionary+ExternallyDereferenceable.swift +++ b/Sources/OpenAPIKit/Utility/Dictionary+ExternallyDereferenceable.swift @@ -7,23 +7,25 @@ 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 + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { + try await withThrowingTaskGroup(of: (Key, Value, OpenAPI.Components, [Loader.Message]).self) { group in for (key, value) in self { group.addTask { - let (newRef, components) = try await value.externallyDereferenced(with: loader) - return (key, newRef, components) + let (newRef, components, messages) = try await value.externallyDereferenced(with: loader) + return (key, newRef, components, messages) } } var newDict = Self() var newComponents = OpenAPI.Components() + var newMessage = [Loader.Message]() - for try await (key, newRef, components) in group { + for try await (key, newRef, components, messages) in group { newDict[key] = newRef try newComponents.merge(components) + newMessage += messages } - return (newDict, newComponents) + return (newDict, newComponents, newMessage) } } } diff --git a/Sources/OpenAPIKit/Utility/Optional+ExternallyDereferenceable.swift b/Sources/OpenAPIKit/Utility/Optional+ExternallyDereferenceable.swift index 79b055b52..87c7a0649 100644 --- a/Sources/OpenAPIKit/Utility/Optional+ExternallyDereferenceable.swift +++ b/Sources/OpenAPIKit/Utility/Optional+ExternallyDereferenceable.swift @@ -6,8 +6,8 @@ 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()) } + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { + 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 38ab9bbc3..1c882a7ce 100644 --- a/Sources/OpenAPIKit/Utility/OrderedDictionary+ExternallyDereferenceable.swift +++ b/Sources/OpenAPIKit/Utility/OrderedDictionary+ExternallyDereferenceable.swift @@ -9,26 +9,28 @@ 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 + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { + try await withThrowingTaskGroup(of: (Key, Value, OpenAPI.Components, [Loader.Message]).self) { group in for (key, value) in self { group.addTask { - let (newRef, components) = try await value.externallyDereferenced(with: loader) - return (key, newRef, components) + let (newRef, components, messages) = try await value.externallyDereferenced(with: loader) + return (key, newRef, components, messages) } } var newDict = Self() var newComponents = OpenAPI.Components() + var newMessages = [Loader.Message]() - for try await (key, newRef, components) in group { + for try await (key, newRef, components, messages) in group { newDict[key] = newRef try newComponents.merge(components) + newMessages += messages } // things may come in out of order because of concurrency // so we reorder after completing all entries. try newDict.applyOrder(self) - return (newDict, newComponents) + return (newDict, newComponents, newMessages) } } } diff --git a/Sources/OpenAPIKit30/Components Object/Components.swift b/Sources/OpenAPIKit30/Components Object/Components.swift index 12b79fced..352f4841b 100644 --- a/Sources/OpenAPIKit30/Components Object/Components.swift +++ b/Sources/OpenAPIKit30/Components Object/Components.swift @@ -312,12 +312,16 @@ extension OpenAPI.Components { } extension OpenAPI.Components { - internal mutating func externallyDereference(with loader: Loader.Type, depth: ExternalDereferenceDepth = .iterations(1)) async throws { + internal mutating func externallyDereference(with loader: Loader.Type, depth: ExternalDereferenceDepth = .iterations(1), context: [Loader.Message] = []) async throws -> [Loader.Message] { if case let .iterations(number) = depth, number <= 0 { - return + return context } + // NOTE: The links and callbacks related code commented out below pushes Swift 5.8 and 5.9 + // over the edge and you get exit code 137 crashes in CI. + // Swift 5.10 handles it fine. + let oldSchemas = schemas let oldResponses = responses let oldParameters = parameters @@ -325,18 +329,20 @@ extension OpenAPI.Components { let oldRequestBodies = requestBodies let oldHeaders = headers let oldSecuritySchemes = securitySchemes + let oldLinks = links 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) + async let (newSchemas, c1, m1) = oldSchemas.externallyDereferenced(with: loader) + async let (newResponses, c2, m2) = oldResponses.externallyDereferenced(with: loader) + async let (newParameters, c3, m3) = oldParameters.externallyDereferenced(with: loader) + async let (newExamples, c4, m4) = oldExamples.externallyDereferenced(with: loader) + async let (newRequestBodies, c5, m5) = oldRequestBodies.externallyDereferenced(with: loader) + async let (newHeaders, c6, m6) = oldHeaders.externallyDereferenced(with: loader) + async let (newSecuritySchemes, c7, m7) = oldSecuritySchemes.externallyDereferenced(with: loader) +// async let (newLinks, c8, m8) = oldLinks.externallyDereferenced(with: loader) +// async let (newCallbacks, c9, m9) = oldCallbacks.externallyDereferenced(with: loader) + async let (newPathItems, c10, m10) = oldPathItems.externallyDereferenced(with: loader) schemas = try await newSchemas responses = try await newResponses @@ -345,7 +351,8 @@ extension OpenAPI.Components { requestBodies = try await newRequestBodies headers = try await newHeaders securitySchemes = try await newSecuritySchemes - callbacks = try await newCallbacks +// links = try await newLinks +// callbacks = try await newCallbacks pathItems = try await newPathItems let c1Resolved = try await c1 @@ -355,8 +362,18 @@ extension OpenAPI.Components { 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 c8Resolved = try await c8 +// let c9Resolved = try await c9 + let c10Resolved = try await c10 + + // For Swift 5.10+ we can delete the following links and callbacks code and uncomment the + // preferred code above. + let (newLinks, c8, m8) = try await oldLinks.externallyDereferenced(with: loader) + links = newLinks + let c8Resolved = c8 + let (newCallbacks, c9, m9) = try await oldCallbacks.externallyDereferenced(with: loader) + callbacks = newCallbacks + let c9Resolved = c9 let noNewComponents = c1Resolved.isEmpty @@ -368,8 +385,11 @@ extension OpenAPI.Components { && c7Resolved.isEmpty && c8Resolved.isEmpty && c9Resolved.isEmpty + && c10Resolved.isEmpty + + let newMessages = try await context + m1 + m2 + m3 + m4 + m5 + m6 + m7 + m8 + m9 + m10 - if noNewComponents { return } + if noNewComponents { return newMessages } try merge(c1Resolved) try merge(c2Resolved) @@ -380,12 +400,13 @@ extension OpenAPI.Components { try merge(c7Resolved) try merge(c8Resolved) try merge(c9Resolved) - + try merge(c10Resolved) + switch depth { case .iterations(let number): - try await externallyDereference(with: loader, depth: .iterations(number - 1)) + return try await externallyDereference(with: loader, depth: .iterations(number - 1), context: newMessages) case .full: - try await externallyDereference(with: loader, depth: .full) + return try await externallyDereference(with: loader, depth: .full, context: newMessages) } } } diff --git a/Sources/OpenAPIKit30/Content/DereferencedContent.swift b/Sources/OpenAPIKit30/Content/DereferencedContent.swift index c7f256c1d..ebfa10f07 100644 --- a/Sources/OpenAPIKit30/Content/DereferencedContent.swift +++ b/Sources/OpenAPIKit30/Content/DereferencedContent.swift @@ -77,28 +77,31 @@ extension OpenAPI.Content: LocallyDereferenceable { } extension OpenAPI.Content: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { - let oldSchema = schema + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { + let oldSchema = schema - async let (newSchema, c1) = oldSchema.externallyDereferenced(with: loader) + async let (newSchema, c1, m1) = oldSchema.externallyDereferenced(with: loader) - var newContent = self - var newComponents = try await c1 + var newContent = self + var newComponents = try await c1 + var newMessages = try await m1 - newContent.schema = try await newSchema + newContent.schema = try await newSchema if let oldExamples = examples { - let (newExamples, c2) = try await oldExamples.externallyDereferenced(with: loader) + let (newExamples, c2, m2) = try await oldExamples.externallyDereferenced(with: loader) newContent.examples = newExamples try newComponents.merge(c2) + newMessages += m2 } if let oldEncoding = encoding { - let (newEncoding, c3) = try await oldEncoding.externallyDereferenced(with: loader) + let (newEncoding, c3, m3) = try await oldEncoding.externallyDereferenced(with: loader) newContent.encoding = newEncoding try newComponents.merge(c3) + newMessages += m3 } - return (newContent, newComponents) + return (newContent, newComponents, newMessages) } } diff --git a/Sources/OpenAPIKit30/Content/DereferencedContentEncoding.swift b/Sources/OpenAPIKit30/Content/DereferencedContentEncoding.swift index 605ae7426..aaa9a1fd5 100644 --- a/Sources/OpenAPIKit30/Content/DereferencedContentEncoding.swift +++ b/Sources/OpenAPIKit30/Content/DereferencedContentEncoding.swift @@ -58,15 +58,17 @@ extension OpenAPI.Content.Encoding: LocallyDereferenceable { } extension OpenAPI.Content.Encoding: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { let newHeaders: OpenAPI.Header.Map? let newComponents: OpenAPI.Components + let newMessages: [Loader.Message] if let oldHeaders = headers { - (newHeaders, newComponents) = try await oldHeaders.externallyDereferenced(with: loader) + (newHeaders, newComponents, newMessages) = try await oldHeaders.externallyDereferenced(with: loader) } else { newHeaders = nil newComponents = .init() + newMessages = [] } let newEncoding = OpenAPI.Content.Encoding( @@ -77,6 +79,6 @@ extension OpenAPI.Content.Encoding: ExternallyDereferenceable { allowReserved: allowReserved ) - return (newEncoding, newComponents) + return (newEncoding, newComponents, newMessages) } } diff --git a/Sources/OpenAPIKit30/Document/Document.swift b/Sources/OpenAPIKit30/Document/Document.swift index 29f7b0f2f..930a0ea6c 100644 --- a/Sources/OpenAPIKit30/Document/Document.swift +++ b/Sources/OpenAPIKit30/Document/Document.swift @@ -323,6 +323,11 @@ extension OpenAPI.Document { /// Create a locally-dereferenced OpenAPI /// Document. /// + /// This function assumes all references are + /// local to the same file. If you want to resolve + /// remote references as well, call `externallyDereference()` + /// first and then locally dereference the result. + /// /// A dereferenced document contains no /// `JSONReferences`. All components have been /// inlined. @@ -346,20 +351,40 @@ extension OpenAPI.Document { return try DereferencedDocument(self) } - public mutating func externallyDereference(with loader: Loader.Type, depth: ExternalDereferenceDepth = .iterations(1)) async throws { + /// Load all remote references into the document. A remote reference is one + /// that points to another file rather than a location within the + /// same file. + /// + /// This function will load remote references into the Components object + /// and replace the remote reference with a local reference to that component. + /// No local references are modified or resolved by this function. You can + /// call `locallyDereferenced()` on the externally dereferenced document if + /// you want to also remove local references by inlining all of them. + /// + /// Externally dereferencing a document requires that you provide both a + /// function that produces a `OpenAPI.ComponentKey` for any given remote + /// file URI and also a function that loads and decodes the data found in + /// that remote file. The latter is less work than it may sound like because + /// the function is told what Decodable thing it wants, so you really just + /// need to decide what decoder to use and provide the file data to that + /// decoder. See `ExternalLoader` documentation for details. + @discardableResult + public mutating func externallyDereference(with loader: Loader.Type, depth: ExternalDereferenceDepth = .iterations(1), context: [Loader.Message] = []) async throws -> [Loader.Message] { if case let .iterations(number) = depth, number <= 0 { - return + return context } let oldPaths = paths - async let (newPaths, c1) = oldPaths.externallyDereferenced(with: loader) + async let (newPaths, c1, m1) = oldPaths.externallyDereferenced(with: loader) paths = try await newPaths try await components.merge(c1) - try await components.externallyDereference(with: loader, depth: depth) + let m2 = try await components.externallyDereference(with: loader, depth: depth) + + return try await context + m1 + m2 } } diff --git a/Sources/OpenAPIKit30/Either/Either+ExternallyDereferenceable.swift b/Sources/OpenAPIKit30/Either/Either+ExternallyDereferenceable.swift index 8c202bb5b..62c919355 100644 --- a/Sources/OpenAPIKit30/Either/Either+ExternallyDereferenceable.swift +++ b/Sources/OpenAPIKit30/Either/Either+ExternallyDereferenceable.swift @@ -10,14 +10,14 @@ import OpenAPIKitCore // MARK: - ExternallyDereferenceable extension Either: ExternallyDereferenceable where A: ExternallyDereferenceable, B: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { switch self { case .a(let a): - let (newA, components) = try await a.externallyDereferenced(with: loader) - return (.a(newA), components) + let (newA, components, messages) = try await a.externallyDereferenced(with: loader) + return (.a(newA), components, messages) case .b(let b): - let (newB, components) = try await b.externallyDereferenced(with: loader) - return (.b(newB), components) + let (newB, components, messages) = try await b.externallyDereferenced(with: loader) + return (.b(newB), components, messages) } } } diff --git a/Sources/OpenAPIKit30/Example.swift b/Sources/OpenAPIKit30/Example.swift index 2eec08ce2..d28424238 100644 --- a/Sources/OpenAPIKit30/Example.swift +++ b/Sources/OpenAPIKit30/Example.swift @@ -185,8 +185,8 @@ extension OpenAPI.Example: LocallyDereferenceable { } extension OpenAPI.Example: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { - return (self, .init()) + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { + return (self, .init(), []) } } diff --git a/Sources/OpenAPIKit30/ExternalLoader.swift b/Sources/OpenAPIKit30/ExternalLoader.swift index 257264995..28cfa8d39 100644 --- a/Sources/OpenAPIKit30/ExternalLoader.swift +++ b/Sources/OpenAPIKit30/ExternalLoader.swift @@ -12,11 +12,17 @@ import Foundation /// without knowing the details of what decoder is being used or how new internal /// references should be named. public protocol ExternalLoader { + /// This can be anything that an implementor of this protocol wants to pass back from + /// the `load()` function and have available after all external loading has been done. + /// + /// A trivial type if no Messages are needed would be Void. + associatedtype Message + /// 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 + static func load(_: URL) async throws -> (T, [Message]) 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 @@ -30,5 +36,5 @@ public protocol ExternalLoader { } public protocol ExternallyDereferenceable { - func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) + func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) } diff --git a/Sources/OpenAPIKit30/Header/DereferencedHeader.swift b/Sources/OpenAPIKit30/Header/DereferencedHeader.swift index ec9881c71..40509234a 100644 --- a/Sources/OpenAPIKit30/Header/DereferencedHeader.swift +++ b/Sources/OpenAPIKit30/Header/DereferencedHeader.swift @@ -84,7 +84,7 @@ extension OpenAPI.Header: LocallyDereferenceable { } extension OpenAPI.Header: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { // if not for a Swift bug, this whole next bit would just be the // next line: @@ -92,16 +92,19 @@ extension OpenAPI.Header: ExternallyDereferenceable { let newSchemaOrContent: Either let newComponents: OpenAPI.Components + let newMessages: [Loader.Message] switch schemaOrContent { case .a(let schemaContext): - let (context, components) = try await schemaContext.externallyDereferenced(with: loader) + let (context, components, messages) = try await schemaContext.externallyDereferenced(with: loader) newSchemaOrContent = .a(context) newComponents = components + newMessages = messages case .b(let contentMap): - let (map, components) = try await contentMap.externallyDereferenced(with: loader) + let (map, components, messages) = try await contentMap.externallyDereferenced(with: loader) newSchemaOrContent = .b(map) newComponents = components + newMessages = messages } let newHeader = OpenAPI.Header( @@ -112,6 +115,6 @@ extension OpenAPI.Header: ExternallyDereferenceable { vendorExtensions: vendorExtensions ) - return (newHeader, newComponents) + return (newHeader, newComponents, newMessages) } } diff --git a/Sources/OpenAPIKit30/JSONReference.swift b/Sources/OpenAPIKit30/JSONReference.swift index e6b425224..6e6350751 100644 --- a/Sources/OpenAPIKit30/JSONReference.swift +++ b/Sources/OpenAPIKit30/JSONReference.swift @@ -375,16 +375,16 @@ 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) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { switch self { case .internal(let ref): - return (.internal(ref), .init()) + 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) + let (component, messages): (ReferenceType, [Loader.Message]) = 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) + return (try components.reference(named: componentKey.rawValue, ofType: ReferenceType.self), components, messages) } } } diff --git a/Sources/OpenAPIKit30/Link.swift b/Sources/OpenAPIKit30/Link.swift index c39d6c219..c416e97b7 100644 --- a/Sources/OpenAPIKit30/Link.swift +++ b/Sources/OpenAPIKit30/Link.swift @@ -279,13 +279,13 @@ 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) + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { + let (newServer, newComponents, newMessages) = try await server.externallyDereferenced(with: loader) var newLink = self newLink.server = newServer - return (newLink, newComponents) + return (newLink, newComponents, newMessages) } } diff --git a/Sources/OpenAPIKit30/Operation/DereferencedOperation.swift b/Sources/OpenAPIKit30/Operation/DereferencedOperation.swift index 80cfb5590..2bb1bbd16 100644 --- a/Sources/OpenAPIKit30/Operation/DereferencedOperation.swift +++ b/Sources/OpenAPIKit30/Operation/DereferencedOperation.swift @@ -126,41 +126,42 @@ extension OpenAPI.Operation: LocallyDereferenceable { } extension OpenAPI.Operation: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { let oldParameters = parameters let oldRequestBody = requestBody let oldResponses = responses + let oldCallbacks = callbacks + let oldServers = servers - 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) + async let (newParameters, c1, m1) = oldParameters.externallyDereferenced(with: loader) + async let (newRequestBody, c2, m2) = oldRequestBody.externallyDereferenced(with: loader) + async let (newResponses, c3, m3) = oldResponses.externallyDereferenced(with: loader) + async let (newCallbacks, c4, m4) = oldCallbacks.externallyDereferenced(with: loader) +// let (newServers, c5, m5) = try await oldServers.externallyDereferenced(with: loader) var newOperation = self var newComponents = try await c1 + var newMessages = try await m1 newOperation.parameters = try await newParameters newOperation.requestBody = try await newRequestBody try await newComponents.merge(c2) + try await newMessages += m2 newOperation.responses = try await newResponses try await newComponents.merge(c3) + try await newMessages += m3 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) - } + try await newMessages += m4 // 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) + if let oldServers { + let (newServers, c5, m5) = try await oldServers.externallyDereferenced(with: loader) newOperation.servers = newServers - try newComponents.merge(c6) + try newComponents.merge(c5) + newMessages += m5 } - return (newOperation, newComponents) + return (newOperation, newComponents, newMessages) } } diff --git a/Sources/OpenAPIKit30/Parameter/DereferencedParameter.swift b/Sources/OpenAPIKit30/Parameter/DereferencedParameter.swift index e0385e1c1..acb30c8d2 100644 --- a/Sources/OpenAPIKit30/Parameter/DereferencedParameter.swift +++ b/Sources/OpenAPIKit30/Parameter/DereferencedParameter.swift @@ -84,29 +84,32 @@ extension OpenAPI.Parameter: LocallyDereferenceable { } extension OpenAPI.Parameter: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { // 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, components, messages) = try await schemaOrContent.externallyDereferenced(with: loader) let newSchemaOrContent: Either let newComponents: OpenAPI.Components + let newMessages: [Loader.Message] switch schemaOrContent { case .a(let schemaContext): - let (context, components) = try await schemaContext.externallyDereferenced(with: loader) + let (context, components, messages) = try await schemaContext.externallyDereferenced(with: loader) newSchemaOrContent = .a(context) newComponents = components + newMessages = messages case .b(let contentMap): - let (map, components) = try await contentMap.externallyDereferenced(with: loader) + let (map, components, messages) = try await contentMap.externallyDereferenced(with: loader) newSchemaOrContent = .b(map) newComponents = components + newMessages = messages } var newParameter = self newParameter.schemaOrContent = newSchemaOrContent - return (newParameter, newComponents) + return (newParameter, newComponents, newMessages) } } diff --git a/Sources/OpenAPIKit30/Parameter/DereferencedSchemaContext.swift b/Sources/OpenAPIKit30/Parameter/DereferencedSchemaContext.swift index 3e7d357f4..9a035f1e7 100644 --- a/Sources/OpenAPIKit30/Parameter/DereferencedSchemaContext.swift +++ b/Sources/OpenAPIKit30/Parameter/DereferencedSchemaContext.swift @@ -70,22 +70,24 @@ extension OpenAPI.Parameter.SchemaContext: LocallyDereferenceable { } extension OpenAPI.Parameter.SchemaContext: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { let oldSchema = schema - async let (newSchema, c1) = oldSchema.externallyDereferenced(with: loader) + async let (newSchema, c1, m1) = oldSchema.externallyDereferenced(with: loader) var newSchemaContext = self var newComponents = try await c1 + var newMessages = try await m1 newSchemaContext.schema = try await newSchema if let oldExamples = examples { - let (newExamples, c2) = try await oldExamples.externallyDereferenced(with: loader) + let (newExamples, c2, m2) = try await oldExamples.externallyDereferenced(with: loader) newSchemaContext.examples = newExamples try newComponents.merge(c2) + newMessages += m2 } - return (newSchemaContext, newComponents) + return (newSchemaContext, newComponents, newMessages) } } diff --git a/Sources/OpenAPIKit30/Path Item/DereferencedPathItem.swift b/Sources/OpenAPIKit30/Path Item/DereferencedPathItem.swift index 9b7e5ac49..542b8aa54 100644 --- a/Sources/OpenAPIKit30/Path Item/DereferencedPathItem.swift +++ b/Sources/OpenAPIKit30/Path Item/DereferencedPathItem.swift @@ -139,7 +139,7 @@ extension OpenAPI.PathItem: LocallyDereferenceable { } extension OpenAPI.PathItem: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { let oldParameters = parameters let oldServers = servers let oldGet = get @@ -151,19 +151,20 @@ extension OpenAPI.PathItem: ExternallyDereferenceable { 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) + async let (newParameters, c1, m1) = oldParameters.externallyDereferenced(with: loader) +// async let (newServers, c2, m2) = oldServers.externallyDereferenced(with: loader) + async let (newGet, c3, m3) = oldGet.externallyDereferenced(with: loader) + async let (newPut, c4, m4) = oldPut.externallyDereferenced(with: loader) + async let (newPost, c5, m5) = oldPost.externallyDereferenced(with: loader) + async let (newDelete, c6, m6) = oldDelete.externallyDereferenced(with: loader) + async let (newOptions, c7, m7) = oldOptions.externallyDereferenced(with: loader) + async let (newHead, c8, m8) = oldHead.externallyDereferenced(with: loader) + async let (newPatch, c9, m9) = oldPatch.externallyDereferenced(with: loader) + async let (newTrace, c10, m10) = oldTrace.externallyDereferenced(with: loader) var pathItem = self var newComponents = try await c1 + var newMessages = try await m1 // 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 @@ -187,12 +188,22 @@ extension OpenAPI.PathItem: ExternallyDereferenceable { try await newComponents.merge(c9) try await newComponents.merge(c10) + try await newMessages += m3 + try await newMessages += m4 + try await newMessages += m5 + try await newMessages += m6 + try await newMessages += m7 + try await newMessages += m8 + try await newMessages += m9 + try await newMessages += m10 + if let oldServers { - async let (newServers, c2) = oldServers.externallyDereferenced(with: loader) + async let (newServers, c2, m2) = oldServers.externallyDereferenced(with: loader) pathItem.servers = try await newServers try await newComponents.merge(c2) + try await newMessages += m2 } - return (pathItem, newComponents) + return (pathItem, newComponents, newMessages) } } diff --git a/Sources/OpenAPIKit30/Request/DereferencedRequest.swift b/Sources/OpenAPIKit30/Request/DereferencedRequest.swift index e59647cde..6c7fd3189 100644 --- a/Sources/OpenAPIKit30/Request/DereferencedRequest.swift +++ b/Sources/OpenAPIKit30/Request/DereferencedRequest.swift @@ -63,12 +63,12 @@ extension OpenAPI.Request: LocallyDereferenceable { } extension OpenAPI.Request: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { var newRequest = self - let (newContent, components) = try await content.externallyDereferenced(with: loader) + let (newContent, components, messages) = try await content.externallyDereferenced(with: loader) newRequest.content = newContent - return (newRequest, components) + return (newRequest, components, messages) } } diff --git a/Sources/OpenAPIKit30/Response/DereferencedResponse.swift b/Sources/OpenAPIKit30/Response/DereferencedResponse.swift index add208773..ced58ae20 100644 --- a/Sources/OpenAPIKit30/Response/DereferencedResponse.swift +++ b/Sources/OpenAPIKit30/Response/DereferencedResponse.swift @@ -78,14 +78,14 @@ extension OpenAPI.Response: LocallyDereferenceable { } extension OpenAPI.Response: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { 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) + async let (newContent, c1, m1) = oldContent.externallyDereferenced(with: loader) + async let (newLinks, c2, m2) = oldLinks.externallyDereferenced(with: loader) +// async let (newHeaders, c3, m3) = oldHeaders.externallyDereferenced(with: loader) var response = self response.content = try await newContent @@ -94,12 +94,16 @@ extension OpenAPI.Response: ExternallyDereferenceable { var components = try await c1 try await components.merge(c2) + var messages = try await m1 + try await messages += m2 + if let oldHeaders { - let (newHeaders, c3) = try await oldHeaders.externallyDereferenced(with: loader) + let (newHeaders, c3, m3) = try await oldHeaders.externallyDereferenced(with: loader) response.headers = newHeaders try components.merge(c3) + messages += m3 } - return (response, components) + return (response, components, messages) } } diff --git a/Sources/OpenAPIKit30/Schema Object/DereferencedJSONSchema.swift b/Sources/OpenAPIKit30/Schema Object/DereferencedJSONSchema.swift index 57776fe42..f749f549a 100644 --- a/Sources/OpenAPIKit30/Schema Object/DereferencedJSONSchema.swift +++ b/Sources/OpenAPIKit30/Schema Object/DereferencedJSONSchema.swift @@ -410,38 +410,47 @@ extension JSONSchema: LocallyDereferenceable { } extension JSONSchema: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { let newSchema: JSONSchema let newComponents: OpenAPI.Components + let newMessages: [Loader.Message] switch value { case .boolean(_): newComponents = .noComponents newSchema = self + newMessages = [] case .number(_, _): newComponents = .noComponents newSchema = self + newMessages = [] case .integer(_, _): newComponents = .noComponents newSchema = self + newMessages = [] case .string(_, _): newComponents = .noComponents newSchema = self + newMessages = [] case .object(let core, let object): var components = OpenAPI.Components() + var messages = [Loader.Message]() - let (newProperties, c1) = try await object.properties.externallyDereferenced(with: loader) + let (newProperties, c1, m1) = try await object.properties.externallyDereferenced(with: loader) try components.merge(c1) + messages += m1 let newAdditionalProperties: Either? if case .b(let schema) = object.additionalProperties { - let (additionalProperties, c2) = try await schema.externallyDereferenced(with: loader) + let (additionalProperties, c2, m2) = try await schema.externallyDereferenced(with: loader) try components.merge(c2) + messages += m2 newAdditionalProperties = .b(additionalProperties) } else { newAdditionalProperties = object.additionalProperties } newComponents = components + newMessages = messages newSchema = .init( schema: .object( core, @@ -455,8 +464,9 @@ extension JSONSchema: ExternallyDereferenceable { vendorExtensions: vendorExtensions ) case .array(let core, let array): - let (newItems, components) = try await array.items.externallyDereferenced(with: loader) + let (newItems, components, messages) = try await array.items.externallyDereferenced(with: loader) newComponents = components + newMessages = messages newSchema = .init( schema: .array( core, @@ -470,36 +480,41 @@ extension JSONSchema: ExternallyDereferenceable { vendorExtensions: vendorExtensions ) case .all(let schema, let core): - let (newSubschemas, components) = try await schema.externallyDereferenced(with: loader) + let (newSubschemas, components, messages) = try await schema.externallyDereferenced(with: loader) newComponents = components + newMessages = messages 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) + let (newSubschemas, components, messages) = try await schema.externallyDereferenced(with: loader) newComponents = components + newMessages = messages 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) + let (newSubschemas, components, messages) = try await schema.externallyDereferenced(with: loader) newComponents = components + newMessages = messages 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) + let (newSubschema, components, messages) = try await schema.externallyDereferenced(with: loader) newComponents = components + newMessages = messages newSchema = .init( schema: .not(newSubschema, core: core), vendorExtensions: vendorExtensions ) case .reference(let reference, let core): - let (newReference, components) = try await reference.externallyDereferenced(with: loader) + let (newReference, components, messages) = try await reference.externallyDereferenced(with: loader) newComponents = components + newMessages = messages newSchema = .init( schema: .reference(newReference, core), vendorExtensions: vendorExtensions @@ -507,8 +522,9 @@ extension JSONSchema: ExternallyDereferenceable { case .fragment(_): newComponents = .noComponents newSchema = self + newMessages = [] } - return (newSchema, newComponents) + return (newSchema, newComponents, newMessages) } } diff --git a/Sources/OpenAPIKit30/Security/SecurityScheme.swift b/Sources/OpenAPIKit30/Security/SecurityScheme.swift index 364196b67..22171d097 100644 --- a/Sources/OpenAPIKit30/Security/SecurityScheme.swift +++ b/Sources/OpenAPIKit30/Security/SecurityScheme.swift @@ -252,8 +252,8 @@ extension OpenAPI.SecurityScheme: LocallyDereferenceable { } extension OpenAPI.SecurityScheme: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { - return (self, .init()) + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { + return (self, .init(), []) } } diff --git a/Sources/OpenAPIKit30/Server.swift b/Sources/OpenAPIKit30/Server.swift index 5990e297b..d44a280bf 100644 --- a/Sources/OpenAPIKit30/Server.swift +++ b/Sources/OpenAPIKit30/Server.swift @@ -246,8 +246,8 @@ extension OpenAPI.Server.Variable { } extension OpenAPI.Server: ExternallyDereferenceable { - public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components) { - return (self, .init()) + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { + return (self, .init(), []) } } diff --git a/Sources/OpenAPIKit30/Utility/Array+ExternallyDereferenceable.swift b/Sources/OpenAPIKit30/Utility/Array+ExternallyDereferenceable.swift index a67002587..3a959b7ce 100644 --- a/Sources/OpenAPIKit30/Utility/Array+ExternallyDereferenceable.swift +++ b/Sources/OpenAPIKit30/Utility/Array+ExternallyDereferenceable.swift @@ -6,8 +6,8 @@ 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 + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { + try await withThrowingTaskGroup(of: (Int, (Element, OpenAPI.Components, [Loader.Message])).self) { group in for (idx, elem) in zip(self.indices, self) { group.addTask { return try await (idx, elem.externallyDereferenced(with: loader)) @@ -16,15 +16,17 @@ extension Array where Element: ExternallyDereferenceable { var newElems = Array<(Int, Element)>() var newComponents = OpenAPI.Components() + var newMessages = [Loader.Message]() - for try await (idx, (elem, components)) in group { + for try await (idx, (elem, components, messages)) in group { newElems.append((idx, elem)) try newComponents.merge(components) + newMessages += messages } // 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) + return (newElems.map { $0.1 }, newComponents, newMessages) } } } diff --git a/Sources/OpenAPIKit30/Utility/Dictionary+ExternallyDereferenceable.swift b/Sources/OpenAPIKit30/Utility/Dictionary+ExternallyDereferenceable.swift index d15b123b2..1369bb788 100644 --- a/Sources/OpenAPIKit30/Utility/Dictionary+ExternallyDereferenceable.swift +++ b/Sources/OpenAPIKit30/Utility/Dictionary+ExternallyDereferenceable.swift @@ -7,23 +7,25 @@ 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 + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { + try await withThrowingTaskGroup(of: (Key, Value, OpenAPI.Components, [Loader.Message]).self) { group in for (key, value) in self { group.addTask { - let (newRef, components) = try await value.externallyDereferenced(with: loader) - return (key, newRef, components) + let (newRef, components, messages) = try await value.externallyDereferenced(with: loader) + return (key, newRef, components, messages) } } var newDict = Self() var newComponents = OpenAPI.Components() + var newMessages = [Loader.Message]() - for try await (key, newRef, components) in group { + for try await (key, newRef, components, messages) in group { newDict[key] = newRef try newComponents.merge(components) + newMessages += messages } - return (newDict, newComponents) + return (newDict, newComponents, newMessages) } } } diff --git a/Sources/OpenAPIKit30/Utility/Optional+ExternallyDereferenceable.swift b/Sources/OpenAPIKit30/Utility/Optional+ExternallyDereferenceable.swift index 79b055b52..87c7a0649 100644 --- a/Sources/OpenAPIKit30/Utility/Optional+ExternallyDereferenceable.swift +++ b/Sources/OpenAPIKit30/Utility/Optional+ExternallyDereferenceable.swift @@ -6,8 +6,8 @@ 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()) } + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { + 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 index 38ab9bbc3..1c882a7ce 100644 --- a/Sources/OpenAPIKit30/Utility/OrderedDictionary+ExternallyDereferenceable.swift +++ b/Sources/OpenAPIKit30/Utility/OrderedDictionary+ExternallyDereferenceable.swift @@ -9,26 +9,28 @@ 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 + public func externallyDereferenced(with loader: Loader.Type) async throws -> (Self, OpenAPI.Components, [Loader.Message]) { + try await withThrowingTaskGroup(of: (Key, Value, OpenAPI.Components, [Loader.Message]).self) { group in for (key, value) in self { group.addTask { - let (newRef, components) = try await value.externallyDereferenced(with: loader) - return (key, newRef, components) + let (newRef, components, messages) = try await value.externallyDereferenced(with: loader) + return (key, newRef, components, messages) } } var newDict = Self() var newComponents = OpenAPI.Components() + var newMessages = [Loader.Message]() - for try await (key, newRef, components) in group { + for try await (key, newRef, components, messages) in group { newDict[key] = newRef try newComponents.merge(components) + newMessages += messages } // things may come in out of order because of concurrency // so we reorder after completing all entries. try newDict.applyOrder(self) - return (newDict, newComponents) + return (newDict, newComponents, newMessages) } } } diff --git a/Tests/OpenAPIKit30Tests/Document/ExternalDereferencingDocumentTests.swift b/Tests/OpenAPIKit30Tests/Document/ExternalDereferencingDocumentTests.swift index 862f47474..d9863a92c 100644 --- a/Tests/OpenAPIKit30Tests/Document/ExternalDereferencingDocumentTests.swift +++ b/Tests/OpenAPIKit30Tests/Document/ExternalDereferencingDocumentTests.swift @@ -14,7 +14,9 @@ final class ExternalDereferencingDocumentTests: XCTestCase { /// 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 { + typealias Message = String + + static func load(_ url: URL) async throws -> (T, [Message]) 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)) @@ -30,7 +32,7 @@ final class ExternalDereferencingDocumentTests: XCTestCase { } else { finished = decoded } - return finished + return (finished, [url.absoluteString]) } static func componentKey(type: T.Type, at url: URL) throws -> OpenAPIKit30.OpenAPI.ComponentKey { @@ -111,6 +113,9 @@ final class ExternalDereferencingDocumentTests: XCTestCase { "$ref": "file://./headers/webhook.json" } } + }, + "enc2": { + "style": "form" } } } @@ -138,7 +143,7 @@ final class ExternalDereferencingDocumentTests: XCTestCase { }, "headers": { "X-Hello": { - "$ref": "file://./headers/webhook.json" + "$ref": "file://./headers/webhook2.json" } } } @@ -150,6 +155,17 @@ final class ExternalDereferencingDocumentTests: XCTestCase { } } """, + "headers_webhook2_json": """ + { + "content": { + "application/json": { + "schema": { + "$ref": "file://./schemas/string_param.json" + } + } + } + } + """, "examples_good_json": """ { "value": "{\\"body\\": \\"request me\\"}" @@ -215,7 +231,8 @@ final class ExternalDereferencingDocumentTests: XCTestCase { .reference(.external(URL(string: "file://./params/name.json")!)) ] ), - "/webhook": .reference(.external(URL(string: "file://./paths/webhook.json")!)) + "/webhook": .reference(.external(URL(string: "file://./paths/webhook.json")!)), + "/callback": .reference(.external(URL(string: "file://./paths/callback.json")!)) ], components: .init( schemas: [ @@ -233,17 +250,39 @@ final class ExternalDereferencingDocumentTests: XCTestCase { try await docCopy1.externallyDereference(with: 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(with: ExampleLoader.self, depth: 3) + try await docCopy2.externallyDereference(with: ExampleLoader.self, depth: 4) docCopy2.components.sort() var docCopy3 = document - try await docCopy3.externallyDereference(with: ExampleLoader.self, depth: .full) + let messages = try await docCopy3.externallyDereference(with: ExampleLoader.self, depth: .full) docCopy3.components.sort() XCTAssertEqual(docCopy1, docCopy2) XCTAssertEqual(docCopy2, docCopy3) + + XCTAssertEqual( + messages.sorted(), + ["file://./callbacks/one.json", + "file://./examples/good.json", + "file://./headers/webhook.json", + "file://./headers/webhook2.json", + "file://./links/first.json", + "file://./params/name.json", + "file://./params/name.json", + "file://./paths/callback.json", + "file://./paths/webhook.json", + "file://./requests/webhook.json", + "file://./responses/webhook.json", + "file://./schemas/basic_object.json", + "file://./schemas/string_param.json", + "file://./schemas/string_param.json", + "file://./schemas/string_param.json", + "file://./schemas/string_param.json", + "file://./schemas/string_param.json#"] + ) } } diff --git a/Tests/OpenAPIKit30Tests/JSONReferenceTests.swift b/Tests/OpenAPIKit30Tests/JSONReferenceTests.swift index 5aca879c0..25332acd5 100644 --- a/Tests/OpenAPIKit30Tests/JSONReferenceTests.swift +++ b/Tests/OpenAPIKit30Tests/JSONReferenceTests.swift @@ -341,8 +341,10 @@ extension JSONReferenceTests { } struct SchemaLoader: ExternalLoader { - static func load(_ url: URL) -> T where T: Decodable { - return JSONSchema.string as! T + typealias Message = String + + static func load(_ url: URL) -> (T, [Message]) where T: Decodable { + return (JSONSchema.string as! T, [url.absoluteString]) } static func componentKey(type: T.Type, at url: URL) throws -> OpenAPI.ComponentKey { @@ -359,27 +361,30 @@ 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) + let (newReference, components, messages) = try await reference.externallyDereferenced(with: SchemaLoader.self) XCTAssertEqual(newReference, .component(named: "__schema_json")) XCTAssertEqual(components, .init(schemas: ["__schema_json": .string])) + XCTAssertEqual(messages, ["./schema.json"]) } func test_externalDerefFragment() async throws { let reference: JSONReference = .external(.init(string: "./schema.json#/test")!) - let (newReference, components) = try await reference.externallyDereferenced(with: SchemaLoader.self) + let (newReference, components, messages) = try await reference.externallyDereferenced(with: SchemaLoader.self) XCTAssertEqual(newReference, .component(named: "__schema_json__test")) XCTAssertEqual(components, .init(schemas: ["__schema_json__test": .string])) + XCTAssertEqual(messages, ["./schema.json#/test"]) } 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) + let (newReference, components, messages) = 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])) + XCTAssertEqual(messages, ["./schema.json#/components/schemas/test"]) } } diff --git a/Tests/OpenAPIKitTests/Document/ExternalDereferencingDocumentTests.swift b/Tests/OpenAPIKitTests/Document/ExternalDereferencingDocumentTests.swift index 35bf2429d..3aa95b8f0 100644 --- a/Tests/OpenAPIKitTests/Document/ExternalDereferencingDocumentTests.swift +++ b/Tests/OpenAPIKitTests/Document/ExternalDereferencingDocumentTests.swift @@ -14,7 +14,9 @@ final class ExternalDereferencingDocumentTests: XCTestCase { /// 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 { + typealias Message = String + + static func load(_ url: URL) async throws -> (T, [Message]) 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)) @@ -30,7 +32,7 @@ final class ExternalDereferencingDocumentTests: XCTestCase { } else { finished = decoded } - return finished + return (finished, [url.absoluteString]) } static func componentKey(type: T.Type, at url: URL) throws -> OpenAPIKit.OpenAPI.ComponentKey { @@ -111,6 +113,9 @@ final class ExternalDereferencingDocumentTests: XCTestCase { "$ref": "file://./headers/webhook.json" } } + }, + "enc2": { + "style": "form" } } } @@ -138,7 +143,7 @@ final class ExternalDereferencingDocumentTests: XCTestCase { }, "headers": { "X-Hello": { - "$ref": "file://./headers/webhook.json" + "$ref": "file://./headers/webhook2.json" } } } @@ -150,6 +155,17 @@ final class ExternalDereferencingDocumentTests: XCTestCase { } } """, + "headers_webhook2_json": """ + { + "content": { + "application/json": { + "schema": { + "$ref": "file://./schemas/string_param.json" + } + } + } + } + """, "examples_good_json": """ { "value": "{\\"body\\": \\"request me\\"}" @@ -236,17 +252,40 @@ final class ExternalDereferencingDocumentTests: XCTestCase { try await docCopy1.externallyDereference(with: 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(with: ExampleLoader.self, depth: 3) + try await docCopy2.externallyDereference(with: ExampleLoader.self, depth: 4) docCopy2.components.sort() var docCopy3 = document - try await docCopy3.externallyDereference(with: ExampleLoader.self, depth: .full) + let messages = try await docCopy3.externallyDereference(with: ExampleLoader.self, depth: .full) docCopy3.components.sort() XCTAssertEqual(docCopy1, docCopy2) XCTAssertEqual(docCopy2, docCopy3) + + XCTAssertEqual( + messages.sorted(), + ["file://./callbacks/one.json", + "file://./examples/good.json", + "file://./headers/webhook.json", + "file://./headers/webhook2.json", + "file://./links/first.json", + "file://./params/name.json", + "file://./params/name.json", + "file://./paths/callback.json", + "file://./paths/webhook.json", + "file://./paths/webhook.json", + "file://./requests/webhook.json", + "file://./responses/webhook.json", + "file://./schemas/basic_object.json", + "file://./schemas/string_param.json", + "file://./schemas/string_param.json", + "file://./schemas/string_param.json", + "file://./schemas/string_param.json", + "file://./schemas/string_param.json#"] + ) } } diff --git a/Tests/OpenAPIKitTests/JSONReferenceTests.swift b/Tests/OpenAPIKitTests/JSONReferenceTests.swift index c5e74f18f..4587d47cf 100644 --- a/Tests/OpenAPIKitTests/JSONReferenceTests.swift +++ b/Tests/OpenAPIKitTests/JSONReferenceTests.swift @@ -17,6 +17,8 @@ final class JSONReferenceTests: XCTestCase { XCTAssertEqual(t1, t2) XCTAssertTrue(t1.isInternal) XCTAssertFalse(t1.isExternal) + XCTAssertEqual(t1.internalValue, .init(rawValue: "#/hello")) + XCTAssertNil(t1.externalValue) let t3 = JSONReference.component(named: "hello") let t4 = JSONReference.internal(.component(name: "hello")) @@ -27,6 +29,8 @@ final class JSONReferenceTests: XCTestCase { let externalTest = JSONReference.external(URL(string: "hello.json")!) XCTAssertFalse(externalTest.isInternal) XCTAssertTrue(externalTest.isExternal) + XCTAssertNil(externalTest.internalValue) + XCTAssertEqual(externalTest.externalValue, URL(string: "hello.json")) let t5 = JSONReference.InternalReference("#/hello/world") let t6 = JSONReference.InternalReference(rawValue: "#/hello/world") @@ -169,6 +173,10 @@ final class JSONReferenceTests: XCTestCase { XCTAssertEqual(t7.openAPIReference(withDescription: "hi").description, "hi") XCTAssertEqual(t8.openAPIReference(withDescription: "hi").description, "hi") XCTAssertEqual(t9.openAPIReference(withDescription: "hi").description, "hi") + + // test dynamic member lookup: + XCTAssertEqual(t1.openAPIReference().internalValue, .component(name: "hello")) + } } @@ -382,28 +390,31 @@ 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) + let (newReference, components, messages) = try await reference.externallyDereferenced(with: SchemaLoader.self) XCTAssertEqual(newReference, .component(named: "__schema_json")) XCTAssertEqual(components, .init(schemas: ["__schema_json": .string])) + XCTAssertEqual(messages, ["./schema.json"]) } func test_externalDerefFragment() async throws { let reference: JSONReference = .external(.init(string: "./schema.json#/test")!) - let (newReference, components) = try await reference.externallyDereferenced(with: SchemaLoader.self) + let (newReference, components, messages) = try await reference.externallyDereferenced(with: SchemaLoader.self) XCTAssertEqual(newReference, .component(named: "__schema_json__test")) XCTAssertEqual(components, .init(schemas: ["__schema_json__test": .string])) + XCTAssertEqual(messages, ["./schema.json#/test"]) } 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) + let (newReference, components, messages) = 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])) + XCTAssertEqual(messages, ["./schema.json#/components/schemas/test"]) } } @@ -414,8 +425,8 @@ extension JSONReferenceTests { } struct SchemaLoader: ExternalLoader { - static func load(_ url: URL) -> T where T: Decodable { - return JSONSchema.string as! T + static func load(_ url: URL) -> (T, [String]) { + return (JSONSchema.string as! T, [url.absoluteString]) } static func componentKey(type: T.Type, at url: URL) throws -> OpenAPI.ComponentKey {