From 4d0c02d1d8c87aeda707c7ae8232de678ba6cf4e Mon Sep 17 00:00:00 2001 From: Franz Busch Date: Wed, 18 Dec 2024 14:26:10 +0100 Subject: [PATCH 1/2] Update release.yml (#705) Update the release.yml file with the latest label changes --- .github/release.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/release.yml b/.github/release.yml index 4d18da56..e29eb846 100644 --- a/.github/release.yml +++ b/.github/release.yml @@ -2,14 +2,13 @@ changelog: categories: - title: SemVer Major labels: - - semver/major + - ⚠️ semver/major - title: SemVer Minor labels: - - semver/minor + - 🆕 semver/minor - title: SemVer Patch labels: - - semver/patch + - 🔨 semver/patch - title: Other Changes labels: - semver/none - - "*" From ad7f0e73246f5083733dbc025097fcdf0fbee2a1 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Wed, 18 Dec 2024 17:11:58 +0100 Subject: [PATCH 2/2] Idiomatic naming strategy as opt-in (#679) ### Motivation Implementation of #683. ### Modifications - Implemented the SOAR-0013 idiomatic naming strategy - Added name overrides - Switched most reference tests to use the new strategy - Updated some docs (I'll update the rest after this lands and gets released, then I can update Examples and IntegrationTest) ### Result SOAR-0013 implemented. ### Test Plan Updated and added new unit tests. --------- Co-authored-by: Si Beaumont Co-authored-by: Si Beaumont --- Sources/_OpenAPIGeneratorCore/Config.swift | 30 ++ .../translateClientMethod.swift | 2 +- .../CommonTranslations/SwiftSafeNames.swift | 89 --- .../translateAllAnyOneOf.swift | 4 +- .../CommonTranslations/translateRawEnum.swift | 4 +- .../CommonTypes/DiscriminatorExtensions.swift | 4 +- .../CommonTypes/StructBlueprint.swift | 2 +- .../Translator/FileTranslator.swift | 44 +- .../Multipart/MultipartContentInspector.swift | 2 +- .../Multipart/translateMultipart.swift | 6 +- .../Operations/OperationDescription.swift | 13 +- .../Parameters/TypedParameter.swift | 2 +- .../RequestBody/translateRequestBody.swift | 6 +- .../Responses/TypedResponseHeader.swift | 2 +- .../Responses/translateResponse.swift | 2 +- .../Responses/translateResponseOutcome.swift | 4 +- .../translateServerMethod.swift | 3 +- .../TypeAssignment/SafeNameGenerator.swift | 367 +++++++++++++ .../TypeAssignment/TypeAssigner.swift | 58 +- .../TypesTranslator/translateOperations.swift | 5 +- .../translateServersVariables.swift | 10 +- .../Articles/Configuring-the-generator.md | 12 +- .../Articles/Project-scope-and-goals.md | 2 +- .../_Resources/client.Package.2.swift | 4 +- .../_Resources/client.Package.3.swift | 4 +- .../_Resources/client.Package.4.swift | 4 +- .../_Resources/client.Package.5.swift | 4 +- .../_Resources/client.console.3.0.txt | 6 +- .../client.openapi-generator-config.yaml | 1 + .../server-openapi-endpoints.main.0.swift | 4 +- .../server-openapi-endpoints.main.1.swift | 4 +- .../server-openapi-endpoints.main.2.swift | 4 +- .../_Resources/server.Package.2.swift | 4 +- .../_Resources/server.Package.3.swift | 4 +- .../_Resources/server.Package.4.swift | 4 +- .../_Resources/server.Package.5.swift | 4 +- .../_Resources/server.main.1.1.swift | 4 +- .../_Resources/server.main.1.2.swift | 4 +- .../Tutorials/_Resources/server.main.2.swift | 8 +- .../server.openapi-generator-config.yaml | 1 + .../swift-openapi-generator/Extensions.swift | 9 +- .../GenerateOptions+runGenerator.swift | 8 + .../GenerateOptions.swift | 18 +- .../swift-openapi-generator/UserConfig.swift | 10 + .../Extensions/Test_String.swift | 70 --- .../Extensions/Test_SwiftSafeNames.swift | 155 ++++++ .../TestUtilities.swift | 34 +- .../Test_OperationDescription.swift | 2 +- .../TypeAssignment/Test_TypeAssigner.swift | 41 +- .../CompatabilityTest.swift | 2 +- .../FileBasedReferenceTests.swift | 13 +- .../ReferenceSources/Petstore/Client.swift | 84 +-- .../ReferenceSources/Petstore/Server.swift | 98 ++-- .../ReferenceSources/Petstore/Types.swift | 508 +++++++++--------- .../SnippetBasedReferenceTests.swift | 60 ++- Tests/PetstoreConsumerTests/Common.swift | 4 +- Tests/PetstoreConsumerTests/TestClient.swift | 56 +- Tests/PetstoreConsumerTests/Test_Client.swift | 22 +- .../Test_Playground.swift | 6 +- Tests/PetstoreConsumerTests/Test_Server.swift | 31 +- Tests/PetstoreConsumerTests/Test_Types.swift | 18 +- 61 files changed, 1263 insertions(+), 727 deletions(-) delete mode 100644 Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift create mode 100644 Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/SafeNameGenerator.swift delete mode 100644 Tests/OpenAPIGeneratorCoreTests/Extensions/Test_String.swift create mode 100644 Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift diff --git a/Sources/_OpenAPIGeneratorCore/Config.swift b/Sources/_OpenAPIGeneratorCore/Config.swift index 6172b709..f65b0ea1 100644 --- a/Sources/_OpenAPIGeneratorCore/Config.swift +++ b/Sources/_OpenAPIGeneratorCore/Config.swift @@ -12,6 +12,20 @@ // //===----------------------------------------------------------------------===// +/// A strategy for turning OpenAPI identifiers into Swift identifiers. +public enum NamingStrategy: String, Sendable, Codable, Equatable { + + /// A defensive strategy that can handle any OpenAPI identifier and produce a non-conflicting Swift identifier. + /// + /// Introduced in [SOAR-0001](https://swiftpackageindex.com/apple/swift-openapi-generator/documentation/swift-openapi-generator/soar-0001). + case defensive + + /// An idiomatic strategy that produces Swift identifiers that more likely conform to Swift conventions. + /// + /// Introduced in [SOAR-0013](https://swiftpackageindex.com/apple/swift-openapi-generator/documentation/swift-openapi-generator/soar-0013). + case idiomatic +} + /// A structure that contains configuration options for a single execution /// of the generator pipeline run. /// @@ -35,6 +49,14 @@ public struct Config: Sendable { /// Filter to apply to the OpenAPI document before generation. public var filter: DocumentFilter? + /// The naming strategy to use for deriving Swift identifiers from OpenAPI identifiers. + /// + /// Defaults to `defensive`. + public var namingStrategy: NamingStrategy + + /// A map of OpenAPI identifiers to desired Swift identifiers, used instead of the naming strategy. + public var nameOverrides: [String: String] + /// Additional pre-release features to enable. public var featureFlags: FeatureFlags @@ -44,18 +66,26 @@ public struct Config: Sendable { /// - access: The access modifier to use for generated declarations. /// - additionalImports: Additional imports to add to each generated file. /// - filter: Filter to apply to the OpenAPI document before generation. + /// - namingStrategy: The naming strategy to use for deriving Swift identifiers from OpenAPI identifiers. + /// Defaults to `defensive`. + /// - nameOverrides: A map of OpenAPI identifiers to desired Swift identifiers, used instead + /// of the naming strategy. /// - featureFlags: Additional pre-release features to enable. public init( mode: GeneratorMode, access: AccessModifier, additionalImports: [String] = [], filter: DocumentFilter? = nil, + namingStrategy: NamingStrategy = .defensive, + nameOverrides: [String: String] = [:], featureFlags: FeatureFlags = [] ) { self.mode = mode self.access = access self.additionalImports = additionalImports self.filter = filter + self.namingStrategy = namingStrategy + self.nameOverrides = nameOverrides self.featureFlags = featureFlags } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/translateClientMethod.swift b/Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/translateClientMethod.swift index 853c373e..0c217fbd 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/translateClientMethod.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/ClientTranslator/translateClientMethod.swift @@ -145,7 +145,7 @@ extension ClientFileTranslator { func translateClientMethod(_ description: OperationDescription) throws -> Declaration { let operationTypeExpr = Expression.identifierType(.member(Constants.Operations.namespace)) - .dot(description.methodName) + .dot(description.operationTypeName) let operationArg = FunctionArgumentDescription(label: "forOperation", expression: operationTypeExpr.dot("id")) let inputArg = FunctionArgumentDescription( diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift deleted file mode 100644 index 9a52bc04..00000000 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift +++ /dev/null @@ -1,89 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftOpenAPIGenerator open source project -// -// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// -import Foundation - -extension String { - - /// Returns a string sanitized to be usable as a Swift identifier. - /// - /// See the proposal SOAR-0001 for details. - /// - /// For example, the string `$nake…` would be returned as `_dollar_nake_x2026_`, because - /// both the dollar and ellipsis sign are not valid characters in a Swift identifier. - /// So, it replaces such characters with their html entity equivalents or unicode hex representation, - /// in case it's not present in the `specialCharsMap`. It marks this replacement with `_` as a delimiter. - /// - /// In addition to replacing illegal characters, it also - /// ensures that the identifier starts with a letter and not a number. - var safeForSwiftCode: String { - guard !isEmpty else { return "_empty" } - - let firstCharSet: CharacterSet = .letters.union(.init(charactersIn: "_")) - let numbers: CharacterSet = .decimalDigits - let otherCharSet: CharacterSet = .alphanumerics.union(.init(charactersIn: "_")) - - var sanitizedScalars: [Unicode.Scalar] = [] - for (index, scalar) in unicodeScalars.enumerated() { - let allowedSet = index == 0 ? firstCharSet : otherCharSet - let outScalar: Unicode.Scalar - if allowedSet.contains(scalar) { - outScalar = scalar - } else if index == 0 && numbers.contains(scalar) { - sanitizedScalars.append("_") - outScalar = scalar - } else { - sanitizedScalars.append("_") - if let entityName = Self.specialCharsMap[scalar] { - for char in entityName.unicodeScalars { sanitizedScalars.append(char) } - } else { - sanitizedScalars.append("x") - let hexString = String(scalar.value, radix: 16, uppercase: true) - for char in hexString.unicodeScalars { sanitizedScalars.append(char) } - } - sanitizedScalars.append("_") - continue - } - sanitizedScalars.append(outScalar) - } - - let validString = String(UnicodeScalarView(sanitizedScalars)) - - //Special case for a single underscore. - //We can't add it to the map as its a valid swift identifier in other cases. - if validString == "_" { return "_underscore_" } - - guard Self.keywords.contains(validString) else { return validString } - return "_\(validString)" - } - - /// A list of Swift keywords. - /// - /// Copied from SwiftSyntax/TokenKind.swift - private static let keywords: Set = [ - "associatedtype", "class", "deinit", "enum", "extension", "func", "import", "init", "inout", "let", "operator", - "precedencegroup", "protocol", "struct", "subscript", "typealias", "var", "fileprivate", "internal", "private", - "public", "static", "defer", "if", "guard", "do", "repeat", "else", "for", "in", "while", "return", "break", - "continue", "fallthrough", "switch", "case", "default", "where", "catch", "throw", "as", "Any", "false", "is", - "nil", "rethrows", "super", "self", "Self", "true", "try", "throws", "yield", "String", "Error", "Int", "Bool", - "Array", "Type", "type", "Protocol", "await", - ] - - /// A map of ASCII printable characters to their HTML entity names. Used to reduce collisions in generated names. - private static let specialCharsMap: [Unicode.Scalar: String] = [ - " ": "space", "!": "excl", "\"": "quot", "#": "num", "$": "dollar", "%": "percnt", "&": "amp", "'": "apos", - "(": "lpar", ")": "rpar", "*": "ast", "+": "plus", ",": "comma", "-": "hyphen", ".": "period", "/": "sol", - ":": "colon", ";": "semi", "<": "lt", "=": "equals", ">": "gt", "?": "quest", "@": "commat", "[": "lbrack", - "\\": "bsol", "]": "rbrack", "^": "hat", "`": "grave", "{": "lcub", "|": "verbar", "}": "rcub", "~": "tilde", - ] -} diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift index 37ac8830..1816cabc 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift @@ -144,7 +144,7 @@ extension TypesFileTranslator { userDescription: nil, parent: typeName ) - let caseName = safeSwiftNameForOneOfMappedType(mappedType) + let caseName = safeSwiftNameForOneOfMappedCase(mappedType) return (caseName, mappedType.rawNames, true, comment, mappedType.typeName.asUsage, []) } } else { @@ -209,7 +209,7 @@ extension TypesFileTranslator { let decoder: Declaration if let discriminator { let originalName = discriminator.propertyName - let swiftName = context.asSwiftSafeName(originalName) + let swiftName = context.safeNameGenerator.swiftMemberName(for: originalName) codingKeysDecls = [ .enum( accessModifier: config.access, diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateRawEnum.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateRawEnum.swift index ed513551..737abe57 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateRawEnum.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateRawEnum.swift @@ -101,12 +101,12 @@ extension FileTranslator { // 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 { - try addIfUnique(id: .string(""), caseName: context.asSwiftSafeName("")) + try addIfUnique(id: .string(""), caseName: context.safeNameGenerator.swiftMemberName(for: "")) } else { guard let rawValue = anyValue as? String else { throw GenericError(message: "Disallowed value for a string enum '\(typeName)': \(anyValue)") } - let caseName = context.asSwiftSafeName(rawValue) + let caseName = context.safeNameGenerator.swiftMemberName(for: rawValue) try addIfUnique(id: .string(rawValue), caseName: caseName) } case .integer: diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/DiscriminatorExtensions.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/DiscriminatorExtensions.swift index 1bc1cd7f..41615b3b 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/DiscriminatorExtensions.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/DiscriminatorExtensions.swift @@ -79,8 +79,8 @@ 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 { - context.asSwiftSafeName(type.rawNames[0]) + func safeSwiftNameForOneOfMappedCase(_ type: OneOfMappedType) -> String { + context.safeNameGenerator.swiftMemberName(for: type.rawNames[0]) } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/StructBlueprint.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/StructBlueprint.swift index 9eef5d6d..7a516a1d 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/StructBlueprint.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/StructBlueprint.swift @@ -153,7 +153,7 @@ struct PropertyBlueprint { extension PropertyBlueprint { /// A name that is verified to be a valid Swift identifier. - var swiftSafeName: String { context.asSwiftSafeName(originalName) } + var swiftSafeName: String { context.safeNameGenerator.swiftMemberName(for: originalName) } /// The JSON path to the property. /// diff --git a/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift b/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift index 4f246521..00dde5d8 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift @@ -44,18 +44,50 @@ protocol FileTranslator { func translateFile(parsedOpenAPI: ParsedOpenAPIRepresentation) throws -> StructuredSwiftRepresentation } +/// A generator that allows overriding the documented name. +struct OverridableSafeNameGenerator: SafeNameGenerator { + + /// The upstream name generator for names that aren't overriden. + var upstream: any SafeNameGenerator + + /// A set of overrides, where the key is the documented name and the value the desired identifier. + var overrides: [String: String] + + func swiftTypeName(for documentedName: String) -> String { + if let override = overrides[documentedName] { return override } + return upstream.swiftTypeName(for: documentedName) + } + + func swiftMemberName(for documentedName: String) -> String { + if let override = overrides[documentedName] { return override } + return upstream.swiftMemberName(for: documentedName) + } + + func swiftContentTypeName(for contentType: ContentType) -> String { + upstream.swiftContentTypeName(for: contentType) + } +} + extension FileTranslator { /// A new context from the file translator. - var context: TranslatorContext { TranslatorContext(asSwiftSafeName: { $0.safeForSwiftCode }) } + var context: TranslatorContext { + let safeNameGenerator: any SafeNameGenerator + switch config.namingStrategy { + case .defensive: safeNameGenerator = .defensive + case .idiomatic: safeNameGenerator = .idiomatic + } + let overridingGenerator = OverridableSafeNameGenerator( + upstream: safeNameGenerator, + overrides: config.nameOverrides + ) + return TranslatorContext(safeNameGenerator: overridingGenerator) + } } /// 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 + /// A type that generates safe names for use as Swift identifiers. + var safeNameGenerator: any SafeNameGenerator } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift b/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift index 4616e4aa..b974190b 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 = context.asSwiftSafeName(key) + let swiftSafeName = context.safeNameGenerator.swiftTypeName(for: 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 b288ff1a..61c839dd 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Multipart/translateMultipart.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Multipart/translateMultipart.swift @@ -137,7 +137,7 @@ extension TypesFileTranslator { switch part { case .documentedTyped(let documentedPart): let caseDecl: Declaration = .enumCase( - name: context.asSwiftSafeName(documentedPart.originalName), + name: context.safeNameGenerator.swiftMemberName(for: 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 = context.asSwiftSafeName(originalName) + let identifier = context.safeNameGenerator.swiftMemberName(for: 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 = context.asSwiftSafeName(originalName) + let identifier = context.safeNameGenerator.swiftMemberName(for: 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 2a72252b..8342dbd6 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Operations/OperationDescription.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Operations/OperationDescription.swift @@ -83,7 +83,14 @@ 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 { context.asSwiftSafeName(operationID) } + var methodName: String { context.safeNameGenerator.swiftMemberName(for: operationID) } + + /// Returns a Swift-safe type name for the operation. + /// + /// 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 operationTypeName: String { context.safeNameGenerator.swiftTypeName(for: operationID) } /// Returns the identifier for the operation. /// @@ -103,7 +110,7 @@ extension OperationDescription { .init( components: [.root, .init(swift: Constants.Operations.namespace, json: "paths")] + path.components.map { .init(swift: nil, json: $0) } + [ - .init(swift: methodName, json: httpMethod.rawValue) + .init(swift: operationTypeName, json: httpMethod.rawValue) ] ) } @@ -292,7 +299,7 @@ extension OperationDescription { } let newPath = OpenAPI.Path(newComponents, trailingSlash: path.trailingSlash) let names: [Expression] = orderedPathParameters.map { param in - .identifierPattern("input").dot("path").dot(context.asSwiftSafeName(param)) + .identifierPattern("input").dot("path").dot(context.safeNameGenerator.swiftMemberName(for: 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 e8eb0700..d61957ab 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Parameters/TypedParameter.swift @@ -48,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 { context.asSwiftSafeName(name) } + var variableName: String { context.safeNameGenerator.swiftMemberName(for: name) } /// A Boolean value that indicates whether the parameter must be specified /// when performing the OpenAPI operation. diff --git a/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/translateRequestBody.swift b/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/translateRequestBody.swift index 1a412704..f14600fd 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/translateRequestBody.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/RequestBody/translateRequestBody.swift @@ -52,7 +52,7 @@ extension TypesFileTranslator { bodyMembers.append(contentsOf: inlineTypeDecls) } let contentType = content.content.contentType - let identifier = typeAssigner.contentSwiftName(contentType) + let identifier = context.safeNameGenerator.swiftContentTypeName(for: contentType) let associatedType = content.resolvedTypeUsage.withOptional(false) let contentCase: Declaration = .commentable( contentType.docComment(typeName: contentTypeName), @@ -148,7 +148,7 @@ extension ClientFileTranslator { var cases: [SwitchCaseDescription] = try contents.map { typedContent in let content = typedContent.content let contentType = content.contentType - let contentTypeIdentifier = typeAssigner.contentSwiftName(contentType) + let contentTypeIdentifier = context.safeNameGenerator.swiftContentTypeName(for: contentType) let contentTypeHeaderValue = contentType.headerValueForSending let extraBodyAssignArgs: [FunctionArgumentDescription] @@ -251,7 +251,7 @@ extension ServerFileTranslator { argumentNames: ["value"], body: [ .expression( - .dot(typeAssigner.contentSwiftName(typedContent.content.contentType)) + .dot(context.safeNameGenerator.swiftContentTypeName(for: typedContent.content.contentType)) .call([.init(label: nil, expression: .identifierPattern("value"))]) ) ] diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift b/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift index ad11fcbc..f8b0e866 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift @@ -39,7 +39,7 @@ struct TypedResponseHeader { extension TypedResponseHeader { /// The name of the header sanitized to be a valid Swift identifier. - var variableName: String { context.asSwiftSafeName(name) } + var variableName: String { context.safeNameGenerator.swiftMemberName(for: name) } /// A Boolean value that indicates whether the response header can /// be omitted in the HTTP response. diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponse.swift b/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponse.swift index a6754853..27da7efa 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponse.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponse.swift @@ -141,7 +141,7 @@ extension TypesFileTranslator { ) throws -> [Declaration] { var bodyCases: [Declaration] = [] let contentType = typedContent.content.contentType - let identifier = typeAssigner.contentSwiftName(contentType) + let identifier = context.safeNameGenerator.swiftContentTypeName(for: contentType) let associatedType = typedContent.resolvedTypeUsage let content = typedContent.content let schema = content.schema diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseOutcome.swift b/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseOutcome.swift index 5fb29ce3..b0b1fcd4 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseOutcome.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Responses/translateResponseOutcome.swift @@ -243,7 +243,7 @@ extension ClientFileTranslator { argumentNames: ["value"], body: [ .expression( - .dot(typeAssigner.contentSwiftName(typedContent.content.contentType)) + .dot(context.safeNameGenerator.swiftContentTypeName(for: typedContent.content.contentType)) .call([.init(label: nil, expression: .identifierPattern("value"))]) ) ] @@ -442,7 +442,7 @@ extension ServerFileTranslator { caseCodeBlocks.append(.expression(assignBodyExpr)) return .init( - kind: .case(.dot(typeAssigner.contentSwiftName(contentType)), ["value"]), + kind: .case(.dot(context.safeNameGenerator.swiftContentTypeName(for: contentType)), ["value"]), body: caseCodeBlocks ) } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/ServerTranslator/translateServerMethod.swift b/Sources/_OpenAPIGeneratorCore/Translator/ServerTranslator/translateServerMethod.swift index ca8aef41..4474d74b 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/ServerTranslator/translateServerMethod.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/ServerTranslator/translateServerMethod.swift @@ -129,9 +129,8 @@ extension ServerFileTranslator { func translateServerMethod(_ description: OperationDescription, serverUrlVariableName: String) throws -> ( registerCall: Expression, functionDecl: Declaration ) { - let operationTypeExpr = Expression.identifierType(.member(Constants.Operations.namespace)) - .dot(description.methodName) + .dot(description.operationTypeName) let operationArg = FunctionArgumentDescription(label: "forOperation", expression: operationTypeExpr.dot("id")) let requestArg = FunctionArgumentDescription(label: "request", expression: .identifierPattern("request")) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/SafeNameGenerator.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/SafeNameGenerator.swift new file mode 100644 index 00000000..2aec2e29 --- /dev/null +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/SafeNameGenerator.swift @@ -0,0 +1,367 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftOpenAPIGenerator open source project +// +// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +import Foundation + +/// Computes a string sanitized to be usable as a Swift identifier in various contexts. +protocol SafeNameGenerator { + + /// Returns a string sanitized to be usable as a Swift type name in a general context. + /// - Parameter documentedName: The input unsanitized string from the OpenAPI document. + /// - Returns: The sanitized string. + func swiftTypeName(for documentedName: String) -> String + + /// Returns a string sanitized to be usable as a Swift member name in a general context. + /// - Parameter documentedName: The input unsanitized string from the OpenAPI document. + /// - Returns: The sanitized string. + func swiftMemberName(for documentedName: String) -> String + + /// Returns a string sanitized to be usable as a Swift identifier for the provided content type. + /// - Parameter contentType: The content type for which to compute a Swift identifier. + /// - Returns: A Swift identifier for the provided content type. + func swiftContentTypeName(for contentType: ContentType) -> String +} + +extension SafeNameGenerator { + + /// Returns a Swift identifier override for the provided content type. + /// - Parameter contentType: A content type. + /// - Returns: A Swift identifer for the content type, or nil if the provided content type doesn't + /// have an override. + func swiftNameOverride(for contentType: ContentType) -> String? { + let rawContentType = contentType.lowercasedTypeSubtypeAndParameters + switch rawContentType { + case "application/json": return "json" + case "application/x-www-form-urlencoded": return "urlEncodedForm" + case "multipart/form-data": return "multipartForm" + case "text/plain": return "plainText" + case "*/*": return "any" + case "application/xml": return "xml" + case "application/octet-stream": return "binary" + case "text/html": return "html" + case "application/yaml": return "yaml" + case "text/csv": return "csv" + case "image/png": return "png" + case "application/pdf": return "pdf" + case "image/jpeg": return "jpeg" + default: return nil + } + } +} + +/// Returns a string sanitized to be usable as a Swift identifier. +/// +/// See the proposal SOAR-0001 for details. +/// +/// For example, the string `$nake…` would be returned as `_dollar_nake_x2026_`, because +/// both the dollar and ellipsis sign are not valid characters in a Swift identifier. +/// So, it replaces such characters with their html entity equivalents or unicode hex representation, +/// in case it's not present in the `specialCharsMap`. It marks this replacement with `_` as a delimiter. +/// +/// In addition to replacing illegal characters, it also +/// ensures that the identifier starts with a letter and not a number. +struct DefensiveSafeNameGenerator: SafeNameGenerator { + + func swiftTypeName(for documentedName: String) -> String { swiftName(for: documentedName) } + func swiftMemberName(for documentedName: String) -> String { swiftName(for: documentedName) } + private func swiftName(for documentedName: String) -> String { + guard !documentedName.isEmpty else { return "_empty" } + + let firstCharSet: CharacterSet = .letters.union(.init(charactersIn: "_")) + let numbers: CharacterSet = .decimalDigits + let otherCharSet: CharacterSet = .alphanumerics.union(.init(charactersIn: "_")) + + var sanitizedScalars: [Unicode.Scalar] = [] + for (index, scalar) in documentedName.unicodeScalars.enumerated() { + let allowedSet = index == 0 ? firstCharSet : otherCharSet + let outScalar: Unicode.Scalar + if allowedSet.contains(scalar) { + outScalar = scalar + } else if index == 0 && numbers.contains(scalar) { + sanitizedScalars.append("_") + outScalar = scalar + } else { + sanitizedScalars.append("_") + if let entityName = Self.specialCharsMap[scalar] { + for char in entityName.unicodeScalars { sanitizedScalars.append(char) } + } else { + sanitizedScalars.append("x") + let hexString = String(scalar.value, radix: 16, uppercase: true) + for char in hexString.unicodeScalars { sanitizedScalars.append(char) } + } + sanitizedScalars.append("_") + continue + } + sanitizedScalars.append(outScalar) + } + + let validString = String(String.UnicodeScalarView(sanitizedScalars)) + + // Special case for a single underscore. + // We can't add it to the map as its a valid swift identifier in other cases. + if validString == "_" { return "_underscore_" } + + guard Self.keywords.contains(validString) else { return validString } + return "_\(validString)" + } + + func swiftContentTypeName(for contentType: ContentType) -> String { + if let common = swiftNameOverride(for: contentType) { return common } + let safedType = swiftName(for: contentType.originallyCasedType) + let safedSubtype = swiftName(for: contentType.originallyCasedSubtype) + let componentSeparator = "_" + let prefix = "\(safedType)\(componentSeparator)\(safedSubtype)" + let params = contentType.lowercasedParameterPairs + guard !params.isEmpty else { return prefix } + let safedParams = + params.map { pair in + pair.split(separator: "=").map { component in swiftName(for: String(component)) } + .joined(separator: componentSeparator) + } + .joined(separator: componentSeparator) + return prefix + componentSeparator + safedParams + } + + /// A list of Swift keywords. + /// + /// Copied from SwiftSyntax/TokenKind.swift + private static let keywords: Set = [ + "associatedtype", "class", "deinit", "enum", "extension", "func", "import", "init", "inout", "let", "operator", + "precedencegroup", "protocol", "struct", "subscript", "typealias", "var", "fileprivate", "internal", "private", + "public", "static", "defer", "if", "guard", "do", "repeat", "else", "for", "in", "while", "return", "break", + "continue", "fallthrough", "switch", "case", "default", "where", "catch", "throw", "as", "Any", "false", "is", + "nil", "rethrows", "super", "self", "Self", "true", "try", "throws", "yield", "String", "Error", "Int", "Bool", + "Array", "Type", "type", "Protocol", "await", + ] + + /// A map of ASCII printable characters to their HTML entity names. Used to reduce collisions in generated names. + private static let specialCharsMap: [Unicode.Scalar: String] = [ + " ": "space", "!": "excl", "\"": "quot", "#": "num", "$": "dollar", "%": "percnt", "&": "amp", "'": "apos", + "(": "lpar", ")": "rpar", "*": "ast", "+": "plus", ",": "comma", "-": "hyphen", ".": "period", "/": "sol", + ":": "colon", ";": "semi", "<": "lt", "=": "equals", ">": "gt", "?": "quest", "@": "commat", "[": "lbrack", + "\\": "bsol", "]": "rbrack", "^": "hat", "`": "grave", "{": "lcub", "|": "verbar", "}": "rcub", "~": "tilde", + ] +} + +extension SafeNameGenerator where Self == DefensiveSafeNameGenerator { + static var defensive: DefensiveSafeNameGenerator { DefensiveSafeNameGenerator() } +} + +/// Returns a string sanitized to be usable as a Swift identifier, and tries to produce UpperCamelCase +/// or lowerCamelCase string, the casing is controlled using the provided options. +/// +/// If the string contains any illegal characters, falls back to the behavior +/// matching `safeForSwiftCode_defensive`. +/// +/// Check out [SOAR-0013](https://swiftpackageindex.com/apple/swift-openapi-generator/documentation/swift-openapi-generator/soar-0013) for details. +struct IdiomaticSafeNameGenerator: SafeNameGenerator { + + /// The defensive strategy to use as fallback. + var defensive: DefensiveSafeNameGenerator + + func swiftTypeName(for documentedName: String) -> String { swiftName(for: documentedName, capitalize: true) } + func swiftMemberName(for documentedName: String) -> String { swiftName(for: documentedName, capitalize: false) } + private func swiftName(for documentedName: String, capitalize: Bool) -> String { + if documentedName.isEmpty { return capitalize ? "_Empty_" : "_empty_" } + + // Detect cases like HELLO_WORLD, sometimes used for constants. + let isAllUppercase = documentedName.allSatisfy { + // Must check that no characters are lowercased, as non-letter characters + // don't return `true` to `isUppercase`. + !$0.isLowercase + } + + // 1. Leave leading underscores as-are + // 2. In the middle: word separators: ["_", "-", "/", "+", ] -> remove and capitalize + // next word + // 3. In the middle: period: ["."] -> replace with "_" + // 4. In the middle: drop ["{", "}"] -> replace with "" + + var buffer: [Character] = [] + buffer.reserveCapacity(documentedName.count) + enum State: Equatable { + case modifying + case preFirstWord + struct AccumulatingFirstWordContext: Equatable { var isAccumulatingInitialUppercase: Bool } + case accumulatingFirstWord(AccumulatingFirstWordContext) + case accumulatingWord + case waitingForWordStarter + } + var state: State = .preFirstWord + for index in documentedName[...].indices { + let char = documentedName[index] + let _state = state + state = .modifying + switch _state { + case .preFirstWord: + if char == "_" { + // Leading underscores are kept. + buffer.append(char) + state = .preFirstWord + } else if char.isNumber { + // The underscore will be added by the defensive strategy. + buffer.append(char) + state = .accumulatingFirstWord(.init(isAccumulatingInitialUppercase: false)) + } else if char.isLetter { + // First character in the identifier. + buffer.append(contentsOf: capitalize ? char.uppercased() : char.lowercased()) + state = .accumulatingFirstWord( + .init(isAccumulatingInitialUppercase: !capitalize && char.isUppercase) + ) + } else { + // Illegal character, keep and let the defensive strategy deal with it. + state = .accumulatingFirstWord(.init(isAccumulatingInitialUppercase: false)) + buffer.append(char) + } + case .accumulatingFirstWord(var context): + if char.isLetter || char.isNumber { + if isAllUppercase { + buffer.append(contentsOf: char.lowercased()) + } else if context.isAccumulatingInitialUppercase { + // Example: "HTTPProxy"/"HTTP_Proxy"/"HTTP_proxy"" should all + // become "httpProxy" when capitalize == false. + // This means treating the first word differently. + // Here we are on the second or later character of the first word (the first + // character is handled in `.preFirstWord`. + // If the first character was uppercase, and we're in lowercasing mode, + // we need to lowercase every consequtive uppercase character while there's + // another uppercase character after it. + if char.isLowercase { + // No accumulating anymore, just append it and turn off accumulation. + buffer.append(char) + context.isAccumulatingInitialUppercase = false + } else { + let suffix = documentedName.suffix(from: documentedName.index(after: index)) + if suffix.count >= 2 { + let next = suffix.first! + let secondNext = suffix.dropFirst().first! + if next.isUppercase && secondNext.isLowercase { + // Finished lowercasing. + context.isAccumulatingInitialUppercase = false + buffer.append(contentsOf: char.lowercased()) + } else if Self.wordSeparators.contains(next) { + // Finished lowercasing. + context.isAccumulatingInitialUppercase = false + buffer.append(contentsOf: char.lowercased()) + } else if next.isUppercase { + // Keep lowercasing. + buffer.append(contentsOf: char.lowercased()) + } else { + // Append as-is, stop accumulating. + context.isAccumulatingInitialUppercase = false + buffer.append(char) + } + } else { + // This is the last or second to last character, + // since we were accumulating capitals, lowercase it. + buffer.append(contentsOf: char.lowercased()) + context.isAccumulatingInitialUppercase = false + } + } + } else { + buffer.append(char) + } + state = .accumulatingFirstWord(context) + } else if ["_", "-", " ", "/", "+"].contains(char) { + // In the middle of an identifier, these are considered + // word separators, so we remove the character and end the current word. + state = .waitingForWordStarter + } else if ["."].contains(char) { + // In the middle of an identifier, these get replaced with + // an underscore, but continue the current word. + buffer.append("_") + state = .accumulatingFirstWord(.init(isAccumulatingInitialUppercase: false)) + } else if ["{", "}"].contains(char) { + // In the middle of an identifier, curly braces are dropped. + state = .accumulatingFirstWord(.init(isAccumulatingInitialUppercase: false)) + } else { + // Illegal character, keep and let the defensive strategy deal with it. + state = .accumulatingFirstWord(.init(isAccumulatingInitialUppercase: false)) + buffer.append(char) + } + case .accumulatingWord: + if char.isLetter || char.isNumber { + if isAllUppercase { buffer.append(contentsOf: char.lowercased()) } else { buffer.append(char) } + state = .accumulatingWord + } else if Self.wordSeparators.contains(char) { + // In the middle of an identifier, these are considered + // word separators, so we remove the character and end the current word. + state = .waitingForWordStarter + } else if ["."].contains(char) { + // In the middle of an identifier, these get replaced with + // an underscore, but continue the current word. + buffer.append("_") + state = .accumulatingWord + } else if ["{", "}"].contains(char) { + // In the middle of an identifier, these are dropped. + state = .accumulatingWord + } else { + // Illegal character, keep and let the defensive strategy deal with it. + state = .accumulatingWord + buffer.append(char) + } + case .waitingForWordStarter: + if ["_", "-", ".", "/", "+", "{", "}"].contains(char) { + // Between words, just drop allowed special characters, since + // we're already between words anyway. + state = .waitingForWordStarter + } else if char.isLetter || char.isNumber { + // Starting a new word in the middle of the identifier. + buffer.append(contentsOf: char.uppercased()) + state = .accumulatingWord + } else { + // Illegal character, keep and let the defensive strategy deal with it. + state = .waitingForWordStarter + buffer.append(char) + } + case .modifying: preconditionFailure("Logic error in \(#function), string: '\(self)'") + } + precondition(state != .modifying, "Logic error in \(#function), string: '\(self)'") + } + let defensiveFallback: (String) -> String + if capitalize { + defensiveFallback = defensive.swiftTypeName + } else { + defensiveFallback = defensive.swiftMemberName + } + return defensiveFallback(String(buffer)) + } + + func swiftContentTypeName(for contentType: ContentType) -> String { + if let common = swiftNameOverride(for: contentType) { return common } + let safedType = swiftMemberName(for: contentType.originallyCasedType) + let safedSubtype = swiftMemberName(for: contentType.originallyCasedSubtype) + let prettifiedSubtype = safedSubtype.uppercasingFirstLetter + let prefix = "\(safedType)\(prettifiedSubtype)" + let params = contentType.lowercasedParameterPairs + guard !params.isEmpty else { return prefix } + let safedParams = + params.map { pair in + pair.split(separator: "=") + .map { component in + let safedComponent = swiftMemberName(for: String(component)) + return safedComponent.uppercasingFirstLetter + } + .joined() + } + .joined() + return prefix + safedParams + } + + /// A list of word separator characters for the idiomatic naming strategy. + private static let wordSeparators: Set = ["_", "-", " ", "/", "+"] +} + +extension SafeNameGenerator where Self == DefensiveSafeNameGenerator { + static var idiomatic: IdiomaticSafeNameGenerator { IdiomaticSafeNameGenerator(defensive: .defensive) } +} diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift index 7f7a46ff..1555731d 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift @@ -59,7 +59,10 @@ 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: context.asSwiftSafeName(originalName), jsonComponent: originalName) + .appending( + swiftComponent: context.safeNameGenerator.swiftTypeName(for: originalName), + jsonComponent: originalName + ) } /// Returns the type name for an OpenAPI-named component namespace. @@ -127,7 +130,7 @@ struct TypeAssigner { { multipartBodyElementTypeName = try typeName(for: ref) } else { - let swiftSafeName = context.asSwiftSafeName(hint) + let swiftSafeName = context.safeNameGenerator.swiftTypeName(for: hint) multipartBodyElementTypeName = parent.appending( swiftComponent: swiftSafeName + Constants.Global.inlineTypeSuffix, jsonComponent: hint @@ -147,7 +150,7 @@ struct TypeAssigner { func typeUsage(withContent content: SchemaContent, components: OpenAPI.Components, inParent parent: TypeName) throws -> TypeUsage? { - let identifier = contentSwiftName(content.contentType) + let identifier = context.safeNameGenerator.swiftContentTypeName(for: content.contentType) if content.contentType.isMultipart { return try typeUsage( usingNamingHint: identifier, @@ -343,7 +346,7 @@ struct TypeAssigner { } return baseType.appending( - swiftComponent: context.asSwiftSafeName(originalName) + suffix, + swiftComponent: context.safeNameGenerator.swiftTypeName(for: originalName) + suffix, jsonComponent: jsonReferenceComponentOverride ?? originalName ) .asUsage.withOptional(try typeMatcher.isOptional(schema, components: components)) @@ -406,7 +409,10 @@ struct TypeAssigner { of componentType: Component.Type ) -> TypeName { typeName(for: Component.self) - .appending(swiftComponent: context.asSwiftSafeName(key.rawValue), jsonComponent: key.rawValue) + .appending( + swiftComponent: context.safeNameGenerator.swiftTypeName(for: key.rawValue), + jsonComponent: key.rawValue + ) } /// Returns a type name for a JSON reference. @@ -471,7 +477,7 @@ struct TypeAssigner { throw JSONReferenceParsingError.nonComponentPathsUnsupported(reference.name) } return typeName(for: componentType) - .appending(swiftComponent: context.asSwiftSafeName(name), jsonComponent: name) + .appending(swiftComponent: context.safeNameGenerator.swiftTypeName(for: name), jsonComponent: name) } /// Returns a type name for the namespace for the specified component type. @@ -495,7 +501,8 @@ struct TypeAssigner { { typeNameForComponents() .appending( - swiftComponent: context.asSwiftSafeName(componentType.openAPIComponentsKey).uppercasingFirstLetter, + swiftComponent: context.safeNameGenerator.swiftTypeName(for: componentType.openAPIComponentsKey) + .uppercasingFirstLetter, jsonComponent: componentType.openAPIComponentsKey ) } @@ -505,43 +512,6 @@ struct TypeAssigner { func typeNameForComponents() -> TypeName { TypeName(components: [.root, .init(swift: Constants.Components.namespace, json: "components")]) } - - /// Returns a Swift-safe identifier used as the name of the content - /// enum case. - /// - /// - Parameter contentType: The content type for which to compute the name. - /// - Returns: A Swift-safe identifier representing the name of the content enum case. - func contentSwiftName(_ contentType: ContentType) -> String { - let rawContentType = contentType.lowercasedTypeSubtypeAndParameters - switch rawContentType { - case "application/json": return "json" - case "application/x-www-form-urlencoded": return "urlEncodedForm" - case "multipart/form-data": return "multipartForm" - case "text/plain": return "plainText" - case "*/*": return "any" - case "application/xml": return "xml" - case "application/octet-stream": return "binary" - case "text/html": return "html" - case "application/yaml": return "yaml" - case "text/csv": return "csv" - case "image/png": return "png" - case "application/pdf": return "pdf" - case "image/jpeg": return "jpeg" - default: - 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 { context.asSwiftSafeName(String($0)) }.joined(separator: "_") - } - .joined(separator: "_") - return prefix + "_" + safedParams - } - } - } extension FileTranslator { diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateOperations.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateOperations.swift index 8beaae76..e8716b30 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateOperations.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateOperations.swift @@ -192,7 +192,10 @@ extension TypesFileTranslator { let contentTypes = try acceptHeaderContentTypes(for: description) guard !contentTypes.isEmpty else { return nil } let cases: [(caseName: String, rawExpr: LiteralDescription)] = contentTypes.map { contentType in - (typeAssigner.contentSwiftName(contentType), .string(contentType.lowercasedTypeAndSubtype)) + ( + context.safeNameGenerator.swiftContentTypeName(for: contentType), + .string(contentType.lowercasedTypeAndSubtype) + ) } return try translateRawRepresentableEnum( typeName: acceptableContentTypeName, diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServersVariables.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServersVariables.swift index c8b96ff0..c4a8e803 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServersVariables.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServersVariables.swift @@ -84,7 +84,7 @@ extension TypesFileTranslator { /// Swift safe identifiers. init(key: String, variable: OpenAPI.Server.Variable, context: TranslatorContext) { self.key = key - swiftSafeKey = context.asSwiftSafeName(key) + swiftSafeKey = context.safeNameGenerator.swiftMemberName(for: key) self.variable = variable } @@ -164,8 +164,8 @@ extension TypesFileTranslator { context: TranslatorContext ) { self.key = key - swiftSafeKey = context.asSwiftSafeName(key) - enumName = context.asSwiftSafeName(key.localizedCapitalized) + swiftSafeKey = context.safeNameGenerator.swiftMemberName(for: key) + enumName = context.safeNameGenerator.swiftTypeName(for: key.localizedCapitalized) self.variable = variable self.enumValues = enumValues self.context = context @@ -199,7 +199,7 @@ extension TypesFileTranslator { .init( label: swiftSafeKey, type: .member([enumName]), - defaultValue: .memberAccess(.dot(context.asSwiftSafeName(variable.default))) + defaultValue: .memberAccess(.dot(context.safeNameGenerator.swiftMemberName(for: variable.default))) ) } @@ -230,7 +230,7 @@ extension TypesFileTranslator { /// - Parameter name: The original name. /// - Returns: A declaration of an enum case. private func translateVariableCase(_ name: String) -> Declaration { - let caseName = context.asSwiftSafeName(name) + let caseName = context.safeNameGenerator.swiftMemberName(for: name) return .enumCase(name: caseName, kind: caseName == name ? .nameOnly : .nameWithRawValue(.string(name))) } } diff --git a/Sources/swift-openapi-generator/Documentation.docc/Articles/Configuring-the-generator.md b/Sources/swift-openapi-generator/Documentation.docc/Articles/Configuring-the-generator.md index cf889a85..256c8cb3 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Articles/Configuring-the-generator.md +++ b/Sources/swift-openapi-generator/Documentation.docc/Articles/Configuring-the-generator.md @@ -35,11 +35,15 @@ The configuration file has the following keys: - `package`: Generated API is accessible from other modules within the same package or project. - `internal` (default): Generated API is accessible from the containing module only. - `additionalImports` (optional): array of strings. Each string value is a Swift module name. An import statement will be added to the generated source files for each module. -- `filter`: (optional): Filters to apply to the OpenAPI document before generation. +- `filter` (optional): Filters to apply to the OpenAPI document before generation. - `operations`: Operations with these operation IDs will be included in the filter. - `tags`: Operations tagged with these tags will be included in the filter. - `paths`: Operations for these paths will be included in the filter. - `schemas`: These (additional) schemas will be included in the filter. +- `namingStrategy` (optional): a string. The strategy of converting OpenAPI identifiers into Swift identifiers. + - `defensive` (default): Produces non-conflicting Swift identifiers for any OpenAPI identifiers. Check out [SOAR-0001](https://swiftpackageindex.com/apple/swift-openapi-generator/documentation/swift-openapi-generator/soar-0001) for details. + - `idiomatic`: Produces more idiomatic Swift identifiers for OpenAPI identifiers. Might produce name conflicts (in that case, switch back to `defensive`). Check out [SOAR-0013](https://swiftpackageindex.com/apple/swift-openapi-generator/documentation/swift-openapi-generator/soar-0013) for details. +- `nameOverrides` (optional): a string to string dictionary. Allows customizing how individual OpenAPI identifiers get converted to Swift identifiers. - `featureFlags` (optional): array of strings. Each string must be a valid feature flag to enable. For a list of currently supported feature flags, check out [FeatureFlags.swift](https://github.com/apple/swift-openapi-generator/blob/main/Sources/_OpenAPIGeneratorCore/FeatureFlags.swift). ### Example config files @@ -50,6 +54,7 @@ To generate client code in a single target: generate: - types - client +namingStrategy: idiomatic ``` To generate server code in a single target: @@ -58,6 +63,7 @@ To generate server code in a single target: generate: - types - server +namingStrategy: idiomatic ``` If you are generating client _and_ server code, you can generate the types in a shared target using the following config: @@ -65,6 +71,7 @@ If you are generating client _and_ server code, you can generate the types in a ```yaml generate: - types +namingStrategy: idiomatic ``` Then, to generate client code that depends on the module from this target, use the following config (where `APITypes` is the name of the library target that contains the generated `types`): @@ -72,6 +79,7 @@ Then, to generate client code that depends on the module from this target, use t ```yaml generate: - client +namingStrategy: idiomatic additionalImports: - APITypes ``` @@ -81,6 +89,7 @@ To use the generated code from other packages, also customize the access modifie ```yaml generate: - client +namingStrategy: idiomatic additionalImports: - APITypes accessModifier: public @@ -97,6 +106,7 @@ For example, to generate client code for only the operations with a given tag, u generate: - types - client +namingStrategy: idiomatic filter: tags: diff --git a/Sources/swift-openapi-generator/Documentation.docc/Articles/Project-scope-and-goals.md b/Sources/swift-openapi-generator/Documentation.docc/Articles/Project-scope-and-goals.md index d34dc231..57f12ffb 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Articles/Project-scope-and-goals.md +++ b/Sources/swift-openapi-generator/Documentation.docc/Articles/Project-scope-and-goals.md @@ -16,7 +16,7 @@ The goal of the project is to compose with the wider OpenAPI tooling ecosystem s The OpenAPI document is considered as the source of truth. The generator aims to produce code that reflects the document where possible. This includes the specification structure, and the identifiers used by the authors of the OpenAPI document. -As a result, the generated code may not always be idiomatic Swift style or conform to your own custom style guidelines. For example, the API operation identifiers may not be `lowerCamelCase`. +As a result, the generated code may not always be idiomatic Swift style or conform to your own custom style guidelines. For example, the API operation identifiers may not be `lowerCamelCase` by default, when using the `defensive` naming strategy. However, an alternative naming strategy called [`idiomatic`](https://swiftpackageindex.com/apple/swift-openapi-generator/documentation/swift-openapi-generator/soar-0013) is available since version 1.6.0 that closer matches Swift conventions. If you require the generated code to conform to specific style, we recommend you preprocess the OpenAPI document to update the identifiers to produce different code. diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.2.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.2.swift index 6e8e8d19..65afa8ef 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.2.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.2.swift @@ -5,8 +5,8 @@ let package = Package( name: "GreetingServiceClient", platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .visionOS(.v1)], dependencies: [ - .package(url: "https://github.com/apple/swift-openapi-generator", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-openapi-generator", from: "1.6.0"), + .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.7.0"), .package(url: "https://github.com/apple/swift-openapi-urlsession", from: "1.0.0"), ], targets: [ diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.3.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.3.swift index b804b173..e54fd0cf 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.3.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.3.swift @@ -5,8 +5,8 @@ let package = Package( name: "GreetingServiceClient", platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .visionOS(.v1)], dependencies: [ - .package(url: "https://github.com/apple/swift-openapi-generator", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-openapi-generator", from: "1.6.0"), + .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.7.0"), .package(url: "https://github.com/apple/swift-openapi-urlsession", from: "1.0.0"), ], targets: [ diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.4.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.4.swift index 1a7d3401..30c6268d 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.4.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.4.swift @@ -5,8 +5,8 @@ let package = Package( name: "GreetingServiceClient", platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .visionOS(.v1)], dependencies: [ - .package(url: "https://github.com/apple/swift-openapi-generator", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-openapi-generator", from: "1.6.0"), + .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.7.0"), .package(url: "https://github.com/apple/swift-openapi-urlsession", from: "1.0.0"), ], targets: [ diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.5.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.5.swift index 1a7d3401..30c6268d 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.5.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.5.swift @@ -5,8 +5,8 @@ let package = Package( name: "GreetingServiceClient", platforms: [.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .visionOS(.v1)], dependencies: [ - .package(url: "https://github.com/apple/swift-openapi-generator", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-openapi-generator", from: "1.6.0"), + .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.7.0"), .package(url: "https://github.com/apple/swift-openapi-urlsession", from: "1.0.0"), ], targets: [ diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.console.3.0.txt b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.console.3.0.txt index 539278f7..c23b89ee 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.console.3.0.txt +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.console.3.0.txt @@ -1,6 +1,6 @@ % swift run --package-path GreetingServiceClient -ok(GreetingServiceClient.Operations.getGreeting.Output.Ok( -headers: GreetingServiceClient.Operations.getGreeting.Output.Ok.Headers(), -body: GreetingServiceClient.Operations.getGreeting.Output.Ok.Body.json( +ok(GreetingServiceClient.Operations.GetGreeting.Output.Ok( +headers: GreetingServiceClient.Operations.GetGreeting.Output.Ok.Headers(), +body: GreetingServiceClient.Operations.GetGreeting.Output.Ok.Body.json( GreetingServiceClient.Components.Schemas.Greeting(message: "Hello, CLI")))) diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.openapi-generator-config.yaml b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.openapi-generator-config.yaml index ecefb471..fe5587de 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.openapi-generator-config.yaml +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.openapi-generator-config.yaml @@ -1,3 +1,4 @@ generate: - types - client +namingStrategy: idiomatic diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server-openapi-endpoints.main.0.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server-openapi-endpoints.main.0.swift index 53565ec0..3052468d 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server-openapi-endpoints.main.0.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server-openapi-endpoints.main.0.swift @@ -6,8 +6,8 @@ import OpenAPIVapor // Define a type that conforms to the generated protocol. struct GreetingServiceAPIImpl: APIProtocol { func getGreeting( - _ input: Operations.getGreeting.Input - ) async throws -> Operations.getGreeting.Output { + _ input: Operations.GetGreeting.Input + ) async throws -> Operations.GetGreeting.Output { let name = input.query.name ?? "Stranger" let greeting = Components.Schemas.Greeting(message: "Hello, \(name)!") return .ok(.init(body: .json(greeting))) diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server-openapi-endpoints.main.1.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server-openapi-endpoints.main.1.swift index a26b7e0a..24cead5f 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server-openapi-endpoints.main.1.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server-openapi-endpoints.main.1.swift @@ -6,8 +6,8 @@ import OpenAPIVapor // Define a type that conforms to the generated protocol. struct GreetingServiceAPIImpl: APIProtocol { func getGreeting( - _ input: Operations.getGreeting.Input - ) async throws -> Operations.getGreeting.Output { + _ input: Operations.GetGreeting.Input + ) async throws -> Operations.GetGreeting.Output { let name = input.query.name ?? "Stranger" let greeting = Components.Schemas.Greeting(message: "Hello, \(name)!") return .ok(.init(body: .json(greeting))) diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server-openapi-endpoints.main.2.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server-openapi-endpoints.main.2.swift index af9f16c4..c296fcb7 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server-openapi-endpoints.main.2.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server-openapi-endpoints.main.2.swift @@ -6,8 +6,8 @@ import OpenAPIVapor // Define a type that conforms to the generated protocol. struct GreetingServiceAPIImpl: APIProtocol { func getGreeting( - _ input: Operations.getGreeting.Input - ) async throws -> Operations.getGreeting.Output { + _ input: Operations.GetGreeting.Input + ) async throws -> Operations.GetGreeting.Output { let name = input.query.name ?? "Stranger" let greeting = Components.Schemas.Greeting(message: "Hello, \(name)!") return .ok(.init(body: .json(greeting))) diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.2.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.2.swift index 27883fd2..ba749d6a 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.2.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.2.swift @@ -7,8 +7,8 @@ let package = Package( .macOS(.v10_15) ], dependencies: [ - .package(url: "https://github.com/apple/swift-openapi-generator", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-openapi-generator", from: "1.6.0"), + .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.7.0"), .package(url: "https://github.com/swift-server/swift-openapi-vapor", from: "1.0.0"), .package(url: "https://github.com/vapor/vapor", from: "4.89.0"), ], diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.3.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.3.swift index dd95b7d7..6d092169 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.3.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.3.swift @@ -7,8 +7,8 @@ let package = Package( .macOS(.v10_15) ], dependencies: [ - .package(url: "https://github.com/apple/swift-openapi-generator", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-openapi-generator", from: "1.6.0"), + .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.7.0"), .package(url: "https://github.com/swift-server/swift-openapi-vapor", from: "1.0.0"), .package(url: "https://github.com/vapor/vapor", from: "4.89.0"), ], diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.4.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.4.swift index 49e4e588..0f7f29d1 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.4.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.4.swift @@ -7,8 +7,8 @@ let package = Package( .macOS(.v10_15) ], dependencies: [ - .package(url: "https://github.com/apple/swift-openapi-generator", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-openapi-generator", from: "1.6.0"), + .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.7.0"), .package(url: "https://github.com/swift-server/swift-openapi-vapor", from: "1.0.0"), .package(url: "https://github.com/vapor/vapor", from: "4.89.0"), ], diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.5.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.5.swift index 49e4e588..0f7f29d1 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.5.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.5.swift @@ -7,8 +7,8 @@ let package = Package( .macOS(.v10_15) ], dependencies: [ - .package(url: "https://github.com/apple/swift-openapi-generator", from: "1.0.0"), - .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.0.0"), + .package(url: "https://github.com/apple/swift-openapi-generator", from: "1.6.0"), + .package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.7.0"), .package(url: "https://github.com/swift-server/swift-openapi-vapor", from: "1.0.0"), .package(url: "https://github.com/vapor/vapor", from: "4.89.0"), ], diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.main.1.1.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.main.1.1.swift index fc748483..d501eaed 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.main.1.1.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.main.1.1.swift @@ -6,8 +6,8 @@ import OpenAPIVapor // Define a type that conforms to the generated protocol. struct GreetingServiceAPIImpl: APIProtocol { func getGreeting( - _ input: Operations.getGreeting.Input - ) async throws -> Operations.getGreeting.Output { + _ input: Operations.GetGreeting.Input + ) async throws -> Operations.GetGreeting.Output { let name = input.query.name ?? "Stranger" let greeting = Components.Schemas.Greeting(message: "Hello, \(name)!") return .ok(.init(body: .json(greeting))) diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.main.1.2.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.main.1.2.swift index 53565ec0..3052468d 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.main.1.2.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.main.1.2.swift @@ -6,8 +6,8 @@ import OpenAPIVapor // Define a type that conforms to the generated protocol. struct GreetingServiceAPIImpl: APIProtocol { func getGreeting( - _ input: Operations.getGreeting.Input - ) async throws -> Operations.getGreeting.Output { + _ input: Operations.GetGreeting.Input + ) async throws -> Operations.GetGreeting.Output { let name = input.query.name ?? "Stranger" let greeting = Components.Schemas.Greeting(message: "Hello, \(name)!") return .ok(.init(body: .json(greeting))) diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.main.2.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.main.2.swift index 27ebe994..59a6149b 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.main.2.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.main.2.swift @@ -6,16 +6,16 @@ import OpenAPIVapor // Define a type that conforms to the generated protocol. struct GreetingServiceAPIImpl: APIProtocol { func getGreeting( - _ input: Operations.getGreeting.Input - ) async throws -> Operations.getGreeting.Output { + _ input: Operations.GetGreeting.Input + ) async throws -> Operations.GetGreeting.Output { let name = input.query.name ?? "Stranger" let greeting = Components.Schemas.Greeting(message: "Hello, \(name)!") return .ok(.init(body: .json(greeting))) } func getEmoji( - _ input: Operations.getEmoji.Input - ) async throws -> Operations.getEmoji.Output { + _ input: Operations.GetEmoji.Input + ) async throws -> Operations.GetEmoji.Output { let emojis = "👋👍👏🙏🤙🤘" let emoji = String(emojis.randomElement()!) return .ok(.init(body: .plainText(.init(emoji)))) diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.openapi-generator-config.yaml b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.openapi-generator-config.yaml index 2a087488..6b839ba4 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.openapi-generator-config.yaml +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.openapi-generator-config.yaml @@ -1,3 +1,4 @@ generate: - types - server +namingStrategy: idiomatic diff --git a/Sources/swift-openapi-generator/Extensions.swift b/Sources/swift-openapi-generator/Extensions.swift index a16d997f..76f43361 100644 --- a/Sources/swift-openapi-generator/Extensions.swift +++ b/Sources/swift-openapi-generator/Extensions.swift @@ -43,7 +43,14 @@ extension CaseIterable where Self: RawRepresentable, Self.RawValue == String { extension _UserConfig { /// An example configuration used in the command-line tool help section. - static var sample: Self { .init(generate: [.types, .client], additionalImports: nil) } + static var sample: Self { + .init( + generate: [.types, .client], + accessModifier: .internal, + filter: .init(operations: ["listPets", "createPet"]), + namingStrategy: .idiomatic + ) + } /// A YAML representation of the configuration. var yamlString: String { get throws { try YAMLEncoder().encode(self) } } diff --git a/Sources/swift-openapi-generator/GenerateOptions+runGenerator.swift b/Sources/swift-openapi-generator/GenerateOptions+runGenerator.swift index 9f3fe83b..9cdf9f06 100644 --- a/Sources/swift-openapi-generator/GenerateOptions+runGenerator.swift +++ b/Sources/swift-openapi-generator/GenerateOptions+runGenerator.swift @@ -32,6 +32,8 @@ extension _GenerateOptions { let sortedModes = try resolvedModes(config) let resolvedAccessModifier = resolvedAccessModifier(config) ?? Config.defaultAccessModifier let resolvedAdditionalImports = resolvedAdditionalImports(config) + let resolvedNamingStragy = resolvedNamingStrategy(config) + let resolvedNameOverrides = resolvedNameOverrides(config) let resolvedFeatureFlags = resolvedFeatureFlags(config) let configs: [Config] = sortedModes.map { .init( @@ -39,6 +41,8 @@ extension _GenerateOptions { access: resolvedAccessModifier, additionalImports: resolvedAdditionalImports, filter: config?.filter, + namingStrategy: resolvedNamingStragy, + nameOverrides: resolvedNameOverrides, featureFlags: resolvedFeatureFlags ) } @@ -51,6 +55,10 @@ extension _GenerateOptions { - Configuration path: \(self.config?.path ?? "") - Generator modes: \(sortedModes.map(\.rawValue).joined(separator: ", ")) - Access modifier: \(resolvedAccessModifier.rawValue) + - Naming strategy: \(resolvedNamingStragy.rawValue) + - Name overrides: \(resolvedNameOverrides.isEmpty ? "" : resolvedNameOverrides + .sorted(by: { $0.key < $1.key }) + .map { "\"\($0.key)\"->\"\($0.value)\"" }.joined(separator: ", ")) - Feature flags: \(resolvedFeatureFlags.isEmpty ? "" : resolvedFeatureFlags.map(\.rawValue).joined(separator: ", ")) - Output file names: \(sortedModes.map(\.outputFileName).joined(separator: ", ")) - Output directory: \(outputDirectory.path) diff --git a/Sources/swift-openapi-generator/GenerateOptions.swift b/Sources/swift-openapi-generator/GenerateOptions.swift index 8b65db9c..deaf69c2 100644 --- a/Sources/swift-openapi-generator/GenerateOptions.swift +++ b/Sources/swift-openapi-generator/GenerateOptions.swift @@ -47,14 +47,6 @@ extension AccessModifier: ExpressibleByArgument {} extension _GenerateOptions { - /// The user-provided user config, not yet resolved with defaults. - var resolvedUserConfig: _UserConfig { - get throws { - let config = try loadedConfig() - return try .init(generate: resolvedModes(config), additionalImports: resolvedAdditionalImports(config)) - } - } - /// Returns a list of the generator modes requested by the user. /// - Parameter config: The configuration specified by the user. /// - Returns: A list of generator modes requested by the user. @@ -83,6 +75,16 @@ extension _GenerateOptions { return [] } + /// Returns the naming strategy requested by the user. + /// - Parameter config: The configuration specified by the user. + /// - Returns: The naming strategy requestd by the user. + func resolvedNamingStrategy(_ config: _UserConfig?) -> NamingStrategy { config?.namingStrategy ?? .defensive } + + /// Returns the name overrides requested by the user. + /// - Parameter config: The configuration specified by the user. + /// - Returns: The name overrides requested by the user + func resolvedNameOverrides(_ config: _UserConfig?) -> [String: String] { config?.nameOverrides ?? [:] } + /// Returns a list of the feature flags requested by the user. /// - Parameter config: The configuration specified by the user. /// - Returns: A set of feature flags requested by the user. diff --git a/Sources/swift-openapi-generator/UserConfig.swift b/Sources/swift-openapi-generator/UserConfig.swift index 0ee210b7..239f67b0 100644 --- a/Sources/swift-openapi-generator/UserConfig.swift +++ b/Sources/swift-openapi-generator/UserConfig.swift @@ -33,6 +33,14 @@ struct _UserConfig: Codable { /// Filter to apply to the OpenAPI document before generation. var filter: DocumentFilter? + /// The strategy to use for naming generated Swift types and members. + var namingStrategy: NamingStrategy? + + /// A dictionary of name overrides for generated types and members. + /// + /// Any names not included use the `namingStrategy` to compute a Swift name. + var nameOverrides: [String: String]? + /// A set of features to explicitly enable. var featureFlags: FeatureFlags? @@ -44,6 +52,8 @@ struct _UserConfig: Codable { case accessModifier case additionalImports case filter + case namingStrategy + case nameOverrides case featureFlags } } diff --git a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_String.swift b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_String.swift deleted file mode 100644 index 9d10b45c..00000000 --- a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_String.swift +++ /dev/null @@ -1,70 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftOpenAPIGenerator open source project -// -// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// -import XCTest -@testable import _OpenAPIGeneratorCore - -final class Test_String: Test_Core { - - func testAsSwiftSafeName() { - let cases: [(String, String)] = [ - // Simple - ("foo", "foo"), - - // Starts with a number - ("3foo", "_3foo"), - - // Keyword - ("default", "_default"), - - // Reserved name - ("Type", "_Type"), - - // Empty string - ("", "_empty"), - - // Special Char in middle - ("inv@lidName", "inv_commat_lidName"), - - // Special Char in first position - ("!nvalidName", "_excl_nvalidName"), - - // Special Char in last position - ("invalidNam?", "invalidNam_quest_"), - - // Valid underscore case - ("__user", "__user"), - - // Invalid underscore case - ("_", "_underscore_"), - - // Special character mixed with character not in map - ("$nake…", "_dollar_nake_x2026_"), - - // Only special character - ("$", "_dollar_"), - - // Only special character not in map - ("……", "_x2026__x2026_"), - - // Non Latin Characters - ("$مرحبا", "_dollar_مرحبا"), - - // Content type components - ("application", "application"), ("vendor1+json", "vendor1_plus_json"), - ] - let translator = makeTranslator() - let asSwiftSafeName: (String) -> String = translator.context.asSwiftSafeName - for (input, sanitized) in cases { XCTAssertEqual(asSwiftSafeName(input), sanitized) } - } -} diff --git a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift new file mode 100644 index 00000000..75e34fe9 --- /dev/null +++ b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift @@ -0,0 +1,155 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftOpenAPIGenerator open source project +// +// Copyright (c) 2023 Apple Inc. and the SwiftOpenAPIGenerator project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftOpenAPIGenerator project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// +import XCTest +@testable import _OpenAPIGeneratorCore + +final class Test_SwiftSafeNames: Test_Core { + func testAsSwiftSafeName() { + let cases: [(original: String, defensive: String, idiomaticUpper: String, idiomaticLower: String)] = [ + + // Simple + ("foo", "foo", "Foo", "foo"), + + // Space + ("Hello world", "Hello_space_world", "HelloWorld", "helloWorld"), + + // Mixed capitalization + ("My_URL_value", "My_URL_value", "MyURLValue", "myURLValue"), + + // Dashes + ("hello-world", "hello_hyphen_world", "HelloWorld", "helloWorld"), + + // Header names + ("Retry-After", "Retry_hyphen_After", "RetryAfter", "retryAfter"), + + // All uppercase + ("HELLO_WORLD", "HELLO_WORLD", "HelloWorld", "helloWorld"), ("HELLO", "HELLO", "Hello", "hello"), + + // Acronyms + ("HTTPProxy", "HTTPProxy", "HTTPProxy", "httpProxy"), + ("HTTP_Proxy", "HTTP_Proxy", "HTTPProxy", "httpProxy"), + ("HTTP_proxy", "HTTP_proxy", "HTTPProxy", "httpProxy"), + ("OneHTTPProxy", "OneHTTPProxy", "OneHTTPProxy", "oneHTTPProxy"), ("iOS", "iOS", "IOS", "iOS"), + // Numbers + ("version 2.0", "version_space_2_period_0", "Version2_0", "version2_0"), + ("V1.2Release", "V1_period_2Release", "V1_2Release", "v1_2Release"), + + // Synthesized operationId from method + path + ( + "get/pets/{petId}/notifications", "get_sol_pets_sol__lcub_petId_rcub__sol_notifications", + "GetPetsPetIdNotifications", "getPetsPetIdNotifications" + ), + ( + "get/name/v{version}.zip", "get_sol_name_sol_v_lcub_version_rcub__period_zip", "GetNameVversion_zip", + "getNameVversion_zip" + ), + + // Technical strings + ("file/path/to/resource", "file_sol_path_sol_to_sol_resource", "FilePathToResource", "filePathToResource"), + ( + "user.name@domain.com", "user_period_name_commat_domain_period_com", "User_name_commat_domain_com", + "user_name_commat_domain_com" + ), ("hello.world.2023", "hello_period_world_period_2023", "Hello_world_2023", "hello_world_2023"), + ("order#123", "order_num_123", "Order_num_123", "order_num_123"), + ("pressKeys#123", "pressKeys_num_123", "PressKeys_num_123", "pressKeys_num_123"), + + // Non-English characters + ("naïve café", "naïve_space_café", "NaïveCafé", "naïveCafé"), + + // Starts with a number + ("3foo", "_3foo", "_3foo", "_3foo"), + + // Keyword + ("default", "_default", "Default", "_default"), + + // Reserved name + ("Type", "_Type", "_Type", "_type"), + + // Empty string + ("", "_empty", "_Empty_", "_empty_"), + + // Special Char in middle + ("inv@lidName", "inv_commat_lidName", "Inv_commat_lidName", "inv_commat_lidName"), + + // Special Char in first position + ("!nvalidName", "_excl_nvalidName", "_excl_nvalidName", "_excl_nvalidName"), + + // Special Char in last position + ("invalidNam?", "invalidNam_quest_", "InvalidNam_quest_", "invalidNam_quest_"), + + // Preserve leading underscores + ("__user_name", "__user_name", "__UserName", "__userName"), + + // Preserve only leading underscores + ("user__name", "user__name", "UserName", "userName"), + + // Invalid underscore case + ("_", "_underscore_", "_underscore_", "_underscore_"), + + // Special character mixed with character not in map + ("$nake…", "_dollar_nake_x2026_", "_dollar_nake_x2026_", "_dollar_nake_x2026_"), + + // Only special character + ("$", "_dollar_", "_dollar_", "_dollar_"), + + // Only special character not in map + ("……", "_x2026__x2026_", "_x2026__x2026_", "_x2026__x2026_"), + + // Non Latin Characters combined with a RTL language + ("$مرحبا", "_dollar_مرحبا", "_dollar_مرحبا", "_dollar_مرحبا"), + + // Emoji + ("heart❤️emoji", "heart_x2764_️emoji", "Heart_x2764_️emoji", "heart_x2764_️emoji"), + + // Content type components + ("application", "application", "Application", "application"), + ("vendor1+json", "vendor1_plus_json", "Vendor1Json", "vendor1Json"), + + // Known real-world examples + ("+1", "_plus_1", "_plus_1", "_plus_1"), ("one+two", "one_plus_two", "OneTwo", "oneTwo"), + ("-1", "_hyphen_1", "_hyphen_1", "_hyphen_1"), ("one-two", "one_hyphen_two", "OneTwo", "oneTwo"), + + // Override + ("MEGA", "m_e_g_a", "m_e_g_a", "m_e_g_a"), + ] + self.continueAfterFailure = true + do { + let translator = makeTranslator(nameOverrides: ["MEGA": "m_e_g_a"]) + let safeNameGenerator = translator.context.safeNameGenerator + for (input, sanitizedDefensive, _, _) in cases { + XCTAssertEqual( + safeNameGenerator.swiftMemberName(for: input), + sanitizedDefensive, + "Defensive, input: \(input)" + ) + } + } + do { + let translator = makeTranslator(namingStrategy: .idiomatic, nameOverrides: ["MEGA": "m_e_g_a"]) + let safeNameGenerator = translator.context.safeNameGenerator + for (input, _, idiomaticUpper, idiomaticLower) in cases { + XCTAssertEqual( + safeNameGenerator.swiftTypeName(for: input), + idiomaticUpper, + "Idiomatic upper, input: \(input)" + ) + XCTAssertEqual( + safeNameGenerator.swiftMemberName(for: input), + idiomaticLower, + "Idiomatic lower, input: \(input)" + ) + } + } + } +} diff --git a/Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift b/Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift index a99d4d30..48651d1c 100644 --- a/Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift +++ b/Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift @@ -28,25 +28,49 @@ class Test_Core: XCTestCase { func makeTranslator( components: OpenAPI.Components = .noComponents, diagnostics: any DiagnosticCollector = PrintingDiagnosticCollector(), + namingStrategy: NamingStrategy = .defensive, + nameOverrides: [String: String] = [:], featureFlags: FeatureFlags = [] ) -> TypesFileTranslator { - makeTypesTranslator(components: components, diagnostics: diagnostics, featureFlags: featureFlags) + makeTypesTranslator( + components: components, + diagnostics: diagnostics, + namingStrategy: namingStrategy, + nameOverrides: nameOverrides, + featureFlags: featureFlags + ) } func makeTypesTranslator( components: OpenAPI.Components = .noComponents, diagnostics: any DiagnosticCollector = PrintingDiagnosticCollector(), + namingStrategy: NamingStrategy = .defensive, + nameOverrides: [String: String] = [:], featureFlags: FeatureFlags = [] ) -> TypesFileTranslator { TypesFileTranslator( - config: makeConfig(featureFlags: featureFlags), + config: makeConfig( + namingStrategy: namingStrategy, + nameOverrides: nameOverrides, + featureFlags: featureFlags + ), diagnostics: diagnostics, components: components ) } - func makeConfig(featureFlags: FeatureFlags = []) -> Config { - .init(mode: .types, access: Config.defaultAccessModifier, featureFlags: featureFlags) + func makeConfig( + namingStrategy: NamingStrategy = .defensive, + nameOverrides: [String: String] = [:], + featureFlags: FeatureFlags = [] + ) -> Config { + .init( + mode: .types, + access: Config.defaultAccessModifier, + namingStrategy: namingStrategy, + nameOverrides: nameOverrides, + featureFlags: featureFlags + ) } func loadSchemaFromYAML(_ yamlString: String) throws -> JSONSchema { @@ -61,8 +85,6 @@ class Test_Core: XCTestCase { var context: TranslatorContext { makeTranslator().context } - var asSwiftSafeName: (String) -> String { context.asSwiftSafeName } - func makeProperty(originalName: String, typeUsage: TypeUsage) -> PropertyBlueprint { .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 6b37703c..abd341a7 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(), - context: .init(asSwiftSafeName: { $0 }) + context: .init(safeNameGenerator: .defensive) ) } } diff --git a/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeAssigner.swift b/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeAssigner.swift index c76ec4c5..b9640bd6 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeAssigner.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeAssigner.swift @@ -56,7 +56,7 @@ class Test_TypeAssigner: Test_Core { "enum": "_enum", ] for (componentKey, expectedSwiftTypeName) in expectedSchemaTypeNames { - XCTAssertEqual(asSwiftSafeName(componentKey.rawValue), expectedSwiftTypeName) + XCTAssertEqual(context.safeNameGenerator.swiftMemberName(for: componentKey.rawValue), expectedSwiftTypeName) } } @@ -105,27 +105,40 @@ class Test_TypeAssigner: Test_Core { } func testContentSwiftName() throws { - let nameMaker = makeTranslator().typeAssigner.contentSwiftName - let cases: [(String, String)] = [ + let defensiveNameMaker = makeTranslator().context.safeNameGenerator.swiftContentTypeName + let idiomaticNameMaker = makeTranslator(namingStrategy: .idiomatic).context.safeNameGenerator + .swiftContentTypeName + let cases: [(input: String, defensive: String, idiomatic: String)] = [ // Short names. - ("application/json", "json"), ("application/x-www-form-urlencoded", "urlEncodedForm"), - ("multipart/form-data", "multipartForm"), ("text/plain", "plainText"), ("*/*", "any"), - ("application/xml", "xml"), ("application/octet-stream", "binary"), ("text/html", "html"), - ("application/yaml", "yaml"), ("text/csv", "csv"), ("image/png", "png"), ("application/pdf", "pdf"), - ("image/jpeg", "jpeg"), + ("application/json", "json", "json"), + ("application/x-www-form-urlencoded", "urlEncodedForm", "urlEncodedForm"), + ("multipart/form-data", "multipartForm", "multipartForm"), ("text/plain", "plainText", "plainText"), + ("*/*", "any", "any"), ("application/xml", "xml", "xml"), ("application/octet-stream", "binary", "binary"), + ("text/html", "html", "html"), ("application/yaml", "yaml", "yaml"), ("text/csv", "csv", "csv"), + ("image/png", "png", "png"), ("application/pdf", "pdf", "pdf"), ("image/jpeg", "jpeg", "jpeg"), // Generic names. - ("application/myformat+json", "application_myformat_plus_json"), ("foo/bar", "foo_bar"), + ("application/myformat+json", "application_myformat_plus_json", "applicationMyformatJson"), + ("foo/bar", "foo_bar", "fooBar"), ("text/event-stream", "text_event_hyphen_stream", "textEventStream"), // Names with a parameter. - ("application/foo", "application_foo"), - ("application/foo; bar=baz; boo=foo", "application_foo_bar_baz_boo_foo"), - ("application/foo; bar = baz", "application_foo_bar_baz"), + ("application/foo", "application_foo", "applicationFoo"), + ("application/foo; bar=baz; boo=foo", "application_foo_bar_baz_boo_foo", "applicationFooBarBazBooFoo"), + ("application/foo; bar = baz", "application_foo_bar_baz", "applicationFooBarBaz"), ] - for (string, name) in cases { + for (string, defensiveName, idiomaticName) in cases { let contentType = try XCTUnwrap(ContentType(string: string)) - XCTAssertEqual(nameMaker(contentType), name, "Case \(string) failed") + XCTAssertEqual( + defensiveNameMaker(contentType), + defensiveName, + "Case \(string) failed for defensive strategy" + ) + XCTAssertEqual( + idiomaticNameMaker(contentType), + idiomaticName, + "Case \(string) failed for idiomatic strategy" + ) } } } diff --git a/Tests/OpenAPIGeneratorReferenceTests/CompatabilityTest.swift b/Tests/OpenAPIGeneratorReferenceTests/CompatabilityTest.swift index ab8906f2..e8b9b357 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/CompatabilityTest.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/CompatabilityTest.swift @@ -292,7 +292,7 @@ fileprivate extension CompatibilityTest { let package = Package( name: "\(packageName)", platforms: [.macOS(.v13)], - dependencies: [.package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.0.0")], + dependencies: [.package(url: "https://github.com/apple/swift-openapi-runtime", from: "1.7.0")], targets: [.target(name: "Harness", dependencies: [.product(name: "OpenAPIRuntime", package: "swift-openapi-runtime")])] ) """ diff --git a/Tests/OpenAPIGeneratorReferenceTests/FileBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/FileBasedReferenceTests.swift index 75b8be78..f45902c3 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/FileBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/FileBasedReferenceTests.swift @@ -21,12 +21,21 @@ struct TestConfig: Encodable { var mode: GeneratorMode var additionalImports: [String]? var featureFlags: FeatureFlags? + var namingStrategy: NamingStrategy + var nameOverrides: [String: String] var referenceOutputDirectory: String } extension TestConfig { var asConfig: Config { - .init(mode: mode, access: .public, additionalImports: additionalImports ?? [], featureFlags: featureFlags ?? []) + .init( + mode: mode, + access: .public, + additionalImports: additionalImports ?? [], + namingStrategy: namingStrategy, + nameOverrides: nameOverrides, + featureFlags: featureFlags ?? [] + ) } } @@ -127,6 +136,8 @@ final class FileBasedReferenceTests: XCTestCase { mode: mode, additionalImports: [], featureFlags: featureFlags, + namingStrategy: .idiomatic, + nameOverrides: [:], referenceOutputDirectory: "ReferenceSources/\(project.fixtureCodeDirectoryName)" ), ignoredDiagnosticMessages: ignoredDiagnosticMessages diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift index 75c9bf22..25f1f53d 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift @@ -47,10 +47,10 @@ public struct Client: APIProtocol { /// /// - Remark: HTTP `GET /pets`. /// - Remark: Generated from `#/paths//pets/get(listPets)`. - public func listPets(_ input: Operations.listPets.Input) async throws -> Operations.listPets.Output { + public func listPets(_ input: Operations.ListPets.Input) async throws -> Operations.ListPets.Output { try await client.send( input: input, - forOperation: Operations.listPets.id, + forOperation: Operations.ListPets.id, serializer: { input in let path = try converter.renderedPath( template: "/pets", @@ -85,7 +85,7 @@ public struct Client: APIProtocol { try converter.setHeaderFieldAsURI( in: &request.headerFields, name: "My-Request-UUID", - value: input.headers.My_hyphen_Request_hyphen_UUID + value: input.headers.myRequestUUID ) try converter.setQueryItemAsURI( in: &request, @@ -103,20 +103,20 @@ public struct Client: APIProtocol { deserializer: { response, responseBody in switch response.status.code { case 200: - let headers: Operations.listPets.Output.Ok.Headers = .init( - My_hyphen_Response_hyphen_UUID: try converter.getRequiredHeaderFieldAsURI( + let headers: Operations.ListPets.Output.Ok.Headers = .init( + myResponseUUID: try converter.getRequiredHeaderFieldAsURI( in: response.headerFields, name: "My-Response-UUID", as: Swift.String.self ), - My_hyphen_Tracing_hyphen_Header: try converter.getOptionalHeaderFieldAsURI( + myTracingHeader: try converter.getOptionalHeaderFieldAsURI( in: response.headerFields, name: "My-Tracing-Header", as: Components.Headers.TracingHeader.self ) ) let contentType = converter.extractContentTypeIfPresent(in: response.headerFields) - let body: Operations.listPets.Output.Ok.Body + let body: Operations.ListPets.Output.Ok.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -141,7 +141,7 @@ public struct Client: APIProtocol { )) default: let contentType = converter.extractContentTypeIfPresent(in: response.headerFields) - let body: Operations.listPets.Output.Default.Body + let body: Operations.ListPets.Output.Default.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -172,10 +172,10 @@ public struct Client: APIProtocol { /// /// - Remark: HTTP `POST /pets`. /// - Remark: Generated from `#/paths//pets/post(createPet)`. - public func createPet(_ input: Operations.createPet.Input) async throws -> Operations.createPet.Output { + public func createPet(_ input: Operations.CreatePet.Input) async throws -> Operations.CreatePet.Output { try await client.send( input: input, - forOperation: Operations.createPet.id, + forOperation: Operations.CreatePet.id, serializer: { input in let path = try converter.renderedPath( template: "/pets", @@ -189,7 +189,7 @@ public struct Client: APIProtocol { try converter.setHeaderFieldAsJSON( in: &request.headerFields, name: "X-Extra-Arguments", - value: input.headers.X_hyphen_Extra_hyphen_Arguments + value: input.headers.xExtraArguments ) converter.setAcceptHeader( in: &request.headerFields, @@ -209,13 +209,13 @@ public struct Client: APIProtocol { deserializer: { response, responseBody in switch response.status.code { case 201: - let headers: Operations.createPet.Output.Created.Headers = .init(X_hyphen_Extra_hyphen_Arguments: try converter.getOptionalHeaderFieldAsJSON( + let headers: Operations.CreatePet.Output.Created.Headers = .init(xExtraArguments: try converter.getOptionalHeaderFieldAsJSON( in: response.headerFields, name: "X-Extra-Arguments", as: Components.Schemas.CodeError.self )) let contentType = converter.extractContentTypeIfPresent(in: response.headerFields) - let body: Operations.createPet.Output.Created.Body + let body: Operations.CreatePet.Output.Created.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -239,7 +239,7 @@ public struct Client: APIProtocol { body: body )) case 400 ... 499: - let headers: Components.Responses.ErrorBadRequest.Headers = .init(X_hyphen_Reason: try converter.getOptionalHeaderFieldAsURI( + let headers: Components.Responses.ErrorBadRequest.Headers = .init(xReason: try converter.getOptionalHeaderFieldAsURI( in: response.headerFields, name: "X-Reason", as: Swift.String.self @@ -255,7 +255,7 @@ public struct Client: APIProtocol { switch chosenContentType { case "application/json": body = try await converter.getResponseBodyAsJSON( - Components.Responses.ErrorBadRequest.Body.jsonPayload.self, + Components.Responses.ErrorBadRequest.Body.JsonPayload.self, from: responseBody, transforming: { value in .json(value) @@ -287,10 +287,10 @@ public struct Client: APIProtocol { /// /// - Remark: HTTP `POST /pets/create`. /// - Remark: Generated from `#/paths//pets/create/post(createPetWithForm)`. - public func createPetWithForm(_ input: Operations.createPetWithForm.Input) async throws -> Operations.createPetWithForm.Output { + public func createPetWithForm(_ input: Operations.CreatePetWithForm.Input) async throws -> Operations.CreatePetWithForm.Output { try await client.send( input: input, - forOperation: Operations.createPetWithForm.id, + forOperation: Operations.CreatePetWithForm.id, serializer: { input in let path = try converter.renderedPath( template: "/pets/create", @@ -330,10 +330,10 @@ public struct Client: APIProtocol { } /// - Remark: HTTP `GET /pets/stats`. /// - Remark: Generated from `#/paths//pets/stats/get(getStats)`. - public func getStats(_ input: Operations.getStats.Input) async throws -> Operations.getStats.Output { + public func getStats(_ input: Operations.GetStats.Input) async throws -> Operations.GetStats.Output { try await client.send( input: input, - forOperation: Operations.getStats.id, + forOperation: Operations.GetStats.id, serializer: { input in let path = try converter.renderedPath( template: "/pets/stats", @@ -354,7 +354,7 @@ public struct Client: APIProtocol { switch response.status.code { case 200: let contentType = converter.extractContentTypeIfPresent(in: response.headerFields) - let body: Operations.getStats.Output.Ok.Body + let body: Operations.GetStats.Output.Ok.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -406,10 +406,10 @@ public struct Client: APIProtocol { } /// - Remark: HTTP `POST /pets/stats`. /// - Remark: Generated from `#/paths//pets/stats/post(postStats)`. - public func postStats(_ input: Operations.postStats.Input) async throws -> Operations.postStats.Output { + public func postStats(_ input: Operations.PostStats.Input) async throws -> Operations.PostStats.Output { try await client.send( input: input, - forOperation: Operations.postStats.id, + forOperation: Operations.PostStats.id, serializer: { input in let path = try converter.renderedPath( template: "/pets/stats", @@ -461,10 +461,10 @@ public struct Client: APIProtocol { } /// - Remark: HTTP `POST /probe/`. /// - Remark: Generated from `#/paths//probe//post(probe)`. - public func probe(_ input: Operations.probe.Input) async throws -> Operations.probe.Output { + public func probe(_ input: Operations.Probe.Input) async throws -> Operations.Probe.Output { try await client.send( input: input, - forOperation: Operations.probe.id, + forOperation: Operations.Probe.id, serializer: { input in let path = try converter.renderedPath( template: "/probe/", @@ -497,10 +497,10 @@ public struct Client: APIProtocol { /// /// - Remark: HTTP `PATCH /pets/{petId}`. /// - Remark: Generated from `#/paths//pets/{petId}/patch(updatePet)`. - public func updatePet(_ input: Operations.updatePet.Input) async throws -> Operations.updatePet.Output { + public func updatePet(_ input: Operations.UpdatePet.Input) async throws -> Operations.UpdatePet.Output { try await client.send( input: input, - forOperation: Operations.updatePet.id, + forOperation: Operations.UpdatePet.id, serializer: { input in let path = try converter.renderedPath( template: "/pets/{}", @@ -536,7 +536,7 @@ public struct Client: APIProtocol { return .noContent(.init()) case 400: let contentType = converter.extractContentTypeIfPresent(in: response.headerFields) - let body: Operations.updatePet.Output.BadRequest.Body + let body: Operations.UpdatePet.Output.BadRequest.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -546,7 +546,7 @@ public struct Client: APIProtocol { switch chosenContentType { case "application/json": body = try await converter.getResponseBodyAsJSON( - Operations.updatePet.Output.BadRequest.Body.jsonPayload.self, + Operations.UpdatePet.Output.BadRequest.Body.JsonPayload.self, from: responseBody, transforming: { value in .json(value) @@ -572,10 +572,10 @@ public struct Client: APIProtocol { /// /// - Remark: HTTP `PUT /pets/{petId}/avatar`. /// - Remark: Generated from `#/paths//pets/{petId}/avatar/put(uploadAvatarForPet)`. - public func uploadAvatarForPet(_ input: Operations.uploadAvatarForPet.Input) async throws -> Operations.uploadAvatarForPet.Output { + public func uploadAvatarForPet(_ input: Operations.UploadAvatarForPet.Input) async throws -> Operations.UploadAvatarForPet.Output { try await client.send( input: input, - forOperation: Operations.uploadAvatarForPet.id, + forOperation: Operations.UploadAvatarForPet.id, serializer: { input in let path = try converter.renderedPath( template: "/pets/{}/avatar", @@ -607,7 +607,7 @@ public struct Client: APIProtocol { switch response.status.code { case 200: let contentType = converter.extractContentTypeIfPresent(in: response.headerFields) - let body: Operations.uploadAvatarForPet.Output.Ok.Body + let body: Operations.UploadAvatarForPet.Output.Ok.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -629,7 +629,7 @@ public struct Client: APIProtocol { return .ok(.init(body: body)) case 412: let contentType = converter.extractContentTypeIfPresent(in: response.headerFields) - let body: Operations.uploadAvatarForPet.Output.PreconditionFailed.Body + let body: Operations.UploadAvatarForPet.Output.PreconditionFailed.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -651,7 +651,7 @@ public struct Client: APIProtocol { return .preconditionFailed(.init(body: body)) case 500: let contentType = converter.extractContentTypeIfPresent(in: response.headerFields) - let body: Operations.uploadAvatarForPet.Output.InternalServerError.Body + let body: Operations.UploadAvatarForPet.Output.InternalServerError.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -685,10 +685,10 @@ public struct Client: APIProtocol { } /// - Remark: HTTP `GET /pets/multipart-typed`. /// - Remark: Generated from `#/paths//pets/multipart-typed/get(multipartDownloadTyped)`. - public func multipartDownloadTyped(_ input: Operations.multipartDownloadTyped.Input) async throws -> Operations.multipartDownloadTyped.Output { + public func multipartDownloadTyped(_ input: Operations.MultipartDownloadTyped.Input) async throws -> Operations.MultipartDownloadTyped.Output { try await client.send( input: input, - forOperation: Operations.multipartDownloadTyped.id, + forOperation: Operations.MultipartDownloadTyped.id, serializer: { input in let path = try converter.renderedPath( template: "/pets/multipart-typed", @@ -719,7 +719,7 @@ public struct Client: APIProtocol { switch chosenContentType { case "multipart/form-data": body = try converter.getResponseBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: responseBody, transforming: { value in .multipartForm(value) @@ -741,10 +741,10 @@ public struct Client: APIProtocol { let (name, filename) = try converter.extractContentDispositionNameAndFilename(in: headerFields) switch name { case "log": - let headers: Components.Responses.MultipartDownloadTypedResponse.Body.multipartFormPayload.logPayload.Headers = .init(x_hyphen_log_hyphen_type: try converter.getOptionalHeaderFieldAsURI( + let headers: Components.Responses.MultipartDownloadTypedResponse.Body.MultipartFormPayload.LogPayload.Headers = .init(xLogType: try converter.getOptionalHeaderFieldAsURI( in: headerFields, name: "x-log-type", - as: Components.Responses.MultipartDownloadTypedResponse.Body.multipartFormPayload.logPayload.Headers.x_hyphen_log_hyphen_typePayload.self + as: Components.Responses.MultipartDownloadTypedResponse.Body.MultipartFormPayload.LogPayload.Headers.XLogTypePayload.self )) try converter.verifyContentTypeIfPresent( in: headerFields, @@ -770,7 +770,7 @@ public struct Client: APIProtocol { matches: "application/json" ) let body = try await converter.getResponseBodyAsJSON( - Components.Responses.MultipartDownloadTypedResponse.Body.multipartFormPayload.metadataPayload.bodyPayload.self, + Components.Responses.MultipartDownloadTypedResponse.Body.MultipartFormPayload.MetadataPayload.BodyPayload.self, from: part.body, transforming: { $0 @@ -819,10 +819,10 @@ public struct Client: APIProtocol { } /// - Remark: HTTP `POST /pets/multipart-typed`. /// - Remark: Generated from `#/paths//pets/multipart-typed/post(multipartUploadTyped)`. - public func multipartUploadTyped(_ input: Operations.multipartUploadTyped.Input) async throws -> Operations.multipartUploadTyped.Output { + public func multipartUploadTyped(_ input: Operations.MultipartUploadTyped.Input) async throws -> Operations.MultipartUploadTyped.Output { try await client.send( input: input, - forOperation: Operations.multipartUploadTyped.id, + forOperation: Operations.MultipartUploadTyped.id, serializer: { input in let path = try converter.renderedPath( template: "/pets/multipart-typed", @@ -859,7 +859,7 @@ public struct Client: APIProtocol { try converter.setHeaderFieldAsURI( in: &headerFields, name: "x-log-type", - value: value.headers.x_hyphen_log_hyphen_type + value: value.headers.xLogType ) let body = try converter.setRequiredRequestBodyAsBinary( value.body, diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift index 80d642b3..c7a02fcf 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift @@ -160,12 +160,12 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { request: request, requestBody: body, metadata: metadata, - forOperation: Operations.listPets.id, + forOperation: Operations.ListPets.id, using: { APIHandler.listPets($0) }, deserializer: { request, requestBody, metadata in - let query: Operations.listPets.Input.Query = .init( + let query: Operations.ListPets.Input.Query = .init( limit: try converter.getOptionalQueryItemAsURI( in: request.soar_query, style: .form, @@ -178,32 +178,32 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { style: .form, explode: true, name: "habitat", - as: Operations.listPets.Input.Query.habitatPayload.self + as: Operations.ListPets.Input.Query.HabitatPayload.self ), feeds: try converter.getOptionalQueryItemAsURI( in: request.soar_query, style: .form, explode: true, name: "feeds", - as: Operations.listPets.Input.Query.feedsPayload.self + as: Operations.ListPets.Input.Query.FeedsPayload.self ), since: try converter.getOptionalQueryItemAsURI( in: request.soar_query, style: .form, explode: true, name: "since", - as: Components.Parameters.query_period_born_hyphen_since.self + as: Components.Parameters.Query_bornSince.self ) ) - let headers: Operations.listPets.Input.Headers = .init( - My_hyphen_Request_hyphen_UUID: try converter.getOptionalHeaderFieldAsURI( + let headers: Operations.ListPets.Input.Headers = .init( + myRequestUUID: try converter.getOptionalHeaderFieldAsURI( in: request.headerFields, name: "My-Request-UUID", as: Swift.String.self ), accept: try converter.extractAcceptHeaderIfPresent(in: request.headerFields) ) - return Operations.listPets.Input( + return Operations.ListPets.Input( query: query, headers: headers ) @@ -217,12 +217,12 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { try converter.setHeaderFieldAsURI( in: &response.headerFields, name: "My-Response-UUID", - value: value.headers.My_hyphen_Response_hyphen_UUID + value: value.headers.myResponseUUID ) try converter.setHeaderFieldAsURI( in: &response.headerFields, name: "My-Tracing-Header", - value: value.headers.My_hyphen_Tracing_hyphen_Header + value: value.headers.myTracingHeader ) let body: OpenAPIRuntime.HTTPBody switch value.body { @@ -273,13 +273,13 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { request: request, requestBody: body, metadata: metadata, - forOperation: Operations.createPet.id, + forOperation: Operations.CreatePet.id, using: { APIHandler.createPet($0) }, deserializer: { request, requestBody, metadata in - let headers: Operations.createPet.Input.Headers = .init( - X_hyphen_Extra_hyphen_Arguments: try converter.getOptionalHeaderFieldAsJSON( + let headers: Operations.CreatePet.Input.Headers = .init( + xExtraArguments: try converter.getOptionalHeaderFieldAsJSON( in: request.headerFields, name: "X-Extra-Arguments", as: Components.Schemas.CodeError.self @@ -287,7 +287,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { accept: try converter.extractAcceptHeaderIfPresent(in: request.headerFields) ) let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.createPet.Input.Body + let body: Operations.CreatePet.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -306,7 +306,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.createPet.Input( + return Operations.CreatePet.Input( headers: headers, body: body ) @@ -320,7 +320,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { try converter.setHeaderFieldAsJSON( in: &response.headerFields, name: "X-Extra-Arguments", - value: value.headers.X_hyphen_Extra_hyphen_Arguments + value: value.headers.xExtraArguments ) let body: OpenAPIRuntime.HTTPBody switch value.body { @@ -343,7 +343,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { try converter.setHeaderFieldAsURI( in: &response.headerFields, name: "X-Reason", - value: value.headers.X_hyphen_Reason + value: value.headers.xReason ) let body: OpenAPIRuntime.HTTPBody switch value.body { @@ -378,13 +378,13 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { request: request, requestBody: body, metadata: metadata, - forOperation: Operations.createPetWithForm.id, + forOperation: Operations.CreatePetWithForm.id, using: { APIHandler.createPetWithForm($0) }, deserializer: { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.createPetWithForm.Input.Body + let body: Operations.CreatePetWithForm.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -403,7 +403,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.createPetWithForm.Input(body: body) + return Operations.CreatePetWithForm.Input(body: body) }, serializer: { output, request in switch output { @@ -429,13 +429,13 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { request: request, requestBody: body, metadata: metadata, - forOperation: Operations.getStats.id, + forOperation: Operations.GetStats.id, using: { APIHandler.getStats($0) }, deserializer: { request, requestBody, metadata in - let headers: Operations.getStats.Input.Headers = .init(accept: try converter.extractAcceptHeaderIfPresent(in: request.headerFields)) - return Operations.getStats.Input(headers: headers) + let headers: Operations.GetStats.Input.Headers = .init(accept: try converter.extractAcceptHeaderIfPresent(in: request.headerFields)) + return Operations.GetStats.Input(headers: headers) }, serializer: { output, request in switch output { @@ -494,13 +494,13 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { request: request, requestBody: body, metadata: metadata, - forOperation: Operations.postStats.id, + forOperation: Operations.PostStats.id, using: { APIHandler.postStats($0) }, deserializer: { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.postStats.Input.Body + let body: Operations.PostStats.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -537,7 +537,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.postStats.Input(body: body) + return Operations.PostStats.Input(body: body) }, serializer: { output, request in switch output { @@ -563,12 +563,12 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { request: request, requestBody: body, metadata: metadata, - forOperation: Operations.probe.id, + forOperation: Operations.Probe.id, using: { APIHandler.probe($0) }, deserializer: { request, requestBody, metadata in - return Operations.probe.Input() + return Operations.Probe.Input() }, serializer: { output, request in switch output { @@ -596,17 +596,17 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { request: request, requestBody: body, metadata: metadata, - forOperation: Operations.updatePet.id, + forOperation: Operations.UpdatePet.id, using: { APIHandler.updatePet($0) }, deserializer: { request, requestBody, metadata in - let path: Operations.updatePet.Input.Path = .init(petId: try converter.getPathParameterAsURI( + let path: Operations.UpdatePet.Input.Path = .init(petId: try converter.getPathParameterAsURI( in: metadata.pathParameters, name: "petId", as: Swift.Int64.self )) - let headers: Operations.updatePet.Input.Headers = .init(accept: try converter.extractAcceptHeaderIfPresent(in: request.headerFields)) + let headers: Operations.UpdatePet.Input.Headers = .init(accept: try converter.extractAcceptHeaderIfPresent(in: request.headerFields)) let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) let body: Components.RequestBodies.UpdatePetRequest? let chosenContentType = try converter.bestContentType( @@ -618,7 +618,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { switch chosenContentType { case "application/json": body = try await converter.getOptionalRequestBodyAsJSON( - Components.RequestBodies.UpdatePetRequest.jsonPayload.self, + Components.RequestBodies.UpdatePetRequest.JsonPayload.self, from: requestBody, transforming: { value in .json(value) @@ -627,7 +627,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.updatePet.Input( + return Operations.UpdatePet.Input( path: path, headers: headers, body: body @@ -677,19 +677,19 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { request: request, requestBody: body, metadata: metadata, - forOperation: Operations.uploadAvatarForPet.id, + forOperation: Operations.UploadAvatarForPet.id, using: { APIHandler.uploadAvatarForPet($0) }, deserializer: { request, requestBody, metadata in - let path: Operations.uploadAvatarForPet.Input.Path = .init(petId: try converter.getPathParameterAsURI( + let path: Operations.UploadAvatarForPet.Input.Path = .init(petId: try converter.getPathParameterAsURI( in: metadata.pathParameters, name: "petId", - as: Components.Parameters.path_period_petId.self + as: Components.Parameters.Path_petId.self )) - let headers: Operations.uploadAvatarForPet.Input.Headers = .init(accept: try converter.extractAcceptHeaderIfPresent(in: request.headerFields)) + let headers: Operations.UploadAvatarForPet.Input.Headers = .init(accept: try converter.extractAcceptHeaderIfPresent(in: request.headerFields)) let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.uploadAvatarForPet.Input.Body + let body: Operations.UploadAvatarForPet.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -708,7 +708,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.uploadAvatarForPet.Input( + return Operations.UploadAvatarForPet.Input( path: path, headers: headers, body: body @@ -787,13 +787,13 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { request: request, requestBody: body, metadata: metadata, - forOperation: Operations.multipartDownloadTyped.id, + forOperation: Operations.MultipartDownloadTyped.id, using: { APIHandler.multipartDownloadTyped($0) }, deserializer: { request, requestBody, metadata in - let headers: Operations.multipartDownloadTyped.Input.Headers = .init(accept: try converter.extractAcceptHeaderIfPresent(in: request.headerFields)) - return Operations.multipartDownloadTyped.Input(headers: headers) + let headers: Operations.MultipartDownloadTyped.Input.Headers = .init(accept: try converter.extractAcceptHeaderIfPresent(in: request.headerFields)) + return Operations.MultipartDownloadTyped.Input(headers: headers) }, serializer: { output, request in switch output { @@ -831,7 +831,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { try converter.setHeaderFieldAsURI( in: &headerFields, name: "x-log-type", - value: value.headers.x_hyphen_log_hyphen_type + value: value.headers.xLogType ) let body = try converter.setResponseBodyAsBinary( value.body, @@ -896,7 +896,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { request: request, requestBody: body, metadata: metadata, - forOperation: Operations.multipartUploadTyped.id, + forOperation: Operations.MultipartUploadTyped.id, using: { APIHandler.multipartUploadTyped($0) }, @@ -912,7 +912,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -934,10 +934,10 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { let (name, filename) = try converter.extractContentDispositionNameAndFilename(in: headerFields) switch name { case "log": - let headers: Components.RequestBodies.MultipartUploadTypedRequest.multipartFormPayload.logPayload.Headers = .init(x_hyphen_log_hyphen_type: try converter.getOptionalHeaderFieldAsURI( + let headers: Components.RequestBodies.MultipartUploadTypedRequest.MultipartFormPayload.LogPayload.Headers = .init(xLogType: try converter.getOptionalHeaderFieldAsURI( in: headerFields, name: "x-log-type", - as: Components.RequestBodies.MultipartUploadTypedRequest.multipartFormPayload.logPayload.Headers.x_hyphen_log_hyphen_typePayload.self + as: Components.RequestBodies.MultipartUploadTypedRequest.MultipartFormPayload.LogPayload.Headers.XLogTypePayload.self )) try converter.verifyContentTypeIfPresent( in: headerFields, @@ -963,7 +963,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { matches: "application/json" ) let body = try await converter.getRequiredRequestBodyAsJSON( - Components.RequestBodies.MultipartUploadTypedRequest.multipartFormPayload.metadataPayload.bodyPayload.self, + Components.RequestBodies.MultipartUploadTypedRequest.MultipartFormPayload.MetadataPayload.BodyPayload.self, from: part.body, transforming: { $0 @@ -997,7 +997,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.multipartUploadTyped.Input(body: body) + return Operations.MultipartUploadTyped.Input(body: body) }, serializer: { output, request in switch output { diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift index b482b8cb..cdcb3785 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift @@ -18,42 +18,42 @@ public protocol APIProtocol: Sendable { /// /// - Remark: HTTP `GET /pets`. /// - Remark: Generated from `#/paths//pets/get(listPets)`. - func listPets(_ input: Operations.listPets.Input) async throws -> Operations.listPets.Output + func listPets(_ input: Operations.ListPets.Input) async throws -> Operations.ListPets.Output /// Create a pet /// /// - Remark: HTTP `POST /pets`. /// - Remark: Generated from `#/paths//pets/post(createPet)`. - func createPet(_ input: Operations.createPet.Input) async throws -> Operations.createPet.Output + func createPet(_ input: Operations.CreatePet.Input) async throws -> Operations.CreatePet.Output /// Create a pet using a url form /// /// - Remark: HTTP `POST /pets/create`. /// - Remark: Generated from `#/paths//pets/create/post(createPetWithForm)`. - func createPetWithForm(_ input: Operations.createPetWithForm.Input) async throws -> Operations.createPetWithForm.Output + func createPetWithForm(_ input: Operations.CreatePetWithForm.Input) async throws -> Operations.CreatePetWithForm.Output /// - Remark: HTTP `GET /pets/stats`. /// - Remark: Generated from `#/paths//pets/stats/get(getStats)`. - func getStats(_ input: Operations.getStats.Input) async throws -> Operations.getStats.Output + func getStats(_ input: Operations.GetStats.Input) async throws -> Operations.GetStats.Output /// - Remark: HTTP `POST /pets/stats`. /// - Remark: Generated from `#/paths//pets/stats/post(postStats)`. - func postStats(_ input: Operations.postStats.Input) async throws -> Operations.postStats.Output + func postStats(_ input: Operations.PostStats.Input) async throws -> Operations.PostStats.Output /// - Remark: HTTP `POST /probe/`. /// - Remark: Generated from `#/paths//probe//post(probe)`. - func probe(_ input: Operations.probe.Input) async throws -> Operations.probe.Output + func probe(_ input: Operations.Probe.Input) async throws -> Operations.Probe.Output /// Update just a specific property of an existing pet. Nothing is updated if no request body is provided. /// /// - Remark: HTTP `PATCH /pets/{petId}`. /// - Remark: Generated from `#/paths//pets/{petId}/patch(updatePet)`. - func updatePet(_ input: Operations.updatePet.Input) async throws -> Operations.updatePet.Output + func updatePet(_ input: Operations.UpdatePet.Input) async throws -> Operations.UpdatePet.Output /// Upload an avatar /// /// - Remark: HTTP `PUT /pets/{petId}/avatar`. /// - Remark: Generated from `#/paths//pets/{petId}/avatar/put(uploadAvatarForPet)`. - func uploadAvatarForPet(_ input: Operations.uploadAvatarForPet.Input) async throws -> Operations.uploadAvatarForPet.Output + func uploadAvatarForPet(_ input: Operations.UploadAvatarForPet.Input) async throws -> Operations.UploadAvatarForPet.Output /// - Remark: HTTP `GET /pets/multipart-typed`. /// - Remark: Generated from `#/paths//pets/multipart-typed/get(multipartDownloadTyped)`. - func multipartDownloadTyped(_ input: Operations.multipartDownloadTyped.Input) async throws -> Operations.multipartDownloadTyped.Output + func multipartDownloadTyped(_ input: Operations.MultipartDownloadTyped.Input) async throws -> Operations.MultipartDownloadTyped.Output /// - Remark: HTTP `POST /pets/multipart-typed`. /// - Remark: Generated from `#/paths//pets/multipart-typed/post(multipartUploadTyped)`. - func multipartUploadTyped(_ input: Operations.multipartUploadTyped.Input) async throws -> Operations.multipartUploadTyped.Output + func multipartUploadTyped(_ input: Operations.MultipartUploadTyped.Input) async throws -> Operations.MultipartUploadTyped.Output } /// Convenience overloads for operation inputs. @@ -66,10 +66,10 @@ extension APIProtocol { /// - Remark: HTTP `GET /pets`. /// - Remark: Generated from `#/paths//pets/get(listPets)`. public func listPets( - query: Operations.listPets.Input.Query = .init(), - headers: Operations.listPets.Input.Headers = .init() - ) async throws -> Operations.listPets.Output { - try await listPets(Operations.listPets.Input( + query: Operations.ListPets.Input.Query = .init(), + headers: Operations.ListPets.Input.Headers = .init() + ) async throws -> Operations.ListPets.Output { + try await listPets(Operations.ListPets.Input( query: query, headers: headers )) @@ -79,10 +79,10 @@ extension APIProtocol { /// - Remark: HTTP `POST /pets`. /// - Remark: Generated from `#/paths//pets/post(createPet)`. public func createPet( - headers: Operations.createPet.Input.Headers = .init(), - body: Operations.createPet.Input.Body - ) async throws -> Operations.createPet.Output { - try await createPet(Operations.createPet.Input( + headers: Operations.CreatePet.Input.Headers = .init(), + body: Operations.CreatePet.Input.Body + ) async throws -> Operations.CreatePet.Output { + try await createPet(Operations.CreatePet.Input( headers: headers, body: body )) @@ -91,34 +91,34 @@ extension APIProtocol { /// /// - Remark: HTTP `POST /pets/create`. /// - Remark: Generated from `#/paths//pets/create/post(createPetWithForm)`. - public func createPetWithForm(body: Operations.createPetWithForm.Input.Body) async throws -> Operations.createPetWithForm.Output { - try await createPetWithForm(Operations.createPetWithForm.Input(body: body)) + public func createPetWithForm(body: Operations.CreatePetWithForm.Input.Body) async throws -> Operations.CreatePetWithForm.Output { + try await createPetWithForm(Operations.CreatePetWithForm.Input(body: body)) } /// - Remark: HTTP `GET /pets/stats`. /// - Remark: Generated from `#/paths//pets/stats/get(getStats)`. - public func getStats(headers: Operations.getStats.Input.Headers = .init()) async throws -> Operations.getStats.Output { - try await getStats(Operations.getStats.Input(headers: headers)) + public func getStats(headers: Operations.GetStats.Input.Headers = .init()) async throws -> Operations.GetStats.Output { + try await getStats(Operations.GetStats.Input(headers: headers)) } /// - Remark: HTTP `POST /pets/stats`. /// - Remark: Generated from `#/paths//pets/stats/post(postStats)`. - public func postStats(body: Operations.postStats.Input.Body) async throws -> Operations.postStats.Output { - try await postStats(Operations.postStats.Input(body: body)) + public func postStats(body: Operations.PostStats.Input.Body) async throws -> Operations.PostStats.Output { + try await postStats(Operations.PostStats.Input(body: body)) } /// - Remark: HTTP `POST /probe/`. /// - Remark: Generated from `#/paths//probe//post(probe)`. - public func probe() async throws -> Operations.probe.Output { - try await probe(Operations.probe.Input()) + public func probe() async throws -> Operations.Probe.Output { + try await probe(Operations.Probe.Input()) } /// Update just a specific property of an existing pet. Nothing is updated if no request body is provided. /// /// - Remark: HTTP `PATCH /pets/{petId}`. /// - Remark: Generated from `#/paths//pets/{petId}/patch(updatePet)`. public func updatePet( - path: Operations.updatePet.Input.Path, - headers: Operations.updatePet.Input.Headers = .init(), + path: Operations.UpdatePet.Input.Path, + headers: Operations.UpdatePet.Input.Headers = .init(), body: Components.RequestBodies.UpdatePetRequest? = nil - ) async throws -> Operations.updatePet.Output { - try await updatePet(Operations.updatePet.Input( + ) async throws -> Operations.UpdatePet.Output { + try await updatePet(Operations.UpdatePet.Input( path: path, headers: headers, body: body @@ -129,11 +129,11 @@ extension APIProtocol { /// - Remark: HTTP `PUT /pets/{petId}/avatar`. /// - Remark: Generated from `#/paths//pets/{petId}/avatar/put(uploadAvatarForPet)`. public func uploadAvatarForPet( - path: Operations.uploadAvatarForPet.Input.Path, - headers: Operations.uploadAvatarForPet.Input.Headers = .init(), - body: Operations.uploadAvatarForPet.Input.Body - ) async throws -> Operations.uploadAvatarForPet.Output { - try await uploadAvatarForPet(Operations.uploadAvatarForPet.Input( + path: Operations.UploadAvatarForPet.Input.Path, + headers: Operations.UploadAvatarForPet.Input.Headers = .init(), + body: Operations.UploadAvatarForPet.Input.Body + ) async throws -> Operations.UploadAvatarForPet.Output { + try await uploadAvatarForPet(Operations.UploadAvatarForPet.Input( path: path, headers: headers, body: body @@ -141,13 +141,13 @@ extension APIProtocol { } /// - Remark: HTTP `GET /pets/multipart-typed`. /// - Remark: Generated from `#/paths//pets/multipart-typed/get(multipartDownloadTyped)`. - public func multipartDownloadTyped(headers: Operations.multipartDownloadTyped.Input.Headers = .init()) async throws -> Operations.multipartDownloadTyped.Output { - try await multipartDownloadTyped(Operations.multipartDownloadTyped.Input(headers: headers)) + public func multipartDownloadTyped(headers: Operations.MultipartDownloadTyped.Input.Headers = .init()) async throws -> Operations.MultipartDownloadTyped.Output { + try await multipartDownloadTyped(Operations.MultipartDownloadTyped.Input(headers: headers)) } /// - Remark: HTTP `POST /pets/multipart-typed`. /// - Remark: Generated from `#/paths//pets/multipart-typed/post(multipartUploadTyped)`. - public func multipartUploadTyped(body: Components.RequestBodies.MultipartUploadTypedRequest) async throws -> Operations.multipartUploadTyped.Output { - try await multipartUploadTyped(Operations.multipartUploadTyped.Input(body: body)) + public func multipartUploadTyped(body: Components.RequestBodies.MultipartUploadTypedRequest) async throws -> Operations.MultipartUploadTyped.Output { + try await multipartUploadTyped(Operations.MultipartUploadTyped.Input(body: body)) } } @@ -472,8 +472,8 @@ public enum Components { @frozen public enum PetKind: String, Codable, Hashable, Sendable, CaseIterable { case cat = "cat" case dog = "dog" - case ELEPHANT = "ELEPHANT" - case BIG_ELEPHANT_1 = "BIG_ELEPHANT_1" + case elephant = "ELEPHANT" + case bigElephant1 = "BIG_ELEPHANT_1" case _dollar_nake = "$nake" case _public = "public" } @@ -556,18 +556,18 @@ public enum Components { /// - Remark: Generated from `#/components/schemas/PetFeeding`. public struct PetFeeding: Codable, Hashable, Sendable { /// - Remark: Generated from `#/components/schemas/PetFeeding/schedule`. - @frozen public enum schedulePayload: String, Codable, Hashable, Sendable, CaseIterable { + @frozen public enum SchedulePayload: String, Codable, Hashable, Sendable, CaseIterable { case hourly = "hourly" case daily = "daily" case weekly = "weekly" } /// - Remark: Generated from `#/components/schemas/PetFeeding/schedule`. - public var schedule: Components.Schemas.PetFeeding.schedulePayload? + public var schedule: Components.Schemas.PetFeeding.SchedulePayload? /// Creates a new `PetFeeding`. /// /// - Parameters: /// - schedule: - public init(schedule: Components.Schemas.PetFeeding.schedulePayload? = nil) { + public init(schedule: Components.Schemas.PetFeeding.SchedulePayload? = nil) { self.schedule = schedule } public enum CodingKeys: String, CodingKey { @@ -575,7 +575,7 @@ public enum Components { } } /// - Remark: Generated from `#/components/schemas/DOB`. - public typealias DOB = Foundation.Date + public typealias Dob = Foundation.Date /// - Remark: Generated from `#/components/schemas/ExtraInfo`. public typealias ExtraInfo = Swift.String /// - Remark: Generated from `#/components/schemas/NoAdditionalProperties`. @@ -1008,9 +1008,9 @@ public enum Components { /// - Remark: Generated from `#/components/schemas/OneOfObjectsWithDiscriminator`. @frozen public enum OneOfObjectsWithDiscriminator: Codable, Hashable, Sendable { /// - Remark: Generated from `#/components/schemas/OneOfObjectsWithDiscriminator/Walk`. - case Walk(Components.Schemas.Walk) + case walk(Components.Schemas.Walk) /// - Remark: Generated from `#/components/schemas/OneOfObjectsWithDiscriminator/MessagedExercise`. - case MessagedExercise(Components.Schemas.MessagedExercise) + case messagedExercise(Components.Schemas.MessagedExercise) public enum CodingKeys: String, CodingKey { case kind } @@ -1022,9 +1022,9 @@ public enum Components { ) switch discriminator { case "Walk", "#/components/schemas/Walk": - self = .Walk(try .init(from: decoder)) + self = .walk(try .init(from: decoder)) case "MessagedExercise", "#/components/schemas/MessagedExercise": - self = .MessagedExercise(try .init(from: decoder)) + self = .messagedExercise(try .init(from: decoder)) default: throw Swift.DecodingError.unknownOneOfDiscriminator( discriminatorKey: CodingKeys.kind, @@ -1035,9 +1035,9 @@ public enum Components { } public func encode(to encoder: any Encoder) throws { switch self { - case let .Walk(value): + case let .walk(value): try value.encode(to: encoder) - case let .MessagedExercise(value): + case let .messagedExercise(value): try value.encode(to: encoder) } } @@ -1130,10 +1130,10 @@ public enum Components { } } /// - Remark: Generated from `#/components/schemas/RecursivePetNested/parent`. - public struct parentPayload: Codable, Hashable, Sendable { + public struct ParentPayload: Codable, Hashable, Sendable { /// - Remark: Generated from `#/components/schemas/RecursivePetNested/parent/nested`. public var nested: Components.Schemas.RecursivePetNested - /// Creates a new `parentPayload`. + /// Creates a new `ParentPayload`. /// /// - Parameters: /// - nested: @@ -1145,7 +1145,7 @@ public enum Components { } } /// - Remark: Generated from `#/components/schemas/RecursivePetNested/parent`. - public var parent: Components.Schemas.RecursivePetNested.parentPayload? { + public var parent: Components.Schemas.RecursivePetNested.ParentPayload? { get { self.storage.value.parent } @@ -1160,7 +1160,7 @@ public enum Components { /// - parent: public init( name: Swift.String, - parent: Components.Schemas.RecursivePetNested.parentPayload? = nil + parent: Components.Schemas.RecursivePetNested.ParentPayload? = nil ) { self.storage = .init(value: .init( name: name, @@ -1183,10 +1183,10 @@ public enum Components { /// - Remark: Generated from `#/components/schemas/RecursivePetNested/name`. var name: Swift.String /// - Remark: Generated from `#/components/schemas/RecursivePetNested/parent`. - struct parentPayload: Codable, Hashable, Sendable { + struct ParentPayload: Codable, Hashable, Sendable { /// - Remark: Generated from `#/components/schemas/RecursivePetNested/parent/nested`. public var nested: Components.Schemas.RecursivePetNested - /// Creates a new `parentPayload`. + /// Creates a new `ParentPayload`. /// /// - Parameters: /// - nested: @@ -1198,10 +1198,10 @@ public enum Components { } } /// - Remark: Generated from `#/components/schemas/RecursivePetNested/parent`. - var parent: Components.Schemas.RecursivePetNested.parentPayload? + var parent: Components.Schemas.RecursivePetNested.ParentPayload? init( name: Swift.String, - parent: Components.Schemas.RecursivePetNested.parentPayload? = nil + parent: Components.Schemas.RecursivePetNested.ParentPayload? = nil ) { self.name = name self.parent = parent @@ -1348,9 +1348,9 @@ public enum Components { /// - Remark: Generated from `#/components/schemas/RecursivePetOneOf`. @frozen public enum RecursivePetOneOf: Codable, Hashable, Sendable { /// - Remark: Generated from `#/components/schemas/RecursivePetOneOf/RecursivePetOneOfFirst`. - case RecursivePetOneOfFirst(Components.Schemas.RecursivePetOneOfFirst) + case recursivePetOneOfFirst(Components.Schemas.RecursivePetOneOfFirst) /// - Remark: Generated from `#/components/schemas/RecursivePetOneOf/RecursivePetOneOfSecond`. - case RecursivePetOneOfSecond(Components.Schemas.RecursivePetOneOfSecond) + case recursivePetOneOfSecond(Components.Schemas.RecursivePetOneOfSecond) public enum CodingKeys: String, CodingKey { case _type = "type" } @@ -1362,9 +1362,9 @@ public enum Components { ) switch discriminator { case "RecursivePetOneOfFirst", "#/components/schemas/RecursivePetOneOfFirst": - self = .RecursivePetOneOfFirst(try .init(from: decoder)) + self = .recursivePetOneOfFirst(try .init(from: decoder)) case "RecursivePetOneOfSecond", "#/components/schemas/RecursivePetOneOfSecond": - self = .RecursivePetOneOfSecond(try .init(from: decoder)) + self = .recursivePetOneOfSecond(try .init(from: decoder)) default: throw Swift.DecodingError.unknownOneOfDiscriminator( discriminatorKey: CodingKeys._type, @@ -1375,9 +1375,9 @@ public enum Components { } public func encode(to encoder: any Encoder) throws { switch self { - case let .RecursivePetOneOfFirst(value): + case let .recursivePetOneOfFirst(value): try value.encode(to: encoder) - case let .RecursivePetOneOfSecond(value): + case let .recursivePetOneOfSecond(value): try value.encode(to: encoder) } } @@ -1542,18 +1542,18 @@ public enum Components { /// Supply this parameter to filter pets born since the provided date. /// /// - Remark: Generated from `#/components/parameters/query.born-since`. - public typealias query_period_born_hyphen_since = Components.Schemas.DOB + public typealias Query_bornSince = Components.Schemas.Dob /// The id of the pet to retrieve /// /// - Remark: Generated from `#/components/parameters/path.petId`. - public typealias path_period_petId = Swift.Int64 + public typealias Path_petId = Swift.Int64 } /// Types generated from the `#/components/requestBodies` section of the OpenAPI document. public enum RequestBodies { /// - Remark: Generated from `#/components/requestBodies/UpdatePetRequest`. @frozen public enum UpdatePetRequest: Sendable, Hashable { /// - Remark: Generated from `#/components/requestBodies/UpdatePetRequest/json`. - public struct jsonPayload: Codable, Hashable, Sendable { + public struct JsonPayload: Codable, Hashable, Sendable { /// Pet name /// /// - Remark: Generated from `#/components/requestBodies/UpdatePetRequest/json/name`. @@ -1562,7 +1562,7 @@ public enum Components { public var kind: Components.Schemas.PetKind? /// - Remark: Generated from `#/components/requestBodies/UpdatePetRequest/json/tag`. public var tag: Swift.String? - /// Creates a new `jsonPayload`. + /// Creates a new `JsonPayload`. /// /// - Parameters: /// - name: Pet name @@ -1584,57 +1584,57 @@ public enum Components { } } /// - Remark: Generated from `#/components/requestBodies/UpdatePetRequest/content/application\/json`. - case json(Components.RequestBodies.UpdatePetRequest.jsonPayload) + case json(Components.RequestBodies.UpdatePetRequest.JsonPayload) } /// - Remark: Generated from `#/components/requestBodies/MultipartUploadTypedRequest`. @frozen public enum MultipartUploadTypedRequest: Sendable, Hashable { /// - Remark: Generated from `#/components/requestBodies/MultipartUploadTypedRequest/multipartForm`. - @frozen public enum multipartFormPayload: Sendable, Hashable { + @frozen public enum MultipartFormPayload: Sendable, Hashable { /// - Remark: Generated from `#/components/requestBodies/MultipartUploadTypedRequest/multipartForm/log`. - public struct logPayload: Sendable, Hashable { + public struct LogPayload: Sendable, Hashable { /// - Remark: Generated from `#/components/requestBodies/MultipartUploadTypedRequest/multipartForm/log/headers`. public struct Headers: Sendable, Hashable { /// - Remark: Generated from `#/components/requestBodies/MultipartUploadTypedRequest/multipartForm/log/headers/x-log-type`. - @frozen public enum x_hyphen_log_hyphen_typePayload: String, Codable, Hashable, Sendable, CaseIterable { + @frozen public enum XLogTypePayload: String, Codable, Hashable, Sendable, CaseIterable { case structured = "structured" case unstructured = "unstructured" } /// The type of the log. /// /// - Remark: Generated from `#/components/requestBodies/MultipartUploadTypedRequest/multipartForm/log/headers/x-log-type`. - public var x_hyphen_log_hyphen_type: Components.RequestBodies.MultipartUploadTypedRequest.multipartFormPayload.logPayload.Headers.x_hyphen_log_hyphen_typePayload? + public var xLogType: Components.RequestBodies.MultipartUploadTypedRequest.MultipartFormPayload.LogPayload.Headers.XLogTypePayload? /// Creates a new `Headers`. /// /// - Parameters: - /// - x_hyphen_log_hyphen_type: The type of the log. - public init(x_hyphen_log_hyphen_type: Components.RequestBodies.MultipartUploadTypedRequest.multipartFormPayload.logPayload.Headers.x_hyphen_log_hyphen_typePayload? = nil) { - self.x_hyphen_log_hyphen_type = x_hyphen_log_hyphen_type + /// - xLogType: The type of the log. + public init(xLogType: Components.RequestBodies.MultipartUploadTypedRequest.MultipartFormPayload.LogPayload.Headers.XLogTypePayload? = nil) { + self.xLogType = xLogType } } /// Received HTTP response headers - public var headers: Components.RequestBodies.MultipartUploadTypedRequest.multipartFormPayload.logPayload.Headers + public var headers: Components.RequestBodies.MultipartUploadTypedRequest.MultipartFormPayload.LogPayload.Headers public var body: OpenAPIRuntime.HTTPBody - /// Creates a new `logPayload`. + /// Creates a new `LogPayload`. /// /// - Parameters: /// - headers: Received HTTP response headers /// - body: public init( - headers: Components.RequestBodies.MultipartUploadTypedRequest.multipartFormPayload.logPayload.Headers = .init(), + headers: Components.RequestBodies.MultipartUploadTypedRequest.MultipartFormPayload.LogPayload.Headers = .init(), body: OpenAPIRuntime.HTTPBody ) { self.headers = headers self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) /// - Remark: Generated from `#/components/requestBodies/MultipartUploadTypedRequest/multipartForm/metadata`. - public struct metadataPayload: Sendable, Hashable { + public struct MetadataPayload: Sendable, Hashable { /// - Remark: Generated from `#/components/requestBodies/MultipartUploadTypedRequest/multipartForm/metadata/content/body`. - public struct bodyPayload: Codable, Hashable, Sendable { + public struct BodyPayload: Codable, Hashable, Sendable { /// - Remark: Generated from `#/components/requestBodies/MultipartUploadTypedRequest/multipartForm/metadata/content/body/createdAt`. public var createdAt: Foundation.Date - /// Creates a new `bodyPayload`. + /// Creates a new `BodyPayload`. /// /// - Parameters: /// - createdAt: @@ -1645,20 +1645,20 @@ public enum Components { case createdAt } } - public var body: Components.RequestBodies.MultipartUploadTypedRequest.multipartFormPayload.metadataPayload.bodyPayload - /// Creates a new `metadataPayload`. + public var body: Components.RequestBodies.MultipartUploadTypedRequest.MultipartFormPayload.MetadataPayload.BodyPayload + /// Creates a new `MetadataPayload`. /// /// - Parameters: /// - body: - public init(body: Components.RequestBodies.MultipartUploadTypedRequest.multipartFormPayload.metadataPayload.bodyPayload) { + public init(body: Components.RequestBodies.MultipartUploadTypedRequest.MultipartFormPayload.MetadataPayload.BodyPayload) { self.body = body } } - case metadata(OpenAPIRuntime.MultipartPart) + case metadata(OpenAPIRuntime.MultipartPart) /// - Remark: Generated from `#/components/requestBodies/MultipartUploadTypedRequest/multipartForm/keyword`. - public struct keywordPayload: Sendable, Hashable { + public struct KeywordPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody - /// Creates a new `keywordPayload`. + /// Creates a new `KeywordPayload`. /// /// - Parameters: /// - body: @@ -1666,11 +1666,11 @@ public enum Components { self.body = body } } - case keyword(OpenAPIRuntime.MultipartPart) + case keyword(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } /// - Remark: Generated from `#/components/requestBodies/MultipartUploadTypedRequest/content/multipart\/form-data`. - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } } /// Types generated from the `#/components/responses` section of the OpenAPI document. @@ -1681,13 +1681,13 @@ public enum Components { /// A description here. /// /// - Remark: Generated from `#/components/responses/ErrorBadRequest/headers/X-Reason`. - public var X_hyphen_Reason: Swift.String? + public var xReason: Swift.String? /// Creates a new `Headers`. /// /// - Parameters: - /// - X_hyphen_Reason: A description here. - public init(X_hyphen_Reason: Swift.String? = nil) { - self.X_hyphen_Reason = X_hyphen_Reason + /// - xReason: A description here. + public init(xReason: Swift.String? = nil) { + self.xReason = xReason } } /// Received HTTP response headers @@ -1695,10 +1695,10 @@ public enum Components { /// - Remark: Generated from `#/components/responses/ErrorBadRequest/content`. @frozen public enum Body: Sendable, Hashable { /// - Remark: Generated from `#/components/responses/ErrorBadRequest/content/json`. - public struct jsonPayload: Codable, Hashable, Sendable { + public struct JsonPayload: Codable, Hashable, Sendable { /// - Remark: Generated from `#/components/responses/ErrorBadRequest/content/json/code`. public var code: Swift.Int - /// Creates a new `jsonPayload`. + /// Creates a new `JsonPayload`. /// /// - Parameters: /// - code: @@ -1710,12 +1710,12 @@ public enum Components { } } /// - Remark: Generated from `#/components/responses/ErrorBadRequest/content/application\/json`. - case json(Components.Responses.ErrorBadRequest.Body.jsonPayload) + case json(Components.Responses.ErrorBadRequest.Body.JsonPayload) /// The associated value of the enum case if `self` is `.json`. /// /// - Throws: An error if `self` is not `.json`. /// - SeeAlso: `.json`. - public var json: Components.Responses.ErrorBadRequest.Body.jsonPayload { + public var json: Components.Responses.ErrorBadRequest.Body.JsonPayload { get throws { switch self { case let .json(body): @@ -1743,52 +1743,52 @@ public enum Components { /// - Remark: Generated from `#/components/responses/MultipartDownloadTypedResponse/content`. @frozen public enum Body: Sendable, Hashable { /// - Remark: Generated from `#/components/responses/MultipartDownloadTypedResponse/content/multipartForm`. - @frozen public enum multipartFormPayload: Sendable, Hashable { + @frozen public enum MultipartFormPayload: Sendable, Hashable { /// - Remark: Generated from `#/components/responses/MultipartDownloadTypedResponse/content/multipartForm/log`. - public struct logPayload: Sendable, Hashable { + public struct LogPayload: Sendable, Hashable { /// - Remark: Generated from `#/components/responses/MultipartDownloadTypedResponse/content/multipartForm/log/headers`. public struct Headers: Sendable, Hashable { /// - Remark: Generated from `#/components/responses/MultipartDownloadTypedResponse/content/multipartForm/log/headers/x-log-type`. - @frozen public enum x_hyphen_log_hyphen_typePayload: String, Codable, Hashable, Sendable, CaseIterable { + @frozen public enum XLogTypePayload: String, Codable, Hashable, Sendable, CaseIterable { case structured = "structured" case unstructured = "unstructured" } /// The type of the log. /// /// - Remark: Generated from `#/components/responses/MultipartDownloadTypedResponse/content/multipartForm/log/headers/x-log-type`. - public var x_hyphen_log_hyphen_type: Components.Responses.MultipartDownloadTypedResponse.Body.multipartFormPayload.logPayload.Headers.x_hyphen_log_hyphen_typePayload? + public var xLogType: Components.Responses.MultipartDownloadTypedResponse.Body.MultipartFormPayload.LogPayload.Headers.XLogTypePayload? /// Creates a new `Headers`. /// /// - Parameters: - /// - x_hyphen_log_hyphen_type: The type of the log. - public init(x_hyphen_log_hyphen_type: Components.Responses.MultipartDownloadTypedResponse.Body.multipartFormPayload.logPayload.Headers.x_hyphen_log_hyphen_typePayload? = nil) { - self.x_hyphen_log_hyphen_type = x_hyphen_log_hyphen_type + /// - xLogType: The type of the log. + public init(xLogType: Components.Responses.MultipartDownloadTypedResponse.Body.MultipartFormPayload.LogPayload.Headers.XLogTypePayload? = nil) { + self.xLogType = xLogType } } /// Received HTTP response headers - public var headers: Components.Responses.MultipartDownloadTypedResponse.Body.multipartFormPayload.logPayload.Headers + public var headers: Components.Responses.MultipartDownloadTypedResponse.Body.MultipartFormPayload.LogPayload.Headers public var body: OpenAPIRuntime.HTTPBody - /// Creates a new `logPayload`. + /// Creates a new `LogPayload`. /// /// - Parameters: /// - headers: Received HTTP response headers /// - body: public init( - headers: Components.Responses.MultipartDownloadTypedResponse.Body.multipartFormPayload.logPayload.Headers = .init(), + headers: Components.Responses.MultipartDownloadTypedResponse.Body.MultipartFormPayload.LogPayload.Headers = .init(), body: OpenAPIRuntime.HTTPBody ) { self.headers = headers self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) /// - Remark: Generated from `#/components/responses/MultipartDownloadTypedResponse/content/multipartForm/metadata`. - public struct metadataPayload: Sendable, Hashable { + public struct MetadataPayload: Sendable, Hashable { /// - Remark: Generated from `#/components/responses/MultipartDownloadTypedResponse/content/multipartForm/metadata/content/body`. - public struct bodyPayload: Codable, Hashable, Sendable { + public struct BodyPayload: Codable, Hashable, Sendable { /// - Remark: Generated from `#/components/responses/MultipartDownloadTypedResponse/content/multipartForm/metadata/content/body/createdAt`. public var createdAt: Foundation.Date - /// Creates a new `bodyPayload`. + /// Creates a new `BodyPayload`. /// /// - Parameters: /// - createdAt: @@ -1799,20 +1799,20 @@ public enum Components { case createdAt } } - public var body: Components.Responses.MultipartDownloadTypedResponse.Body.multipartFormPayload.metadataPayload.bodyPayload - /// Creates a new `metadataPayload`. + public var body: Components.Responses.MultipartDownloadTypedResponse.Body.MultipartFormPayload.MetadataPayload.BodyPayload + /// Creates a new `MetadataPayload`. /// /// - Parameters: /// - body: - public init(body: Components.Responses.MultipartDownloadTypedResponse.Body.multipartFormPayload.metadataPayload.bodyPayload) { + public init(body: Components.Responses.MultipartDownloadTypedResponse.Body.MultipartFormPayload.MetadataPayload.BodyPayload) { self.body = body } } - case metadata(OpenAPIRuntime.MultipartPart) + case metadata(OpenAPIRuntime.MultipartPart) /// - Remark: Generated from `#/components/responses/MultipartDownloadTypedResponse/content/multipartForm/keyword`. - public struct keywordPayload: Sendable, Hashable { + public struct KeywordPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody - /// Creates a new `keywordPayload`. + /// Creates a new `KeywordPayload`. /// /// - Parameters: /// - body: @@ -1820,16 +1820,16 @@ public enum Components { self.body = body } } - case keyword(OpenAPIRuntime.MultipartPart) + case keyword(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } /// - Remark: Generated from `#/components/responses/MultipartDownloadTypedResponse/content/multipart\/form-data`. - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) /// The associated value of the enum case if `self` is `.multipartForm`. /// /// - Throws: An error if `self` is not `.multipartForm`. /// - SeeAlso: `.multipartForm`. - public var multipartForm: OpenAPIRuntime.MultipartBody { + public var multipartForm: OpenAPIRuntime.MultipartBody { get throws { switch self { case let .multipartForm(body): @@ -1867,7 +1867,7 @@ public enum Operations { /// /// - Remark: HTTP `GET /pets`. /// - Remark: Generated from `#/paths//pets/get(listPets)`. - public enum listPets { + public enum ListPets { public static let id: Swift.String = "listPets" public struct Input: Sendable, Hashable { /// - Remark: Generated from `#/paths/pets/GET/query`. @@ -1877,28 +1877,28 @@ public enum Operations { /// - Remark: Generated from `#/paths/pets/GET/query/limit`. public var limit: Swift.Int32? /// - Remark: Generated from `#/paths/pets/GET/query/habitat`. - @frozen public enum habitatPayload: String, Codable, Hashable, Sendable, CaseIterable { + @frozen public enum HabitatPayload: String, Codable, Hashable, Sendable, CaseIterable { case water = "water" case land = "land" case air = "air" - case _empty = "" + case _empty_ = "" } /// - Remark: Generated from `#/paths/pets/GET/query/habitat`. - public var habitat: Operations.listPets.Input.Query.habitatPayload? - /// - Remark: Generated from `#/paths/pets/GET/query/feedsPayload`. - @frozen public enum feedsPayloadPayload: String, Codable, Hashable, Sendable, CaseIterable { + public var habitat: Operations.ListPets.Input.Query.HabitatPayload? + /// - Remark: Generated from `#/paths/pets/GET/query/FeedsPayload`. + @frozen public enum FeedsPayloadPayload: String, Codable, Hashable, Sendable, CaseIterable { case omnivore = "omnivore" case carnivore = "carnivore" case herbivore = "herbivore" } /// - Remark: Generated from `#/paths/pets/GET/query/feeds`. - public typealias feedsPayload = [Operations.listPets.Input.Query.feedsPayloadPayload] + public typealias FeedsPayload = [Operations.ListPets.Input.Query.FeedsPayloadPayload] /// - Remark: Generated from `#/paths/pets/GET/query/feeds`. - public var feeds: Operations.listPets.Input.Query.feedsPayload? + public var feeds: Operations.ListPets.Input.Query.FeedsPayload? /// Supply this parameter to filter pets born since the provided date. /// /// - Remark: Generated from `#/paths/pets/GET/query/since`. - public var since: Components.Parameters.query_period_born_hyphen_since? + public var since: Components.Parameters.Query_bornSince? /// Creates a new `Query`. /// /// - Parameters: @@ -1908,9 +1908,9 @@ public enum Operations { /// - since: Supply this parameter to filter pets born since the provided date. public init( limit: Swift.Int32? = nil, - habitat: Operations.listPets.Input.Query.habitatPayload? = nil, - feeds: Operations.listPets.Input.Query.feedsPayload? = nil, - since: Components.Parameters.query_period_born_hyphen_since? = nil + habitat: Operations.ListPets.Input.Query.HabitatPayload? = nil, + feeds: Operations.ListPets.Input.Query.FeedsPayload? = nil, + since: Components.Parameters.Query_bornSince? = nil ) { self.limit = limit self.habitat = habitat @@ -1918,36 +1918,36 @@ public enum Operations { self.since = since } } - public var query: Operations.listPets.Input.Query + public var query: Operations.ListPets.Input.Query /// - Remark: Generated from `#/paths/pets/GET/header`. public struct Headers: Sendable, Hashable { /// Request identifier /// /// - Remark: Generated from `#/paths/pets/GET/header/My-Request-UUID`. - public var My_hyphen_Request_hyphen_UUID: Swift.String? - public var accept: [OpenAPIRuntime.AcceptHeaderContentType] + public var myRequestUUID: Swift.String? + public var accept: [OpenAPIRuntime.AcceptHeaderContentType] /// Creates a new `Headers`. /// /// - Parameters: - /// - My_hyphen_Request_hyphen_UUID: Request identifier + /// - myRequestUUID: Request identifier /// - accept: public init( - My_hyphen_Request_hyphen_UUID: Swift.String? = nil, - accept: [OpenAPIRuntime.AcceptHeaderContentType] = .defaultValues() + myRequestUUID: Swift.String? = nil, + accept: [OpenAPIRuntime.AcceptHeaderContentType] = .defaultValues() ) { - self.My_hyphen_Request_hyphen_UUID = My_hyphen_Request_hyphen_UUID + self.myRequestUUID = myRequestUUID self.accept = accept } } - public var headers: Operations.listPets.Input.Headers + public var headers: Operations.ListPets.Input.Headers /// Creates a new `Input`. /// /// - Parameters: /// - query: /// - headers: public init( - query: Operations.listPets.Input.Query = .init(), - headers: Operations.listPets.Input.Headers = .init() + query: Operations.ListPets.Input.Query = .init(), + headers: Operations.ListPets.Input.Headers = .init() ) { self.query = query self.headers = headers @@ -1960,26 +1960,26 @@ public enum Operations { /// Response identifier /// /// - Remark: Generated from `#/paths/pets/GET/responses/200/headers/My-Response-UUID`. - public var My_hyphen_Response_hyphen_UUID: Swift.String + public var myResponseUUID: Swift.String /// A description here. /// /// - Remark: Generated from `#/paths/pets/GET/responses/200/headers/My-Tracing-Header`. - public var My_hyphen_Tracing_hyphen_Header: Components.Headers.TracingHeader? + public var myTracingHeader: Components.Headers.TracingHeader? /// Creates a new `Headers`. /// /// - Parameters: - /// - My_hyphen_Response_hyphen_UUID: Response identifier - /// - My_hyphen_Tracing_hyphen_Header: A description here. + /// - myResponseUUID: Response identifier + /// - myTracingHeader: A description here. public init( - My_hyphen_Response_hyphen_UUID: Swift.String, - My_hyphen_Tracing_hyphen_Header: Components.Headers.TracingHeader? = nil + myResponseUUID: Swift.String, + myTracingHeader: Components.Headers.TracingHeader? = nil ) { - self.My_hyphen_Response_hyphen_UUID = My_hyphen_Response_hyphen_UUID - self.My_hyphen_Tracing_hyphen_Header = My_hyphen_Tracing_hyphen_Header + self.myResponseUUID = myResponseUUID + self.myTracingHeader = myTracingHeader } } /// Received HTTP response headers - public var headers: Operations.listPets.Output.Ok.Headers + public var headers: Operations.ListPets.Output.Ok.Headers /// - Remark: Generated from `#/paths/pets/GET/responses/200/content`. @frozen public enum Body: Sendable, Hashable { /// - Remark: Generated from `#/paths/pets/GET/responses/200/content/application\/json`. @@ -1998,15 +1998,15 @@ public enum Operations { } } /// Received HTTP response body - public var body: Operations.listPets.Output.Ok.Body + public var body: Operations.ListPets.Output.Ok.Body /// Creates a new `Ok`. /// /// - Parameters: /// - headers: Received HTTP response headers /// - body: Received HTTP response body public init( - headers: Operations.listPets.Output.Ok.Headers, - body: Operations.listPets.Output.Ok.Body + headers: Operations.ListPets.Output.Ok.Headers, + body: Operations.ListPets.Output.Ok.Body ) { self.headers = headers self.body = body @@ -2017,12 +2017,12 @@ public enum Operations { /// - Remark: Generated from `#/paths//pets/get(listPets)/responses/200`. /// /// HTTP response code: `200 ok`. - case ok(Operations.listPets.Output.Ok) + case ok(Operations.ListPets.Output.Ok) /// The associated value of the enum case if `self` is `.ok`. /// /// - Throws: An error if `self` is not `.ok`. /// - SeeAlso: `.ok`. - public var ok: Operations.listPets.Output.Ok { + public var ok: Operations.ListPets.Output.Ok { get throws { switch self { case let .ok(response): @@ -2054,12 +2054,12 @@ public enum Operations { } } /// Received HTTP response body - public var body: Operations.listPets.Output.Default.Body + public var body: Operations.ListPets.Output.Default.Body /// Creates a new `Default`. /// /// - Parameters: /// - body: Received HTTP response body - public init(body: Operations.listPets.Output.Default.Body) { + public init(body: Operations.ListPets.Output.Default.Body) { self.body = body } } @@ -2068,12 +2068,12 @@ public enum Operations { /// - Remark: Generated from `#/paths//pets/get(listPets)/responses/default`. /// /// HTTP response code: `default`. - case `default`(statusCode: Swift.Int, Operations.listPets.Output.Default) + case `default`(statusCode: Swift.Int, Operations.ListPets.Output.Default) /// The associated value of the enum case if `self` is `.`default``. /// /// - Throws: An error if `self` is not `.`default``. /// - SeeAlso: `.`default``. - public var `default`: Operations.listPets.Output.Default { + public var `default`: Operations.ListPets.Output.Default { get throws { switch self { case let .`default`(_, response): @@ -2117,7 +2117,7 @@ public enum Operations { /// /// - Remark: HTTP `POST /pets`. /// - Remark: Generated from `#/paths//pets/post(createPet)`. - public enum createPet { + public enum CreatePet { public static let id: Swift.String = "createPet" public struct Input: Sendable, Hashable { /// - Remark: Generated from `#/paths/pets/POST/header`. @@ -2125,36 +2125,36 @@ public enum Operations { /// A description here. /// /// - Remark: Generated from `#/paths/pets/POST/header/X-Extra-Arguments`. - public var X_hyphen_Extra_hyphen_Arguments: Components.Schemas.CodeError? - public var accept: [OpenAPIRuntime.AcceptHeaderContentType] + public var xExtraArguments: Components.Schemas.CodeError? + public var accept: [OpenAPIRuntime.AcceptHeaderContentType] /// Creates a new `Headers`. /// /// - Parameters: - /// - X_hyphen_Extra_hyphen_Arguments: A description here. + /// - xExtraArguments: A description here. /// - accept: public init( - X_hyphen_Extra_hyphen_Arguments: Components.Schemas.CodeError? = nil, - accept: [OpenAPIRuntime.AcceptHeaderContentType] = .defaultValues() + xExtraArguments: Components.Schemas.CodeError? = nil, + accept: [OpenAPIRuntime.AcceptHeaderContentType] = .defaultValues() ) { - self.X_hyphen_Extra_hyphen_Arguments = X_hyphen_Extra_hyphen_Arguments + self.xExtraArguments = xExtraArguments self.accept = accept } } - public var headers: Operations.createPet.Input.Headers + public var headers: Operations.CreatePet.Input.Headers /// - Remark: Generated from `#/paths/pets/POST/requestBody`. @frozen public enum Body: Sendable, Hashable { /// - Remark: Generated from `#/paths/pets/POST/requestBody/content/application\/json`. case json(Components.Schemas.CreatePetRequest) } - public var body: Operations.createPet.Input.Body + public var body: Operations.CreatePet.Input.Body /// Creates a new `Input`. /// /// - Parameters: /// - headers: /// - body: public init( - headers: Operations.createPet.Input.Headers = .init(), - body: Operations.createPet.Input.Body + headers: Operations.CreatePet.Input.Headers = .init(), + body: Operations.CreatePet.Input.Body ) { self.headers = headers self.body = body @@ -2167,17 +2167,17 @@ public enum Operations { /// A description here. /// /// - Remark: Generated from `#/paths/pets/POST/responses/201/headers/X-Extra-Arguments`. - public var X_hyphen_Extra_hyphen_Arguments: Components.Schemas.CodeError? + public var xExtraArguments: Components.Schemas.CodeError? /// Creates a new `Headers`. /// /// - Parameters: - /// - X_hyphen_Extra_hyphen_Arguments: A description here. - public init(X_hyphen_Extra_hyphen_Arguments: Components.Schemas.CodeError? = nil) { - self.X_hyphen_Extra_hyphen_Arguments = X_hyphen_Extra_hyphen_Arguments + /// - xExtraArguments: A description here. + public init(xExtraArguments: Components.Schemas.CodeError? = nil) { + self.xExtraArguments = xExtraArguments } } /// Received HTTP response headers - public var headers: Operations.createPet.Output.Created.Headers + public var headers: Operations.CreatePet.Output.Created.Headers /// - Remark: Generated from `#/paths/pets/POST/responses/201/content`. @frozen public enum Body: Sendable, Hashable { /// - Remark: Generated from `#/paths/pets/POST/responses/201/content/application\/json`. @@ -2196,15 +2196,15 @@ public enum Operations { } } /// Received HTTP response body - public var body: Operations.createPet.Output.Created.Body + public var body: Operations.CreatePet.Output.Created.Body /// Creates a new `Created`. /// /// - Parameters: /// - headers: Received HTTP response headers /// - body: Received HTTP response body public init( - headers: Operations.createPet.Output.Created.Headers = .init(), - body: Operations.createPet.Output.Created.Body + headers: Operations.CreatePet.Output.Created.Headers = .init(), + body: Operations.CreatePet.Output.Created.Body ) { self.headers = headers self.body = body @@ -2215,12 +2215,12 @@ public enum Operations { /// - Remark: Generated from `#/paths//pets/post(createPet)/responses/201`. /// /// HTTP response code: `201 created`. - case created(Operations.createPet.Output.Created) + case created(Operations.CreatePet.Output.Created) /// The associated value of the enum case if `self` is `.created`. /// /// - Throws: An error if `self` is not `.created`. /// - SeeAlso: `.created`. - public var created: Operations.createPet.Output.Created { + public var created: Operations.CreatePet.Output.Created { get throws { switch self { case let .created(response): @@ -2291,7 +2291,7 @@ public enum Operations { /// /// - Remark: HTTP `POST /pets/create`. /// - Remark: Generated from `#/paths//pets/create/post(createPetWithForm)`. - public enum createPetWithForm { + public enum CreatePetWithForm { public static let id: Swift.String = "createPetWithForm" public struct Input: Sendable, Hashable { /// - Remark: Generated from `#/paths/pets/create/POST/requestBody`. @@ -2299,12 +2299,12 @@ public enum Operations { /// - Remark: Generated from `#/paths/pets/create/POST/requestBody/content/application\/x-www-form-urlencoded`. case urlEncodedForm(Components.Schemas.CreatePetRequest) } - public var body: Operations.createPetWithForm.Input.Body + public var body: Operations.CreatePetWithForm.Input.Body /// Creates a new `Input`. /// /// - Parameters: /// - body: - public init(body: Operations.createPetWithForm.Input.Body) { + public init(body: Operations.CreatePetWithForm.Input.Body) { self.body = body } } @@ -2318,7 +2318,7 @@ public enum Operations { /// - Remark: Generated from `#/paths//pets/create/post(createPetWithForm)/responses/204`. /// /// HTTP response code: `204 noContent`. - case noContent(Operations.createPetWithForm.Output.NoContent) + case noContent(Operations.CreatePetWithForm.Output.NoContent) /// Successfully created pet using a url form /// /// - Remark: Generated from `#/paths//pets/create/post(createPetWithForm)/responses/204`. @@ -2331,7 +2331,7 @@ public enum Operations { /// /// - Throws: An error if `self` is not `.noContent`. /// - SeeAlso: `.noContent`. - public var noContent: Operations.createPetWithForm.Output.NoContent { + public var noContent: Operations.CreatePetWithForm.Output.NoContent { get throws { switch self { case let .noContent(response): @@ -2352,26 +2352,26 @@ public enum Operations { } /// - Remark: HTTP `GET /pets/stats`. /// - Remark: Generated from `#/paths//pets/stats/get(getStats)`. - public enum getStats { + public enum GetStats { public static let id: Swift.String = "getStats" public struct Input: Sendable, Hashable { /// - Remark: Generated from `#/paths/pets/stats/GET/header`. public struct Headers: Sendable, Hashable { - public var accept: [OpenAPIRuntime.AcceptHeaderContentType] + public var accept: [OpenAPIRuntime.AcceptHeaderContentType] /// Creates a new `Headers`. /// /// - Parameters: /// - accept: - public init(accept: [OpenAPIRuntime.AcceptHeaderContentType] = .defaultValues()) { + public init(accept: [OpenAPIRuntime.AcceptHeaderContentType] = .defaultValues()) { self.accept = accept } } - public var headers: Operations.getStats.Input.Headers + public var headers: Operations.GetStats.Input.Headers /// Creates a new `Input`. /// /// - Parameters: /// - headers: - public init(headers: Operations.getStats.Input.Headers = .init()) { + public init(headers: Operations.GetStats.Input.Headers = .init()) { self.headers = headers } } @@ -2438,12 +2438,12 @@ public enum Operations { } } /// Received HTTP response body - public var body: Operations.getStats.Output.Ok.Body + public var body: Operations.GetStats.Output.Ok.Body /// Creates a new `Ok`. /// /// - Parameters: /// - body: Received HTTP response body - public init(body: Operations.getStats.Output.Ok.Body) { + public init(body: Operations.GetStats.Output.Ok.Body) { self.body = body } } @@ -2452,12 +2452,12 @@ public enum Operations { /// - Remark: Generated from `#/paths//pets/stats/get(getStats)/responses/200`. /// /// HTTP response code: `200 ok`. - case ok(Operations.getStats.Output.Ok) + case ok(Operations.GetStats.Output.Ok) /// The associated value of the enum case if `self` is `.ok`. /// /// - Throws: An error if `self` is not `.ok`. /// - SeeAlso: `.ok`. - public var ok: Operations.getStats.Output.Ok { + public var ok: Operations.GetStats.Output.Ok { get throws { switch self { case let .ok(response): @@ -2515,7 +2515,7 @@ public enum Operations { } /// - Remark: HTTP `POST /pets/stats`. /// - Remark: Generated from `#/paths//pets/stats/post(postStats)`. - public enum postStats { + public enum PostStats { public static let id: Swift.String = "postStats" public struct Input: Sendable, Hashable { /// - Remark: Generated from `#/paths/pets/stats/POST/requestBody`. @@ -2527,12 +2527,12 @@ public enum Operations { /// - Remark: Generated from `#/paths/pets/stats/POST/requestBody/content/application\/octet-stream`. case binary(OpenAPIRuntime.HTTPBody) } - public var body: Operations.postStats.Input.Body + public var body: Operations.PostStats.Input.Body /// Creates a new `Input`. /// /// - Parameters: /// - body: - public init(body: Operations.postStats.Input.Body) { + public init(body: Operations.PostStats.Input.Body) { self.body = body } } @@ -2546,7 +2546,7 @@ public enum Operations { /// - Remark: Generated from `#/paths//pets/stats/post(postStats)/responses/202`. /// /// HTTP response code: `202 accepted`. - case accepted(Operations.postStats.Output.Accepted) + case accepted(Operations.PostStats.Output.Accepted) /// Accepted data. /// /// - Remark: Generated from `#/paths//pets/stats/post(postStats)/responses/202`. @@ -2559,7 +2559,7 @@ public enum Operations { /// /// - Throws: An error if `self` is not `.accepted`. /// - SeeAlso: `.accepted`. - public var accepted: Operations.postStats.Output.Accepted { + public var accepted: Operations.PostStats.Output.Accepted { get throws { switch self { case let .accepted(response): @@ -2580,7 +2580,7 @@ public enum Operations { } /// - Remark: HTTP `POST /probe/`. /// - Remark: Generated from `#/paths//probe//post(probe)`. - public enum probe { + public enum Probe { public static let id: Swift.String = "probe" public struct Input: Sendable, Hashable { /// Creates a new `Input`. @@ -2596,7 +2596,7 @@ public enum Operations { /// - Remark: Generated from `#/paths//probe//post(probe)/responses/204`. /// /// HTTP response code: `204 noContent`. - case noContent(Operations.probe.Output.NoContent) + case noContent(Operations.Probe.Output.NoContent) /// Ack /// /// - Remark: Generated from `#/paths//probe//post(probe)/responses/204`. @@ -2609,7 +2609,7 @@ public enum Operations { /// /// - Throws: An error if `self` is not `.noContent`. /// - SeeAlso: `.noContent`. - public var noContent: Operations.probe.Output.NoContent { + public var noContent: Operations.Probe.Output.NoContent { get throws { switch self { case let .noContent(response): @@ -2632,7 +2632,7 @@ public enum Operations { /// /// - Remark: HTTP `PATCH /pets/{petId}`. /// - Remark: Generated from `#/paths//pets/{petId}/patch(updatePet)`. - public enum updatePet { + public enum UpdatePet { public static let id: Swift.String = "updatePet" public struct Input: Sendable, Hashable { /// - Remark: Generated from `#/paths/pets/{petId}/PATCH/path`. @@ -2649,19 +2649,19 @@ public enum Operations { self.petId = petId } } - public var path: Operations.updatePet.Input.Path + public var path: Operations.UpdatePet.Input.Path /// - Remark: Generated from `#/paths/pets/{petId}/PATCH/header`. public struct Headers: Sendable, Hashable { - public var accept: [OpenAPIRuntime.AcceptHeaderContentType] + public var accept: [OpenAPIRuntime.AcceptHeaderContentType] /// Creates a new `Headers`. /// /// - Parameters: /// - accept: - public init(accept: [OpenAPIRuntime.AcceptHeaderContentType] = .defaultValues()) { + public init(accept: [OpenAPIRuntime.AcceptHeaderContentType] = .defaultValues()) { self.accept = accept } } - public var headers: Operations.updatePet.Input.Headers + public var headers: Operations.UpdatePet.Input.Headers public var body: Components.RequestBodies.UpdatePetRequest? /// Creates a new `Input`. /// @@ -2670,8 +2670,8 @@ public enum Operations { /// - headers: /// - body: public init( - path: Operations.updatePet.Input.Path, - headers: Operations.updatePet.Input.Headers = .init(), + path: Operations.UpdatePet.Input.Path, + headers: Operations.UpdatePet.Input.Headers = .init(), body: Components.RequestBodies.UpdatePetRequest? = nil ) { self.path = path @@ -2689,7 +2689,7 @@ public enum Operations { /// - Remark: Generated from `#/paths//pets/{petId}/patch(updatePet)/responses/204`. /// /// HTTP response code: `204 noContent`. - case noContent(Operations.updatePet.Output.NoContent) + case noContent(Operations.UpdatePet.Output.NoContent) /// Successfully updated /// /// - Remark: Generated from `#/paths//pets/{petId}/patch(updatePet)/responses/204`. @@ -2702,7 +2702,7 @@ public enum Operations { /// /// - Throws: An error if `self` is not `.noContent`. /// - SeeAlso: `.noContent`. - public var noContent: Operations.updatePet.Output.NoContent { + public var noContent: Operations.UpdatePet.Output.NoContent { get throws { switch self { case let .noContent(response): @@ -2719,10 +2719,10 @@ public enum Operations { /// - Remark: Generated from `#/paths/pets/{petId}/PATCH/responses/400/content`. @frozen public enum Body: Sendable, Hashable { /// - Remark: Generated from `#/paths/pets/{petId}/PATCH/responses/400/content/json`. - public struct jsonPayload: Codable, Hashable, Sendable { + public struct JsonPayload: Codable, Hashable, Sendable { /// - Remark: Generated from `#/paths/pets/{petId}/PATCH/responses/400/content/json/message`. public var message: Swift.String - /// Creates a new `jsonPayload`. + /// Creates a new `JsonPayload`. /// /// - Parameters: /// - message: @@ -2734,12 +2734,12 @@ public enum Operations { } } /// - Remark: Generated from `#/paths/pets/{petId}/PATCH/responses/400/content/application\/json`. - case json(Operations.updatePet.Output.BadRequest.Body.jsonPayload) + case json(Operations.UpdatePet.Output.BadRequest.Body.JsonPayload) /// The associated value of the enum case if `self` is `.json`. /// /// - Throws: An error if `self` is not `.json`. /// - SeeAlso: `.json`. - public var json: Operations.updatePet.Output.BadRequest.Body.jsonPayload { + public var json: Operations.UpdatePet.Output.BadRequest.Body.JsonPayload { get throws { switch self { case let .json(body): @@ -2749,12 +2749,12 @@ public enum Operations { } } /// Received HTTP response body - public var body: Operations.updatePet.Output.BadRequest.Body + public var body: Operations.UpdatePet.Output.BadRequest.Body /// Creates a new `BadRequest`. /// /// - Parameters: /// - body: Received HTTP response body - public init(body: Operations.updatePet.Output.BadRequest.Body) { + public init(body: Operations.UpdatePet.Output.BadRequest.Body) { self.body = body } } @@ -2763,12 +2763,12 @@ public enum Operations { /// - Remark: Generated from `#/paths//pets/{petId}/patch(updatePet)/responses/400`. /// /// HTTP response code: `400 badRequest`. - case badRequest(Operations.updatePet.Output.BadRequest) + case badRequest(Operations.UpdatePet.Output.BadRequest) /// The associated value of the enum case if `self` is `.badRequest`. /// /// - Throws: An error if `self` is not `.badRequest`. /// - SeeAlso: `.badRequest`. - public var badRequest: Operations.updatePet.Output.BadRequest { + public var badRequest: Operations.UpdatePet.Output.BadRequest { get throws { switch self { case let .badRequest(response): @@ -2816,7 +2816,7 @@ public enum Operations { /// /// - Remark: HTTP `PUT /pets/{petId}/avatar`. /// - Remark: Generated from `#/paths//pets/{petId}/avatar/put(uploadAvatarForPet)`. - public enum uploadAvatarForPet { + public enum UploadAvatarForPet { public static let id: Swift.String = "uploadAvatarForPet" public struct Input: Sendable, Hashable { /// - Remark: Generated from `#/paths/pets/{petId}/avatar/PUT/path`. @@ -2824,34 +2824,34 @@ public enum Operations { /// The id of the pet to retrieve /// /// - Remark: Generated from `#/paths/pets/{petId}/avatar/PUT/path/petId`. - public var petId: Components.Parameters.path_period_petId + public var petId: Components.Parameters.Path_petId /// Creates a new `Path`. /// /// - Parameters: /// - petId: The id of the pet to retrieve - public init(petId: Components.Parameters.path_period_petId) { + public init(petId: Components.Parameters.Path_petId) { self.petId = petId } } - public var path: Operations.uploadAvatarForPet.Input.Path + public var path: Operations.UploadAvatarForPet.Input.Path /// - Remark: Generated from `#/paths/pets/{petId}/avatar/PUT/header`. public struct Headers: Sendable, Hashable { - public var accept: [OpenAPIRuntime.AcceptHeaderContentType] + public var accept: [OpenAPIRuntime.AcceptHeaderContentType] /// Creates a new `Headers`. /// /// - Parameters: /// - accept: - public init(accept: [OpenAPIRuntime.AcceptHeaderContentType] = .defaultValues()) { + public init(accept: [OpenAPIRuntime.AcceptHeaderContentType] = .defaultValues()) { self.accept = accept } } - public var headers: Operations.uploadAvatarForPet.Input.Headers + public var headers: Operations.UploadAvatarForPet.Input.Headers /// - Remark: Generated from `#/paths/pets/{petId}/avatar/PUT/requestBody`. @frozen public enum Body: Sendable, Hashable { /// - Remark: Generated from `#/paths/pets/{petId}/avatar/PUT/requestBody/content/application\/octet-stream`. case binary(OpenAPIRuntime.HTTPBody) } - public var body: Operations.uploadAvatarForPet.Input.Body + public var body: Operations.UploadAvatarForPet.Input.Body /// Creates a new `Input`. /// /// - Parameters: @@ -2859,9 +2859,9 @@ public enum Operations { /// - headers: /// - body: public init( - path: Operations.uploadAvatarForPet.Input.Path, - headers: Operations.uploadAvatarForPet.Input.Headers = .init(), - body: Operations.uploadAvatarForPet.Input.Body + path: Operations.UploadAvatarForPet.Input.Path, + headers: Operations.UploadAvatarForPet.Input.Headers = .init(), + body: Operations.UploadAvatarForPet.Input.Body ) { self.path = path self.headers = headers @@ -2888,12 +2888,12 @@ public enum Operations { } } /// Received HTTP response body - public var body: Operations.uploadAvatarForPet.Output.Ok.Body + public var body: Operations.UploadAvatarForPet.Output.Ok.Body /// Creates a new `Ok`. /// /// - Parameters: /// - body: Received HTTP response body - public init(body: Operations.uploadAvatarForPet.Output.Ok.Body) { + public init(body: Operations.UploadAvatarForPet.Output.Ok.Body) { self.body = body } } @@ -2902,12 +2902,12 @@ public enum Operations { /// - Remark: Generated from `#/paths//pets/{petId}/avatar/put(uploadAvatarForPet)/responses/200`. /// /// HTTP response code: `200 ok`. - case ok(Operations.uploadAvatarForPet.Output.Ok) + case ok(Operations.UploadAvatarForPet.Output.Ok) /// The associated value of the enum case if `self` is `.ok`. /// /// - Throws: An error if `self` is not `.ok`. /// - SeeAlso: `.ok`. - public var ok: Operations.uploadAvatarForPet.Output.Ok { + public var ok: Operations.UploadAvatarForPet.Output.Ok { get throws { switch self { case let .ok(response): @@ -2939,12 +2939,12 @@ public enum Operations { } } /// Received HTTP response body - public var body: Operations.uploadAvatarForPet.Output.PreconditionFailed.Body + public var body: Operations.UploadAvatarForPet.Output.PreconditionFailed.Body /// Creates a new `PreconditionFailed`. /// /// - Parameters: /// - body: Received HTTP response body - public init(body: Operations.uploadAvatarForPet.Output.PreconditionFailed.Body) { + public init(body: Operations.UploadAvatarForPet.Output.PreconditionFailed.Body) { self.body = body } } @@ -2953,12 +2953,12 @@ public enum Operations { /// - Remark: Generated from `#/paths//pets/{petId}/avatar/put(uploadAvatarForPet)/responses/412`. /// /// HTTP response code: `412 preconditionFailed`. - case preconditionFailed(Operations.uploadAvatarForPet.Output.PreconditionFailed) + case preconditionFailed(Operations.UploadAvatarForPet.Output.PreconditionFailed) /// The associated value of the enum case if `self` is `.preconditionFailed`. /// /// - Throws: An error if `self` is not `.preconditionFailed`. /// - SeeAlso: `.preconditionFailed`. - public var preconditionFailed: Operations.uploadAvatarForPet.Output.PreconditionFailed { + public var preconditionFailed: Operations.UploadAvatarForPet.Output.PreconditionFailed { get throws { switch self { case let .preconditionFailed(response): @@ -2990,12 +2990,12 @@ public enum Operations { } } /// Received HTTP response body - public var body: Operations.uploadAvatarForPet.Output.InternalServerError.Body + public var body: Operations.UploadAvatarForPet.Output.InternalServerError.Body /// Creates a new `InternalServerError`. /// /// - Parameters: /// - body: Received HTTP response body - public init(body: Operations.uploadAvatarForPet.Output.InternalServerError.Body) { + public init(body: Operations.UploadAvatarForPet.Output.InternalServerError.Body) { self.body = body } } @@ -3004,12 +3004,12 @@ public enum Operations { /// - Remark: Generated from `#/paths//pets/{petId}/avatar/put(uploadAvatarForPet)/responses/500`. /// /// HTTP response code: `500 internalServerError`. - case internalServerError(Operations.uploadAvatarForPet.Output.InternalServerError) + case internalServerError(Operations.UploadAvatarForPet.Output.InternalServerError) /// The associated value of the enum case if `self` is `.internalServerError`. /// /// - Throws: An error if `self` is not `.internalServerError`. /// - SeeAlso: `.internalServerError`. - public var internalServerError: Operations.uploadAvatarForPet.Output.InternalServerError { + public var internalServerError: Operations.UploadAvatarForPet.Output.InternalServerError { get throws { switch self { case let .internalServerError(response): @@ -3067,26 +3067,26 @@ public enum Operations { } /// - Remark: HTTP `GET /pets/multipart-typed`. /// - Remark: Generated from `#/paths//pets/multipart-typed/get(multipartDownloadTyped)`. - public enum multipartDownloadTyped { + public enum MultipartDownloadTyped { public static let id: Swift.String = "multipartDownloadTyped" public struct Input: Sendable, Hashable { /// - Remark: Generated from `#/paths/pets/multipart-typed/GET/header`. public struct Headers: Sendable, Hashable { - public var accept: [OpenAPIRuntime.AcceptHeaderContentType] + public var accept: [OpenAPIRuntime.AcceptHeaderContentType] /// Creates a new `Headers`. /// /// - Parameters: /// - accept: - public init(accept: [OpenAPIRuntime.AcceptHeaderContentType] = .defaultValues()) { + public init(accept: [OpenAPIRuntime.AcceptHeaderContentType] = .defaultValues()) { self.accept = accept } } - public var headers: Operations.multipartDownloadTyped.Input.Headers + public var headers: Operations.MultipartDownloadTyped.Input.Headers /// Creates a new `Input`. /// /// - Parameters: /// - headers: - public init(headers: Operations.multipartDownloadTyped.Input.Headers = .init()) { + public init(headers: Operations.MultipartDownloadTyped.Input.Headers = .init()) { self.headers = headers } } @@ -3147,7 +3147,7 @@ public enum Operations { } /// - Remark: HTTP `POST /pets/multipart-typed`. /// - Remark: Generated from `#/paths//pets/multipart-typed/post(multipartUploadTyped)`. - public enum multipartUploadTyped { + public enum MultipartUploadTyped { public static let id: Swift.String = "multipartUploadTyped" public struct Input: Sendable, Hashable { public var body: Components.RequestBodies.MultipartUploadTypedRequest @@ -3169,7 +3169,7 @@ public enum Operations { /// - Remark: Generated from `#/paths//pets/multipart-typed/post(multipartUploadTyped)/responses/202`. /// /// HTTP response code: `202 accepted`. - case accepted(Operations.multipartUploadTyped.Output.Accepted) + case accepted(Operations.MultipartUploadTyped.Output.Accepted) /// Successfully accepted the data. /// /// - Remark: Generated from `#/paths//pets/multipart-typed/post(multipartUploadTyped)/responses/202`. @@ -3182,7 +3182,7 @@ public enum Operations { /// /// - Throws: An error if `self` is not `.accepted`. /// - SeeAlso: `.accepted`. - public var accepted: Operations.multipartUploadTyped.Output.Accepted { + public var accepted: Operations.MultipartUploadTyped.Output.Accepted { get throws { switch self { case let .accepted(response): diff --git a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift index 3013bd7b..64b30763 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift @@ -2307,6 +2307,54 @@ final class SnippetBasedReferenceTests: XCTestCase { ) } + func testSynthesizedOperationId_defensive() throws { + let paths = """ + /pets/{petId}/notifications: + parameters: + - name: petId + in: path + required: true + schema: + type: string + get: + responses: + '204': + description: A success response. + """ + try self.assertPathsTranslation( + paths, + """ + public protocol APIProtocol: Sendable { + func get_sol_pets_sol__lcub_petId_rcub__sol_notifications(_ input: Operations.get_sol_pets_sol__lcub_petId_rcub__sol_notifications.Input) async throws -> Operations.get_sol_pets_sol__lcub_petId_rcub__sol_notifications.Output + } + """ + ) + } + + func testSynthesizedOperationId_idiomatic() throws { + let paths = """ + /pets/{petId}/notifications: + parameters: + - name: petId + in: path + required: true + schema: + type: string + get: + responses: + '204': + description: A success response. + """ + try self.assertPathsTranslation( + paths, + namingStrategy: .idiomatic, + """ + public protocol APIProtocol: Sendable { + func getPetsPetIdNotifications(_ input: Operations.GetPetsPetIdNotifications.Input) async throws -> Operations.GetPetsPetIdNotifications.Output + } + """ + ) + } func testServerRegisterHandlers_oneOperation() throws { try self.assertServerRegisterHandlers( """ @@ -5828,17 +5876,24 @@ extension SnippetBasedReferenceTests { func makeTypesTranslator( accessModifier: AccessModifier = .public, + namingStrategy: NamingStrategy = .defensive, featureFlags: FeatureFlags = [], ignoredDiagnosticMessages: Set = [], componentsYAML: String ) throws -> TypesFileTranslator { let components = try YAMLDecoder().decode(OpenAPI.Components.self, from: componentsYAML) return TypesFileTranslator( - config: Config(mode: .types, access: accessModifier, featureFlags: featureFlags), + config: Config( + mode: .types, + access: accessModifier, + namingStrategy: namingStrategy, + featureFlags: featureFlags + ), diagnostics: XCTestDiagnosticCollector(test: self, ignoredDiagnosticMessages: ignoredDiagnosticMessages), components: components ) } + func makeTypesTranslator( accessModifier: AccessModifier = .public, featureFlags: FeatureFlags = [], @@ -6079,11 +6134,12 @@ extension SnippetBasedReferenceTests { func assertPathsTranslation( _ pathsYAML: String, componentsYAML: String = "{}", + namingStrategy: NamingStrategy = .defensive, _ expectedSwift: String, file: StaticString = #filePath, line: UInt = #line ) throws { - let translator = try makeTypesTranslator(componentsYAML: componentsYAML) + let translator = try makeTypesTranslator(namingStrategy: namingStrategy, 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) diff --git a/Tests/PetstoreConsumerTests/Common.swift b/Tests/PetstoreConsumerTests/Common.swift index e635c7ad..0fa07dc9 100644 --- a/Tests/PetstoreConsumerTests/Common.swift +++ b/Tests/PetstoreConsumerTests/Common.swift @@ -14,8 +14,8 @@ import XCTest import HTTPTypes -extension Operations.listPets.Output { - static var success: Self { .ok(.init(headers: .init(My_hyphen_Response_hyphen_UUID: "abcd"), body: .json([]))) } +extension Operations.ListPets.Output { + static var success: Self { .ok(.init(headers: .init(myResponseUUID: "abcd"), body: .json([]))) } } extension HTTPRequest { diff --git a/Tests/PetstoreConsumerTests/TestClient.swift b/Tests/PetstoreConsumerTests/TestClient.swift index b08e72b5..140d108d 100644 --- a/Tests/PetstoreConsumerTests/TestClient.swift +++ b/Tests/PetstoreConsumerTests/TestClient.swift @@ -15,81 +15,81 @@ import OpenAPIRuntime import Foundation struct TestClient: APIProtocol { - typealias ListPetsSignature = @Sendable (Operations.listPets.Input) async throws -> Operations.listPets.Output + typealias ListPetsSignature = @Sendable (Operations.ListPets.Input) async throws -> Operations.ListPets.Output var listPetsBlock: ListPetsSignature? - func listPets(_ input: Operations.listPets.Input) async throws -> Operations.listPets.Output { + func listPets(_ input: Operations.ListPets.Input) async throws -> Operations.ListPets.Output { guard let block = listPetsBlock else { throw UnspecifiedBlockError() } return try await block(input) } - typealias CreatePetSignature = @Sendable (Operations.createPet.Input) async throws -> Operations.createPet.Output + typealias CreatePetSignature = @Sendable (Operations.CreatePet.Input) async throws -> Operations.CreatePet.Output var createPetBlock: CreatePetSignature? - func createPet(_ input: Operations.createPet.Input) async throws -> Operations.createPet.Output { + func createPet(_ input: Operations.CreatePet.Input) async throws -> Operations.CreatePet.Output { guard let block = createPetBlock else { throw UnspecifiedBlockError() } return try await block(input) } - typealias CreatePetWithFormSignature = @Sendable (Operations.createPetWithForm.Input) async throws -> - Operations.createPetWithForm.Output + typealias CreatePetWithFormSignature = @Sendable (Operations.CreatePetWithForm.Input) async throws -> + Operations.CreatePetWithForm.Output var createPetWithFormBlock: CreatePetWithFormSignature? - func createPetWithForm(_ input: Operations.createPetWithForm.Input) async throws - -> Operations.createPetWithForm.Output + func createPetWithForm(_ input: Operations.CreatePetWithForm.Input) async throws + -> Operations.CreatePetWithForm.Output { guard let block = createPetWithFormBlock else { throw UnspecifiedBlockError() } return try await block(input) } - typealias GetStatsSignature = @Sendable (Operations.getStats.Input) async throws -> Operations.getStats.Output + typealias GetStatsSignature = @Sendable (Operations.GetStats.Input) async throws -> Operations.GetStats.Output var getStatsBlock: GetStatsSignature? - func getStats(_ input: Operations.getStats.Input) async throws -> Operations.getStats.Output { + func getStats(_ input: Operations.GetStats.Input) async throws -> Operations.GetStats.Output { guard let block = getStatsBlock else { throw UnspecifiedBlockError() } return try await block(input) } - typealias PostStatsSignature = @Sendable (Operations.postStats.Input) async throws -> Operations.postStats.Output + typealias PostStatsSignature = @Sendable (Operations.PostStats.Input) async throws -> Operations.PostStats.Output var postStatsBlock: PostStatsSignature? - func postStats(_ input: Operations.postStats.Input) async throws -> Operations.postStats.Output { + func postStats(_ input: Operations.PostStats.Input) async throws -> Operations.PostStats.Output { guard let block = postStatsBlock else { throw UnspecifiedBlockError() } return try await block(input) } - typealias ProbeSignature = @Sendable (Operations.probe.Input) async throws -> Operations.probe.Output + typealias ProbeSignature = @Sendable (Operations.Probe.Input) async throws -> Operations.Probe.Output var probeBlock: ProbeSignature? - func probe(_ input: Operations.probe.Input) async throws -> Operations.probe.Output { + func probe(_ input: Operations.Probe.Input) async throws -> Operations.Probe.Output { guard let block = probeBlock else { throw UnspecifiedBlockError() } return try await block(input) } - typealias UpdatePetSignature = @Sendable (Operations.updatePet.Input) async throws -> Operations.updatePet.Output + typealias UpdatePetSignature = @Sendable (Operations.UpdatePet.Input) async throws -> Operations.UpdatePet.Output var updatePetBlock: UpdatePetSignature? - func updatePet(_ input: Operations.updatePet.Input) async throws -> Operations.updatePet.Output { + func updatePet(_ input: Operations.UpdatePet.Input) async throws -> Operations.UpdatePet.Output { guard let block = updatePetBlock else { throw UnspecifiedBlockError() } return try await block(input) } - typealias UploadAvatarForPetSignature = @Sendable (Operations.uploadAvatarForPet.Input) async throws -> - Operations.uploadAvatarForPet.Output + typealias UploadAvatarForPetSignature = @Sendable (Operations.UploadAvatarForPet.Input) async throws -> + Operations.UploadAvatarForPet.Output var uploadAvatarForPetBlock: UploadAvatarForPetSignature? - func uploadAvatarForPet(_ input: Operations.uploadAvatarForPet.Input) async throws - -> Operations.uploadAvatarForPet.Output + func uploadAvatarForPet(_ input: Operations.UploadAvatarForPet.Input) async throws + -> Operations.UploadAvatarForPet.Output { guard let block = uploadAvatarForPetBlock else { throw UnspecifiedBlockError() } return try await block(input) } - typealias MultipartDownloadTypedSignature = @Sendable (Operations.multipartDownloadTyped.Input) async throws -> - Operations.multipartDownloadTyped.Output + typealias MultipartDownloadTypedSignature = @Sendable (Operations.MultipartDownloadTyped.Input) async throws -> + Operations.MultipartDownloadTyped.Output var multipartDownloadTypedBlock: MultipartDownloadTypedSignature? - func multipartDownloadTyped(_ input: Operations.multipartDownloadTyped.Input) async throws - -> Operations.multipartDownloadTyped.Output + func multipartDownloadTyped(_ input: Operations.MultipartDownloadTyped.Input) async throws + -> Operations.MultipartDownloadTyped.Output { guard let block = multipartDownloadTypedBlock else { throw UnspecifiedBlockError() } return try await block(input) } - typealias MultipartUploadTypedSignature = @Sendable (Operations.multipartUploadTyped.Input) async throws -> - Operations.multipartUploadTyped.Output + typealias MultipartUploadTypedSignature = @Sendable (Operations.MultipartUploadTyped.Input) async throws -> + Operations.MultipartUploadTyped.Output var multipartUploadTypedBlock: MultipartUploadTypedSignature? - func multipartUploadTyped(_ input: Operations.multipartUploadTyped.Input) async throws - -> Operations.multipartUploadTyped.Output + func multipartUploadTyped(_ input: Operations.MultipartUploadTyped.Input) async throws + -> Operations.MultipartUploadTyped.Output { guard let block = multipartUploadTypedBlock else { throw UnspecifiedBlockError() } return try await block(input) diff --git a/Tests/PetstoreConsumerTests/Test_Client.swift b/Tests/PetstoreConsumerTests/Test_Client.swift index 5b8b81fa..76cb89f9 100644 --- a/Tests/PetstoreConsumerTests/Test_Client.swift +++ b/Tests/PetstoreConsumerTests/Test_Client.swift @@ -67,15 +67,15 @@ final class Test_Client: XCTestCase { let response = try await client.listPets( .init( query: .init(limit: 24, habitat: .water, feeds: [.herbivore, .carnivore], since: .test), - headers: .init(My_hyphen_Request_hyphen_UUID: "abcd-1234") + headers: .init(myRequestUUID: "abcd-1234") ) ) guard case let .ok(value) = response else { XCTFail("Unexpected response: \(response)") return } - XCTAssertEqual(value.headers.My_hyphen_Response_hyphen_UUID, "abcd") - XCTAssertEqual(value.headers.My_hyphen_Tracing_hyphen_Header, "1234") + XCTAssertEqual(value.headers.myResponseUUID, "abcd") + XCTAssertEqual(value.headers.myTracingHeader, "1234") switch value.body { case .json(let pets): XCTAssertEqual(pets, [.init(id: 1, name: "Fluffz")]) } @@ -154,13 +154,13 @@ final class Test_Client: XCTestCase { ) } let response = try await client.createPet( - .init(headers: .init(X_hyphen_Extra_hyphen_Arguments: .init(code: 1)), body: .json(.init(name: "Fluffz"))) + .init(headers: .init(xExtraArguments: .init(code: 1)), body: .json(.init(name: "Fluffz"))) ) guard case let .created(value) = response else { XCTFail("Unexpected response: \(response)") return } - XCTAssertEqual(value.headers.X_hyphen_Extra_hyphen_Arguments, .init(code: 1)) + XCTAssertEqual(value.headers.xExtraArguments, .init(code: 1)) switch value.body { case .json(let pets): XCTAssertEqual(pets, .init(id: 1, name: "Fluffz")) } @@ -186,7 +186,7 @@ final class Test_Client: XCTestCase { return } XCTAssertEqual(statusCode, 400) - XCTAssertEqual(value.headers.X_hyphen_Reason, "bad luck") + XCTAssertEqual(value.headers.xReason, "bad luck") switch value.body { case .json(let body): XCTAssertEqual(body, .init(code: 1)) } @@ -301,7 +301,7 @@ final class Test_Client: XCTestCase { } let response = try await client.createPet( .init( - headers: .init(X_hyphen_Extra_hyphen_Arguments: .init(code: 1)), + headers: .init(xExtraArguments: .init(code: 1)), body: .json( .init( name: "Fluffz", @@ -314,7 +314,7 @@ final class Test_Client: XCTestCase { XCTFail("Unexpected response: \(response)") return } - XCTAssertEqual(value.headers.X_hyphen_Extra_hyphen_Arguments, .init(code: 1)) + XCTAssertEqual(value.headers.xExtraArguments, .init(code: 1)) switch value.body { case .json(let pets): XCTAssertEqual( @@ -713,11 +713,11 @@ final class Test_Client: XCTestCase { try await XCTAssertEqualData(requestBody, Data.multipartTypedBodyAsSlice) return (.init(status: .accepted), nil) } - let parts: MultipartBody = [ + let parts: MultipartBody = [ .log( .init( payload: .init( - headers: .init(x_hyphen_log_hyphen_type: .unstructured), + headers: .init(xLogType: .unstructured), body: .init("here be logs!\nand more lines\nwheee\n") ), filename: "process.log" @@ -765,7 +765,7 @@ final class Test_Client: XCTestCase { return } XCTAssertEqual(log.filename, "process.log") - XCTAssertEqual(log.payload.headers, .init(x_hyphen_log_hyphen_type: .unstructured)) + XCTAssertEqual(log.payload.headers, .init(xLogType: .unstructured)) try await XCTAssertEqualData(log.payload.body, "here be logs!\nand more lines\nwheee\n".utf8) } do { diff --git a/Tests/PetstoreConsumerTests/Test_Playground.swift b/Tests/PetstoreConsumerTests/Test_Playground.swift index a20d64e5..181ccf38 100644 --- a/Tests/PetstoreConsumerTests/Test_Playground.swift +++ b/Tests/PetstoreConsumerTests/Test_Playground.swift @@ -27,7 +27,7 @@ final class Test_Playground: XCTestCase { // Server let serverHandler: - @Sendable (Operations.uploadAvatarForPet.Input) async throws -> Operations.uploadAvatarForPet.Output = { + @Sendable (Operations.UploadAvatarForPet.Input) async throws -> Operations.UploadAvatarForPet.Output = { input in // The server handler verifies the pet id, sends back // the start of the 200 response, and then streams back @@ -108,7 +108,7 @@ final class Test_Playground: XCTestCase { // Server - let serverHandler: @Sendable (Operations.getStats.Input) async throws -> Operations.getStats.Output = { input in + let serverHandler: @Sendable (Operations.GetStats.Input) async throws -> Operations.GetStats.Output = { input in // The server handler sends back the start of the 200 response, // and then sends a few chunks. @@ -160,7 +160,7 @@ final class Test_Playground: XCTestCase { // Server - let serverHandler: @Sendable (Operations.getStats.Input) async throws -> Operations.getStats.Output = { input in + let serverHandler: @Sendable (Operations.GetStats.Input) async throws -> Operations.GetStats.Output = { input in // The server handler sends back the start of the 200 response, // and then sends a few chunks. diff --git a/Tests/PetstoreConsumerTests/Test_Server.swift b/Tests/PetstoreConsumerTests/Test_Server.swift index 5f6be366..9a8fa0f9 100644 --- a/Tests/PetstoreConsumerTests/Test_Server.swift +++ b/Tests/PetstoreConsumerTests/Test_Server.swift @@ -33,10 +33,10 @@ final class Test_Server: XCTestCase { XCTAssertEqual(input.query.habitat, .water) XCTAssertEqual(input.query.since, .test) XCTAssertEqual(input.query.feeds, [.carnivore, .herbivore]) - XCTAssertEqual(input.headers.My_hyphen_Request_hyphen_UUID, "abcd-1234") + XCTAssertEqual(input.headers.myRequestUUID, "abcd-1234") return .ok( .init( - headers: .init(My_hyphen_Response_hyphen_UUID: "abcd", My_hyphen_Tracing_hyphen_Header: "1234"), + headers: .init(myResponseUUID: "abcd", myTracingHeader: "1234"), body: .json([.init(id: 1, name: "Fluffz")]) ) ) @@ -101,14 +101,11 @@ final class Test_Server: XCTestCase { func testCreatePet_201() async throws { client = .init(createPetBlock: { input in - XCTAssertEqual(input.headers.X_hyphen_Extra_hyphen_Arguments, .init(code: 1)) + XCTAssertEqual(input.headers.xExtraArguments, .init(code: 1)) guard case let .json(createPet) = input.body else { throw TestError.unexpectedValue(input.body) } XCTAssertEqual(createPet, .init(name: "Fluffz")) return .created( - .init( - headers: .init(X_hyphen_Extra_hyphen_Arguments: .init(code: 1)), - body: .json(.init(id: 1, name: "Fluffz")) - ) + .init(headers: .init(xExtraArguments: .init(code: 1)), body: .json(.init(id: 1, name: "Fluffz"))) ) }) let (response, responseBody) = try await server.createPet( @@ -149,10 +146,7 @@ final class Test_Server: XCTestCase { func testCreatePet_400() async throws { client = .init(createPetBlock: { input in - .clientError( - statusCode: 400, - .init(headers: .init(X_hyphen_Reason: "bad luck"), body: .json(.init(code: 1))) - ) + .clientError(statusCode: 400, .init(headers: .init(xReason: "bad luck"), body: .json(.init(code: 1)))) }) let (response, responseBody) = try await server.createPet( .init( @@ -235,7 +229,7 @@ final class Test_Server: XCTestCase { func testCreatePet_201_withBase64() async throws { client = .init(createPetBlock: { input in - XCTAssertEqual(input.headers.X_hyphen_Extra_hyphen_Arguments, .init(code: 1)) + XCTAssertEqual(input.headers.xExtraArguments, .init(code: 1)) guard case let .json(createPet) = input.body else { throw TestError.unexpectedValue(input.body) } XCTAssertEqual( createPet, @@ -245,10 +239,7 @@ final class Test_Server: XCTestCase { ) ) return .created( - .init( - headers: .init(X_hyphen_Extra_hyphen_Arguments: .init(code: 1)), - body: .json(.init(id: 1, name: "Fluffz")) - ) + .init(headers: .init(xExtraArguments: .init(code: 1)), body: .json(.init(id: 1, name: "Fluffz"))) ) }) let (response, responseBody) = try await server.createPet( @@ -771,11 +762,11 @@ final class Test_Server: XCTestCase { func testMultipartDownloadTyped_202() async throws { client = .init(multipartDownloadTypedBlock: { input in - let parts: MultipartBody = [ + let parts: MultipartBody = [ .log( .init( payload: .init( - headers: .init(x_hyphen_log_hyphen_type: .unstructured), + headers: .init(xLogType: .unstructured), body: .init("here be logs!\nand more lines\nwheee\n") ), filename: "process.log" @@ -802,7 +793,7 @@ final class Test_Server: XCTestCase { func testMultipartUploadTyped_202() async throws { client = .init(multipartUploadTypedBlock: { input in - let body: MultipartBody + let body: MultipartBody switch input.body { case .multipartForm(let value): body = value } @@ -814,7 +805,7 @@ final class Test_Server: XCTestCase { return .undocumented(statusCode: 500, .init()) } XCTAssertEqual(log.filename, "process.log") - XCTAssertEqual(log.payload.headers, .init(x_hyphen_log_hyphen_type: .unstructured)) + XCTAssertEqual(log.payload.headers, .init(xLogType: .unstructured)) try await XCTAssertEqualData(log.payload.body, "here be logs!\nand more lines\nwheee\n".utf8) } do { diff --git a/Tests/PetstoreConsumerTests/Test_Types.swift b/Tests/PetstoreConsumerTests/Test_Types.swift index 7aaba0cc..d01973b0 100644 --- a/Tests/PetstoreConsumerTests/Test_Types.swift +++ b/Tests/PetstoreConsumerTests/Test_Types.swift @@ -100,7 +100,7 @@ final class Test_Types: XCTestCase { expectedJSON: #""2023-01-18T10:04:11Z""# ) try testJSON( - Components.Schemas.MixedAnyOf(value2: .BIG_ELEPHANT_1, value4: #"BIG_ELEPHANT_1"#), + Components.Schemas.MixedAnyOf(value2: .bigElephant1, value4: #"BIG_ELEPHANT_1"#), expectedJSON: #""BIG_ELEPHANT_1""# ) try testJSON( @@ -111,7 +111,7 @@ final class Test_Types: XCTestCase { Components.Schemas.MixedOneOf.case1(Date(timeIntervalSince1970: 1_674_036_251)), expectedJSON: #""2023-01-18T10:04:11Z""# ) - try testJSON(Components.Schemas.MixedOneOf.PetKind(.BIG_ELEPHANT_1), expectedJSON: #""BIG_ELEPHANT_1""#) + try testJSON(Components.Schemas.MixedOneOf.PetKind(.bigElephant1), expectedJSON: #""BIG_ELEPHANT_1""#) try testJSON( Components.Schemas.MixedOneOf.Pet(.init(id: 1, name: "Fluffz")), expectedJSON: #"{"id":1,"name":"Fluffz"}"# @@ -148,9 +148,9 @@ final class Test_Types: XCTestCase { try _testRoundtrip(Components.Schemas.OneOfAny.case4(.init(message: "hello"))) } func testOneOfWithDiscriminator_roundtrip() throws { - try _testRoundtrip(Components.Schemas.OneOfObjectsWithDiscriminator.Walk(.init(kind: "Walk", length: 1))) + try _testRoundtrip(Components.Schemas.OneOfObjectsWithDiscriminator.walk(.init(kind: "Walk", length: 1))) try _testRoundtrip( - Components.Schemas.OneOfObjectsWithDiscriminator.MessagedExercise( + Components.Schemas.OneOfObjectsWithDiscriminator.messagedExercise( .init(value1: .init(kind: "MessagedExercise"), value2: .init(message: "hello")) ) ) @@ -167,23 +167,23 @@ final class Test_Types: XCTestCase { ) } func testThrowingShorthandAPIs() throws { - let created = Operations.createPet.Output.Created(body: .json(.init(id: 42, name: "Scruffy"))) - let output = Operations.createPet.Output.created(created) + let created = Operations.CreatePet.Output.Created(body: .json(.init(id: 42, name: "Scruffy"))) + let output = Operations.CreatePet.Output.created(created) XCTAssertEqual(try output.created, created) XCTAssertThrowsError(try output.clientError) { error in guard case let .unexpectedResponseStatus(expectedStatus, actualOutput) = error as? RuntimeError, - expectedStatus == "clientError", actualOutput as? Operations.createPet.Output == output + expectedStatus == "clientError", actualOutput as? Operations.CreatePet.Output == output else { XCTFail("Expected error, but not this: \(error)") return } } let stats = Components.Schemas.PetStats(count: 42) - let ok = Operations.getStats.Output.Ok(body: .json(stats)) + let ok = Operations.GetStats.Output.Ok(body: .json(stats)) XCTAssertEqual(try ok.body.json, stats) XCTAssertThrowsError(try ok.body.plainText) { error in guard case let .unexpectedResponseBody(expectedContentType, actualBody) = error as? RuntimeError, - expectedContentType == "text/plain", actualBody as? Operations.getStats.Output.Ok.Body == .json(stats) + expectedContentType == "text/plain", actualBody as? Operations.GetStats.Output.Ok.Body == .json(stats) else { XCTFail("Expected error, but not this: \(error)") return