From 16420dfa30f2aa25d83e883edbcda9a2e197993b Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Tue, 1 Aug 2023 16:15:09 +0200 Subject: [PATCH 1/2] [Generator] Consistent style for initializing local variables (#159) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Generator] Consistent style for initializing local variables ### Motivation Move to a consistent style when initializing local variables, always use `let foo = Foo(...)` vs `let foo: Foo = .init(...)`. ### Modifications Updated all occurrences of the latter to use the former. Also updated for the generated code. ### Result Consistent local variable initialization. ### Test Plan All tests passed. Reviewed by: gjcairo, simonjbeaumont Builds: ✔︎ pull request validation (5.8) - Build finished. ✔︎ pull request validation (5.9) - Build finished. ✔︎ pull request validation (docc test) - Build finished. ✔︎ pull request validation (integration test) - Build finished. ✔︎ pull request validation (nightly) - Build finished. ✔︎ pull request validation (soundness) - Build finished. https://github.com/apple/swift-openapi-generator/pull/159 --- .../translateClientMethod.swift | 8 ++++---- .../CommonTranslations/translateCodable.swift | 2 +- .../translateObjectStruct.swift | 2 +- .../translateStringEnum.swift | 8 ++++---- .../translateTypealias.swift | 2 +- .../RequestBody/translateRequestBody.swift | 4 ++-- .../Responses/translateResponseOutcome.swift | 3 +-- .../translateServerMethod.swift | 12 +++++------ .../translateAPIProtocol.swift | 2 +- .../Renderer/Test_TextBasedRenderer.swift | 2 +- .../ReferenceSources/Petstore/Server.swift | 20 +++++++++---------- 11 files changed, 32 insertions(+), 33 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/translateClientMethod.swift b/Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/translateClientMethod.swift index 4f106d36f..be92013ce 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/translateClientMethod.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/translateClientMethod.swift @@ -172,19 +172,19 @@ extension ClientFileTranslator { .identifier(Constants.Operations.namespace) .dot(description.methodName) - let operationArg: FunctionArgumentDescription = .init( + let operationArg = FunctionArgumentDescription( label: "forOperation", expression: operationTypeExpr.dot("id") ) - let inputArg: FunctionArgumentDescription = .init( + let inputArg = FunctionArgumentDescription( label: "input", expression: .identifier(Constants.Operation.Input.variableName) ) - let serializerArg: FunctionArgumentDescription = .init( + let serializerArg = FunctionArgumentDescription( label: "serializer", expression: try translateClientSerializer(description) ) - let deserializerArg: FunctionArgumentDescription = .init( + let deserializerArg = FunctionArgumentDescription( label: "deserializer", expression: try translateClientDeserializer(description) ) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateCodable.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateCodable.swift index 47247f08b..4795a4efb 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateCodable.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateCodable.swift @@ -29,7 +29,7 @@ extension FileTranslator { let knownKeys = properties .map(\.originalName) - let knownKeysFunctionArg: FunctionArgumentDescription = .init( + let knownKeysFunctionArg = FunctionArgumentDescription( label: "knownKeys", expression: .literal( .array( diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateObjectStruct.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateObjectStruct.swift index 480c66792..d5369c794 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateObjectStruct.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateObjectStruct.swift @@ -144,7 +144,7 @@ extension FileTranslator { typeUsage = valueTypeUsage.asDictionaryValue } - let extraProperty: PropertyBlueprint = .init( + let extraProperty = PropertyBlueprint( comment: .doc("A container of undocumented properties."), originalName: "additionalProperties", typeUsage: typeUsage, diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStringEnum.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStringEnum.swift index 5e97d58ea..5c87f9df3 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStringEnum.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStringEnum.swift @@ -72,7 +72,7 @@ extension FileTranslator { ] ) } - let unknownCase: SwitchCaseDescription = .init( + let unknownCase = SwitchCaseDescription( kind: .default, body: [ .expression( @@ -126,7 +126,7 @@ extension FileTranslator { ] ) } - let unknownCase: SwitchCaseDescription = .init( + let unknownCase = SwitchCaseDescription( kind: .case( .valueBinding( kind: .let, @@ -147,7 +147,7 @@ extension FileTranslator { ] ) - let variableDescription: VariableDescription = .init( + let variableDescription = VariableDescription( accessModifier: config.access, kind: .var, left: "rawValue", @@ -186,7 +186,7 @@ extension FileTranslator { ) } - let enumDescription: EnumDescription = .init( + let enumDescription = EnumDescription( isFrozen: true, accessModifier: config.access, name: typeName.shortSwiftName, diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateTypealias.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateTypealias.swift index f35ad7e8c..00304c162 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateTypealias.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateTypealias.swift @@ -26,7 +26,7 @@ extension FileTranslator { userDescription: String?, to existingTypeUsage: TypeUsage ) throws -> Declaration { - let typealiasDescription: TypealiasDescription = .init( + let typealiasDescription = TypealiasDescription( accessModifier: config.access, name: typeName.shortSwiftName, existingType: existingTypeUsage.fullyQualifiedNonOptionalSwiftName diff --git a/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/translateRequestBody.swift b/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/translateRequestBody.swift index 68d7b9889..c9a57f419 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/translateRequestBody.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/translateRequestBody.swift @@ -204,7 +204,7 @@ extension ClientFileTranslator { ]) ) ) - let caseDesc: SwitchCaseDescription = .init( + let caseDesc = SwitchCaseDescription( kind: .case(.dot(contentTypeIdentifier), ["value"]), body: [ .expression(bodyAssignExpr) @@ -213,7 +213,7 @@ extension ClientFileTranslator { return caseDesc } if !requestBody.request.required { - let noneCase: SwitchCaseDescription = .init( + let noneCase = SwitchCaseDescription( kind: .case(.dot("none")), body: [ .expression( diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseOutcome.swift b/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseOutcome.swift index 829f75891..2af82bb3f 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseOutcome.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseOutcome.swift @@ -276,8 +276,7 @@ extension ServerFileTranslator { let responseVarDecl: Declaration = .variable( kind: .var, left: "response", - type: "Response", - right: .dot("init") + right: .identifier("Response") .call([ .init(label: "statusCode", expression: statusCodeExpr) ]) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/ServerTranslator/translateServerMethod.swift b/Sources/_OpenAPIGeneratorCore/Translator/ServerTranslator/translateServerMethod.swift index 78b8edd65..e0f035758 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/ServerTranslator/translateServerMethod.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/ServerTranslator/translateServerMethod.swift @@ -192,19 +192,19 @@ extension ServerFileTranslator { .identifier(Constants.Operations.namespace) .dot(description.methodName) - let operationArg: FunctionArgumentDescription = .init( + let operationArg = FunctionArgumentDescription( label: "forOperation", expression: operationTypeExpr.dot("id") ) - let requestArg: FunctionArgumentDescription = .init( + let requestArg = FunctionArgumentDescription( label: "request", expression: .identifier("request") ) - let metadataArg: FunctionArgumentDescription = .init( + let metadataArg = FunctionArgumentDescription( label: "with", expression: .identifier("metadata") ) - let methodArg: FunctionArgumentDescription = .init( + let methodArg = FunctionArgumentDescription( label: "using", expression: .closureInvocation( body: [ @@ -218,11 +218,11 @@ extension ServerFileTranslator { ] ) ) - let deserializerArg: FunctionArgumentDescription = .init( + let deserializerArg = FunctionArgumentDescription( label: "deserializer", expression: try translateServerDeserializer(description) ) - let serializerArg: FunctionArgumentDescription = .init( + let serializerArg = FunctionArgumentDescription( label: "serializer", expression: try translateServerSerializer(description) ) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateAPIProtocol.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateAPIProtocol.swift index 1a44377be..2fb5497aa 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateAPIProtocol.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateAPIProtocol.swift @@ -30,7 +30,7 @@ extension TypesFileTranslator { operations .map(translateAPIProtocolDeclaration(operation:)) - let protocolDescription: ProtocolDescription = .init( + let protocolDescription = ProtocolDescription( accessModifier: config.access, name: Constants.APIProtocol.typeName, conformances: Constants.APIProtocol.conformances, diff --git a/Tests/OpenAPIGeneratorCoreTests/Renderer/Test_TextBasedRenderer.swift b/Tests/OpenAPIGeneratorCoreTests/Renderer/Test_TextBasedRenderer.swift index d8c1f4935..f1b30e63f 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Renderer/Test_TextBasedRenderer.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Renderer/Test_TextBasedRenderer.swift @@ -16,7 +16,7 @@ import XCTest final class Test_TextBasedRenderer: XCTestCase { - var renderer: TextBasedRenderer = .init() + var renderer = TextBasedRenderer() func testComment() throws { try _test( diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift index 30b52c39e..628e9805d 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift @@ -114,7 +114,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { switch output { case let .ok(value): suppressUnusedWarning(value) - var response: Response = .init(statusCode: 200) + var response = Response(statusCode: 200) suppressMutabilityWarning(&response) try converter.setHeaderFieldAsText( in: &response.headerFields, @@ -141,7 +141,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { return response case let .`default`(statusCode, value): suppressUnusedWarning(value) - var response: Response = .init(statusCode: statusCode) + var response = Response(statusCode: statusCode) suppressMutabilityWarning(&response) switch value.body { case let .json(value): @@ -203,7 +203,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { switch output { case let .created(value): suppressUnusedWarning(value) - var response: Response = .init(statusCode: 201) + var response = Response(statusCode: 201) suppressMutabilityWarning(&response) try converter.setHeaderFieldAsJSON( in: &response.headerFields, @@ -225,7 +225,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { return response case let .badRequest(value): suppressUnusedWarning(value) - var response: Response = .init(statusCode: 400) + var response = Response(statusCode: 400) suppressMutabilityWarning(&response) try converter.setHeaderFieldAsText( in: &response.headerFields, @@ -277,7 +277,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { switch output { case let .noContent(value): suppressUnusedWarning(value) - var response: Response = .init(statusCode: 204) + var response = Response(statusCode: 204) suppressMutabilityWarning(&response) return response case let .undocumented(statusCode, _): return .init(statusCode: statusCode) @@ -328,12 +328,12 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { switch output { case let .noContent(value): suppressUnusedWarning(value) - var response: Response = .init(statusCode: 204) + var response = Response(statusCode: 204) suppressMutabilityWarning(&response) return response case let .badRequest(value): suppressUnusedWarning(value) - var response: Response = .init(statusCode: 400) + var response = Response(statusCode: 400) suppressMutabilityWarning(&response) switch value.body { case let .json(value): @@ -398,7 +398,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { switch output { case let .ok(value): suppressUnusedWarning(value) - var response: Response = .init(statusCode: 200) + var response = Response(statusCode: 200) suppressMutabilityWarning(&response) switch value.body { case let .binary(value): @@ -415,7 +415,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { return response case let .preconditionFailed(value): suppressUnusedWarning(value) - var response: Response = .init(statusCode: 412) + var response = Response(statusCode: 412) suppressMutabilityWarning(&response) switch value.body { case let .json(value): @@ -432,7 +432,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { return response case let .internalServerError(value): suppressUnusedWarning(value) - var response: Response = .init(statusCode: 500) + var response = Response(statusCode: 500) suppressMutabilityWarning(&response) switch value.body { case let .text(value): From 81f874350eb3322ffac4d63771963d287dc57d85 Mon Sep 17 00:00:00 2001 From: Si Beaumont Date: Tue, 1 Aug 2023 15:34:40 +0100 Subject: [PATCH 2/2] Update to OpenAPIKit 3.0.0-alpha.9 (#147) ### Motivation Investigation into #128 led to a new release of OpenAPIKit, which supports path references. Supporting path references isn't something we want to do right now because: 1. OpenAPI 3.0.3 only supports external path references (cf. 3.1, which supports internal references too). 2. Swift OpenAPI Generator currently only supports OpenAPI 3.0.x. 3. Swift OpenAPI Generator currently doesn't support external references. The OpenAPIKit update comes with a new API, because the paths are now an `Either` to support being a `PathItem` or a reference to a `PathItem`. For now we'd like to be keeping up to date with OpenAPIKit so that, when we support OpenAPI 3.1 and/or external references, we have the APIs we need in the upstream package. ### Modifications - Update to OpenAPIKit 3.0.0-alpha.9. - Attempt to resolve the path item during translation. ### Result An error will be thrown when using an OpenAPI document with a path reference. ### Test Plan Added reference tests. --------- Signed-off-by: Si Beaumont --- Package.swift | 2 +- .../translateAllAnyOneOf.swift | 4 +- .../Operations/OperationDescription.swift | 13 +- .../TypesTranslator/TypesFileTranslator.swift | 14 +- .../translateAPIProtocol.swift | 5 +- .../Parser/Test_YamsParser.swift | 8 +- .../SnippetBasedReferenceTests.swift | 156 ++++++++++++++++++ 7 files changed, 184 insertions(+), 18 deletions(-) diff --git a/Package.swift b/Package.swift index 166ee211d..d2798fda6 100644 --- a/Package.swift +++ b/Package.swift @@ -64,7 +64,7 @@ let package = Package( // Read OpenAPI documents .package( url: "https://github.com/mattpolzin/OpenAPIKit.git", - exact: "3.0.0-alpha.7" + exact: "3.0.0-alpha.9" ), .package( url: "https://github.com/jpsim/Yams.git", diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift index d47a6b0b9..c28fc00ef 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift @@ -55,10 +55,10 @@ extension FileTranslator { switch type { case .allOf: // AllOf uses all required properties. - propertyType = rawPropertyType + propertyType = rawPropertyType.withOptional(false) case .anyOf: // AnyOf uses all optional properties. - propertyType = rawPropertyType.asOptional + propertyType = rawPropertyType.withOptional(true) } let comment: Comment? = .property( originalName: key, diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Operations/OperationDescription.swift b/Sources/_OpenAPIGeneratorCore/Translator/Operations/OperationDescription.swift index 4872cc655..547079968 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Operations/OperationDescription.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Operations/OperationDescription.swift @@ -59,13 +59,20 @@ extension OperationDescription { /// - components: The components from the OpenAPI document. /// - asSwiftSafeName: A converted function from user-provided strings /// to strings safe to be used as a Swift identifier. + /// - Throws: if `map` contains any references; see discussion for details. + /// + /// This function will throw an error if `map` contains any references, because: + /// 1. OpenAPI 3.0.3 only supports external path references (cf. 3.1, which supports internal references too) + /// 2. Swift OpenAPI Generator currently only supports OpenAPI 3.0.x. + /// 3. Swift OpenAPI Generator currently doesn't support external references. static func all( from map: OpenAPI.PathItem.Map, in components: OpenAPI.Components, asSwiftSafeName: @escaping (String) -> String - ) -> [OperationDescription] { - map.flatMap { path, value in - value.endpoints.map { endpoint in + ) throws -> [OperationDescription] { + try map.flatMap { path, value in + let value = try value.resolve(in: components) + return value.endpoints.map { endpoint in OperationDescription( path: path, endpoint: endpoint, diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/TypesFileTranslator.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/TypesFileTranslator.swift index 0e76feb04..7aa46c68c 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/TypesFileTranslator.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/TypesFileTranslator.swift @@ -41,19 +41,17 @@ struct TypesFileTranslator: FileTranslator { + config.additionalImports .map { ImportDescription(moduleName: $0) } - let apiProtocol = translateAPIProtocol(doc.paths) + let apiProtocol = try translateAPIProtocol(doc.paths) let serversDecl = translateServers(doc.servers) let components = try translateComponents(doc.components) - let operationDescriptions = - OperationDescription - .all( - from: parsedOpenAPI.paths, - in: doc.components, - asSwiftSafeName: swiftSafeName - ) + let operationDescriptions = try OperationDescription.all( + from: parsedOpenAPI.paths, + in: doc.components, + asSwiftSafeName: swiftSafeName + ) let operations = try translateOperations(operationDescriptions) let typesFile = FileDescription( diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateAPIProtocol.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateAPIProtocol.swift index 2fb5497aa..422320ee0 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateAPIProtocol.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateAPIProtocol.swift @@ -19,9 +19,10 @@ extension TypesFileTranslator { /// per HTTP operation defined in the OpenAPI document. /// - Parameter paths: The paths object from the OpenAPI document. /// - Returns: A protocol declaration. - func translateAPIProtocol(_ paths: OpenAPI.PathItem.Map) -> Declaration { + /// - Throws: If `paths` contains any references. + func translateAPIProtocol(_ paths: OpenAPI.PathItem.Map) throws -> Declaration { - let operations = OperationDescription.all( + let operations = try OperationDescription.all( from: paths, in: components, asSwiftSafeName: swiftSafeName diff --git a/Tests/OpenAPIGeneratorCoreTests/Parser/Test_YamsParser.swift b/Tests/OpenAPIGeneratorCoreTests/Parser/Test_YamsParser.swift index 18d9f52b2..9cbed1933 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Parser/Test_YamsParser.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Parser/Test_YamsParser.swift @@ -118,8 +118,12 @@ final class Test_YamsParser: Test_Core { description: Success """ - let expected = - "/foo.yaml: error: Expected to find `responses` key for the **GET** endpoint under `/system` but it is missing." + let expected = """ + /foo.yaml: error: Found neither a $ref nor a PathItem in Document.paths['/system']. + + PathItem could not be decoded because: + Expected to find `responses` key for the **GET** endpoint under `/system` but it is missing.. + """ assertThrownError(try _test(yaml), expectedDiagnostic: expected) } diff --git a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift index 8245303a3..510db7027 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift @@ -347,6 +347,103 @@ final class SnippetBasedReferenceTests: XCTestCase { ) } + func testComponentsSchemasAllOfOneStringRef() throws { + try self.assertSchemasTranslation( + """ + schemas: + A: + type: string + MyAllOf: + allOf: + - $ref: '#/components/schemas/A' + """, + """ + public enum Schemas { + public typealias A = Swift.String + public struct MyAllOf: Codable, Equatable, Hashable, Sendable { + public var value1: Components.Schemas.A + public init(value1: Components.Schemas.A) { + self.value1 = value1 + } + public init(from decoder: any Decoder) throws { + value1 = try .init(from: decoder) + } + public func encode(to encoder: any Encoder) throws { + try value1.encode(to: encoder) + } + } + } + """ + ) + } + + func testComponentsSchemasObjectWithRequiredAllOfOneStringRefProperty() throws { + try self.assertSchemasTranslation( + """ + schemas: + A: + type: string + B: + type: object + required: + - c + properties: + c: + allOf: + - $ref: "#/components/schemas/A" + """, + """ + public enum Schemas { + public typealias A = Swift.String + public struct B: Codable, Equatable, Hashable, Sendable { + public struct cPayload: Codable, Equatable, Hashable, Sendable { + public var value1: Components.Schemas.A + public init(value1: Components.Schemas.A) { self.value1 = value1 } + public init(from decoder: any Decoder) throws { value1 = try .init(from: decoder) } + public func encode(to encoder: any Encoder) throws { try value1.encode(to: encoder) } + } + public var c: Components.Schemas.B.cPayload + public init(c: Components.Schemas.B.cPayload) { self.c = c } + public enum CodingKeys: String, CodingKey { case c } + } + } + """ + ) + } + + func testComponentsSchemasObjectWithOptionalAllOfOneStringRefProperty() throws { + try self.assertSchemasTranslation( + """ + schemas: + A: + type: string + B: + type: object + required: [] + properties: + c: + allOf: + - $ref: "#/components/schemas/A" + """, + """ + public enum Schemas { + public typealias A = Swift.String + public struct B: Codable, Equatable, Hashable, Sendable { + public struct cPayload: Codable, Equatable, Hashable, Sendable { + public var value1: Components.Schemas.A + public init(value1: Components.Schemas.A) { self.value1 = value1 } + public init(from decoder: any Decoder) throws { value1 = try .init(from: decoder) } + public func encode(to encoder: any Encoder) throws { try value1.encode(to: encoder) } + } + public var c: Components.Schemas.B.cPayload? + public init(c: Components.Schemas.B.cPayload? = nil) { self.c = c } + public enum CodingKeys: String, CodingKey { case c } + } + } + """ + ) + } + func testComponentsSchemasEnum() throws { try self.assertSchemasTranslation( """ @@ -629,6 +726,52 @@ final class SnippetBasedReferenceTests: XCTestCase { """ ) } + + func testPathsSimplestCase() throws { + try self.assertPathsTranslation( + """ + /health: + get: + operationId: getHealth + responses: + '200': + description: A success response with a greeting. + content: + text/plain: + schema: + type: string + """, + """ + public protocol APIProtocol: Sendable { + func getHealth(_ input: Operations.getHealth.Input) async throws -> Operations.getHealth.Output + } + """ + ) + } + + func testPathWithPathItemReference() throws { + XCTAssertThrowsError( + try self.assertPathsTranslation( + """ + /health: + get: + operationId: getHealth + responses: + '200': + description: A success response with a greeting. + content: + text/plain: + schema: + type: string + /health2: + $ref: "#/paths/~1health" + """, + """ + unused: This test throws an error. + """ + ) + ) + } } extension SnippetBasedReferenceTests { @@ -720,6 +863,19 @@ extension SnippetBasedReferenceTests { let translation = try translator.translateComponentRequestBodies(translator.components.requestBodies) try XCTAssertSwiftEquivalent(translation, expectedSwift, file: file, line: line) } + + func assertPathsTranslation( + _ pathsYAML: String, + componentsYAML: String = "{}", + _ expectedSwift: String, + file: StaticString = #filePath, + line: UInt = #line + ) throws { + let translator = try makeTypesTranslator(componentsYAML: componentsYAML) + let paths = try YAMLDecoder().decode(OpenAPI.PathItem.Map.self, from: pathsYAML) + let translation = try translator.translateAPIProtocol(paths) + try XCTAssertSwiftEquivalent(translation, expectedSwift, file: file, line: line) + } } private func XCTAssertEqualWithDiff(