From 89d49d2f2b7e669b37fdf6f262f457e43fd04639 Mon Sep 17 00:00:00 2001 From: Si Beaumont Date: Wed, 2 Oct 2024 11:28:48 +0100 Subject: [PATCH 1/3] ci: Add missing environment variable to integration-test (#641) ### Motivation When we migrated the CI to Github Actions we lost an important configuration for the integration test that makes sure we're using the copy of this repo from the PR, rather than cloning from upstream. This can be seen in the logs of recent PR jobs: ``` ** Cloning https://github.com/apple/swift-openapi-generator to /tmp/run-integration-test.sh.SUVBe45RMQ/swift-openapi-generator Cloning into '/tmp/run-integration-test.sh.SUVBe45RMQ/swift-openapi-generator'... ** Extracting name for Swift package: /__w/swift-openapi-generator/swift-openapi-generator ** Overriding dependency in /tmp/run-integration-test.sh.SUVBe45RMQ/swift-openapi-generator/IntegrationTest on swift-openapi-generator to use /__w/swift-openapi-generator/swift-openapi-generator ``` This used to be specified in the Docker Compose file as an environment variable but dropped off during migration. ### Modifications Add the environment variable back to use `file://${GITHUB_WORKSPACE}` for the clone. Note that one cannot use the `env:` map when making use of a reusable GitHub workflow with `uses:` so it's been added in the command passed to the matrix job, itself. ### Result The integration test CI will use this repo for the test itself so we can make sure we're testing the right thing. ### Test Plan The logs on _this_ PR job show that we're now cloning from the GitHub workspace: ``` ** Cloning file:///__w/swift-openapi-generator/swift-openapi-generator to /tmp/run-integration-test.sh.OWgemDungm/swift-openapi-generator Cloning into '/tmp/run-integration-test.sh.OWgemDungm/swift-openapi-generator'... Note: switching to '9664e9cb2170a5fa8a1bad3266891d4b4248e6ba'. ... ** Extracting name for Swift package: /__w/swift-openapi-generator/swift-openapi-generator ** Overriding dependency in /tmp/run-integration-test.sh.OWgemDungm/swift-openapi-generator/IntegrationTest on swift-openapi-generator to use /__w/swift-openapi-generator/swift-openapi-generator ``` --- .github/workflows/pull_request.yml | 2 +- .github/workflows/scheduled.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 76f71961..8c598a5a 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -33,7 +33,7 @@ jobs: uses: apple/swift-nio/.github/workflows/swift_matrix.yml@main with: name: "Integration test" - matrix_linux_command: "apt-get update -yq && apt-get install -yq jq && ./scripts/run-integration-test.sh" + matrix_linux_command: "apt-get update -yq && apt-get install -yq jq && SWIFT_OPENAPI_GENERATOR_REPO_URL=file://${GITHUB_WORKSPACE} ./scripts/run-integration-test.sh" matrix_linux_5_8_enabled: false matrix_linux_nightly_main_enabled: false diff --git a/.github/workflows/scheduled.yml b/.github/workflows/scheduled.yml index cb5f46df..6c367b83 100644 --- a/.github/workflows/scheduled.yml +++ b/.github/workflows/scheduled.yml @@ -20,7 +20,7 @@ jobs: uses: apple/swift-nio/.github/workflows/swift_matrix.yml@main with: name: "Integration test" - matrix_linux_command: "apt-get update -yq && apt-get install -yq jq && ./scripts/run-integration-test.sh" + matrix_linux_command: "apt-get update -yq && apt-get install -yq jq && SWIFT_OPENAPI_GENERATOR_REPO_URL=file://${GITHUB_WORKSPACE} ./scripts/run-integration-test.sh" matrix_linux_5_8_enabled: false example-packages: From de51f3d9fd470464f8b44c84518a451b540aed2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paul=20Heidekr=C3=BCger?= Date: Thu, 3 Oct 2024 10:59:28 -0700 Subject: [PATCH 2/3] Docs: Reference Handling of Custom Terminating Byte Sequences (#625) ### Motivation See: https://github.com/apple/swift-openapi-runtime/pull/115 ### Modifications This PR references the changes introduced by TODO in the documentation. ### Result Users planning to generate code for OpenAPI specs with custom terminating byte sequences will hopefully find it easier to make the necessary changes in their code. Please let me know if there are other places where these changes could be mentioned. ### Test Plan . --------- Co-authored-by: Honza Dvorsky --- .../Documentation.docc/Articles/Useful-OpenAPI-patterns.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/swift-openapi-generator/Documentation.docc/Articles/Useful-OpenAPI-patterns.md b/Sources/swift-openapi-generator/Documentation.docc/Articles/Useful-OpenAPI-patterns.md index 7c6140ab..258848f3 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Articles/Useful-OpenAPI-patterns.md +++ b/Sources/swift-openapi-generator/Documentation.docc/Articles/Useful-OpenAPI-patterns.md @@ -113,8 +113,9 @@ The returned binary body contains the raw events, and the stream can be split up - encode: `AsyncSequence.asEncodedJSONSequence(encoder:)` - Server-sent Events - decode (if data is JSON): `AsyncSequence>.asDecodedServerSentEventsWithJSONData(of:decoder:)` + - decode (if data is JSON with a non-JSON terminating byte sequence): `AsyncSequence>.asDecodedServerSentEventsWithJSONData(of:decoder:while:)` - encode (if data is JSON): `AsyncSequence.asEncodedServerSentEventsWithJSONData(encoder:)` - - decode (for other data): `AsyncSequence>.asDecodedServerSentEvents()` + - decode (for other data): `AsyncSequence>.asDecodedServerSentEvents(while:)` - encode (for other data): `AsyncSequence.asEncodedServerSentEvents()` See the `event-streams-*` client and server examples in to learn how to produce and consume these sequences. From 07ee940041497e3fd991a506bf2f96d321ec53f3 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Fri, 4 Oct 2024 15:52:26 +0200 Subject: [PATCH 3/3] [Refactor] Introduce TranslatorContext (1/2) (#638) ### Motivation To make upcoming PRs less noisy, we need a way to propagate configuration all the way deep to types like TypeAssigner/TypeMatcher, which historically didn't have that need. ### Modifications Introduced a new wrapper type called TranslatorContext, which wraps the existing closure that converts arbitrary strings into safe Swift names (which might in the future also need to be further customized). The idea is that this new struct is what we'd attach more config to shortly. There'll be a few more smaller PRs like this, I'm breaking them up for easier review. ### Result Generalized the way to propagate config into low level translator utilities. ### Test Plan All tests still pass. --- .../ClientTranslator/ClientTranslator.swift | 3 +- .../CommonTranslations/SwiftSafeNames.swift | 11 +------ .../translateAllAnyOneOf.swift | 4 +-- .../translateObjectStruct.swift | 4 +-- .../translateStringEnum.swift | 4 +-- .../CommonTypes/DiscriminatorExtensions.swift | 4 ++- .../CommonTypes/StructBlueprint.swift | 7 ++--- .../Translator/FileTranslator.swift | 16 ++++++++++ .../Multipart/MultipartContentInspector.swift | 2 +- .../Multipart/translateMultipart.swift | 10 +++---- .../Operations/OperationDescription.swift | 21 ++++++------- .../Parameters/TypedParameter.swift | 9 +++--- .../Parameters/translateParameter.swift | 2 +- .../RequestBody/translateRequestBody.swift | 2 +- .../Responses/TypedResponseHeader.swift | 9 +++--- .../Responses/translateResponse.swift | 4 +-- .../Responses/translateResponseHeader.swift | 2 +- .../ServerTranslator/ServerTranslator.swift | 6 +--- .../TypeAssignment/TypeAssigner.swift | 30 +++++++++---------- .../TypeAssignment/TypeMatcher.swift | 7 ++--- .../TypesTranslator/TypesFileTranslator.swift | 6 +--- .../translateAPIProtocol.swift | 4 +-- .../TypesTranslator/translateOperations.swift | 4 +-- .../TypesTranslator/translateServers.swift | 2 +- .../Extensions/Test_String.swift | 2 +- .../TestUtilities.swift | 6 ++-- .../Test_OperationDescription.swift | 2 +- .../SnippetBasedReferenceTests.swift | 10 ++----- 28 files changed, 92 insertions(+), 101 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/ClientTranslator.swift b/Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/ClientTranslator.swift index 29381dfb..4df9a035 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/ClientTranslator.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/ClientTranslator.swift @@ -37,8 +37,7 @@ struct ClientFileTranslator: FileTranslator { let imports = Constants.File.clientServerImports + config.additionalImports.map { ImportDescription(moduleName: $0) } - let clientMethodDecls = - try OperationDescription.all(from: doc.paths, in: components, asSwiftSafeName: swiftSafeName) + let clientMethodDecls = try OperationDescription.all(from: doc.paths, in: components, context: context) .map(translateClientMethod(_:)) let clientStructPropertyDecl: Declaration = .commentable( diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift index 35d04086..9a52bc04 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift @@ -13,16 +13,7 @@ //===----------------------------------------------------------------------===// import Foundation -extension FileTranslator { - - /// Returns a copy of the string modified to be a valid Swift identifier. - /// - /// - Parameter string: The string to convert to be safe for Swift. - /// - Returns: A Swift-safe version of the input string. - func swiftSafeName(for string: String) -> String { string.safeForSwiftCode } -} - -fileprivate extension String { +extension String { /// Returns a string sanitized to be usable as a Swift identifier. /// diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift index ad7a1b63..b007d6e2 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift @@ -78,7 +78,7 @@ extension TypesFileTranslator { originalName: key, typeUsage: propertyType, associatedDeclarations: associatedDeclarations, - asSwiftSafeName: swiftSafeName + context: context ) var referenceStack = ReferenceStack.empty let isKeyValuePairSchema = try TypeMatcher.isKeyValuePair( @@ -209,7 +209,7 @@ extension TypesFileTranslator { let decoder: Declaration if let discriminator { let originalName = discriminator.propertyName - let swiftName = swiftSafeName(for: originalName) + let swiftName = context.asSwiftSafeName(originalName) codingKeysDecls = [ .enum( accessModifier: config.access, diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateObjectStruct.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateObjectStruct.swift index 4b48e2e1..704d5b61 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateObjectStruct.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateObjectStruct.swift @@ -100,7 +100,7 @@ extension TypesFileTranslator { originalName: key, typeUsage: propertyType, associatedDeclarations: associatedDeclarations, - asSwiftSafeName: swiftSafeName + context: context ) } @@ -175,7 +175,7 @@ extension TypesFileTranslator { default: .emptyInit, isSerializedInTopLevelDictionary: false, associatedDeclarations: associatedDeclarations, - asSwiftSafeName: swiftSafeName + context: context ) return (.allowingAdditionalProperties, extraProperty) } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStringEnum.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStringEnum.swift index 3bc47bb8..9add9482 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStringEnum.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStringEnum.swift @@ -49,11 +49,11 @@ 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 { return (swiftSafeName(for: ""), .string("")) } + if isNullable && anyValue is Void { return (context.asSwiftSafeName(""), .string("")) } guard let rawValue = anyValue as? String else { throw GenericError(message: "Disallowed value for a string enum '\(typeName)': \(anyValue)") } - let caseName = swiftSafeName(for: rawValue) + let caseName = context.asSwiftSafeName(rawValue) return (caseName, .string(rawValue)) case .integer: let rawValue: Int diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/DiscriminatorExtensions.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/DiscriminatorExtensions.swift index b27a61c1..1bc1cd7f 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/DiscriminatorExtensions.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/DiscriminatorExtensions.swift @@ -79,7 +79,9 @@ extension FileTranslator { /// component. /// - Parameter type: The `OneOfMappedType` for which to determine the case name. /// - Returns: A string representing the safe Swift name for the specified `OneOfMappedType`. - func safeSwiftNameForOneOfMappedType(_ type: OneOfMappedType) -> String { swiftSafeName(for: type.rawNames[0]) } + func safeSwiftNameForOneOfMappedType(_ type: OneOfMappedType) -> String { + context.asSwiftSafeName(type.rawNames[0]) + } } extension OpenAPI.Discriminator { diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/StructBlueprint.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/StructBlueprint.swift index 2b3d7123..9eef5d6d 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/StructBlueprint.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/StructBlueprint.swift @@ -146,15 +146,14 @@ struct PropertyBlueprint { /// referring to them in the property. var associatedDeclarations: [Declaration] = [] - /// A converted function from user-provided strings to strings - /// safe to be used as a Swift identifier. - var asSwiftSafeName: (String) -> String + /// A set of configuration values that inform translation. + var context: TranslatorContext } extension PropertyBlueprint { /// A name that is verified to be a valid Swift identifier. - var swiftSafeName: String { asSwiftSafeName(originalName) } + var swiftSafeName: String { context.asSwiftSafeName(originalName) } /// The JSON path to the property. /// diff --git a/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift b/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift index ecbf9771..4f246521 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift @@ -43,3 +43,19 @@ protocol FileTranslator { /// - Throws: An error if translation encounters issues or errors during the process. func translateFile(parsedOpenAPI: ParsedOpenAPIRepresentation) throws -> StructuredSwiftRepresentation } + +extension FileTranslator { + + /// A new context from the file translator. + var context: TranslatorContext { TranslatorContext(asSwiftSafeName: { $0.safeForSwiftCode }) } +} + +/// A set of configuration values for concrete file translators. +struct TranslatorContext { + + /// A closure that returns a copy of the string modified to be a valid Swift identifier. + /// + /// - Parameter string: The string to convert to be safe for Swift. + /// - Returns: A Swift-safe version of the input string. + var asSwiftSafeName: (String) -> String +} diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift b/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift index 1c27dbd9..f5a83882 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift @@ -120,7 +120,7 @@ extension FileTranslator { } var parts: [MultipartSchemaTypedContent] = try topLevelObject.properties.compactMap { (key, value) -> MultipartSchemaTypedContent? in - let swiftSafeName = swiftSafeName(for: key) + let swiftSafeName = context.asSwiftSafeName(key) let typeName = typeName.appending( swiftComponent: swiftSafeName + Constants.Global.inlineTypeSuffix, jsonComponent: key diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Multipart/translateMultipart.swift b/Sources/_OpenAPIGeneratorCore/Translator/Multipart/translateMultipart.swift index 186cfda8..2af6cc8f 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Multipart/translateMultipart.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Multipart/translateMultipart.swift @@ -64,7 +64,7 @@ extension TypesFileTranslator { typeUsage: headersTypeName.asUsage, default: headersStructBlueprint.hasEmptyInit ? .emptyInit : nil, associatedDeclarations: [headersStructDecl], - asSwiftSafeName: swiftSafeName + context: context ) } else { headersProperty = nil @@ -90,7 +90,7 @@ extension TypesFileTranslator { originalName: Constants.Operation.Body.variableName, typeUsage: bodyTypeUsage, associatedDeclarations: associatedDeclarations, - asSwiftSafeName: swiftSafeName + context: context ) let structDecl = translateStructBlueprint( .init( @@ -137,7 +137,7 @@ extension TypesFileTranslator { switch part { case .documentedTyped(let documentedPart): let caseDecl: Declaration = .enumCase( - name: swiftSafeName(for: documentedPart.originalName), + name: context.asSwiftSafeName(documentedPart.originalName), kind: .nameWithAssociatedValues([.init(type: .init(part.wrapperTypeUsage))]) ) let decl = try translateMultipartPartContent( @@ -404,7 +404,7 @@ extension FileTranslator { switch part { case .documentedTyped(let part): let originalName = part.originalName - let identifier = swiftSafeName(for: originalName) + let identifier = context.asSwiftSafeName(originalName) let contentType = part.partInfo.contentType let partTypeName = part.typeName let schema = part.schema @@ -613,7 +613,7 @@ extension FileTranslator { switch part { case .documentedTyped(let part): let originalName = part.originalName - let identifier = swiftSafeName(for: originalName) + let identifier = context.asSwiftSafeName(originalName) let contentType = part.partInfo.contentType let headersTypeName = part.typeName.appending( swiftComponent: Constants.Operation.Output.Payload.Headers.typeName, diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Operations/OperationDescription.swift b/Sources/_OpenAPIGeneratorCore/Translator/Operations/OperationDescription.swift index aea727dc..2a72252b 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Operations/OperationDescription.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Operations/OperationDescription.swift @@ -31,9 +31,8 @@ struct OperationDescription { /// The OpenAPI components, used to resolve JSON references. var components: OpenAPI.Components - /// A converted function from user-provided strings to strings - /// safe to be used as a Swift identifier. - var asSwiftSafeName: (String) -> String + /// A set of configuration values that inform translation. + var context: TranslatorContext /// The OpenAPI operation object. var operation: OpenAPI.Operation { endpoint.operation } @@ -52,7 +51,7 @@ extension OperationDescription { /// - Parameters: /// - map: The paths from the OpenAPI document. /// - components: The components from the OpenAPI document. - /// - asSwiftSafeName: A converted function from user-provided strings + /// - context: A set of configuration values that inform translation. /// to strings safe to be used as a Swift identifier. /// - Returns: An array of `OperationDescription` instances, each representing /// an operation discovered in the provided paths. @@ -62,11 +61,9 @@ extension OperationDescription { /// 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 - ) throws -> [OperationDescription] { + static func all(from map: OpenAPI.PathItem.Map, in components: OpenAPI.Components, context: TranslatorContext) + throws -> [OperationDescription] + { try map.flatMap { path, value in let value = try value.resolve(in: components) return value.endpoints.map { endpoint in @@ -75,7 +72,7 @@ extension OperationDescription { endpoint: endpoint, pathParameters: value.parameters, components: components, - asSwiftSafeName: asSwiftSafeName + context: context ) } } @@ -86,7 +83,7 @@ extension OperationDescription { /// Uses the `operationID` value in the OpenAPI operation, if one was /// specified. Otherwise, computes a unique name from the operation's /// path and HTTP method. - var methodName: String { asSwiftSafeName(operationID) } + var methodName: String { context.asSwiftSafeName(operationID) } /// Returns the identifier for the operation. /// @@ -295,7 +292,7 @@ extension OperationDescription { } let newPath = OpenAPI.Path(newComponents, trailingSlash: path.trailingSlash) let names: [Expression] = orderedPathParameters.map { param in - .identifierPattern("input").dot("path").dot(asSwiftSafeName(param)) + .identifierPattern("input").dot("path").dot(context.asSwiftSafeName(param)) } let arrayExpr: Expression = .literal(.array(names)) return (newPath.rawValue, arrayExpr) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift b/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift index 66a0ce26..8f66192b 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift @@ -34,9 +34,8 @@ struct TypedParameter { /// The coding strategy appropriate for this parameter. var codingStrategy: CodingStrategy - /// A converted function from user-provided strings to strings - /// safe to be used as a Swift identifier. - var asSwiftSafeName: (String) -> String + /// A set of configuration values that inform translation. + var context: TranslatorContext } extension TypedParameter: CustomStringConvertible { @@ -49,7 +48,7 @@ extension TypedParameter { var name: String { parameter.name } /// The name of the parameter sanitized to be a valid Swift identifier. - var variableName: String { asSwiftSafeName(name) } + var variableName: String { context.asSwiftSafeName(name) } /// A Boolean value that indicates whether the parameter must be specified /// when performing the OpenAPI operation. @@ -208,7 +207,7 @@ extension FileTranslator { explode: explode, typeUsage: usage, codingStrategy: codingStrategy, - asSwiftSafeName: swiftSafeName + context: context ) } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Parameters/translateParameter.swift b/Sources/_OpenAPIGeneratorCore/Translator/Parameters/translateParameter.swift index 0176a8cd..3dcdbe75 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Parameters/translateParameter.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Parameters/translateParameter.swift @@ -42,7 +42,7 @@ extension TypesFileTranslator { originalName: parameter.name, typeUsage: parameter.typeUsage, associatedDeclarations: associatedDeclarations, - asSwiftSafeName: swiftSafeName + context: context ) } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/translateRequestBody.swift b/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/translateRequestBody.swift index 76c0c6f7..09e7776d 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/translateRequestBody.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/translateRequestBody.swift @@ -92,7 +92,7 @@ extension TypesFileTranslator { typeUsage: bodyEnumTypeUsage, default: nil, associatedDeclarations: extraDecls, - asSwiftSafeName: swiftSafeName + context: context ) return bodyProperty } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift b/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift index 4cf5a85a..ad11fcbc 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift @@ -32,15 +32,14 @@ struct TypedResponseHeader { /// The coding strategy appropriate for this parameter. var codingStrategy: CodingStrategy - /// A converted function from user-provided strings to strings - /// safe to be used as a Swift identifier. - var asSwiftSafeName: (String) -> String + /// A set of configuration values that inform translation. + var context: TranslatorContext } extension TypedResponseHeader { /// The name of the header sanitized to be a valid Swift identifier. - var variableName: String { asSwiftSafeName(name) } + var variableName: String { context.asSwiftSafeName(name) } /// A Boolean value that indicates whether the response header can /// be omitted in the HTTP response. @@ -152,7 +151,7 @@ extension FileTranslator { schema: schema, typeUsage: usage, codingStrategy: codingStrategy, - asSwiftSafeName: swiftSafeName + context: context ) } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponse.swift b/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponse.swift index 13a44064..60f2bb95 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponse.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponse.swift @@ -50,7 +50,7 @@ extension TypesFileTranslator { typeUsage: headersTypeName.asUsage, default: headersStructBlueprint.hasEmptyInit ? .emptyInit : nil, associatedDeclarations: [headersStructDecl], - asSwiftSafeName: swiftSafeName + context: context ) } else { headersProperty = nil @@ -92,7 +92,7 @@ extension TypesFileTranslator { typeUsage: contentTypeUsage, default: hasNoContent ? .nil : nil, associatedDeclarations: [contentEnumDecl], - asSwiftSafeName: swiftSafeName + context: context ) } else { bodyProperty = nil diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseHeader.swift b/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseHeader.swift index 229d575a..37feea59 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseHeader.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseHeader.swift @@ -78,7 +78,7 @@ extension TypesFileTranslator { typeUsage: typeUsage, default: header.header.required ? nil : .nil, associatedDeclarations: associatedDeclarations, - asSwiftSafeName: swiftSafeName + context: context ) } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/ServerTranslator/ServerTranslator.swift b/Sources/_OpenAPIGeneratorCore/Translator/ServerTranslator/ServerTranslator.swift index e62df4f6..3f54d4ad 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/ServerTranslator/ServerTranslator.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/ServerTranslator/ServerTranslator.swift @@ -35,11 +35,7 @@ struct ServerFileTranslator: FileTranslator { let imports = Constants.File.clientServerImports + config.additionalImports.map { ImportDescription(moduleName: $0) } - let allOperations = try OperationDescription.all( - from: doc.paths, - in: components, - asSwiftSafeName: swiftSafeName - ) + let allOperations = try OperationDescription.all(from: doc.paths, in: components, context: context) let (registerHandlersDecl, serverMethodDecls) = try translateRegisterHandlers(allOperations) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift index 72e18f8a..2c8ab438 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift @@ -41,9 +41,8 @@ import Foundation /// cases when it's a simple string schema. struct TypeAssigner { - /// A converted function from user-provided strings to strings - /// safe to be used as a Swift identifier. - var asSwiftSafeName: (String) -> String + /// A set of configuration values that inform translation. + var context: TranslatorContext /// Returns a type name for an OpenAPI-named component type. /// @@ -60,7 +59,7 @@ struct TypeAssigner { /// - Returns: A Swift type name for the specified component type. func typeName(forComponentOriginallyNamed originalName: String, in location: TypeLocation) -> TypeName { typeName(forLocation: location) - .appending(swiftComponent: asSwiftSafeName(originalName), jsonComponent: originalName) + .appending(swiftComponent: context.asSwiftSafeName(originalName), jsonComponent: originalName) } /// Returns the type name for an OpenAPI-named component namespace. @@ -126,7 +125,7 @@ struct TypeAssigner { if let ref = TypeMatcher.multipartElementTypeReferenceIfReferenceable(schema: schema, encoding: encoding) { multipartBodyElementTypeName = try typeName(for: ref) } else { - let swiftSafeName = asSwiftSafeName(hint) + let swiftSafeName = context.asSwiftSafeName(hint) multipartBodyElementTypeName = parent.appending( swiftComponent: swiftSafeName + Constants.Global.inlineTypeSuffix, jsonComponent: hint @@ -328,7 +327,7 @@ struct TypeAssigner { inParent parent: TypeName, subtype: SubtypeNamingMethod ) throws -> TypeUsage { - let typeMatcher = TypeMatcher(asSwiftSafeName: asSwiftSafeName) + let typeMatcher = TypeMatcher(context: context) // Check if this type can be simply referenced without // creating a new inline type. if let referenceableType = try typeMatcher.tryMatchReferenceableType(for: schema, components: components) { @@ -342,7 +341,7 @@ struct TypeAssigner { } return baseType.appending( - swiftComponent: asSwiftSafeName(originalName) + suffix, + swiftComponent: context.asSwiftSafeName(originalName) + suffix, jsonComponent: jsonReferenceComponentOverride ?? originalName ) .asUsage.withOptional(try typeMatcher.isOptional(schema, components: components)) @@ -405,7 +404,7 @@ struct TypeAssigner { of componentType: Component.Type ) -> TypeName { typeName(for: Component.self) - .appending(swiftComponent: asSwiftSafeName(key.rawValue), jsonComponent: key.rawValue) + .appending(swiftComponent: context.asSwiftSafeName(key.rawValue), jsonComponent: key.rawValue) } /// Returns a type name for a JSON reference. @@ -469,7 +468,8 @@ struct TypeAssigner { guard case let .component(name) = reference else { throw JSONReferenceParsingError.nonComponentPathsUnsupported(reference.name) } - return typeName(for: componentType).appending(swiftComponent: asSwiftSafeName(name), jsonComponent: name) + return typeName(for: componentType) + .appending(swiftComponent: context.asSwiftSafeName(name), jsonComponent: name) } /// Returns a type name for the namespace for the specified component type. @@ -493,7 +493,7 @@ struct TypeAssigner { { typeNameForComponents() .appending( - swiftComponent: asSwiftSafeName(componentType.openAPIComponentsKey).uppercasingFirstLetter, + swiftComponent: context.asSwiftSafeName(componentType.openAPIComponentsKey).uppercasingFirstLetter, jsonComponent: componentType.openAPIComponentsKey ) } @@ -526,14 +526,14 @@ struct TypeAssigner { case "application/pdf": return "pdf" case "image/jpeg": return "jpeg" default: - let safedType = asSwiftSafeName(contentType.originallyCasedType) - let safedSubtype = asSwiftSafeName(contentType.originallyCasedSubtype) + let safedType = context.asSwiftSafeName(contentType.originallyCasedType) + let safedSubtype = context.asSwiftSafeName(contentType.originallyCasedSubtype) let prefix = "\(safedType)_\(safedSubtype)" let params = contentType.lowercasedParameterPairs guard !params.isEmpty else { return prefix } let safedParams = params.map { pair in - pair.split(separator: "=").map { asSwiftSafeName(String($0)) }.joined(separator: "_") + pair.split(separator: "=").map { context.asSwiftSafeName(String($0)) }.joined(separator: "_") } .joined(separator: "_") return prefix + "_" + safedParams @@ -545,10 +545,10 @@ struct TypeAssigner { extension FileTranslator { /// A configured type assigner. - var typeAssigner: TypeAssigner { TypeAssigner(asSwiftSafeName: swiftSafeName) } + var typeAssigner: TypeAssigner { TypeAssigner(context: context) } /// A configured type matcher. - var typeMatcher: TypeMatcher { TypeMatcher(asSwiftSafeName: swiftSafeName) } + var typeMatcher: TypeMatcher { TypeMatcher(context: context) } } /// An error used during the parsing of JSON references specified in an diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift index 08ac4a1e..8c8c252a 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeMatcher.swift @@ -16,9 +16,8 @@ import OpenAPIKit /// A set of functions that match Swift types onto OpenAPI types. struct TypeMatcher { - /// A converted function from user-provided strings to strings - /// safe to be used as a Swift identifier. - var asSwiftSafeName: (String) -> String + /// A set of configuration values that inform translation. + var context: TranslatorContext /// Returns the type name of a built-in type that matches the specified /// schema. @@ -71,7 +70,7 @@ struct TypeMatcher { test: { (schema) -> TypeUsage? in if let builtinType = Self._tryMatchBuiltinNonRecursive(for: schema) { return builtinType } guard case let .reference(ref, _) = schema else { return nil } - return try TypeAssigner(asSwiftSafeName: asSwiftSafeName).typeName(for: ref).asUsage + return try TypeAssigner(context: context).typeName(for: ref).asUsage }, matchedArrayHandler: { elementType, nullableItems in nullableItems ? elementType.asOptional.asArray : elementType.asArray diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/TypesFileTranslator.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/TypesFileTranslator.swift index fb0cc056..3ad52eb0 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/TypesFileTranslator.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/TypesFileTranslator.swift @@ -45,11 +45,7 @@ struct TypesFileTranslator: FileTranslator { let multipartSchemaNames = try parseSchemaNamesUsedInMultipart(paths: doc.paths, components: doc.components) let components = try translateComponents(doc.components, multipartSchemaNames: multipartSchemaNames) - let operationDescriptions = try OperationDescription.all( - from: doc.paths, - in: doc.components, - asSwiftSafeName: swiftSafeName - ) + let operationDescriptions = try OperationDescription.all(from: doc.paths, in: doc.components, context: context) 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 3b061854..2f9134ed 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateAPIProtocol.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateAPIProtocol.swift @@ -22,7 +22,7 @@ extension TypesFileTranslator { /// - Throws: If `paths` contains any references. func translateAPIProtocol(_ paths: OpenAPI.PathItem.Map) throws -> Declaration { - let operations = try OperationDescription.all(from: paths, in: components, asSwiftSafeName: swiftSafeName) + let operations = try OperationDescription.all(from: paths, in: components, context: context) let functionDecls = operations.map(translateAPIProtocolDeclaration(operation:)) let protocolDescription = ProtocolDescription( @@ -38,7 +38,7 @@ extension TypesFileTranslator { /// Returns an extension to the `APIProtocol` protocol, with some syntactic sugar APIs. func translateAPIProtocolExtension(_ paths: OpenAPI.PathItem.Map) throws -> Declaration { - let operations = try OperationDescription.all(from: paths, in: components, asSwiftSafeName: swiftSafeName) + let operations = try OperationDescription.all(from: paths, in: components, context: context) // This looks for all initializers in the operation input struct and creates a flattened function. let flattenedOperations = try operations.flatMap { operation in diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateOperations.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateOperations.swift index df3576ac..55d01890 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateOperations.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateOperations.swift @@ -71,7 +71,7 @@ extension TypesFileTranslator { typeUsage: structTypeName.asUsage, default: defaultValue, associatedDeclarations: [structDecl], - asSwiftSafeName: swiftSafeName + context: context ) } let bodyProperty = try parseRequestBodyAsProperty( @@ -89,7 +89,7 @@ extension TypesFileTranslator { originalName: Constants.Operation.AcceptableContentType.variableName, typeUsage: description.acceptableArrayName, default: .expression(.dot("defaultValues").call([])), - asSwiftSafeName: swiftSafeName + context: context ) extraHeaderProperties = [acceptPropertyBlueprint] } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServers.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServers.swift index bb7a6552..84ed698e 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServers.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServers.swift @@ -26,7 +26,7 @@ extension TypesFileTranslator { func translateServer(index: Int, server: OpenAPI.Server) -> Declaration { let methodName = "\(Constants.ServerURL.propertyPrefix)\(index+1)" let safeVariables = server.variables.map { (key, value) in - (originalKey: key, swiftSafeKey: swiftSafeName(for: key), value: value) + (originalKey: key, swiftSafeKey: context.asSwiftSafeName(key), value: value) } let parameters: [ParameterDescription] = safeVariables.map { (originalKey, swiftSafeKey, value) in .init(label: swiftSafeKey, type: .init(TypeName.string), defaultValue: .literal(value.default)) diff --git a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_String.swift b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_String.swift index c9c58c94..9d10b45c 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_String.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_String.swift @@ -64,7 +64,7 @@ final class Test_String: Test_Core { ("application", "application"), ("vendor1+json", "vendor1_plus_json"), ] let translator = makeTranslator() - let asSwiftSafeName: (String) -> String = translator.swiftSafeName + let asSwiftSafeName: (String) -> String = translator.context.asSwiftSafeName for (input, sanitized) in cases { XCTAssertEqual(asSwiftSafeName(input), sanitized) } } } diff --git a/Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift b/Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift index 67d2e94a..a99d4d30 100644 --- a/Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift +++ b/Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift @@ -59,10 +59,12 @@ class Test_Core: XCTestCase { var typeMatcher: TypeMatcher { makeTranslator().typeMatcher } - var asSwiftSafeName: (String) -> String { makeTranslator().swiftSafeName } + var context: TranslatorContext { makeTranslator().context } + + var asSwiftSafeName: (String) -> String { context.asSwiftSafeName } func makeProperty(originalName: String, typeUsage: TypeUsage) -> PropertyBlueprint { - .init(originalName: originalName, typeUsage: typeUsage, asSwiftSafeName: asSwiftSafeName) + .init(originalName: originalName, typeUsage: typeUsage, context: context) } } diff --git a/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift b/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift index a5da80a4..6b37703c 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift @@ -144,7 +144,7 @@ final class Test_OperationDescription: Test_Core { endpoint: endpoint, pathParameters: pathItem.parameters, components: .init(), - asSwiftSafeName: { $0 } + context: .init(asSwiftSafeName: { $0 }) ) } } diff --git a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift index 0196c5ee..f4811396 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift @@ -5284,7 +5284,7 @@ extension SnippetBasedReferenceTests { let operationDescriptions = try OperationDescription.all( from: document.paths, in: document.components, - asSwiftSafeName: types.swiftSafeName + context: types.context ) let operation = try XCTUnwrap(operationDescriptions.first) let generatedTypesStructuredSwift = try types.translateOperationInput(operation) @@ -5343,7 +5343,7 @@ extension SnippetBasedReferenceTests { let operationDescriptions = try OperationDescription.all( from: document.paths, in: document.components, - asSwiftSafeName: types.swiftSafeName + context: types.context ) let operation = try XCTUnwrap(operationDescriptions.first) let generatedTypesStructuredSwift = try types.translateOperationOutput(operation) @@ -5465,11 +5465,7 @@ extension SnippetBasedReferenceTests { ) throws { let (_, _, translator) = try makeTranslators() let paths = try YAMLDecoder().decode(OpenAPI.PathItem.Map.self, from: pathsYAML) - let operations = try OperationDescription.all( - from: paths, - in: .noComponents, - asSwiftSafeName: translator.swiftSafeName - ) + let operations = try OperationDescription.all(from: paths, in: .noComponents, context: translator.context) let (registerHandlersDecl, _) = try translator.translateRegisterHandlers(operations) try XCTAssertSwiftEquivalent(registerHandlersDecl, expectedSwift, file: file, line: line) }