Skip to content

Commit

Permalink
update OAS 3.0 components merge function. fix tests after refactor.
Browse files Browse the repository at this point in the history
  • Loading branch information
mattpolzin committed Apr 24, 2024
1 parent e4a2b52 commit 0df6960
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 48 deletions.
51 changes: 40 additions & 11 deletions Sources/OpenAPIKit30/Components Object/Components.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,47 @@ extension OpenAPI.Components {
public let newComponent: String
}

private func detectCollision<T: Equatable>(type: String) throws -> (_ old: T, _ new: T) throws -> T {
return { old, new in
// theoretically we can detect collisions here, but we would need to compare
// for equality up-to but not including the difference between an external and
// internal reference which is not supported yet.
// if(old == new) { return old }
// throw ComponentCollision(componentType: type, existingComponent: String(describing:old), newComponent: String(describing:new))

// Given we aren't ensuring there are no collisions, the old version is going to be
// the one more likely to have been _further_ dereferenced than the new record, so
// we keep that version.
return old
}
}

public mutating func merge(_ other: OpenAPI.Components) throws {
try schemas.merge(other.schemas, uniquingKeysWith: { a, b in throw ComponentCollision(componentType: "schema", existingComponent: String(describing: a), newComponent: String(describing: b)) })
try responses.merge(other.responses, uniquingKeysWith: { a, b in throw ComponentCollision(componentType: "responses", existingComponent: String(describing: a), newComponent: String(describing: b)) })
try parameters.merge(other.parameters, uniquingKeysWith: { a, b in throw ComponentCollision(componentType: "parameters", existingComponent: String(describing: a), newComponent: String(describing: b)) })
try examples.merge(other.examples, uniquingKeysWith: { a, b in throw ComponentCollision(componentType: "examples", existingComponent: String(describing: a), newComponent: String(describing: b)) })
try requestBodies.merge(other.requestBodies, uniquingKeysWith: { a, b in throw ComponentCollision(componentType: "requestBodies", existingComponent: String(describing: a), newComponent: String(describing: b)) })
try headers.merge(other.headers, uniquingKeysWith: { a, b in throw ComponentCollision(componentType: "headers", existingComponent: String(describing: a), newComponent: String(describing: b)) })
try securitySchemes.merge(other.securitySchemes, uniquingKeysWith: { a, b in throw ComponentCollision(componentType: "securitySchemes", existingComponent: String(describing: a), newComponent: String(describing: b)) })
try links.merge(other.links, uniquingKeysWith: { a, b in throw ComponentCollision(componentType: "links", existingComponent: String(describing: a), newComponent: String(describing: b)) })
try callbacks.merge(other.callbacks, uniquingKeysWith: { a, b in throw ComponentCollision(componentType: "callbacks", existingComponent: String(describing: a), newComponent: String(describing: b)) })
try pathItems.merge(other.pathItems, uniquingKeysWith: { a, b in throw ComponentCollision(componentType: "pathItems", existingComponent: String(describing: a), newComponent: String(describing: b)) })
try vendorExtensions.merge(other.vendorExtensions, uniquingKeysWith: { a, b in throw ComponentCollision(componentType: "vendorExtensions", existingComponent: String(describing: a), newComponent: String(describing: b)) })
try schemas.merge(other.schemas, uniquingKeysWith: detectCollision(type: "schema"))
try responses.merge(other.responses, uniquingKeysWith: detectCollision(type: "responses"))
try parameters.merge(other.parameters, uniquingKeysWith: detectCollision(type: "parameters"))
try examples.merge(other.examples, uniquingKeysWith: detectCollision(type: "examples"))
try requestBodies.merge(other.requestBodies, uniquingKeysWith: detectCollision(type: "requestBodies"))
try headers.merge(other.headers, uniquingKeysWith: detectCollision(type: "headers"))
try securitySchemes.merge(other.securitySchemes, uniquingKeysWith: detectCollision(type: "securitySchemes"))
try links.merge(other.links, uniquingKeysWith: detectCollision(type: "links"))
try callbacks.merge(other.callbacks, uniquingKeysWith: detectCollision(type: "callbacks"))
try pathItems.merge(other.pathItems, uniquingKeysWith: detectCollision(type: "pathItems"))
try vendorExtensions.merge(other.vendorExtensions, uniquingKeysWith: detectCollision(type: "vendorExtensions"))
}

/// Sort the components within each type by the component key.
public mutating func sort() {
schemas.sortKeys()
responses.sortKeys()
parameters.sortKeys()
examples.sortKeys()
requestBodies.sortKeys()
headers.sortKeys()
securitySchemes.sortKeys()
links.sortKeys()
callbacks.sortKeys()
pathItems.sortKeys()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import Foundation
import Yams
import OpenAPIKit
import OpenAPIKit30
import XCTest

final class ExternalDereferencingDocumentTests: XCTestCase {
Expand All @@ -25,15 +25,17 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
// 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)
//TODO: revisit vendor extensions mutability
#warning("revisit vendor extensions mutability")
// extendable.vendorExtensions["x-source-url"] = AnyCodable(url)
finished = extendable as! T
} else {
finished = decoded
}
return finished
}

static func componentKey<T>(type: T.Type, at url: URL) throws -> OpenAPIKit.OpenAPI.ComponentKey {
static func componentKey<T>(type: T.Type, at url: URL) throws -> OpenAPIKit30.OpenAPI.ComponentKey {
// do anything you want here to determine what key the new component should be stored at.
// for the example, we will just transform the URL into a valid components key:
let urlString = url.pathComponents.dropFirst()
Expand All @@ -43,7 +45,7 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
}

/// Mock up some data, just for the example.
static func mockData(_ key: OpenAPIKit.OpenAPI.ComponentKey) async throws -> Data {
static func mockData(_ key: OpenAPIKit30.OpenAPI.ComponentKey) async throws -> Data {
return try XCTUnwrap(files[key.rawValue])
}

Expand Down Expand Up @@ -217,9 +219,6 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
),
"/webhook": .reference(.external(URL(string: "file://./paths/webhook.json")!))
],
webhooks: [
"webhook": .reference(.external(URL(string: "file://./paths/webhook.json")!))
],
components: .init(
schemas: [
"name_param": .reference(.external(URL(string: "file://./schemas/string_param.json")!))
Expand All @@ -233,17 +232,17 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
encoder.outputFormatting = .prettyPrinted

var docCopy1 = document
try await docCopy1.externallyDereference(in: ExampleLoader.self)
try await docCopy1.externallyDereference(in: ExampleLoader.self)
try await docCopy1.externallyDereference(in: ExampleLoader.self)
try await docCopy1.externallyDereference(with: ExampleLoader.self)
try await docCopy1.externallyDereference(with: ExampleLoader.self)
try await docCopy1.externallyDereference(with: ExampleLoader.self)
docCopy1.components.sort()

var docCopy2 = document
try await docCopy2.externallyDereference(in: ExampleLoader.self, depth: 3)
try await docCopy2.externallyDereference(with: ExampleLoader.self, depth: 3)
docCopy2.components.sort()

var docCopy3 = document
try await docCopy3.externallyDereference(in: ExampleLoader.self, depth: .full)
try await docCopy3.externallyDereference(with: ExampleLoader.self, depth: .full)
docCopy3.components.sort()

XCTAssertEqual(docCopy1, docCopy2)
Expand Down
33 changes: 13 additions & 20 deletions Tests/OpenAPIKit30Tests/JSONReferenceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,19 @@ extension JSONReferenceTests {
struct ReferenceWrapper: Codable, Equatable {
let reference: JSONReference<JSONSchema>
}

struct SchemaLoader: ExternalLoader {
static func load<T>(_ url: URL) -> T where T: Decodable {
return JSONSchema.string as! T
}

static func componentKey<T>(type: T.Type, at url: URL) throws -> OpenAPI.ComponentKey {
return try .forceInit(rawValue: url.absoluteString
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "#", with: "_")
.replacingOccurrences(of: ".", with: "_"))
}
}
}

// MARK: - External Dereferencing
Expand Down Expand Up @@ -370,23 +383,3 @@ extension JSONReferenceTests {
XCTAssertEqual(components, .init(schemas: ["__schema_json__components_schemas_test": .string]))
}
}

// MARK: - Test Types
extension JSONReferenceTests {
struct ReferenceWrapper: Codable, Equatable {
let reference: JSONReference<JSONSchema>
}

struct SchemaLoader: ExternalLoader {
static func load<T>(_ url: URL) -> T where T: Decodable {
return JSONSchema.string as! T
}

static func componentKey<T>(type: T.Type, at url: URL) throws -> OpenAPI.ComponentKey {
return try .forceInit(rawValue: url.absoluteString
.replacingOccurrences(of: "/", with: "_")
.replacingOccurrences(of: "#", with: "_")
.replacingOccurrences(of: ".", with: "_"))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -233,17 +233,17 @@ final class ExternalDereferencingDocumentTests: XCTestCase {
encoder.outputFormatting = .prettyPrinted

var docCopy1 = document
try await docCopy1.externallyDereference(in: ExampleLoader.self)
try await docCopy1.externallyDereference(in: ExampleLoader.self)
try await docCopy1.externallyDereference(in: ExampleLoader.self)
try await docCopy1.externallyDereference(with: ExampleLoader.self)
try await docCopy1.externallyDereference(with: ExampleLoader.self)
try await docCopy1.externallyDereference(with: ExampleLoader.self)
docCopy1.components.sort()

var docCopy2 = document
try await docCopy2.externallyDereference(in: ExampleLoader.self, depth: 3)
try await docCopy2.externallyDereference(with: ExampleLoader.self, depth: 3)
docCopy2.components.sort()

var docCopy3 = document
try await docCopy3.externallyDereference(in: ExampleLoader.self, depth: .full)
try await docCopy3.externallyDereference(with: ExampleLoader.self, depth: .full)
docCopy3.components.sort()

XCTAssertEqual(docCopy1, docCopy2)
Expand Down

0 comments on commit 0df6960

Please sign in to comment.