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"