diff --git a/Sources/SwaggerSwiftCore/Generator/Generator.swift b/Sources/SwaggerSwiftCore/Generator/Generator.swift index 1bbda34..3c4d4af 100644 --- a/Sources/SwaggerSwiftCore/Generator/Generator.swift +++ b/Sources/SwaggerSwiftCore/Generator/Generator.swift @@ -313,6 +313,7 @@ public struct Generator { try dateDecodingStrategy.replacingOccurrences(of: "", with: accControl).write(toFile: "\(targetPath)/DateDecodingStrategy.swift") try apiInitializeFile.replacingOccurrences(of: "", with: accControl).write(toFile: "\(targetPath)/APIInitialize.swift") try apiInitializerFile.replacingOccurrences(of: "", with: accControl).write(toFile: "\(targetPath)/APIInitializer.swift") + try stringCodingKey.replacingOccurrences(of: "", with: accControl).write(toFile: "\(targetPath)/StringCodingKey.swift") if swaggerFile.createSwiftPackage == false { let globalHeadersDefinitions = globalHeadersModel.writeExtensions(inCommonPackageNamed: nil) diff --git a/Sources/SwaggerSwiftCore/Models/Model.swift b/Sources/SwaggerSwiftCore/Models/Model.swift index 461bc25..18d4e85 100644 --- a/Sources/SwaggerSwiftCore/Models/Model.swift +++ b/Sources/SwaggerSwiftCore/Models/Model.swift @@ -60,14 +60,11 @@ struct Model { let fieldHasDefaultValue = fields.contains(where: { $0.defaultValue != nil }) let fieldHasOptionalURL = fields.contains(where: { $0.type.toString(required: true) == "URL" && !$0.isRequired }) - if isCodable && (fieldsChangesSwaggerFieldNames || fieldHasDefaultValue || fieldHasOptionalURL) { + if isCodable { model += "\n\n" - model += decodeFunction().indentLines(1) - } - - if isCodable && fieldsChangesSwaggerFieldNames { + model += decodeFunction(accessControl: accessControl).indentLines(1) model += "\n\n" - model += codingKeysFunction().indentLines(1) + model += encodeFunction(accessControl: accessControl).indentLines(1) } if embeddedDefinitions.count > 0 { @@ -85,19 +82,44 @@ struct Model { return model } - private func codingKeysFunction() -> String { - let cases = fields.map { "case \($0.safeParameterName.value.variableNameFormatted) = \"\($0.argumentLabel)\"" }.joined(separator: "\n").indentLines(1) + private func encodeFunction(accessControl: APIAccessControl) -> String { + let encodeFields = fields.map { + let variableName = $0.safePropertyName.value.variableNameFormatted + let codingKey = $0.argumentLabel + let typeName = $0.type.toString(required: true) + let encodeIfPresent: String + if $0.isRequired == false || $0.defaultValue != nil { + encodeIfPresent = "IfPresent" + } else { + encodeIfPresent = "" + } + + let defaultValue: String + if let defaultValueValue = $0.defaultValue { + defaultValue = " ?? \(defaultValueValue)" + } else { + defaultValue = "" + } + + return "try container.encode\(encodeIfPresent)(\(variableName)\(defaultValue), forKey: \"\(codingKey)\")" + }.joined(separator: "\n") + + let functionBody = """ +var container = encoder.container(keyedBy: StringCodingKey.self) +\(encodeFields) +""" return """ - enum CodingKeys: String, CodingKey { - \(cases) - } - """ +\(accessControl.rawValue) func encode(to encoder: Encoder) throws { +\(functionBody.indentLines(1)) +} +""" } - private func decodeFunction() -> String { + private func decodeFunction(accessControl: APIAccessControl) -> String { let decodeFields = fields.map { let variableName = $0.safePropertyName.value.variableNameFormatted + let codingKey = $0.argumentLabel let typeName = $0.type.toString(required: true) let decodeIfPresent: String if $0.isRequired == false || $0.defaultValue != nil { @@ -116,24 +138,24 @@ struct Model { if !$0.isRequired, typeName == "URL" { return """ // Allows the backend to return badly formatted urls - if let urlString = try container.decode\(decodeIfPresent)(String.self, forKey: .\(variableName))\(defaultValue) { + if let urlString = try container.decode\(decodeIfPresent)(String.self, forKey: \"\(codingKey)\")\(defaultValue) { self.\(variableName) = URL(string: urlString) } else { self.\(variableName) = nil } """ } else { - return "self.\(variableName) = try container.decode\(decodeIfPresent)(\(typeName).self, forKey: .\(variableName))\(defaultValue)" + return "self.\(variableName) = try container.decode\(decodeIfPresent)(\(typeName).self, forKey: \"\(codingKey)\")\(defaultValue)" } }.joined(separator: "\n") let functionBody = """ -let container = try decoder.container(keyedBy: CodingKeys.self) +let container = try decoder.container(keyedBy: StringCodingKey.self) \(decodeFields) """ return """ -public init(from decoder: Decoder) throws { +\(accessControl.rawValue) init(from decoder: Decoder) throws { \(functionBody.indentLines(1)) } """ diff --git a/Sources/SwaggerSwiftCore/Static Files/StringCodingKey.swift b/Sources/SwaggerSwiftCore/Static Files/StringCodingKey.swift new file mode 100644 index 0000000..391e763 --- /dev/null +++ b/Sources/SwaggerSwiftCore/Static Files/StringCodingKey.swift @@ -0,0 +1,29 @@ +let stringCodingKey = """ +import Foundation + + struct StringCodingKey: CodingKey, ExpressibleByStringLiteral { + private let string: String + private var int: Int? + + var stringValue: String { return string } + + init(string: String) { + self.string = string + } + + init?(stringValue: String) { + self.string = stringValue + } + + var intValue: Int? { return int } + + init?(intValue: Int) { + self.string = String(describing: intValue) + self.int = intValue + } + + init(stringLiteral value: String) { + self.string = value + } +} +""" diff --git a/Tests/SwaggerSwiftCoreTests/SwaggerSwiftCoreTests.swift b/Tests/SwaggerSwiftCoreTests/SwaggerSwiftCoreTests.swift index 78db376..7e9b487 100644 --- a/Tests/SwaggerSwiftCoreTests/SwaggerSwiftCoreTests.swift +++ b/Tests/SwaggerSwiftCoreTests/SwaggerSwiftCoreTests.swift @@ -22,14 +22,19 @@ public struct Test: Codable, Sendable { } public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) + let container = try decoder.container(keyedBy: StringCodingKey.self) // Allows the backend to return badly formatted urls - if let urlString = try container.decodeIfPresent(String.self, forKey: .url) { + if let urlString = try container.decodeIfPresent(String.self, forKey: "url") { self.url = URL(string: urlString) } else { self.url = nil } } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + try container.encodeIfPresent(url, forKey: "url") + } } """) } @@ -52,6 +57,16 @@ public struct Test: Codable, Sendable { public init(url: URL) { self.url = url } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: StringCodingKey.self) + self.url = try container.decode(URL.self, forKey: "url") + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: StringCodingKey.self) + try container.encode(url, forKey: "url") + } } """) }