From 140f5c0919fecaebbb189a0762f530259ad0c957 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Wed, 13 Nov 2024 18:26:53 +0100 Subject: [PATCH 01/31] [Experiment] Optimistic naming strategy as opt-in --- Sources/_OpenAPIGeneratorCore/Config.swift | 13 + .../translateClientMethod.swift | 2 +- .../CommonTranslations/SwiftSafeNames.swift | 328 +++++++++++++++++- .../translateAllAnyOneOf.swift | 2 +- .../translateStringEnum.swift | 4 +- .../CommonTypes/DiscriminatorExtensions.swift | 2 +- .../CommonTypes/StructBlueprint.swift | 2 +- .../Translator/FileTranslator.swift | 21 +- .../Multipart/MultipartContentInspector.swift | 2 +- .../Multipart/translateMultipart.swift | 6 +- .../Operations/OperationDescription.swift | 13 +- .../Parameters/TypedParameter.swift | 2 +- .../Responses/TypedResponseHeader.swift | 2 +- .../translateServerMethod.swift | 3 +- .../TypeAssignment/TypeAssigner.swift | 18 +- .../translateServersVariables.swift | 10 +- .../FilterCommand.swift | 11 +- .../GenerateOptions+runGenerator.swift | 8 + .../GenerateOptions.swift | 16 +- .../swift-openapi-generator/UserConfig.swift | 10 + .../Extensions/Test_String.swift | 70 ---- .../Extensions/Test_SwiftSafeNames.swift | 127 +++++++ .../TestUtilities.swift | 34 +- .../Test_OperationDescription.swift | 2 +- .../TypeAssignment/Test_TypeAssigner.swift | 2 +- 25 files changed, 589 insertions(+), 121 deletions(-) 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..cabba9dc 100644 --- a/Sources/_OpenAPIGeneratorCore/Config.swift +++ b/Sources/_OpenAPIGeneratorCore/Config.swift @@ -12,6 +12,11 @@ // //===----------------------------------------------------------------------===// +public enum NamingStrategy: String, Sendable, Codable, Equatable { + case defensive + case optimistic +} + /// A structure that contains configuration options for a single execution /// of the generator pipeline run. /// @@ -35,6 +40,10 @@ public struct Config: Sendable { /// Filter to apply to the OpenAPI document before generation. public var filter: DocumentFilter? + public var namingStrategy: NamingStrategy? + + public var nameOverrides: [String: String]? + /// Additional pre-release features to enable. public var featureFlags: FeatureFlags @@ -50,12 +59,16 @@ public struct Config: Sendable { access: AccessModifier, additionalImports: [String] = [], filter: DocumentFilter? = nil, + namingStrategy: NamingStrategy? = nil, + nameOverrides: [String: String]? = nil, 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 index 9a52bc04..8494b08e 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift @@ -13,6 +13,16 @@ //===----------------------------------------------------------------------===// import Foundation +struct SwiftNameOptions: OptionSet { + let rawValue: Int32 + + static let none = SwiftNameOptions([]) + + static let capitalize = SwiftNameOptions(rawValue: 1 << 0) + + static let all: SwiftNameOptions = [.capitalize] +} + extension String { /// Returns a string sanitized to be usable as a Swift identifier. @@ -26,7 +36,7 @@ extension String { /// /// In addition to replacing illegal characters, it also /// ensures that the identifier starts with a letter and not a number. - var safeForSwiftCode: String { + func safeForSwiftCode_defensive(options: SwiftNameOptions) -> String { guard !isEmpty else { return "_empty" } let firstCharSet: CharacterSet = .letters.union(.init(charactersIn: "_")) @@ -67,6 +77,200 @@ extension String { return "_\(validString)" } + /// 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`. + func safeForSwiftCode_optimistic(options: SwiftNameOptions) -> String { + let capitalize = options.contains(.capitalize) + if isEmpty { + return capitalize ? "_Empty_" : "_empty_" + } + + // Detect cases like HELLO_WORLD, sometimes used for constants. + let isAllUppercase = 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 "_" + + var buffer: [Character] = [] + buffer.reserveCapacity(count) + + enum State { + case modifying + case preFirstWord + case accumulatingWord + case waitingForWordStarter + } + var state: State = .preFirstWord + for char in self { + let _state = state + state = .modifying + switch _state { + case .preFirstWord: + if char == "_" { + // Leading underscores are kept. + buffer.append(char) + state = .preFirstWord + } else if char.isNumber { + // Prefix with an underscore if the first character is a number. + buffer.append("_") + buffer.append(char) + state = .accumulatingWord + } else if char.isLetter { + // First character in the identifier. + buffer.append(contentsOf: capitalize ? char.uppercased() : char.lowercased()) + state = .accumulatingWord + } else { + // Illegal character, fall back to the defensive strategy. + return safeForSwiftCode_defensive(options: options) + } + case .accumulatingWord: + if char.isLetter || char.isNumber { + if isAllUppercase { + buffer.append(contentsOf: char.lowercased()) + } else { + buffer.append(char) + } + state = .accumulatingWord + } else if char == "_" || char == "-" || char == " " { + // In the middle of an identifier, dashes, underscores, and spaces are considered + // word separators, so we remove the character and end the current word. + state = .waitingForWordStarter + } else if char == "." { + // In the middle of an identifier, a period gets replaced with an underscore, but continues + // the current word. + buffer.append("_") + state = .accumulatingWord + } else { + // Illegal character, fall back to the defensive strategy. + return safeForSwiftCode_defensive(options: options) + } + case .waitingForWordStarter: + if char == "_" || char == "-" { + // Between words, just drop dashes, underscores, and spaces, 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, fall back to the defensive strategy. + return safeForSwiftCode_defensive(options: options) + } + case .modifying: + preconditionFailure("Logic error in \(#function), string: '\(self)'") + } + precondition(state != .modifying, "Logic error in \(#function), string: '\(self)'") + } + if buffer.isEmpty || state == .preFirstWord { + return safeForSwiftCode_defensive(options: options) + } + // Check for keywords + let newString = String(buffer) + if Self.keywords.contains(newString) { + return "_\(newString)" + } + return newString + } + + private static let identifierHeadCharactersRanges: [ClosedRange] = { + // https://docs.swift.org/swift-book/documentation/the-swift-programming-language/lexicalstructure/#Identifiers + var ranges: [ClosedRange] = [] + // identifier-head → Upper- or lowercase letter A through Z + ranges.append("A"..."Z") + ranges.append("a"..."z") + // identifier-head → _ + ranges.append("_") + // identifier-head → U+00A8, U+00AA, U+00AD, U+00AF, U+00B2–U+00B5, or U+00B7–U+00BA + ranges.appendFromSet([0x00A8, 0x00AA, 0x00AD, 0x00AF]) + ranges.appendFromScalars(0x00B2...0x00B5) + ranges.appendFromScalars(0x00B7...0x00BA) + // identifier-head → U+00BC–U+00BE, U+00C0–U+00D6, U+00D8–U+00F6, or U+00F8–U+00FF + ranges.appendFromScalars(0x00BC...0x00BE) + ranges.appendFromScalars(0x00C0...0x00D6) + ranges.appendFromScalars(0x00D8...0x00F6) + ranges.appendFromScalars(0x00F8...0x00FF) + // identifier-head → U+0100–U+02FF, U+0370–U+167F, U+1681–U+180D, or U+180F–U+1DBF + ranges.appendFromScalars(0x0100...0x02FF) + ranges.appendFromScalars(0x0370...0x167F) + ranges.appendFromScalars(0x1681...0x180D) + ranges.appendFromScalars(0x180F...0x1DBF) + // identifier-head → U+1E00–U+1FFF + ranges.appendFromScalars(0x1E00...0x1FFF) + // identifier-head → U+200B–U+200D, U+202A–U+202E, U+203F–U+2040, U+2054, or U+2060–U+206F + ranges.appendFromScalars(0x200B...0x200D) + ranges.appendFromScalars(0x202A...0x202E) + ranges.appendFromScalars(0x203F...0x2040) + ranges.appendFromScalar(0x2054) + ranges.appendFromScalars(0x2060...0x206F) + // identifier-head → U+2070–U+20CF, U+2100–U+218F, U+2460–U+24FF, or U+2776–U+2793 + ranges.appendFromScalars(0x2070...0x20CF) + ranges.appendFromScalars(0x2100...0x218F) + ranges.appendFromScalars(0x2460...0x24FF) + ranges.appendFromScalars(0x2776...0x2793) + // identifier-head → U+2C00–U+2DFF or U+2E80–U+2FFF + ranges.appendFromScalars(0x2C00...0x2DFF) + ranges.appendFromScalars(0x2E80...0x2FFF) + // identifier-head → U+3004–U+3007, U+3021–U+302F, U+3031–U+303F, or U+3040–U+D7FF + ranges.appendFromScalars(0x3004...0x3007) + ranges.appendFromScalars(0x3021...0x302F) + ranges.appendFromScalars(0x3031...0x303F) + ranges.appendFromScalars(0x3040...0xD7FF) + // identifier-head → U+F900–U+FD3D, U+FD40–U+FDCF, U+FDF0–U+FE1F, or U+FE30–U+FE44 + ranges.appendFromScalars(0xF900...0xFD3D) + ranges.appendFromScalars(0xFD40...0xFDCF) + ranges.appendFromScalars(0xFDF0...0xFE1F) + ranges.appendFromScalars(0xFE30...0xFE44) + // identifier-head → U+FE47–U+FFFD + ranges.appendFromScalars(0xFE47...0xFFFD) + // identifier-head → U+10000–U+1FFFD, U+20000–U+2FFFD, U+30000–U+3FFFD, or U+40000–U+4FFFD + ranges.appendFromScalars(0x10000...0x1FFFD) + ranges.appendFromScalars(0x20000...0x2FFFD) + ranges.appendFromScalars(0x30000...0x3FFFD) + ranges.appendFromScalars(0x40000...0x4FFFD) + // identifier-head → U+50000–U+5FFFD, U+60000–U+6FFFD, U+70000–U+7FFFD, or U+80000–U+8FFFD + ranges.appendFromScalars(0x50000...0x5FFFD) + ranges.appendFromScalars(0x60000...0x6FFFD) + ranges.appendFromScalars(0x70000...0x7FFFD) + ranges.appendFromScalars(0x80000...0x8FFFD) + // identifier-head → U+90000–U+9FFFD, U+A0000–U+AFFFD, U+B0000–U+BFFFD, or U+C0000–U+CFFFD + ranges.appendFromScalars(0x90000...0x9FFFD) + ranges.appendFromScalars(0xA0000...0xAFFFD) + ranges.appendFromScalars(0xB0000...0xBFFFD) + ranges.appendFromScalars(0xC0000...0xCFFFD) + // identifier-head → U+D0000–U+DFFFD or U+E0000–U+EFFFD + ranges.appendFromScalars(0xD0000...0xDFFFD) + ranges.appendFromScalars(0xE0000...0xEFFFD) + return ranges + }() + + private static let identifierNonHeadCharactersRanges: [ClosedRange] = { + var ranges: [ClosedRange] = [] + // identifier-character → Digit 0 through 9 + ranges.append("0"..."9") + // identifier-character → U+0300–U+036F, U+1DC0–U+1DFF, U+20D0–U+20FF, or U+FE20–U+FE2F + ranges.appendFromScalars(0x0300...0x036F) + ranges.appendFromScalars(0x1DC0...0x1DFF) + ranges.appendFromScalars(0x20D0...0x20FF) + ranges.appendFromScalars(0xFE20...0xFE2F) + return ranges + }() + + private static let identifierCharactersRanges: [ClosedRange] = { + var ranges: [ClosedRange] = [] + ranges.append(contentsOf: identifierHeadCharactersRanges) + ranges.append(contentsOf: identifierNonHeadCharactersRanges) + return ranges + }() + /// A list of Swift keywords. /// /// Copied from SwiftSyntax/TokenKind.swift @@ -87,3 +291,125 @@ extension String { "\\": "bsol", "]": "rbrack", "^": "hat", "`": "grave", "{": "lcub", "|": "verbar", "}": "rcub", "~": "tilde", ] } + +extension [ClosedRange] { + func contains(_ char: Character) -> Bool { + // TODO: This could be optimized if we create a data structure of sorted ranges and use binary search. + for range in self { + if range.contains(char) { + return true + } + } + return false + } + + mutating func appendFromScalar(_ scalar: Int) { + append(Character(UnicodeScalar(scalar)!)...Character(UnicodeScalar(scalar)!)) + } + + mutating func appendFromSet(_ set: Set) { + append(contentsOf: ClosedRange.fromSet(set)) + } + + mutating func appendFromScalars(_ range: ClosedRange) { + append(Character(UnicodeScalar(range.lowerBound)!)...Character(UnicodeScalar(range.upperBound)!)) + } +} + +extension ClosedRange: @retroactive ExpressibleByUnicodeScalarLiteral where Bound == Character { + public typealias UnicodeScalarLiteralType = Character + public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) { + let char = Character(unicodeScalarLiteral: value) + self = char...char + } +} + +extension ClosedRange where Bound == Character { + static func fromSet(_ set: Set) -> [Self] { + set.map { + Character(UnicodeScalar($0)!)...Character(UnicodeScalar($0)!) + } + } +} + +fileprivate extension Set where Element == Character { + mutating func insert(charactersInString string: String) { + for character in string { + insert(character) + } + } + + mutating func insert(allFrom lowerBound: Character, upTo upperBound: Character) { + for byte in lowerBound.asciiValue!...upperBound.asciiValue! { + insert(Character(UnicodeScalar(byte))) + } + } + + mutating func insert(scalarWithCode code: Int) { + insert(Character(UnicodeScalar(code)!)) + } + + mutating func insert(scalarInSet set: Set) { + for code in set { + insert(scalarWithCode: code) + } + } + + mutating func insert(scalarsInRangeFrom lowerBound: Int, upTo upperBound: Int) { + for code in lowerBound...upperBound { + insert(scalarWithCode: code) + } + } +} + +@available(*, deprecated) +fileprivate extension UInt8 { + + var isUppercaseLetter: Bool { + (0x41...0x5a).contains(self) + } + + var isLowercaseLetter: Bool { + (0x61...0x7a).contains(self) + } + + var isLetter: Bool { + isUppercaseLetter || isLowercaseLetter + } + + var isNumber: Bool { + (0x30...0x39).contains(self) + } + + var isLowercaseLetterOrNumber: Bool { + isLowercaseLetter || isNumber + } + + var isAlphanumeric: Bool { + isLetter || isNumber + } + + var asUppercase: UInt8 { + if isUppercaseLetter || isNumber { + return self + } + if isLowercaseLetter { + return self - 0x20 + } + preconditionFailure("Cannot uppercase not a letter or number") + } + + var asLowercase: UInt8 { + if isLowercaseLetter || isNumber { + return self + } + if isUppercaseLetter { + return self + 0x20 + } + preconditionFailure("Cannot lowercase not a letter or number") + } + + var isWordSeparator: Bool { + self == 0x2d /* - */ || self == 0x5f /* _ */ + } +} diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift index 37ac8830..e77493ee 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift @@ -209,7 +209,7 @@ extension TypesFileTranslator { let decoder: Declaration if let discriminator { let originalName = discriminator.propertyName - let swiftName = context.asSwiftSafeName(originalName) + let swiftName = context.asSwiftSafeName(originalName, .none) codingKeysDecls = [ .enum( accessModifier: config.access, diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStringEnum.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStringEnum.swift index 9add9482..7296626f 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStringEnum.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateStringEnum.swift @@ -49,11 +49,11 @@ extension FileTranslator { // In nullable enum schemas, empty strings are parsed as Void. // This is unlikely to be fixed, so handling that case here. // https://github.com/apple/swift-openapi-generator/issues/118 - if isNullable && anyValue is Void { return (context.asSwiftSafeName(""), .string("")) } + if isNullable && anyValue is Void { return (context.asSwiftSafeName("", .none), .string("")) } 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.asSwiftSafeName(rawValue, .none) return (caseName, .string(rawValue)) case .integer: let rawValue: Int diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/DiscriminatorExtensions.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/DiscriminatorExtensions.swift index 1bc1cd7f..922542d4 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/DiscriminatorExtensions.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/DiscriminatorExtensions.swift @@ -80,7 +80,7 @@ extension FileTranslator { /// - 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]) + context.asSwiftSafeName(type.rawNames[0], .capitalize) } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/StructBlueprint.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/StructBlueprint.swift index 9eef5d6d..c295a5bd 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.asSwiftSafeName(originalName, .none) } /// The JSON path to the property. /// diff --git a/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift b/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift index 4f246521..15bada2a 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift @@ -47,7 +47,24 @@ protocol FileTranslator { extension FileTranslator { /// A new context from the file translator. - var context: TranslatorContext { TranslatorContext(asSwiftSafeName: { $0.safeForSwiftCode }) } + var context: TranslatorContext { + let asSwiftSafeName: (String, SwiftNameOptions) -> String + switch config.namingStrategy { + case .defensive, .none: + asSwiftSafeName = { $0.safeForSwiftCode_defensive(options: $1) } + case .optimistic: + asSwiftSafeName = { $0.safeForSwiftCode_optimistic(options: $1) } + } + let overrides = config.nameOverrides ?? [:] + return TranslatorContext( + asSwiftSafeName: { name, options in + if let override = overrides[name] { + return override + } + return asSwiftSafeName(name, options) + } + ) + } } /// A set of configuration values for concrete file translators. @@ -57,5 +74,5 @@ struct TranslatorContext { /// /// - Parameter string: The string to convert to be safe for Swift. /// - Returns: A Swift-safe version of the input string. - var asSwiftSafeName: (String) -> String + var asSwiftSafeName: (String, SwiftNameOptions) -> String } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift b/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift index f5a83882..f69692de 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.asSwiftSafeName(key, .capitalize) 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..21163449 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.asSwiftSafeName(documentedPart.originalName, .none), 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.asSwiftSafeName(originalName, .none) 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.asSwiftSafeName(originalName, .none) 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..bceb302c 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.asSwiftSafeName(operationID, .none) } + + /// 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.asSwiftSafeName(operationID, .capitalize) } /// 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.asSwiftSafeName(param, .none)) } 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..b5d8b425 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.asSwiftSafeName(name, .none) } /// A Boolean value that indicates whether the parameter must be specified /// when performing the OpenAPI operation. diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift b/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift index ad11fcbc..7c16ece6 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.asSwiftSafeName(name, .none) } /// A Boolean value that indicates whether the response header can /// be omitted in the HTTP response. 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/TypeAssigner.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift index 7f7a46ff..3a7284f9 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift @@ -59,7 +59,7 @@ struct TypeAssigner { /// - Returns: A Swift type name for the specified component type. func typeName(forComponentOriginallyNamed originalName: String, in location: TypeLocation) -> TypeName { typeName(forLocation: location) - .appending(swiftComponent: context.asSwiftSafeName(originalName), jsonComponent: originalName) + .appending(swiftComponent: context.asSwiftSafeName(originalName, .capitalize), jsonComponent: originalName) } /// Returns the type name for an OpenAPI-named component namespace. @@ -127,7 +127,7 @@ struct TypeAssigner { { multipartBodyElementTypeName = try typeName(for: ref) } else { - let swiftSafeName = context.asSwiftSafeName(hint) + let swiftSafeName = context.asSwiftSafeName(hint, .capitalize) multipartBodyElementTypeName = parent.appending( swiftComponent: swiftSafeName + Constants.Global.inlineTypeSuffix, jsonComponent: hint @@ -343,7 +343,7 @@ struct TypeAssigner { } return baseType.appending( - swiftComponent: context.asSwiftSafeName(originalName) + suffix, + swiftComponent: context.asSwiftSafeName(originalName, .capitalize) + suffix, jsonComponent: jsonReferenceComponentOverride ?? originalName ) .asUsage.withOptional(try typeMatcher.isOptional(schema, components: components)) @@ -406,7 +406,7 @@ struct TypeAssigner { of componentType: Component.Type ) -> TypeName { typeName(for: Component.self) - .appending(swiftComponent: context.asSwiftSafeName(key.rawValue), jsonComponent: key.rawValue) + .appending(swiftComponent: context.asSwiftSafeName(key.rawValue, .capitalize), jsonComponent: key.rawValue) } /// Returns a type name for a JSON reference. @@ -471,7 +471,7 @@ struct TypeAssigner { throw JSONReferenceParsingError.nonComponentPathsUnsupported(reference.name) } return typeName(for: componentType) - .appending(swiftComponent: context.asSwiftSafeName(name), jsonComponent: name) + .appending(swiftComponent: context.asSwiftSafeName(name, .capitalize), jsonComponent: name) } /// Returns a type name for the namespace for the specified component type. @@ -495,7 +495,7 @@ struct TypeAssigner { { typeNameForComponents() .appending( - swiftComponent: context.asSwiftSafeName(componentType.openAPIComponentsKey).uppercasingFirstLetter, + swiftComponent: context.asSwiftSafeName(componentType.openAPIComponentsKey, .capitalize).uppercasingFirstLetter, jsonComponent: componentType.openAPIComponentsKey ) } @@ -528,14 +528,14 @@ struct TypeAssigner { case "application/pdf": return "pdf" case "image/jpeg": return "jpeg" default: - let safedType = context.asSwiftSafeName(contentType.originallyCasedType) - let safedSubtype = context.asSwiftSafeName(contentType.originallyCasedSubtype) + let safedType = context.asSwiftSafeName(contentType.originallyCasedType, .none) + let safedSubtype = context.asSwiftSafeName(contentType.originallyCasedSubtype, .none) 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: "_") + pair.split(separator: "=").map { context.asSwiftSafeName(String($0), .none) }.joined(separator: "_") } .joined(separator: "_") return prefix + "_" + safedParams diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServersVariables.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServersVariables.swift index c8b96ff0..4a67ea3e 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.asSwiftSafeName(key, .none) 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.asSwiftSafeName(key, .none) + enumName = context.asSwiftSafeName(key.localizedCapitalized, .capitalize) 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.asSwiftSafeName(variable.default, .none))) ) } @@ -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.asSwiftSafeName(name, .none) return .enumCase(name: caseName, kind: caseName == name ? .nameOnly : .nameWithRawValue(.string(name))) } } diff --git a/Sources/swift-openapi-generator/FilterCommand.swift b/Sources/swift-openapi-generator/FilterCommand.swift index 4eff9bc4..6066654b 100644 --- a/Sources/swift-openapi-generator/FilterCommand.swift +++ b/Sources/swift-openapi-generator/FilterCommand.swift @@ -81,13 +81,20 @@ private func timing(_ title: String, _ operation: @autoclosure () throws } private let sampleConfig = _UserConfig( - generate: [], + generate: [ + .types, + .client + ], filter: DocumentFilter( operations: ["getGreeting"], tags: ["greetings"], paths: ["/greeting"], schemas: ["Greeting"] - ) + ), + namingStrategy: .optimistic, + nameOverrides: [ + "SPECIAL_NAME": "SpecialName" + ] ) enum OutputFormat: String, ExpressibleByArgument { 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..53fbf419 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,14 @@ extension _GenerateOptions { return [] } + func resolvedNamingStrategy(_ config: _UserConfig?) -> NamingStrategy { + config?.namingStrategy ?? .defensive + } + + 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..db349e01 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..f345a1ae --- /dev/null +++ b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift @@ -0,0 +1,127 @@ +//===----------------------------------------------------------------------===// +// +// 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, optimisticUpper: String, optimisticLower: 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"), + + // Numbers + ("version 2.0", "version_space_2_period_0", "Version2_0", "version2_0"), + ("V1.2Release", "V1_period_2Release", "V1_2Release", "v1_2Release"), + + // Technical strings + ("file/path/to/resource", "file_sol_path_sol_to_sol_resource", "file_sol_path_sol_to_sol_resource", "file_sol_path_sol_to_sol_resource"), + ("user.name@domain.com", "user_period_name_commat_domain_period_com", "user_period_name_commat_domain_period_com", "user_period_name_commat_domain_period_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", "__user", "__User", "__user"), + + // 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", "vendor1_plus_json", "vendor1_plus_json"), + + // 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 asSwiftSafeName: (String, SwiftNameOptions) -> String = translator.context.asSwiftSafeName + for (input, sanitizedDefensive, _, _) in cases { + XCTAssertEqual(asSwiftSafeName(input, .none), sanitizedDefensive, "Defensive, input: \(input)") + } + } + do { + let translator = makeTranslator( + namingStrategy: .optimistic, + nameOverrides: ["MEGA": "m_e_g_a"] + ) + let asSwiftSafeName: (String, SwiftNameOptions) -> String = translator.context.asSwiftSafeName + for (input, _, optimisticUpper, optimisticLower) in cases { + XCTAssertEqual(asSwiftSafeName(input, .capitalize), optimisticUpper, "Optimistic upper, input: \(input)") + XCTAssertEqual(asSwiftSafeName(input, .none), optimisticLower, "Optimistic lower, input: \(input)") + } + } + } +} diff --git a/Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift b/Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift index a99d4d30..ab87d1d8 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? = nil, + 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? = nil, + 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? = nil, + 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,7 +85,7 @@ class Test_Core: XCTestCase { var context: TranslatorContext { makeTranslator().context } - var asSwiftSafeName: (String) -> String { context.asSwiftSafeName } + var asSwiftSafeName: (String, SwiftNameOptions) -> 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..7f69d464 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(asSwiftSafeName: { string, _ in string }) ) } } diff --git a/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeAssigner.swift b/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeAssigner.swift index c76ec4c5..c8e8b008 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(asSwiftSafeName(componentKey.rawValue, .none), expectedSwiftTypeName) } } From d9b901e705e81be3fab65d3b82d82047a9fc392e Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Fri, 22 Nov 2024 09:41:33 +0100 Subject: [PATCH 02/31] Remove unneeded code --- .../CommonTranslations/SwiftSafeNames.swift | 212 ------------------ .../CommonTranslations/translateRawEnum.swift | 4 +- 2 files changed, 2 insertions(+), 214 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift index 8494b08e..b12a64b1 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift @@ -181,96 +181,6 @@ extension String { return newString } - private static let identifierHeadCharactersRanges: [ClosedRange] = { - // https://docs.swift.org/swift-book/documentation/the-swift-programming-language/lexicalstructure/#Identifiers - var ranges: [ClosedRange] = [] - // identifier-head → Upper- or lowercase letter A through Z - ranges.append("A"..."Z") - ranges.append("a"..."z") - // identifier-head → _ - ranges.append("_") - // identifier-head → U+00A8, U+00AA, U+00AD, U+00AF, U+00B2–U+00B5, or U+00B7–U+00BA - ranges.appendFromSet([0x00A8, 0x00AA, 0x00AD, 0x00AF]) - ranges.appendFromScalars(0x00B2...0x00B5) - ranges.appendFromScalars(0x00B7...0x00BA) - // identifier-head → U+00BC–U+00BE, U+00C0–U+00D6, U+00D8–U+00F6, or U+00F8–U+00FF - ranges.appendFromScalars(0x00BC...0x00BE) - ranges.appendFromScalars(0x00C0...0x00D6) - ranges.appendFromScalars(0x00D8...0x00F6) - ranges.appendFromScalars(0x00F8...0x00FF) - // identifier-head → U+0100–U+02FF, U+0370–U+167F, U+1681–U+180D, or U+180F–U+1DBF - ranges.appendFromScalars(0x0100...0x02FF) - ranges.appendFromScalars(0x0370...0x167F) - ranges.appendFromScalars(0x1681...0x180D) - ranges.appendFromScalars(0x180F...0x1DBF) - // identifier-head → U+1E00–U+1FFF - ranges.appendFromScalars(0x1E00...0x1FFF) - // identifier-head → U+200B–U+200D, U+202A–U+202E, U+203F–U+2040, U+2054, or U+2060–U+206F - ranges.appendFromScalars(0x200B...0x200D) - ranges.appendFromScalars(0x202A...0x202E) - ranges.appendFromScalars(0x203F...0x2040) - ranges.appendFromScalar(0x2054) - ranges.appendFromScalars(0x2060...0x206F) - // identifier-head → U+2070–U+20CF, U+2100–U+218F, U+2460–U+24FF, or U+2776–U+2793 - ranges.appendFromScalars(0x2070...0x20CF) - ranges.appendFromScalars(0x2100...0x218F) - ranges.appendFromScalars(0x2460...0x24FF) - ranges.appendFromScalars(0x2776...0x2793) - // identifier-head → U+2C00–U+2DFF or U+2E80–U+2FFF - ranges.appendFromScalars(0x2C00...0x2DFF) - ranges.appendFromScalars(0x2E80...0x2FFF) - // identifier-head → U+3004–U+3007, U+3021–U+302F, U+3031–U+303F, or U+3040–U+D7FF - ranges.appendFromScalars(0x3004...0x3007) - ranges.appendFromScalars(0x3021...0x302F) - ranges.appendFromScalars(0x3031...0x303F) - ranges.appendFromScalars(0x3040...0xD7FF) - // identifier-head → U+F900–U+FD3D, U+FD40–U+FDCF, U+FDF0–U+FE1F, or U+FE30–U+FE44 - ranges.appendFromScalars(0xF900...0xFD3D) - ranges.appendFromScalars(0xFD40...0xFDCF) - ranges.appendFromScalars(0xFDF0...0xFE1F) - ranges.appendFromScalars(0xFE30...0xFE44) - // identifier-head → U+FE47–U+FFFD - ranges.appendFromScalars(0xFE47...0xFFFD) - // identifier-head → U+10000–U+1FFFD, U+20000–U+2FFFD, U+30000–U+3FFFD, or U+40000–U+4FFFD - ranges.appendFromScalars(0x10000...0x1FFFD) - ranges.appendFromScalars(0x20000...0x2FFFD) - ranges.appendFromScalars(0x30000...0x3FFFD) - ranges.appendFromScalars(0x40000...0x4FFFD) - // identifier-head → U+50000–U+5FFFD, U+60000–U+6FFFD, U+70000–U+7FFFD, or U+80000–U+8FFFD - ranges.appendFromScalars(0x50000...0x5FFFD) - ranges.appendFromScalars(0x60000...0x6FFFD) - ranges.appendFromScalars(0x70000...0x7FFFD) - ranges.appendFromScalars(0x80000...0x8FFFD) - // identifier-head → U+90000–U+9FFFD, U+A0000–U+AFFFD, U+B0000–U+BFFFD, or U+C0000–U+CFFFD - ranges.appendFromScalars(0x90000...0x9FFFD) - ranges.appendFromScalars(0xA0000...0xAFFFD) - ranges.appendFromScalars(0xB0000...0xBFFFD) - ranges.appendFromScalars(0xC0000...0xCFFFD) - // identifier-head → U+D0000–U+DFFFD or U+E0000–U+EFFFD - ranges.appendFromScalars(0xD0000...0xDFFFD) - ranges.appendFromScalars(0xE0000...0xEFFFD) - return ranges - }() - - private static let identifierNonHeadCharactersRanges: [ClosedRange] = { - var ranges: [ClosedRange] = [] - // identifier-character → Digit 0 through 9 - ranges.append("0"..."9") - // identifier-character → U+0300–U+036F, U+1DC0–U+1DFF, U+20D0–U+20FF, or U+FE20–U+FE2F - ranges.appendFromScalars(0x0300...0x036F) - ranges.appendFromScalars(0x1DC0...0x1DFF) - ranges.appendFromScalars(0x20D0...0x20FF) - ranges.appendFromScalars(0xFE20...0xFE2F) - return ranges - }() - - private static let identifierCharactersRanges: [ClosedRange] = { - var ranges: [ClosedRange] = [] - ranges.append(contentsOf: identifierHeadCharactersRanges) - ranges.append(contentsOf: identifierNonHeadCharactersRanges) - return ranges - }() - /// A list of Swift keywords. /// /// Copied from SwiftSyntax/TokenKind.swift @@ -291,125 +201,3 @@ extension String { "\\": "bsol", "]": "rbrack", "^": "hat", "`": "grave", "{": "lcub", "|": "verbar", "}": "rcub", "~": "tilde", ] } - -extension [ClosedRange] { - func contains(_ char: Character) -> Bool { - // TODO: This could be optimized if we create a data structure of sorted ranges and use binary search. - for range in self { - if range.contains(char) { - return true - } - } - return false - } - - mutating func appendFromScalar(_ scalar: Int) { - append(Character(UnicodeScalar(scalar)!)...Character(UnicodeScalar(scalar)!)) - } - - mutating func appendFromSet(_ set: Set) { - append(contentsOf: ClosedRange.fromSet(set)) - } - - mutating func appendFromScalars(_ range: ClosedRange) { - append(Character(UnicodeScalar(range.lowerBound)!)...Character(UnicodeScalar(range.upperBound)!)) - } -} - -extension ClosedRange: @retroactive ExpressibleByUnicodeScalarLiteral where Bound == Character { - public typealias UnicodeScalarLiteralType = Character - public init(unicodeScalarLiteral value: UnicodeScalarLiteralType) { - let char = Character(unicodeScalarLiteral: value) - self = char...char - } -} - -extension ClosedRange where Bound == Character { - static func fromSet(_ set: Set) -> [Self] { - set.map { - Character(UnicodeScalar($0)!)...Character(UnicodeScalar($0)!) - } - } -} - -fileprivate extension Set where Element == Character { - mutating func insert(charactersInString string: String) { - for character in string { - insert(character) - } - } - - mutating func insert(allFrom lowerBound: Character, upTo upperBound: Character) { - for byte in lowerBound.asciiValue!...upperBound.asciiValue! { - insert(Character(UnicodeScalar(byte))) - } - } - - mutating func insert(scalarWithCode code: Int) { - insert(Character(UnicodeScalar(code)!)) - } - - mutating func insert(scalarInSet set: Set) { - for code in set { - insert(scalarWithCode: code) - } - } - - mutating func insert(scalarsInRangeFrom lowerBound: Int, upTo upperBound: Int) { - for code in lowerBound...upperBound { - insert(scalarWithCode: code) - } - } -} - -@available(*, deprecated) -fileprivate extension UInt8 { - - var isUppercaseLetter: Bool { - (0x41...0x5a).contains(self) - } - - var isLowercaseLetter: Bool { - (0x61...0x7a).contains(self) - } - - var isLetter: Bool { - isUppercaseLetter || isLowercaseLetter - } - - var isNumber: Bool { - (0x30...0x39).contains(self) - } - - var isLowercaseLetterOrNumber: Bool { - isLowercaseLetter || isNumber - } - - var isAlphanumeric: Bool { - isLetter || isNumber - } - - var asUppercase: UInt8 { - if isUppercaseLetter || isNumber { - return self - } - if isLowercaseLetter { - return self - 0x20 - } - preconditionFailure("Cannot uppercase not a letter or number") - } - - var asLowercase: UInt8 { - if isLowercaseLetter || isNumber { - return self - } - if isUppercaseLetter { - return self + 0x20 - } - preconditionFailure("Cannot lowercase not a letter or number") - } - - var isWordSeparator: Bool { - self == 0x2d /* - */ || self == 0x5f /* _ */ - } -} diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateRawEnum.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateRawEnum.swift index ed513551..cb7507be 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.asSwiftSafeName("", .none)) } 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.asSwiftSafeName(rawValue, .none) try addIfUnique(id: .string(rawValue), caseName: caseName) } case .integer: From 3ded3fec5290a0eec1a027905db50c5715f72a02 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Fri, 22 Nov 2024 10:33:15 +0100 Subject: [PATCH 03/31] Updated draft proposal --- .../Documentation.docc/Proposals/Proposals.md | 1 + .../Documentation.docc/Proposals/SOAR-0013.md | 160 ++++++++++++++++++ .../Extensions/Test_SwiftSafeNames.swift | 6 +- 3 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0013.md diff --git a/Sources/swift-openapi-generator/Documentation.docc/Proposals/Proposals.md b/Sources/swift-openapi-generator/Documentation.docc/Proposals/Proposals.md index 0bc8421e..bdaff6a5 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Proposals/Proposals.md +++ b/Sources/swift-openapi-generator/Documentation.docc/Proposals/Proposals.md @@ -54,3 +54,4 @@ If you have any questions, tag [Honza Dvorsky](https://github.com/czechboy0) or - - - +- diff --git a/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0013.md b/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0013.md new file mode 100644 index 00000000..44832c25 --- /dev/null +++ b/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0013.md @@ -0,0 +1,160 @@ +# SOAR-0013: Optimistic naming strategy + +Introduce an alternative naming strategy for more idiomatic Swift identifiers, including a way to provide custom name overrides. + +## Overview + +- Proposal: SOAR-0013 +- Author(s): [Honza Dvorsky](https://github.com/czechboy0), [Si Beaumont](https://github.com/simonjbeaumont) +- Status: **Awaiting Review** +- Issues: + - [apple/swift-openapi-generator#112][issuePlugin] + - [apple/swift-openapi-generator#107][issue1] + - [apple/swift-openapi-generator#503][issue2] + - [apple/swift-openapi-generator#244][issue3] + - [apple/swift-openapi-generator#405][issue4] +- Implementation: + - [apple/swift-openapi-generator#679][pr] +- New configuration options: + - `namingStrategy` + - `nameOverrides` +- Affected components: + - generator + +### Introduction + +Introduce a new naming strategy as an opt-in feature, instructing the generator to produce more conventional Swift names, and offer a way to completely customize how any OpenAPI identifier gets projected to a Swift identifier. + +### Motivation + +The purpose of Swift OpenAPI Generator is to generate Swift code from OpenAPI documents. As part of that process, names specified in the OpenAPI document have to be converted to names in Swift code - and there are many ways to do that. We call these "naming strategies" in this proposal. + +When Swift OpenAPI Generator 0.1.0 went open-source in May 2023, it had a simple naming strategy that produced relatively conventional Swift identifiers from OpenAPI names, however when tested on a large test corpus of around 3000 OpenAPI documents, it produced an unacceptably high number of non-compiling packages due to naming conflicts. + +The root cause of conflicts are the different allowed character sets for OpenAPI names and Swift identifiers. OpenAPI has a more flexible allowed character set than Swift identifiers. + +In response to the findings on the test corpus, the proposal [SOAR-0001: Improved mapping of identifiers][soar0001], which shipped in 0.2.0, changed the naming strategy to avoid conflicts and resulted in no conflicts produced in the test corpus, allowing hundreds of additional OpenAPI documents to be correctly handled by Swift OpenAPI Generator. + +The way the conflicts are avoided in the naming strategy from SOAR-0001 is by turning any special characters (any characters that aren't letters, numbers, or an underscore) into words, resulting in identifiers like: + +``` +User -> User +User_1 -> User_1 +user-name -> user_hyphen_name +my.org.User -> my_period_org_period_User +``` + +The existing naming strategy also avoids changing the character casing, as we discovered OpenAPI documents with properties within an object schema that only differred by case. + +The decision to rely on a naming strategy that can handle all the tested OpenAPI documents was the right one, and it has allowed more developers to get value from Swift OpenAPI Generator since then. + +However, we've also [heard][issue1] [from][issue2] [adopters][issue3] [who][issue4] don't use special characters in their OpenAPI documents, and how some of the generated Swift names are still difficult to read and are simply unpleasant to look at. + +We originally believed that a fully generalized solution, such as a ["naming extension"][issuePlugin], would be the right way to go, however after further consideration, we believe we can offer a solution that solves the majority of the problem without needing to invent an extension architecture for Swift OpenAPI Generator. + +### Proposed solution + +We propose to introduce a second, opt-in naming strategy, which produces idiomatic Swift identifiers from arbitrary OpenAPI names, and a way to fully customize the conversion from an OpenAPI name to a Swift identifier using a string -> string map. + +For clarity, we'll refer to the existing naming strategy as the "defensive" naming strategy, and to the new proposed strategy as the "optimistic" naming strategy. The names reflect the strengths of each strategy - the defensive strategy can handle any OpenAPI document and produce compiling Swift code, the optimistic naming strategy produces prettier names, but does not work for all documents, and falls back to the defensive strategy when needed on a per-name basis. + +Part of the new strategy is adjusting the capitalization, and producing `UpperCamelCase` names for types, and `lowerCamelCase` names for members, as is common in hand-written Swift code. + +> Warning: Due to the optimistic naming strategy changing capitalization, it is possible to get non-compiling Swift code from more OpenAPI documents than with the defensive naming strategy. We recommend you try to use the optimistic naming strategy on your OpenAPI document, and if it produces conflicts, switch back to the defensive naming strategy, which avoids conflicts. However, the number of documents that result in conflicts with the optimistic naming strategy is estimated to be very small (<1%). + +The second feature introduced as part of this proposal is a way to provide a string -> string map to fully override only specific OpenAPI names and provide their exact Swift identifiers. This is the ultimate escape hatch when both naming strategies fail to provide the desired result for the adopter. + +#### Examples + +To get a sense for the proposed change, check out the table below that compares the existing defensive strategy against the proposed optimistic strategy on a set of examples: + +| OpenAPI name | Defensive | Optimistic (capitalized) | Optimistic (non-capitalized) | +| ------------ | --------- | ------------------------ | ---------------------------- | +| `foo` | `foo` | `Foo` | `foo` | +| `Hello world` | `Hello_space_world` | `HelloWorld` | `helloWorld` | +| `My_URL_value` | `My_URL_value` | `MyURLValue` | `myURLValue` | +| `Retry-After` | `Retry_hyphen_After` | `RetryAfter` | `retryAfter` | +| `NOT_AVAILABLE` | `NOT_AVAILABLE` | `NotAvailable` | `notAvailable` | +| `version 2.0` | `version_space_2_period_0` | `Version2_0` | `version2_0` | +| `naïve café` | `naïve_space_café` | `NaïveCafé` | `naïveCafé` | +| `__user` | `__user` | `__User` | `__user` | +| `order#123` | `order_num_123` | `order_num_123` | `order_num_123` | + +Notice that in the last example, since the OpenAPI name contains the pound (`#`) character, the optimistic naming strategy falls back to the defensive naming strategy. In all the other cases, however, the resulting names are more idiomatic Swift identifiers. + +> Tip: For more examples, check out the updated [test suite](https://github.com/czechboy0/swift-openapi-generator/blob/hd-naming-strategy-optimistic/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift). + +### Detailed design + +This section goes into detail of the [draft implementation][pr] that you can already check out and try to run on your OpenAPI document. + +> Note: To enable it, you'll need to add `namingStrategy: optimistic` to your `openapi-generator-config.yaml` file. + +#### Naming logic + +The optimistic naming strategy (check out the current code [here][impl], look for the method `safeForSwiftCode_optimistic`) is built around the decision to _only_ optimize for names that include the following: + +- letters +- numbers +- periods (`.`, ASCII: `0x2e`) +- dashes (`-`, ASCII: `0x2d`) +- underscores (`_`, ASCII: `0x5f`) +- spaces (` `, ASCII: `0x20`) + +> Note: We let [`Swift.String.isLetter`](https://developer.apple.com/documentation/swift/character/isletter) decide whether a character is a letter, which has the advantage of including letters in the non-ASCII range. Swift identifiers also support a [wide range](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/lexicalstructure/#Identifiers) of alphanumeric characters. + +If the OpenAPI name includes any _other_ characters, the optimistic naming strategy _falls back_ to the defensive naming strategy for that input string only. + +There's a second special case for handling all uppercased names, such as `NOT_AVAILABLE` - if this situation is detected, the optimistic naming strategy turns it into `NotAvailable` for types and `notAvailable` for members. + +The best way to understand the detailed logic is to check out the [code][impl], feel free to leave comments on the pull request. + +#### Naming strategy configuration + +Since Swift OpenAPI Generator is on a stable 1.x version, we cannot change the naming strategy for everyone, as it would be considered an API break. So this new naming strategy is fully opt-in using a new configuration key called `namingStrategy`, with the following allowed values: + +- `defensive`: the existing naming strategy introduced in 0.2.0 +- `optimistic`: the new naming strategy proposed here +- not specified: defaults to `defensive` for backwards compatibility + +Enabling this feature in the configuration file would look like this: + +```yaml +namingStrategy: optimistic +``` + +#### Name overrides + +While the new naming strategy produces much improved Swift names, there are still cases when the adopter knows better how they'd like a specific OpenAPI name be translated to a Swift identifier. + +A good examples are the `+1` and `-1` properties in the GitHub OpenAPI document: using both strategies, the names would be `_plus_1` and `_hyphen_1`, respectively. While such names aren't too confusing, the adopter might want to customize them to, for example: `thumbsUp` and `thumbsDown`. + +Enabling this feature in the configuration file would look like this: + +```yaml +nameOverrides: + '+1': 'thumbsUp' + '-1': 'thumbsDown' +``` + +### API stability + +Both the new naming strategy and name overrides are purely additive, and require the adopter to explicitly opt-in. + +### Future directions + +With this proposal, we plan to abandon the ["naming extensions" idea][issuePlugin], as we consider the solution in this proposal to solve the name conversion problem for Swift OpenAPI Generator 1.x for all use cases. + +### Alternatives considered + +- ["Naming extensions"][issuePlugin], however that'd require the community to build and maintain custom naming strategies, and it was not clear that this feature would be possible in SwiftPM using only current features. +- Not changing anything, this was the status quo since 0.2.0, but adopters have made it clear that there is room to improve the naming strategy through the several filed issues linked at the top of the proposal, so we feel that some action here is justified. + +[soar0001]: https://swiftpackageindex.com/apple/swift-openapi-generator/documentation/swift-openapi-generator/soar-0001 +[issue1]: https://github.com/apple/swift-openapi-generator/issues/107 +[issue2]: https://github.com/apple/swift-openapi-generator/issues/503 +[issue3]: https://github.com/apple/swift-openapi-generator/issues/244 +[issue4]: https://github.com/apple/swift-openapi-generator/issues/405 +[issuePlugin]: https://github.com/apple/swift-openapi-generator/issues/112 +[pr]: https://github.com/apple/swift-openapi-generator/pull/679 +[impl]: https://github.com/czechboy0/swift-openapi-generator/blob/hd-naming-strategy-optimistic/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift diff --git a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift index f345a1ae..399f7493 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift @@ -98,7 +98,11 @@ final class Test_SwiftSafeNames: Test_Core { // Content type components ("application", "application", "Application", "application"), ("vendor1+json", "vendor1_plus_json", "vendor1_plus_json", "vendor1_plus_json"), - + + // Known real-world examples + ("+1", "_plus_1", "_plus_1", "_plus_1"), + ("-1", "_hyphen_1", "_hyphen_1", "_hyphen_1"), + // Override ("MEGA", "m_e_g_a", "m_e_g_a", "m_e_g_a"), ] From 4303842668f7ab63c29e638287eb5fe076e4249e Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Mon, 25 Nov 2024 18:32:37 +0100 Subject: [PATCH 04/31] Update SOAR-0013.md Co-authored-by: Si Beaumont --- .../Documentation.docc/Proposals/SOAR-0013.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0013.md b/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0013.md index 44832c25..b2f1e94f 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0013.md +++ b/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0013.md @@ -29,7 +29,7 @@ Introduce a new naming strategy as an opt-in feature, instructing the generator The purpose of Swift OpenAPI Generator is to generate Swift code from OpenAPI documents. As part of that process, names specified in the OpenAPI document have to be converted to names in Swift code - and there are many ways to do that. We call these "naming strategies" in this proposal. -When Swift OpenAPI Generator 0.1.0 went open-source in May 2023, it had a simple naming strategy that produced relatively conventional Swift identifiers from OpenAPI names, however when tested on a large test corpus of around 3000 OpenAPI documents, it produced an unacceptably high number of non-compiling packages due to naming conflicts. +When Swift OpenAPI Generator 0.1.0 went open-source in May 2023, it had a simple naming strategy that produced relatively conventional Swift identifiers from OpenAPI names. However, when tested on a large test corpus of around 3000 OpenAPI documents, it produced an unacceptably high number of non-compiling packages due to naming conflicts. The root cause of conflicts are the different allowed character sets for OpenAPI names and Swift identifiers. OpenAPI has a more flexible allowed character set than Swift identifiers. From 6c0f05cc402b7a606148a462fe401fad45a7be3d Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Tue, 26 Nov 2024 11:56:45 +0100 Subject: [PATCH 05/31] PR feedback --- .../Documentation.docc/Proposals/SOAR-0013.md | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0013.md b/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0013.md index 44832c25..b77b73c9 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0013.md +++ b/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0013.md @@ -1,4 +1,4 @@ -# SOAR-0013: Optimistic naming strategy +# SOAR-0013: Idiomatic naming strategy Introduce an alternative naming strategy for more idiomatic Swift identifiers, including a way to provide custom name overrides. @@ -33,6 +33,8 @@ When Swift OpenAPI Generator 0.1.0 went open-source in May 2023, it had a simple The root cause of conflicts are the different allowed character sets for OpenAPI names and Swift identifiers. OpenAPI has a more flexible allowed character set than Swift identifiers. +The existing naming strategy also avoids changing the character casing, as we discovered OpenAPI documents with properties within an object schema that only differred by case. + In response to the findings on the test corpus, the proposal [SOAR-0001: Improved mapping of identifiers][soar0001], which shipped in 0.2.0, changed the naming strategy to avoid conflicts and resulted in no conflicts produced in the test corpus, allowing hundreds of additional OpenAPI documents to be correctly handled by Swift OpenAPI Generator. The way the conflicts are avoided in the naming strategy from SOAR-0001 is by turning any special characters (any characters that aren't letters, numbers, or an underscore) into words, resulting in identifiers like: @@ -44,31 +46,27 @@ user-name -> user_hyphen_name my.org.User -> my_period_org_period_User ``` -The existing naming strategy also avoids changing the character casing, as we discovered OpenAPI documents with properties within an object schema that only differred by case. - The decision to rely on a naming strategy that can handle all the tested OpenAPI documents was the right one, and it has allowed more developers to get value from Swift OpenAPI Generator since then. However, we've also [heard][issue1] [from][issue2] [adopters][issue3] [who][issue4] don't use special characters in their OpenAPI documents, and how some of the generated Swift names are still difficult to read and are simply unpleasant to look at. -We originally believed that a fully generalized solution, such as a ["naming extension"][issuePlugin], would be the right way to go, however after further consideration, we believe we can offer a solution that solves the majority of the problem without needing to invent an extension architecture for Swift OpenAPI Generator. - ### Proposed solution We propose to introduce a second, opt-in naming strategy, which produces idiomatic Swift identifiers from arbitrary OpenAPI names, and a way to fully customize the conversion from an OpenAPI name to a Swift identifier using a string -> string map. -For clarity, we'll refer to the existing naming strategy as the "defensive" naming strategy, and to the new proposed strategy as the "optimistic" naming strategy. The names reflect the strengths of each strategy - the defensive strategy can handle any OpenAPI document and produce compiling Swift code, the optimistic naming strategy produces prettier names, but does not work for all documents, and falls back to the defensive strategy when needed on a per-name basis. +For clarity, we'll refer to the existing naming strategy as the "defensive" naming strategy, and to the new proposed strategy as the "idiomatic" naming strategy. The names reflect the strengths of each strategy - the defensive strategy can handle any OpenAPI document and produce compiling Swift code, the idiomatic naming strategy produces prettier names, but does not work for all documents, and falls back to the defensive strategy when needed on a per-name basis. Part of the new strategy is adjusting the capitalization, and producing `UpperCamelCase` names for types, and `lowerCamelCase` names for members, as is common in hand-written Swift code. -> Warning: Due to the optimistic naming strategy changing capitalization, it is possible to get non-compiling Swift code from more OpenAPI documents than with the defensive naming strategy. We recommend you try to use the optimistic naming strategy on your OpenAPI document, and if it produces conflicts, switch back to the defensive naming strategy, which avoids conflicts. However, the number of documents that result in conflicts with the optimistic naming strategy is estimated to be very small (<1%). +> Warning: Due to the idiomatic naming strategy changing capitalization, it is possible to get non-compiling Swift code from more OpenAPI documents than with the defensive naming strategy. We recommend you try to use the idiomatic naming strategy on your OpenAPI document, and if it produces conflicts, switch back to the defensive naming strategy, which avoids conflicts. However, the number of documents that result in conflicts with the idiomatic naming strategy is estimated to be very small (<1%). The second feature introduced as part of this proposal is a way to provide a string -> string map to fully override only specific OpenAPI names and provide their exact Swift identifiers. This is the ultimate escape hatch when both naming strategies fail to provide the desired result for the adopter. #### Examples -To get a sense for the proposed change, check out the table below that compares the existing defensive strategy against the proposed optimistic strategy on a set of examples: +To get a sense for the proposed change, check out the table below that compares the existing defensive strategy against the proposed idiomatic strategy on a set of examples: -| OpenAPI name | Defensive | Optimistic (capitalized) | Optimistic (non-capitalized) | +| OpenAPI name | Defensive | Idiomatic (capitalized) | Idiomatic (non-capitalized) | | ------------ | --------- | ------------------------ | ---------------------------- | | `foo` | `foo` | `Foo` | `foo` | | `Hello world` | `Hello_space_world` | `HelloWorld` | `helloWorld` | @@ -80,7 +78,7 @@ To get a sense for the proposed change, check out the table below that compares | `__user` | `__user` | `__User` | `__user` | | `order#123` | `order_num_123` | `order_num_123` | `order_num_123` | -Notice that in the last example, since the OpenAPI name contains the pound (`#`) character, the optimistic naming strategy falls back to the defensive naming strategy. In all the other cases, however, the resulting names are more idiomatic Swift identifiers. +Notice that in the last example, since the OpenAPI name contains the pound (`#`) character, the idiomatic naming strategy falls back to the defensive naming strategy. In all the other cases, however, the resulting names are more idiomatic Swift identifiers. > Tip: For more examples, check out the updated [test suite](https://github.com/czechboy0/swift-openapi-generator/blob/hd-naming-strategy-optimistic/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift). @@ -88,11 +86,11 @@ Notice that in the last example, since the OpenAPI name contains the pound (`#`) This section goes into detail of the [draft implementation][pr] that you can already check out and try to run on your OpenAPI document. -> Note: To enable it, you'll need to add `namingStrategy: optimistic` to your `openapi-generator-config.yaml` file. +> Note: To enable it, you'll need to add `namingStrategy: idiomatic` to your `openapi-generator-config.yaml` file. #### Naming logic -The optimistic naming strategy (check out the current code [here][impl], look for the method `safeForSwiftCode_optimistic`) is built around the decision to _only_ optimize for names that include the following: +The idiomatic naming strategy (check out the current code [here][impl], look for the method `safeForSwiftCode_idiomatic`) is built around the decision to _only_ optimize for names that include the following: - letters - numbers @@ -103,9 +101,9 @@ The optimistic naming strategy (check out the current code [here][impl], look fo > Note: We let [`Swift.String.isLetter`](https://developer.apple.com/documentation/swift/character/isletter) decide whether a character is a letter, which has the advantage of including letters in the non-ASCII range. Swift identifiers also support a [wide range](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/lexicalstructure/#Identifiers) of alphanumeric characters. -If the OpenAPI name includes any _other_ characters, the optimistic naming strategy _falls back_ to the defensive naming strategy for that input string only. +If the OpenAPI name includes any _other_ characters, the idiomatic naming strategy _falls back_ to the defensive naming strategy for that input string only. -There's a second special case for handling all uppercased names, such as `NOT_AVAILABLE` - if this situation is detected, the optimistic naming strategy turns it into `NotAvailable` for types and `notAvailable` for members. +There's a second special case for handling all uppercased names, such as `NOT_AVAILABLE` - if this situation is detected, the idiomatic naming strategy turns it into `NotAvailable` for types and `notAvailable` for members. The best way to understand the detailed logic is to check out the [code][impl], feel free to leave comments on the pull request. @@ -114,13 +112,13 @@ The best way to understand the detailed logic is to check out the [code][impl], Since Swift OpenAPI Generator is on a stable 1.x version, we cannot change the naming strategy for everyone, as it would be considered an API break. So this new naming strategy is fully opt-in using a new configuration key called `namingStrategy`, with the following allowed values: - `defensive`: the existing naming strategy introduced in 0.2.0 -- `optimistic`: the new naming strategy proposed here +- `idiomatic`: the new naming strategy proposed here - not specified: defaults to `defensive` for backwards compatibility Enabling this feature in the configuration file would look like this: ```yaml -namingStrategy: optimistic +namingStrategy: idiomatic ``` #### Name overrides From d6c037cda6f1522bd9432aa4f7c7ffaa564d2132 Mon Sep 17 00:00:00 2001 From: Si Beaumont Date: Wed, 27 Nov 2024 12:09:22 +0000 Subject: [PATCH 06/31] Proposal updates --- .../Documentation.docc/Proposals/SOAR-0013.md | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0013.md b/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0013.md index f351131e..a027cbe6 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0013.md +++ b/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0013.md @@ -29,13 +29,11 @@ Introduce a new naming strategy as an opt-in feature, instructing the generator The purpose of Swift OpenAPI Generator is to generate Swift code from OpenAPI documents. As part of that process, names specified in the OpenAPI document have to be converted to names in Swift code - and there are many ways to do that. We call these "naming strategies" in this proposal. -When Swift OpenAPI Generator 0.1.0 went open-source in May 2023, it had a simple naming strategy that produced relatively conventional Swift identifiers from OpenAPI names. However, when tested on a large test corpus of around 3000 OpenAPI documents, it produced an unacceptably high number of non-compiling packages due to naming conflicts. +When Swift OpenAPI Generator 0.1.0 went open-source in May 2023, it had a simple naming strategy that produced relatively conventional Swift identifiers from OpenAPI names. -The root cause of conflicts are the different allowed character sets for OpenAPI names and Swift identifiers. OpenAPI has a more flexible allowed character set than Swift identifiers. +However, when tested on a large test corpus of around 3000 OpenAPI documents, it produced an unacceptably high number of non-compiling packages due to naming conflicts. The root cause of conflicts are the different allowed character sets for OpenAPI names and Swift identifiers. OpenAPI has a more flexible allowed character set than Swift identifiers. -The existing naming strategy also avoids changing the character casing, as we discovered OpenAPI documents with properties within an object schema that only differred by case. - -In response to the findings on the test corpus, the proposal [SOAR-0001: Improved mapping of identifiers][soar0001], which shipped in 0.2.0, changed the naming strategy to avoid conflicts and resulted in no conflicts produced in the test corpus, allowing hundreds of additional OpenAPI documents to be correctly handled by Swift OpenAPI Generator. +In response to these findings, [SOAR-0001: Improved mapping of identifiers][soar0001], which shipped in 0.2.0, changed the naming strategy to avoid these conflicts, allowing hundreds of additional OpenAPI documents to be correctly handled by Swift OpenAPI Generator. This addressed all issues related to naming conflicts in the test corpus. This is the existing naming strategy today. This strategy also avoids changing the character casing, as we discovered OpenAPI documents with properties within an object schema that only differed by case. The way the conflicts are avoided in the naming strategy from SOAR-0001 is by turning any special characters (any characters that aren't letters, numbers, or an underscore) into words, resulting in identifiers like: @@ -46,9 +44,7 @@ user-name -> user_hyphen_name my.org.User -> my_period_org_period_User ``` -The decision to rely on a naming strategy that can handle all the tested OpenAPI documents was the right one, and it has allowed more developers to get value from Swift OpenAPI Generator since then. - -However, we've also [heard][issue1] [from][issue2] [adopters][issue3] [who][issue4] don't use special characters in their OpenAPI documents, and how some of the generated Swift names are still difficult to read and are simply unpleasant to look at. +Our priority was to support as many valid OpenAPI documents as possible. However, we've also [heard][issue1] [from][issue2] [adopters][issue3] [who][issue4] would prefer more idiomatic generated code and don't benefit from the defensive naming strategy. ### Proposed solution @@ -58,10 +54,12 @@ For clarity, we'll refer to the existing naming strategy as the "defensive" nami Part of the new strategy is adjusting the capitalization, and producing `UpperCamelCase` names for types, and `lowerCamelCase` names for members, as is common in hand-written Swift code. -> Warning: Due to the idiomatic naming strategy changing capitalization, it is possible to get non-compiling Swift code from more OpenAPI documents than with the defensive naming strategy. We recommend you try to use the idiomatic naming strategy on your OpenAPI document, and if it produces conflicts, switch back to the defensive naming strategy, which avoids conflicts. However, the number of documents that result in conflicts with the idiomatic naming strategy is estimated to be very small (<1%). +After attempting to produce an more idiomatic identifier, this strategy will fall back to the defensive strategy, to replace any remaining invalid symbols. The second feature introduced as part of this proposal is a way to provide a string -> string map to fully override only specific OpenAPI names and provide their exact Swift identifiers. This is the ultimate escape hatch when both naming strategies fail to provide the desired result for the adopter. +> Note: Because the idiomatic naming strategy can change capitalization, in rare cases it can still generate non-compiling code from a valid OpenAPI document. We recommend using the idiomatic naming strategy and, if it produces conflicts, address them using name overrides, or switch back to the defensive naming strategy. + #### Examples To get a sense for the proposed change, check out the table below that compares the existing defensive strategy against the proposed idiomatic strategy on a set of examples: @@ -101,7 +99,7 @@ The idiomatic naming strategy (check out the current code [here][impl], look for > Note: We let [`Swift.String.isLetter`](https://developer.apple.com/documentation/swift/character/isletter) decide whether a character is a letter, which has the advantage of including letters in the non-ASCII range. Swift identifiers also support a [wide range](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/lexicalstructure/#Identifiers) of alphanumeric characters. -If the OpenAPI name includes any _other_ characters, the idiomatic naming strategy _falls back_ to the defensive naming strategy for that input string only. +If the OpenAPI name includes any _other_ characters, the idiomatic naming strategy _falls back_ to the defensive naming strategy for that input string only. There's a second special case for handling all uppercased names, such as `NOT_AVAILABLE` - if this situation is detected, the idiomatic naming strategy turns it into `NotAvailable` for types and `notAvailable` for members. From 8f51c49bc51f64e7189818a564042f64779093a9 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Wed, 27 Nov 2024 13:17:32 +0100 Subject: [PATCH 07/31] Remove proposal, broken out into #683 --- .../Documentation.docc/Proposals/Proposals.md | 1 - .../Documentation.docc/Proposals/SOAR-0013.md | 156 ------------------ 2 files changed, 157 deletions(-) delete mode 100644 Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0013.md diff --git a/Sources/swift-openapi-generator/Documentation.docc/Proposals/Proposals.md b/Sources/swift-openapi-generator/Documentation.docc/Proposals/Proposals.md index bdaff6a5..0bc8421e 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Proposals/Proposals.md +++ b/Sources/swift-openapi-generator/Documentation.docc/Proposals/Proposals.md @@ -54,4 +54,3 @@ If you have any questions, tag [Honza Dvorsky](https://github.com/czechboy0) or - - - -- diff --git a/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0013.md b/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0013.md deleted file mode 100644 index a027cbe6..00000000 --- a/Sources/swift-openapi-generator/Documentation.docc/Proposals/SOAR-0013.md +++ /dev/null @@ -1,156 +0,0 @@ -# SOAR-0013: Idiomatic naming strategy - -Introduce an alternative naming strategy for more idiomatic Swift identifiers, including a way to provide custom name overrides. - -## Overview - -- Proposal: SOAR-0013 -- Author(s): [Honza Dvorsky](https://github.com/czechboy0), [Si Beaumont](https://github.com/simonjbeaumont) -- Status: **Awaiting Review** -- Issues: - - [apple/swift-openapi-generator#112][issuePlugin] - - [apple/swift-openapi-generator#107][issue1] - - [apple/swift-openapi-generator#503][issue2] - - [apple/swift-openapi-generator#244][issue3] - - [apple/swift-openapi-generator#405][issue4] -- Implementation: - - [apple/swift-openapi-generator#679][pr] -- New configuration options: - - `namingStrategy` - - `nameOverrides` -- Affected components: - - generator - -### Introduction - -Introduce a new naming strategy as an opt-in feature, instructing the generator to produce more conventional Swift names, and offer a way to completely customize how any OpenAPI identifier gets projected to a Swift identifier. - -### Motivation - -The purpose of Swift OpenAPI Generator is to generate Swift code from OpenAPI documents. As part of that process, names specified in the OpenAPI document have to be converted to names in Swift code - and there are many ways to do that. We call these "naming strategies" in this proposal. - -When Swift OpenAPI Generator 0.1.0 went open-source in May 2023, it had a simple naming strategy that produced relatively conventional Swift identifiers from OpenAPI names. - -However, when tested on a large test corpus of around 3000 OpenAPI documents, it produced an unacceptably high number of non-compiling packages due to naming conflicts. The root cause of conflicts are the different allowed character sets for OpenAPI names and Swift identifiers. OpenAPI has a more flexible allowed character set than Swift identifiers. - -In response to these findings, [SOAR-0001: Improved mapping of identifiers][soar0001], which shipped in 0.2.0, changed the naming strategy to avoid these conflicts, allowing hundreds of additional OpenAPI documents to be correctly handled by Swift OpenAPI Generator. This addressed all issues related to naming conflicts in the test corpus. This is the existing naming strategy today. This strategy also avoids changing the character casing, as we discovered OpenAPI documents with properties within an object schema that only differed by case. - -The way the conflicts are avoided in the naming strategy from SOAR-0001 is by turning any special characters (any characters that aren't letters, numbers, or an underscore) into words, resulting in identifiers like: - -``` -User -> User -User_1 -> User_1 -user-name -> user_hyphen_name -my.org.User -> my_period_org_period_User -``` - -Our priority was to support as many valid OpenAPI documents as possible. However, we've also [heard][issue1] [from][issue2] [adopters][issue3] [who][issue4] would prefer more idiomatic generated code and don't benefit from the defensive naming strategy. - -### Proposed solution - -We propose to introduce a second, opt-in naming strategy, which produces idiomatic Swift identifiers from arbitrary OpenAPI names, and a way to fully customize the conversion from an OpenAPI name to a Swift identifier using a string -> string map. - -For clarity, we'll refer to the existing naming strategy as the "defensive" naming strategy, and to the new proposed strategy as the "idiomatic" naming strategy. The names reflect the strengths of each strategy - the defensive strategy can handle any OpenAPI document and produce compiling Swift code, the idiomatic naming strategy produces prettier names, but does not work for all documents, and falls back to the defensive strategy when needed on a per-name basis. - -Part of the new strategy is adjusting the capitalization, and producing `UpperCamelCase` names for types, and `lowerCamelCase` names for members, as is common in hand-written Swift code. - -After attempting to produce an more idiomatic identifier, this strategy will fall back to the defensive strategy, to replace any remaining invalid symbols. - -The second feature introduced as part of this proposal is a way to provide a string -> string map to fully override only specific OpenAPI names and provide their exact Swift identifiers. This is the ultimate escape hatch when both naming strategies fail to provide the desired result for the adopter. - -> Note: Because the idiomatic naming strategy can change capitalization, in rare cases it can still generate non-compiling code from a valid OpenAPI document. We recommend using the idiomatic naming strategy and, if it produces conflicts, address them using name overrides, or switch back to the defensive naming strategy. - -#### Examples - -To get a sense for the proposed change, check out the table below that compares the existing defensive strategy against the proposed idiomatic strategy on a set of examples: - -| OpenAPI name | Defensive | Idiomatic (capitalized) | Idiomatic (non-capitalized) | -| ------------ | --------- | ------------------------ | ---------------------------- | -| `foo` | `foo` | `Foo` | `foo` | -| `Hello world` | `Hello_space_world` | `HelloWorld` | `helloWorld` | -| `My_URL_value` | `My_URL_value` | `MyURLValue` | `myURLValue` | -| `Retry-After` | `Retry_hyphen_After` | `RetryAfter` | `retryAfter` | -| `NOT_AVAILABLE` | `NOT_AVAILABLE` | `NotAvailable` | `notAvailable` | -| `version 2.0` | `version_space_2_period_0` | `Version2_0` | `version2_0` | -| `naïve café` | `naïve_space_café` | `NaïveCafé` | `naïveCafé` | -| `__user` | `__user` | `__User` | `__user` | -| `order#123` | `order_num_123` | `order_num_123` | `order_num_123` | - -Notice that in the last example, since the OpenAPI name contains the pound (`#`) character, the idiomatic naming strategy falls back to the defensive naming strategy. In all the other cases, however, the resulting names are more idiomatic Swift identifiers. - -> Tip: For more examples, check out the updated [test suite](https://github.com/czechboy0/swift-openapi-generator/blob/hd-naming-strategy-optimistic/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift). - -### Detailed design - -This section goes into detail of the [draft implementation][pr] that you can already check out and try to run on your OpenAPI document. - -> Note: To enable it, you'll need to add `namingStrategy: idiomatic` to your `openapi-generator-config.yaml` file. - -#### Naming logic - -The idiomatic naming strategy (check out the current code [here][impl], look for the method `safeForSwiftCode_idiomatic`) is built around the decision to _only_ optimize for names that include the following: - -- letters -- numbers -- periods (`.`, ASCII: `0x2e`) -- dashes (`-`, ASCII: `0x2d`) -- underscores (`_`, ASCII: `0x5f`) -- spaces (` `, ASCII: `0x20`) - -> Note: We let [`Swift.String.isLetter`](https://developer.apple.com/documentation/swift/character/isletter) decide whether a character is a letter, which has the advantage of including letters in the non-ASCII range. Swift identifiers also support a [wide range](https://docs.swift.org/swift-book/documentation/the-swift-programming-language/lexicalstructure/#Identifiers) of alphanumeric characters. - -If the OpenAPI name includes any _other_ characters, the idiomatic naming strategy _falls back_ to the defensive naming strategy for that input string only. - -There's a second special case for handling all uppercased names, such as `NOT_AVAILABLE` - if this situation is detected, the idiomatic naming strategy turns it into `NotAvailable` for types and `notAvailable` for members. - -The best way to understand the detailed logic is to check out the [code][impl], feel free to leave comments on the pull request. - -#### Naming strategy configuration - -Since Swift OpenAPI Generator is on a stable 1.x version, we cannot change the naming strategy for everyone, as it would be considered an API break. So this new naming strategy is fully opt-in using a new configuration key called `namingStrategy`, with the following allowed values: - -- `defensive`: the existing naming strategy introduced in 0.2.0 -- `idiomatic`: the new naming strategy proposed here -- not specified: defaults to `defensive` for backwards compatibility - -Enabling this feature in the configuration file would look like this: - -```yaml -namingStrategy: idiomatic -``` - -#### Name overrides - -While the new naming strategy produces much improved Swift names, there are still cases when the adopter knows better how they'd like a specific OpenAPI name be translated to a Swift identifier. - -A good examples are the `+1` and `-1` properties in the GitHub OpenAPI document: using both strategies, the names would be `_plus_1` and `_hyphen_1`, respectively. While such names aren't too confusing, the adopter might want to customize them to, for example: `thumbsUp` and `thumbsDown`. - -Enabling this feature in the configuration file would look like this: - -```yaml -nameOverrides: - '+1': 'thumbsUp' - '-1': 'thumbsDown' -``` - -### API stability - -Both the new naming strategy and name overrides are purely additive, and require the adopter to explicitly opt-in. - -### Future directions - -With this proposal, we plan to abandon the ["naming extensions" idea][issuePlugin], as we consider the solution in this proposal to solve the name conversion problem for Swift OpenAPI Generator 1.x for all use cases. - -### Alternatives considered - -- ["Naming extensions"][issuePlugin], however that'd require the community to build and maintain custom naming strategies, and it was not clear that this feature would be possible in SwiftPM using only current features. -- Not changing anything, this was the status quo since 0.2.0, but adopters have made it clear that there is room to improve the naming strategy through the several filed issues linked at the top of the proposal, so we feel that some action here is justified. - -[soar0001]: https://swiftpackageindex.com/apple/swift-openapi-generator/documentation/swift-openapi-generator/soar-0001 -[issue1]: https://github.com/apple/swift-openapi-generator/issues/107 -[issue2]: https://github.com/apple/swift-openapi-generator/issues/503 -[issue3]: https://github.com/apple/swift-openapi-generator/issues/244 -[issue4]: https://github.com/apple/swift-openapi-generator/issues/405 -[issuePlugin]: https://github.com/apple/swift-openapi-generator/issues/112 -[pr]: https://github.com/apple/swift-openapi-generator/pull/679 -[impl]: https://github.com/czechboy0/swift-openapi-generator/blob/hd-naming-strategy-optimistic/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift From 7cbca175461bb615e55ed4496823af4394a49141 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Wed, 27 Nov 2024 13:25:30 +0100 Subject: [PATCH 08/31] Update Petstore fixtures to use idiomatic naming --- .../FileBasedReferenceTests.swift | 13 +- .../ReferenceSources/Petstore/Client.swift | 36 ++--- .../ReferenceSources/Petstore/Server.swift | 42 ++--- .../ReferenceSources/Petstore/Types.swift | 146 +++++++++--------- Tests/PetstoreConsumerTests/Common.swift | 2 +- Tests/PetstoreConsumerTests/TestClient.swift | 28 ++-- .../Test_Playground.swift | 4 +- Tests/PetstoreConsumerTests/Test_Types.swift | 10 +- 8 files changed, 146 insertions(+), 135 deletions(-) diff --git a/Tests/OpenAPIGeneratorReferenceTests/FileBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/FileBasedReferenceTests.swift index 75b8be78..42f18a09 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: .optimistic, + 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..913a6872 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", @@ -103,7 +103,7 @@ public struct Client: APIProtocol { deserializer: { response, responseBody in switch response.status.code { case 200: - let headers: Operations.listPets.Output.Ok.Headers = .init( + let headers: Operations.ListPets.Output.Ok.Headers = .init( My_hyphen_Response_hyphen_UUID: try converter.getRequiredHeaderFieldAsURI( in: response.headerFields, name: "My-Response-UUID", @@ -116,7 +116,7 @@ public struct Client: APIProtocol { ) ) 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", @@ -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(X_hyphen_Extra_hyphen_Arguments: 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: [ @@ -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/", diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift index 80d642b3..ba4df912 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,14 +178,14 @@ 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, @@ -195,7 +195,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { as: Components.Parameters.query_period_born_hyphen_since.self ) ) - let headers: Operations.listPets.Input.Headers = .init( + let headers: Operations.ListPets.Input.Headers = .init( My_hyphen_Request_hyphen_UUID: try converter.getOptionalHeaderFieldAsURI( in: request.headerFields, name: "My-Request-UUID", @@ -203,7 +203,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { ), accept: try converter.extractAcceptHeaderIfPresent(in: request.headerFields) ) - return Operations.listPets.Input( + return Operations.ListPets.Input( query: query, headers: headers ) @@ -273,12 +273,12 @@ 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( + let headers: Operations.CreatePet.Input.Headers = .init( X_hyphen_Extra_hyphen_Arguments: try converter.getOptionalHeaderFieldAsJSON( in: request.headerFields, name: "X-Extra-Arguments", @@ -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 ) @@ -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 { diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift index 47b0390d..76c061da 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift @@ -18,26 +18,26 @@ 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}`. @@ -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,23 +91,23 @@ 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. /// @@ -1844,7 +1844,7 @@ public enum Operations { case _empty = "" } /// - Remark: Generated from `#/paths/pets/GET/query/habitat`. - public var habitat: Operations.listPets.Input.Query.habitatPayload? + 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" @@ -1852,9 +1852,9 @@ public enum Operations { 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`. @@ -1868,8 +1868,8 @@ 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, + habitat: Operations.ListPets.Input.Query.habitatPayload? = nil, + feeds: Operations.ListPets.Input.Query.feedsPayload? = nil, since: Components.Parameters.query_period_born_hyphen_since? = nil ) { self.limit = limit @@ -1878,14 +1878,14 @@ 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 accept: [OpenAPIRuntime.AcceptHeaderContentType] /// Creates a new `Headers`. /// /// - Parameters: @@ -1893,21 +1893,21 @@ public enum Operations { /// - accept: public init( My_hyphen_Request_hyphen_UUID: Swift.String? = nil, - accept: [OpenAPIRuntime.AcceptHeaderContentType] = .defaultValues() + accept: [OpenAPIRuntime.AcceptHeaderContentType] = .defaultValues() ) { self.My_hyphen_Request_hyphen_UUID = My_hyphen_Request_hyphen_UUID 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 @@ -1939,7 +1939,7 @@ public enum Operations { } } /// 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`. @@ -1958,15 +1958,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 @@ -1977,12 +1977,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): @@ -2014,12 +2014,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 } } @@ -2028,12 +2028,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): @@ -2086,7 +2086,7 @@ public enum Operations { /// /// - 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 accept: [OpenAPIRuntime.AcceptHeaderContentType] /// Creates a new `Headers`. /// /// - Parameters: @@ -2094,27 +2094,27 @@ public enum Operations { /// - accept: public init( X_hyphen_Extra_hyphen_Arguments: Components.Schemas.CodeError? = nil, - accept: [OpenAPIRuntime.AcceptHeaderContentType] = .defaultValues() + accept: [OpenAPIRuntime.AcceptHeaderContentType] = .defaultValues() ) { self.X_hyphen_Extra_hyphen_Arguments = X_hyphen_Extra_hyphen_Arguments 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 @@ -2137,7 +2137,7 @@ public enum Operations { } } /// 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`. @@ -2156,15 +2156,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 @@ -2175,12 +2175,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): @@ -2259,12 +2259,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 } } @@ -2278,7 +2278,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`. @@ -2291,7 +2291,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): @@ -2317,21 +2317,21 @@ public enum Operations { 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 } } @@ -2398,12 +2398,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 } } @@ -2412,12 +2412,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): @@ -2487,12 +2487,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 } } @@ -2506,7 +2506,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`. @@ -2519,7 +2519,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): @@ -2556,7 +2556,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`. @@ -2569,7 +2569,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): diff --git a/Tests/PetstoreConsumerTests/Common.swift b/Tests/PetstoreConsumerTests/Common.swift index e635c7ad..a750448d 100644 --- a/Tests/PetstoreConsumerTests/Common.swift +++ b/Tests/PetstoreConsumerTests/Common.swift @@ -14,7 +14,7 @@ import XCTest import HTTPTypes -extension Operations.listPets.Output { +extension Operations.ListPets.Output { static var success: Self { .ok(.init(headers: .init(My_hyphen_Response_hyphen_UUID: "abcd"), body: .json([]))) } } diff --git a/Tests/PetstoreConsumerTests/TestClient.swift b/Tests/PetstoreConsumerTests/TestClient.swift index b08e72b5..f4cd1df1 100644 --- a/Tests/PetstoreConsumerTests/TestClient.swift +++ b/Tests/PetstoreConsumerTests/TestClient.swift @@ -15,47 +15,47 @@ 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) } diff --git a/Tests/PetstoreConsumerTests/Test_Playground.swift b/Tests/PetstoreConsumerTests/Test_Playground.swift index a20d64e5..b655547a 100644 --- a/Tests/PetstoreConsumerTests/Test_Playground.swift +++ b/Tests/PetstoreConsumerTests/Test_Playground.swift @@ -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_Types.swift b/Tests/PetstoreConsumerTests/Test_Types.swift index 7aaba0cc..c7293551 100644 --- a/Tests/PetstoreConsumerTests/Test_Types.swift +++ b/Tests/PetstoreConsumerTests/Test_Types.swift @@ -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 From c41d3de881d1305f4fd500f951fc4a033d5a9466 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Wed, 27 Nov 2024 14:12:25 +0100 Subject: [PATCH 09/31] Updated Petstore tests --- .../ReferenceSources/Petstore/Client.swift | 50 +-- .../ReferenceSources/Petstore/Server.swift | 60 +-- .../ReferenceSources/Petstore/Types.swift | 348 +++++++++--------- .../SnippetBasedReferenceTests.swift | 192 +++++----- Tests/PetstoreConsumerTests/Common.swift | 2 +- Tests/PetstoreConsumerTests/TestClient.swift | 28 +- Tests/PetstoreConsumerTests/Test_Client.swift | 22 +- .../Test_Playground.swift | 2 +- Tests/PetstoreConsumerTests/Test_Server.swift | 22 +- Tests/PetstoreConsumerTests/Test_Types.swift | 4 +- 10 files changed, 365 insertions(+), 365 deletions(-) diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift index 913a6872..25f1f53d 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Client.swift @@ -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, @@ -104,12 +104,12 @@ public struct Client: APIProtocol { switch response.status.code { case 200: let headers: Operations.ListPets.Output.Ok.Headers = .init( - My_hyphen_Response_hyphen_UUID: try converter.getRequiredHeaderFieldAsURI( + 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 @@ -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,7 +209,7 @@ 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 @@ -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) @@ -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 ba4df912..c7a02fcf 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Server.swift @@ -178,25 +178,25 @@ 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( + myRequestUUID: try converter.getOptionalHeaderFieldAsURI( in: request.headerFields, name: "My-Request-UUID", as: Swift.String.self @@ -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 { @@ -279,7 +279,7 @@ fileprivate extension UniversalServer where APIHandler: APIProtocol { }, deserializer: { request, requestBody, metadata in let headers: Operations.CreatePet.Input.Headers = .init( - X_hyphen_Extra_hyphen_Arguments: try converter.getOptionalHeaderFieldAsJSON( + xExtraArguments: try converter.getOptionalHeaderFieldAsJSON( in: request.headerFields, name: "X-Extra-Arguments", as: Components.Schemas.CodeError.self @@ -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 { @@ -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 76c061da..641c1adb 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift @@ -42,18 +42,18 @@ public protocol APIProtocol: Sendable { /// /// - 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. @@ -114,11 +114,11 @@ extension APIProtocol { /// - 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`. @@ -1090,10 +1090,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: @@ -1105,7 +1105,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 { storage.value.parent } @@ -1120,7 +1120,7 @@ public enum Components { /// - parent: public init( name: Swift.String, - parent: Components.Schemas.RecursivePetNested.parentPayload? = nil + parent: Components.Schemas.RecursivePetNested.ParentPayload? = nil ) { storage = .init(value: .init( name: name, @@ -1143,10 +1143,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: @@ -1158,10 +1158,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 @@ -1502,18 +1502,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`. @@ -1522,7 +1522,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 @@ -1544,57 +1544,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: @@ -1605,20 +1605,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: @@ -1626,11 +1626,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. @@ -1641,13 +1641,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 @@ -1655,10 +1655,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: @@ -1670,12 +1670,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): @@ -1703,52 +1703,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: @@ -1759,20 +1759,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: @@ -1780,16 +1780,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): @@ -1827,7 +1827,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`. @@ -1837,28 +1837,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: @@ -1868,9 +1868,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 @@ -1884,18 +1884,18 @@ public enum Operations { /// Request identifier /// /// - Remark: Generated from `#/paths/pets/GET/header/My-Request-UUID`. - public var My_hyphen_Request_hyphen_UUID: Swift.String? + 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, + 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 } } @@ -1920,22 +1920,22 @@ 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 @@ -2077,7 +2077,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`. @@ -2085,18 +2085,18 @@ 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 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, + 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 } } @@ -2127,13 +2127,13 @@ 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 @@ -2251,7 +2251,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`. @@ -2312,7 +2312,7 @@ 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`. @@ -2475,7 +2475,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`. @@ -2540,7 +2540,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`. @@ -2592,7 +2592,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`. @@ -2609,19 +2609,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`. /// @@ -2630,8 +2630,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 @@ -2649,7 +2649,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`. @@ -2662,7 +2662,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): @@ -2679,10 +2679,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: @@ -2694,12 +2694,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): @@ -2709,12 +2709,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 } } @@ -2723,12 +2723,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): @@ -2776,7 +2776,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`. @@ -2784,34 +2784,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: @@ -2819,9 +2819,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 @@ -2848,12 +2848,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 } } @@ -2862,12 +2862,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): @@ -2899,12 +2899,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 } } @@ -2913,12 +2913,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): @@ -2950,12 +2950,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 } } @@ -2964,12 +2964,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): @@ -3027,26 +3027,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 } } @@ -3107,7 +3107,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 @@ -3129,7 +3129,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`. @@ -3142,7 +3142,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 05e9c70e..4582431e 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift @@ -1301,7 +1301,7 @@ final class SnippetBasedReferenceTests: XCTestCase { public enum Schemas { @frozen public enum MyEnum: String, Codable, Hashable, Sendable, CaseIterable { case one = "one" - case _empty = "" + case _empty_ = "" case _dollar_tart = "$tart" case _public = "public" } @@ -2033,9 +2033,9 @@ final class SnippetBasedReferenceTests: XCTestCase { public enum Responses { public struct BadRequest: Sendable, Hashable { public struct Headers: Sendable, Hashable { - public var X_hyphen_Reason: Swift.String? - public init(X_hyphen_Reason: Swift.String? = nil) { - self.X_hyphen_Reason = X_hyphen_Reason + public var xReason: Swift.String? + public init(xReason: Swift.String? = nil) { + self.xReason = xReason } } public var headers: Components.Responses.BadRequest.Headers @@ -2065,12 +2065,12 @@ final class SnippetBasedReferenceTests: XCTestCase { public enum Responses { public struct BadRequest: Sendable, Hashable { public struct Headers: Sendable, Hashable { - @frozen public enum X_hyphen_ReasonPayload: String, Codable, Hashable, Sendable, CaseIterable { + @frozen public enum xReasonPayload: String, Codable, Hashable, Sendable, CaseIterable { case badLuck = "badLuck" } - public var X_hyphen_Reason: Components.Responses.BadRequest.Headers.X_hyphen_ReasonPayload? - public init(X_hyphen_Reason: Components.Responses.BadRequest.Headers.X_hyphen_ReasonPayload? = nil) { - self.X_hyphen_Reason = X_hyphen_Reason + public var xReason: Components.Responses.BadRequest.Headers.xReasonPayload? + public init(xReason: Components.Responses.BadRequest.Headers.xReasonPayload? = nil) { + self.xReason = xReason } } public var headers: Components.Responses.BadRequest.Headers @@ -2099,9 +2099,9 @@ final class SnippetBasedReferenceTests: XCTestCase { public enum Responses { public struct BadRequest: Sendable, Hashable { public struct Headers: Sendable, Hashable { - public var X_hyphen_Reason: Swift.String - public init(X_hyphen_Reason: Swift.String) { - self.X_hyphen_Reason = X_hyphen_Reason + public var xReason: Swift.String + public init(xReason: Swift.String) { + self.xReason = xReason } } public var headers: Components.Responses.BadRequest.Headers @@ -2252,31 +2252,31 @@ final class SnippetBasedReferenceTests: XCTestCase { #""" public enum RequestBodies { @frozen public enum MultipartUploadTypedRequest: Sendable, Hashable { - @frozen public enum multipartFormPayload: Sendable, Hashable { - public struct logPayload: Sendable, Hashable { + @frozen public enum MultipartFormPayload: Sendable, Hashable { + public struct LogPayload: Sendable, Hashable { public struct Headers: Sendable, Hashable { - @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" } - public var x_hyphen_log_hyphen_type: Components.RequestBodies.MultipartUploadTypedRequest.multipartFormPayload.logPayload.Headers.x_hyphen_log_hyphen_typePayload? - 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 + public var xLogType: Components.RequestBodies.MultipartUploadTypedRequest.MultipartFormPayload.LogPayload.Headers.XLogTypePayload? + public init(xLogType: Components.RequestBodies.MultipartUploadTypedRequest.MultipartFormPayload.LogPayload.Headers.XLogTypePayload? = nil) { + self.xLogType = xLogType } } - public var headers: Components.RequestBodies.MultipartUploadTypedRequest.multipartFormPayload.logPayload.Headers + public var headers: Components.RequestBodies.MultipartUploadTypedRequest.MultipartFormPayload.LogPayload.Headers public var body: OpenAPIRuntime.HTTPBody 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) - public struct metadataPayload: Sendable, Hashable { - public struct bodyPayload: Codable, Hashable, Sendable { + case log(OpenAPIRuntime.MultipartPart) + public struct MetadataPayload: Sendable, Hashable { + public struct BodyPayload: Codable, Hashable, Sendable { public var createdAt: Foundation.Date public init(createdAt: Foundation.Date) { self.createdAt = createdAt @@ -2285,22 +2285,22 @@ final class SnippetBasedReferenceTests: XCTestCase { case createdAt } } - public var body: Components.RequestBodies.MultipartUploadTypedRequest.multipartFormPayload.metadataPayload.bodyPayload - public init(body: Components.RequestBodies.MultipartUploadTypedRequest.multipartFormPayload.metadataPayload.bodyPayload) { + public var body: Components.RequestBodies.MultipartUploadTypedRequest.MultipartFormPayload.MetadataPayload.BodyPayload + public init(body: Components.RequestBodies.MultipartUploadTypedRequest.MultipartFormPayload.MetadataPayload.BodyPayload) { self.body = body } } - case metadata(OpenAPIRuntime.MultipartPart) - public struct keywordPayload: Sendable, Hashable { + case metadata(OpenAPIRuntime.MultipartPart) + public struct KeywordPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody public init(body: OpenAPIRuntime.HTTPBody) { self.body = body } } - case keyword(OpenAPIRuntime.MultipartPart) + case keyword(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } } """# @@ -3129,17 +3129,17 @@ final class SnippetBasedReferenceTests: XCTestCase { requestBodies: """ public enum RequestBodies { @frozen public enum MultipartRequest: Sendable, Hashable { - @frozen public enum multipartFormPayload: Sendable, Hashable { - public struct logPayload: Sendable, Hashable { + @frozen public enum MultipartFormPayload: Sendable, Hashable { + public struct LogPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody public init(body: OpenAPIRuntime.HTTPBody) { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } } """, @@ -3206,7 +3206,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -3274,17 +3274,17 @@ final class SnippetBasedReferenceTests: XCTestCase { types: """ public struct Input: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum multipartFormPayload: Sendable, Hashable { - public struct logPayload: Sendable, Hashable { + @frozen public enum MultipartFormPayload: Sendable, Hashable { + public struct LogPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody public init(body: OpenAPIRuntime.HTTPBody) { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } public var body: Operations.post_sol_foo.Input.Body public init(body: Operations.post_sol_foo.Input.Body) { @@ -3355,7 +3355,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -3428,17 +3428,17 @@ final class SnippetBasedReferenceTests: XCTestCase { types: """ public struct Input: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum multipartFormPayload: Sendable, Hashable { + @frozen public enum MultipartFormPayload: Sendable, Hashable { public struct infoPayload: Sendable, Hashable { public var body: Components.Schemas.Info public init(body: Components.Schemas.Info) { self.body = body } } - case info(OpenAPIRuntime.MultipartPart) + case info(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } public var body: Operations.post_sol_foo.Input.Body public init(body: Operations.post_sol_foo.Input.Body) { @@ -3509,7 +3509,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -3595,13 +3595,13 @@ final class SnippetBasedReferenceTests: XCTestCase { schemas: """ public enum Schemas { @frozen public enum Multipet: Sendable, Hashable { - public struct logPayload: Sendable, Hashable { + public struct LogPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody public init(body: OpenAPIRuntime.HTTPBody) { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } } @@ -3750,28 +3750,28 @@ final class SnippetBasedReferenceTests: XCTestCase { types: """ public struct Input: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum multipartFormPayload: Sendable, Hashable { - public struct logPayload: Sendable, Hashable { + @frozen public enum MultipartFormPayload: Sendable, Hashable { + public struct LogPayload: Sendable, Hashable { public struct Headers: Sendable, Hashable { - public var x_hyphen_log_hyphen_type: Swift.String? - public init(x_hyphen_log_hyphen_type: Swift.String? = nil) { - self.x_hyphen_log_hyphen_type = x_hyphen_log_hyphen_type + public var xLogType: Swift.String? + public init(xLogType: Swift.String? = nil) { + self.xLogType = xLogType } } - public var headers: Operations.post_sol_foo.Input.Body.multipartFormPayload.logPayload.Headers + public var headers: Operations.post_sol_foo.Input.Body.MultipartFormPayload.LogPayload.Headers public var body: OpenAPIRuntime.HTTPBody public init( - headers: Operations.post_sol_foo.Input.Body.multipartFormPayload.logPayload.Headers = .init(), + headers: Operations.post_sol_foo.Input.Body.MultipartFormPayload.LogPayload.Headers = .init(), body: OpenAPIRuntime.HTTPBody ) { self.headers = headers self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } public var body: Operations.post_sol_foo.Input.Body public init(body: Operations.post_sol_foo.Input.Body) { @@ -3782,13 +3782,13 @@ final class SnippetBasedReferenceTests: XCTestCase { schemas: """ public enum Schemas { @frozen public enum Multipet: Sendable, Hashable { - public struct logPayload: Sendable, Hashable { + public struct LogPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody public init(body: OpenAPIRuntime.HTTPBody) { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } } @@ -3826,7 +3826,7 @@ final class SnippetBasedReferenceTests: XCTestCase { 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, @@ -3861,7 +3861,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -3879,7 +3879,7 @@ final class SnippetBasedReferenceTests: XCTestCase { let (name, filename) = try converter.extractContentDispositionNameAndFilename(in: headerFields) switch name { case "log": - let headers: Operations.post_sol_foo.Input.Body.multipartFormPayload.logPayload.Headers = .init(x_hyphen_log_hyphen_type: try converter.getOptionalHeaderFieldAsURI( + let headers: Operations.post_sol_foo.Input.Body.MultipartFormPayload.LogPayload.Headers = .init(xLogType: try converter.getOptionalHeaderFieldAsURI( in: headerFields, name: "x-log-type", as: Swift.String.self @@ -3932,10 +3932,10 @@ final class SnippetBasedReferenceTests: XCTestCase { types: """ public struct Input: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum multipartFormPayload: Sendable, Hashable { + @frozen public enum MultipartFormPayload: Sendable, Hashable { case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } public var body: Operations.post_sol_foo.Input.Body public init(body: Operations.post_sol_foo.Input.Body) { @@ -3990,7 +3990,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -4038,10 +4038,10 @@ final class SnippetBasedReferenceTests: XCTestCase { types: """ public struct Input: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum multipartFormPayload: Sendable, Hashable { + @frozen public enum MultipartFormPayload: Sendable, Hashable { case other(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } public var body: Operations.post_sol_foo.Input.Body public init(body: Operations.post_sol_foo.Input.Body) { @@ -4096,7 +4096,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -4149,16 +4149,16 @@ final class SnippetBasedReferenceTests: XCTestCase { types: """ public struct Input: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum multipartFormPayload: Sendable, Hashable { - public struct logPayload: Sendable, Hashable { + @frozen public enum MultipartFormPayload: Sendable, Hashable { + public struct LogPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody public init(body: OpenAPIRuntime.HTTPBody) { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } public var body: Operations.post_sol_foo.Input.Body public init(body: Operations.post_sol_foo.Input.Body) { @@ -4227,7 +4227,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -4302,14 +4302,14 @@ final class SnippetBasedReferenceTests: XCTestCase { types: """ public struct Input: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum multipartFormPayload: Sendable, Hashable { - public struct logPayload: Sendable, Hashable { + @frozen public enum MultipartFormPayload: Sendable, Hashable { + public struct LogPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody public init(body: OpenAPIRuntime.HTTPBody) { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) public struct additionalPropertiesPayload: Codable, Hashable, Sendable { public var foo: Swift.String? public init(foo: Swift.String? = nil) { @@ -4319,9 +4319,9 @@ final class SnippetBasedReferenceTests: XCTestCase { case foo } } - case additionalProperties(OpenAPIRuntime.MultipartDynamicallyNamedPart) + case additionalProperties(OpenAPIRuntime.MultipartDynamicallyNamedPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } public var body: Operations.post_sol_foo.Input.Body public init(body: Operations.post_sol_foo.Input.Body) { @@ -4404,7 +4404,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -4443,7 +4443,7 @@ final class SnippetBasedReferenceTests: XCTestCase { matches: "application/json" ) let body = try await converter.getRequiredRequestBodyAsJSON( - Operations.post_sol_foo.Input.Body.multipartFormPayload.additionalPropertiesPayload.self, + Operations.post_sol_foo.Input.Body.MultipartFormPayload.additionalPropertiesPayload.self, from: part.body, transforming: { $0 @@ -4499,17 +4499,17 @@ final class SnippetBasedReferenceTests: XCTestCase { types: """ public struct Input: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum multipartFormPayload: Sendable, Hashable { - public struct logPayload: Sendable, Hashable { + @frozen public enum MultipartFormPayload: Sendable, Hashable { + public struct LogPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody public init(body: OpenAPIRuntime.HTTPBody) { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) case additionalProperties(OpenAPIRuntime.MultipartDynamicallyNamedPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } public var body: Operations.post_sol_foo.Input.Body public init(body: Operations.post_sol_foo.Input.Body) { @@ -4592,7 +4592,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -4674,10 +4674,10 @@ final class SnippetBasedReferenceTests: XCTestCase { types: """ public struct Input: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum multipartFormPayload: Sendable, Hashable { + @frozen public enum MultipartFormPayload: Sendable, Hashable { case additionalProperties(OpenAPIRuntime.MultipartDynamicallyNamedPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } public var body: Operations.post_sol_foo.Input.Body public init(body: Operations.post_sol_foo.Input.Body) { @@ -4744,7 +4744,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -4832,18 +4832,18 @@ final class SnippetBasedReferenceTests: XCTestCase { public enum Responses { public struct MultipartResponse: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum multipartFormPayload: Sendable, Hashable { - public struct logPayload: Sendable, Hashable { + @frozen public enum MultipartFormPayload: Sendable, Hashable { + public struct LogPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody public init(body: OpenAPIRuntime.HTTPBody) { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) - public var multipartForm: OpenAPIRuntime.MultipartBody { + case multipartForm(OpenAPIRuntime.MultipartBody) + public var multipartForm: OpenAPIRuntime.MultipartBody { get throws { switch self { case let .multipartForm(body): @@ -4927,7 +4927,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getResponseBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: responseBody, transforming: { value in .multipartForm(value) @@ -5003,18 +5003,18 @@ final class SnippetBasedReferenceTests: XCTestCase { @frozen public enum Output: Sendable, Hashable { public struct Ok: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum multipartFormPayload: Sendable, Hashable { - public struct logPayload: Sendable, Hashable { + @frozen public enum MultipartFormPayload: Sendable, Hashable { + public struct LogPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody public init(body: OpenAPIRuntime.HTTPBody) { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) - public var multipartForm: OpenAPIRuntime.MultipartBody { + case multipartForm(OpenAPIRuntime.MultipartBody) + public var multipartForm: OpenAPIRuntime.MultipartBody { get throws { switch self { case let .multipartForm(body): @@ -5113,7 +5113,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getResponseBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: responseBody, transforming: { value in .multipartForm(value) diff --git a/Tests/PetstoreConsumerTests/Common.swift b/Tests/PetstoreConsumerTests/Common.swift index a750448d..0fa07dc9 100644 --- a/Tests/PetstoreConsumerTests/Common.swift +++ b/Tests/PetstoreConsumerTests/Common.swift @@ -15,7 +15,7 @@ import XCTest import HTTPTypes extension Operations.ListPets.Output { - static var success: Self { .ok(.init(headers: .init(My_hyphen_Response_hyphen_UUID: "abcd"), body: .json([]))) } + 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 f4cd1df1..140d108d 100644 --- a/Tests/PetstoreConsumerTests/TestClient.swift +++ b/Tests/PetstoreConsumerTests/TestClient.swift @@ -60,36 +60,36 @@ struct TestClient: APIProtocol { 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 b655547a..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 diff --git a/Tests/PetstoreConsumerTests/Test_Server.swift b/Tests/PetstoreConsumerTests/Test_Server.swift index 5f6be366..156dad0d 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,12 +101,12 @@ 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)), + headers: .init(xExtraArguments: .init(code: 1)), body: .json(.init(id: 1, name: "Fluffz")) ) ) @@ -151,7 +151,7 @@ final class Test_Server: XCTestCase { client = .init(createPetBlock: { input in .clientError( statusCode: 400, - .init(headers: .init(X_hyphen_Reason: "bad luck"), body: .json(.init(code: 1))) + .init(headers: .init(xReason: "bad luck"), body: .json(.init(code: 1))) ) }) let (response, responseBody) = try await server.createPet( @@ -235,7 +235,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, @@ -246,7 +246,7 @@ final class Test_Server: XCTestCase { ) return .created( .init( - headers: .init(X_hyphen_Extra_hyphen_Arguments: .init(code: 1)), + headers: .init(xExtraArguments: .init(code: 1)), body: .json(.init(id: 1, name: "Fluffz")) ) ) @@ -771,11 +771,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 +802,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 +814,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 c7293551..e75fb12d 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"}"# From bda0eb3ea466bf5fe27d069c87b6d4843e55c92d Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Wed, 27 Nov 2024 14:54:14 +0100 Subject: [PATCH 10/31] Updating tests --- Sources/_OpenAPIGeneratorCore/Config.swift | 2 +- .../CommonTranslations/SwiftSafeNames.swift | 2 +- .../translateAllAnyOneOf.swift | 2 +- .../CommonTypes/DiscriminatorExtensions.swift | 4 +- .../Translator/FileTranslator.swift | 4 +- .../FilterCommand.swift | 2 +- .../Extensions/Test_SwiftSafeNames.swift | 18 ++- .../FileBasedReferenceTests.swift | 2 +- .../SnippetBasedReferenceTests.swift | 125 ++++++++++++------ 9 files changed, 109 insertions(+), 52 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Config.swift b/Sources/_OpenAPIGeneratorCore/Config.swift index cabba9dc..897c1d47 100644 --- a/Sources/_OpenAPIGeneratorCore/Config.swift +++ b/Sources/_OpenAPIGeneratorCore/Config.swift @@ -14,7 +14,7 @@ public enum NamingStrategy: String, Sendable, Codable, Equatable { case defensive - case optimistic + case idiomatic } /// A structure that contains configuration options for a single execution diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift index b12a64b1..8c137251 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift @@ -82,7 +82,7 @@ extension String { /// /// If the string contains any illegal characters, falls back to the behavior /// matching `safeForSwiftCode_defensive`. - func safeForSwiftCode_optimistic(options: SwiftNameOptions) -> String { + func safeForSwiftCode_idiomatic(options: SwiftNameOptions) -> String { let capitalize = options.contains(.capitalize) if isEmpty { return capitalize ? "_Empty_" : "_empty_" diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift index e77493ee..92d6745f 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 { diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/DiscriminatorExtensions.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/DiscriminatorExtensions.swift index 922542d4..3d0bb3a3 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], .capitalize) + func safeSwiftNameForOneOfMappedCase(_ type: OneOfMappedType) -> String { + context.asSwiftSafeName(type.rawNames[0], .none) } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift b/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift index 15bada2a..e1a33cae 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift @@ -52,8 +52,8 @@ extension FileTranslator { switch config.namingStrategy { case .defensive, .none: asSwiftSafeName = { $0.safeForSwiftCode_defensive(options: $1) } - case .optimistic: - asSwiftSafeName = { $0.safeForSwiftCode_optimistic(options: $1) } + case .idiomatic: + asSwiftSafeName = { $0.safeForSwiftCode_idiomatic(options: $1) } } let overrides = config.nameOverrides ?? [:] return TranslatorContext( diff --git a/Sources/swift-openapi-generator/FilterCommand.swift b/Sources/swift-openapi-generator/FilterCommand.swift index 6066654b..850de174 100644 --- a/Sources/swift-openapi-generator/FilterCommand.swift +++ b/Sources/swift-openapi-generator/FilterCommand.swift @@ -91,7 +91,7 @@ private let sampleConfig = _UserConfig( paths: ["/greeting"], schemas: ["Greeting"] ), - namingStrategy: .optimistic, + namingStrategy: .idiomatic, nameOverrides: [ "SPECIAL_NAME": "SpecialName" ] diff --git a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift index 399f7493..1ed61153 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift @@ -16,7 +16,7 @@ import XCTest final class Test_SwiftSafeNames: Test_Core { func testAsSwiftSafeName() { - let cases: [(original: String, defensive: String, optimisticUpper: String, optimisticLower: String)] = [ + let cases: [(original: String, defensive: String, idiomaticUpper: String, idiomaticLower: String)] = [ // Simple ("foo", "foo", "Foo", "foo"), @@ -40,6 +40,14 @@ final class Test_SwiftSafeNames: Test_Core { ("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" + ), + // Technical strings ("file/path/to/resource", "file_sol_path_sol_to_sol_resource", "file_sol_path_sol_to_sol_resource", "file_sol_path_sol_to_sol_resource"), ("user.name@domain.com", "user_period_name_commat_domain_period_com", "user_period_name_commat_domain_period_com", "user_period_name_commat_domain_period_com"), @@ -118,13 +126,13 @@ final class Test_SwiftSafeNames: Test_Core { } do { let translator = makeTranslator( - namingStrategy: .optimistic, + namingStrategy: .idiomatic, nameOverrides: ["MEGA": "m_e_g_a"] ) let asSwiftSafeName: (String, SwiftNameOptions) -> String = translator.context.asSwiftSafeName - for (input, _, optimisticUpper, optimisticLower) in cases { - XCTAssertEqual(asSwiftSafeName(input, .capitalize), optimisticUpper, "Optimistic upper, input: \(input)") - XCTAssertEqual(asSwiftSafeName(input, .none), optimisticLower, "Optimistic lower, input: \(input)") + for (input, _, idiomaticUpper, idiomaticLower) in cases { + XCTAssertEqual(asSwiftSafeName(input, .capitalize), idiomaticUpper, "Idiomatic upper, input: \(input)") + XCTAssertEqual(asSwiftSafeName(input, .none), idiomaticLower, "Idiomatic lower, input: \(input)") } } } diff --git a/Tests/OpenAPIGeneratorReferenceTests/FileBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/FileBasedReferenceTests.swift index 42f18a09..f45902c3 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/FileBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/FileBasedReferenceTests.swift @@ -136,7 +136,7 @@ final class FileBasedReferenceTests: XCTestCase { mode: mode, additionalImports: [], featureFlags: featureFlags, - namingStrategy: .optimistic, + namingStrategy: .idiomatic, nameOverrides: [:], referenceOutputDirectory: "ReferenceSources/\(project.fixtureCodeDirectoryName)" ), diff --git a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift index 4582431e..c8898255 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift @@ -812,20 +812,20 @@ final class SnippetBasedReferenceTests: XCTestCase { try self.assertSchemasTranslation( """ schemas: - A: + Aaa: type: object properties: which: type: string - B: + Bbb: type: object properties: which: type: string MyOneOf: oneOf: - - $ref: '#/components/schemas/A' - - $ref: '#/components/schemas/B' + - $ref: '#/components/schemas/Aaa' + - $ref: '#/components/schemas/Bbb' - type: string - type: object properties: @@ -836,7 +836,7 @@ final class SnippetBasedReferenceTests: XCTestCase { """, """ public enum Schemas { - public struct A: Codable, Hashable, Sendable { + public struct Aaa: Codable, Hashable, Sendable { public var which: Swift.String? public init(which: Swift.String? = nil) { self.which = which @@ -845,7 +845,7 @@ final class SnippetBasedReferenceTests: XCTestCase { case which } } - public struct B: Codable, Hashable, Sendable { + public struct Bbb: Codable, Hashable, Sendable { public var which: Swift.String? public init(which: Swift.String? = nil) { self.which = which @@ -855,8 +855,8 @@ final class SnippetBasedReferenceTests: XCTestCase { } } @frozen public enum MyOneOf: Codable, Hashable, Sendable { - case A(Components.Schemas.A) - case B(Components.Schemas.B) + case aaa(Components.Schemas.Aaa) + case bbb(Components.Schemas.Bbb) public enum CodingKeys: String, CodingKey { case which } @@ -867,10 +867,10 @@ final class SnippetBasedReferenceTests: XCTestCase { forKey: .which ) switch discriminator { - case "A", "#/components/schemas/A": - self = .A(try .init(from: decoder)) - case "B", "#/components/schemas/B": - self = .B(try .init(from: decoder)) + case "Aaa", "#/components/schemas/Aaa": + self = .aaa(try .init(from: decoder)) + case "Bbb", "#/components/schemas/Bbb": + self = .bbb(try .init(from: decoder)) default: throw Swift.DecodingError.unknownOneOfDiscriminator( discriminatorKey: CodingKeys.which, @@ -881,9 +881,9 @@ final class SnippetBasedReferenceTests: XCTestCase { } public func encode(to encoder: any Encoder) throws { switch self { - case let .A(value): + case let .aaa(value): try value.encode(to: encoder) - case let .B(value): + case let .bbb(value): try value.encode(to: encoder) } } @@ -897,36 +897,36 @@ final class SnippetBasedReferenceTests: XCTestCase { try self.assertSchemasTranslation( """ schemas: - A: + Aaa: type: object properties: which: type: string - B: + Bbb: type: object properties: which: type: string - C: + Ccc: type: object properties: which: type: string MyOneOf: oneOf: - - $ref: '#/components/schemas/A' - - $ref: '#/components/schemas/B' - - $ref: '#/components/schemas/C' + - $ref: '#/components/schemas/Aaa' + - $ref: '#/components/schemas/Bbb' + - $ref: '#/components/schemas/Ccc' discriminator: propertyName: which mapping: - a: '#/components/schemas/A' - a2: '#/components/schemas/A' - b: '#/components/schemas/B' + a: '#/components/schemas/Aaa' + a2: '#/components/schemas/Aaa' + b: '#/components/schemas/Bbb' """, """ public enum Schemas { - public struct A: Codable, Hashable, Sendable { + public struct Aaa: Codable, Hashable, Sendable { public var which: Swift.String? public init(which: Swift.String? = nil) { self.which = which @@ -935,7 +935,7 @@ final class SnippetBasedReferenceTests: XCTestCase { case which } } - public struct B: Codable, Hashable, Sendable { + public struct Bbb: Codable, Hashable, Sendable { public var which: Swift.String? public init(which: Swift.String? = nil) { self.which = which @@ -944,7 +944,7 @@ final class SnippetBasedReferenceTests: XCTestCase { case which } } - public struct C: Codable, Hashable, Sendable { + public struct Ccc: Codable, Hashable, Sendable { public var which: Swift.String? public init(which: Swift.String? = nil) { self.which = which @@ -954,10 +954,10 @@ final class SnippetBasedReferenceTests: XCTestCase { } } @frozen public enum MyOneOf: Codable, Hashable, Sendable { - case a(Components.Schemas.A) - case a2(Components.Schemas.A) - case b(Components.Schemas.B) - case C(Components.Schemas.C) + case a(Components.Schemas.Aaa) + case a2(Components.Schemas.Aaa) + case b(Components.Schemas.Bbb) + case ccc(Components.Schemas.Ccc) public enum CodingKeys: String, CodingKey { case which } @@ -974,8 +974,8 @@ final class SnippetBasedReferenceTests: XCTestCase { self = .a2(try .init(from: decoder)) case "b": self = .b(try .init(from: decoder)) - case "C", "#/components/schemas/C": - self = .C(try .init(from: decoder)) + case "Ccc", "#/components/schemas/Ccc": + self = .ccc(try .init(from: decoder)) default: throw Swift.DecodingError.unknownOneOfDiscriminator( discriminatorKey: CodingKeys.which, @@ -992,7 +992,7 @@ final class SnippetBasedReferenceTests: XCTestCase { try value.encode(to: encoder) case let .b(value): try value.encode(to: encoder) - case let .C(value): + case let .ccc(value): try value.encode(to: encoder) } } @@ -2356,6 +2356,30 @@ final class SnippetBasedReferenceTests: XCTestCase { """ ) } + + func testSynthesizedOperationId() 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 getHealthNew(_ input: Operations.getHealthNew.Input) async throws -> Operations.getHealthNew.Output + } + """ + ) + } func testServerRegisterHandlers_oneOperation() throws { try self.assertServerRegisterHandlers( @@ -5519,7 +5543,12 @@ extension SnippetBasedReferenceTests { ) 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: .idiomatic, + featureFlags: featureFlags + ), diagnostics: XCTestDiagnosticCollector(test: self, ignoredDiagnosticMessages: ignoredDiagnosticMessages), components: components ) @@ -5531,7 +5560,12 @@ extension SnippetBasedReferenceTests { components: OpenAPI.Components = .noComponents ) throws -> TypesFileTranslator { TypesFileTranslator( - config: Config(mode: .types, access: accessModifier, featureFlags: featureFlags), + config: Config( + mode: .types, + access: accessModifier, + namingStrategy: .idiomatic, + featureFlags: featureFlags + ), diagnostics: XCTestDiagnosticCollector(test: self, ignoredDiagnosticMessages: ignoredDiagnosticMessages), components: components ) @@ -5545,17 +5579,32 @@ extension SnippetBasedReferenceTests { let collector = XCTestDiagnosticCollector(test: self, ignoredDiagnosticMessages: ignoredDiagnosticMessages) return ( TypesFileTranslator( - config: Config(mode: .types, access: .public, featureFlags: featureFlags), + config: Config( + mode: .types, + access: .public, + namingStrategy: .idiomatic, + featureFlags: featureFlags + ), diagnostics: collector, components: components ), ClientFileTranslator( - config: Config(mode: .client, access: .public, featureFlags: featureFlags), + config: Config( + mode: .client, + access: .public, + namingStrategy: .idiomatic, + featureFlags: featureFlags + ), diagnostics: collector, components: components ), ServerFileTranslator( - config: Config(mode: .server, access: .public, featureFlags: featureFlags), + config: Config( + mode: .server, + access: .public, + namingStrategy: .idiomatic, + featureFlags: featureFlags + ), diagnostics: collector, components: components ) From 14b090ac590e369be6c6ea47f978adb459b1e499 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Thu, 28 Nov 2024 11:35:09 +0100 Subject: [PATCH 11/31] Update the implementation with the latest feedback on the proposal --- .../CommonTranslations/SwiftSafeNames.swift | 30 ++++++++++++------- .../Extensions/Test_SwiftSafeNames.swift | 16 ++++++---- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift index 8c137251..d67dcb8e 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift @@ -97,13 +97,15 @@ extension String { // 1. Leave leading underscores as-are // 2. In the middle: word separators: ["_", "-", ] -> remove and capitalize next word - // 3. In the middle: period: ["."] -> replace with "_" + // 3. In the middle: period: [".", "/"] -> replace with "_" + // 4. In the middle: drop ["{", "}"] -> replace with "" var buffer: [Character] = [] buffer.reserveCapacity(count) enum State { case modifying + case fallback case preFirstWord case accumulatingWord case waitingForWordStarter @@ -129,7 +131,7 @@ extension String { state = .accumulatingWord } else { // Illegal character, fall back to the defensive strategy. - return safeForSwiftCode_defensive(options: options) + state = .fallback } case .accumulatingWord: if char.isLetter || char.isNumber { @@ -139,22 +141,25 @@ extension String { buffer.append(char) } state = .accumulatingWord - } else if char == "_" || char == "-" || char == " " { + } else if ["_", "-", " "].contains(char) { // In the middle of an identifier, dashes, underscores, and spaces are considered // word separators, so we remove the character and end the current word. state = .waitingForWordStarter - } else if char == "." { - // In the middle of an identifier, a period gets replaced with an underscore, but continues - // the current word. + } else if [".", "/"].contains(char) { + // In the middle of an identifier, a period or a slash gets replaced with + // an underscore, but continues the current word. buffer.append("_") state = .accumulatingWord + } else if ["{", "}"].contains(char) { + // In the middle of an identifier, curly braces are dropped. + state = .accumulatingWord } else { // Illegal character, fall back to the defensive strategy. - return safeForSwiftCode_defensive(options: options) + state = .fallback } case .waitingForWordStarter: - if char == "_" || char == "-" { - // Between words, just drop dashes, underscores, and spaces, since + 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 { @@ -163,12 +168,15 @@ extension String { state = .accumulatingWord } else { // Illegal character, fall back to the defensive strategy. - return safeForSwiftCode_defensive(options: options) + state = .fallback } - case .modifying: + case .modifying, .fallback: preconditionFailure("Logic error in \(#function), string: '\(self)'") } precondition(state != .modifying, "Logic error in \(#function), string: '\(self)'") + if case .fallback = state { + return safeForSwiftCode_defensive(options: options) + } } if buffer.isEmpty || state == .preFirstWord { return safeForSwiftCode_defensive(options: options) diff --git a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift index 1ed61153..b8adfb28 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift @@ -39,17 +39,23 @@ final class Test_SwiftSafeNames: Test_Core { // 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_pets_petId_notifications", + "get_pets_petId_notifications", ), - + ( + "get/name/v{version}.zip", + "get_sol_name_sol_v_lcub_version_rcub__period_zip", + "Get_name_vversion_zip", + "get_name_vversion_zip" + ), + // Technical strings - ("file/path/to/resource", "file_sol_path_sol_to_sol_resource", "file_sol_path_sol_to_sol_resource", "file_sol_path_sol_to_sol_resource"), + ("file/path/to/resource", "file_sol_path_sol_to_sol_resource", "File_path_to_resource", "file_path_to_resource"), ("user.name@domain.com", "user_period_name_commat_domain_period_com", "user_period_name_commat_domain_period_com", "user_period_name_commat_domain_period_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"), From acb471e68f9e057a4edb6d9b5b960019e4d61716 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Mon, 2 Dec 2024 09:56:26 +0100 Subject: [PATCH 12/31] Add test cases for acronyms --- .../Extensions/Test_SwiftSafeNames.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift index b8adfb28..9b2698cb 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift @@ -36,6 +36,10 @@ final class Test_SwiftSafeNames: Test_Core { // All uppercase ("HELLO_WORLD", "HELLO_WORLD", "HelloWorld", "helloWorld"), + // Acronyms + ("HTTPProxy", "HTTPProxy", "HTTPProxy", "hTTPProxy"), + ("OneHTTPProxy", "OneHTTPProxy", "OneHTTPProxy", "oneHTTPProxy"), + // Numbers ("version 2.0", "version_space_2_period_0", "Version2_0", "version2_0"), ("V1.2Release", "V1_period_2Release", "V1_2Release", "v1_2Release"), From 4bd4904651a15b2571ba0e679ab56de3b3d97fe1 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Tue, 10 Dec 2024 15:54:04 +0100 Subject: [PATCH 13/31] PR feedback --- Sources/_OpenAPIGeneratorCore/Config.swift | 4 +- .../CommonTranslations/SwiftSafeNames.swift | 121 +++++-- .../Translator/FileTranslator.swift | 18 +- .../Multipart/MultipartContentInspector.swift | 2 +- .../TypeAssignment/TypeAssigner.swift | 3 +- .../swift-openapi-generator/Extensions.swift | 5 + .../FilterCommand.swift | 9 +- .../GenerateOptions.swift | 10 +- .../swift-openapi-generator/UserConfig.swift | 2 +- .../Extensions/Test_SwiftSafeNames.swift | 41 +-- .../ReferenceSources/Petstore/Types.swift | 24 +- .../SnippetBasedReferenceTests.swift | 340 +++++++++--------- Tests/PetstoreConsumerTests/Test_Server.swift | 15 +- Tests/PetstoreConsumerTests/Test_Types.swift | 4 +- 14 files changed, 303 insertions(+), 295 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Config.swift b/Sources/_OpenAPIGeneratorCore/Config.swift index 897c1d47..7b0156ef 100644 --- a/Sources/_OpenAPIGeneratorCore/Config.swift +++ b/Sources/_OpenAPIGeneratorCore/Config.swift @@ -41,9 +41,7 @@ public struct Config: Sendable { public var filter: DocumentFilter? public var namingStrategy: NamingStrategy? - public var nameOverrides: [String: String]? - /// Additional pre-release features to enable. public var featureFlags: FeatureFlags @@ -53,6 +51,8 @@ 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 OpenAPI -> Swift name conversion strategy. + /// - nameOverrides: A map of OpenAPI -> Swift name overrides. /// - featureFlags: Additional pre-release features to enable. public init( mode: GeneratorMode, diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift index d67dcb8e..98614a45 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift @@ -15,11 +15,8 @@ import Foundation struct SwiftNameOptions: OptionSet { let rawValue: Int32 - static let none = SwiftNameOptions([]) - static let capitalize = SwiftNameOptions(rawValue: 1 << 0) - static let all: SwiftNameOptions = [.capitalize] } @@ -36,7 +33,7 @@ extension String { /// /// In addition to replacing illegal characters, it also /// ensures that the identifier starts with a letter and not a number. - func safeForSwiftCode_defensive(options: SwiftNameOptions) -> String { + func safeForSwiftCode_defensive(options: SwiftNameOptions) -> String { guard !isEmpty else { return "_empty" } let firstCharSet: CharacterSet = .letters.union(.init(charactersIn: "_")) @@ -84,10 +81,7 @@ extension String { /// matching `safeForSwiftCode_defensive`. func safeForSwiftCode_idiomatic(options: SwiftNameOptions) -> String { let capitalize = options.contains(.capitalize) - if isEmpty { - return capitalize ? "_Empty_" : "_empty_" - } - + if isEmpty { return capitalize ? "_Empty_" : "_empty_" } // Detect cases like HELLO_WORLD, sometimes used for constants. let isAllUppercase = allSatisfy { // Must check that no characters are lowercased, as non-letter characters @@ -96,22 +90,24 @@ extension String { } // 1. Leave leading underscores as-are - // 2. In the middle: word separators: ["_", "-", ] -> remove and capitalize next word - // 3. In the middle: period: [".", "/"] -> replace with "_" + // 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(count) - - enum State { + enum State: Equatable { case modifying - case fallback case preFirstWord + struct AccumulatingFirstWordContext: Equatable { var isAccumulatingInitialUppercase: Bool } + case accumulatingFirstWord(AccumulatingFirstWordContext) case accumulatingWord case waitingForWordStarter + case fallback } var state: State = .preFirstWord - for char in self { + for index in self[...].indices { + let char = self[index] let _state = state state = .modifying switch _state { @@ -124,30 +120,93 @@ extension String { // Prefix with an underscore if the first character is a number. buffer.append("_") buffer.append(char) - state = .accumulatingWord + state = .accumulatingFirstWord(.init(isAccumulatingInitialUppercase: false)) } else if char.isLetter { // First character in the identifier. buffer.append(contentsOf: capitalize ? char.uppercased() : char.lowercased()) - state = .accumulatingWord + state = .accumulatingFirstWord( + .init(isAccumulatingInitialUppercase: !capitalize && char.isUppercase) + ) } else { // Illegal character, fall back to the defensive strategy. state = .fallback } - case .accumulatingWord: + 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 = suffix(from: self.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, fall back to the defensive strategy. + state = .fallback + } + case .accumulatingWord: + if char.isLetter || char.isNumber { + if isAllUppercase { buffer.append(contentsOf: char.lowercased()) } else { buffer.append(char) } state = .accumulatingWord - } else if ["_", "-", " "].contains(char) { - // In the middle of an identifier, dashes, underscores, and spaces are considered + } 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, a period or a slash gets replaced with - // an underscore, but continues the current word. + } 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) { @@ -170,24 +229,18 @@ extension String { // Illegal character, fall back to the defensive strategy. state = .fallback } - case .modifying, .fallback: - preconditionFailure("Logic error in \(#function), string: '\(self)'") + case .modifying, .fallback: preconditionFailure("Logic error in \(#function), string: '\(self)'") } precondition(state != .modifying, "Logic error in \(#function), string: '\(self)'") - if case .fallback = state { - return safeForSwiftCode_defensive(options: options) - } - } - if buffer.isEmpty || state == .preFirstWord { - return safeForSwiftCode_defensive(options: options) + if case .fallback = state { return safeForSwiftCode_defensive(options: options) } } + if buffer.isEmpty || state == .preFirstWord { return safeForSwiftCode_defensive(options: options) } // Check for keywords let newString = String(buffer) - if Self.keywords.contains(newString) { - return "_\(newString)" - } + if Self.keywords.contains(newString) { return "_\(newString)" } return newString } + private static let wordSeparators: Set = ["_", "-", " ", "/"] /// A list of Swift keywords. /// diff --git a/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift b/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift index e1a33cae..8794e248 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift @@ -50,20 +50,14 @@ extension FileTranslator { var context: TranslatorContext { let asSwiftSafeName: (String, SwiftNameOptions) -> String switch config.namingStrategy { - case .defensive, .none: - asSwiftSafeName = { $0.safeForSwiftCode_defensive(options: $1) } - case .idiomatic: - asSwiftSafeName = { $0.safeForSwiftCode_idiomatic(options: $1) } + case .defensive, .none: asSwiftSafeName = { $0.safeForSwiftCode_defensive(options: $1) } + case .idiomatic: asSwiftSafeName = { $0.safeForSwiftCode_idiomatic(options: $1) } } let overrides = config.nameOverrides ?? [:] - return TranslatorContext( - asSwiftSafeName: { name, options in - if let override = overrides[name] { - return override - } - return asSwiftSafeName(name, options) - } - ) + return TranslatorContext(asSwiftSafeName: { name, options in + if let override = overrides[name] { return override } + return asSwiftSafeName(name, options) + }) } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift b/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift index f69692de..112d9d9b 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift @@ -305,7 +305,7 @@ extension FileTranslator { return nil } let finalContentTypeSource: MultipartPartInfo.ContentTypeSource - if let encoding, let contentType = encoding.contentType { + if let contentTypes = encoding?.contentTypes, let contentType = contentTypes.first, contentTypes.count == 1 { finalContentTypeSource = try .explicit(contentType.asGeneratorContentType) } else { finalContentTypeSource = candidateSource diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift index 3a7284f9..fae4eb92 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift @@ -495,7 +495,8 @@ struct TypeAssigner { { typeNameForComponents() .appending( - swiftComponent: context.asSwiftSafeName(componentType.openAPIComponentsKey, .capitalize).uppercasingFirstLetter, + swiftComponent: context.asSwiftSafeName(componentType.openAPIComponentsKey, .capitalize) + .uppercasingFirstLetter, jsonComponent: componentType.openAPIComponentsKey ) } diff --git a/Sources/swift-openapi-generator/Extensions.swift b/Sources/swift-openapi-generator/Extensions.swift index 280d740d..23f36ef3 100644 --- a/Sources/swift-openapi-generator/Extensions.swift +++ b/Sources/swift-openapi-generator/Extensions.swift @@ -18,9 +18,14 @@ import Yams #if $RetroactiveAttribute extension URL: @retroactive ExpressibleByArgument {} +#if compiler(<6.0) extension GeneratorMode: @retroactive ExpressibleByArgument {} extension FeatureFlag: @retroactive ExpressibleByArgument {} #else +extension GeneratorMode: ExpressibleByArgument {} +extension FeatureFlag: ExpressibleByArgument {} +#endif +#else extension URL: ExpressibleByArgument {} extension GeneratorMode: ExpressibleByArgument {} extension FeatureFlag: ExpressibleByArgument {} diff --git a/Sources/swift-openapi-generator/FilterCommand.swift b/Sources/swift-openapi-generator/FilterCommand.swift index 850de174..ce7c5f09 100644 --- a/Sources/swift-openapi-generator/FilterCommand.swift +++ b/Sources/swift-openapi-generator/FilterCommand.swift @@ -81,10 +81,7 @@ private func timing(_ title: String, _ operation: @autoclosure () throws } private let sampleConfig = _UserConfig( - generate: [ - .types, - .client - ], + generate: [.types, .client], filter: DocumentFilter( operations: ["getGreeting"], tags: ["greetings"], @@ -92,9 +89,7 @@ private let sampleConfig = _UserConfig( schemas: ["Greeting"] ), namingStrategy: .idiomatic, - nameOverrides: [ - "SPECIAL_NAME": "SpecialName" - ] + nameOverrides: ["SPECIAL_NAME": "SpecialName"] ) enum OutputFormat: String, ExpressibleByArgument { diff --git a/Sources/swift-openapi-generator/GenerateOptions.swift b/Sources/swift-openapi-generator/GenerateOptions.swift index 53fbf419..9ad724c3 100644 --- a/Sources/swift-openapi-generator/GenerateOptions.swift +++ b/Sources/swift-openapi-generator/GenerateOptions.swift @@ -75,14 +75,8 @@ extension _GenerateOptions { return [] } - func resolvedNamingStrategy(_ config: _UserConfig?) -> NamingStrategy { - config?.namingStrategy ?? .defensive - } - - func resolvedNameOverrides(_ config: _UserConfig?) -> [String: String] { - config?.nameOverrides ?? [:] - } - + func resolvedNamingStrategy(_ config: _UserConfig?) -> NamingStrategy { config?.namingStrategy ?? .defensive } + 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 db349e01..239f67b0 100644 --- a/Sources/swift-openapi-generator/UserConfig.swift +++ b/Sources/swift-openapi-generator/UserConfig.swift @@ -37,7 +37,7 @@ struct _UserConfig: Codable { 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]? diff --git a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift index 9b2698cb..03bc92b9 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift @@ -37,31 +37,30 @@ final class Test_SwiftSafeNames: Test_Core { ("HELLO_WORLD", "HELLO_WORLD", "HelloWorld", "helloWorld"), // Acronyms - ("HTTPProxy", "HTTPProxy", "HTTPProxy", "hTTPProxy"), - ("OneHTTPProxy", "OneHTTPProxy", "OneHTTPProxy", "oneHTTPProxy"), - + ("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", - "Get_pets_petId_notifications", - "get_pets_petId_notifications", + "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", - "Get_name_vversion_zip", - "get_name_vversion_zip" + "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", "File_path_to_resource", "file_path_to_resource"), - ("user.name@domain.com", "user_period_name_commat_domain_period_com", "user_period_name_commat_domain_period_com", "user_period_name_commat_domain_period_com"), - ("hello.world.2023", "hello_period_world_period_2023", "Hello_world_2023", "hello_world_2023"), + ("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_period_name_commat_domain_period_com", "user_period_name_commat_domain_period_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"), @@ -94,7 +93,6 @@ final class Test_SwiftSafeNames: Test_Core { // Preserve only leading underscores ("user__name", "user__name", "UserName", "userName"), - // Invalid underscore case ("_", "_underscore_", "_underscore_", "_underscore_"), @@ -109,7 +107,6 @@ final class Test_SwiftSafeNames: Test_Core { // Non Latin Characters combined with a RTL language ("$مرحبا", "_dollar_مرحبا", "_dollar_مرحبا", "_dollar_مرحبا"), - // Emoji ("heart❤️emoji", "heart_x2764_️emoji", "heart_x2764_️emoji", "heart_x2764_️emoji"), @@ -118,27 +115,21 @@ final class Test_SwiftSafeNames: Test_Core { ("vendor1+json", "vendor1_plus_json", "vendor1_plus_json", "vendor1_plus_json"), // Known real-world examples - ("+1", "_plus_1", "_plus_1", "_plus_1"), - ("-1", "_hyphen_1", "_hyphen_1", "_hyphen_1"), + ("+1", "_plus_1", "_plus_1", "_plus_1"), ("-1", "_hyphen_1", "_hyphen_1", "_hyphen_1"), // 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 translator = makeTranslator(nameOverrides: ["MEGA": "m_e_g_a"]) let asSwiftSafeName: (String, SwiftNameOptions) -> String = translator.context.asSwiftSafeName for (input, sanitizedDefensive, _, _) in cases { XCTAssertEqual(asSwiftSafeName(input, .none), sanitizedDefensive, "Defensive, input: \(input)") } } do { - let translator = makeTranslator( - namingStrategy: .idiomatic, - nameOverrides: ["MEGA": "m_e_g_a"] - ) + let translator = makeTranslator(namingStrategy: .idiomatic, nameOverrides: ["MEGA": "m_e_g_a"]) let asSwiftSafeName: (String, SwiftNameOptions) -> String = translator.context.asSwiftSafeName for (input, _, idiomaticUpper, idiomaticLower) in cases { XCTAssertEqual(asSwiftSafeName(input, .capitalize), idiomaticUpper, "Idiomatic upper, input: \(input)") diff --git a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift index 641c1adb..b6b7e25d 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/Resources/ReferenceSources/Petstore/Types.swift @@ -968,9 +968,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 } @@ -982,9 +982,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, @@ -995,9 +995,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) } } @@ -1308,9 +1308,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" } @@ -1322,9 +1322,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, @@ -1335,9 +1335,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) } } diff --git a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift index e2b0e294..f0ffe2d3 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift @@ -1202,24 +1202,24 @@ final class SnippetBasedReferenceTests: XCTestCase { try self.assertSchemasTranslation( """ schemas: - A: + Aaa: type: string - B: + Bbb: type: object required: - - c + - ccc properties: - c: + ccc: allOf: - - $ref: "#/components/schemas/A" + - $ref: "#/components/schemas/Aaa" """, """ public enum Schemas { - public typealias A = Swift.String - public struct B: Codable, Hashable, Sendable { - public struct cPayload: Codable, Hashable, Sendable { - public var value1: Components.Schemas.A - public init(value1: Components.Schemas.A) { + public typealias Aaa = Swift.String + public struct Bbb: Codable, Hashable, Sendable { + public struct CccPayload: Codable, Hashable, Sendable { + public var value1: Components.Schemas.Aaa + public init(value1: Components.Schemas.Aaa) { self.value1 = value1 } public init(from decoder: any Decoder) throws { @@ -1229,12 +1229,12 @@ final class SnippetBasedReferenceTests: XCTestCase { try encoder.encodeToSingleValueContainer(value1) } } - public var c: Components.Schemas.B.cPayload - public init(c: Components.Schemas.B.cPayload) { - self.c = c + public var ccc: Components.Schemas.Bbb.CccPayload + public init(ccc: Components.Schemas.Bbb.CccPayload) { + self.ccc = ccc } public enum CodingKeys: String, CodingKey { - case c + case ccc } } } @@ -1246,23 +1246,23 @@ final class SnippetBasedReferenceTests: XCTestCase { try self.assertSchemasTranslation( """ schemas: - A: + Aaa: type: string - B: + Bbb: type: object required: [] properties: - c: + ccc: allOf: - - $ref: "#/components/schemas/A" + - $ref: "#/components/schemas/Aaa" """, """ public enum Schemas { - public typealias A = Swift.String - public struct B: Codable, Hashable, Sendable { - public struct cPayload: Codable, Hashable, Sendable { - public var value1: Components.Schemas.A - public init(value1: Components.Schemas.A) { + public typealias Aaa = Swift.String + public struct Bbb: Codable, Hashable, Sendable { + public struct CccPayload: Codable, Hashable, Sendable { + public var value1: Components.Schemas.Aaa + public init(value1: Components.Schemas.Aaa) { self.value1 = value1 } public init(from decoder: any Decoder) throws { @@ -1272,12 +1272,12 @@ final class SnippetBasedReferenceTests: XCTestCase { try encoder.encodeToSingleValueContainer(value1) } } - public var c: Components.Schemas.B.cPayload? - public init(c: Components.Schemas.B.cPayload? = nil) { - self.c = c + public var ccc: Components.Schemas.Bbb.CccPayload? + public init(ccc: Components.Schemas.Bbb.CccPayload? = nil) { + self.ccc = ccc } public enum CodingKeys: String, CodingKey { - case c + case ccc } } } @@ -1610,7 +1610,7 @@ final class SnippetBasedReferenceTests: XCTestCase { yield &storage.value.name } } - public struct parentPayload: Codable, Hashable, Sendable { + public struct ParentPayload: Codable, Hashable, Sendable { public var nested: Components.Schemas.Node public init(nested: Components.Schemas.Node) { self.nested = nested @@ -1619,7 +1619,7 @@ final class SnippetBasedReferenceTests: XCTestCase { case nested } } - public var parent: Components.Schemas.Node.parentPayload? { + public var parent: Components.Schemas.Node.ParentPayload? { get { storage.value.parent } @@ -1629,7 +1629,7 @@ final class SnippetBasedReferenceTests: XCTestCase { } public init( name: Swift.String, - parent: Components.Schemas.Node.parentPayload? = nil + parent: Components.Schemas.Node.ParentPayload? = nil ) { storage = .init(value: .init( name: name, @@ -1649,7 +1649,7 @@ final class SnippetBasedReferenceTests: XCTestCase { private var storage: OpenAPIRuntime.CopyOnWriteBox private struct Storage: Codable, Hashable, Sendable { var name: Swift.String - struct parentPayload: Codable, Hashable, Sendable { + struct ParentPayload: Codable, Hashable, Sendable { public var nested: Components.Schemas.Node public init(nested: Components.Schemas.Node) { self.nested = nested @@ -1658,10 +1658,10 @@ final class SnippetBasedReferenceTests: XCTestCase { case nested } } - var parent: Components.Schemas.Node.parentPayload? + var parent: Components.Schemas.Node.ParentPayload? init( name: Swift.String, - parent: Components.Schemas.Node.parentPayload? = nil + parent: Components.Schemas.Node.ParentPayload? = nil ) { self.name = name self.parent = parent @@ -1978,11 +1978,11 @@ final class SnippetBasedReferenceTests: XCTestCase { public enum Responses { public struct BadRequest: Sendable, Hashable { public struct Headers: Sendable, Hashable { - @frozen public enum xReasonPayload: String, Codable, Hashable, Sendable, CaseIterable { + @frozen public enum XReasonPayload: String, Codable, Hashable, Sendable, CaseIterable { case badLuck = "badLuck" } - public var xReason: Components.Responses.BadRequest.Headers.xReasonPayload? - public init(xReason: Components.Responses.BadRequest.Headers.xReasonPayload? = nil) { + public var xReason: Components.Responses.BadRequest.Headers.XReasonPayload? + public init(xReason: Components.Responses.BadRequest.Headers.XReasonPayload? = nil) { self.xReason = xReason } } @@ -2108,7 +2108,7 @@ final class SnippetBasedReferenceTests: XCTestCase { """ public enum RequestBodies { @frozen public enum MyRequestBody: Sendable, Hashable { - public struct urlEncodedFormPayload: Codable, Hashable, Sendable { + public struct UrlEncodedFormPayload: Codable, Hashable, Sendable { public var foo: Swift.String public init(foo: Swift.String) { self.foo = foo @@ -2117,7 +2117,7 @@ final class SnippetBasedReferenceTests: XCTestCase { case foo } } - case urlEncodedForm(Components.RequestBodies.MyRequestBody.urlEncodedFormPayload) + case urlEncodedForm(Components.RequestBodies.MyRequestBody.UrlEncodedFormPayload) } } """ @@ -2249,8 +2249,8 @@ final class SnippetBasedReferenceTests: XCTestCase { """ public protocol APIProtocol: Sendable { @available(*, deprecated) - func getHealthOld(_ input: Operations.getHealthOld.Input) async throws -> Operations.getHealthOld.Output - func getHealthNew(_ input: Operations.getHealthNew.Input) async throws -> Operations.getHealthNew.Output + func getHealthOld(_ input: Operations.GetHealthOld.Input) async throws -> Operations.GetHealthOld.Output + func getHealthNew(_ input: Operations.GetHealthNew.Input) async throws -> Operations.GetHealthNew.Output } """ ) @@ -2259,17 +2259,16 @@ final class SnippetBasedReferenceTests: XCTestCase { """ extension APIProtocol { @available(*, deprecated) - public func getHealthOld(headers: Operations.getHealthOld.Input.Headers = .init()) async throws -> Operations.getHealthOld.Output { - try await getHealthOld(Operations.getHealthOld.Input(headers: headers)) + public func getHealthOld(headers: Operations.GetHealthOld.Input.Headers = .init()) async throws -> Operations.GetHealthOld.Output { + try await getHealthOld(Operations.GetHealthOld.Input(headers: headers)) } - public func getHealthNew(headers: Operations.getHealthNew.Input.Headers = .init()) async throws -> Operations.getHealthNew.Output { - try await getHealthNew(Operations.getHealthNew.Input(headers: headers)) + public func getHealthNew(headers: Operations.GetHealthNew.Input.Headers = .init()) async throws -> Operations.GetHealthNew.Output { + try await getHealthNew(Operations.GetHealthNew.Input(headers: headers)) } } """ ) } - func testSynthesizedOperationId() throws { let paths = """ /pets/{petId}/notifications: @@ -2288,7 +2287,7 @@ final class SnippetBasedReferenceTests: XCTestCase { paths, """ public protocol APIProtocol: Sendable { - func getHealthNew(_ input: Operations.getHealthNew.Input) async throws -> Operations.getHealthNew.Output + func getPetsPetIdNotifications(_ input: Operations.GetPetsPetIdNotifications.Input) async throws -> Operations.GetPetsPetIdNotifications.Output } """ ) @@ -2498,8 +2497,8 @@ final class SnippetBasedReferenceTests: XCTestCase { self.manyUnexploded = manyUnexploded } } - public var query: Operations.get_sol_foo.Input.Query - public init(query: Operations.get_sol_foo.Input.Query = .init()) { + public var query: Operations.GetFoo.Input.Query + public init(query: Operations.GetFoo.Input.Query = .init()) { self.query = query } } @@ -2541,7 +2540,7 @@ final class SnippetBasedReferenceTests: XCTestCase { """, server: """ { request, requestBody, metadata in - let query: Operations.get_sol_foo.Input.Query = .init( + let query: Operations.GetFoo.Input.Query = .init( single: try converter.getOptionalQueryItemAsURI( in: request.soar_query, style: .form, @@ -2564,7 +2563,7 @@ final class SnippetBasedReferenceTests: XCTestCase { as: [Swift.String].self ) ) - return Operations.get_sol_foo.Input(query: query) + return Operations.GetFoo.Input(query: query) } """ ) @@ -2612,8 +2611,8 @@ final class SnippetBasedReferenceTests: XCTestCase { self.num = num } } - public var path: Operations.getFoo.Input.Path - public init(path: Operations.getFoo.Input.Path) { + public var path: Operations.GetFoo.Input.Path + public init(path: Operations.GetFoo.Input.Path) { self.path = path } } @@ -2638,7 +2637,7 @@ final class SnippetBasedReferenceTests: XCTestCase { """, server: """ { request, requestBody, metadata in - let path: Operations.getFoo.Input.Path = .init( + let path: Operations.GetFoo.Input.Path = .init( b: try converter.getPathParameterAsURI( in: metadata.pathParameters, name: "b", @@ -2655,7 +2654,7 @@ final class SnippetBasedReferenceTests: XCTestCase { as: Swift.Int.self ) ) - return Operations.getFoo.Input(path: path) + return Operations.GetFoo.Input(path: path) } """ ) @@ -2680,13 +2679,13 @@ final class SnippetBasedReferenceTests: XCTestCase { input: """ public struct Input: Sendable, Hashable { public struct Path: Sendable, Hashable { - public var p_period_a_hyphen_b: Swift.String - public init(p_period_a_hyphen_b: Swift.String) { - self.p_period_a_hyphen_b = p_period_a_hyphen_b + public var p_aB: Swift.String + public init(p_aB: Swift.String) { + self.p_aB = p_aB } } - public var path: Operations.getFoo.Input.Path - public init(path: Operations.getFoo.Input.Path) { + public var path: Operations.GetFoo.Input.Path + public init(path: Operations.GetFoo.Input.Path) { self.path = path } } @@ -2696,7 +2695,7 @@ final class SnippetBasedReferenceTests: XCTestCase { let path = try converter.renderedPath( template: "/foo/{}", parameters: [ - input.path.p_period_a_hyphen_b + input.path.p_aB ] ) var request: HTTPTypes.HTTPRequest = .init( @@ -2709,12 +2708,12 @@ final class SnippetBasedReferenceTests: XCTestCase { """, server: """ { request, requestBody, metadata in - let path: Operations.getFoo.Input.Path = .init(p_period_a_hyphen_b: try converter.getPathParameterAsURI( + let path: Operations.GetFoo.Input.Path = .init(p_aB: try converter.getPathParameterAsURI( in: metadata.pathParameters, name: "p.a-b", as: Swift.String.self )) - return Operations.getFoo.Input(path: path) + return Operations.GetFoo.Input(path: path) } """ ) @@ -2740,8 +2739,8 @@ final class SnippetBasedReferenceTests: XCTestCase { @frozen public enum Body: Sendable, Hashable { case json(Swift.String) } - public var body: Operations.get_sol_foo.Input.Body - public init(body: Operations.get_sol_foo.Input.Body) { + public var body: Operations.GetFoo.Input.Body + public init(body: Operations.GetFoo.Input.Body) { self.body = body } } @@ -2772,7 +2771,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.get_sol_foo.Input.Body + let body: Operations.GetFoo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -2791,7 +2790,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.get_sol_foo.Input(body: body) + return Operations.GetFoo.Input(body: body) } """ ) @@ -2817,8 +2816,8 @@ final class SnippetBasedReferenceTests: XCTestCase { @frozen public enum Body: Sendable, Hashable { case json(Swift.String) } - public var body: Operations.get_sol_foo.Input.Body - public init(body: Operations.get_sol_foo.Input.Body) { + public var body: Operations.GetFoo.Input.Body + public init(body: Operations.GetFoo.Input.Body) { self.body = body } } @@ -2849,7 +2848,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.get_sol_foo.Input.Body + let body: Operations.GetFoo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -2868,7 +2867,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.get_sol_foo.Input(body: body) + return Operations.GetFoo.Input(body: body) } """ ) @@ -2894,8 +2893,8 @@ final class SnippetBasedReferenceTests: XCTestCase { @frozen public enum Body: Sendable, Hashable { case json(Swift.String) } - public var body: Operations.get_sol_foo.Input.Body? - public init(body: Operations.get_sol_foo.Input.Body? = nil) { + public var body: Operations.GetFoo.Input.Body? + public init(body: Operations.GetFoo.Input.Body? = nil) { self.body = body } } @@ -2928,7 +2927,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.get_sol_foo.Input.Body? + let body: Operations.GetFoo.Input.Body? let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -2947,7 +2946,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.get_sol_foo.Input(body: body) + return Operations.GetFoo.Input(body: body) } """ ) @@ -2973,8 +2972,8 @@ final class SnippetBasedReferenceTests: XCTestCase { @frozen public enum Body: Sendable, Hashable { case json(Swift.String) } - public var body: Operations.get_sol_foo.Input.Body? - public init(body: Operations.get_sol_foo.Input.Body? = nil) { + public var body: Operations.GetFoo.Input.Body? + public init(body: Operations.GetFoo.Input.Body? = nil) { self.body = body } } @@ -3007,7 +3006,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.get_sol_foo.Input.Body? + let body: Operations.GetFoo.Input.Body? let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -3026,7 +3025,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.get_sol_foo.Input(body: body) + return Operations.GetFoo.Input(body: body) } """ ) @@ -3166,7 +3165,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.getFoo.Input(body: body) + return Operations.GetFoo.Input(body: body) } """ ) @@ -3549,7 +3548,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.post_sol_foo.Input(body: body) + return Operations.PostFoo.Input(body: body) } """ ) @@ -3583,13 +3582,13 @@ final class SnippetBasedReferenceTests: XCTestCase { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } - public var body: Operations.post_sol_foo.Input.Body - public init(body: Operations.post_sol_foo.Input.Body) { + public var body: Operations.PostFoo.Input.Body + public init(body: Operations.PostFoo.Input.Body) { self.body = body } } @@ -3647,7 +3646,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.post_sol_foo.Input.Body + let body: Operations.PostFoo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -3657,7 +3656,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -3698,7 +3697,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.post_sol_foo.Input(body: body) + return Operations.PostFoo.Input(body: body) } """ ) @@ -3731,19 +3730,19 @@ final class SnippetBasedReferenceTests: XCTestCase { public struct Input: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { @frozen public enum MultipartFormPayload: Sendable, Hashable { - public struct infoPayload: Sendable, Hashable { + public struct InfoPayload: Sendable, Hashable { public var body: Components.Schemas.Info public init(body: Components.Schemas.Info) { self.body = body } } - case info(OpenAPIRuntime.MultipartPart) + case info(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } - public var body: Operations.post_sol_foo.Input.Body - public init(body: Operations.post_sol_foo.Input.Body) { + public var body: Operations.PostFoo.Input.Body + public init(body: Operations.PostFoo.Input.Body) { self.body = body } } @@ -3801,7 +3800,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.post_sol_foo.Input.Body + let body: Operations.PostFoo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -3811,7 +3810,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -3852,7 +3851,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.post_sol_foo.Input(body: body) + return Operations.PostFoo.Input(body: body) } """ ) @@ -3888,8 +3887,8 @@ final class SnippetBasedReferenceTests: XCTestCase { @frozen public enum Body: Sendable, Hashable { case multipartForm(OpenAPIRuntime.MultipartBody) } - public var body: Operations.post_sol_foo.Input.Body - public init(body: Operations.post_sol_foo.Input.Body) { + public var body: Operations.PostFoo.Input.Body + public init(body: Operations.PostFoo.Input.Body) { self.body = body } } @@ -3961,7 +3960,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.post_sol_foo.Input.Body + let body: Operations.PostFoo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -4012,7 +4011,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.post_sol_foo.Input(body: body) + return Operations.PostFoo.Input(body: body) } """ ) @@ -4060,23 +4059,23 @@ final class SnippetBasedReferenceTests: XCTestCase { self.xLogType = xLogType } } - public var headers: Operations.post_sol_foo.Input.Body.MultipartFormPayload.LogPayload.Headers + public var headers: Operations.PostFoo.Input.Body.MultipartFormPayload.LogPayload.Headers public var body: OpenAPIRuntime.HTTPBody public init( - headers: Operations.post_sol_foo.Input.Body.MultipartFormPayload.LogPayload.Headers = .init(), + headers: Operations.PostFoo.Input.Body.MultipartFormPayload.LogPayload.Headers = .init(), body: OpenAPIRuntime.HTTPBody ) { self.headers = headers self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } - public var body: Operations.post_sol_foo.Input.Body - public init(body: Operations.post_sol_foo.Input.Body) { + public var body: Operations.PostFoo.Input.Body + public init(body: Operations.PostFoo.Input.Body) { self.body = body } } @@ -4153,7 +4152,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.post_sol_foo.Input.Body + let body: Operations.PostFoo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -4163,7 +4162,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -4181,7 +4180,7 @@ final class SnippetBasedReferenceTests: XCTestCase { let (name, filename) = try converter.extractContentDispositionNameAndFilename(in: headerFields) switch name { case "log": - let headers: Operations.post_sol_foo.Input.Body.MultipartFormPayload.LogPayload.Headers = .init(xLogType: try converter.getOptionalHeaderFieldAsURI( + let headers: Operations.PostFoo.Input.Body.MultipartFormPayload.LogPayload.Headers = .init(xLogType: try converter.getOptionalHeaderFieldAsURI( in: headerFields, name: "x-log-type", as: Swift.String.self @@ -4212,7 +4211,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.post_sol_foo.Input(body: body) + return Operations.PostFoo.Input(body: body) } """ ) @@ -4237,10 +4236,10 @@ final class SnippetBasedReferenceTests: XCTestCase { @frozen public enum MultipartFormPayload: Sendable, Hashable { case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } - public var body: Operations.post_sol_foo.Input.Body - public init(body: Operations.post_sol_foo.Input.Body) { + public var body: Operations.PostFoo.Input.Body + public init(body: Operations.PostFoo.Input.Body) { self.body = body } } @@ -4282,7 +4281,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.post_sol_foo.Input.Body + let body: Operations.PostFoo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -4292,7 +4291,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -4315,7 +4314,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.post_sol_foo.Input(body: body) + return Operations.PostFoo.Input(body: body) } """ ) @@ -4343,10 +4342,10 @@ final class SnippetBasedReferenceTests: XCTestCase { @frozen public enum MultipartFormPayload: Sendable, Hashable { case other(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } - public var body: Operations.post_sol_foo.Input.Body - public init(body: Operations.post_sol_foo.Input.Body) { + public var body: Operations.PostFoo.Input.Body + public init(body: Operations.PostFoo.Input.Body) { self.body = body } } @@ -4388,7 +4387,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.post_sol_foo.Input.Body + let body: Operations.PostFoo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -4398,7 +4397,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -4421,7 +4420,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.post_sol_foo.Input(body: body) + return Operations.PostFoo.Input(body: body) } """ ) @@ -4458,12 +4457,12 @@ final class SnippetBasedReferenceTests: XCTestCase { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } - public var body: Operations.post_sol_foo.Input.Body - public init(body: Operations.post_sol_foo.Input.Body) { + public var body: Operations.PostFoo.Input.Body + public init(body: Operations.PostFoo.Input.Body) { self.body = body } } @@ -4519,7 +4518,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.post_sol_foo.Input.Body + let body: Operations.PostFoo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -4529,7 +4528,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -4570,7 +4569,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.post_sol_foo.Input(body: body) + return Operations.PostFoo.Input(body: body) } """ ) @@ -4611,8 +4610,8 @@ final class SnippetBasedReferenceTests: XCTestCase { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) - public struct additionalPropertiesPayload: Codable, Hashable, Sendable { + case log(OpenAPIRuntime.MultipartPart) + public struct AdditionalPropertiesPayload: Codable, Hashable, Sendable { public var foo: Swift.String? public init(foo: Swift.String? = nil) { self.foo = foo @@ -4621,12 +4620,12 @@ final class SnippetBasedReferenceTests: XCTestCase { case foo } } - case additionalProperties(OpenAPIRuntime.MultipartDynamicallyNamedPart) + case additionalProperties(OpenAPIRuntime.MultipartDynamicallyNamedPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } - public var body: Operations.post_sol_foo.Input.Body - public init(body: Operations.post_sol_foo.Input.Body) { + public var body: Operations.PostFoo.Input.Body + public init(body: Operations.PostFoo.Input.Body) { self.body = body } } @@ -4696,7 +4695,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.post_sol_foo.Input.Body + let body: Operations.PostFoo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -4706,7 +4705,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -4745,7 +4744,7 @@ final class SnippetBasedReferenceTests: XCTestCase { matches: "application/json" ) let body = try await converter.getRequiredRequestBodyAsJSON( - Operations.post_sol_foo.Input.Body.MultipartFormPayload.additionalPropertiesPayload.self, + Operations.PostFoo.Input.Body.MultipartFormPayload.AdditionalPropertiesPayload.self, from: part.body, transforming: { $0 @@ -4762,7 +4761,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.post_sol_foo.Input(body: body) + return Operations.PostFoo.Input(body: body) } """ ) @@ -4808,13 +4807,13 @@ final class SnippetBasedReferenceTests: XCTestCase { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) case additionalProperties(OpenAPIRuntime.MultipartDynamicallyNamedPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } - public var body: Operations.post_sol_foo.Input.Body - public init(body: Operations.post_sol_foo.Input.Body) { + public var body: Operations.PostFoo.Input.Body + public init(body: Operations.PostFoo.Input.Body) { self.body = body } } @@ -4884,7 +4883,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.post_sol_foo.Input.Body + let body: Operations.PostFoo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -4894,7 +4893,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -4950,7 +4949,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.post_sol_foo.Input(body: body) + return Operations.PostFoo.Input(body: body) } """ ) @@ -4979,10 +4978,10 @@ final class SnippetBasedReferenceTests: XCTestCase { @frozen public enum MultipartFormPayload: Sendable, Hashable { case additionalProperties(OpenAPIRuntime.MultipartDynamicallyNamedPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } - public var body: Operations.post_sol_foo.Input.Body - public init(body: Operations.post_sol_foo.Input.Body) { + public var body: Operations.PostFoo.Input.Body + public init(body: Operations.PostFoo.Input.Body) { self.body = body } } @@ -5036,7 +5035,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.post_sol_foo.Input.Body + let body: Operations.PostFoo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -5046,7 +5045,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -5084,7 +5083,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.post_sol_foo.Input(body: body) + return Operations.PostFoo.Input(body: body) } """ ) @@ -5312,11 +5311,11 @@ final class SnippetBasedReferenceTests: XCTestCase { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) - public var multipartForm: OpenAPIRuntime.MultipartBody { + case multipartForm(OpenAPIRuntime.MultipartBody) + public var multipartForm: OpenAPIRuntime.MultipartBody { get throws { switch self { case let .multipartForm(body): @@ -5325,13 +5324,13 @@ final class SnippetBasedReferenceTests: XCTestCase { } } } - public var body: Operations.get_sol_foo.Output.Ok.Body - public init(body: Operations.get_sol_foo.Output.Ok.Body) { + public var body: Operations.GetFoo.Output.Ok.Body + public init(body: Operations.GetFoo.Output.Ok.Body) { self.body = body } } - case ok(Operations.get_sol_foo.Output.Ok) - public var ok: Operations.get_sol_foo.Output.Ok { + case ok(Operations.GetFoo.Output.Ok) + public var ok: Operations.GetFoo.Output.Ok { get throws { switch self { case let .ok(response): @@ -5405,7 +5404,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch response.status.code { case 200: let contentType = converter.extractContentTypeIfPresent(in: response.headerFields) - let body: Operations.get_sol_foo.Output.Ok.Body + let body: Operations.GetFoo.Output.Ok.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -5415,7 +5414,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getResponseBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: responseBody, transforming: { value in .multipartForm(value) @@ -5857,32 +5856,17 @@ extension SnippetBasedReferenceTests { let collector = XCTestDiagnosticCollector(test: self, ignoredDiagnosticMessages: ignoredDiagnosticMessages) return ( TypesFileTranslator( - config: Config( - mode: .types, - access: .public, - namingStrategy: .idiomatic, - featureFlags: featureFlags - ), + config: Config(mode: .types, access: .public, namingStrategy: .idiomatic, featureFlags: featureFlags), diagnostics: collector, components: components ), ClientFileTranslator( - config: Config( - mode: .client, - access: .public, - namingStrategy: .idiomatic, - featureFlags: featureFlags - ), + config: Config(mode: .client, access: .public, namingStrategy: .idiomatic, featureFlags: featureFlags), diagnostics: collector, components: components ), ServerFileTranslator( - config: Config( - mode: .server, - access: .public, - namingStrategy: .idiomatic, - featureFlags: featureFlags - ), + config: Config(mode: .server, access: .public, namingStrategy: .idiomatic, featureFlags: featureFlags), diagnostics: collector, components: components ) diff --git a/Tests/PetstoreConsumerTests/Test_Server.swift b/Tests/PetstoreConsumerTests/Test_Server.swift index 156dad0d..9a8fa0f9 100644 --- a/Tests/PetstoreConsumerTests/Test_Server.swift +++ b/Tests/PetstoreConsumerTests/Test_Server.swift @@ -105,10 +105,7 @@ final class Test_Server: XCTestCase { guard case let .json(createPet) = input.body else { throw TestError.unexpectedValue(input.body) } XCTAssertEqual(createPet, .init(name: "Fluffz")) return .created( - .init( - headers: .init(xExtraArguments: .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(xReason: "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( @@ -245,10 +239,7 @@ final class Test_Server: XCTestCase { ) ) return .created( - .init( - headers: .init(xExtraArguments: .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( diff --git a/Tests/PetstoreConsumerTests/Test_Types.swift b/Tests/PetstoreConsumerTests/Test_Types.swift index e75fb12d..d01973b0 100644 --- a/Tests/PetstoreConsumerTests/Test_Types.swift +++ b/Tests/PetstoreConsumerTests/Test_Types.swift @@ -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")) ) ) From 6d98aa49420eca0a841677b4e8de6a693e587e43 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Tue, 10 Dec 2024 16:14:53 +0100 Subject: [PATCH 14/31] Clarify SwiftNameOptions --- .../CommonTranslations/SwiftSafeNames.swift | 15 +++++++++------ .../translateAllAnyOneOf.swift | 2 +- .../CommonTranslations/translateRawEnum.swift | 4 ++-- .../CommonTypes/DiscriminatorExtensions.swift | 2 +- .../CommonTypes/StructBlueprint.swift | 2 +- .../Multipart/MultipartContentInspector.swift | 2 +- .../Multipart/translateMultipart.swift | 6 +++--- .../Operations/OperationDescription.swift | 6 +++--- .../Translator/Parameters/TypedParameter.swift | 2 +- .../Responses/TypedResponseHeader.swift | 2 +- .../TypeAssignment/TypeAssigner.swift | 18 +++++++++--------- .../translateServersVariables.swift | 10 +++++----- .../Extensions/Test_SwiftSafeNames.swift | 6 +++--- .../TypeAssignment/Test_TypeAssigner.swift | 2 +- 14 files changed, 41 insertions(+), 38 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift index 98614a45..fcd06c6d 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift @@ -13,11 +13,14 @@ //===----------------------------------------------------------------------===// import Foundation -struct SwiftNameOptions: OptionSet { - let rawValue: Int32 - static let none = SwiftNameOptions([]) - static let capitalize = SwiftNameOptions(rawValue: 1 << 0) - static let all: SwiftNameOptions = [.capitalize] +struct SwiftNameOptions { + enum NameKind { + case capitalized + case noncapitalized + } + var kind: NameKind + static let capitalized = SwiftNameOptions(kind: .capitalized) + static let noncapitalized = SwiftNameOptions(kind: .noncapitalized) } extension String { @@ -80,7 +83,7 @@ extension String { /// If the string contains any illegal characters, falls back to the behavior /// matching `safeForSwiftCode_defensive`. func safeForSwiftCode_idiomatic(options: SwiftNameOptions) -> String { - let capitalize = options.contains(.capitalize) + let capitalize = options.kind == .capitalized if isEmpty { return capitalize ? "_Empty_" : "_empty_" } // Detect cases like HELLO_WORLD, sometimes used for constants. let isAllUppercase = allSatisfy { diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift index 92d6745f..6a6d4b70 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift @@ -209,7 +209,7 @@ extension TypesFileTranslator { let decoder: Declaration if let discriminator { let originalName = discriminator.propertyName - let swiftName = context.asSwiftSafeName(originalName, .none) + let swiftName = context.asSwiftSafeName(originalName, .noncapitalized) codingKeysDecls = [ .enum( accessModifier: config.access, diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateRawEnum.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateRawEnum.swift index cb7507be..2b59ed38 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("", .none)) + try addIfUnique(id: .string(""), caseName: context.asSwiftSafeName("", .noncapitalized)) } else { guard let rawValue = anyValue as? String else { throw GenericError(message: "Disallowed value for a string enum '\(typeName)': \(anyValue)") } - let caseName = context.asSwiftSafeName(rawValue, .none) + let caseName = context.asSwiftSafeName(rawValue, .noncapitalized) 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 3d0bb3a3..a5fb1172 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/DiscriminatorExtensions.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/DiscriminatorExtensions.swift @@ -80,7 +80,7 @@ extension FileTranslator { /// - Parameter type: The `OneOfMappedType` for which to determine the case name. /// - Returns: A string representing the safe Swift name for the specified `OneOfMappedType`. func safeSwiftNameForOneOfMappedCase(_ type: OneOfMappedType) -> String { - context.asSwiftSafeName(type.rawNames[0], .none) + context.asSwiftSafeName(type.rawNames[0], .noncapitalized) } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/StructBlueprint.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/StructBlueprint.swift index c295a5bd..10b5da2f 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, .none) } + var swiftSafeName: String { context.asSwiftSafeName(originalName, .noncapitalized) } /// The JSON path to the property. /// diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift b/Sources/_OpenAPIGeneratorCore/Translator/Multipart/MultipartContentInspector.swift index 8be5f9a6..cc039388 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, .capitalize) + let swiftSafeName = context.asSwiftSafeName(key, .capitalized) 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 21163449..1c2d2ed6 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, .none), + name: context.asSwiftSafeName(documentedPart.originalName, .noncapitalized), 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, .none) + let identifier = context.asSwiftSafeName(originalName, .noncapitalized) 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, .none) + let identifier = context.asSwiftSafeName(originalName, .noncapitalized) 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 bceb302c..3626af27 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Operations/OperationDescription.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Operations/OperationDescription.swift @@ -83,14 +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, .none) } + var methodName: String { context.asSwiftSafeName(operationID, .noncapitalized) } /// 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.asSwiftSafeName(operationID, .capitalize) } + var operationTypeName: String { context.asSwiftSafeName(operationID, .capitalized) } /// Returns the identifier for the operation. /// @@ -299,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, .none)) + .identifierPattern("input").dot("path").dot(context.asSwiftSafeName(param, .noncapitalized)) } 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 b5d8b425..bfeb5467 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, .none) } + var variableName: String { context.asSwiftSafeName(name, .noncapitalized) } /// A Boolean value that indicates whether the parameter must be specified /// when performing the OpenAPI operation. diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift b/Sources/_OpenAPIGeneratorCore/Translator/Responses/TypedResponseHeader.swift index 7c16ece6..5d0932bd 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, .none) } + var variableName: String { context.asSwiftSafeName(name, .noncapitalized) } /// A Boolean value that indicates whether the response header can /// be omitted in the HTTP response. diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift index fae4eb92..5ecf852c 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift @@ -59,7 +59,7 @@ struct TypeAssigner { /// - Returns: A Swift type name for the specified component type. func typeName(forComponentOriginallyNamed originalName: String, in location: TypeLocation) -> TypeName { typeName(forLocation: location) - .appending(swiftComponent: context.asSwiftSafeName(originalName, .capitalize), jsonComponent: originalName) + .appending(swiftComponent: context.asSwiftSafeName(originalName, .capitalized), jsonComponent: originalName) } /// Returns the type name for an OpenAPI-named component namespace. @@ -127,7 +127,7 @@ struct TypeAssigner { { multipartBodyElementTypeName = try typeName(for: ref) } else { - let swiftSafeName = context.asSwiftSafeName(hint, .capitalize) + let swiftSafeName = context.asSwiftSafeName(hint, .capitalized) multipartBodyElementTypeName = parent.appending( swiftComponent: swiftSafeName + Constants.Global.inlineTypeSuffix, jsonComponent: hint @@ -343,7 +343,7 @@ struct TypeAssigner { } return baseType.appending( - swiftComponent: context.asSwiftSafeName(originalName, .capitalize) + suffix, + swiftComponent: context.asSwiftSafeName(originalName, .capitalized) + suffix, jsonComponent: jsonReferenceComponentOverride ?? originalName ) .asUsage.withOptional(try typeMatcher.isOptional(schema, components: components)) @@ -406,7 +406,7 @@ struct TypeAssigner { of componentType: Component.Type ) -> TypeName { typeName(for: Component.self) - .appending(swiftComponent: context.asSwiftSafeName(key.rawValue, .capitalize), jsonComponent: key.rawValue) + .appending(swiftComponent: context.asSwiftSafeName(key.rawValue, .capitalized), jsonComponent: key.rawValue) } /// Returns a type name for a JSON reference. @@ -471,7 +471,7 @@ struct TypeAssigner { throw JSONReferenceParsingError.nonComponentPathsUnsupported(reference.name) } return typeName(for: componentType) - .appending(swiftComponent: context.asSwiftSafeName(name, .capitalize), jsonComponent: name) + .appending(swiftComponent: context.asSwiftSafeName(name, .capitalized), jsonComponent: name) } /// Returns a type name for the namespace for the specified component type. @@ -495,7 +495,7 @@ struct TypeAssigner { { typeNameForComponents() .appending( - swiftComponent: context.asSwiftSafeName(componentType.openAPIComponentsKey, .capitalize) + swiftComponent: context.asSwiftSafeName(componentType.openAPIComponentsKey, .capitalized) .uppercasingFirstLetter, jsonComponent: componentType.openAPIComponentsKey ) @@ -529,14 +529,14 @@ struct TypeAssigner { case "application/pdf": return "pdf" case "image/jpeg": return "jpeg" default: - let safedType = context.asSwiftSafeName(contentType.originallyCasedType, .none) - let safedSubtype = context.asSwiftSafeName(contentType.originallyCasedSubtype, .none) + let safedType = context.asSwiftSafeName(contentType.originallyCasedType, .noncapitalized) + let safedSubtype = context.asSwiftSafeName(contentType.originallyCasedSubtype, .noncapitalized) 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), .none) }.joined(separator: "_") + pair.split(separator: "=").map { context.asSwiftSafeName(String($0), .noncapitalized) }.joined(separator: "_") } .joined(separator: "_") return prefix + "_" + safedParams diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServersVariables.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypesTranslator/translateServersVariables.swift index 4a67ea3e..4517dd92 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, .none) + swiftSafeKey = context.asSwiftSafeName(key, .noncapitalized) self.variable = variable } @@ -164,8 +164,8 @@ extension TypesFileTranslator { context: TranslatorContext ) { self.key = key - swiftSafeKey = context.asSwiftSafeName(key, .none) - enumName = context.asSwiftSafeName(key.localizedCapitalized, .capitalize) + swiftSafeKey = context.asSwiftSafeName(key, .noncapitalized) + enumName = context.asSwiftSafeName(key.localizedCapitalized, .capitalized) 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, .none))) + defaultValue: .memberAccess(.dot(context.asSwiftSafeName(variable.default, .noncapitalized))) ) } @@ -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, .none) + let caseName = context.asSwiftSafeName(name, .noncapitalized) return .enumCase(name: caseName, kind: caseName == name ? .nameOnly : .nameWithRawValue(.string(name))) } } diff --git a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift index 03bc92b9..8142d5ec 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift @@ -125,15 +125,15 @@ final class Test_SwiftSafeNames: Test_Core { let translator = makeTranslator(nameOverrides: ["MEGA": "m_e_g_a"]) let asSwiftSafeName: (String, SwiftNameOptions) -> String = translator.context.asSwiftSafeName for (input, sanitizedDefensive, _, _) in cases { - XCTAssertEqual(asSwiftSafeName(input, .none), sanitizedDefensive, "Defensive, input: \(input)") + XCTAssertEqual(asSwiftSafeName(input, .noncapitalized), sanitizedDefensive, "Defensive, input: \(input)") } } do { let translator = makeTranslator(namingStrategy: .idiomatic, nameOverrides: ["MEGA": "m_e_g_a"]) let asSwiftSafeName: (String, SwiftNameOptions) -> String = translator.context.asSwiftSafeName for (input, _, idiomaticUpper, idiomaticLower) in cases { - XCTAssertEqual(asSwiftSafeName(input, .capitalize), idiomaticUpper, "Idiomatic upper, input: \(input)") - XCTAssertEqual(asSwiftSafeName(input, .none), idiomaticLower, "Idiomatic lower, input: \(input)") + XCTAssertEqual(asSwiftSafeName(input, .capitalized), idiomaticUpper, "Idiomatic upper, input: \(input)") + XCTAssertEqual(asSwiftSafeName(input, .noncapitalized), idiomaticLower, "Idiomatic lower, input: \(input)") } } } diff --git a/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeAssigner.swift b/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeAssigner.swift index c8e8b008..745a65e0 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, .none), expectedSwiftTypeName) + XCTAssertEqual(asSwiftSafeName(componentKey.rawValue, .noncapitalized), expectedSwiftTypeName) } } From a098cd10763be1191ee60c6edea2abfb6db6397d Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Wed, 11 Dec 2024 09:48:43 +0100 Subject: [PATCH 15/31] Update docs --- Sources/_OpenAPIGeneratorCore/Config.swift | 25 ++++++++++++++-- .../CommonTranslations/SwiftSafeNames.swift | 30 +++++++++++++++---- .../Articles/Configuring-the-generator.md | 12 +++++++- .../Articles/Project-scope-and-goals.md | 2 +- .../_Resources/client.Package.0.swift | 2 +- .../_Resources/client.Package.1.swift | 2 +- .../_Resources/client.Package.2.swift | 6 ++-- .../_Resources/client.Package.3.swift | 6 ++-- .../_Resources/client.Package.4.swift | 6 ++-- .../_Resources/client.Package.5.swift | 6 ++-- .../_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.0.swift | 2 +- .../_Resources/server.Package.1.swift | 2 +- .../_Resources/server.Package.2.swift | 6 ++-- .../_Resources/server.Package.3.swift | 6 ++-- .../_Resources/server.Package.4.swift | 6 ++-- .../_Resources/server.Package.5.swift | 6 ++-- .../_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 | 12 +++++++- 26 files changed, 118 insertions(+), 55 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Config.swift b/Sources/_OpenAPIGeneratorCore/Config.swift index 7b0156ef..d15e58a0 100644 --- a/Sources/_OpenAPIGeneratorCore/Config.swift +++ b/Sources/_OpenAPIGeneratorCore/Config.swift @@ -12,8 +12,21 @@ // //===----------------------------------------------------------------------===// +/// 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. + /// + /// This strategy is the default in Swift OpenAPI Generator 1.x. + /// + /// 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. + /// + /// Opt-in since Swift OpenAPI Generator 1.6.0. + /// + /// Introduced in [SOAR-0013](https://swiftpackageindex.com/apple/swift-openapi-generator/documentation/swift-openapi-generator/soar-0013). case idiomatic } @@ -40,8 +53,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 @@ -51,8 +70,10 @@ 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 OpenAPI -> Swift name conversion strategy. - /// - nameOverrides: A map of OpenAPI -> Swift name overrides. + /// - 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, diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift index fcd06c6d..5ca75be6 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift @@ -13,14 +13,30 @@ //===----------------------------------------------------------------------===// import Foundation +/// Extra context for the `safeForSwiftCode_` family of functions to produce more appropriate Swift identifiers. struct SwiftNameOptions { - enum NameKind { + + /// An option for controlling capitalization. + /// + /// Generally, type names are capitalized, for example: `Foo`. + /// And member names are not capitalized, for example: `foo`. + enum Capitalization { + + /// Capitalize the name, used for type names, for example: `Foo`. case capitalized + + /// Don't capitalize the name, used for member names, for example: `foo`. case noncapitalized } - var kind: NameKind - static let capitalized = SwiftNameOptions(kind: .capitalized) - static let noncapitalized = SwiftNameOptions(kind: .noncapitalized) + + /// The capitalization option. + var capitalization: Capitalization + + /// Preset options for capitalized names. + static let capitalized = SwiftNameOptions(capitalization: .capitalized) + + /// Preset options for non-capitalized names. + static let noncapitalized = SwiftNameOptions(capitalization: .noncapitalized) } extension String { @@ -82,8 +98,10 @@ extension String { /// /// 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. func safeForSwiftCode_idiomatic(options: SwiftNameOptions) -> String { - let capitalize = options.kind == .capitalized + let capitalize = options.capitalization == .capitalized if isEmpty { return capitalize ? "_Empty_" : "_empty_" } // Detect cases like HELLO_WORLD, sometimes used for constants. let isAllUppercase = allSatisfy { @@ -243,6 +261,8 @@ extension String { if Self.keywords.contains(newString) { return "_\(newString)" } return newString } + + /// A list of word separator characters for the idiomatic naming strategy. private static let wordSeparators: Set = ["_", "-", " ", "/"] /// A list of Swift keywords. 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..513f178f 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. Customizes 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.0.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.0.swift index 3596a147..2f4286f1 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.0.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.0.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 6.0 import PackageDescription let package = Package( diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.1.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.1.swift index f92bb26c..eef1320a 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.1.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.1.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 6.0 import PackageDescription let package = Package( 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..a580365c 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 @@ -1,12 +1,12 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 6.0 import PackageDescription 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..249f8f72 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 @@ -1,12 +1,12 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 6.0 import PackageDescription 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..35a36e77 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 @@ -1,12 +1,12 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 6.0 import PackageDescription 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..35a36e77 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 @@ -1,12 +1,12 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 6.0 import PackageDescription 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.0.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.0.swift index 48155db2..e6db09f1 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.0.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.0.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 6.0 import PackageDescription let package = Package( diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.1.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.1.swift index aa31ee69..081a77a7 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.1.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.1.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 6.0 import PackageDescription let package = Package( 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..a0d063fb 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 @@ -1,4 +1,4 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 6.0 import PackageDescription let package = Package( @@ -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..c57eb61f 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 @@ -1,4 +1,4 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 6.0 import PackageDescription let package = Package( @@ -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..0456c054 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 @@ -1,4 +1,4 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 6.0 import PackageDescription let package = Package( @@ -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..0456c054 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 @@ -1,4 +1,4 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 6.0 import PackageDescription let package = Package( @@ -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..a3f27c2f 100644 --- a/Sources/swift-openapi-generator/Extensions.swift +++ b/Sources/swift-openapi-generator/Extensions.swift @@ -43,7 +43,17 @@ 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) } } From fd6281165d62689e35c5914de7a7a74e1aebcec1 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Wed, 11 Dec 2024 10:11:14 +0100 Subject: [PATCH 16/31] Bump the runtime version in test --- Tests/OpenAPIGeneratorReferenceTests/CompatabilityTest.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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")])] ) """ From 21e5f059ef8a9a5285f62eb07e6f672bc765a2f5 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Mon, 16 Dec 2024 08:30:31 +0100 Subject: [PATCH 17/31] Reformatted --- .../Translator/Operations/OperationDescription.swift | 2 +- .../Translator/TypeAssignment/TypeAssigner.swift | 3 ++- Sources/swift-openapi-generator/Extensions.swift | 5 +---- .../Extensions/Test_SwiftSafeNames.swift | 12 ++++++++++-- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/Operations/OperationDescription.swift b/Sources/_OpenAPIGeneratorCore/Translator/Operations/OperationDescription.swift index 3626af27..50535d37 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Operations/OperationDescription.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Operations/OperationDescription.swift @@ -299,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, .noncapitalized)) + .identifierPattern("input").dot("path").dot(context.asSwiftSafeName(param, .noncapitalized)) } let arrayExpr: Expression = .literal(.array(names)) return (newPath.rawValue, arrayExpr) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift index 5ecf852c..6077a06f 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift @@ -536,7 +536,8 @@ struct TypeAssigner { guard !params.isEmpty else { return prefix } let safedParams = params.map { pair in - pair.split(separator: "=").map { context.asSwiftSafeName(String($0), .noncapitalized) }.joined(separator: "_") + pair.split(separator: "=").map { context.asSwiftSafeName(String($0), .noncapitalized) } + .joined(separator: "_") } .joined(separator: "_") return prefix + "_" + safedParams diff --git a/Sources/swift-openapi-generator/Extensions.swift b/Sources/swift-openapi-generator/Extensions.swift index a3f27c2f..76f43361 100644 --- a/Sources/swift-openapi-generator/Extensions.swift +++ b/Sources/swift-openapi-generator/Extensions.swift @@ -47,10 +47,7 @@ extension _UserConfig { .init( generate: [.types, .client], accessModifier: .internal, - filter: .init(operations: [ - "listPets", - "createPet" - ]), + filter: .init(operations: ["listPets", "createPet"]), namingStrategy: .idiomatic ) } diff --git a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift index 8142d5ec..55507a62 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift @@ -125,7 +125,11 @@ final class Test_SwiftSafeNames: Test_Core { let translator = makeTranslator(nameOverrides: ["MEGA": "m_e_g_a"]) let asSwiftSafeName: (String, SwiftNameOptions) -> String = translator.context.asSwiftSafeName for (input, sanitizedDefensive, _, _) in cases { - XCTAssertEqual(asSwiftSafeName(input, .noncapitalized), sanitizedDefensive, "Defensive, input: \(input)") + XCTAssertEqual( + asSwiftSafeName(input, .noncapitalized), + sanitizedDefensive, + "Defensive, input: \(input)" + ) } } do { @@ -133,7 +137,11 @@ final class Test_SwiftSafeNames: Test_Core { let asSwiftSafeName: (String, SwiftNameOptions) -> String = translator.context.asSwiftSafeName for (input, _, idiomaticUpper, idiomaticLower) in cases { XCTAssertEqual(asSwiftSafeName(input, .capitalized), idiomaticUpper, "Idiomatic upper, input: \(input)") - XCTAssertEqual(asSwiftSafeName(input, .noncapitalized), idiomaticLower, "Idiomatic lower, input: \(input)") + XCTAssertEqual( + asSwiftSafeName(input, .noncapitalized), + idiomaticLower, + "Idiomatic lower, input: \(input)" + ) } } } From 3da75b89f9a5e0b3a94b4078a61dcda3e9194fea Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Mon, 16 Dec 2024 14:03:27 +0100 Subject: [PATCH 18/31] Apply suggestions from code review Co-authored-by: Si Beaumont --- Sources/_OpenAPIGeneratorCore/Config.swift | 4 ---- .../Documentation.docc/Articles/Configuring-the-generator.md | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Config.swift b/Sources/_OpenAPIGeneratorCore/Config.swift index d15e58a0..3eb9dbc1 100644 --- a/Sources/_OpenAPIGeneratorCore/Config.swift +++ b/Sources/_OpenAPIGeneratorCore/Config.swift @@ -17,15 +17,11 @@ public enum NamingStrategy: String, Sendable, Codable, Equatable { /// A defensive strategy that can handle any OpenAPI identifier and produce a non-conflicting Swift identifier. /// - /// This strategy is the default in Swift OpenAPI Generator 1.x. - /// /// 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. /// - /// Opt-in since Swift OpenAPI Generator 1.6.0. - /// /// Introduced in [SOAR-0013](https://swiftpackageindex.com/apple/swift-openapi-generator/documentation/swift-openapi-generator/soar-0013). case idiomatic } 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 513f178f..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 @@ -40,9 +40,9 @@ The configuration file has the following keys: - `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. Customizes the strategy of converting OpenAPI identifiers into Swift identifiers. +- `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. + - `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). From cda4a4c552c6d2d8763f5c8867ee3eb61f724c1f Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Mon, 16 Dec 2024 14:03:56 +0100 Subject: [PATCH 19/31] Additional test cases --- .../Extensions/Test_SwiftSafeNames.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift index 55507a62..ca44d585 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift @@ -35,6 +35,7 @@ final class Test_SwiftSafeNames: Test_Core { // All uppercase ("HELLO_WORLD", "HELLO_WORLD", "HelloWorld", "helloWorld"), + ("HELLO", "HELLO", "Hello", "hello"), // Acronyms ("HTTPProxy", "HTTPProxy", "HTTPProxy", "httpProxy"), @@ -89,10 +90,11 @@ final class Test_SwiftSafeNames: Test_Core { ("invalidNam?", "invalidNam_quest_", "invalidNam_quest_", "invalidNam_quest_"), // Preserve leading underscores - ("__user", "__user", "__User", "__user"), + ("__user_name", "__user_name", "__UserName", "__userName"), // Preserve only leading underscores ("user__name", "user__name", "UserName", "userName"), + // Invalid underscore case ("_", "_underscore_", "_underscore_", "_underscore_"), @@ -107,6 +109,7 @@ final class Test_SwiftSafeNames: Test_Core { // Non Latin Characters combined with a RTL language ("$مرحبا", "_dollar_مرحبا", "_dollar_مرحبا", "_dollar_مرحبا"), + // Emoji ("heart❤️emoji", "heart_x2764_️emoji", "heart_x2764_️emoji", "heart_x2764_️emoji"), From 69de944fa3226e2fe764b9ce74c5aabef0a7042f Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Mon, 16 Dec 2024 16:44:18 +0100 Subject: [PATCH 20/31] Some PR feedback, some temporary updates to snippet reference tests (will be moved around in subsequent commits, not final --- Sources/_OpenAPIGeneratorCore/Config.swift | 8 ++--- .../Translator/FileTranslator.swift | 18 +++++++---- .../TypeAssignment/TypeAssigner.swift | 31 ++++++++++++++----- .../FilterCommand.swift | 4 +-- .../GenerateOptions.swift | 8 +++++ .../TestUtilities.swift | 6 ++-- .../Test_OperationDescription.swift | 5 ++- .../SnippetBasedReferenceTests.swift | 16 +++++----- 8 files changed, 63 insertions(+), 33 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Config.swift b/Sources/_OpenAPIGeneratorCore/Config.swift index 3eb9dbc1..f65b0ea1 100644 --- a/Sources/_OpenAPIGeneratorCore/Config.swift +++ b/Sources/_OpenAPIGeneratorCore/Config.swift @@ -52,10 +52,10 @@ public struct Config: Sendable { /// The naming strategy to use for deriving Swift identifiers from OpenAPI identifiers. /// /// Defaults to `defensive`. - public var namingStrategy: NamingStrategy? + public var namingStrategy: NamingStrategy /// A map of OpenAPI identifiers to desired Swift identifiers, used instead of the naming strategy. - public var nameOverrides: [String: String]? + public var nameOverrides: [String: String] /// Additional pre-release features to enable. public var featureFlags: FeatureFlags @@ -76,8 +76,8 @@ public struct Config: Sendable { access: AccessModifier, additionalImports: [String] = [], filter: DocumentFilter? = nil, - namingStrategy: NamingStrategy? = nil, - nameOverrides: [String: String]? = nil, + namingStrategy: NamingStrategy = .defensive, + nameOverrides: [String: String] = [:], featureFlags: FeatureFlags = [] ) { self.mode = mode diff --git a/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift b/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift index 8794e248..0068cefc 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift @@ -50,14 +50,17 @@ extension FileTranslator { var context: TranslatorContext { let asSwiftSafeName: (String, SwiftNameOptions) -> String switch config.namingStrategy { - case .defensive, .none: asSwiftSafeName = { $0.safeForSwiftCode_defensive(options: $1) } + case .defensive: asSwiftSafeName = { $0.safeForSwiftCode_defensive(options: $1) } case .idiomatic: asSwiftSafeName = { $0.safeForSwiftCode_idiomatic(options: $1) } } - let overrides = config.nameOverrides ?? [:] - return TranslatorContext(asSwiftSafeName: { name, options in - if let override = overrides[name] { return override } - return asSwiftSafeName(name, options) - }) + let overrides = config.nameOverrides + return TranslatorContext( + asSwiftSafeName: { name, options in + if let override = overrides[name] { return override } + return asSwiftSafeName(name, options) + }, + namingStrategy: config.namingStrategy + ) } } @@ -69,4 +72,7 @@ struct TranslatorContext { /// - Parameter string: The string to convert to be safe for Swift. /// - Returns: A Swift-safe version of the input string. var asSwiftSafeName: (String, SwiftNameOptions) -> String + + /// The naming strategy. + var namingStrategy: NamingStrategy } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift index 6077a06f..d4ebb3af 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift @@ -531,19 +531,34 @@ struct TypeAssigner { default: let safedType = context.asSwiftSafeName(contentType.originallyCasedType, .noncapitalized) let safedSubtype = context.asSwiftSafeName(contentType.originallyCasedSubtype, .noncapitalized) - let prefix = "\(safedType)_\(safedSubtype)" + let componentSeparator: String + let capitalizeNonFirstWords: Bool + switch context.namingStrategy { + case .defensive: + componentSeparator = "_" + capitalizeNonFirstWords = false + case .idiomatic: + componentSeparator = "" + capitalizeNonFirstWords = true + } + let prettifiedSubtype = capitalizeNonFirstWords ? safedSubtype.capitalized : safedSubtype + let prefix = "\(safedType)\(componentSeparator)\(prettifiedSubtype)" let params = contentType.lowercasedParameterPairs guard !params.isEmpty else { return prefix } let safedParams = - params.map { pair in - pair.split(separator: "=").map { context.asSwiftSafeName(String($0), .noncapitalized) } - .joined(separator: "_") - } - .joined(separator: "_") - return prefix + "_" + safedParams + params.map { pair in + pair + .split(separator: "=") + .map { component in + let safedComponent = context.asSwiftSafeName(String(component), .noncapitalized) + return capitalizeNonFirstWords ? safedComponent.capitalized : safedComponent + } + .joined(separator: componentSeparator) + } + .joined(separator: componentSeparator) + return prefix + componentSeparator + safedParams } } - } extension FileTranslator { diff --git a/Sources/swift-openapi-generator/FilterCommand.swift b/Sources/swift-openapi-generator/FilterCommand.swift index 4546851b..a9b88f6f 100644 --- a/Sources/swift-openapi-generator/FilterCommand.swift +++ b/Sources/swift-openapi-generator/FilterCommand.swift @@ -87,9 +87,7 @@ private let sampleConfig = _UserConfig( tags: ["greetings"], paths: ["/greeting"], schemas: ["Greeting"] - ), - namingStrategy: .idiomatic, - nameOverrides: ["SPECIAL_NAME": "SpecialName"] + ) ) enum OutputFormat: String, ExpressibleByArgument { diff --git a/Sources/swift-openapi-generator/GenerateOptions.swift b/Sources/swift-openapi-generator/GenerateOptions.swift index 9ad724c3..deaf69c2 100644 --- a/Sources/swift-openapi-generator/GenerateOptions.swift +++ b/Sources/swift-openapi-generator/GenerateOptions.swift @@ -75,8 +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/Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift b/Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift index ab87d1d8..c4603591 100644 --- a/Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift +++ b/Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift @@ -28,7 +28,7 @@ class Test_Core: XCTestCase { func makeTranslator( components: OpenAPI.Components = .noComponents, diagnostics: any DiagnosticCollector = PrintingDiagnosticCollector(), - namingStrategy: NamingStrategy? = nil, + namingStrategy: NamingStrategy = .defensive, nameOverrides: [String: String] = [:], featureFlags: FeatureFlags = [] ) -> TypesFileTranslator { @@ -44,7 +44,7 @@ class Test_Core: XCTestCase { func makeTypesTranslator( components: OpenAPI.Components = .noComponents, diagnostics: any DiagnosticCollector = PrintingDiagnosticCollector(), - namingStrategy: NamingStrategy? = nil, + namingStrategy: NamingStrategy = .defensive, nameOverrides: [String: String] = [:], featureFlags: FeatureFlags = [] ) -> TypesFileTranslator { @@ -60,7 +60,7 @@ class Test_Core: XCTestCase { } func makeConfig( - namingStrategy: NamingStrategy? = nil, + namingStrategy: NamingStrategy = .defensive, nameOverrides: [String: String] = [:], featureFlags: FeatureFlags = [] ) -> Config { diff --git a/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift b/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift index 7f69d464..72424b65 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift @@ -144,7 +144,10 @@ final class Test_OperationDescription: Test_Core { endpoint: endpoint, pathParameters: pathItem.parameters, components: .init(), - context: .init(asSwiftSafeName: { string, _ in string }) + context: .init( + asSwiftSafeName: { string, _ in string }, + namingStrategy: .defensive + ) ) } } diff --git a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift index 5a3cfca9..44490403 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift @@ -3106,7 +3106,7 @@ final class SnippetBasedReferenceTests: XCTestCase { public enum RequestBodies { @frozen public enum MultipleContentTypes: Sendable, Hashable { case json(Swift.Int) - case application_json_foo_bar(Swift.Int) + case applicationJsonFooBar(Swift.Int) case plainText(OpenAPIRuntime.HTTPBody) case binary(OpenAPIRuntime.HTTPBody) } @@ -3131,7 +3131,7 @@ final class SnippetBasedReferenceTests: XCTestCase { headerFields: &request.headerFields, contentType: "application/json; charset=utf-8" ) - case let .application_json_foo_bar(value): + case let .applicationJsonFooBar(value): body = try converter.setRequiredRequestBodyAsJSON( value, headerFields: &request.headerFields, @@ -3180,7 +3180,7 @@ final class SnippetBasedReferenceTests: XCTestCase { Swift.Int.self, from: requestBody, transforming: { value in - .application_json_foo_bar(value) + .applicationJsonFooBar(value) } ) case "text/plain": @@ -3268,11 +3268,11 @@ final class SnippetBasedReferenceTests: XCTestCase { } } } - case application_json_foo_bar(Swift.Int) - public var application_json_foo_bar: Swift.Int { + case applicationJsonFooBar(Swift.Int) + public var applicationJsonFooBar: Swift.Int { get throws { switch self { - case let .application_json_foo_bar(body): + case let .applicationJsonFooBar(body): return body default: try throwUnexpectedResponseBody( @@ -3337,7 +3337,7 @@ final class SnippetBasedReferenceTests: XCTestCase { headerFields: &response.headerFields, contentType: "application/json; charset=utf-8" ) - case let .application_json_foo_bar(value): + case let .applicationJsonFooBar(value): try converter.validateAcceptIfPresent( "application/json; foo=bar", in: request.headerFields @@ -3401,7 +3401,7 @@ final class SnippetBasedReferenceTests: XCTestCase { Swift.Int.self, from: responseBody, transforming: { value in - .application_json_foo_bar(value) + .applicationJsonFooBar(value) } ) case "text/plain": From dbcd035babe64649322dcd2426ee90749e6fb8b0 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Mon, 16 Dec 2024 16:46:11 +0100 Subject: [PATCH 21/31] Remove SwiftNameOptions.Capitalization --- .../CommonTranslations/SwiftSafeNames.swift | 21 ++++--------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift index 5ca75be6..ecca89ba 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift @@ -16,27 +16,14 @@ import Foundation /// Extra context for the `safeForSwiftCode_` family of functions to produce more appropriate Swift identifiers. struct SwiftNameOptions { - /// An option for controlling capitalization. - /// - /// Generally, type names are capitalized, for example: `Foo`. - /// And member names are not capitalized, for example: `foo`. - enum Capitalization { - - /// Capitalize the name, used for type names, for example: `Foo`. - case capitalized - - /// Don't capitalize the name, used for member names, for example: `foo`. - case noncapitalized - } - /// The capitalization option. - var capitalization: Capitalization + var isCapitalized: Bool /// Preset options for capitalized names. - static let capitalized = SwiftNameOptions(capitalization: .capitalized) + static let capitalized = SwiftNameOptions(isCapitalized: true) /// Preset options for non-capitalized names. - static let noncapitalized = SwiftNameOptions(capitalization: .noncapitalized) + static let noncapitalized = SwiftNameOptions(isCapitalized: false) } extension String { @@ -101,7 +88,7 @@ extension String { /// /// Check out [SOAR-0013](https://swiftpackageindex.com/apple/swift-openapi-generator/documentation/swift-openapi-generator/soar-0013) for details. func safeForSwiftCode_idiomatic(options: SwiftNameOptions) -> String { - let capitalize = options.capitalization == .capitalized + let capitalize = options.isCapitalized if isEmpty { return capitalize ? "_Empty_" : "_empty_" } // Detect cases like HELLO_WORLD, sometimes used for constants. let isAllUppercase = allSatisfy { From 8567ad38409e422b4b0132a2225b44e32d8a430e Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Mon, 16 Dec 2024 16:51:53 +0100 Subject: [PATCH 22/31] Undo changes to tutorial Package.swift --- .../Tutorials/_Resources/client.Package.0.swift | 2 +- .../Tutorials/_Resources/client.Package.1.swift | 2 +- .../Tutorials/_Resources/client.Package.2.swift | 2 +- .../Tutorials/_Resources/client.Package.3.swift | 2 +- .../Tutorials/_Resources/client.Package.4.swift | 2 +- .../Tutorials/_Resources/client.Package.5.swift | 2 +- .../Tutorials/_Resources/server.Package.0.swift | 2 +- .../Tutorials/_Resources/server.Package.1.swift | 2 +- .../Tutorials/_Resources/server.Package.2.swift | 2 +- .../Tutorials/_Resources/server.Package.3.swift | 2 +- .../Tutorials/_Resources/server.Package.4.swift | 2 +- .../Tutorials/_Resources/server.Package.5.swift | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.0.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.0.swift index 2f4286f1..3596a147 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.0.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.0.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 5.9 import PackageDescription let package = Package( diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.1.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.1.swift index eef1320a..f92bb26c 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.1.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/client.Package.1.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 5.9 import PackageDescription let package = Package( 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 a580365c..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 @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 5.9 import PackageDescription let package = Package( 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 249f8f72..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 @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 5.9 import PackageDescription let package = Package( 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 35a36e77..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 @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 5.9 import PackageDescription let package = Package( 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 35a36e77..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 @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 5.9 import PackageDescription let package = Package( diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.0.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.0.swift index e6db09f1..48155db2 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.0.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.0.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 5.9 import PackageDescription let package = Package( diff --git a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.1.swift b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.1.swift index 081a77a7..aa31ee69 100644 --- a/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.1.swift +++ b/Sources/swift-openapi-generator/Documentation.docc/Tutorials/_Resources/server.Package.1.swift @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 5.9 import PackageDescription let package = Package( 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 a0d063fb..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 @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 5.9 import PackageDescription let package = Package( 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 c57eb61f..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 @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 5.9 import PackageDescription let package = Package( 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 0456c054..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 @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 5.9 import PackageDescription let package = Package( 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 0456c054..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 @@ -1,4 +1,4 @@ -// swift-tools-version: 6.0 +// swift-tools-version: 5.9 import PackageDescription let package = Package( From 96f90be0aa096af9587f0aefa7ed107f64a194bd Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Mon, 16 Dec 2024 16:53:26 +0100 Subject: [PATCH 23/31] Undo changes to the filter command --- Sources/swift-openapi-generator/FilterCommand.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/swift-openapi-generator/FilterCommand.swift b/Sources/swift-openapi-generator/FilterCommand.swift index a9b88f6f..bb689b50 100644 --- a/Sources/swift-openapi-generator/FilterCommand.swift +++ b/Sources/swift-openapi-generator/FilterCommand.swift @@ -81,7 +81,7 @@ private func timing(_ title: String, _ operation: @autoclosure () throws } private let sampleConfig = _UserConfig( - generate: [.types, .client], + generate: [], filter: DocumentFilter( operations: ["getGreeting"], tags: ["greetings"], From bd28e532859325d23aef2815cc420a60dd330574 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Mon, 16 Dec 2024 19:12:46 +0100 Subject: [PATCH 24/31] Added + as a word separator --- .../CommonTranslations/SwiftSafeNames.swift | 11 +++--- .../TypeAssignment/TypeAssigner.swift | 4 +- .../TypeAssignment/Test_TypeAssigner.swift | 38 ++++++++++++------- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift index ecca89ba..3cfbd773 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift @@ -98,7 +98,8 @@ extension String { } // 1. Leave leading underscores as-are - // 2. In the middle: word separators: ["_", "-", "/", ] -> remove and capitalize next word + // 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 "" @@ -188,7 +189,7 @@ extension String { buffer.append(char) } state = .accumulatingFirstWord(context) - } else if ["_", "-", " ", "/"].contains(char) { + } 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 @@ -218,14 +219,14 @@ extension String { buffer.append("_") state = .accumulatingWord } else if ["{", "}"].contains(char) { - // In the middle of an identifier, curly braces are dropped. + // In the middle of an identifier, these are dropped. state = .accumulatingWord } else { // Illegal character, fall back to the defensive strategy. state = .fallback } case .waitingForWordStarter: - if ["_", "-", ".", "/", "{", "}"].contains(char) { + if ["_", "-", ".", "/", "+", "{", "}"].contains(char) { // Between words, just drop allowed special characters, since // we're already between words anyway. state = .waitingForWordStarter @@ -250,7 +251,7 @@ extension String { } /// A list of word separator characters for the idiomatic naming strategy. - private static let wordSeparators: Set = ["_", "-", " ", "/"] + private static let wordSeparators: Set = ["_", "-", " ", "/", "+"] /// A list of Swift keywords. /// diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift index d4ebb3af..dd9f0309 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift @@ -541,7 +541,7 @@ struct TypeAssigner { componentSeparator = "" capitalizeNonFirstWords = true } - let prettifiedSubtype = capitalizeNonFirstWords ? safedSubtype.capitalized : safedSubtype + let prettifiedSubtype = capitalizeNonFirstWords ? safedSubtype.uppercasingFirstLetter : safedSubtype let prefix = "\(safedType)\(componentSeparator)\(prettifiedSubtype)" let params = contentType.lowercasedParameterPairs guard !params.isEmpty else { return prefix } @@ -551,7 +551,7 @@ struct TypeAssigner { .split(separator: "=") .map { component in let safedComponent = context.asSwiftSafeName(String(component), .noncapitalized) - return capitalizeNonFirstWords ? safedComponent.capitalized : safedComponent + return capitalizeNonFirstWords ? safedComponent.uppercasingFirstLetter : safedComponent } .joined(separator: componentSeparator) } diff --git a/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeAssigner.swift b/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeAssigner.swift index 745a65e0..6ee66e10 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeAssigner.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeAssigner.swift @@ -105,27 +105,39 @@ class Test_TypeAssigner: Test_Core { } func testContentSwiftName() throws { - let nameMaker = makeTranslator().typeAssigner.contentSwiftName - let cases: [(String, String)] = [ + let defensiveNameMaker = makeTranslator().typeAssigner.contentSwiftName + let idiomaticNameMaker = makeTranslator(namingStrategy: .idiomatic).typeAssigner.contentSwiftName + 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") } } } From 4cb7b1418c2f94634d6dccf0f8c853aca1b826f4 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Tue, 17 Dec 2024 09:09:04 +0100 Subject: [PATCH 25/31] Revert snippet tests changes, add only specific new tests --- .../SnippetBasedReferenceTests.swift | 603 +++++++++--------- 1 file changed, 315 insertions(+), 288 deletions(-) diff --git a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift index 44490403..42373a3b 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift @@ -849,20 +849,20 @@ final class SnippetBasedReferenceTests: XCTestCase { try self.assertSchemasTranslation( """ schemas: - Aaa: + A: type: object properties: which: type: string - Bbb: + B: type: object properties: which: type: string MyOneOf: oneOf: - - $ref: '#/components/schemas/Aaa' - - $ref: '#/components/schemas/Bbb' + - $ref: '#/components/schemas/A' + - $ref: '#/components/schemas/B' - type: string - type: object properties: @@ -873,7 +873,7 @@ final class SnippetBasedReferenceTests: XCTestCase { """, """ public enum Schemas { - public struct Aaa: Codable, Hashable, Sendable { + public struct A: Codable, Hashable, Sendable { public var which: Swift.String? public init(which: Swift.String? = nil) { self.which = which @@ -882,7 +882,7 @@ final class SnippetBasedReferenceTests: XCTestCase { case which } } - public struct Bbb: Codable, Hashable, Sendable { + public struct B: Codable, Hashable, Sendable { public var which: Swift.String? public init(which: Swift.String? = nil) { self.which = which @@ -892,8 +892,8 @@ final class SnippetBasedReferenceTests: XCTestCase { } } @frozen public enum MyOneOf: Codable, Hashable, Sendable { - case aaa(Components.Schemas.Aaa) - case bbb(Components.Schemas.Bbb) + case A(Components.Schemas.A) + case B(Components.Schemas.B) public enum CodingKeys: String, CodingKey { case which } @@ -904,10 +904,10 @@ final class SnippetBasedReferenceTests: XCTestCase { forKey: .which ) switch discriminator { - case "Aaa", "#/components/schemas/Aaa": - self = .aaa(try .init(from: decoder)) - case "Bbb", "#/components/schemas/Bbb": - self = .bbb(try .init(from: decoder)) + case "A", "#/components/schemas/A": + self = .A(try .init(from: decoder)) + case "B", "#/components/schemas/B": + self = .B(try .init(from: decoder)) default: throw Swift.DecodingError.unknownOneOfDiscriminator( discriminatorKey: CodingKeys.which, @@ -918,9 +918,9 @@ final class SnippetBasedReferenceTests: XCTestCase { } public func encode(to encoder: any Encoder) throws { switch self { - case let .aaa(value): + case let .A(value): try value.encode(to: encoder) - case let .bbb(value): + case let .B(value): try value.encode(to: encoder) } } @@ -934,36 +934,36 @@ final class SnippetBasedReferenceTests: XCTestCase { try self.assertSchemasTranslation( """ schemas: - Aaa: + A: type: object properties: which: type: string - Bbb: + B: type: object properties: which: type: string - Ccc: + C: type: object properties: which: type: string MyOneOf: oneOf: - - $ref: '#/components/schemas/Aaa' - - $ref: '#/components/schemas/Bbb' - - $ref: '#/components/schemas/Ccc' + - $ref: '#/components/schemas/A' + - $ref: '#/components/schemas/B' + - $ref: '#/components/schemas/C' discriminator: propertyName: which mapping: - a: '#/components/schemas/Aaa' - a2: '#/components/schemas/Aaa' - b: '#/components/schemas/Bbb' + a: '#/components/schemas/A' + a2: '#/components/schemas/A' + b: '#/components/schemas/B' """, """ public enum Schemas { - public struct Aaa: Codable, Hashable, Sendable { + public struct A: Codable, Hashable, Sendable { public var which: Swift.String? public init(which: Swift.String? = nil) { self.which = which @@ -972,7 +972,7 @@ final class SnippetBasedReferenceTests: XCTestCase { case which } } - public struct Bbb: Codable, Hashable, Sendable { + public struct B: Codable, Hashable, Sendable { public var which: Swift.String? public init(which: Swift.String? = nil) { self.which = which @@ -981,7 +981,7 @@ final class SnippetBasedReferenceTests: XCTestCase { case which } } - public struct Ccc: Codable, Hashable, Sendable { + public struct C: Codable, Hashable, Sendable { public var which: Swift.String? public init(which: Swift.String? = nil) { self.which = which @@ -991,10 +991,10 @@ final class SnippetBasedReferenceTests: XCTestCase { } } @frozen public enum MyOneOf: Codable, Hashable, Sendable { - case a(Components.Schemas.Aaa) - case a2(Components.Schemas.Aaa) - case b(Components.Schemas.Bbb) - case ccc(Components.Schemas.Ccc) + case a(Components.Schemas.A) + case a2(Components.Schemas.A) + case b(Components.Schemas.B) + case C(Components.Schemas.C) public enum CodingKeys: String, CodingKey { case which } @@ -1011,8 +1011,8 @@ final class SnippetBasedReferenceTests: XCTestCase { self = .a2(try .init(from: decoder)) case "b": self = .b(try .init(from: decoder)) - case "Ccc", "#/components/schemas/Ccc": - self = .ccc(try .init(from: decoder)) + case "C", "#/components/schemas/C": + self = .C(try .init(from: decoder)) default: throw Swift.DecodingError.unknownOneOfDiscriminator( discriminatorKey: CodingKeys.which, @@ -1029,7 +1029,7 @@ final class SnippetBasedReferenceTests: XCTestCase { try value.encode(to: encoder) case let .b(value): try value.encode(to: encoder) - case let .ccc(value): + case let .C(value): try value.encode(to: encoder) } } @@ -1239,24 +1239,24 @@ final class SnippetBasedReferenceTests: XCTestCase { try self.assertSchemasTranslation( """ schemas: - Aaa: + A: type: string - Bbb: + B: type: object required: - - ccc + - c properties: - ccc: + c: allOf: - - $ref: "#/components/schemas/Aaa" + - $ref: "#/components/schemas/A" """, """ public enum Schemas { - public typealias Aaa = Swift.String - public struct Bbb: Codable, Hashable, Sendable { - public struct CccPayload: Codable, Hashable, Sendable { - public var value1: Components.Schemas.Aaa - public init(value1: Components.Schemas.Aaa) { + public typealias A = Swift.String + public struct B: Codable, Hashable, Sendable { + public struct cPayload: Codable, Hashable, Sendable { + public var value1: Components.Schemas.A + public init(value1: Components.Schemas.A) { self.value1 = value1 } public init(from decoder: any Decoder) throws { @@ -1266,12 +1266,12 @@ final class SnippetBasedReferenceTests: XCTestCase { try encoder.encodeToSingleValueContainer(self.value1) } } - public var ccc: Components.Schemas.Bbb.CccPayload - public init(ccc: Components.Schemas.Bbb.CccPayload) { - self.ccc = ccc + public var c: Components.Schemas.B.cPayload + public init(c: Components.Schemas.B.cPayload) { + self.c = c } public enum CodingKeys: String, CodingKey { - case ccc + case c } } } @@ -1283,23 +1283,23 @@ final class SnippetBasedReferenceTests: XCTestCase { try self.assertSchemasTranslation( """ schemas: - Aaa: + A: type: string - Bbb: + B: type: object required: [] properties: - ccc: + c: allOf: - - $ref: "#/components/schemas/Aaa" + - $ref: "#/components/schemas/A" """, """ public enum Schemas { - public typealias Aaa = Swift.String - public struct Bbb: Codable, Hashable, Sendable { - public struct CccPayload: Codable, Hashable, Sendable { - public var value1: Components.Schemas.Aaa - public init(value1: Components.Schemas.Aaa) { + public typealias A = Swift.String + public struct B: Codable, Hashable, Sendable { + public struct cPayload: Codable, Hashable, Sendable { + public var value1: Components.Schemas.A + public init(value1: Components.Schemas.A) { self.value1 = value1 } public init(from decoder: any Decoder) throws { @@ -1309,12 +1309,12 @@ final class SnippetBasedReferenceTests: XCTestCase { try encoder.encodeToSingleValueContainer(self.value1) } } - public var ccc: Components.Schemas.Bbb.CccPayload? - public init(ccc: Components.Schemas.Bbb.CccPayload? = nil) { - self.ccc = ccc + public var c: Components.Schemas.B.cPayload? + public init(c: Components.Schemas.B.cPayload? = nil) { + self.c = c } public enum CodingKeys: String, CodingKey { - case ccc + case c } } } @@ -1338,7 +1338,7 @@ final class SnippetBasedReferenceTests: XCTestCase { public enum Schemas { @frozen public enum MyEnum: String, Codable, Hashable, Sendable, CaseIterable { case one = "one" - case _empty_ = "" + case _empty = "" case _dollar_tart = "$tart" case _public = "public" } @@ -1647,7 +1647,7 @@ final class SnippetBasedReferenceTests: XCTestCase { yield &self.storage.value.name } } - public struct ParentPayload: Codable, Hashable, Sendable { + public struct parentPayload: Codable, Hashable, Sendable { public var nested: Components.Schemas.Node public init(nested: Components.Schemas.Node) { self.nested = nested @@ -1656,7 +1656,7 @@ final class SnippetBasedReferenceTests: XCTestCase { case nested } } - public var parent: Components.Schemas.Node.ParentPayload? { + public var parent: Components.Schemas.Node.parentPayload? { get { self.storage.value.parent } @@ -1666,7 +1666,7 @@ final class SnippetBasedReferenceTests: XCTestCase { } public init( name: Swift.String, - parent: Components.Schemas.Node.ParentPayload? = nil + parent: Components.Schemas.Node.parentPayload? = nil ) { self.storage = .init(value: .init( name: name, @@ -1686,7 +1686,7 @@ final class SnippetBasedReferenceTests: XCTestCase { private var storage: OpenAPIRuntime.CopyOnWriteBox private struct Storage: Codable, Hashable, Sendable { var name: Swift.String - struct ParentPayload: Codable, Hashable, Sendable { + struct parentPayload: Codable, Hashable, Sendable { public var nested: Components.Schemas.Node public init(nested: Components.Schemas.Node) { self.nested = nested @@ -1695,10 +1695,10 @@ final class SnippetBasedReferenceTests: XCTestCase { case nested } } - var parent: Components.Schemas.Node.ParentPayload? + var parent: Components.Schemas.Node.parentPayload? init( name: Swift.String, - parent: Components.Schemas.Node.ParentPayload? = nil + parent: Components.Schemas.Node.parentPayload? = nil ) { self.name = name self.parent = parent @@ -1983,9 +1983,9 @@ final class SnippetBasedReferenceTests: XCTestCase { public enum Responses { public struct BadRequest: Sendable, Hashable { public struct Headers: Sendable, Hashable { - public var xReason: Swift.String? - public init(xReason: Swift.String? = nil) { - self.xReason = xReason + public var X_hyphen_Reason: Swift.String? + public init(X_hyphen_Reason: Swift.String? = nil) { + self.X_hyphen_Reason = X_hyphen_Reason } } public var headers: Components.Responses.BadRequest.Headers @@ -2015,12 +2015,12 @@ final class SnippetBasedReferenceTests: XCTestCase { public enum Responses { public struct BadRequest: Sendable, Hashable { public struct Headers: Sendable, Hashable { - @frozen public enum XReasonPayload: String, Codable, Hashable, Sendable, CaseIterable { + @frozen public enum X_hyphen_ReasonPayload: String, Codable, Hashable, Sendable, CaseIterable { case badLuck = "badLuck" } - public var xReason: Components.Responses.BadRequest.Headers.XReasonPayload? - public init(xReason: Components.Responses.BadRequest.Headers.XReasonPayload? = nil) { - self.xReason = xReason + public var X_hyphen_Reason: Components.Responses.BadRequest.Headers.X_hyphen_ReasonPayload? + public init(X_hyphen_Reason: Components.Responses.BadRequest.Headers.X_hyphen_ReasonPayload? = nil) { + self.X_hyphen_Reason = X_hyphen_Reason } } public var headers: Components.Responses.BadRequest.Headers @@ -2049,9 +2049,9 @@ final class SnippetBasedReferenceTests: XCTestCase { public enum Responses { public struct BadRequest: Sendable, Hashable { public struct Headers: Sendable, Hashable { - public var xReason: Swift.String - public init(xReason: Swift.String) { - self.xReason = xReason + public var X_hyphen_Reason: Swift.String + public init(X_hyphen_Reason: Swift.String) { + self.X_hyphen_Reason = X_hyphen_Reason } } public var headers: Components.Responses.BadRequest.Headers @@ -2145,7 +2145,7 @@ final class SnippetBasedReferenceTests: XCTestCase { """ public enum RequestBodies { @frozen public enum MyRequestBody: Sendable, Hashable { - public struct UrlEncodedFormPayload: Codable, Hashable, Sendable { + public struct urlEncodedFormPayload: Codable, Hashable, Sendable { public var foo: Swift.String public init(foo: Swift.String) { self.foo = foo @@ -2154,7 +2154,7 @@ final class SnippetBasedReferenceTests: XCTestCase { case foo } } - case urlEncodedForm(Components.RequestBodies.MyRequestBody.UrlEncodedFormPayload) + case urlEncodedForm(Components.RequestBodies.MyRequestBody.urlEncodedFormPayload) } } """ @@ -2202,31 +2202,31 @@ final class SnippetBasedReferenceTests: XCTestCase { #""" public enum RequestBodies { @frozen public enum MultipartUploadTypedRequest: Sendable, Hashable { - @frozen public enum MultipartFormPayload: Sendable, Hashable { - public struct LogPayload: Sendable, Hashable { + @frozen public enum multipartFormPayload: Sendable, Hashable { + public struct logPayload: Sendable, Hashable { public struct Headers: Sendable, Hashable { - @frozen public enum XLogTypePayload: String, Codable, Hashable, Sendable, CaseIterable { + @frozen public enum x_hyphen_log_hyphen_typePayload: String, Codable, Hashable, Sendable, CaseIterable { case structured = "structured" case unstructured = "unstructured" } - public var xLogType: Components.RequestBodies.MultipartUploadTypedRequest.MultipartFormPayload.LogPayload.Headers.XLogTypePayload? - public init(xLogType: Components.RequestBodies.MultipartUploadTypedRequest.MultipartFormPayload.LogPayload.Headers.XLogTypePayload? = nil) { - self.xLogType = xLogType + public var x_hyphen_log_hyphen_type: Components.RequestBodies.MultipartUploadTypedRequest.multipartFormPayload.logPayload.Headers.x_hyphen_log_hyphen_typePayload? + 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 } } - public var headers: Components.RequestBodies.MultipartUploadTypedRequest.MultipartFormPayload.LogPayload.Headers + public var headers: Components.RequestBodies.MultipartUploadTypedRequest.multipartFormPayload.logPayload.Headers public var body: OpenAPIRuntime.HTTPBody 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) - public struct MetadataPayload: Sendable, Hashable { - public struct BodyPayload: Codable, Hashable, Sendable { + case log(OpenAPIRuntime.MultipartPart) + public struct metadataPayload: Sendable, Hashable { + public struct bodyPayload: Codable, Hashable, Sendable { public var createdAt: Foundation.Date public init(createdAt: Foundation.Date) { self.createdAt = createdAt @@ -2235,22 +2235,22 @@ final class SnippetBasedReferenceTests: XCTestCase { case createdAt } } - public var body: Components.RequestBodies.MultipartUploadTypedRequest.MultipartFormPayload.MetadataPayload.BodyPayload - public init(body: Components.RequestBodies.MultipartUploadTypedRequest.MultipartFormPayload.MetadataPayload.BodyPayload) { + public var body: Components.RequestBodies.MultipartUploadTypedRequest.multipartFormPayload.metadataPayload.bodyPayload + public init(body: Components.RequestBodies.MultipartUploadTypedRequest.multipartFormPayload.metadataPayload.bodyPayload) { self.body = body } } - case metadata(OpenAPIRuntime.MultipartPart) - public struct KeywordPayload: Sendable, Hashable { + case metadata(OpenAPIRuntime.MultipartPart) + public struct keywordPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody public init(body: OpenAPIRuntime.HTTPBody) { self.body = body } } - case keyword(OpenAPIRuntime.MultipartPart) + case keyword(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } } """# @@ -2286,8 +2286,8 @@ final class SnippetBasedReferenceTests: XCTestCase { """ public protocol APIProtocol: Sendable { @available(*, deprecated) - func getHealthOld(_ input: Operations.GetHealthOld.Input) async throws -> Operations.GetHealthOld.Output - func getHealthNew(_ input: Operations.GetHealthNew.Input) async throws -> Operations.GetHealthNew.Output + func getHealthOld(_ input: Operations.getHealthOld.Input) async throws -> Operations.getHealthOld.Output + func getHealthNew(_ input: Operations.getHealthNew.Input) async throws -> Operations.getHealthNew.Output } """ ) @@ -2296,40 +2296,66 @@ final class SnippetBasedReferenceTests: XCTestCase { """ extension APIProtocol { @available(*, deprecated) - public func getHealthOld(headers: Operations.GetHealthOld.Input.Headers = .init()) async throws -> Operations.GetHealthOld.Output { - try await getHealthOld(Operations.GetHealthOld.Input(headers: headers)) + public func getHealthOld(headers: Operations.getHealthOld.Input.Headers = .init()) async throws -> Operations.getHealthOld.Output { + try await getHealthOld(Operations.getHealthOld.Input(headers: headers)) } - public func getHealthNew(headers: Operations.GetHealthNew.Input.Headers = .init()) async throws -> Operations.GetHealthNew.Output { - try await getHealthNew(Operations.GetHealthNew.Input(headers: headers)) + public func getHealthNew(headers: Operations.getHealthNew.Input.Headers = .init()) async throws -> Operations.getHealthNew.Output { + try await getHealthNew(Operations.getHealthNew.Input(headers: headers)) } } """ ) } - func testSynthesizedOperationId() throws { + + 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. - """ + /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 getPetsPetIdNotifications(_ input: Operations.GetPetsPetIdNotifications.Input) async throws -> Operations.GetPetsPetIdNotifications.Output - } - """ + """ + 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( """ @@ -2534,8 +2560,8 @@ final class SnippetBasedReferenceTests: XCTestCase { self.manyUnexploded = manyUnexploded } } - public var query: Operations.GetFoo.Input.Query - public init(query: Operations.GetFoo.Input.Query = .init()) { + public var query: Operations.get_sol_foo.Input.Query + public init(query: Operations.get_sol_foo.Input.Query = .init()) { self.query = query } } @@ -2577,7 +2603,7 @@ final class SnippetBasedReferenceTests: XCTestCase { """, server: """ { request, requestBody, metadata in - let query: Operations.GetFoo.Input.Query = .init( + let query: Operations.get_sol_foo.Input.Query = .init( single: try converter.getOptionalQueryItemAsURI( in: request.soar_query, style: .form, @@ -2600,7 +2626,7 @@ final class SnippetBasedReferenceTests: XCTestCase { as: [Swift.String].self ) ) - return Operations.GetFoo.Input(query: query) + return Operations.get_sol_foo.Input(query: query) } """ ) @@ -2648,8 +2674,8 @@ final class SnippetBasedReferenceTests: XCTestCase { self.num = num } } - public var path: Operations.GetFoo.Input.Path - public init(path: Operations.GetFoo.Input.Path) { + public var path: Operations.getFoo.Input.Path + public init(path: Operations.getFoo.Input.Path) { self.path = path } } @@ -2674,7 +2700,7 @@ final class SnippetBasedReferenceTests: XCTestCase { """, server: """ { request, requestBody, metadata in - let path: Operations.GetFoo.Input.Path = .init( + let path: Operations.getFoo.Input.Path = .init( b: try converter.getPathParameterAsURI( in: metadata.pathParameters, name: "b", @@ -2691,7 +2717,7 @@ final class SnippetBasedReferenceTests: XCTestCase { as: Swift.Int.self ) ) - return Operations.GetFoo.Input(path: path) + return Operations.getFoo.Input(path: path) } """ ) @@ -2716,13 +2742,13 @@ final class SnippetBasedReferenceTests: XCTestCase { input: """ public struct Input: Sendable, Hashable { public struct Path: Sendable, Hashable { - public var p_aB: Swift.String - public init(p_aB: Swift.String) { - self.p_aB = p_aB + public var p_period_a_hyphen_b: Swift.String + public init(p_period_a_hyphen_b: Swift.String) { + self.p_period_a_hyphen_b = p_period_a_hyphen_b } } - public var path: Operations.GetFoo.Input.Path - public init(path: Operations.GetFoo.Input.Path) { + public var path: Operations.getFoo.Input.Path + public init(path: Operations.getFoo.Input.Path) { self.path = path } } @@ -2732,7 +2758,7 @@ final class SnippetBasedReferenceTests: XCTestCase { let path = try converter.renderedPath( template: "/foo/{}", parameters: [ - input.path.p_aB + input.path.p_period_a_hyphen_b ] ) var request: HTTPTypes.HTTPRequest = .init( @@ -2745,12 +2771,12 @@ final class SnippetBasedReferenceTests: XCTestCase { """, server: """ { request, requestBody, metadata in - let path: Operations.GetFoo.Input.Path = .init(p_aB: try converter.getPathParameterAsURI( + let path: Operations.getFoo.Input.Path = .init(p_period_a_hyphen_b: try converter.getPathParameterAsURI( in: metadata.pathParameters, name: "p.a-b", as: Swift.String.self )) - return Operations.GetFoo.Input(path: path) + return Operations.getFoo.Input(path: path) } """ ) @@ -2776,8 +2802,8 @@ final class SnippetBasedReferenceTests: XCTestCase { @frozen public enum Body: Sendable, Hashable { case json(Swift.String) } - public var body: Operations.GetFoo.Input.Body - public init(body: Operations.GetFoo.Input.Body) { + public var body: Operations.get_sol_foo.Input.Body + public init(body: Operations.get_sol_foo.Input.Body) { self.body = body } } @@ -2808,7 +2834,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.GetFoo.Input.Body + let body: Operations.get_sol_foo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -2827,7 +2853,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.GetFoo.Input(body: body) + return Operations.get_sol_foo.Input(body: body) } """ ) @@ -2853,8 +2879,8 @@ final class SnippetBasedReferenceTests: XCTestCase { @frozen public enum Body: Sendable, Hashable { case json(Swift.String) } - public var body: Operations.GetFoo.Input.Body - public init(body: Operations.GetFoo.Input.Body) { + public var body: Operations.get_sol_foo.Input.Body + public init(body: Operations.get_sol_foo.Input.Body) { self.body = body } } @@ -2885,7 +2911,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.GetFoo.Input.Body + let body: Operations.get_sol_foo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -2904,7 +2930,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.GetFoo.Input(body: body) + return Operations.get_sol_foo.Input(body: body) } """ ) @@ -2930,8 +2956,8 @@ final class SnippetBasedReferenceTests: XCTestCase { @frozen public enum Body: Sendable, Hashable { case json(Swift.String) } - public var body: Operations.GetFoo.Input.Body? - public init(body: Operations.GetFoo.Input.Body? = nil) { + public var body: Operations.get_sol_foo.Input.Body? + public init(body: Operations.get_sol_foo.Input.Body? = nil) { self.body = body } } @@ -2964,7 +2990,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.GetFoo.Input.Body? + let body: Operations.get_sol_foo.Input.Body? let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -2983,7 +3009,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.GetFoo.Input(body: body) + return Operations.get_sol_foo.Input(body: body) } """ ) @@ -3009,8 +3035,8 @@ final class SnippetBasedReferenceTests: XCTestCase { @frozen public enum Body: Sendable, Hashable { case json(Swift.String) } - public var body: Operations.GetFoo.Input.Body? - public init(body: Operations.GetFoo.Input.Body? = nil) { + public var body: Operations.get_sol_foo.Input.Body? + public init(body: Operations.get_sol_foo.Input.Body? = nil) { self.body = body } } @@ -3043,7 +3069,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.GetFoo.Input.Body? + let body: Operations.get_sol_foo.Input.Body? let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -3062,7 +3088,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.GetFoo.Input(body: body) + return Operations.get_sol_foo.Input(body: body) } """ ) @@ -3106,7 +3132,7 @@ final class SnippetBasedReferenceTests: XCTestCase { public enum RequestBodies { @frozen public enum MultipleContentTypes: Sendable, Hashable { case json(Swift.Int) - case applicationJsonFooBar(Swift.Int) + case application_json_foo_bar(Swift.Int) case plainText(OpenAPIRuntime.HTTPBody) case binary(OpenAPIRuntime.HTTPBody) } @@ -3131,7 +3157,7 @@ final class SnippetBasedReferenceTests: XCTestCase { headerFields: &request.headerFields, contentType: "application/json; charset=utf-8" ) - case let .applicationJsonFooBar(value): + case let .application_json_foo_bar(value): body = try converter.setRequiredRequestBodyAsJSON( value, headerFields: &request.headerFields, @@ -3180,7 +3206,7 @@ final class SnippetBasedReferenceTests: XCTestCase { Swift.Int.self, from: requestBody, transforming: { value in - .applicationJsonFooBar(value) + .application_json_foo_bar(value) } ) case "text/plain": @@ -3202,7 +3228,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.GetFoo.Input(body: body) + return Operations.getFoo.Input(body: body) } """ ) @@ -3268,11 +3294,11 @@ final class SnippetBasedReferenceTests: XCTestCase { } } } - case applicationJsonFooBar(Swift.Int) - public var applicationJsonFooBar: Swift.Int { + case application_json_foo_bar(Swift.Int) + public var application_json_foo_bar: Swift.Int { get throws { switch self { - case let .applicationJsonFooBar(body): + case let .application_json_foo_bar(body): return body default: try throwUnexpectedResponseBody( @@ -3337,7 +3363,7 @@ final class SnippetBasedReferenceTests: XCTestCase { headerFields: &response.headerFields, contentType: "application/json; charset=utf-8" ) - case let .applicationJsonFooBar(value): + case let .application_json_foo_bar(value): try converter.validateAcceptIfPresent( "application/json; foo=bar", in: request.headerFields @@ -3401,7 +3427,7 @@ final class SnippetBasedReferenceTests: XCTestCase { Swift.Int.self, from: responseBody, transforming: { value in - .applicationJsonFooBar(value) + .application_json_foo_bar(value) } ) case "text/plain": @@ -3467,17 +3493,17 @@ final class SnippetBasedReferenceTests: XCTestCase { requestBodies: """ public enum RequestBodies { @frozen public enum MultipartRequest: Sendable, Hashable { - @frozen public enum MultipartFormPayload: Sendable, Hashable { - public struct LogPayload: Sendable, Hashable { + @frozen public enum multipartFormPayload: Sendable, Hashable { + public struct logPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody public init(body: OpenAPIRuntime.HTTPBody) { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } } """, @@ -3544,7 +3570,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -3585,7 +3611,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.PostFoo.Input(body: body) + return Operations.post_sol_foo.Input(body: body) } """ ) @@ -3612,20 +3638,20 @@ final class SnippetBasedReferenceTests: XCTestCase { input: """ public struct Input: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum MultipartFormPayload: Sendable, Hashable { - public struct LogPayload: Sendable, Hashable { + @frozen public enum multipartFormPayload: Sendable, Hashable { + public struct logPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody public init(body: OpenAPIRuntime.HTTPBody) { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } - public var body: Operations.PostFoo.Input.Body - public init(body: Operations.PostFoo.Input.Body) { + public var body: Operations.post_sol_foo.Input.Body + public init(body: Operations.post_sol_foo.Input.Body) { self.body = body } } @@ -3683,7 +3709,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.PostFoo.Input.Body + let body: Operations.post_sol_foo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -3693,7 +3719,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -3734,7 +3760,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.PostFoo.Input(body: body) + return Operations.post_sol_foo.Input(body: body) } """ ) @@ -3766,20 +3792,20 @@ final class SnippetBasedReferenceTests: XCTestCase { input: """ public struct Input: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum MultipartFormPayload: Sendable, Hashable { - public struct InfoPayload: Sendable, Hashable { + @frozen public enum multipartFormPayload: Sendable, Hashable { + public struct infoPayload: Sendable, Hashable { public var body: Components.Schemas.Info public init(body: Components.Schemas.Info) { self.body = body } } - case info(OpenAPIRuntime.MultipartPart) + case info(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } - public var body: Operations.PostFoo.Input.Body - public init(body: Operations.PostFoo.Input.Body) { + public var body: Operations.post_sol_foo.Input.Body + public init(body: Operations.post_sol_foo.Input.Body) { self.body = body } } @@ -3837,7 +3863,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.PostFoo.Input.Body + let body: Operations.post_sol_foo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -3847,7 +3873,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -3888,7 +3914,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.PostFoo.Input(body: body) + return Operations.post_sol_foo.Input(body: body) } """ ) @@ -3924,8 +3950,8 @@ final class SnippetBasedReferenceTests: XCTestCase { @frozen public enum Body: Sendable, Hashable { case multipartForm(OpenAPIRuntime.MultipartBody) } - public var body: Operations.PostFoo.Input.Body - public init(body: Operations.PostFoo.Input.Body) { + public var body: Operations.post_sol_foo.Input.Body + public init(body: Operations.post_sol_foo.Input.Body) { self.body = body } } @@ -3933,13 +3959,13 @@ final class SnippetBasedReferenceTests: XCTestCase { schemas: """ public enum Schemas { @frozen public enum Multipet: Sendable, Hashable { - public struct LogPayload: Sendable, Hashable { + public struct logPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody public init(body: OpenAPIRuntime.HTTPBody) { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } } @@ -3997,7 +4023,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.PostFoo.Input.Body + let body: Operations.post_sol_foo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -4048,7 +4074,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.PostFoo.Input(body: body) + return Operations.post_sol_foo.Input(body: body) } """ ) @@ -4088,31 +4114,31 @@ final class SnippetBasedReferenceTests: XCTestCase { input: """ public struct Input: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum MultipartFormPayload: Sendable, Hashable { - public struct LogPayload: Sendable, Hashable { + @frozen public enum multipartFormPayload: Sendable, Hashable { + public struct logPayload: Sendable, Hashable { public struct Headers: Sendable, Hashable { - public var xLogType: Swift.String? - public init(xLogType: Swift.String? = nil) { - self.xLogType = xLogType + public var x_hyphen_log_hyphen_type: Swift.String? + public init(x_hyphen_log_hyphen_type: Swift.String? = nil) { + self.x_hyphen_log_hyphen_type = x_hyphen_log_hyphen_type } } - public var headers: Operations.PostFoo.Input.Body.MultipartFormPayload.LogPayload.Headers + public var headers: Operations.post_sol_foo.Input.Body.multipartFormPayload.logPayload.Headers public var body: OpenAPIRuntime.HTTPBody public init( - headers: Operations.PostFoo.Input.Body.MultipartFormPayload.LogPayload.Headers = .init(), + headers: Operations.post_sol_foo.Input.Body.multipartFormPayload.logPayload.Headers = .init(), body: OpenAPIRuntime.HTTPBody ) { self.headers = headers self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } - public var body: Operations.PostFoo.Input.Body - public init(body: Operations.PostFoo.Input.Body) { + public var body: Operations.post_sol_foo.Input.Body + public init(body: Operations.post_sol_foo.Input.Body) { self.body = body } } @@ -4120,13 +4146,13 @@ final class SnippetBasedReferenceTests: XCTestCase { schemas: """ public enum Schemas { @frozen public enum Multipet: Sendable, Hashable { - public struct LogPayload: Sendable, Hashable { + public struct logPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody public init(body: OpenAPIRuntime.HTTPBody) { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } } @@ -4164,7 +4190,7 @@ final class SnippetBasedReferenceTests: XCTestCase { try converter.setHeaderFieldAsURI( in: &headerFields, name: "x-log-type", - value: value.headers.xLogType + value: value.headers.x_hyphen_log_hyphen_type ) let body = try converter.setRequiredRequestBodyAsBinary( value.body, @@ -4189,7 +4215,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.PostFoo.Input.Body + let body: Operations.post_sol_foo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -4199,7 +4225,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -4217,7 +4243,7 @@ final class SnippetBasedReferenceTests: XCTestCase { let (name, filename) = try converter.extractContentDispositionNameAndFilename(in: headerFields) switch name { case "log": - let headers: Operations.PostFoo.Input.Body.MultipartFormPayload.LogPayload.Headers = .init(xLogType: try converter.getOptionalHeaderFieldAsURI( + let headers: Operations.post_sol_foo.Input.Body.multipartFormPayload.logPayload.Headers = .init(x_hyphen_log_hyphen_type: try converter.getOptionalHeaderFieldAsURI( in: headerFields, name: "x-log-type", as: Swift.String.self @@ -4248,7 +4274,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.PostFoo.Input(body: body) + return Operations.post_sol_foo.Input(body: body) } """ ) @@ -4270,13 +4296,13 @@ final class SnippetBasedReferenceTests: XCTestCase { input: """ public struct Input: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum MultipartFormPayload: Sendable, Hashable { + @frozen public enum multipartFormPayload: Sendable, Hashable { case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } - public var body: Operations.PostFoo.Input.Body - public init(body: Operations.PostFoo.Input.Body) { + public var body: Operations.post_sol_foo.Input.Body + public init(body: Operations.post_sol_foo.Input.Body) { self.body = body } } @@ -4318,7 +4344,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.PostFoo.Input.Body + let body: Operations.post_sol_foo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -4328,7 +4354,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -4351,7 +4377,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.PostFoo.Input(body: body) + return Operations.post_sol_foo.Input(body: body) } """ ) @@ -4376,13 +4402,13 @@ final class SnippetBasedReferenceTests: XCTestCase { input: """ public struct Input: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum MultipartFormPayload: Sendable, Hashable { + @frozen public enum multipartFormPayload: Sendable, Hashable { case other(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } - public var body: Operations.PostFoo.Input.Body - public init(body: Operations.PostFoo.Input.Body) { + public var body: Operations.post_sol_foo.Input.Body + public init(body: Operations.post_sol_foo.Input.Body) { self.body = body } } @@ -4424,7 +4450,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.PostFoo.Input.Body + let body: Operations.post_sol_foo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -4434,7 +4460,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -4457,7 +4483,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.PostFoo.Input(body: body) + return Operations.post_sol_foo.Input(body: body) } """ ) @@ -4487,19 +4513,19 @@ final class SnippetBasedReferenceTests: XCTestCase { input: """ public struct Input: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum MultipartFormPayload: Sendable, Hashable { - public struct LogPayload: Sendable, Hashable { + @frozen public enum multipartFormPayload: Sendable, Hashable { + public struct logPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody public init(body: OpenAPIRuntime.HTTPBody) { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } - public var body: Operations.PostFoo.Input.Body - public init(body: Operations.PostFoo.Input.Body) { + public var body: Operations.post_sol_foo.Input.Body + public init(body: Operations.post_sol_foo.Input.Body) { self.body = body } } @@ -4555,7 +4581,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.PostFoo.Input.Body + let body: Operations.post_sol_foo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -4565,7 +4591,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -4606,7 +4632,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.PostFoo.Input(body: body) + return Operations.post_sol_foo.Input(body: body) } """ ) @@ -4640,15 +4666,15 @@ final class SnippetBasedReferenceTests: XCTestCase { input: """ public struct Input: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum MultipartFormPayload: Sendable, Hashable { - public struct LogPayload: Sendable, Hashable { + @frozen public enum multipartFormPayload: Sendable, Hashable { + public struct logPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody public init(body: OpenAPIRuntime.HTTPBody) { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) - public struct AdditionalPropertiesPayload: Codable, Hashable, Sendable { + case log(OpenAPIRuntime.MultipartPart) + public struct additionalPropertiesPayload: Codable, Hashable, Sendable { public var foo: Swift.String? public init(foo: Swift.String? = nil) { self.foo = foo @@ -4657,12 +4683,12 @@ final class SnippetBasedReferenceTests: XCTestCase { case foo } } - case additionalProperties(OpenAPIRuntime.MultipartDynamicallyNamedPart) + case additionalProperties(OpenAPIRuntime.MultipartDynamicallyNamedPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } - public var body: Operations.PostFoo.Input.Body - public init(body: Operations.PostFoo.Input.Body) { + public var body: Operations.post_sol_foo.Input.Body + public init(body: Operations.post_sol_foo.Input.Body) { self.body = body } } @@ -4732,7 +4758,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.PostFoo.Input.Body + let body: Operations.post_sol_foo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -4742,7 +4768,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -4781,7 +4807,7 @@ final class SnippetBasedReferenceTests: XCTestCase { matches: "application/json" ) let body = try await converter.getRequiredRequestBodyAsJSON( - Operations.PostFoo.Input.Body.MultipartFormPayload.AdditionalPropertiesPayload.self, + Operations.post_sol_foo.Input.Body.multipartFormPayload.additionalPropertiesPayload.self, from: part.body, transforming: { $0 @@ -4798,7 +4824,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.PostFoo.Input(body: body) + return Operations.post_sol_foo.Input(body: body) } """ ) @@ -4837,20 +4863,20 @@ final class SnippetBasedReferenceTests: XCTestCase { input: """ public struct Input: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum MultipartFormPayload: Sendable, Hashable { - public struct LogPayload: Sendable, Hashable { + @frozen public enum multipartFormPayload: Sendable, Hashable { + public struct logPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody public init(body: OpenAPIRuntime.HTTPBody) { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) case additionalProperties(OpenAPIRuntime.MultipartDynamicallyNamedPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } - public var body: Operations.PostFoo.Input.Body - public init(body: Operations.PostFoo.Input.Body) { + public var body: Operations.post_sol_foo.Input.Body + public init(body: Operations.post_sol_foo.Input.Body) { self.body = body } } @@ -4920,7 +4946,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.PostFoo.Input.Body + let body: Operations.post_sol_foo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -4930,7 +4956,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -4986,7 +5012,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.PostFoo.Input(body: body) + return Operations.post_sol_foo.Input(body: body) } """ ) @@ -5012,13 +5038,13 @@ final class SnippetBasedReferenceTests: XCTestCase { input: """ public struct Input: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum MultipartFormPayload: Sendable, Hashable { + @frozen public enum multipartFormPayload: Sendable, Hashable { case additionalProperties(OpenAPIRuntime.MultipartDynamicallyNamedPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) + case multipartForm(OpenAPIRuntime.MultipartBody) } - public var body: Operations.PostFoo.Input.Body - public init(body: Operations.PostFoo.Input.Body) { + public var body: Operations.post_sol_foo.Input.Body + public init(body: Operations.post_sol_foo.Input.Body) { self.body = body } } @@ -5072,7 +5098,7 @@ final class SnippetBasedReferenceTests: XCTestCase { server: """ { request, requestBody, metadata in let contentType = converter.extractContentTypeIfPresent(in: request.headerFields) - let body: Operations.PostFoo.Input.Body + let body: Operations.post_sol_foo.Input.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -5082,7 +5108,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getRequiredRequestBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: requestBody, transforming: { value in .multipartForm(value) @@ -5120,7 +5146,7 @@ final class SnippetBasedReferenceTests: XCTestCase { default: preconditionFailure("bestContentType chose an invalid content type.") } - return Operations.PostFoo.Input(body: body) + return Operations.post_sol_foo.Input(body: body) } """ ) @@ -5170,18 +5196,18 @@ final class SnippetBasedReferenceTests: XCTestCase { public enum Responses { public struct MultipartResponse: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum MultipartFormPayload: Sendable, Hashable { - public struct LogPayload: Sendable, Hashable { + @frozen public enum multipartFormPayload: Sendable, Hashable { + public struct logPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody public init(body: OpenAPIRuntime.HTTPBody) { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) - public var multipartForm: OpenAPIRuntime.MultipartBody { + case multipartForm(OpenAPIRuntime.MultipartBody) + public var multipartForm: OpenAPIRuntime.MultipartBody { get throws { switch self { case let .multipartForm(body): @@ -5265,7 +5291,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getResponseBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: responseBody, transforming: { value in .multipartForm(value) @@ -5341,18 +5367,18 @@ final class SnippetBasedReferenceTests: XCTestCase { @frozen public enum Output: Sendable, Hashable { public struct Ok: Sendable, Hashable { @frozen public enum Body: Sendable, Hashable { - @frozen public enum MultipartFormPayload: Sendable, Hashable { - public struct LogPayload: Sendable, Hashable { + @frozen public enum multipartFormPayload: Sendable, Hashable { + public struct logPayload: Sendable, Hashable { public var body: OpenAPIRuntime.HTTPBody public init(body: OpenAPIRuntime.HTTPBody) { self.body = body } } - case log(OpenAPIRuntime.MultipartPart) + case log(OpenAPIRuntime.MultipartPart) case undocumented(OpenAPIRuntime.MultipartRawPart) } - case multipartForm(OpenAPIRuntime.MultipartBody) - public var multipartForm: OpenAPIRuntime.MultipartBody { + case multipartForm(OpenAPIRuntime.MultipartBody) + public var multipartForm: OpenAPIRuntime.MultipartBody { get throws { switch self { case let .multipartForm(body): @@ -5361,13 +5387,13 @@ final class SnippetBasedReferenceTests: XCTestCase { } } } - public var body: Operations.GetFoo.Output.Ok.Body - public init(body: Operations.GetFoo.Output.Ok.Body) { + public var body: Operations.get_sol_foo.Output.Ok.Body + public init(body: Operations.get_sol_foo.Output.Ok.Body) { self.body = body } } - case ok(Operations.GetFoo.Output.Ok) - public var ok: Operations.GetFoo.Output.Ok { + case ok(Operations.get_sol_foo.Output.Ok) + public var ok: Operations.get_sol_foo.Output.Ok { get throws { switch self { case let .ok(response): @@ -5441,7 +5467,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch response.status.code { case 200: let contentType = converter.extractContentTypeIfPresent(in: response.headerFields) - let body: Operations.GetFoo.Output.Ok.Body + let body: Operations.get_sol_foo.Output.Ok.Body let chosenContentType = try converter.bestContentType( received: contentType, options: [ @@ -5451,7 +5477,7 @@ final class SnippetBasedReferenceTests: XCTestCase { switch chosenContentType { case "multipart/form-data": body = try converter.getResponseBodyAsMultipart( - OpenAPIRuntime.MultipartBody.self, + OpenAPIRuntime.MultipartBody.self, from: responseBody, transforming: { value in .multipartForm(value) @@ -5851,6 +5877,7 @@ extension SnippetBasedReferenceTests { func makeTypesTranslator( accessModifier: AccessModifier = .public, + namingStrategy: NamingStrategy = .defensive, featureFlags: FeatureFlags = [], ignoredDiagnosticMessages: Set = [], componentsYAML: String @@ -5860,13 +5887,14 @@ extension SnippetBasedReferenceTests { config: Config( mode: .types, access: accessModifier, - namingStrategy: .idiomatic, + namingStrategy: namingStrategy, featureFlags: featureFlags ), diagnostics: XCTestDiagnosticCollector(test: self, ignoredDiagnosticMessages: ignoredDiagnosticMessages), components: components ) } + func makeTypesTranslator( accessModifier: AccessModifier = .public, featureFlags: FeatureFlags = [], @@ -5874,12 +5902,7 @@ extension SnippetBasedReferenceTests { components: OpenAPI.Components = .noComponents ) throws -> TypesFileTranslator { TypesFileTranslator( - config: Config( - mode: .types, - access: accessModifier, - namingStrategy: .idiomatic, - featureFlags: featureFlags - ), + config: Config(mode: .types, access: accessModifier, featureFlags: featureFlags), diagnostics: XCTestDiagnosticCollector(test: self, ignoredDiagnosticMessages: ignoredDiagnosticMessages), components: components ) @@ -5893,17 +5916,17 @@ extension SnippetBasedReferenceTests { let collector = XCTestDiagnosticCollector(test: self, ignoredDiagnosticMessages: ignoredDiagnosticMessages) return ( TypesFileTranslator( - config: Config(mode: .types, access: .public, namingStrategy: .idiomatic, featureFlags: featureFlags), + config: Config(mode: .types, access: .public, featureFlags: featureFlags), diagnostics: collector, components: components ), ClientFileTranslator( - config: Config(mode: .client, access: .public, namingStrategy: .idiomatic, featureFlags: featureFlags), + config: Config(mode: .client, access: .public, featureFlags: featureFlags), diagnostics: collector, components: components ), ServerFileTranslator( - config: Config(mode: .server, access: .public, namingStrategy: .idiomatic, featureFlags: featureFlags), + config: Config(mode: .server, access: .public, featureFlags: featureFlags), diagnostics: collector, components: components ) @@ -6112,11 +6135,15 @@ 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) From 837e6ed7dd896f27d9b09783e2b84b9b6ecb5143 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Tue, 17 Dec 2024 09:11:35 +0100 Subject: [PATCH 26/31] Formatting --- .../TypeAssignment/TypeAssigner.swift | 19 +++-- .../Extensions/Test_SwiftSafeNames.swift | 3 +- .../Test_OperationDescription.swift | 5 +- .../TypeAssignment/Test_TypeAssigner.swift | 30 ++++---- .../SnippetBasedReferenceTests.swift | 74 +++++++++---------- 5 files changed, 61 insertions(+), 70 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift index dd9f0309..6aac6351 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift @@ -546,16 +546,15 @@ struct TypeAssigner { let params = contentType.lowercasedParameterPairs guard !params.isEmpty else { return prefix } let safedParams = - params.map { pair in - pair - .split(separator: "=") - .map { component in - let safedComponent = context.asSwiftSafeName(String(component), .noncapitalized) - return capitalizeNonFirstWords ? safedComponent.uppercasingFirstLetter : safedComponent - } - .joined(separator: componentSeparator) - } - .joined(separator: componentSeparator) + params.map { pair in + pair.split(separator: "=") + .map { component in + let safedComponent = context.asSwiftSafeName(String(component), .noncapitalized) + return capitalizeNonFirstWords ? safedComponent.uppercasingFirstLetter : safedComponent + } + .joined(separator: componentSeparator) + } + .joined(separator: componentSeparator) return prefix + componentSeparator + safedParams } } diff --git a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift index ca44d585..7e75d55c 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift @@ -34,8 +34,7 @@ final class Test_SwiftSafeNames: Test_Core { ("Retry-After", "Retry_hyphen_After", "RetryAfter", "retryAfter"), // All uppercase - ("HELLO_WORLD", "HELLO_WORLD", "HelloWorld", "helloWorld"), - ("HELLO", "HELLO", "Hello", "hello"), + ("HELLO_WORLD", "HELLO_WORLD", "HelloWorld", "helloWorld"), ("HELLO", "HELLO", "Hello", "hello"), // Acronyms ("HTTPProxy", "HTTPProxy", "HTTPProxy", "httpProxy"), diff --git a/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift b/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift index 72424b65..762dfd33 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Translator/Operations/Test_OperationDescription.swift @@ -144,10 +144,7 @@ final class Test_OperationDescription: Test_Core { endpoint: endpoint, pathParameters: pathItem.parameters, components: .init(), - context: .init( - asSwiftSafeName: { string, _ in string }, - namingStrategy: .defensive - ) + context: .init(asSwiftSafeName: { string, _ in string }, namingStrategy: .defensive) ) } } diff --git a/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeAssigner.swift b/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeAssigner.swift index 6ee66e10..cafd0789 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeAssigner.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeAssigner.swift @@ -112,22 +112,14 @@ class Test_TypeAssigner: Test_Core { // Short names. ("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"), + ("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", "applicationMyformatJson"), - ("foo/bar", "foo_bar", "fooBar"), - ("text/event-stream", "text_event_hyphen_stream", "textEventStream"), + ("foo/bar", "foo_bar", "fooBar"), ("text/event-stream", "text_event_hyphen_stream", "textEventStream"), // Names with a parameter. ("application/foo", "application_foo", "applicationFoo"), @@ -136,8 +128,16 @@ class Test_TypeAssigner: Test_Core { ] for (string, defensiveName, idiomaticName) in cases { let contentType = try XCTUnwrap(ContentType(string: string)) - XCTAssertEqual(defensiveNameMaker(contentType), defensiveName, "Case \(string) failed for defensive strategy") - XCTAssertEqual(idiomaticNameMaker(contentType), idiomaticName, "Case \(string) failed for idiomatic strategy") + 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/SnippetBasedReferenceTests.swift b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift index 42373a3b..64b30763 100644 --- a/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift +++ b/Tests/OpenAPIGeneratorReferenceTests/SnippetBasedReferenceTests.swift @@ -2309,53 +2309,52 @@ 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. - """ + /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 - } - """ + """ + 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. - """ + /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 - } - """ + """ + public protocol APIProtocol: Sendable { + func getPetsPetIdNotifications(_ input: Operations.GetPetsPetIdNotifications.Input) async throws -> Operations.GetPetsPetIdNotifications.Output + } + """ ) } - func testServerRegisterHandlers_oneOperation() throws { try self.assertServerRegisterHandlers( """ @@ -6140,10 +6139,7 @@ extension SnippetBasedReferenceTests { file: StaticString = #filePath, line: UInt = #line ) throws { - let translator = try makeTypesTranslator( - namingStrategy: namingStrategy, - 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) From af9b5ffcc21c440f7811d2ab730dc7bb8f514554 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Tue, 17 Dec 2024 09:17:14 +0100 Subject: [PATCH 27/31] Update test now that we also handle plus signs in the idiomatic strategy --- .../Extensions/Test_SwiftSafeNames.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift index 7e75d55c..b1b7fe1b 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift @@ -114,7 +114,7 @@ final class Test_SwiftSafeNames: Test_Core { // Content type components ("application", "application", "Application", "application"), - ("vendor1+json", "vendor1_plus_json", "vendor1_plus_json", "vendor1_plus_json"), + ("vendor1+json", "vendor1_plus_json", "Vendor1Json", "vendor1Json"), // Known real-world examples ("+1", "_plus_1", "_plus_1", "_plus_1"), ("-1", "_hyphen_1", "_hyphen_1", "_hyphen_1"), From 9c9f6578d1c9d1822e5473eac741e7c948400333 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Tue, 17 Dec 2024 09:27:12 +0100 Subject: [PATCH 28/31] Always run the string through the defensive strategy after finishing the idiomatic strategy --- .../CommonTranslations/SwiftSafeNames.swift | 35 +++++++++---------- 1 file changed, 17 insertions(+), 18 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift index 3cfbd773..6847404f 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift @@ -72,8 +72,8 @@ extension String { 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. + // 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 } @@ -90,6 +90,7 @@ extension String { func safeForSwiftCode_idiomatic(options: SwiftNameOptions) -> String { let capitalize = options.isCapitalized if isEmpty { return capitalize ? "_Empty_" : "_empty_" } + // Detect cases like HELLO_WORLD, sometimes used for constants. let isAllUppercase = allSatisfy { // Must check that no characters are lowercased, as non-letter characters @@ -112,7 +113,6 @@ extension String { case accumulatingFirstWord(AccumulatingFirstWordContext) case accumulatingWord case waitingForWordStarter - case fallback } var state: State = .preFirstWord for index in self[...].indices { @@ -137,8 +137,9 @@ extension String { .init(isAccumulatingInitialUppercase: !capitalize && char.isUppercase) ) } else { - // Illegal character, fall back to the defensive strategy. - state = .fallback + // Illegal character, keep and let the defensive strategy deal with it. + state = .preFirstWord + buffer.append(char) } case .accumulatingFirstWord(var context): if char.isLetter || char.isNumber { @@ -202,8 +203,9 @@ extension String { // In the middle of an identifier, curly braces are dropped. state = .accumulatingFirstWord(.init(isAccumulatingInitialUppercase: false)) } else { - // Illegal character, fall back to the defensive strategy. - state = .fallback + // 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 { @@ -222,8 +224,9 @@ extension String { // In the middle of an identifier, these are dropped. state = .accumulatingWord } else { - // Illegal character, fall back to the defensive strategy. - state = .fallback + // Illegal character, keep and let the defensive strategy deal with it. + state = .accumulatingWord + buffer.append(char) } case .waitingForWordStarter: if ["_", "-", ".", "/", "+", "{", "}"].contains(char) { @@ -235,19 +238,15 @@ extension String { buffer.append(contentsOf: char.uppercased()) state = .accumulatingWord } else { - // Illegal character, fall back to the defensive strategy. - state = .fallback + // Illegal character, keep and let the defensive strategy deal with it. + state = .waitingForWordStarter + buffer.append(char) } - case .modifying, .fallback: preconditionFailure("Logic error in \(#function), string: '\(self)'") + case .modifying: preconditionFailure("Logic error in \(#function), string: '\(self)'") } precondition(state != .modifying, "Logic error in \(#function), string: '\(self)'") - if case .fallback = state { return safeForSwiftCode_defensive(options: options) } } - if buffer.isEmpty || state == .preFirstWord { return safeForSwiftCode_defensive(options: options) } - // Check for keywords - let newString = String(buffer) - if Self.keywords.contains(newString) { return "_\(newString)" } - return newString + return String(buffer).safeForSwiftCode_defensive(options: options) } /// A list of word separator characters for the idiomatic naming strategy. From 4d4e9b6ef0a986230d2e5cab50f24eac227229c7 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Tue, 17 Dec 2024 09:54:06 +0100 Subject: [PATCH 29/31] Test fixes --- .../CommonTranslations/SwiftSafeNames.swift | 5 ++--- .../Extensions/Test_SwiftSafeNames.swift | 17 +++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift index 6847404f..43cd0e49 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift @@ -126,8 +126,7 @@ extension String { buffer.append(char) state = .preFirstWord } else if char.isNumber { - // Prefix with an underscore if the first character is a number. - buffer.append("_") + // The underscore will be added by the defensive strategy. buffer.append(char) state = .accumulatingFirstWord(.init(isAccumulatingInitialUppercase: false)) } else if char.isLetter { @@ -138,7 +137,7 @@ extension String { ) } else { // Illegal character, keep and let the defensive strategy deal with it. - state = .preFirstWord + state = .accumulatingFirstWord(.init(isAccumulatingInitialUppercase: false)) buffer.append(char) } case .accumulatingFirstWord(var context): diff --git a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift index b1b7fe1b..fe97ea80 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift @@ -58,11 +58,11 @@ final class Test_SwiftSafeNames: Test_Core { // 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_period_name_commat_domain_period_com", "user_period_name_commat_domain_period_com" + "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"), + ("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é"), @@ -80,13 +80,13 @@ final class Test_SwiftSafeNames: Test_Core { ("", "_empty", "_Empty_", "_empty_"), // Special Char in middle - ("inv@lidName", "inv_commat_lidName", "inv_commat_lidName", "inv_commat_lidName"), + ("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_"), + ("invalidNam?", "invalidNam_quest_", "InvalidNam_quest_", "invalidNam_quest_"), // Preserve leading underscores ("__user_name", "__user_name", "__UserName", "__userName"), @@ -110,14 +110,15 @@ final class Test_SwiftSafeNames: Test_Core { ("$مرحبا", "_dollar_مرحبا", "_dollar_مرحبا", "_dollar_مرحبا"), // Emoji - ("heart❤️emoji", "heart_x2764_️emoji", "heart_x2764_️emoji", "heart_x2764_️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"), ("-1", "_hyphen_1", "_hyphen_1", "_hyphen_1"), + ("+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"), From 603970caa38d806115665f03e69cd846d39f5aae Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Wed, 18 Dec 2024 10:10:35 +0100 Subject: [PATCH 30/31] Start of refactor of SafeNameGenerator --- .../SafeNameGenerator.swift} | 216 ++++++++++++++---- .../TypeAssignment/TypeAssigner.swift | 51 +---- 2 files changed, 176 insertions(+), 91 deletions(-) rename Sources/_OpenAPIGeneratorCore/Translator/{CommonTranslations/SwiftSafeNames.swift => TypeAssignment/SafeNameGenerator.swift} (66%) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/SafeNameGenerator.swift similarity index 66% rename from Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift rename to Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/SafeNameGenerator.swift index 43cd0e49..0267f363 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/SwiftSafeNames.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/SafeNameGenerator.swift @@ -26,28 +26,72 @@ struct SwiftNameOptions { static let noncapitalized = SwiftNameOptions(isCapitalized: false) } -extension String { +/// 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 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. - func safeForSwiftCode_defensive(options: SwiftNameOptions) -> String { - guard !isEmpty else { return "_empty" } + /// Returns a string sanitized to be usable as a Swift identifier in a general context. + /// - Parameters: + /// - documentedName: The input unsanitized string from the OpenAPI document. + /// - options: Additional context for how the sanitized string will be used. + /// - Returns: The sanitized string. + func swiftName(for documentedName: String, options: SwiftNameOptions) -> 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 contentTypeSwiftName(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 swiftName(for documentedName: String, options: SwiftNameOptions) -> 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 unicodeScalars.enumerated() { + for (index, scalar) in documentedName.unicodeScalars.enumerated() { let allowedSet = index == 0 ? firstCharSet : otherCharSet let outScalar: Unicode.Scalar if allowedSet.contains(scalar) { @@ -70,7 +114,7 @@ extension String { sanitizedScalars.append(outScalar) } - let validString = String(UnicodeScalarView(sanitizedScalars)) + 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. @@ -80,19 +124,73 @@ extension String { return "_\(validString)" } - /// 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`. + func contentTypeSwiftName(for contentType: ContentType) -> String { + if let common = swiftNameOverride(for: contentType) { + return common + } + let safedType = swiftName(for: contentType.originallyCasedType, options: .noncapitalized) + let safedSubtype = swiftName(for: contentType.originallyCasedSubtype, options: .noncapitalized) + 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), options: .noncapitalized) + } + .joined(separator: componentSeparator) + } + .joined(separator: componentSeparator) + return prefix + componentSeparator + safedParams + } + + /// A list of Swift keywords. /// - /// Check out [SOAR-0013](https://swiftpackageindex.com/apple/swift-openapi-generator/documentation/swift-openapi-generator/soar-0013) for details. - func safeForSwiftCode_idiomatic(options: SwiftNameOptions) -> String { + /// 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 swiftName(for documentedName: String, options: SwiftNameOptions) -> String { let capitalize = options.isCapitalized - if isEmpty { return capitalize ? "_Empty_" : "_empty_" } + if documentedName.isEmpty { return capitalize ? "_Empty_" : "_empty_" } // Detect cases like HELLO_WORLD, sometimes used for constants. - let isAllUppercase = allSatisfy { + let isAllUppercase = documentedName.allSatisfy { // Must check that no characters are lowercased, as non-letter characters // don't return `true` to `isUppercase`. !$0.isLowercase @@ -105,7 +203,7 @@ extension String { // 4. In the middle: drop ["{", "}"] -> replace with "" var buffer: [Character] = [] - buffer.reserveCapacity(count) + buffer.reserveCapacity(documentedName.count) enum State: Equatable { case modifying case preFirstWord @@ -115,8 +213,8 @@ extension String { case waitingForWordStarter } var state: State = .preFirstWord - for index in self[...].indices { - let char = self[index] + for index in documentedName[...].indices { + let char = documentedName[index] let _state = state state = .modifying switch _state { @@ -158,7 +256,7 @@ extension String { buffer.append(char) context.isAccumulatingInitialUppercase = false } else { - let suffix = suffix(from: self.index(after: index)) + let suffix = documentedName.suffix(from: documentedName.index(after: index)) if suffix.count >= 2 { let next = suffix.first! let secondNext = suffix.dropFirst().first! @@ -245,29 +343,51 @@ extension String { } precondition(state != .modifying, "Logic error in \(#function), string: '\(self)'") } - return String(buffer).safeForSwiftCode_defensive(options: options) + return defensive.swiftName(for: String(buffer), options: options) + } + + func contentTypeSwiftName(for contentType: ContentType) -> String { + if let common = swiftNameOverride(for: contentType) { + return common + } + let safedType = swiftName(for: contentType.originallyCasedType, options: .noncapitalized) + let safedSubtype = swiftName(for: contentType.originallyCasedSubtype, options: .noncapitalized) + 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 = swiftName(for: String(component), options: .noncapitalized) + return safedComponent.uppercasingFirstLetter + } + .joined() + } + .joined() + return prefix + safedParams } /// A list of word separator characters for the idiomatic naming strategy. private static let wordSeparators: Set = ["_", "-", " ", "/", "+"] +} - /// 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", - ] +extension SafeNameGenerator where Self == DefensiveSafeNameGenerator { + static var idiomatic: IdiomaticSafeNameGenerator { + IdiomaticSafeNameGenerator(defensive: .defensive) + } +} - /// 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 String { + + @available(*, deprecated) + func safeForSwiftCode_defensive(options: SwiftNameOptions) -> String { + DefensiveSafeNameGenerator().swiftName(for: self, options: options) + } + + @available(*, deprecated) + func safeForSwiftCode_idiomatic(options: SwiftNameOptions) -> String { + IdiomaticSafeNameGenerator(defensive: DefensiveSafeNameGenerator()).swiftName(for: self, options: options) + } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift index 6aac6351..e0cc8ade 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift @@ -512,51 +512,16 @@ struct TypeAssigner { /// /// - Parameter contentType: The content type for which to compute the name. /// - Returns: A Swift-safe identifier representing the name of the content enum case. + @available(*, deprecated) 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, .noncapitalized) - let safedSubtype = context.asSwiftSafeName(contentType.originallyCasedSubtype, .noncapitalized) - let componentSeparator: String - let capitalizeNonFirstWords: Bool - switch context.namingStrategy { - case .defensive: - componentSeparator = "_" - capitalizeNonFirstWords = false - case .idiomatic: - componentSeparator = "" - capitalizeNonFirstWords = true - } - let prettifiedSubtype = capitalizeNonFirstWords ? safedSubtype.uppercasingFirstLetter : safedSubtype - let prefix = "\(safedType)\(componentSeparator)\(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 = context.asSwiftSafeName(String(component), .noncapitalized) - return capitalizeNonFirstWords ? safedComponent.uppercasingFirstLetter : safedComponent - } - .joined(separator: componentSeparator) - } - .joined(separator: componentSeparator) - return prefix + componentSeparator + safedParams + let nameGenerator: any SafeNameGenerator + switch context.namingStrategy { + case .defensive: + nameGenerator = .defensive + case .idiomatic: + nameGenerator = .idiomatic } + return nameGenerator.contentTypeSwiftName(for: contentType) } } From 762925d96bd178cd203f0700bd07a7d2276fdb25 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Wed, 18 Dec 2024 13:39:27 +0100 Subject: [PATCH 31/31] Finished refactoring --- .../translateAllAnyOneOf.swift | 2 +- .../CommonTranslations/translateRawEnum.swift | 4 +- .../CommonTypes/DiscriminatorExtensions.swift | 2 +- .../CommonTypes/StructBlueprint.swift | 2 +- .../Translator/FileTranslator.swift | 51 +++++--- .../Multipart/MultipartContentInspector.swift | 2 +- .../Multipart/translateMultipart.swift | 6 +- .../Operations/OperationDescription.swift | 6 +- .../Parameters/TypedParameter.swift | 2 +- .../RequestBody/translateRequestBody.swift | 6 +- .../Responses/TypedResponseHeader.swift | 2 +- .../Responses/translateResponse.swift | 2 +- .../Responses/translateResponseOutcome.swift | 4 +- .../TypeAssignment/SafeNameGenerator.swift | 120 +++++++----------- .../TypeAssignment/TypeAssigner.swift | 37 ++---- .../TypesTranslator/translateOperations.swift | 5 +- .../translateServersVariables.swift | 10 +- .../Extensions/Test_SwiftSafeNames.swift | 14 +- .../TestUtilities.swift | 2 - .../Test_OperationDescription.swift | 2 +- .../TypeAssignment/Test_TypeAssigner.swift | 7 +- 21 files changed, 136 insertions(+), 152 deletions(-) diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift index 6a6d4b70..1816cabc 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTranslations/translateAllAnyOneOf.swift @@ -209,7 +209,7 @@ extension TypesFileTranslator { let decoder: Declaration if let discriminator { let originalName = discriminator.propertyName - let swiftName = context.asSwiftSafeName(originalName, .noncapitalized) + 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 2b59ed38..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("", .noncapitalized)) + 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, .noncapitalized) + 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 a5fb1172..41615b3b 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/DiscriminatorExtensions.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/DiscriminatorExtensions.swift @@ -80,7 +80,7 @@ extension FileTranslator { /// - Parameter type: The `OneOfMappedType` for which to determine the case name. /// - Returns: A string representing the safe Swift name for the specified `OneOfMappedType`. func safeSwiftNameForOneOfMappedCase(_ type: OneOfMappedType) -> String { - context.asSwiftSafeName(type.rawNames[0], .noncapitalized) + context.safeNameGenerator.swiftMemberName(for: type.rawNames[0]) } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/StructBlueprint.swift b/Sources/_OpenAPIGeneratorCore/Translator/CommonTypes/StructBlueprint.swift index 10b5da2f..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, .noncapitalized) } + 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 0068cefc..00dde5d8 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/FileTranslator.swift @@ -44,35 +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 { - let asSwiftSafeName: (String, SwiftNameOptions) -> String + let safeNameGenerator: any SafeNameGenerator switch config.namingStrategy { - case .defensive: asSwiftSafeName = { $0.safeForSwiftCode_defensive(options: $1) } - case .idiomatic: asSwiftSafeName = { $0.safeForSwiftCode_idiomatic(options: $1) } + case .defensive: safeNameGenerator = .defensive + case .idiomatic: safeNameGenerator = .idiomatic } - let overrides = config.nameOverrides - return TranslatorContext( - asSwiftSafeName: { name, options in - if let override = overrides[name] { return override } - return asSwiftSafeName(name, options) - }, - namingStrategy: config.namingStrategy + 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, SwiftNameOptions) -> String - - /// The naming strategy. - var namingStrategy: NamingStrategy + /// 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 cc039388..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, .capitalized) + 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 1c2d2ed6..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, .noncapitalized), + 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, .noncapitalized) + 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, .noncapitalized) + 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 50535d37..8342dbd6 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/Operations/OperationDescription.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/Operations/OperationDescription.swift @@ -83,14 +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, .noncapitalized) } + 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.asSwiftSafeName(operationID, .capitalized) } + var operationTypeName: String { context.safeNameGenerator.swiftTypeName(for: operationID) } /// Returns the identifier for the operation. /// @@ -299,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, .noncapitalized)) + .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 bfeb5467..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, .noncapitalized) } + 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 5d0932bd..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, .noncapitalized) } + 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/TypeAssignment/SafeNameGenerator.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/SafeNameGenerator.swift index 0267f363..2aec2e29 100644 --- a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/SafeNameGenerator.swift +++ b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/SafeNameGenerator.swift @@ -13,33 +13,23 @@ //===----------------------------------------------------------------------===// import Foundation -/// Extra context for the `safeForSwiftCode_` family of functions to produce more appropriate Swift identifiers. -struct SwiftNameOptions { - - /// The capitalization option. - var isCapitalized: Bool - - /// Preset options for capitalized names. - static let capitalized = SwiftNameOptions(isCapitalized: true) - - /// Preset options for non-capitalized names. - static let noncapitalized = SwiftNameOptions(isCapitalized: false) -} - /// 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 identifier in a general context. - /// - Parameters: - /// - documentedName: The input unsanitized string from the OpenAPI document. - /// - options: Additional context for how the sanitized string will be used. + /// 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 swiftName(for documentedName: String, options: SwiftNameOptions) -> 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 contentTypeSwiftName(for contentType: ContentType) -> String + func swiftContentTypeName(for contentType: ContentType) -> String } extension SafeNameGenerator { @@ -64,8 +54,7 @@ extension SafeNameGenerator { case "image/png": return "png" case "application/pdf": return "pdf" case "image/jpeg": return "jpeg" - default: - return nil + default: return nil } } } @@ -83,7 +72,9 @@ extension SafeNameGenerator { /// ensures that the identifier starts with a letter and not a number. struct DefensiveSafeNameGenerator: SafeNameGenerator { - func swiftName(for documentedName: String, options: SwiftNameOptions) -> String { + 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: "_")) @@ -124,25 +115,20 @@ struct DefensiveSafeNameGenerator: SafeNameGenerator { return "_\(validString)" } - func contentTypeSwiftName(for contentType: ContentType) -> String { - if let common = swiftNameOverride(for: contentType) { - return common - } - let safedType = swiftName(for: contentType.originallyCasedType, options: .noncapitalized) - let safedSubtype = swiftName(for: contentType.originallyCasedSubtype, options: .noncapitalized) + 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), options: .noncapitalized) - } - .joined(separator: componentSeparator) - } - .joined(separator: componentSeparator) + params.map { pair in + pair.split(separator: "=").map { component in swiftName(for: String(component)) } + .joined(separator: componentSeparator) + } + .joined(separator: componentSeparator) return prefix + componentSeparator + safedParams } @@ -168,9 +154,7 @@ struct DefensiveSafeNameGenerator: SafeNameGenerator { } extension SafeNameGenerator where Self == DefensiveSafeNameGenerator { - static var defensive: DefensiveSafeNameGenerator { - DefensiveSafeNameGenerator() - } + static var defensive: DefensiveSafeNameGenerator { DefensiveSafeNameGenerator() } } /// Returns a string sanitized to be usable as a Swift identifier, and tries to produce UpperCamelCase @@ -185,8 +169,9 @@ struct IdiomaticSafeNameGenerator: SafeNameGenerator { /// The defensive strategy to use as fallback. var defensive: DefensiveSafeNameGenerator - func swiftName(for documentedName: String, options: SwiftNameOptions) -> String { - let capitalize = options.isCapitalized + 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. @@ -343,29 +328,33 @@ struct IdiomaticSafeNameGenerator: SafeNameGenerator { } precondition(state != .modifying, "Logic error in \(#function), string: '\(self)'") } - return defensive.swiftName(for: String(buffer), options: options) + let defensiveFallback: (String) -> String + if capitalize { + defensiveFallback = defensive.swiftTypeName + } else { + defensiveFallback = defensive.swiftMemberName + } + return defensiveFallback(String(buffer)) } - func contentTypeSwiftName(for contentType: ContentType) -> String { - if let common = swiftNameOverride(for: contentType) { - return common - } - let safedType = swiftName(for: contentType.originallyCasedType, options: .noncapitalized) - let safedSubtype = swiftName(for: contentType.originallyCasedSubtype, options: .noncapitalized) + 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 = swiftName(for: String(component), options: .noncapitalized) - return safedComponent.uppercasingFirstLetter - } - .joined() - } - .joined() + params.map { pair in + pair.split(separator: "=") + .map { component in + let safedComponent = swiftMemberName(for: String(component)) + return safedComponent.uppercasingFirstLetter + } + .joined() + } + .joined() return prefix + safedParams } @@ -374,20 +363,5 @@ struct IdiomaticSafeNameGenerator: SafeNameGenerator { } extension SafeNameGenerator where Self == DefensiveSafeNameGenerator { - static var idiomatic: IdiomaticSafeNameGenerator { - IdiomaticSafeNameGenerator(defensive: .defensive) - } -} - -extension String { - - @available(*, deprecated) - func safeForSwiftCode_defensive(options: SwiftNameOptions) -> String { - DefensiveSafeNameGenerator().swiftName(for: self, options: options) - } - - @available(*, deprecated) - func safeForSwiftCode_idiomatic(options: SwiftNameOptions) -> String { - IdiomaticSafeNameGenerator(defensive: DefensiveSafeNameGenerator()).swiftName(for: self, options: options) - } + static var idiomatic: IdiomaticSafeNameGenerator { IdiomaticSafeNameGenerator(defensive: .defensive) } } diff --git a/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift b/Sources/_OpenAPIGeneratorCore/Translator/TypeAssignment/TypeAssigner.swift index e0cc8ade..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, .capitalized), 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, .capitalized) + 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, .capitalized) + 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, .capitalized), 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, .capitalized), 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,7 @@ struct TypeAssigner { { typeNameForComponents() .appending( - swiftComponent: context.asSwiftSafeName(componentType.openAPIComponentsKey, .capitalized) + swiftComponent: context.safeNameGenerator.swiftTypeName(for: componentType.openAPIComponentsKey) .uppercasingFirstLetter, jsonComponent: componentType.openAPIComponentsKey ) @@ -506,23 +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. - @available(*, deprecated) - func contentSwiftName(_ contentType: ContentType) -> String { - let nameGenerator: any SafeNameGenerator - switch context.namingStrategy { - case .defensive: - nameGenerator = .defensive - case .idiomatic: - nameGenerator = .idiomatic - } - return nameGenerator.contentTypeSwiftName(for: contentType) - } } 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 4517dd92..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, .noncapitalized) + swiftSafeKey = context.safeNameGenerator.swiftMemberName(for: key) self.variable = variable } @@ -164,8 +164,8 @@ extension TypesFileTranslator { context: TranslatorContext ) { self.key = key - swiftSafeKey = context.asSwiftSafeName(key, .noncapitalized) - enumName = context.asSwiftSafeName(key.localizedCapitalized, .capitalized) + 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, .noncapitalized))) + 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, .noncapitalized) + let caseName = context.safeNameGenerator.swiftMemberName(for: name) return .enumCase(name: caseName, kind: caseName == name ? .nameOnly : .nameWithRawValue(.string(name))) } } diff --git a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift index fe97ea80..75e34fe9 100644 --- a/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift +++ b/Tests/OpenAPIGeneratorCoreTests/Extensions/Test_SwiftSafeNames.swift @@ -126,10 +126,10 @@ final class Test_SwiftSafeNames: Test_Core { self.continueAfterFailure = true do { let translator = makeTranslator(nameOverrides: ["MEGA": "m_e_g_a"]) - let asSwiftSafeName: (String, SwiftNameOptions) -> String = translator.context.asSwiftSafeName + let safeNameGenerator = translator.context.safeNameGenerator for (input, sanitizedDefensive, _, _) in cases { XCTAssertEqual( - asSwiftSafeName(input, .noncapitalized), + safeNameGenerator.swiftMemberName(for: input), sanitizedDefensive, "Defensive, input: \(input)" ) @@ -137,11 +137,15 @@ final class Test_SwiftSafeNames: Test_Core { } do { let translator = makeTranslator(namingStrategy: .idiomatic, nameOverrides: ["MEGA": "m_e_g_a"]) - let asSwiftSafeName: (String, SwiftNameOptions) -> String = translator.context.asSwiftSafeName + let safeNameGenerator = translator.context.safeNameGenerator for (input, _, idiomaticUpper, idiomaticLower) in cases { - XCTAssertEqual(asSwiftSafeName(input, .capitalized), idiomaticUpper, "Idiomatic upper, input: \(input)") XCTAssertEqual( - asSwiftSafeName(input, .noncapitalized), + 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 c4603591..48651d1c 100644 --- a/Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift +++ b/Tests/OpenAPIGeneratorCoreTests/TestUtilities.swift @@ -85,8 +85,6 @@ class Test_Core: XCTestCase { var context: TranslatorContext { makeTranslator().context } - var asSwiftSafeName: (String, SwiftNameOptions) -> 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 762dfd33..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: { string, _ in string }, namingStrategy: .defensive) + context: .init(safeNameGenerator: .defensive) ) } } diff --git a/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeAssigner.swift b/Tests/OpenAPIGeneratorCoreTests/Translator/TypeAssignment/Test_TypeAssigner.swift index cafd0789..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, .noncapitalized), expectedSwiftTypeName) + XCTAssertEqual(context.safeNameGenerator.swiftMemberName(for: componentKey.rawValue), expectedSwiftTypeName) } } @@ -105,8 +105,9 @@ class Test_TypeAssigner: Test_Core { } func testContentSwiftName() throws { - let defensiveNameMaker = makeTranslator().typeAssigner.contentSwiftName - let idiomaticNameMaker = makeTranslator(namingStrategy: .idiomatic).typeAssigner.contentSwiftName + let defensiveNameMaker = makeTranslator().context.safeNameGenerator.swiftContentTypeName + let idiomaticNameMaker = makeTranslator(namingStrategy: .idiomatic).context.safeNameGenerator + .swiftContentTypeName let cases: [(input: String, defensive: String, idiomatic: String)] = [ // Short names.