From d0e9fb557fe9121eb7121e6ae62600f4bad46d77 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Mon, 9 Mar 2026 15:02:22 -0500 Subject: [PATCH 1/2] Add some conveniences for working with references to JSON Schemas --- .../Components+JSONReference.swift | 10 ++++++ Sources/OpenAPIKit/JSONReference.swift | 36 +++++++++++++++++++ .../OpenAPIKitTests/JSONReferenceTests.swift | 15 ++++++++ 3 files changed, 61 insertions(+) diff --git a/Sources/OpenAPIKit/Components Object/Components+JSONReference.swift b/Sources/OpenAPIKit/Components Object/Components+JSONReference.swift index 5a10f642d6..e21d933aa8 100644 --- a/Sources/OpenAPIKit/Components Object/Components+JSONReference.swift +++ b/Sources/OpenAPIKit/Components Object/Components+JSONReference.swift @@ -230,6 +230,11 @@ extension OpenAPI.Components { /// If you want to look something up without throwing, you might want to use the subscript /// operator on the `Components`. /// + /// If you are recursing through JSONSchema references, you may want to + /// call `flattenToJsonSchema()` on the result of `lookupOnce()` because + /// `lookupOnce()` will wrap any resulting `JSONSchema` reference in an + /// `OpenAPI.Reference` which adds work if all you want to do is recurse. + /// /// If you also want to fully dereference the value in question instead /// of just looking it up see the various `dereference` functions /// on this type for more information. @@ -287,6 +292,11 @@ extension OpenAPI.Components { /// If you want to look something up without throwing, you might want to use the subscript /// operator on the `Components`. /// + /// If you are recursing through JSONSchema references, you may want to + /// call `flattenToJsonSchema()` on the result of `lookupOnce()` because + /// `lookupOnce()` will wrap any resulting `JSONSchema` reference in an + /// `OpenAPI.Reference` which adds work if all you want to do is recurse. + /// /// If you also want to fully dereference the value in question instead /// of just looking it up see the various `dereference` functions /// on this type for more information. diff --git a/Sources/OpenAPIKit/JSONReference.swift b/Sources/OpenAPIKit/JSONReference.swift index e0207c5239..d89de99599 100644 --- a/Sources/OpenAPIKit/JSONReference.swift +++ b/Sources/OpenAPIKit/JSONReference.swift @@ -387,6 +387,42 @@ extension OpenAPI { } } +public extension OpenAPI.Reference where ReferenceType == JSONSchema { + /// When an OpenAPI.Reference points at a JSONSchema, it can be represented + /// as a JSONSchema directly because JSONSchema has an innate ability to + /// represent references itself. + /// + /// Example: + /// + /// OpenAPI.Reference.component(named: "hello").asJsonSchema() + /// // results in JSONSchema.reference(.component(named: "hello")) + func asJsonSchema() -> JSONSchema { + .reference(jsonReference) + .overriddenNonNil(summary: summary) + .overriddenNonNil(description: description) + } +} + +public extension Either where A == OpenAPI.Reference, B == JSONSchema { + /// When an Either represents a reference or a schema, it can be + /// "flattened" to a JSONSchema because JSONSchema has an innate ability to + /// represent references itself. + /// + /// Examples: + /// + /// Either, JSONSchema>.a(.component(named: "hello")).flattenToJsonSchema() + /// // results in JSONSchema.reference(.component(named: "hello")) + /// + /// Either, JSONSchema>.b(.string).flattenToJsonSchema() + /// // results in JSONSchema.string + func flattenToJsonSchema() -> JSONSchema { + switch self { + case .a(let reference): reference.asJsonSchema() + case .b(let schema): schema + } + } +} + public extension JSONReference { /// Create an OpenAPI.Reference from the given JSONReference. func openAPIReference(withDescription description: String? = nil) -> OpenAPI.Reference { diff --git a/Tests/OpenAPIKitTests/JSONReferenceTests.swift b/Tests/OpenAPIKitTests/JSONReferenceTests.swift index 7ee1f98852..c033008a0f 100644 --- a/Tests/OpenAPIKitTests/JSONReferenceTests.swift +++ b/Tests/OpenAPIKitTests/JSONReferenceTests.swift @@ -180,6 +180,21 @@ final class JSONReferenceTests: XCTestCase { XCTAssertEqual(t1.openAPIReference().internalValue, .component(name: "hello")) } + + func test_asJsonSchema() { + XCTAssertEqual( + OpenAPI.Reference.component(named: "hello").asJsonSchema(), + JSONSchema.reference(.component(named: "hello")) + ) + XCTAssertEqual( + Either, JSONSchema>.a(.component(named: "hello")).flattenToJsonSchema(), + JSONSchema.reference(.component(named: "hello")) + ) + XCTAssertEqual( + Either, JSONSchema>.b(.string).flattenToJsonSchema(), + .string + ) + } } // MARK: Codable From 675a776d302deac2c353bce0ce5276a3b6314970 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Mon, 9 Mar 2026 15:31:10 -0500 Subject: [PATCH 2/2] add test coverage of OpenAPI.Reference overridable stuff --- Tests/OpenAPIKitTests/JSONReferenceTests.swift | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Tests/OpenAPIKitTests/JSONReferenceTests.swift b/Tests/OpenAPIKitTests/JSONReferenceTests.swift index c033008a0f..fc0f5447d7 100644 --- a/Tests/OpenAPIKitTests/JSONReferenceTests.swift +++ b/Tests/OpenAPIKitTests/JSONReferenceTests.swift @@ -178,7 +178,21 @@ final class JSONReferenceTests: XCTestCase { // test dynamic member lookup: XCTAssertEqual(t1.openAPIReference().internalValue, .component(name: "hello")) + } + func test_openAPIRefOverridable() { + XCTAssertEqual( + OpenAPI.Reference.component(named: "hello").overriddenNonNil(summary: "hi").overriddenNonNil(description: "hello there"), + .component(named: "hello", summary: "hi", description: "hello there") + ) + XCTAssertEqual( + OpenAPI.Reference.component(named: "hello", summary: "hi").overriddenNonNil(summary: nil), + .component(named: "hello", summary: "hi") + ) + XCTAssertEqual( + OpenAPI.Reference.component(named: "hello", description: "hello there").overriddenNonNil(description: nil), + .component(named: "hello", description: "hello there") + ) } func test_asJsonSchema() {