Skip to content

Commit a2045e6

Browse files
Merge pull request #2 from akkp-windsurf/devin/1758700929-extend-shared-types-response-types
feat: extend shared types mechanism to include response types
2 parents e3b74bf + 4d113f5 commit a2045e6

File tree

3 files changed

+64
-1
lines changed

3 files changed

+64
-1
lines changed

plugins/client/graphql-kotlin-client-generator/src/main/kotlin/com/expediagroup/graphql/plugin/client/generator/GraphQLClientGenerator.kt

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ class GraphQLClientGenerator(
204204
}
205205
fileSpecs.add(polymorphicTypeSpec.build())
206206
}
207-
context.typeSpecs.minus(polymorphicTypes).forEach { (className, typeSpec) ->
207+
context.typeSpecs.minus(polymorphicTypes).minus(context.responseClassToTypeSpecs.keys).forEach { (className, typeSpec) ->
208208
val outputTypeFileSpec = FileSpec.builder(className.packageName, className.simpleName)
209209
.addType(typeSpec)
210210
.build()
@@ -213,9 +213,13 @@ class GraphQLClientGenerator(
213213
operationFileSpec.addType(operationTypeSpec.build())
214214
fileSpecs.add(operationFileSpec.build())
215215

216+
// Post-process to identify shared response types
217+
identifySharedResponseTypes(context)
218+
216219
// shared types
217220
sharedTypes.putAll(context.enumClassToTypeSpecs.mapValues { listOf(it.value) })
218221
sharedTypes.putAll(context.inputClassToTypeSpecs.mapValues { listOf(it.value) })
222+
sharedTypes.putAll(context.responseClassToTypeSpecs.mapValues { listOf(it.value) })
219223
context.scalarClassToConverterTypeSpecs
220224
.values
221225
.forEach {
@@ -284,3 +288,47 @@ internal fun String.toUpperUnderscore(): String {
284288
}
285289
return builder.toString()
286290
}
291+
292+
/**
293+
* Post-process to identify response types that can be shared across operations.
294+
* This function analyzes all generated types and moves structurally identical response types to shared storage.
295+
*/
296+
private fun identifySharedResponseTypes(context: GraphQLClientGeneratorContext) {
297+
val signatureToTypes = mutableMapOf<String, MutableList<Pair<ClassName, TypeSpec>>>()
298+
299+
// Group types by their structural signature (only for ObjectTypeDefinition with selection sets)
300+
context.typeSpecs.forEach { (className, typeSpec) ->
301+
// Only consider types that are not polymorphic and have a selection set signature
302+
if (!context.polymorphicTypes.containsKey(className)) {
303+
val packageParts = className.packageName.split(".")
304+
if (packageParts.size > 2) {
305+
val operationName = packageParts.last()
306+
// Try to extract GraphQL type name from the class name
307+
val graphQLTypeName = typeSpec.name ?: return@forEach
308+
309+
// Generate signature for this type (simplified approach)
310+
val signature = "$graphQLTypeName:${typeSpec.propertySpecs.map { it.name }.sorted().joinToString(",")}"
311+
312+
signatureToTypes.getOrPut(signature) { mutableListOf() }.add(className to typeSpec)
313+
}
314+
}
315+
}
316+
317+
// Move types that appear multiple times to shared storage
318+
signatureToTypes.forEach { (signature, types) ->
319+
if (types.size > 1) {
320+
// Use the first type as the shared type
321+
val (sharedClassName, sharedTypeSpec) = types.first()
322+
val taggedTypeSpec = sharedTypeSpec.toBuilder()
323+
.tag(String::class.java, signature)
324+
.build()
325+
326+
context.responseClassToTypeSpecs[sharedClassName] = taggedTypeSpec
327+
328+
// Remove all instances from regular type specs
329+
types.forEach { (className, _) ->
330+
context.typeSpecs.remove(className)
331+
}
332+
}
333+
}
334+
}

plugins/client/graphql-kotlin-client-generator/src/main/kotlin/com/expediagroup/graphql/plugin/client/generator/GraphQLClientGeneratorContext.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ data class GraphQLClientGeneratorContext(
4545
// shared type caches
4646
val enumClassToTypeSpecs: MutableMap<ClassName, TypeSpec> = mutableMapOf()
4747
val inputClassToTypeSpecs: MutableMap<ClassName, TypeSpec> = mutableMapOf()
48+
val responseClassToTypeSpecs: MutableMap<ClassName, TypeSpec> = mutableMapOf()
4849
val scalarClassToConverterTypeSpecs: MutableMap<ClassName, ScalarConverterInfo> = mutableMapOf()
4950
val typeAliases: MutableMap<String, TypeAliasSpec> = mutableMapOf()
5051
internal fun isTypeAlias(typeName: String) = typeAliases.containsKey(typeName)

plugins/client/graphql-kotlin-client-generator/src/main/kotlin/com/expediagroup/graphql/plugin/client/generator/types/generateTypeName.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,3 +258,17 @@ private fun calculateSelectedFields(
258258
}
259259
return result
260260
}
261+
262+
/**
263+
* Generate a structural signature for a response type based on GraphQL type definition and selection set.
264+
* This signature is used to identify structurally identical response types across different operations.
265+
*/
266+
internal fun generateResponseTypeSignature(
267+
graphQLTypeName: String,
268+
selectionSet: SelectionSet?,
269+
context: GraphQLClientGeneratorContext
270+
): String {
271+
if (selectionSet == null) return graphQLTypeName
272+
val selectedFields = calculateSelectedFields(context, graphQLTypeName, selectionSet)
273+
return "$graphQLTypeName:${selectedFields.sorted().joinToString(",")}"
274+
}

0 commit comments

Comments
 (0)