From 70f60d043e24bb2f5ecb894ac1afb2685dc8e597 Mon Sep 17 00:00:00 2001 From: Anton Titkov Date: Thu, 23 Oct 2025 03:47:57 +0300 Subject: [PATCH 01/20] Bump OpenAPIKit version --- Package.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index f2705fef1..cdb703d6d 100644 --- a/Package.swift +++ b/Package.swift @@ -45,8 +45,8 @@ let package = Package( .package(url: "https://github.com/apple/swift-collections", from: "1.1.4"), // Read OpenAPI documents - .package(url: "https://github.com/mattpolzin/OpenAPIKit", from: "3.9.0"), - .package(url: "https://github.com/jpsim/Yams", "4.0.0"..<"7.0.0"), + .package(url: "https://github.com/mattpolzin/OpenAPIKit", from: "4.0.0"), + .package(url: "https://github.com/jpsim/Yams", "5.1.0"..<"7.0.0"), // CLI Tool .package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"), From 29b994e9abd5b5adecc0cf2af0d2f1ed0e38063c Mon Sep 17 00:00:00 2001 From: Anton Titkov Date: Thu, 23 Oct 2025 03:47:57 +0300 Subject: [PATCH 02/20] Update OpenAPI.Content.Encoding usage according to OpenAPIKit v4 Migration Guide --- .../Translator/TypeAssignment/Test_TypeMatcher.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeMatcher.swift b/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeMatcher.swift index ec8aafc77..45e5944e6 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeMatcher.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeMatcher.swift @@ -245,7 +245,7 @@ final class Test_TypeMatcher: Test_Core { static let multipartElementTypeReferenceIfReferenceableTypes: [(UnresolvedSchema?, OrderedDictionary?, String?)] = [ (nil, nil, nil), (.b(.string), nil, nil), (.a(.component(named: "Foo")), nil, "Foo"), - (.a(.component(named: "Foo")), ["foo": .init(contentType: .json)], nil), + (.a(.component(named: "Foo")), ["foo": .init(contentTypes: [.json])], nil), ] func testMultipartElementTypeReferenceIfReferenceableTypes() throws { for (schema, encoding, name) in Self.multipartElementTypeReferenceIfReferenceableTypes { From 591125057281d4415c267d4942c171182dd1b6e8 Mon Sep 17 00:00:00 2001 From: Anton Titkov Date: Thu, 23 Oct 2025 03:47:57 +0300 Subject: [PATCH 03/20] Update wording for `testEmitsComplexOpenAPIParsingError` and `testSchemaWarningsForwardedToGeneratorDiagnostics` --- Tests/OpenAPIGeneratorCoreTests/Parser/Test_YamsParser.swift | 2 +- .../Translator/TypesTranslator/Test_translateSchemas.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/OpenAPIGeneratorCoreTests/Parser/Test_YamsParser.swift b/Tests/OpenAPIGeneratorCoreTests/Parser/Test_YamsParser.swift index 46bcd0294..71939e14d 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Parser/Test_YamsParser.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Parser/Test_YamsParser.swift @@ -126,7 +126,7 @@ final class Test_YamsParser: Test_Core { /foo.yaml: error: Found neither a $ref nor a PathItem in Document.paths['/system']. PathItem could not be decoded because: - Inconsistency encountered when parsing `Vendor Extension` for the **GET** endpoint under `/system`: Found at least one vendor extension property that does not begin with the required 'x-' prefix. Invalid properties: [ resonance ].. + Problem encountered when parsing `Vendor Extension` for the **GET** endpoint under `/system`: Found at least one vendor extension property that does not begin with the required 'x-' prefix. Invalid properties: [ resonance ].. """ assertThrownError(try _test(yaml), expectedDiagnostic: expected) } diff --git a/Tests/OpenAPIGeneratorCoreTests/Translator/TypesTranslator/Test_translateSchemas.swift b/Tests/OpenAPIGeneratorCoreTests/Translator/TypesTranslator/Test_translateSchemas.swift index 1d1e89f52..ee25c10f9 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Translator/TypesTranslator/Test_translateSchemas.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Translator/TypesTranslator/Test_translateSchemas.swift @@ -35,7 +35,7 @@ class Test_translateSchemas: Test_Core { ( schemaWithWarnings, [ - "warning: Schema warning: Inconsistency encountered when parsing `OpenAPI Schema`: Found schema attributes not consistent with the type specified: string. Specifically, attributes for these other types: [\"array\"]. [context: codingPath=, contextString=, subjectName=OpenAPI Schema]" + "warning: Schema warning: Problem encountered when parsing `OpenAPI Schema`: Found schema attributes not consistent with the type specified: string. Specifically, attributes for these other types: [\"array\"]. [context: codingPath=, contextString=, subjectName=OpenAPI Schema]" ] ), ] From 62fe491051904970723b98e4831a34fbe8c30664 Mon Sep 17 00:00:00 2001 From: Anton Titkov Date: Thu, 23 Oct 2025 03:47:57 +0300 Subject: [PATCH 04/20] Update checks for null values --- .../Translator/CommonTranslations/translateRawEnum.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateRawEnum.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateRawEnum.swift index 737abe571..e1b984ebc 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateRawEnum.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateRawEnum.swift @@ -100,7 +100,9 @@ extension FileTranslator { // In nullable enum schemas, empty strings are parsed as Void. // This is unlikely to be fixed, so handling that case here. // https://github.com/apple/swift-openapi-generator/issues/118 - if isNullable && anyValue is Void { + // Also handle nil values in nullable schemas. + let isNullValue = anyValue is Void || (anyValue as? String) == nil + if isNullable && isNullValue { try addIfUnique(id: .string(""), caseName: context.safeNameGenerator.swiftMemberName(for: "")) } else { guard let rawValue = anyValue as? String else { From 0aa0187e4c62281b7cd8eda8321183ca695977fe Mon Sep 17 00:00:00 2001 From: Anton Titkov Date: Tue, 28 Oct 2025 16:39:01 +0300 Subject: [PATCH 05/20] Bump OpenAPIKit to 4.3.1 or greater --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index cdb703d6d..7020b96ba 100644 --- a/Package.swift +++ b/Package.swift @@ -45,7 +45,7 @@ let package = Package( .package(url: "https://github.com/apple/swift-collections", from: "1.1.4"), // Read OpenAPI documents - .package(url: "https://github.com/mattpolzin/OpenAPIKit", from: "4.0.0"), + .package(url: "https://github.com/mattpolzin/OpenAPIKit", from: "4.3.1"), .package(url: "https://github.com/jpsim/Yams", "5.1.0"..<"7.0.0"), // CLI Tool From acada4dacbc9c6362bac99dab9a051552b5a3a99 Mon Sep 17 00:00:00 2001 From: Anton Titkov Date: Fri, 27 Mar 2026 17:56:28 +0300 Subject: [PATCH 06/20] Fix "Compatibility test" CI suite Co-authored-by: Mathew Polzin --- .../CompatabilityTest.swift | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/Tests/OpenAPIGeneratorReferenceTests/CompatabilityTest.swift b/Tests/OpenAPIGeneratorReferenceTests/CompatabilityTest.swift index b99a4c89d..62cd19ada 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/CompatabilityTest.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/CompatabilityTest.swift @@ -88,7 +88,7 @@ final class CompatibilityTest: XCTestCase { "https://raw.githubusercontent.com/discourse/discourse_api_docs/8182f1b21ca62cc9ac85fd3a82cae8304033a672/openapi.yml", license: .apache, expectedDiagnostics: [ - "Validation warning: Inconsistency encountered when parsing `OpenAPI Schema`: Found nothing but unsupported attributes..", + "Validation warning: Problem encountered when parsing `OpenAPI Schema`: Found nothing but unsupported attributes..", "Multipart request bodies must always be required, but found an optional one - skipping. Mark as `required: true` to get this body generated.", ], skipBuild: compatibilityTestSkipBuild @@ -100,15 +100,15 @@ final class CompatibilityTest: XCTestCase { "https://raw.githubusercontent.com/github/rest-api-description/322663c9c909974af16363b4dc0873c428bdbe34/descriptions-next/api.github.com/api.github.com.yaml", license: .mit, expectedDiagnostics: [ - "Validation warning: Inconsistency encountered when parsing `OpenAPI Schema`: Found schema attributes not consistent with the type specified: array. Specifically, attributes for these other types: [\"object\"].", - "Validation warning: Inconsistency encountered when parsing `OpenAPI Schema`: Found schema attributes not consistent with the type specified: string. Specifically, attributes for these other types: [\"object\"].", - "Validation warning: Inconsistency encountered when parsing `OpenAPI Schema`: Found nothing but unsupported attributes..", - "Schema warning: Inconsistency encountered when parsing `OpenAPI Schema`: Found schema attributes not consistent with the type specified: string. Specifically, attributes for these other types: [\"array\"].", - "Validation warning: Inconsistency encountered when parsing `Schema`: A schema contains properties for multiple types of schemas, namely: [\"array\", \"object\"]..", - "Validation warning: Inconsistency encountered when parsing `OpenAPI Schema`: Found schema attributes not consistent with the type specified: string. Specifically, attributes for these other types: [\"array\"].", + "Validation warning: Problem encountered when parsing `OpenAPI Schema`: Found schema attributes not consistent with the type specified: array. Specifically, attributes for these other types: [\"object\"].", + "Validation warning: Problem encountered when parsing `OpenAPI Schema`: Found schema attributes not consistent with the type specified: string. Specifically, attributes for these other types: [\"object\"].", + "Validation warning: Problem encountered when parsing `OpenAPI Schema`: Found nothing but unsupported attributes..", + "Schema warning: Problem encountered when parsing `OpenAPI Schema`: Found schema attributes not consistent with the type specified: string. Specifically, attributes for these other types: [\"array\"].", + "Validation warning: Problem encountered when parsing `Schema`: A schema contains properties for multiple types of schemas, namely: [\"array\", \"object\"]..", + "Validation warning: Problem encountered when parsing `OpenAPI Schema`: Found schema attributes not consistent with the type specified: string. Specifically, attributes for these other types: [\"array\"].", "A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property.", - "Schema warning: Inconsistency encountered when parsing `OpenAPI Schema`: Found schema attributes not consistent with the type specified: array. Specifically, attributes for these other types: [\"object\"].", - "Schema warning: Inconsistency encountered when parsing `Schema`: A schema contains properties for multiple types of schemas, namely: [\"array\", \"object\"]..", + "Schema warning: Problem encountered when parsing `OpenAPI Schema`: Found schema attributes not consistent with the type specified: array. Specifically, attributes for these other types: [\"object\"].", + "Schema warning: Problem encountered when parsing `Schema`: A schema contains properties for multiple types of schemas, namely: [\"array\", \"object\"]..", "Schema \"null\" is not supported, reason: \"schema type\", skipping", ], skipBuild: true @@ -121,11 +121,11 @@ final class CompatibilityTest: XCTestCase { license: .mit, expectedDiagnostics: [ "Multipart request bodies must always be required, but found an optional one - skipping. Mark as `required: true` to get this body generated.", - "Validation warning: Inconsistency encountered when parsing `OpenAPI Schema`: Found nothing but unsupported attributes..", - "Validation warning: Inconsistency encountered when parsing `OpenAPI Schema`: Found schema attributes not consistent with the type specified: string. Specifically, attributes for these other types: [\"array\"].", + "Validation warning: Problem encountered when parsing `OpenAPI Schema`: Found nothing but unsupported attributes..", + "Validation warning: Problem encountered when parsing `OpenAPI Schema`: Found schema attributes not consistent with the type specified: string. Specifically, attributes for these other types: [\"array\"].", "A property name only appears in the required list, but not in the properties map - this is likely a typo; skipping this property.", - "Validation warning: Inconsistency encountered when parsing `OpenAPI Schema`: Found schema attributes not consistent with the type specified: string. Specifically, attributes for these other types: [\"object\"].", - "Schema warning: Inconsistency encountered when parsing `OpenAPI Schema`: Found schema attributes not consistent with the type specified: string. Specifically, attributes for these other types: [\"array\"].", + "Validation warning: Problem encountered when parsing `OpenAPI Schema`: Found schema attributes not consistent with the type specified: string. Specifically, attributes for these other types: [\"object\"].", + "Schema warning: Problem encountered when parsing `OpenAPI Schema`: Found schema attributes not consistent with the type specified: string. Specifically, attributes for these other types: [\"array\"].", "Schema \"null\" is not supported, reason: \"schema type\", skipping", ], skipBuild: true From dfec3ee6edd8fef598bce29ec096884284007cf8 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Mon, 9 Mar 2026 16:06:43 -0500 Subject: [PATCH 07/20] Update OpenAPIKit to v5 --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index be9b65cf6..5ccea35fa 100644 --- a/Package.swift +++ b/Package.swift @@ -46,7 +46,7 @@ let package = Package( .package(url: "https://github.com/apple/swift-collections", from: "1.1.4"), // Read OpenAPI documents - .package(url: "https://github.com/mattpolzin/OpenAPIKit", from: "4.3.1"), + .package(url: "https://github.com/mattpolzin/OpenAPIKit", from: "5.1.1"), .package(url: "https://github.com/jpsim/Yams", "5.1.0"..<"7.0.0"), // CLI Tool From eec4ef06d9b8a0338760a027907561c0228c368d Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Mon, 9 Mar 2026 16:06:27 -0500 Subject: [PATCH 08/20] remove pre-support for v3.2.0 OpenAPI Documents. these are now supported natively by OpenAPIKit. --- .../_OpenAPIGeneratorCore/Parser/YamsParser.swift | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Parser/YamsParser.swift b/Sources/_OpenAPIGeneratorCore/Parser/YamsParser.swift index ac646b56b..909507b8d 100644 --- a/Sources/_OpenAPIGeneratorCore/Parser/YamsParser.swift +++ b/Sources/_OpenAPIGeneratorCore/Parser/YamsParser.swift @@ -54,13 +54,6 @@ public struct YamsParser: ParserProtocol { let decoder = YAMLDecoder() let openapiData = input.contents - let decodingOptions = [ - DocumentConfiguration.versionMapKey: [ - // Until we move to OpenAPIKit v5.0+ we will parse OAS 3.2.0 as if it were OAS 3.1.2 - "3.2.0": OpenAPI.Document.Version.v3_1_2 - ] - ] - struct OpenAPIVersionedDocument: Decodable { var openapi: String? } let versionedDocument: OpenAPIVersionedDocument @@ -83,11 +76,7 @@ public struct YamsParser: ParserProtocol { case "3.1.0", "3.1.1", "3.1.2": document = try decoder.decode(OpenAPIKit.OpenAPI.Document.self, from: input.contents) case "3.2.0": - document = try decoder.decode( - OpenAPIKit.OpenAPI.Document.self, - from: input.contents, - userInfo: decodingOptions - ) + document = try decoder.decode(OpenAPIKit.OpenAPI.Document.self, from: input.contents) default: throw Diagnostic.openAPIVersionError( versionString: "openapi: \(openAPIVersion)", From 6220411ec1341571555c9a957de932f9a5342772 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Mon, 9 Mar 2026 10:29:47 -0500 Subject: [PATCH 09/20] replace content.encoding with content.encodingMap to continue handling OAS documents prior to 3.2.0 in the same manner for now --- Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift | 2 +- .../Translator/Content/ContentInspector.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift b/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift index 82b32d88d..ca0b349d5 100644 --- a/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift +++ b/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift @@ -386,7 +386,7 @@ private extension FilteredDocumentBuilder { mutating func includeComponentsReferencedBy(_ content: OpenAPI.Content) throws { if let schema = content.schema { try includeSchema(schema) } - if let encoding = content.encoding { + if let encoding = content.encodingMap { for encoding in encoding.values { if let headers = encoding.headers { for header in headers.values { try includeHeader(header) } } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift b/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift index cc4cf8fc2..1806c75e5 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift @@ -201,7 +201,7 @@ extension FileTranslator { ) return nil } - return .init(contentType: contentType, schema: contentValue.schema, encoding: contentValue.encoding) + return .init(contentType: contentType, schema: contentValue.schema, encoding: contentValue.encodingMap) } if !excludeBinary, contentType.isBinary { return .init(contentType: contentType, schema: .b(.string(contentEncoding: .binary))) From 2436218d91f44a630634b3a9724d5b260543b052 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Mon, 9 Mar 2026 10:43:51 -0500 Subject: [PATCH 10/20] fix exhaustive checks on parameter location --- .../Translator/Parameters/TypedParameter.swift | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift b/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift index 7ac1f18fc..4498dba4f 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift @@ -151,6 +151,9 @@ extension FileTranslator { case .cookie: try diagnostics.emitUnsupported("Cookie params", foundIn: foundIn) return nil + case .querystring: + try diagnostics.emitUnsupported("QueryString params", foundIn: foundIn) + return nil } case let .b(contentMap): @@ -165,13 +168,12 @@ extension FileTranslator { codingStrategy = typedContent.content.contentType.codingStrategy // Defaults are defined by the OpenAPI specification: - // https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-10 - switch parameter.location { - case .query, .cookie: - style = .form + // https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.2.0.md#fixed-fields-10 + style = OpenAPI.Parameter.SchemaContext.Style.default(for: parameter.location) + switch style { + case .form: explode = true - case .path, .header: - style = .simple + default: explode = false } } @@ -221,6 +223,7 @@ extension OpenAPI.Parameter.Context.Location { case .header: return "Headers" case .query: return "Query" case .cookie: return "Cookies" + case .querystring: return "QueryString" } } @@ -236,6 +239,7 @@ extension OpenAPI.Parameter.Context.Location { case .header: return "headers" case .query: return "query" case .cookie: return "cookies" + case .querystring: return "querystring" } } } From 7d7527c3ab7558c2fe9d446d4befaf67bb0b9289 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Mon, 9 Mar 2026 11:35:21 -0500 Subject: [PATCH 11/20] fix Content and Content Map code --- .../Hooks/FilteredDocument.swift | 23 +++++++------ .../Parser/validateDoc.swift | 33 ++++++++++++------- .../Translator/Content/ContentInspector.swift | 16 +++++---- .../Multipart/MultipartContentInspector.swift | 7 +++- 4 files changed, 49 insertions(+), 30 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift b/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift index ca0b349d5..a42db63dd 100644 --- a/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift +++ b/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift @@ -366,16 +366,19 @@ private extension FilteredDocumentBuilder { case .b(let schema): try includeComponentsReferencedBy(schema) } case .b(let contentMap): - for value in contentMap.values { - switch value.schema { - case .a(let reference): - guard try requiredSchemaReferences.insert(reference.internalComponentKey).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) - case .b(let schema): try includeComponentsReferencedBy(schema) - case .none: continue - } - } + for value in contentMap.values { try includeComponentsReferencedBy(value) } + } + } + + mutating func includeComponentsReferencedBy( + _ contentMapEntry: Either, OpenAPI.Content> + ) throws { + let content: OpenAPI.Content + switch contentMapEntry { + case .a(let ref): content = try document.components.lookup(ref) + case .b(let value): content = value } + try includeComponentsReferencedBy(content) } mutating func includeComponentsReferencedBy(_ response: OpenAPI.Response) throws { @@ -385,7 +388,7 @@ private extension FilteredDocumentBuilder { } mutating func includeComponentsReferencedBy(_ content: OpenAPI.Content) throws { - if let schema = content.schema { try includeSchema(schema) } + if let schema = content.schema { try includeComponentsReferencedBy(schema) } if let encoding = content.encodingMap { for encoding in encoding.values { if let headers = encoding.headers { for header in headers.values { try includeHeader(header) } } diff --git a/Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift b/Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift index 8b83a9505..65c949cd0 100644 --- a/Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift +++ b/Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift @@ -124,21 +124,30 @@ func validateReferences(in doc: ParsedOpenAPIRepresentation) throws { func validateReferencesInContentTypes(_ content: OpenAPI.Content.Map, location: String) throws { for (contentKey, contentType) in content { - if let reference = contentType.schema?.reference { + switch contentType { + case .a(let ref): try validateReference( - reference, + ref, in: doc.components, - location: location + "/content/\(contentKey.rawValue)/schema" + location: location + "/content/\(contentKey.rawValue)" ) - } - if let eitherExamples = contentType.examples?.values { - for example in eitherExamples { - if let reference = example.reference { - try validateReference( - reference, - in: doc.components, - location: location + "/content/\(contentKey.rawValue)/examples" - ) + case .b(let contentType): + if let reference: JSONReference = contentType.schema?.reference { + try validateReference( + .init(reference), + in: doc.components, + location: location + "/content/\(contentKey.rawValue)/schema" + ) + } + if let eitherExamples = contentType.examples?.values { + for example in eitherExamples { + if let reference = example.reference { + try validateReference( + reference, + in: doc.components, + location: location + "/content/\(contentKey.rawValue)/examples" + ) + } } } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift b/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift index 1806c75e5..571aec686 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift @@ -97,7 +97,7 @@ extension FileTranslator { return try contents.compactMap { key, value in try parseContentIfSupported( contentKey: key, - contentValue: value, + contentValue: try components.lookup(value), excludeBinary: excludeBinary, isRequired: isRequired, foundIn: foundIn + "/\(key.rawValue)" @@ -129,12 +129,14 @@ extension FileTranslator { let chosenContent: (type: ContentType, schema: SchemaContent, content: OpenAPI.Content)? if let (contentType, contentValue) = mapWithContentTypes.first(where: { $0.type.isJSON }) { - chosenContent = (contentType, .init(contentType: contentType, schema: contentValue.schema), contentValue) + let contentValue = try components.lookup(contentValue) + chosenContent = (contentType, .init(contentType: contentType, schema: contentValue.schema.map(Either.schema)), contentValue) } else if !excludeBinary, let (contentType, contentValue) = mapWithContentTypes.first(where: { $0.type.isBinary }) { + let contentValue = try components.lookup(contentValue) chosenContent = ( - contentType, .init(contentType: contentType, schema: .b(.string(contentEncoding: .binary))), + contentType, .init(contentType: contentType, schema: .schema(.string(contentEncoding: .binary))), contentValue ) } else { @@ -188,8 +190,8 @@ extension FileTranslator { foundIn: "\(foundIn), content \(contentType.originallyCasedTypeAndSubtype)" ) } - if contentType.isJSON { return .init(contentType: contentType, schema: contentValue.schema) } - if contentType.isUrlEncodedForm { return .init(contentType: contentType, schema: contentValue.schema) } + if contentType.isJSON { return .init(contentType: contentType, schema: contentValue.schema.map(Either.schema)) } + if contentType.isUrlEncodedForm { return .init(contentType: contentType, schema: contentValue.schema.map(Either.schema)) } if contentType.isMultipart { guard isRequired else { try diagnostics.emit( @@ -201,10 +203,10 @@ extension FileTranslator { ) return nil } - return .init(contentType: contentType, schema: contentValue.schema, encoding: contentValue.encodingMap) + return .init(contentType: contentType, schema: contentValue.schema.map(Either.schema), encoding: contentValue.encodingMap) } if !excludeBinary, contentType.isBinary { - return .init(contentType: contentType, schema: .b(.string(contentEncoding: .binary))) + return .init(contentType: contentType, schema: .schema(.string(contentEncoding: .binary))) } try diagnostics.emitUnsupported("Unsupported content", foundIn: foundIn) return nil diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift b/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift index 19dcf61f3..77abc1a93 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift @@ -365,7 +365,12 @@ extension FileTranslator { func visitContentMap(_ contentMap: OpenAPI.Content.Map) throws { for (key, value) in contentMap { guard try key.asGeneratorContentType.isMultipart else { continue } - guard let schema = value.schema, case let .a(ref) = schema, let name = ref.name, + let content: OpenAPI.Content + switch value { + case .a(let ref): content = try components.lookup(ref) + case .b(let value): content = value + } + guard let ref = content.schema?.reference, let name = ref.name, let componentKey = OpenAPI.ComponentKey(rawValue: name) else { continue } refs.insert(componentKey) From a389607272aa1073bbeb6f396995e384d2c52e9d Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Mon, 9 Mar 2026 12:36:45 -0500 Subject: [PATCH 12/20] fix component entry lookup --- .../Hooks/FilteredDocument.swift | 13 ++----------- .../_OpenAPIGeneratorCore/Parser/validateDoc.swift | 2 ++ .../TypesTranslator/translateComponents.swift | 12 ++++++++---- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift b/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift index a42db63dd..7760580d3 100644 --- a/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift +++ b/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift @@ -169,7 +169,7 @@ struct FilteredDocumentBuilder { /// - Parameter name: The key in the `#/components/schemas` map in the OpenAPI document. /// - Throws: If the named schema does not exist in original OpenAPI document. mutating func includeSchema(_ name: String) throws { - try includeSchema(.a(OpenAPI.Reference.component(named: name))) + try includeComponentsReferencedBy(.reference(.component(named: name))) } } @@ -203,15 +203,6 @@ private extension FilteredDocumentBuilder { } } - mutating func includeSchema(_ maybeReference: Either, JSONSchema>) throws { - switch maybeReference { - case .a(let reference): - guard try requiredSchemaReferences.insert(reference.internalComponentKey).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) - case .b(let value): try includeComponentsReferencedBy(value) - } - } - mutating func includeParameter(_ maybeReference: Either, OpenAPI.Parameter>) throws { @@ -330,7 +321,7 @@ private extension FilteredDocumentBuilder { case .reference(let reference, _): let referenceKey = try OpenAPI.ComponentKey(stringLiteral: reference.requiredName) guard requiredSchemaReferences.insert(referenceKey).inserted else { return } - try includeComponentsReferencedBy(document.components.lookup(reference)) + try includeComponentsReferencedBy(document.components.lookupOnce(reference).flattenToJsonSchema()) case .object(_, let object): for schema in object.properties.values { try includeComponentsReferencedBy(schema) } diff --git a/Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift b/Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift index 65c949cd0..3ed8b9c24 100644 --- a/Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift +++ b/Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift @@ -67,6 +67,7 @@ func validateContentTypes(in doc: ParsedOpenAPIRepresentation, validate: (String } for (key, component) in doc.components.requestBodies { + let component = try doc.components.lookup(component) for contentType in component.content.keys { if !validate(contentType.rawValue) { throw Diagnostic.error( @@ -81,6 +82,7 @@ func validateContentTypes(in doc: ParsedOpenAPIRepresentation, validate: (String } for (key, component) in doc.components.responses { + let component = try doc.components.lookup(component) for contentType in component.content.keys { if !validate(contentType.rawValue) { throw Diagnostic.error( diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateComponents.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateComponents.swift index 54a1b6b9f..63f1b5033 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateComponents.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateComponents.swift @@ -29,10 +29,14 @@ extension TypesFileTranslator { { let schemas = try translateSchemas(components.schemas, multipartSchemaNames: multipartSchemaNames) - let parameters = try translateComponentParameters(components.parameters) - let requestBodies = try translateComponentRequestBodies(components.requestBodies) - let responses = try translateComponentResponses(components.responses) - let headers = try translateComponentHeaders(components.headers) + let resolvedParameters = try components.parameters.mapValues { try components.lookup($0) } + let parameters = try translateComponentParameters(resolvedParameters) + let resolvedRequestBodies = try components.requestBodies.mapValues { try components.lookup($0) } + let requestBodies = try translateComponentRequestBodies(resolvedRequestBodies) + let resolvedResponses = try components.responses.mapValues { try components.lookup($0) } + let responses = try translateComponentResponses(resolvedResponses) + let resolvedHeaders = try components.headers.mapValues { try components.lookup($0) } + let headers = try translateComponentHeaders(resolvedHeaders) let componentsDecl: Declaration = .commentable( .doc( From 9c9d5af943d727d733052590e1da066ee7d9d436 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Tue, 10 Mar 2026 09:34:54 -0500 Subject: [PATCH 13/20] introduce assumeLookupOnce temporarily to maintain existing invariant that no Components Objects entries are references --- .../Extensions/OpenAPIKit.swift | 26 ++++++++++++++++++- .../Hooks/FilteredDocument.swift | 24 ++++++++--------- .../Parser/validateDoc.swift | 4 +-- .../Translator/Content/ContentInspector.swift | 6 ++--- .../Multipart/MultipartContentInspector.swift | 12 ++++----- .../Parameters/TypedParameter.swift | 2 +- .../RequestBody/TypedRequestBody.swift | 2 +- .../Translator/Responses/TypedResponse.swift | 2 +- .../Responses/TypedResponseHeader.swift | 2 +- .../TypeAssignment/TypeAssigner.swift | 6 +++++ .../TypeAssignment/TypeMatcher.swift | 8 +++--- .../TypeAssignment/isSchemaSupported.swift | 8 +++--- .../TypesTranslator/translateComponents.swift | 8 +++--- 13 files changed, 70 insertions(+), 40 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Extensions/OpenAPIKit.swift b/Sources/_OpenAPIGeneratorCore/Extensions/OpenAPIKit.swift index ea055e6bb..76879870e 100644 --- a/Sources/_OpenAPIGeneratorCore/Extensions/OpenAPIKit.swift +++ b/Sources/_OpenAPIGeneratorCore/Extensions/OpenAPIKit.swift @@ -22,12 +22,36 @@ extension Either { /// - Throws: An error if there's an issue looking up the value in the components. func resolve(in components: OpenAPI.Components) throws -> B where A == OpenAPI.Reference { switch self { - case let .a(a): return try components.lookup(a) + case let .a(a): return try components.assumeLookupOnce(a) case let .b(b): return b } } } +extension OpenAPI.Components { + func assumeLookupOnce(_ reference: OpenAPI.Reference) throws -> ReferenceType{ + guard let result = try lookupOnce(reference).b else { + throw JSONReferenceParsingError.componentsReferenceEntryUnsupported(reference.absoluteString) + } + return result + } + + func assumeLookupOnce(_ reference: JSONReference) throws -> ReferenceType{ + guard let result = try lookupOnce(reference).b else { + throw JSONReferenceParsingError.componentsReferenceEntryUnsupported(reference.absoluteString) + } + return result + } + + func assumeLookupOnce(_ maybeReference: Either, ReferenceType>) throws -> ReferenceType{ + guard let result = try lookupOnce(maybeReference).b else { + throw JSONReferenceParsingError.componentsReferenceEntryUnsupported(maybeReference.a?.absoluteString) + } + return result + } + +} + extension JSONSchema.Schema { /// Returns the name of the schema. diff --git a/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift b/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift index 7760580d3..0eac40485 100644 --- a/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift +++ b/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift @@ -103,7 +103,7 @@ struct FilteredDocumentBuilder { guard let methods = requiredEndpoints[path] else { continue } switch pathItem { case .a(let reference): - components.pathItems[try reference.internalComponentKey] = try document.components.lookup(reference) + components.pathItems[try reference.internalComponentKey] = try document.components.assumeLookupOnce(reference) .filteringEndpoints { methods.contains($0.method) } case .b(let pathItem): filteredDocument.paths[path] = .b(pathItem.filteringEndpoints { methods.contains($0.method) }) @@ -198,7 +198,7 @@ private extension FilteredDocumentBuilder { switch maybeReference { case .a(let reference): guard try requiredPathItemReferences.insert(reference.internalComponentKey).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) + try includeComponentsReferencedBy(try document.components.assumeLookupOnce(reference)) case .b(let value): try includeComponentsReferencedBy(value) } } @@ -209,7 +209,7 @@ private extension FilteredDocumentBuilder { switch maybeReference { case .a(let reference): guard try requiredParameterReferences.insert(reference.internalComponentKey).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) + try includeComponentsReferencedBy(try document.components.assumeLookupOnce(reference)) case .b(let value): try includeComponentsReferencedBy(value) } } @@ -220,7 +220,7 @@ private extension FilteredDocumentBuilder { switch maybeReference { case .a(let reference): guard try requiredResponseReferences.insert(reference.internalComponentKey).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) + try includeComponentsReferencedBy(try document.components.assumeLookupOnce(reference)) case .b(let value): try includeComponentsReferencedBy(value) } } @@ -229,7 +229,7 @@ private extension FilteredDocumentBuilder { switch maybeReference { case .a(let reference): guard try requiredHeaderReferences.insert(reference.internalComponentKey).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) + try includeComponentsReferencedBy(try document.components.assumeLookupOnce(reference)) case .b(let value): try includeComponentsReferencedBy(value) } } @@ -238,7 +238,7 @@ private extension FilteredDocumentBuilder { switch maybeReference { case .a(let reference): guard try requiredLinkReferences.insert(reference.internalComponentKey).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) + try includeComponentsReferencedBy(try document.components.assumeLookupOnce(reference)) case .b(let value): try includeComponentsReferencedBy(value) } } @@ -249,7 +249,7 @@ private extension FilteredDocumentBuilder { switch maybeReference { case .a(let reference): guard try requiredCallbacksReferences.insert(reference.internalComponentKey).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) + try includeComponentsReferencedBy(try document.components.assumeLookupOnce(reference)) case .b(let value): try includeComponentsReferencedBy(value) } } @@ -260,7 +260,7 @@ private extension FilteredDocumentBuilder { switch maybeReference { case .a(let reference): guard try requiredRequestReferences.insert(reference.internalComponentKey).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) + try includeComponentsReferencedBy(try document.components.assumeLookupOnce(reference)) case .b(let value): try includeComponentsReferencedBy(value) } } @@ -269,7 +269,7 @@ private extension FilteredDocumentBuilder { switch maybeReference { case .a(let reference): guard try requiredExampleReferences.insert(reference.internalComponentKey).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) + try includeComponentsReferencedBy(try document.components.assumeLookupOnce(reference)) case .b(let value): try includeComponentsReferencedBy(value) } } @@ -278,7 +278,7 @@ private extension FilteredDocumentBuilder { for (path, maybePathItemReference) in document.paths { let originalPathItem: OpenAPI.PathItem switch maybePathItemReference { - case .a(let reference): originalPathItem = try document.components.lookup(reference) + case .a(let reference): originalPathItem = try document.components.assumeLookupOnce(reference) case .b(let pathItem): originalPathItem = pathItem } @@ -353,7 +353,7 @@ private extension FilteredDocumentBuilder { switch schemaContext.schema { case .a(let reference): guard try requiredSchemaReferences.insert(reference.internalComponentKey).inserted else { return } - try includeComponentsReferencedBy(try document.components.lookup(reference)) + try includeComponentsReferencedBy(try document.components.assumeLookupOnce(reference)) case .b(let schema): try includeComponentsReferencedBy(schema) } case .b(let contentMap): @@ -366,7 +366,7 @@ private extension FilteredDocumentBuilder { ) throws { let content: OpenAPI.Content switch contentMapEntry { - case .a(let ref): content = try document.components.lookup(ref) + case .a(let ref): content = try document.components.assumeLookupOnce(ref) case .b(let value): content = value } try includeComponentsReferencedBy(content) diff --git a/Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift b/Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift index 3ed8b9c24..4698b772f 100644 --- a/Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift +++ b/Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift @@ -67,7 +67,7 @@ func validateContentTypes(in doc: ParsedOpenAPIRepresentation, validate: (String } for (key, component) in doc.components.requestBodies { - let component = try doc.components.lookup(component) + let component = try doc.components.assumeLookupOnce(component) for contentType in component.content.keys { if !validate(contentType.rawValue) { throw Diagnostic.error( @@ -82,7 +82,7 @@ func validateContentTypes(in doc: ParsedOpenAPIRepresentation, validate: (String } for (key, component) in doc.components.responses { - let component = try doc.components.lookup(component) + let component = try doc.components.assumeLookupOnce(component) for contentType in component.content.keys { if !validate(contentType.rawValue) { throw Diagnostic.error( diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift b/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift index 571aec686..5e4a47a51 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift @@ -97,7 +97,7 @@ extension FileTranslator { return try contents.compactMap { key, value in try parseContentIfSupported( contentKey: key, - contentValue: try components.lookup(value), + contentValue: try components.assumeLookupOnce(value), excludeBinary: excludeBinary, isRequired: isRequired, foundIn: foundIn + "/\(key.rawValue)" @@ -129,12 +129,12 @@ extension FileTranslator { let chosenContent: (type: ContentType, schema: SchemaContent, content: OpenAPI.Content)? if let (contentType, contentValue) = mapWithContentTypes.first(where: { $0.type.isJSON }) { - let contentValue = try components.lookup(contentValue) + let contentValue = try components.assumeLookupOnce(contentValue) chosenContent = (contentType, .init(contentType: contentType, schema: contentValue.schema.map(Either.schema)), contentValue) } else if !excludeBinary, let (contentType, contentValue) = mapWithContentTypes.first(where: { $0.type.isBinary }) { - let contentValue = try components.lookup(contentValue) + let contentValue = try components.assumeLookupOnce(contentValue) chosenContent = ( contentType, .init(contentType: contentType, schema: .schema(.string(contentEncoding: .binary))), contentValue diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift b/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift index 77abc1a93..b669d8a77 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift @@ -297,14 +297,14 @@ extension FileTranslator { case .string(_, let context): candidateSource = try inferStringContent(context) case .object, .all, .one, .any, .fragment, .array: candidateSource = .infer(.complex) case .reference(let ref, _): - guard let source = try inferSchema(components.lookup(ref))?.1 else { return nil } + guard let source = try inferSchema(components.assumeLookupOnce(ref))?.1 else { return nil } candidateSource = source } } else { candidateSource = .infer(.complex) } case .reference(let ref, _): - guard let (refRepetitionKind, refCandidateSource) = try inferSchema(components.lookup(ref)) else { + guard let (refRepetitionKind, refCandidateSource) = try inferSchema(components.assumeLookupOnce(ref)) else { return nil } repetitionKind = refRepetitionKind @@ -367,7 +367,7 @@ extension FileTranslator { guard try key.asGeneratorContentType.isMultipart else { continue } let content: OpenAPI.Content switch value { - case .a(let ref): content = try components.lookup(ref) + case .a(let ref): content = try components.assumeLookupOnce(ref) case .b(let value): content = value } guard let ref = content.schema?.reference, let name = ref.name, @@ -382,7 +382,7 @@ extension FileTranslator { if let requestBodyEither = operation.requestBody { let requestBody: OpenAPI.Request switch requestBodyEither { - case .a(let ref): requestBody = try components.lookup(ref) + case .a(let ref): requestBody = try components.assumeLookupOnce(ref) case .b(let value): requestBody = value } try visitContentMap(requestBody.content) @@ -390,7 +390,7 @@ extension FileTranslator { for responseOutcome in operation.responseOutcomes { let response: OpenAPI.Response switch responseOutcome.response { - case .a(let ref): response = try components.lookup(ref) + case .a(let ref): response = try components.assumeLookupOnce(ref) case .b(let value): response = value } try visitContentMap(response.content) @@ -400,7 +400,7 @@ extension FileTranslator { for (_, value) in paths { let pathItem: OpenAPI.PathItem switch value { - case .a(let ref): pathItem = try components.lookup(ref) + case .a(let ref): pathItem = try components.assumeLookupOnce(ref) case .b(let value): pathItem = value } try visitPath(pathItem) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift b/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift index 4498dba4f..e8f846e9e 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift @@ -99,7 +99,7 @@ extension FileTranslator { // Collect the parameter let parameter: OpenAPI.Parameter switch unresolvedParameter { - case let .a(ref): parameter = try components.lookup(ref) + case let .a(ref): parameter = try components.assumeLookupOnce(ref) case let .b(_parameter): parameter = _parameter } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/TypedRequestBody.swift b/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/TypedRequestBody.swift index 2279bd1a2..707534585 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/TypedRequestBody.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/TypedRequestBody.swift @@ -73,7 +73,7 @@ extension FileTranslator { let isInlined: Bool switch unresolvedRequest { case .a(let reference): - request = try components.lookup(reference) + request = try components.assumeLookupOnce(reference) isInlined = false case .b(let _request): request = _request diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponse.swift b/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponse.swift index ee4e371f0..78f9b7318 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponse.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponse.swift @@ -45,7 +45,7 @@ extension FileTranslator { switch unresolvedResponse { case .a(let reference): typeName = try typeAssigner.typeName(for: reference) - response = try components.lookup(reference) + response = try components.assumeLookupOnce(reference) isInlined = false case .b(let _response): let responseKind = outcome.status.value.asKind diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift b/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift index f8b0e866b..b30130601 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift @@ -105,7 +105,7 @@ extension FileTranslator { // Collect the header let header: OpenAPI.Header switch unresolvedResponseHeader { - case let .a(ref): header = try components.lookup(ref) + case let .a(ref): header = try components.assumeLookupOnce(ref) case let .b(_header): header = _header } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift index 1555731d3..08bbed130 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift @@ -534,6 +534,10 @@ enum JSONReferenceParsingError: Swift.Error { /// An error thrown when parsing a JSON reference that points to /// other OpenAPI documents. case externalPathsUnsupported(String) + + // An error thrown when following a reference leads to a component entry + // that is itself a reference. + case componentsReferenceEntryUnsupported(String?) } extension JSONReferenceParsingError: CustomStringConvertible { @@ -543,6 +547,8 @@ extension JSONReferenceParsingError: CustomStringConvertible { return "JSON references outside of #/components are not supported, found: \(string ?? "")" case let .externalPathsUnsupported(string): return "External JSON references are not supported, found: \(string)" + case let .componentsReferenceEntryUnsupported(string): + return "#/components entries that are themselves references are not supported, found: \(string ?? "")" } } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift index 1c503ae74..a81d131e8 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift @@ -199,7 +199,7 @@ struct TypeMatcher { // only key-value pair schemas can be valid recursive types. return true } - let targetSchema = try components.lookup(ref) + let targetSchema = try components.assumeLookupOnce(ref) try referenceStack.push(ref) defer { referenceStack.pop() } return try isKeyValuePair(targetSchema, referenceStack: &referenceStack, components: components) @@ -231,7 +231,7 @@ struct TypeMatcher { } let schemaToCheck: JSONSchema switch schema { - case .a(let ref): schemaToCheck = try components.lookup(ref) + case .a(let ref): schemaToCheck = try components.assumeLookupOnce(ref) case let .b(schema): schemaToCheck = schema } return try isKeyValuePair(schemaToCheck, referenceStack: &referenceStack, components: components) @@ -246,7 +246,7 @@ struct TypeMatcher { func isOptional(_ schema: JSONSchema, components: OpenAPI.Components) throws -> Bool { if schema.nullable || !schema.required { return true } guard case .reference(let ref, _) = schema.value else { return false } - let targetSchema = try components.lookup(ref) + let targetSchema = try components.assumeLookupOnce(ref) return try isOptional(targetSchema, components: components) } @@ -263,7 +263,7 @@ struct TypeMatcher { } switch schema { case .a(let ref): - let targetSchema = try components.lookup(ref) + let targetSchema = try components.assumeLookupOnce(ref) return try isOptional(targetSchema, components: components) case .b(let schema): return try isOptional(schema, components: components) } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift index 49fb4466f..afb7596cf 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift @@ -145,7 +145,7 @@ extension FileTranslator { return .supported } // reference is supported iff the existing type is supported - let existingSchema = try components.lookup(ref) + let existingSchema = try components.assumeLookupOnce(ref) try referenceStack.push(ref) defer { referenceStack.pop() } return try isSchemaSupported(existingSchema, referenceStack: &referenceStack) @@ -315,7 +315,7 @@ extension FileTranslator { return .supported } // reference is supported iff the existing type is supported - let referencedSchema = try components.lookup(ref) + let referencedSchema = try components.assumeLookupOnce(ref) try referenceStack.push(ref) defer { referenceStack.pop() } return try isObjectishSchemaAndSupported(referencedSchema, referenceStack: &referenceStack) @@ -365,7 +365,7 @@ extension FileTranslator { return .supported } // reference is supported iff the existing type is supported - let referencedSchema = try components.lookup(ref) + let referencedSchema = try components.assumeLookupOnce(ref) try referenceStack.push(ref) defer { referenceStack.pop() } return try isObjectOrRefToObjectSchemaAndSupported(referencedSchema, referenceStack: &referenceStack) @@ -391,7 +391,7 @@ extension FileTranslator { return nil } // reference is supported iff the existing type is supported - let referencedSchema = try components.lookup(ref) + let referencedSchema = try components.assumeLookupOnce(ref) try referenceStack.push(ref) defer { referenceStack.pop() } return try flattenedTopLevelMultipartObject(referencedSchema, referenceStack: &referenceStack) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateComponents.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateComponents.swift index 63f1b5033..fff0fe522 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateComponents.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateComponents.swift @@ -29,13 +29,13 @@ extension TypesFileTranslator { { let schemas = try translateSchemas(components.schemas, multipartSchemaNames: multipartSchemaNames) - let resolvedParameters = try components.parameters.mapValues { try components.lookup($0) } + let resolvedParameters = try components.parameters.mapValues { try components.assumeLookupOnce($0) } let parameters = try translateComponentParameters(resolvedParameters) - let resolvedRequestBodies = try components.requestBodies.mapValues { try components.lookup($0) } + let resolvedRequestBodies = try components.requestBodies.mapValues { try components.assumeLookupOnce($0) } let requestBodies = try translateComponentRequestBodies(resolvedRequestBodies) - let resolvedResponses = try components.responses.mapValues { try components.lookup($0) } + let resolvedResponses = try components.responses.mapValues { try components.assumeLookupOnce($0) } let responses = try translateComponentResponses(resolvedResponses) - let resolvedHeaders = try components.headers.mapValues { try components.lookup($0) } + let resolvedHeaders = try components.headers.mapValues { try components.assumeLookupOnce($0) } let headers = try translateComponentHeaders(resolvedHeaders) let componentsDecl: Declaration = .commentable( From 63e789c4208ec41424c24e49bc77b3b7dda31450 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Tue, 10 Mar 2026 09:29:05 -0500 Subject: [PATCH 14/20] fix code that inspects UnresolvedSchemas for references --- .../Responses/TypedResponseHeader.swift | 16 ++++++++++------ .../TypeAssignment/TypeAssigner.swift | 18 +++++++++++------- .../TypeAssignment/TypeMatcher.swift | 15 +++++++++++++-- .../TypeAssignment/isSchemaSupported.swift | 9 ++++++++- 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift b/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift index b30130601..0146ea4c6 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift @@ -135,12 +135,16 @@ extension FileTranslator { switch schema { case let .a(reference): type = try typeAssigner.typeName(for: reference).asUsage case let .b(schema): - type = try typeAssigner.typeUsage( - forParameterNamed: name, - withSchema: schema, - components: components, - inParent: parent - ) + switch schema.value { + case let .reference(reference, _): type = try typeAssigner.typeName(for: reference).asUsage + default: + type = try typeAssigner.typeUsage( + forParameterNamed: name, + withSchema: schema, + components: components, + inParent: parent + ) + } } } let isOptional = try !header.required || typeMatcher.isOptional(schema, components: components) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift index 08bbed130..a49974767 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift @@ -94,13 +94,17 @@ struct TypeAssigner { switch schema { case let .a(reference): associatedType = try typeName(for: reference).asUsage case let .b(schema): - associatedType = try _typeUsage( - forPotentiallyInlinedValueNamed: hint, - withSchema: schema, - components: components, - inParent: parent, - subtype: .appendScope - ) + switch schema.value { + case let .reference(reference, _): associatedType = try typeName(for: reference).asUsage + default: + associatedType = try _typeUsage( + forPotentiallyInlinedValueNamed: hint, + withSchema: schema, + components: components, + inParent: parent, + subtype: .appendScope + ) + } } } else { associatedType = nil diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift index a81d131e8..5634a4fb1 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift @@ -156,8 +156,19 @@ struct TypeMatcher { ) -> OpenAPI.Reference? { // If the schema is a ref AND no encoding is provided, we can reference the type. // Otherwise, we must inline. - guard case .a(let ref) = schema, encoding == nil || encoding!.isEmpty else { return nil } - return ref + guard let schema, encoding == nil || encoding!.isEmpty else { return nil } + + switch schema { + case .a(let ref): + return ref + case .b(let schema): + switch schema.value { + case let .reference(ref, _): + return .init(ref) + default: + return nil + } + } } /// Returns a Boolean value that indicates whether the schema diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift index afb7596cf..5a1a62c2a 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift @@ -196,7 +196,14 @@ extension FileTranslator { case .a: // references are supported return .supported - case let .b(schema): return try isSchemaSupported(schema, referenceStack: &referenceStack) + case let .b(schema): + switch schema.value { + case .reference: + // references are supported + return .supported + default: + return try isSchemaSupported(schema, referenceStack: &referenceStack) + } } } From 38bc15c8881d35793d3d87a83662b5094cd180db Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Wed, 11 Mar 2026 08:44:01 -0500 Subject: [PATCH 15/20] cleanup/simlipfy after fixing UnresolvedSchema lookups This could be squashed into 'fix code that inspects UnresolvedSchemas for references' except for the fact that two commits tells the story better. The first commit fixes by looking into JSONSchema.references when they are found in UnresolvedSchemas in places where that is needed. This second commit cleans up by flattening the UnresolvedSchema so that we only need the inner of the two switch statements. --- .../Responses/TypedResponseHeader.swift | 26 ++++++++--------- .../TypeAssignment/TypeAssigner.swift | 28 +++++++++---------- .../TypeAssignment/TypeMatcher.swift | 19 ++++++------- .../TypeAssignment/isSchemaSupported.swift | 18 ++++++------ 4 files changed, 44 insertions(+), 47 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift b/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift index 0146ea4c6..78e127a54 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift @@ -132,19 +132,19 @@ extension FileTranslator { switch unresolvedResponseHeader { case let .a(ref): type = try typeAssigner.typeName(for: ref).asUsage case .b: - switch schema { - case let .a(reference): type = try typeAssigner.typeName(for: reference).asUsage - case let .b(schema): - switch schema.value { - case let .reference(reference, _): type = try typeAssigner.typeName(for: reference).asUsage - default: - type = try typeAssigner.typeUsage( - forParameterNamed: name, - withSchema: schema, - components: components, - inParent: parent - ) - } + // we want to look under both OpenAPI.Reference and + // JSONSchema.reference so we flatten the value before inspecting + // it: + let unboxedSchema = schema.flattenToJsonSchema() + switch unboxedSchema.value { + case let .reference(reference, _): type = try typeAssigner.typeName(for: reference).asUsage + default: + type = try typeAssigner.typeUsage( + forParameterNamed: name, + withSchema: unboxedSchema, + components: components, + inParent: parent + ) } } let isOptional = try !header.required || typeMatcher.isOptional(schema, components: components) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift index a49974767..c011a0bc4 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift @@ -91,20 +91,20 @@ struct TypeAssigner { ) throws -> TypeUsage? { let associatedType: TypeUsage? if let schema { - switch schema { - case let .a(reference): associatedType = try typeName(for: reference).asUsage - case let .b(schema): - switch schema.value { - case let .reference(reference, _): associatedType = try typeName(for: reference).asUsage - default: - associatedType = try _typeUsage( - forPotentiallyInlinedValueNamed: hint, - withSchema: schema, - components: components, - inParent: parent, - subtype: .appendScope - ) - } + // we want to look under both OpenAPI.Reference and + // JSONSchema.reference so we flatten the value before inspecting + // it: + let unboxedSchema = schema.flattenToJsonSchema() + switch unboxedSchema.value { + case let .reference(reference, _): associatedType = try typeName(for: reference).asUsage + default: + associatedType = try _typeUsage( + forPotentiallyInlinedValueNamed: hint, + withSchema: unboxedSchema, + components: components, + inParent: parent, + subtype: .appendScope + ) } } else { associatedType = nil diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift index 5634a4fb1..2bc65ee23 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift @@ -158,16 +158,15 @@ struct TypeMatcher { // Otherwise, we must inline. guard let schema, encoding == nil || encoding!.isEmpty else { return nil } - switch schema { - case .a(let ref): - return ref - case .b(let schema): - switch schema.value { - case let .reference(ref, _): - return .init(ref) - default: - return nil - } + // we want to look under both OpenAPI.Reference and + // JSONSchema.reference so we flatten the value before inspecting + // it: + let unboxedSchema = schema.flattenToJsonSchema() + switch unboxedSchema.value { + case let .reference(ref, _): + return .init(ref) + default: + return nil } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift index 5a1a62c2a..a0ce8549a 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift @@ -192,18 +192,16 @@ extension FileTranslator { // fragment type is supported return .supported } - switch schema { - case .a: + // we want to look under both OpenAPI.Reference and + // JSONSchema.reference so we flatten the value before inspecting + // it: + let unboxedSchema = schema.flattenToJsonSchema() + switch unboxedSchema.value { + case .reference: // references are supported return .supported - case let .b(schema): - switch schema.value { - case .reference: - // references are supported - return .supported - default: - return try isSchemaSupported(schema, referenceStack: &referenceStack) - } + default: + return try isSchemaSupported(unboxedSchema, referenceStack: &referenceStack) } } From 211348958dbaa305c47520735fdbf3566e3905f4 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Mon, 9 Mar 2026 15:57:24 -0500 Subject: [PATCH 16/20] test fixes --- .../Parser/Test_validateDoc.swift | 77 +++++++++---------- .../Test_OperationDescription.swift | 14 ++-- .../SnippetBasedReferenceTests.swift | 23 ++++-- 3 files changed, 62 insertions(+), 52 deletions(-) diff --git a/Tests/OpenAPIGeneratorCoreTests/Parser/Test_validateDoc.swift b/Tests/OpenAPIGeneratorCoreTests/Parser/Test_validateDoc.swift index a4a48f39b..62948dd94 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Parser/Test_validateDoc.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Parser/Test_validateDoc.swift @@ -81,13 +81,13 @@ final class Test_validateDoc: Test_Core { .init( get: .init( requestBody: .b( - .init(content: [.init(rawValue: "application/xml")!: .init(schema: .string)]) + .init(content: [.init(rawValue: "application/xml")!: .content(.init(schema: .string))]) ), responses: [ .init(integerLiteral: 200): .b( .init( description: "Test description 1", - content: [.init(rawValue: "application/json")!: .init(schema: .string)] + content: [.init(rawValue: "application/json")!: .content(.init(schema: .string))] ) ) ] @@ -97,12 +97,12 @@ final class Test_validateDoc: Test_Core { "/path2": .b( .init( get: .init( - requestBody: .b(.init(content: [.init(rawValue: "text/html")!: .init(schema: .string)])), + requestBody: .b(.init(content: [.init(rawValue: "text/html")!: .content(.init(schema: .string))])), responses: [ .init(integerLiteral: 200): .b( .init( description: "Test description 2", - content: [.init(rawValue: "text/plain")!: .init(schema: .string)] + content: [.init(rawValue: "text/plain")!: .content(.init(schema: .string))] ) ) ] @@ -127,12 +127,12 @@ final class Test_validateDoc: Test_Core { "/path1": .b( .init( get: .init( - requestBody: .b(.init(content: [.init(rawValue: "application/")!: .init(schema: .string)])), + requestBody: .b(.init(content: [.init(rawValue: "application/")!: .content(.init(schema: .string))])), responses: [ .init(integerLiteral: 200): .b( .init( description: "Test description 1", - content: [.init(rawValue: "application/json")!: .init(schema: .string)] + content: [.init(rawValue: "application/json")!: .content(.init(schema: .string))] ) ) ] @@ -142,12 +142,12 @@ final class Test_validateDoc: Test_Core { "/path2": .b( .init( get: .init( - requestBody: .b(.init(content: [.init(rawValue: "text/html")!: .init(schema: .string)])), + requestBody: .b(.init(content: [.init(rawValue: "text/html")!: .content(.init(schema: .string))])), responses: [ .init(integerLiteral: 200): .b( .init( description: "Test description 2", - content: [.init(rawValue: "text/plain")!: .init(schema: .string)] + content: [.init(rawValue: "text/plain")!: .content(.init(schema: .string))] ) ) ] @@ -179,13 +179,13 @@ final class Test_validateDoc: Test_Core { .init( get: .init( requestBody: .b( - .init(content: [.init(rawValue: "application/xml")!: .init(schema: .string)]) + .init(content: [.init(rawValue: "application/xml")!: .content(.init(schema: .string))]) ), responses: [ .init(integerLiteral: 200): .b( .init( description: "Test description 1", - content: [.init(rawValue: "application/json")!: .init(schema: .string)] + content: [.init(rawValue: "application/json")!: .content(.init(schema: .string))] ) ) ] @@ -195,12 +195,12 @@ final class Test_validateDoc: Test_Core { "/path2": .b( .init( get: .init( - requestBody: .b(.init(content: [.init(rawValue: "text/html")!: .init(schema: .string)])), + requestBody: .b(.init(content: [.init(rawValue: "text/html")!: .content(.init(schema: .string))])), responses: [ .init(integerLiteral: 200): .b( .init( description: "Test description 2", - content: [.init(rawValue: "/plain")!: .init(schema: .string)] + content: [.init(rawValue: "/plain")!: .content(.init(schema: .string))] ) ) ] @@ -232,13 +232,13 @@ final class Test_validateDoc: Test_Core { .init( get: .init( requestBody: .b( - .init(content: [.init(rawValue: "application/xml")!: .init(schema: .string)]) + .init(content: [.init(rawValue: "application/xml")!: .content(.init(schema: .string))]) ), responses: [ .init(integerLiteral: 200): .b( .init( description: "Test description 1", - content: [.init(rawValue: "application/json")!: .init(schema: .string)] + content: [.init(rawValue: "application/json")!: .content(.init(schema: .string))] ) ) ] @@ -246,9 +246,9 @@ final class Test_validateDoc: Test_Core { ) ) ], - components: .init(requestBodies: [ - "exampleRequestBody1": .init(content: [.init(rawValue: "application/pdf")!: .init(schema: .string)]), - "exampleRequestBody2": .init(content: [.init(rawValue: "image/")!: .init(schema: .string)]), + components: .direct(requestBodies: [ + "exampleRequestBody1": .init(content: [.init(rawValue: "application/pdf")!: .content(.init(schema: .string))]), + "exampleRequestBody2": .init(content: [.init(rawValue: "image/")!: .content(.init(schema: .string))]), ]) ) XCTAssertThrowsError( @@ -273,13 +273,13 @@ final class Test_validateDoc: Test_Core { .init( get: .init( requestBody: .b( - .init(content: [.init(rawValue: "application/xml")!: .init(schema: .string)]) + .init(content: [.init(rawValue: "application/xml")!: .content(.init(schema: .string))]) ), responses: [ .init(integerLiteral: 200): .b( .init( description: "Test description 1", - content: [.init(rawValue: "application/json")!: .init(schema: .string)] + content: [.init(rawValue: "application/json")!: .content(.init(schema: .string))] ) ) ] @@ -287,14 +287,14 @@ final class Test_validateDoc: Test_Core { ) ) ], - components: .init(responses: [ + components: .direct(responses: [ "exampleRequestBody1": .init( description: "Test description 1", - content: [.init(rawValue: "application/pdf")!: .init(schema: .string)] + content: [.init(rawValue: "application/pdf")!: .content(.init(schema: .string))] ), "exampleRequestBody2": .init( description: "Test description 2", - content: [.init(rawValue: "")!: .init(schema: .string)] + content: [.init(rawValue: "")!: .content(.init(schema: .string))] ), ]) ) @@ -321,13 +321,12 @@ final class Test_validateDoc: Test_Core { get: .init( parameters: .init( arrayLiteral: .b( - .init( + .path( name: "ID", - context: .path, content: [ - .init(rawValue: "text/plain")!: .init( - schema: .a(.component(named: "Path1ParametersContentSchemaReference")) - ) + .init(rawValue: "text/plain")!: .content(.init( + schema: .reference(.component(named: "Path1ParametersContentSchemaReference")) + )) ] ) ), @@ -340,9 +339,9 @@ final class Test_validateDoc: Test_Core { .init( description: "ResponseDescription", content: [ - .init(rawValue: "text/plain")!: .init( - schema: .a(.component(named: "ResponsesContentSchemaReference")) - ) + .init(rawValue: "text/plain")!: .content(.init( + schema: .reference(.component(named: "ResponsesContentSchemaReference")) + )) ] ) ), @@ -360,9 +359,9 @@ final class Test_validateDoc: Test_Core { parameters: .init(arrayLiteral: .a(.component(named: "Path3ExampleID"))), requestBody: .b( .init(content: [ - .init(rawValue: "text/html")!: .init( - schema: .a(.component(named: "RequestBodyContentSchemaReference")) - ) + .init(rawValue: "text/html")!: .content(.init( + schema: .reference(.component(named: "RequestBodyContentSchemaReference")) + )) ]) ), responses: [:], @@ -371,7 +370,7 @@ final class Test_validateDoc: Test_Core { ) ), ], - components: .init( + components: .direct( schemas: [ "ResponsesContentSchemaReference": .init(schema: .string(.init(), .init())), "RequestBodyContentSchemaReference": .init(schema: .integer(.init(), .init())), @@ -379,8 +378,8 @@ final class Test_validateDoc: Test_Core { ], responses: ["ResponsesReference": .init(description: "Description")], parameters: [ - "Path3ExampleID": .init(name: "ID", context: .path, content: .init()), - "Path1ParametersReference": .init(name: "Schema", context: .path, schema: .array), + "Path3ExampleID": .path(name: "ID", content: .init()), + "Path1ParametersReference": .path(name: "Schema", schema: .array), ], requestBodies: [ "RequestBodyReference": .init(content: .init()) @@ -404,9 +403,9 @@ final class Test_validateDoc: Test_Core { get: .init( requestBody: .b( .init(content: [ - .init(rawValue: "text/html")!: .init( - schema: .a(.component(named: "RequestBodyContentSchemaReference")) - ) + .init(rawValue: "text/html")!: .content(.init( + schema: .reference(.component(named: "RequestBodyContentSchemaReference")) + )) ]) ), responses: [:] diff --git a/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift b/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift index abd341a71..d371ed1b8 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift @@ -19,10 +19,10 @@ final class Test_OperationDescription: Test_Core { func testAllParameters_duplicates_retainOnlyOperationParameters() throws { let pathLevelParameter = UnresolvedParameter.b( - OpenAPI.Parameter(name: "test", context: .query(required: false), schema: .integer) + OpenAPI.Parameter.query(name: "test", required: false, schema: .integer) ) let operationLevelParameter = UnresolvedParameter.b( - OpenAPI.Parameter(name: "test", context: .query(required: false), schema: .string) + OpenAPI.Parameter.query(name: "test", required: false, schema: .string) ) let pathItem = OpenAPI.PathItem( @@ -37,10 +37,10 @@ final class Test_OperationDescription: Test_Core { func testAllParameters_duplicates_keepsDuplicatesFromDifferentLocation() throws { let pathLevelParameter = UnresolvedParameter.b( - OpenAPI.Parameter(name: "test", context: .query(required: false), schema: .integer) + OpenAPI.Parameter.query(name: "test", required: false, schema: .integer) ) let operationLevelParameter = UnresolvedParameter.b( - OpenAPI.Parameter(name: "test", context: .path, schema: .string) + OpenAPI.Parameter.path(name: "test", schema: .string) ) let pathItem = OpenAPI.PathItem( @@ -55,13 +55,13 @@ final class Test_OperationDescription: Test_Core { func testAllParameters_duplicates_ordering() throws { let pathLevelParameter = UnresolvedParameter.b( - OpenAPI.Parameter(name: "test1", context: .query(required: false), schema: .integer) + OpenAPI.Parameter.query(name: "test1", required: false, schema: .integer) ) let duplicatedParameter = UnresolvedParameter.b( - OpenAPI.Parameter(name: "test2", context: .query(required: false), schema: .integer) + OpenAPI.Parameter.query(name: "test2", required: false, schema: .integer) ) let operationLevelParameter = UnresolvedParameter.b( - OpenAPI.Parameter(name: "test3", context: .query(required: false), schema: .string) + OpenAPI.Parameter.query(name: "test3", required: false, schema: .string) ) let pathItem = OpenAPI.PathItem( diff --git a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift index c014e0215..0cea20bc2 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift @@ -6321,7 +6321,9 @@ extension SnippetBasedReferenceTests { line: UInt = #line ) throws { let translator = try makeTypesTranslator(componentsYAML: componentsYAML) - let translation = try translator.translateComponentHeaders(translator.components.headers) + let components = translator.components + let componentHeaders = try components.headers.mapValues { try components.assumeLookupOnce($0) } + let translation = try translator.translateComponentHeaders(componentHeaders) try XCTAssertSwiftEquivalent(translation, expectedSwift, file: file, line: line) } @@ -6333,7 +6335,9 @@ extension SnippetBasedReferenceTests { line: UInt = #line ) throws { let translator = try makeTypesTranslator(accessModifier: accessModifier, componentsYAML: componentsYAML) - let translation = try translator.translateComponentParameters(translator.components.parameters) + let components = translator.components + let componentParameters = try components.parameters.mapValues { try components.assumeLookupOnce($0) } + let translation = try translator.translateComponentParameters(componentParameters) try XCTAssertSwiftEquivalent(translation, expectedSwift, file: file, line: line) } @@ -6379,8 +6383,9 @@ extension SnippetBasedReferenceTests { try XCTAssertSwiftEquivalent(generatedSchemasStructuredSwift, expectedSchemasSwift, file: file, line: line) } if let expectedRequestBodiesSwift { + let componentRequestBodies = try components.requestBodies.mapValues { try components.assumeLookupOnce($0) } let generatedRequestBodiesStructuredSwift = try types.translateComponentRequestBodies( - document.components.requestBodies + componentRequestBodies ) try XCTAssertSwiftEquivalent( generatedRequestBodiesStructuredSwift, @@ -6438,8 +6443,10 @@ extension SnippetBasedReferenceTests { try XCTAssertSwiftEquivalent(generatedSchemasStructuredSwift, expectedSchemasSwift, file: file, line: line) } if let expectedResponsesSwift { + let components = document.components + let componentResponses = try components.responses.mapValues { try components.assumeLookupOnce($0) } let generatedRequestBodiesStructuredSwift = try types.translateComponentResponses( - document.components.responses + componentResponses ) try XCTAssertSwiftEquivalent( generatedRequestBodiesStructuredSwift, @@ -6496,7 +6503,9 @@ extension SnippetBasedReferenceTests { ignoredDiagnosticMessages: ignoredDiagnosticMessages, componentsYAML: componentsYAML ) - let translation = try translator.translateComponentResponses(translator.components.responses) + let components = translator.components + let componentResponses = try components.responses.mapValues { try components.assumeLookupOnce($0) } + let translation = try translator.translateComponentResponses(componentResponses) try XCTAssertSwiftEquivalent(translation, expectedSwift, file: file, line: line) } @@ -6513,7 +6522,9 @@ extension SnippetBasedReferenceTests { ignoredDiagnosticMessages: ignoredDiagnosticMessages, componentsYAML: componentsYAML ) - let translation = try translator.translateComponentRequestBodies(translator.components.requestBodies) + let components = translator.components + let componentRequestBodies = try components.requestBodies.mapValues { try components.assumeLookupOnce($0) } + let translation = try translator.translateComponentRequestBodies(componentRequestBodies) try XCTAssertSwiftEquivalent(translation, expectedSwift, file: file, line: line) } From e995adc937367e25ee3c7d5b6591e255106aecff Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Mon, 13 Apr 2026 09:12:08 -0500 Subject: [PATCH 17/20] swift format --- Package.swift | 2 +- .../Extensions/OpenAPIKit.swift | 18 +++-- .../Hooks/FilteredDocument.swift | 11 ++- .../Parser/YamsParser.swift | 3 +- .../Parser/validateDoc.swift | 6 +- .../Translator/Content/ContentInspector.swift | 15 +++- .../Multipart/MultipartContentInspector.swift | 5 +- .../Parameters/TypedParameter.swift | 6 +- .../TypeAssignment/TypeMatcher.swift | 6 +- .../TypeAssignment/isSchemaSupported.swift | 3 +- .../Parser/Test_validateDoc.swift | 76 +++++++++++++------ .../Test_OperationDescription.swift | 4 +- .../SnippetBasedReferenceTests.swift | 4 +- 13 files changed, 95 insertions(+), 64 deletions(-) diff --git a/Package.swift b/Package.swift index 5ccea35fa..4e1f77765 100644 --- a/Package.swift +++ b/Package.swift @@ -165,4 +165,4 @@ for target in package.targets { case .macro, .plugin, .system, .binary: () // not applicable @unknown default: () // we don't know what to do here, do nothing } -}// --- END: STANDARD CROSS-REPO SETTINGS DO NOT EDIT --- // +} // --- END: STANDARD CROSS-REPO SETTINGS DO NOT EDIT --- // diff --git a/Sources/_OpenAPIGeneratorCore/Extensions/OpenAPIKit.swift b/Sources/_OpenAPIGeneratorCore/Extensions/OpenAPIKit.swift index 76879870e..791c11c11 100644 --- a/Sources/_OpenAPIGeneratorCore/Extensions/OpenAPIKit.swift +++ b/Sources/_OpenAPIGeneratorCore/Extensions/OpenAPIKit.swift @@ -29,23 +29,29 @@ extension Either { } extension OpenAPI.Components { - func assumeLookupOnce(_ reference: OpenAPI.Reference) throws -> ReferenceType{ + func assumeLookupOnce(_ reference: OpenAPI.Reference) + throws -> ReferenceType + { guard let result = try lookupOnce(reference).b else { - throw JSONReferenceParsingError.componentsReferenceEntryUnsupported(reference.absoluteString) + throw JSONReferenceParsingError.componentsReferenceEntryUnsupported(reference.absoluteString) } return result } - func assumeLookupOnce(_ reference: JSONReference) throws -> ReferenceType{ + func assumeLookupOnce(_ reference: JSONReference) throws + -> ReferenceType + { guard let result = try lookupOnce(reference).b else { - throw JSONReferenceParsingError.componentsReferenceEntryUnsupported(reference.absoluteString) + throw JSONReferenceParsingError.componentsReferenceEntryUnsupported(reference.absoluteString) } return result } - func assumeLookupOnce(_ maybeReference: Either, ReferenceType>) throws -> ReferenceType{ + func assumeLookupOnce( + _ maybeReference: Either, ReferenceType> + ) throws -> ReferenceType { guard let result = try lookupOnce(maybeReference).b else { - throw JSONReferenceParsingError.componentsReferenceEntryUnsupported(maybeReference.a?.absoluteString) + throw JSONReferenceParsingError.componentsReferenceEntryUnsupported(maybeReference.a?.absoluteString) } return result } diff --git a/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift b/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift index 0eac40485..e057eb9fc 100644 --- a/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift +++ b/Sources/_OpenAPIGeneratorCore/Hooks/FilteredDocument.swift @@ -103,8 +103,8 @@ struct FilteredDocumentBuilder { guard let methods = requiredEndpoints[path] else { continue } switch pathItem { case .a(let reference): - components.pathItems[try reference.internalComponentKey] = try document.components.assumeLookupOnce(reference) - .filteringEndpoints { methods.contains($0.method) } + components.pathItems[try reference.internalComponentKey] = try document.components + .assumeLookupOnce(reference).filteringEndpoints { methods.contains($0.method) } case .b(let pathItem): filteredDocument.paths[path] = .b(pathItem.filteringEndpoints { methods.contains($0.method) }) } @@ -356,8 +356,7 @@ private extension FilteredDocumentBuilder { try includeComponentsReferencedBy(try document.components.assumeLookupOnce(reference)) case .b(let schema): try includeComponentsReferencedBy(schema) } - case .b(let contentMap): - for value in contentMap.values { try includeComponentsReferencedBy(value) } + case .b(let contentMap): for value in contentMap.values { try includeComponentsReferencedBy(value) } } } @@ -366,8 +365,8 @@ private extension FilteredDocumentBuilder { ) throws { let content: OpenAPI.Content switch contentMapEntry { - case .a(let ref): content = try document.components.assumeLookupOnce(ref) - case .b(let value): content = value + case .a(let ref): content = try document.components.assumeLookupOnce(ref) + case .b(let value): content = value } try includeComponentsReferencedBy(content) } diff --git a/Sources/_OpenAPIGeneratorCore/Parser/YamsParser.swift b/Sources/_OpenAPIGeneratorCore/Parser/YamsParser.swift index 909507b8d..2e8f4d404 100644 --- a/Sources/_OpenAPIGeneratorCore/Parser/YamsParser.swift +++ b/Sources/_OpenAPIGeneratorCore/Parser/YamsParser.swift @@ -75,8 +75,7 @@ public struct YamsParser: ParserProtocol { document = openAPI30Document.convert(to: .v3_1_0) case "3.1.0", "3.1.1", "3.1.2": document = try decoder.decode(OpenAPIKit.OpenAPI.Document.self, from: input.contents) - case "3.2.0": - document = try decoder.decode(OpenAPIKit.OpenAPI.Document.self, from: input.contents) + case "3.2.0": document = try decoder.decode(OpenAPIKit.OpenAPI.Document.self, from: input.contents) default: throw Diagnostic.openAPIVersionError( versionString: "openapi: \(openAPIVersion)", diff --git a/Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift b/Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift index 4698b772f..ed6e08a20 100644 --- a/Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift +++ b/Sources/_OpenAPIGeneratorCore/Parser/validateDoc.swift @@ -128,11 +128,7 @@ func validateReferences(in doc: ParsedOpenAPIRepresentation) throws { for (contentKey, contentType) in content { switch contentType { case .a(let ref): - try validateReference( - ref, - in: doc.components, - location: location + "/content/\(contentKey.rawValue)" - ) + try validateReference(ref, in: doc.components, location: location + "/content/\(contentKey.rawValue)") case .b(let contentType): if let reference: JSONReference = contentType.schema?.reference { try validateReference( diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift b/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift index 5e4a47a51..bf149670b 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Content/ContentInspector.swift @@ -130,7 +130,10 @@ extension FileTranslator { let chosenContent: (type: ContentType, schema: SchemaContent, content: OpenAPI.Content)? if let (contentType, contentValue) = mapWithContentTypes.first(where: { $0.type.isJSON }) { let contentValue = try components.assumeLookupOnce(contentValue) - chosenContent = (contentType, .init(contentType: contentType, schema: contentValue.schema.map(Either.schema)), contentValue) + chosenContent = ( + contentType, .init(contentType: contentType, schema: contentValue.schema.map(Either.schema)), + contentValue + ) } else if !excludeBinary, let (contentType, contentValue) = mapWithContentTypes.first(where: { $0.type.isBinary }) { @@ -191,7 +194,9 @@ extension FileTranslator { ) } if contentType.isJSON { return .init(contentType: contentType, schema: contentValue.schema.map(Either.schema)) } - if contentType.isUrlEncodedForm { return .init(contentType: contentType, schema: contentValue.schema.map(Either.schema)) } + if contentType.isUrlEncodedForm { + return .init(contentType: contentType, schema: contentValue.schema.map(Either.schema)) + } if contentType.isMultipart { guard isRequired else { try diagnostics.emit( @@ -203,7 +208,11 @@ extension FileTranslator { ) return nil } - return .init(contentType: contentType, schema: contentValue.schema.map(Either.schema), encoding: contentValue.encodingMap) + return .init( + contentType: contentType, + schema: contentValue.schema.map(Either.schema), + encoding: contentValue.encodingMap + ) } if !excludeBinary, contentType.isBinary { return .init(contentType: contentType, schema: .schema(.string(contentEncoding: .binary))) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift b/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift index b669d8a77..540d3beaf 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift @@ -304,9 +304,8 @@ extension FileTranslator { candidateSource = .infer(.complex) } case .reference(let ref, _): - guard let (refRepetitionKind, refCandidateSource) = try inferSchema(components.assumeLookupOnce(ref)) else { - return nil - } + guard let (refRepetitionKind, refCandidateSource) = try inferSchema(components.assumeLookupOnce(ref)) + else { return nil } repetitionKind = refRepetitionKind candidateSource = refCandidateSource } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift b/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift index e8f846e9e..e3953b2cf 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift @@ -171,10 +171,8 @@ extension FileTranslator { // https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.2.0.md#fixed-fields-10 style = OpenAPI.Parameter.SchemaContext.Style.default(for: parameter.location) switch style { - case .form: - explode = true - default: - explode = false + case .form: explode = true + default: explode = false } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift index 2bc65ee23..2dd1b16e5 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift @@ -163,10 +163,8 @@ struct TypeMatcher { // it: let unboxedSchema = schema.flattenToJsonSchema() switch unboxedSchema.value { - case let .reference(ref, _): - return .init(ref) - default: - return nil + case let .reference(ref, _): return .init(ref) + default: return nil } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift index a0ce8549a..1a4153121 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/isSchemaSupported.swift @@ -200,8 +200,7 @@ extension FileTranslator { case .reference: // references are supported return .supported - default: - return try isSchemaSupported(unboxedSchema, referenceStack: &referenceStack) + default: return try isSchemaSupported(unboxedSchema, referenceStack: &referenceStack) } } diff --git a/Tests/OpenAPIGeneratorCoreTests/Parser/Test_validateDoc.swift b/Tests/OpenAPIGeneratorCoreTests/Parser/Test_validateDoc.swift index 62948dd94..b876620df 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Parser/Test_validateDoc.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Parser/Test_validateDoc.swift @@ -87,7 +87,9 @@ final class Test_validateDoc: Test_Core { .init(integerLiteral: 200): .b( .init( description: "Test description 1", - content: [.init(rawValue: "application/json")!: .content(.init(schema: .string))] + content: [ + .init(rawValue: "application/json")!: .content(.init(schema: .string)) + ] ) ) ] @@ -97,7 +99,9 @@ final class Test_validateDoc: Test_Core { "/path2": .b( .init( get: .init( - requestBody: .b(.init(content: [.init(rawValue: "text/html")!: .content(.init(schema: .string))])), + requestBody: .b( + .init(content: [.init(rawValue: "text/html")!: .content(.init(schema: .string))]) + ), responses: [ .init(integerLiteral: 200): .b( .init( @@ -127,12 +131,16 @@ final class Test_validateDoc: Test_Core { "/path1": .b( .init( get: .init( - requestBody: .b(.init(content: [.init(rawValue: "application/")!: .content(.init(schema: .string))])), + requestBody: .b( + .init(content: [.init(rawValue: "application/")!: .content(.init(schema: .string))]) + ), responses: [ .init(integerLiteral: 200): .b( .init( description: "Test description 1", - content: [.init(rawValue: "application/json")!: .content(.init(schema: .string))] + content: [ + .init(rawValue: "application/json")!: .content(.init(schema: .string)) + ] ) ) ] @@ -142,7 +150,9 @@ final class Test_validateDoc: Test_Core { "/path2": .b( .init( get: .init( - requestBody: .b(.init(content: [.init(rawValue: "text/html")!: .content(.init(schema: .string))])), + requestBody: .b( + .init(content: [.init(rawValue: "text/html")!: .content(.init(schema: .string))]) + ), responses: [ .init(integerLiteral: 200): .b( .init( @@ -185,7 +195,9 @@ final class Test_validateDoc: Test_Core { .init(integerLiteral: 200): .b( .init( description: "Test description 1", - content: [.init(rawValue: "application/json")!: .content(.init(schema: .string))] + content: [ + .init(rawValue: "application/json")!: .content(.init(schema: .string)) + ] ) ) ] @@ -195,7 +207,9 @@ final class Test_validateDoc: Test_Core { "/path2": .b( .init( get: .init( - requestBody: .b(.init(content: [.init(rawValue: "text/html")!: .content(.init(schema: .string))])), + requestBody: .b( + .init(content: [.init(rawValue: "text/html")!: .content(.init(schema: .string))]) + ), responses: [ .init(integerLiteral: 200): .b( .init( @@ -238,7 +252,9 @@ final class Test_validateDoc: Test_Core { .init(integerLiteral: 200): .b( .init( description: "Test description 1", - content: [.init(rawValue: "application/json")!: .content(.init(schema: .string))] + content: [ + .init(rawValue: "application/json")!: .content(.init(schema: .string)) + ] ) ) ] @@ -247,7 +263,9 @@ final class Test_validateDoc: Test_Core { ) ], components: .direct(requestBodies: [ - "exampleRequestBody1": .init(content: [.init(rawValue: "application/pdf")!: .content(.init(schema: .string))]), + "exampleRequestBody1": .init(content: [ + .init(rawValue: "application/pdf")!: .content(.init(schema: .string)) + ]), "exampleRequestBody2": .init(content: [.init(rawValue: "image/")!: .content(.init(schema: .string))]), ]) ) @@ -279,7 +297,9 @@ final class Test_validateDoc: Test_Core { .init(integerLiteral: 200): .b( .init( description: "Test description 1", - content: [.init(rawValue: "application/json")!: .content(.init(schema: .string))] + content: [ + .init(rawValue: "application/json")!: .content(.init(schema: .string)) + ] ) ) ] @@ -324,9 +344,13 @@ final class Test_validateDoc: Test_Core { .path( name: "ID", content: [ - .init(rawValue: "text/plain")!: .content(.init( - schema: .reference(.component(named: "Path1ParametersContentSchemaReference")) - )) + .init(rawValue: "text/plain")!: .content( + .init( + schema: .reference( + .component(named: "Path1ParametersContentSchemaReference") + ) + ) + ) ] ) ), @@ -339,9 +363,13 @@ final class Test_validateDoc: Test_Core { .init( description: "ResponseDescription", content: [ - .init(rawValue: "text/plain")!: .content(.init( - schema: .reference(.component(named: "ResponsesContentSchemaReference")) - )) + .init(rawValue: "text/plain")!: .content( + .init( + schema: .reference( + .component(named: "ResponsesContentSchemaReference") + ) + ) + ) ] ) ), @@ -359,9 +387,11 @@ final class Test_validateDoc: Test_Core { parameters: .init(arrayLiteral: .a(.component(named: "Path3ExampleID"))), requestBody: .b( .init(content: [ - .init(rawValue: "text/html")!: .content(.init( - schema: .reference(.component(named: "RequestBodyContentSchemaReference")) - )) + .init(rawValue: "text/html")!: .content( + .init( + schema: .reference(.component(named: "RequestBodyContentSchemaReference")) + ) + ) ]) ), responses: [:], @@ -403,9 +433,11 @@ final class Test_validateDoc: Test_Core { get: .init( requestBody: .b( .init(content: [ - .init(rawValue: "text/html")!: .content(.init( - schema: .reference(.component(named: "RequestBodyContentSchemaReference")) - )) + .init(rawValue: "text/html")!: .content( + .init( + schema: .reference(.component(named: "RequestBodyContentSchemaReference")) + ) + ) ]) ), responses: [:] diff --git a/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift b/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift index d371ed1b8..c48244326 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift @@ -39,9 +39,7 @@ final class Test_OperationDescription: Test_Core { let pathLevelParameter = UnresolvedParameter.b( OpenAPI.Parameter.query(name: "test", required: false, schema: .integer) ) - let operationLevelParameter = UnresolvedParameter.b( - OpenAPI.Parameter.path(name: "test", schema: .string) - ) + let operationLevelParameter = UnresolvedParameter.b(OpenAPI.Parameter.path(name: "test", schema: .string)) let pathItem = OpenAPI.PathItem( parameters: [pathLevelParameter], diff --git a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift index 0cea20bc2..4a08b8ed0 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift @@ -6445,9 +6445,7 @@ extension SnippetBasedReferenceTests { if let expectedResponsesSwift { let components = document.components let componentResponses = try components.responses.mapValues { try components.assumeLookupOnce($0) } - let generatedRequestBodiesStructuredSwift = try types.translateComponentResponses( - componentResponses - ) + let generatedRequestBodiesStructuredSwift = try types.translateComponentResponses(componentResponses) try XCTAssertSwiftEquivalent( generatedRequestBodiesStructuredSwift, expectedResponsesSwift, From e1465213e777a24f62039f7887191f0021ad2f4f Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Mon, 13 Apr 2026 09:31:06 -0500 Subject: [PATCH 18/20] Address Swift 6 on Linux type inference failure Specifically, Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateComponents.swift:38:54: error: generic parameter 'T' could not be inferred --- .../Translator/TypesTranslator/translateComponents.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateComponents.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateComponents.swift index fff0fe522..7a832b061 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateComponents.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateComponents.swift @@ -35,7 +35,10 @@ extension TypesFileTranslator { let requestBodies = try translateComponentRequestBodies(resolvedRequestBodies) let resolvedResponses = try components.responses.mapValues { try components.assumeLookupOnce($0) } let responses = try translateComponentResponses(resolvedResponses) - let resolvedHeaders = try components.headers.mapValues { try components.assumeLookupOnce($0) } + let resolvedHeaders = try components.headers.mapValues { + (header: Either, OpenAPI.Header>) in + try components.assumeLookupOnce(header) + } let headers = try translateComponentHeaders(resolvedHeaders) let componentsDecl: Declaration = .commentable( From 59478d69dd05f65250174db4747c0e55f20caca2 Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Mon, 13 Apr 2026 09:34:29 -0500 Subject: [PATCH 19/20] undo Yams minimum version bump --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index 4e1f77765..de4950ee3 100644 --- a/Package.swift +++ b/Package.swift @@ -47,7 +47,7 @@ let package = Package( // Read OpenAPI documents .package(url: "https://github.com/mattpolzin/OpenAPIKit", from: "5.1.1"), - .package(url: "https://github.com/jpsim/Yams", "5.1.0"..<"7.0.0"), + .package(url: "https://github.com/jpsim/Yams", "4.0.0"..<"7.0.0"), // CLI Tool .package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"), From f4ae36f4cea5f1ece79c742b9ec2ee1bd4946b6f Mon Sep 17 00:00:00 2001 From: Mathew Polzin Date: Tue, 28 Apr 2026 22:04:22 -0500 Subject: [PATCH 20/20] bump to OpenAPIKit 6 --- Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Package.swift b/Package.swift index de4950ee3..b2f52ee81 100644 --- a/Package.swift +++ b/Package.swift @@ -46,7 +46,7 @@ let package = Package( .package(url: "https://github.com/apple/swift-collections", from: "1.1.4"), // Read OpenAPI documents - .package(url: "https://github.com/mattpolzin/OpenAPIKit", from: "5.1.1"), + .package(url: "https://github.com/mattpolzin/OpenAPIKit", from: "6.0.0", traits: []), .package(url: "https://github.com/jpsim/Yams", "4.0.0"..<"7.0.0"), // CLI Tool