From 31a225d2e932ebe17ac79813e7b6d6e394c51c9d Mon Sep 17 00:00:00 2001 From: Jay Herron Date: Sun, 9 Aug 2026 22:56:26 -0600 Subject: [PATCH] fix: Give input objects coding keys if property name is protected This fixes issues when input object properties have names that are protected Swift keywords. Previously, the fields were renamed which would prevent decoding from the GraphQL input maps. --- .../Generator/GraphQLTypesGenerator.swift | 21 +++++++++++++ .../TypeGeneratorTests.swift | 31 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/Sources/GraphQLGeneratorCore/Generator/GraphQLTypesGenerator.swift b/Sources/GraphQLGeneratorCore/Generator/GraphQLTypesGenerator.swift index 533b6c9..0a7ace5 100644 --- a/Sources/GraphQLGeneratorCore/Generator/GraphQLTypesGenerator.swift +++ b/Sources/GraphQLGeneratorCore/Generator/GraphQLTypesGenerator.swift @@ -249,6 +249,27 @@ package struct GraphQLTypesGenerator { """ } + // Property names are sanitized to be valid Swift identifiers, so where that renames a field, coding keys are + // required for the value to decode from the GraphQL field name. + if fields.keys.contains(where: { nameGenerator.swiftMemberName(for: $0) != $0 }) { + output += """ + + enum CodingKeys: String, CodingKey { + """ + for fieldName in fields.keys { + let safeName = nameGenerator.swiftMemberName(for: fieldName) + let rawValue = safeName == fieldName ? "" : " = \"\(fieldName)\"" + output += """ + + case \(safeName)\(rawValue) + """ + } + output += """ + + } + """ + } + // Swift auto-generates memberwise initializers for structs, so we don't need to generate one output += """ diff --git a/Tests/GraphQLGeneratorCoreTests/TypeGeneratorTests.swift b/Tests/GraphQLGeneratorCoreTests/TypeGeneratorTests.swift index f15d995..786cbe2 100644 --- a/Tests/GraphQLGeneratorCoreTests/TypeGeneratorTests.swift +++ b/Tests/GraphQLGeneratorCoreTests/TypeGeneratorTests.swift @@ -80,6 +80,37 @@ struct TypeGeneratorTests { #expect(result == expected) } + /// Field names that are not valid, idiomatic Swift identifiers are renamed, so the struct needs coding keys to + /// keep decoding from the GraphQL field names. + @Test func generateInputStructWithNonIdiomaticFieldNames() throws { + let inputType = try GraphQLInputObjectType( + name: "FilterInput", + fields: [ + "ID": InputObjectField(type: GraphQLNonNull(GraphQLID)), + "where": InputObjectField(type: GraphQLString), + "name": InputObjectField(type: GraphQLString), + ] + ) + + let result = try generator.generateInputStruct(for: inputType) + + let expected = """ + + struct FilterInput: Codable, Sendable { + let id: String + let _where: String? + let name: String? + enum CodingKeys: String, CodingKey { + case id = "ID" + case _where = "where" + case name + } + } + """ + + #expect(result == expected) + } + @Test func generateInputStructWithRecursiveTypes() throws { let addressInput = try GraphQLInputObjectType( name: "AddressInput"