From e8d5674015ca7a4a030662e9be7de36347401307 Mon Sep 17 00:00:00 2001 From: Franklin Wang <9077461+gnawf@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:34:20 +1200 Subject: [PATCH 1/2] Fix renamed shared input type bug (#584) * Add test for renamed shared input type bug * Add failing test for bug * Pick up shared type renames when only used as input type * Update comment --- .../NadelExecutionBlueprintFactory.kt | 74 ++++++---- .../tests/next/UnderlyingSchemaGenerator.kt | 2 +- .../nadel/tests/next/UpdateTestSnapshots.kt | 2 +- .../fixtures/rename/RenamedInputTypeTest.kt | 83 +++++++++++ .../rename/RenamedInputTypeTestSnapshot.kt | 84 +++++++++++ .../rename/RenamedSharedInputTypeTest.kt | 133 ++++++++++++++++++ .../RenamedSharedInputTypeTestSnapshot.kt | 98 +++++++++++++ 7 files changed, 449 insertions(+), 27 deletions(-) create mode 100644 test/src/test/kotlin/graphql/nadel/tests/next/fixtures/rename/RenamedInputTypeTest.kt create mode 100644 test/src/test/kotlin/graphql/nadel/tests/next/fixtures/rename/RenamedInputTypeTestSnapshot.kt create mode 100644 test/src/test/kotlin/graphql/nadel/tests/next/fixtures/rename/RenamedSharedInputTypeTest.kt create mode 100644 test/src/test/kotlin/graphql/nadel/tests/next/fixtures/rename/RenamedSharedInputTypeTestSnapshot.kt diff --git a/lib/src/main/java/graphql/nadel/engine/blueprint/NadelExecutionBlueprintFactory.kt b/lib/src/main/java/graphql/nadel/engine/blueprint/NadelExecutionBlueprintFactory.kt index c85f11f22..fc8ed4d58 100644 --- a/lib/src/main/java/graphql/nadel/engine/blueprint/NadelExecutionBlueprintFactory.kt +++ b/lib/src/main/java/graphql/nadel/engine/blueprint/NadelExecutionBlueprintFactory.kt @@ -10,14 +10,14 @@ import graphql.language.FieldDefinition import graphql.language.ImplementingTypeDefinition import graphql.nadel.Service import graphql.nadel.dsl.FieldMappingDefinition +import graphql.nadel.dsl.NadelHydrationDefinition import graphql.nadel.dsl.RemoteArgumentSource import graphql.nadel.dsl.TypeMappingDefinition -import graphql.nadel.dsl.NadelHydrationDefinition import graphql.nadel.engine.blueprint.hydration.NadelBatchHydrationMatchStrategy import graphql.nadel.engine.blueprint.hydration.NadelHydrationActorInputDef import graphql.nadel.engine.blueprint.hydration.NadelHydrationActorInputDef.ValueSource.FieldResultValue -import graphql.nadel.engine.blueprint.hydration.NadelHydrationStrategy import graphql.nadel.engine.blueprint.hydration.NadelHydrationCondition +import graphql.nadel.engine.blueprint.hydration.NadelHydrationStrategy import graphql.nadel.engine.transform.query.NadelQueryPath import graphql.nadel.engine.util.AnyImplementingTypeDefinition import graphql.nadel.engine.util.AnyNamedNode @@ -771,44 +771,68 @@ private class SharedTypesAnalysis( overallParentType: AnyImplementingTypeDefinition, underlyingParentType: GraphQLFieldsContainer, ): List { - val overallOutputTypeName = overallField.type.unwrapAll().name - val underlyingField = getUnderlyingField(overallField, overallParentType, underlyingParentType) ?: return emptyList() - val renameInstruction = if (overallOutputTypeName !in serviceDefinedTypes) { + val overallOutputTypeName = overallField.type.unwrapAll().name + val underlyingOutputTypeName = underlyingField.type.unwrapAll().name + val outputTypeRenameInstruction = getTypeRenameInstructionOrNull( + overallTypeName = overallOutputTypeName, + underlyingTypeName = underlyingOutputTypeName, + serviceDefinedTypes = serviceDefinedTypes, + service = service, + ) + + val argumentTypeRenameInstructions = overallField.inputValueDefinitions + .mapNotNull { overallArgument -> + val underlyingArgument = underlyingField.getArgument(overallArgument.name) + getTypeRenameInstructionOrNull( + overallTypeName = overallArgument.type.unwrapAll().name, + underlyingTypeName = underlyingArgument.type.unwrapAll().name, + serviceDefinedTypes = serviceDefinedTypes, + service = service, + ) + } + + val overallOutputTypeDefinition = (engineSchema.getType(overallOutputTypeName) as? GraphQLFieldsContainer?) + ?.definition as AnyImplementingTypeDefinition? + + return listOfNotNull(outputTypeRenameInstruction) + argumentTypeRenameInstructions + (overallOutputTypeDefinition + ?.let { + investigateTypeRenames( + visitedTypes, + service, + serviceDefinedTypes, + overallType = overallOutputTypeDefinition, + underlyingType = underlyingField.type.unwrapAll() as GraphQLFieldsContainer, + ) + } ?: emptyList()) + } + + private fun getTypeRenameInstructionOrNull( + overallTypeName: String, + underlyingTypeName: String, + serviceDefinedTypes: Set, + service: Service, + ): NadelTypeRenameInstruction? { + return if (overallTypeName !in serviceDefinedTypes) { // Service does not own type, it is shared // If the name is different than the overall type, then we mark the rename - when (val underlyingOutputTypeName = underlyingField.type.unwrapAll().name) { - overallOutputTypeName -> null + when (underlyingTypeName) { + overallTypeName -> null in scalarTypeNames -> null - else -> when (typeRenameInstructions[overallOutputTypeName]) { + else -> when (typeRenameInstructions[overallTypeName]) { null -> error("Nadel does not allow implicit renames") else -> NadelTypeRenameInstruction( service, - overallName = overallOutputTypeName, - underlyingName = underlyingOutputTypeName, + overallName = overallTypeName, + underlyingName = underlyingTypeName, ) } } } else { null } - - val overallOutputType = engineSchema.getType(overallOutputTypeName) - // Ensure type exists, schema transformation can delete types, so let's just ignore it - .let { it ?: return emptyList() } - // Return if not field container - .let { it as? GraphQLFieldsContainer ?: return emptyList() } - .let { it.definition as AnyImplementingTypeDefinition } - - return listOfNotNull(renameInstruction) + investigateTypeRenames( - visitedTypes, - service, - serviceDefinedTypes, - overallType = overallOutputType, - underlyingType = underlyingField.type.unwrapAll() as GraphQLFieldsContainer, - ) } private fun getUnderlyingField( diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/UnderlyingSchemaGenerator.kt b/test/src/test/kotlin/graphql/nadel/tests/next/UnderlyingSchemaGenerator.kt index 7580e6904..f0d622eaf 100644 --- a/test/src/test/kotlin/graphql/nadel/tests/next/UnderlyingSchemaGenerator.kt +++ b/test/src/test/kotlin/graphql/nadel/tests/next/UnderlyingSchemaGenerator.kt @@ -333,11 +333,11 @@ private fun transformFields(fieldDefinitions: List): List field.transform { fieldBuilder -> - fieldBuilder .name(field.getUnderlyingName()) .directives(field.directives.filterNotNadelDirectives()) .type(field.type.getUnderlyingType()) + .inputValueDefinitions(transformInputValueDefinitions(field.inputValueDefinitions)) } } .toList() diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/UpdateTestSnapshots.kt b/test/src/test/kotlin/graphql/nadel/tests/next/UpdateTestSnapshots.kt index c836e34d1..264e73bb6 100644 --- a/test/src/test/kotlin/graphql/nadel/tests/next/UpdateTestSnapshots.kt +++ b/test/src/test/kotlin/graphql/nadel/tests/next/UpdateTestSnapshots.kt @@ -223,7 +223,7 @@ private fun makeConstructorInvocationToExpectedServiceCall(call: TestExecutionCa ExpectedServiceCall::query.name add("%L = %S,\n", ExpectedServiceCall::service.name, call.service) add("%L = %S,\n", ExpectedServiceCall::query.name, call.query.replaceIndent(" ")) - add("%L = %S,\n", ExpectedServiceCall::variables.name, call.variables) + add("%L = %S,\n", ExpectedServiceCall::variables.name, writeResultJson(call.variables)) add("%L = %S,\n", ExpectedServiceCall::result.name, writeResultJson(call.result)) add("delayedResults = %M", listOfJsonStringsMember) diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/rename/RenamedInputTypeTest.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/rename/RenamedInputTypeTest.kt new file mode 100644 index 000000000..23fcc9a9b --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/rename/RenamedInputTypeTest.kt @@ -0,0 +1,83 @@ +package graphql.nadel.tests.next.fixtures.rename + +import graphql.nadel.NadelExecutionHints +import graphql.nadel.tests.next.NadelIntegrationTest + +/** + * The `ConfluenceLegacyPathType` input type was renamed. + * + * In the test snapshot we ensure the variable is defined as `PathType`. + */ +class RenamedInputTypeTest : NadelIntegrationTest( + query = """ + query { + me { + profilePicture { + path(type: ABSOLUTE) + } + } + } + """.trimIndent(), + services = listOf( + Service( + name = "confluence_legacy", + overallSchema = """ + type Query { + me: ConfluenceLegacyUser + } + type ConfluenceLegacyUser @renamed(from: "User") { + profilePicture: ConfluenceLegacyProfilePicture + } + type ConfluenceLegacyProfilePicture @renamed(from: "ProfilePicture") { + path(type: ConfluenceLegacyPathType!): String + } + enum ConfluenceLegacyPathType @renamed(from: "PathType") { + ABSOLUTE + RELATIVE + } + """.trimIndent(), + runtimeWiring = { wiring -> + data class ProfilePicture( + val absolutePath: String, + val relativePath: String, + ) + + data class User( + val profilePicture: ProfilePicture, + ) + + wiring + .type("Query") { type -> + type + .dataFetcher("me") { env -> + User( + profilePicture = ProfilePicture( + relativePath = "/wiki/aa-avatar/5ee0a4ef55749e0ab6e0fb70", + absolutePath = "https://atlassian.net/wiki/aa-avatar/5ee0a4ef55749e0ab6e0fb70", + ), + ) + } + } + .type("ProfilePicture") { type -> + type + .dataFetcher("path") { env -> + val pfp = env.getSource()!! + when (val urlType = env.getArgument("type")) { + "ABSOLUTE" -> pfp.absolutePath + "RELATIVE" -> pfp.relativePath + else -> throw IllegalArgumentException(urlType) + } + } + } + }, + ), + ), +) { + override fun makeExecutionHints(): NadelExecutionHints.Builder { + return super.makeExecutionHints() + // todo: this should be on by default + .allDocumentVariablesHint { + true + } + } +} diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/rename/RenamedInputTypeTestSnapshot.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/rename/RenamedInputTypeTestSnapshot.kt new file mode 100644 index 000000000..6810dc60e --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/rename/RenamedInputTypeTestSnapshot.kt @@ -0,0 +1,84 @@ +// @formatter:off +package graphql.nadel.tests.next.fixtures.rename + +import graphql.nadel.tests.next.ExpectedNadelResult +import graphql.nadel.tests.next.ExpectedServiceCall +import graphql.nadel.tests.next.TestSnapshot +import graphql.nadel.tests.next.listOfJsonStrings +import kotlin.Suppress +import kotlin.collections.List +import kotlin.collections.listOf + +private suspend fun main() { + graphql.nadel.tests.next.update() +} + +/** + * This class is generated. Do NOT modify. + * + * Refer to [graphql.nadel.tests.next.UpdateTestSnapshots + */ +@Suppress("unused") +public class RenamedInputTypeTestSnapshot : TestSnapshot() { + override val calls: List = listOf( + ExpectedServiceCall( + service = "confluence_legacy", + query = """ + | query (${'$'}v0: PathType!) { + | me { + | profilePicture { + | path(type: ${'$'}v0) + | } + | } + | } + """.trimMargin(), + variables = """ + | { + | "v0": "ABSOLUTE" + | } + """.trimMargin(), + result = """ + | { + | "data": { + | "me": { + | "profilePicture": { + | "path": "https://atlassian.net/wiki/aa-avatar/5ee0a4ef55749e0ab6e0fb70" + | } + | } + | } + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + ), + ), + ) + + /** + * ```json + * { + * "data": { + * "me": { + * "profilePicture": { + * "path": "https://atlassian.net/wiki/aa-avatar/5ee0a4ef55749e0ab6e0fb70" + * } + * } + * } + * } + * ``` + */ + override val result: ExpectedNadelResult = ExpectedNadelResult( + result = """ + | { + | "data": { + | "me": { + | "profilePicture": { + | "path": "https://atlassian.net/wiki/aa-avatar/5ee0a4ef55749e0ab6e0fb70" + | } + | } + | } + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + ), + ) +} diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/rename/RenamedSharedInputTypeTest.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/rename/RenamedSharedInputTypeTest.kt new file mode 100644 index 000000000..b1db15f1b --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/rename/RenamedSharedInputTypeTest.kt @@ -0,0 +1,133 @@ +package graphql.nadel.tests.next.fixtures.rename + +import graphql.nadel.NadelExecutionHints +import graphql.nadel.tests.next.NadelIntegrationTest + +/** + * The ConfluenceLegacyPathType enum type is renamed, and we share it between the two defined services in this test. + * The enum is also exclusively used as an input type, never as an output type. + * + * We need to ensure that it's renamed when sent to the underlying service. + */ +class RenamedSharedInputTypeTest : NadelIntegrationTest( + query = """ + query { + something { + users { + profilePicture { + path(type: ABSOLUTE) + } + } + } + } + """.trimIndent(), + services = listOf( + Service( + name = "confluence_legacy", + overallSchema = """ + type Query { + me: ConfluenceLegacyUser + } + type ConfluenceLegacyUser @renamed(from: "User") { + profilePicture: ConfluenceLegacyProfilePicture + } + type ConfluenceLegacyProfilePicture @renamed(from: "ProfilePicture") { + path(type: ConfluenceLegacyPathType!): String + } + enum ConfluenceLegacyPathType @renamed(from: "PathType") { + ABSOLUTE + RELATIVE + } + """.trimIndent(), + runtimeWiring = { wiring -> + wiring + .type("Query") { type -> + type + .dataFetcher("me") { env -> + throw UnsupportedOperationException("Not implemented") + } + } + }, + ), + Service( + name = "confluence_something", + overallSchema = """ + type Query { + something: ConfluenceLegacySomething + } + type ConfluenceLegacySomething @renamed(from: "Something") { + users: [ConfluenceLegacyUser] + } + """.trimIndent(), + // Need to explicitly declare underlying schema for shared type + underlyingSchema = """ + type Query { + something: Something + } + type Something { + users: [User] + } + type User { + profilePicture: ProfilePicture + } + type ProfilePicture { + path(type: PathType!): String + } + enum PathType { + ABSOLUTE + RELATIVE + } + """.trimIndent(), + runtimeWiring = { wiring -> + data class ProfilePicture( + val absolutePath: String, + val relativePath: String, + ) + + data class User( + val profilePicture: ProfilePicture, + ) + + data class Something( + val users: List, + ) + + wiring + .type("Query") { type -> + type + .dataFetcher("something") { env -> + Something( + users = listOf( + User( + profilePicture = ProfilePicture( + relativePath = "/wiki/aa-avatar/5ee0a4ef55749e0ab6e0fb70", + absolutePath = "https://atlassian.net/wiki/aa-avatar/5ee0a4ef55749e0ab6e0fb70", + ), + ), + ), + ) + } + } + .type("ProfilePicture") { type -> + type + .dataFetcher("path") { env -> + val pfp = env.getSource()!! + when (val urlType = env.getArgument("type")) { + "ABSOLUTE" -> pfp.absolutePath + "RELATIVE" -> pfp.relativePath + else -> throw IllegalArgumentException(urlType) + } + } + } + }, + ), + ), +) { + override fun makeExecutionHints(): NadelExecutionHints.Builder { + return super.makeExecutionHints() + // todo: this should be on by default + .allDocumentVariablesHint { + true + } + } +} diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/rename/RenamedSharedInputTypeTestSnapshot.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/rename/RenamedSharedInputTypeTestSnapshot.kt new file mode 100644 index 000000000..38e2e20fa --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/rename/RenamedSharedInputTypeTestSnapshot.kt @@ -0,0 +1,98 @@ +// @formatter:off +package graphql.nadel.tests.next.fixtures.rename + +import graphql.nadel.tests.next.ExpectedNadelResult +import graphql.nadel.tests.next.ExpectedServiceCall +import graphql.nadel.tests.next.TestSnapshot +import graphql.nadel.tests.next.listOfJsonStrings +import kotlin.Suppress +import kotlin.collections.List +import kotlin.collections.listOf + +private suspend fun main() { + graphql.nadel.tests.next.update() +} + +/** + * This class is generated. Do NOT modify. + * + * Refer to [graphql.nadel.tests.next.UpdateTestSnapshots + */ +@Suppress("unused") +public class RenamedSharedInputTypeTestSnapshot : TestSnapshot() { + override val calls: List = listOf( + ExpectedServiceCall( + service = "confluence_something", + query = """ + | query (${'$'}v0: PathType!) { + | something { + | users { + | profilePicture { + | path(type: ${'$'}v0) + | } + | } + | } + | } + """.trimMargin(), + variables = """ + | { + | "v0": "ABSOLUTE" + | } + """.trimMargin(), + result = """ + | { + | "data": { + | "something": { + | "users": [ + | { + | "profilePicture": { + | "path": "https://atlassian.net/wiki/aa-avatar/5ee0a4ef55749e0ab6e0fb70" + | } + | } + | ] + | } + | } + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + ), + ), + ) + + /** + * ```json + * { + * "data": { + * "something": { + * "users": [ + * { + * "profilePicture": { + * "path": "https://atlassian.net/wiki/aa-avatar/5ee0a4ef55749e0ab6e0fb70" + * } + * } + * ] + * } + * } + * } + * ``` + */ + override val result: ExpectedNadelResult = ExpectedNadelResult( + result = """ + | { + | "data": { + | "something": { + | "users": [ + | { + | "profilePicture": { + | "path": "https://atlassian.net/wiki/aa-avatar/5ee0a4ef55749e0ab6e0fb70" + | } + | } + | ] + | } + | } + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + ), + ) +} From 5630e9aaa36609c656945c8a31271e1804458b7f Mon Sep 17 00:00:00 2001 From: sbarker2 <109047597+sbarker2@users.noreply.github.com> Date: Mon, 2 Sep 2024 09:17:30 +1000 Subject: [PATCH 2/2] Apply transforms to defer payloads (#579) * add error test for defer (#574) * add initial defer transform tests (#576) * initial defer logic (#577) * get transforms actually working for deferred payloads (#578) * improve JsonNodes class * passing in NadelQueryPath instead of List to allow better type safety * remove unused if block * add ability to log errors for defer transforms * add extra defer tests for fields in lists and fields in hydrations * move processing of graphql errors * remove incorrect test * remove unused test * add logic to move transform functions next to each other * add level of abstraction for reused code in transform functions * add assertShouldNeverHappen when prefix does not match query path in JsonNodes * remove empty file * clean up defer transform code --- .../main/java/graphql/nadel/NextgenEngine.kt | 44 +++- .../graphql/nadel/ServiceExecutionResult.kt | 4 +- .../transform/NadelDeepRenameTransform.kt | 2 + .../engine/transform/NadelRenameTransform.kt | 2 + .../engine/transform/NadelTransformUtil.kt | 3 + .../hydration/NadelHydrationTransform.kt | 1 + .../batch/NadelBatchHydrationTransform.kt | 1 + .../nadel/engine/transform/query/NFUtil.kt | 11 + .../engine/transform/query/NadelQueryPath.kt | 17 ++ .../result/NadelResultTransformer.kt | 100 +++++++-- .../engine/transform/result/json/JsonNodes.kt | 24 ++- .../graphql/nadel/engine/util/GraphQLUtil.kt | 20 +- .../transforms/DeferredFieldIsRenamedTest.kt | 69 ++++++ .../DeferredFieldIsRenamedTestSnapshot.kt | 109 ++++++++++ .../DeferredListFieldIsRenamedTest.kt | 102 +++++++++ .../DeferredListFieldIsRenamedTestSnapshot.kt | 160 ++++++++++++++ ...leRenameTransformsInsideAndOutsideDefer.kt | 71 ++++++ ...TransformsInsideAndOutsideDeferSnapshot.kt | 111 ++++++++++ ...ultipleRenamedFieldsAreDeferredTogether.kt | 75 +++++++ ...enamedFieldsAreDeferredTogetherSnapshot.kt | 114 ++++++++++ .../MultipleSeparateDefersWithTransforms.kt | 79 +++++++ ...pleSeparateDefersWithTransformsSnapshot.kt | 146 +++++++++++++ ...ngDeferButNonDeferredFieldIsRenamedTest.kt | 69 ++++++ ...utNonDeferredFieldIsRenamedTestSnapshot.kt | 110 ++++++++++ .../transforms/RenameInnerFieldInsideDefer.kt | 79 +++++++ .../RenameInnerFieldInsideDeferSnapshot.kt | 111 ++++++++++ .../defer/HydrationDeferInRenamedField.kt | 36 ++++ ...ydrationRenamedFieldInDeferTestSnapshot.kt | 133 ++++++++++++ ...NestedHydrationsInsideDeferTestSnapshot.kt | 203 ++++++++++++++++++ 29 files changed, 1953 insertions(+), 53 deletions(-) create mode 100644 test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/DeferredFieldIsRenamedTest.kt create mode 100644 test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/DeferredFieldIsRenamedTestSnapshot.kt create mode 100644 test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/DeferredListFieldIsRenamedTest.kt create mode 100644 test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/DeferredListFieldIsRenamedTestSnapshot.kt create mode 100644 test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleRenameTransformsInsideAndOutsideDefer.kt create mode 100644 test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleRenameTransformsInsideAndOutsideDeferSnapshot.kt create mode 100644 test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleRenamedFieldsAreDeferredTogether.kt create mode 100644 test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleRenamedFieldsAreDeferredTogetherSnapshot.kt create mode 100644 test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleSeparateDefersWithTransforms.kt create mode 100644 test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleSeparateDefersWithTransformsSnapshot.kt create mode 100644 test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/QueryContainingDeferButNonDeferredFieldIsRenamedTest.kt create mode 100644 test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/QueryContainingDeferButNonDeferredFieldIsRenamedTestSnapshot.kt create mode 100644 test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/RenameInnerFieldInsideDefer.kt create mode 100644 test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/RenameInnerFieldInsideDeferSnapshot.kt create mode 100644 test/src/test/kotlin/graphql/nadel/tests/next/fixtures/hydration/defer/HydrationRenamedFieldInDeferTestSnapshot.kt create mode 100644 test/src/test/kotlin/graphql/nadel/tests/next/fixtures/hydration/defer/RenamedFieldInsideNestedHydrationsInsideDeferTestSnapshot.kt diff --git a/lib/src/main/java/graphql/nadel/NextgenEngine.kt b/lib/src/main/java/graphql/nadel/NextgenEngine.kt index c5b065b8e..ee158d2a2 100644 --- a/lib/src/main/java/graphql/nadel/NextgenEngine.kt +++ b/lib/src/main/java/graphql/nadel/NextgenEngine.kt @@ -6,6 +6,7 @@ import graphql.ExecutionResult import graphql.GraphQLError import graphql.execution.ExecutionIdProvider import graphql.execution.instrumentation.InstrumentationState +import graphql.incremental.DeferPayload import graphql.incremental.IncrementalExecutionResultImpl import graphql.introspection.Introspection.TypeNameMetaFieldDef import graphql.language.Document @@ -25,6 +26,7 @@ import graphql.nadel.engine.transform.query.DynamicServiceResolution import graphql.nadel.engine.transform.query.NadelFieldToService import graphql.nadel.engine.transform.query.NadelQueryTransformer import graphql.nadel.engine.transform.result.NadelResultTransformer +import graphql.nadel.engine.util.MutableJsonMap import graphql.nadel.engine.util.beginExecute import graphql.nadel.engine.util.compileToDocument import graphql.nadel.engine.util.copy @@ -62,6 +64,8 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.cancel import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.future.asCompletableFuture import kotlinx.coroutines.future.asDeferred import kotlinx.coroutines.future.await @@ -291,6 +295,29 @@ internal class NextgenEngine( executionHydrationDetails = executionContext.hydrationDetails, ) } + if (result is NadelIncrementalServiceExecutionResult) { + executionContext.incrementalResultSupport.defer( + result.incrementalItemPublisher + .asFlow() + .onEach {delayedIncrementalResult -> + // Transform + delayedIncrementalResult.incremental + ?.filterIsInstance() + ?.forEach {deferPayload -> + resultTransformer + .transform( + executionContext = executionContext, + serviceExecutionContext = serviceExecutionContext, + executionPlan = executionPlan, + artificialFields = queryTransform.artificialFields, + overallToUnderlyingFields = queryTransform.overallToUnderlyingFields, + service = service, + result = result, + deferPayload = deferPayload, + ) } + } + ) + } val transformedResult: ServiceExecutionResult = when { topLevelField.name.startsWith("__") -> result else -> timer.time(step = RootStep.ResultTransforming) { @@ -381,18 +408,15 @@ internal class NextgenEngine( ) } - if (serviceExecResult is NadelIncrementalServiceExecutionResult) { - executionContext.incrementalResultSupport.defer( - serviceExecResult.incrementalItemPublisher.asFlow() - ) + val transformedData: MutableJsonMap = serviceExecResult.data.let { data -> + data.takeIf { transformedQuery.resultKey in data } + ?: mutableMapOf(transformedQuery.resultKey to null) } - return serviceExecResult.copy( - data = serviceExecResult.data.let { data -> - data.takeIf { transformedQuery.resultKey in data } - ?: mutableMapOf(transformedQuery.resultKey to null) - }, - ) + return when(serviceExecResult) { + is NadelServiceExecutionResultImpl -> serviceExecResult.copy(data = transformedData) + is NadelIncrementalServiceExecutionResult -> serviceExecResult.copy(data = transformedData) + } } private fun chooseServiceExecution( diff --git a/lib/src/main/java/graphql/nadel/ServiceExecutionResult.kt b/lib/src/main/java/graphql/nadel/ServiceExecutionResult.kt index f7be68684..7323e169c 100644 --- a/lib/src/main/java/graphql/nadel/ServiceExecutionResult.kt +++ b/lib/src/main/java/graphql/nadel/ServiceExecutionResult.kt @@ -10,7 +10,7 @@ sealed class ServiceExecutionResult { abstract val extensions: MutableMap } -class NadelIncrementalServiceExecutionResult( +data class NadelIncrementalServiceExecutionResult( override val data: MutableMap = LinkedHashMap(), override val errors: MutableList> = ArrayList(), override val extensions: MutableMap = LinkedHashMap(), @@ -19,7 +19,7 @@ class NadelIncrementalServiceExecutionResult( val hasNext: Boolean, ) : ServiceExecutionResult() -class NadelServiceExecutionResultImpl @JvmOverloads constructor( +data class NadelServiceExecutionResultImpl @JvmOverloads constructor( override val data: MutableMap = LinkedHashMap(), override val errors: MutableList> = ArrayList(), override val extensions: MutableMap = LinkedHashMap(), diff --git a/lib/src/main/java/graphql/nadel/engine/transform/NadelDeepRenameTransform.kt b/lib/src/main/java/graphql/nadel/engine/transform/NadelDeepRenameTransform.kt index 976c7c573..beb4a5886 100644 --- a/lib/src/main/java/graphql/nadel/engine/transform/NadelDeepRenameTransform.kt +++ b/lib/src/main/java/graphql/nadel/engine/transform/NadelDeepRenameTransform.kt @@ -216,6 +216,7 @@ internal class NadelDeepRenameTransform : NadelTransform { return NadelTransformUtil.makeTypeNameField( aliasHelper = state.aliasHelper, objectTypeNames = objectTypeNames, + deferredExecutions = field.deferredExecutions, ) } @@ -170,6 +171,7 @@ internal class NadelRenameTransform : NadelTransform { queryPathToField = NadelQueryPath(listOf(rename.underlyingName)), fieldArguments = field.normalizedArguments, fieldChildren = transformer.transform(field.children), + deferredExecutions = field.deferredExecutions, ) ) } diff --git a/lib/src/main/java/graphql/nadel/engine/transform/NadelTransformUtil.kt b/lib/src/main/java/graphql/nadel/engine/transform/NadelTransformUtil.kt index 52570b4ed..19f6ad532 100644 --- a/lib/src/main/java/graphql/nadel/engine/transform/NadelTransformUtil.kt +++ b/lib/src/main/java/graphql/nadel/engine/transform/NadelTransformUtil.kt @@ -11,6 +11,7 @@ import graphql.nadel.engine.util.getField import graphql.nadel.engine.util.makeFieldCoordinates import graphql.normalized.ExecutableNormalizedField import graphql.normalized.ExecutableNormalizedField.newNormalizedField +import graphql.normalized.incremental.NormalizedDeferredExecution import graphql.schema.GraphQLFieldDefinition object NadelTransformUtil { @@ -36,11 +37,13 @@ object NadelTransformUtil { fun makeTypeNameField( aliasHelper: NadelAliasHelper, objectTypeNames: List, + deferredExecutions: LinkedHashSet ): ExecutableNormalizedField { return newNormalizedField() .alias(aliasHelper.typeNameResultKey) .fieldName(TypeNameMetaFieldDef.name) .objectTypeNames(objectTypeNames) + .deferredExecutions(deferredExecutions) .build() } diff --git a/lib/src/main/java/graphql/nadel/engine/transform/hydration/NadelHydrationTransform.kt b/lib/src/main/java/graphql/nadel/engine/transform/hydration/NadelHydrationTransform.kt index b804b4b61..99cb7d2f8 100644 --- a/lib/src/main/java/graphql/nadel/engine/transform/hydration/NadelHydrationTransform.kt +++ b/lib/src/main/java/graphql/nadel/engine/transform/hydration/NadelHydrationTransform.kt @@ -145,6 +145,7 @@ internal class NadelHydrationTransform( return makeTypeNameField( aliasHelper = state.aliasHelper, objectTypeNames = objectTypeNames, + deferredExecutions = linkedSetOf(), ) } diff --git a/lib/src/main/java/graphql/nadel/engine/transform/hydration/batch/NadelBatchHydrationTransform.kt b/lib/src/main/java/graphql/nadel/engine/transform/hydration/batch/NadelBatchHydrationTransform.kt index ac2340dbe..012befe55 100644 --- a/lib/src/main/java/graphql/nadel/engine/transform/hydration/batch/NadelBatchHydrationTransform.kt +++ b/lib/src/main/java/graphql/nadel/engine/transform/hydration/batch/NadelBatchHydrationTransform.kt @@ -140,6 +140,7 @@ internal class NadelBatchHydrationTransform( return makeTypeNameField( aliasHelper = state.aliasHelper, objectTypeNames = objectTypeNames, + deferredExecutions = linkedSetOf(), ) } } diff --git a/lib/src/main/java/graphql/nadel/engine/transform/query/NFUtil.kt b/lib/src/main/java/graphql/nadel/engine/transform/query/NFUtil.kt index a945d530a..d5ad5f456 100644 --- a/lib/src/main/java/graphql/nadel/engine/transform/query/NFUtil.kt +++ b/lib/src/main/java/graphql/nadel/engine/transform/query/NFUtil.kt @@ -2,6 +2,7 @@ package graphql.nadel.engine.transform.query import graphql.normalized.ExecutableNormalizedField import graphql.normalized.NormalizedInputValue +import graphql.normalized.incremental.NormalizedDeferredExecution import graphql.schema.GraphQLInterfaceType import graphql.schema.GraphQLObjectType import graphql.schema.GraphQLOutputType @@ -18,6 +19,7 @@ object NFUtil { pathToField: NadelQueryPath, fieldArguments: Map, fieldChildren: List, + deferredExecutions: LinkedHashSet = LinkedHashSet(), ): List { return createFieldRecursively( schema, @@ -26,6 +28,7 @@ object NFUtil { fieldArguments, fieldChildren, pathToFieldIndex = 0, + deferredExecutions, ) } @@ -35,6 +38,7 @@ object NFUtil { queryPathToField: NadelQueryPath, fieldArguments: Map, fieldChildren: List, + deferredExecutions: LinkedHashSet = LinkedHashSet(), ): ExecutableNormalizedField { return createParticularField( schema, @@ -43,6 +47,7 @@ object NFUtil { fieldArguments, fieldChildren, pathToFieldIndex = 0, + deferredExecutions, ) } @@ -53,6 +58,7 @@ object NFUtil { fieldArguments: Map, fieldChildren: List, pathToFieldIndex: Int, + deferredExecutions: LinkedHashSet, ): List { // Note: remember that we are creating fields that do not exist in the original NF // Thus, we need to handle interfaces and object types @@ -65,6 +71,7 @@ object NFUtil { fieldArguments, fieldChildren, pathToFieldIndex, + deferredExecutions, ) } is GraphQLObjectType -> listOf( @@ -75,6 +82,7 @@ object NFUtil { fieldArguments, fieldChildren, pathToFieldIndex, + deferredExecutions, ) ) else -> error("Unsupported type '${parentType.javaClass.name}'") @@ -88,6 +96,7 @@ object NFUtil { fieldArguments: Map, fieldChildren: List, pathToFieldIndex: Int, + deferredExecutions: LinkedHashSet, ): ExecutableNormalizedField { val fieldName = queryPathToField.segments[pathToFieldIndex] val fieldDef = parentType.getFieldDefinition(fieldName) @@ -96,6 +105,7 @@ object NFUtil { return ExecutableNormalizedField.newNormalizedField() .objectTypeNames(listOf(parentType.name)) .fieldName(fieldName) + .deferredExecutions(deferredExecutions) .also { builder -> if (pathToFieldIndex == queryPathToField.segments.lastIndex) { builder.normalizedArguments(fieldArguments) @@ -112,6 +122,7 @@ object NFUtil { fieldArguments, fieldChildren, pathToFieldIndex = pathToFieldIndex + 1, + deferredExecutions, ) } ) diff --git a/lib/src/main/java/graphql/nadel/engine/transform/query/NadelQueryPath.kt b/lib/src/main/java/graphql/nadel/engine/transform/query/NadelQueryPath.kt index 7843415d7..5d530f551 100644 --- a/lib/src/main/java/graphql/nadel/engine/transform/query/NadelQueryPath.kt +++ b/lib/src/main/java/graphql/nadel/engine/transform/query/NadelQueryPath.kt @@ -1,5 +1,7 @@ package graphql.nadel.engine.transform.query +import graphql.Assert.assertShouldNeverHappen + data class NadelQueryPath(val segments: List) { val size: Int get() = segments.size @@ -23,6 +25,21 @@ data class NadelQueryPath(val segments: List) { return segments.last() } + fun startsWith(prefix: List): Boolean { + if (prefix.size > segments.size) return false + for (i in 0..): NadelQueryPath { + if (this.startsWith(prefix)) { + return NadelQueryPath(segments.drop(prefix.size)) + } + return assertShouldNeverHappen("NadelQueryPath did not start with prefix") + } + companion object { val root = NadelQueryPath(emptyList()) diff --git a/lib/src/main/java/graphql/nadel/engine/transform/result/NadelResultTransformer.kt b/lib/src/main/java/graphql/nadel/engine/transform/result/NadelResultTransformer.kt index 1fa73da9e..2f0c2877c 100644 --- a/lib/src/main/java/graphql/nadel/engine/transform/result/NadelResultTransformer.kt +++ b/lib/src/main/java/graphql/nadel/engine/transform/result/NadelResultTransformer.kt @@ -1,11 +1,15 @@ package graphql.nadel.engine.transform.result +import graphql.GraphQLError +import graphql.incremental.DeferPayload +import graphql.nadel.NadelIncrementalServiceExecutionResult import graphql.nadel.Service import graphql.nadel.ServiceExecutionResult import graphql.nadel.engine.NadelExecutionContext import graphql.nadel.engine.NadelServiceExecutionContext import graphql.nadel.engine.blueprint.NadelOverallExecutionBlueprint import graphql.nadel.engine.plan.NadelExecutionPlan +import graphql.nadel.engine.transform.query.NadelQueryPath import graphql.nadel.engine.transform.result.json.JsonNodes import graphql.nadel.engine.util.JsonMap import graphql.nadel.engine.util.MutableJsonMap @@ -24,22 +28,70 @@ internal class NadelResultTransformer(private val executionBlueprint: NadelOvera artificialFields: List, overallToUnderlyingFields: Map>, service: Service, - result: ServiceExecutionResult, + result: ServiceExecutionResult ): ServiceExecutionResult { val nodes = JsonNodes(result.data) + val instructions = getMutationInstructions( + executionContext, + serviceExecutionContext, + executionPlan, + artificialFields, + overallToUnderlyingFields, + service, + result, + nodes + ) + mutate(result, instructions) + return result + } - val deferredInstructions = ArrayList>>() + suspend fun transform( + executionContext: NadelExecutionContext, + serviceExecutionContext: NadelServiceExecutionContext, + executionPlan: NadelExecutionPlan, + artificialFields: List, + overallToUnderlyingFields: Map>, + service: Service, + result: ServiceExecutionResult, + deferPayload: DeferPayload + ): DeferPayload { + val nodes = JsonNodes( + deferPayload.getData() ?: emptyMap(), + pathPrefix = NadelQueryPath(deferPayload.path.filterIsInstance()) + ) + val instructions = getMutationInstructions( + executionContext, + serviceExecutionContext, + executionPlan, + artificialFields, + overallToUnderlyingFields, + service, + result, + nodes + ) + mutate(deferPayload, instructions) + return deferPayload + } + + private suspend fun getMutationInstructions( + executionContext: NadelExecutionContext, + serviceExecutionContext: NadelServiceExecutionContext, + executionPlan: NadelExecutionPlan, + artificialFields: List, + overallToUnderlyingFields: Map>, + service: Service, + result: ServiceExecutionResult, + nodes: JsonNodes + ): List { + val asyncInstructions = ArrayList>>() coroutineScope { for ((field, steps) in executionPlan.transformationSteps) { - // This can be null if we did not end up sending the field e.g. for hydration val underlyingFields = overallToUnderlyingFields[field] - if (underlyingFields.isNullOrEmpty()) { - continue - } + if (underlyingFields.isNullOrEmpty()) continue for (step in steps) { - deferredInstructions.add( + asyncInstructions.add( async { step.transform.getResultInstructions( executionContext, @@ -50,29 +102,24 @@ internal class NadelResultTransformer(private val executionBlueprint: NadelOvera underlyingFields.first().parent, result, step.state, - nodes, + nodes ) - }, + } ) } } - deferredInstructions.add( + asyncInstructions.add( async { getRemoveArtificialFieldInstructions(artificialFields, nodes) - }, + } ) } - val instructions = deferredInstructions - .awaitAll() - .flatten() - - mutate(result, instructions) - - return result + return asyncInstructions.awaitAll().flatten() } + private fun mutate(result: ServiceExecutionResult, instructions: List) { instructions.forEach { transformation -> when (transformation) { @@ -83,6 +130,16 @@ internal class NadelResultTransformer(private val executionBlueprint: NadelOvera } } + private fun mutate(result: DeferPayload, instructions: List) { + instructions.forEach { transformation -> + when (transformation) { + is NadelResultInstruction.Set -> process(transformation) + is NadelResultInstruction.Remove -> process(transformation) + is NadelResultInstruction.AddError -> processGraphQLErrors(transformation, result.errors) + } + } + } + private fun process( instruction: NadelResultInstruction.Set, ) { @@ -110,6 +167,13 @@ internal class NadelResultTransformer(private val executionBlueprint: NadelOvera mutableErrors.add(newError) } + private fun processGraphQLErrors( + instruction: NadelResultInstruction.AddError, + errors: List?, + ) { + errors?.asMutable()?.add(instruction.error) + } + private fun getRemoveArtificialFieldInstructions( artificialFields: List, nodes: JsonNodes, diff --git a/lib/src/main/java/graphql/nadel/engine/transform/result/json/JsonNodes.kt b/lib/src/main/java/graphql/nadel/engine/transform/result/json/JsonNodes.kt index b801c2461..2a2e2569e 100644 --- a/lib/src/main/java/graphql/nadel/engine/transform/result/json/JsonNodes.kt +++ b/lib/src/main/java/graphql/nadel/engine/transform/result/json/JsonNodes.kt @@ -1,5 +1,7 @@ package graphql.nadel.engine.transform.result.json +import graphql.Assert +import graphql.Assert.assertShouldNeverHappen import graphql.nadel.engine.transform.query.NadelQueryPath import graphql.nadel.engine.util.AnyList import graphql.nadel.engine.util.AnyMap @@ -11,6 +13,9 @@ import java.util.concurrent.ConcurrentHashMap * * Use [NadelCachingJsonNodes] for the most part because that is faster. * It is the default implementation. + * + * @param data The JSON map data. + * @param pathPrefix For incremental (defer) payloads, this is the prefix that needs to be removed from the path. */ interface JsonNodes { /** @@ -19,12 +24,12 @@ interface JsonNodes { fun getNodesAt(queryPath: NadelQueryPath, flatten: Boolean = false): List companion object { - internal var nodesFactory: (JsonMap) -> JsonNodes = { - NadelCachingJsonNodes(it) + internal var nodesFactory: (JsonMap, NadelQueryPath?) -> JsonNodes = { data, pathPrefix -> + NadelCachingJsonNodes(data, pathPrefix) } - operator fun invoke(data: JsonMap): JsonNodes { - return nodesFactory(data) + operator fun invoke(data: JsonMap, pathPrefix: NadelQueryPath? = null): JsonNodes { + return nodesFactory(data, pathPrefix) } } } @@ -34,18 +39,27 @@ interface JsonNodes { */ class NadelCachingJsonNodes( private val data: JsonMap, + private val pathPrefix: NadelQueryPath? = null, // for incremental (defer) payloads, we pass in the prefix we need to remove from path ) : JsonNodes { private val nodes = ConcurrentHashMap>() override fun getNodesAt(queryPath: NadelQueryPath, flatten: Boolean): List { val rootNode = JsonNode(data) - return getNodesAt(rootNode, queryPath, flatten) + + return if (pathPrefix == null) { + getNodesAt(rootNode, queryPath, flatten) + } else if (queryPath.startsWith(pathPrefix.segments)) { + getNodesAt(rootNode, queryPath.removePrefix(pathPrefix.segments), flatten) + } else { + emptyList() + } } /** * Extracts the nodes at the given query selection path. */ private fun getNodesAt(rootNode: JsonNode, queryPath: NadelQueryPath, flatten: Boolean = false): List { + var queue = listOf(rootNode) // todo work backwards here instead of forwards diff --git a/lib/src/main/java/graphql/nadel/engine/util/GraphQLUtil.kt b/lib/src/main/java/graphql/nadel/engine/util/GraphQLUtil.kt index 5fa6f5742..a19d3290f 100644 --- a/lib/src/main/java/graphql/nadel/engine/util/GraphQLUtil.kt +++ b/lib/src/main/java/graphql/nadel/engine/util/GraphQLUtil.kt @@ -40,6 +40,7 @@ import graphql.language.Type import graphql.language.TypeName import graphql.language.UnionTypeExtensionDefinition import graphql.language.Value +import graphql.nadel.NadelIncrementalServiceExecutionResult import graphql.nadel.NadelOperationKind import graphql.nadel.NadelServiceExecutionResultImpl import graphql.nadel.ServiceExecutionResult @@ -67,6 +68,7 @@ import graphql.schema.GraphQLUnionType import graphql.schema.GraphQLUnmodifiedType import graphql.schema.idl.TypeUtil import kotlinx.coroutines.future.asDeferred +import org.reactivestreams.Publisher internal typealias AnyAstValue = Value<*> internal typealias AnyAstNode = Node<*> @@ -365,14 +367,6 @@ fun ExecutionIdProvider.provide(executionInput: ExecutionInput): ExecutionId { return provide(executionInput.query, executionInput.operationName, executionInput.context) } -fun ServiceExecutionResult.copy( - data: MutableJsonMap = this.data, - errors: MutableList = this.errors, - extensions: MutableJsonMap = this.extensions, -): ServiceExecutionResult { - return newServiceExecutionResult(data, errors, extensions) -} - fun newServiceExecutionResult( data: MutableJsonMap = mutableMapOf(), errors: MutableList = mutableListOf(), @@ -381,16 +375,6 @@ fun newServiceExecutionResult( return NadelServiceExecutionResultImpl(data, errors, extensions) } -fun newServiceExecutionResult( - error: GraphQLError, -): ServiceExecutionResult { - return newServiceExecutionResult( - errors = mutableListOf( - error.toSpecification(), - ), - ) -} - fun newExecutionResult( data: Any? = null, error: GraphQLError, diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/DeferredFieldIsRenamedTest.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/DeferredFieldIsRenamedTest.kt new file mode 100644 index 000000000..3c507a54c --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/DeferredFieldIsRenamedTest.kt @@ -0,0 +1,69 @@ +package graphql.nadel.tests.next.fixtures.defer.transforms + +import graphql.nadel.NadelExecutionHints +import graphql.nadel.tests.next.NadelIntegrationTest + +open class DeferredFieldIsRenamedTest : NadelIntegrationTest( + query = """ + query { + defer { + hello + ...@defer { + overallString + } + } + } + """.trimIndent(), + services = listOf( + Service( + name = "defer", + overallSchema = """ + directive @defer(if: Boolean, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT + + type Query { + defer: DeferApi + } + type DeferApi { + hello: String + overallString: String @renamed(from: "underlyingString") + } + + """.trimIndent(), + underlyingSchema = """ + directive @defer(if: Boolean, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT + + type Query { + defer: DeferApi + } + type DeferApi { + hello: String + underlyingString: String + } + + """.trimIndent(), + runtimeWiring = { wiring -> + wiring + .type("Query") { type -> + type + .dataFetcher("defer") { env -> + Any() + } + } + .type("DeferApi") { type -> + type + .dataFetcher("hello") { env -> + "hello there" + } + .dataFetcher("underlyingString") { env -> + "string for the deferred renamed field" + } + } + }, + ), + ), +) { + override fun makeExecutionHints(): NadelExecutionHints.Builder { + return super.makeExecutionHints() + .deferSupport { true } + } +} \ No newline at end of file diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/DeferredFieldIsRenamedTestSnapshot.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/DeferredFieldIsRenamedTestSnapshot.kt new file mode 100644 index 000000000..3f8fc8269 --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/DeferredFieldIsRenamedTestSnapshot.kt @@ -0,0 +1,109 @@ +// @formatter:off +package graphql.nadel.tests.next.fixtures.defer.transforms + +import graphql.nadel.tests.next.ExpectedNadelResult +import graphql.nadel.tests.next.ExpectedServiceCall +import graphql.nadel.tests.next.TestSnapshot +import graphql.nadel.tests.next.listOfJsonStrings +import kotlin.Suppress +import kotlin.collections.List +import kotlin.collections.listOf + +private suspend fun main() { + graphql.nadel.tests.next.update() +} + +/** + * This class is generated. Do NOT modify. + * + * Refer to [graphql.nadel.tests.next.UpdateTestSnapshots + */ +@Suppress("unused") +public class DeferredFieldIsRenamedTestSnapshot : TestSnapshot() { + override val calls: List = listOf( + ExpectedServiceCall( + service = "defer", + query = """ + | { + | defer { + | hello + | ... @defer { + | rename__overallString__underlyingString: underlyingString + | __typename__rename__overallString: __typename + | } + | } + | } + """.trimMargin(), + variables = "{}", + result = """ + | { + | "data": { + | "defer": { + | "hello": "hello there" + | } + | }, + | "hasNext": true + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + """ + | { + | "hasNext": false, + | "incremental": [ + | { + | "path": [ + | "defer" + | ], + | "data": { + | "overallString": "string for the deferred renamed field" + | } + | } + | ] + | } + """.trimMargin(), + ), + ), + ) + + /** + * ```json + * { + * "data": { + * "defer": { + * "hello": "hello there", + * "overallString": "string for the deferred renamed field" + * } + * } + * } + * ``` + */ + override val result: ExpectedNadelResult = ExpectedNadelResult( + result = """ + | { + | "data": { + | "defer": { + | "hello": "hello there" + | } + | }, + | "hasNext": true + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + """ + | { + | "hasNext": false, + | "incremental": [ + | { + | "path": [ + | "defer" + | ], + | "data": { + | "overallString": "string for the deferred renamed field" + | } + | } + | ] + | } + """.trimMargin(), + ), + ) +} diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/DeferredListFieldIsRenamedTest.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/DeferredListFieldIsRenamedTest.kt new file mode 100644 index 000000000..e715e2aea --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/DeferredListFieldIsRenamedTest.kt @@ -0,0 +1,102 @@ +package graphql.nadel.tests.next.fixtures.defer.transforms + +import graphql.nadel.NadelExecutionHints +import graphql.nadel.tests.next.NadelIntegrationTest + +open class DeferredListFieldIsRenamedTest : NadelIntegrationTest( + query = """ + query { + ...@defer { + issues { + key + assigneeId + awesomeIssueName + } + } + } + """.trimIndent(), + services = listOf( + Service( + name = "defer", + overallSchema = """ + directive @defer(if: Boolean, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT + + type Query { + issues: [Issue!] + } + + type Issue { + key: String! + assigneeId: ID! + awesomeIssueName: String @renamed(from: "title") + related: [Issue!] + parent: Issue + } + """.trimIndent(), + underlyingSchema = """ + directive @defer(if: Boolean, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT + + type Query { + issues: [Issue!] + } + + type Issue { + key: String! + assigneeId: ID! + title: String + related: [Issue!] + parent: Issue + } + """.trimIndent(), + runtimeWiring = { wiring -> + data class Issue( + val key: String, + val assigneeId: String, + val title: String? = null, + val parentKey: String? = null, + val relatedKeys: List = emptyList(), + ) + val issues = listOf( + Issue( + key = "GQLGW-1", + assigneeId = "ari:cloud:identity::user/1", + title = "Issue 1", + ), + Issue( + key = "GQLGW-2", + assigneeId = "ari:cloud:identity::user/2", + parentKey = "GQLGW-1", + relatedKeys = listOf("GQLGW-1"), + title = "Issue 2", + ), + Issue( + key = "GQLGW-3", + assigneeId = "ari:cloud:identity::user/1", + parentKey = "GQLGW-1", + relatedKeys = listOf("GQLGW-1", "GQLGW-2"), + title = "Issue 3", + ), + Issue( + key = "GQLGW-4", + assigneeId = "ari:cloud:identity::user/3", + parentKey = "GQLGW-1", + relatedKeys = listOf("GQLGW-1", "GQLGW-2", "GQLGW-3"), + // no title here to test rename on null result + ), + ) + wiring + .type("Query") { type -> + type + .dataFetcher("issues") { env -> + issues + } + } + }, + ), + ), +) { + override fun makeExecutionHints(): NadelExecutionHints.Builder { + return super.makeExecutionHints() + .deferSupport { true } + } +} \ No newline at end of file diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/DeferredListFieldIsRenamedTestSnapshot.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/DeferredListFieldIsRenamedTestSnapshot.kt new file mode 100644 index 000000000..d1da355ee --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/DeferredListFieldIsRenamedTestSnapshot.kt @@ -0,0 +1,160 @@ +// @formatter:off +package graphql.nadel.tests.next.fixtures.defer.transforms + +import graphql.nadel.tests.next.ExpectedNadelResult +import graphql.nadel.tests.next.ExpectedServiceCall +import graphql.nadel.tests.next.TestSnapshot +import graphql.nadel.tests.next.listOfJsonStrings +import kotlin.Suppress +import kotlin.collections.List +import kotlin.collections.listOf + +private suspend fun main() { + graphql.nadel.tests.next.update() +} + +/** + * This class is generated. Do NOT modify. + * + * Refer to [graphql.nadel.tests.next.UpdateTestSnapshots + */ +@Suppress("unused") +public class DeferredListFieldIsRenamedTestSnapshot : TestSnapshot() { + override val calls: List = listOf( + ExpectedServiceCall( + service = "defer", + query = """ + | { + | ... @defer { + | issues { + | key + | assigneeId + | rename__awesomeIssueName__title: title + | __typename__rename__awesomeIssueName: __typename + | } + | } + | } + """.trimMargin(), + variables = "{}", + result = """ + | { + | "data": {}, + | "hasNext": true + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + """ + | { + | "hasNext": false, + | "incremental": [ + | { + | "path": [], + | "data": { + | "issues": [ + | { + | "key": "GQLGW-1", + | "assigneeId": "ari:cloud:identity::user/1", + | "awesomeIssueName": "Issue 1" + | }, + | { + | "key": "GQLGW-2", + | "assigneeId": "ari:cloud:identity::user/2", + | "awesomeIssueName": "Issue 2" + | }, + | { + | "key": "GQLGW-3", + | "assigneeId": "ari:cloud:identity::user/1", + | "awesomeIssueName": "Issue 3" + | }, + | { + | "key": "GQLGW-4", + | "assigneeId": "ari:cloud:identity::user/3", + | "awesomeIssueName": null + | } + | ] + | } + | } + | ] + | } + """.trimMargin(), + ), + ), + ) + + /** + * ```json + * { + * "data": { + * "issues": [ + * { + * "key": "GQLGW-1", + * "assigneeId": "ari:cloud:identity::user/1", + * "awesomeIssueName": "Issue 1" + * }, + * { + * "key": "GQLGW-2", + * "assigneeId": "ari:cloud:identity::user/2", + * "awesomeIssueName": "Issue 2" + * }, + * { + * "key": "GQLGW-3", + * "assigneeId": "ari:cloud:identity::user/1", + * "awesomeIssueName": "Issue 3" + * }, + * { + * "key": "GQLGW-4", + * "assigneeId": "ari:cloud:identity::user/3", + * "awesomeIssueName": null + * } + * ] + * } + * } + * ``` + */ + override val result: ExpectedNadelResult = ExpectedNadelResult( + result = """ + | { + | "data": { + | "issues": null + | }, + | "hasNext": true + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + """ + | { + | "hasNext": false, + | "incremental": [ + | { + | "path": [], + | "data": { + | "issues": [ + | { + | "key": "GQLGW-1", + | "assigneeId": "ari:cloud:identity::user/1", + | "awesomeIssueName": "Issue 1" + | }, + | { + | "key": "GQLGW-2", + | "assigneeId": "ari:cloud:identity::user/2", + | "awesomeIssueName": "Issue 2" + | }, + | { + | "key": "GQLGW-3", + | "assigneeId": "ari:cloud:identity::user/1", + | "awesomeIssueName": "Issue 3" + | }, + | { + | "key": "GQLGW-4", + | "assigneeId": "ari:cloud:identity::user/3", + | "awesomeIssueName": null + | } + | ] + | } + | } + | ] + | } + """.trimMargin(), + ), + ) +} diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleRenameTransformsInsideAndOutsideDefer.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleRenameTransformsInsideAndOutsideDefer.kt new file mode 100644 index 000000000..2d4546b7b --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleRenameTransformsInsideAndOutsideDefer.kt @@ -0,0 +1,71 @@ +package graphql.nadel.tests.next.fixtures.defer.transforms + +import graphql.nadel.NadelExecutionHints +import graphql.nadel.tests.next.NadelIntegrationTest + +open class MultipleRenameTransformsInsideAndOutsideDefer : NadelIntegrationTest( + query = """ + query { + defer { + fastRenamedString + ...@defer { + slowRenamedString + } + } + } + """.trimIndent(), + services = listOf( + Service( + name = "defer", + overallSchema = """ + directive @defer(if: Boolean, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT + + type Query { + defer: DeferApi + } + type DeferApi { + hello: String + slowRenamedString: String @renamed(from: "slowString") + fastRenamedString: String @renamed(from: "fastString") + } + + """.trimIndent(), + underlyingSchema = """ + directive @defer(if: Boolean, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT + + type Query { + defer: DeferApi + } + type DeferApi { + hello: String + slowString: String + fastString: String + } + + """.trimIndent(), + runtimeWiring = { wiring -> + wiring + .type("Query") { type -> + type + .dataFetcher("defer") { env -> + Any() + } + } + .type("DeferApi") { type -> + type + .dataFetcher("slowString") { env -> + "this is the slow string (deferred)" + } + .dataFetcher("fastString") { env -> + "this is the fast string (not deferred)" + } + } + }, + ), + ), +) { + override fun makeExecutionHints(): NadelExecutionHints.Builder { + return super.makeExecutionHints() + .deferSupport { true } + } +} \ No newline at end of file diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleRenameTransformsInsideAndOutsideDeferSnapshot.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleRenameTransformsInsideAndOutsideDeferSnapshot.kt new file mode 100644 index 000000000..0367d8d9a --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleRenameTransformsInsideAndOutsideDeferSnapshot.kt @@ -0,0 +1,111 @@ +// @formatter:off +package graphql.nadel.tests.next.fixtures.defer.transforms + +import graphql.nadel.tests.next.ExpectedNadelResult +import graphql.nadel.tests.next.ExpectedServiceCall +import graphql.nadel.tests.next.TestSnapshot +import graphql.nadel.tests.next.listOfJsonStrings +import kotlin.Suppress +import kotlin.collections.List +import kotlin.collections.listOf + +private suspend fun main() { + graphql.nadel.tests.next.update() +} + +/** + * This class is generated. Do NOT modify. + * + * Refer to [graphql.nadel.tests.next.UpdateTestSnapshots + */ +@Suppress("unused") +public class MultipleRenameTransformsInsideAndOutsideDeferSnapshot : TestSnapshot() { + override val calls: List = listOf( + ExpectedServiceCall( + service = "defer", + query = """ + | { + | defer { + | rename__fastRenamedString__fastString: fastString + | __typename__rename__fastRenamedString: __typename + | ... @defer { + | rename__slowRenamedString__slowString: slowString + | __typename__rename__slowRenamedString: __typename + | } + | } + | } + """.trimMargin(), + variables = "{}", + result = """ + | { + | "data": { + | "defer": { + | "rename__fastRenamedString__fastString": "this is the fast string (not deferred)", + | "__typename__rename__fastRenamedString": "DeferApi" + | } + | }, + | "hasNext": true + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + """ + | { + | "hasNext": false, + | "incremental": [ + | { + | "path": [ + | "defer" + | ], + | "data": { + | "slowRenamedString": "this is the slow string (deferred)" + | } + | } + | ] + | } + """.trimMargin(), + ), + ), + ) + + /** + * ```json + * { + * "data": { + * "defer": { + * "fastRenamedString": "this is the fast string (not deferred)", + * "slowRenamedString": "this is the slow string (deferred)" + * } + * } + * } + * ``` + */ + override val result: ExpectedNadelResult = ExpectedNadelResult( + result = """ + | { + | "data": { + | "defer": { + | "fastRenamedString": "this is the fast string (not deferred)" + | } + | }, + | "hasNext": true + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + """ + | { + | "hasNext": false, + | "incremental": [ + | { + | "path": [ + | "defer" + | ], + | "data": { + | "slowRenamedString": "this is the slow string (deferred)" + | } + | } + | ] + | } + """.trimMargin(), + ), + ) +} diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleRenamedFieldsAreDeferredTogether.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleRenamedFieldsAreDeferredTogether.kt new file mode 100644 index 000000000..5f14a9b5e --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleRenamedFieldsAreDeferredTogether.kt @@ -0,0 +1,75 @@ +package graphql.nadel.tests.next.fixtures.defer.transforms + +import graphql.nadel.NadelExecutionHints +import graphql.nadel.tests.next.NadelIntegrationTest + +open class MultipleRenamedFieldsAreDeferredTogether : NadelIntegrationTest( + query = """ + query { + defer { + hello + ...@defer { + overallString + overallString2 + } + } + } + """.trimIndent(), + services = listOf( + Service( + name = "defer", + overallSchema = """ + directive @defer(if: Boolean, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT + + type Query { + defer: DeferApi + } + type DeferApi { + hello: String + overallString: String @renamed(from: "underlyingString") + overallString2: String @renamed(from: "underlyingString2") + } + + """.trimIndent(), + underlyingSchema = """ + directive @defer(if: Boolean, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT + + type Query { + defer: DeferApi + } + type DeferApi { + hello: String + underlyingString: String + underlyingString2: String + } + + """.trimIndent(), + runtimeWiring = { wiring -> + wiring + .type("Query") { type -> + type + .dataFetcher("defer") { env -> + Any() + } + } + .type("DeferApi") { type -> + type + .dataFetcher("hello") { env -> + "hello there" + } + .dataFetcher("underlyingString") { env -> + "deferred string 1" + } + .dataFetcher("underlyingString2") { env -> + "deferred string 2" + } + } + }, + ), + ), +) { + override fun makeExecutionHints(): NadelExecutionHints.Builder { + return super.makeExecutionHints() + .deferSupport { true } + } +} \ No newline at end of file diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleRenamedFieldsAreDeferredTogetherSnapshot.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleRenamedFieldsAreDeferredTogetherSnapshot.kt new file mode 100644 index 000000000..91e4c7451 --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleRenamedFieldsAreDeferredTogetherSnapshot.kt @@ -0,0 +1,114 @@ +// @formatter:off +package graphql.nadel.tests.next.fixtures.defer.transforms + +import graphql.nadel.tests.next.ExpectedNadelResult +import graphql.nadel.tests.next.ExpectedServiceCall +import graphql.nadel.tests.next.TestSnapshot +import graphql.nadel.tests.next.listOfJsonStrings +import kotlin.Suppress +import kotlin.collections.List +import kotlin.collections.listOf + +private suspend fun main() { + graphql.nadel.tests.next.update() +} + +/** + * This class is generated. Do NOT modify. + * + * Refer to [graphql.nadel.tests.next.UpdateTestSnapshots + */ +@Suppress("unused") +public class MultipleRenamedFieldsAreDeferredTogetherSnapshot : TestSnapshot() { + override val calls: List = listOf( + ExpectedServiceCall( + service = "defer", + query = """ + | { + | defer { + | hello + | ... @defer { + | rename__overallString__underlyingString: underlyingString + | __typename__rename__overallString: __typename + | rename__overallString2__underlyingString2: underlyingString2 + | __typename__rename__overallString2: __typename + | } + | } + | } + """.trimMargin(), + variables = "{}", + result = """ + | { + | "data": { + | "defer": { + | "hello": "hello there" + | } + | }, + | "hasNext": true + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + """ + | { + | "hasNext": false, + | "incremental": [ + | { + | "path": [ + | "defer" + | ], + | "data": { + | "overallString": "deferred string 1", + | "overallString2": "deferred string 2" + | } + | } + | ] + | } + """.trimMargin(), + ), + ), + ) + + /** + * ```json + * { + * "data": { + * "defer": { + * "hello": "hello there", + * "overallString": "deferred string 1", + * "overallString2": "deferred string 2" + * } + * } + * } + * ``` + */ + override val result: ExpectedNadelResult = ExpectedNadelResult( + result = """ + | { + | "data": { + | "defer": { + | "hello": "hello there" + | } + | }, + | "hasNext": true + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + """ + | { + | "hasNext": false, + | "incremental": [ + | { + | "path": [ + | "defer" + | ], + | "data": { + | "overallString": "deferred string 1", + | "overallString2": "deferred string 2" + | } + | } + | ] + | } + """.trimMargin(), + ), + ) +} diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleSeparateDefersWithTransforms.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleSeparateDefersWithTransforms.kt new file mode 100644 index 000000000..78d8f05b2 --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleSeparateDefersWithTransforms.kt @@ -0,0 +1,79 @@ +package graphql.nadel.tests.next.fixtures.defer.transforms + +import graphql.nadel.NadelExecutionHints +import graphql.nadel.tests.next.NadelIntegrationTest + +open class MultipleSeparateDefersWithTransforms : NadelIntegrationTest( + query = """ + query { + defer { + fastRenamedString + ...@defer { + slowRenamedString + } + ...@defer { + anotherSlowRenamedString + } + } + } + """.trimIndent(), + services = listOf( + Service( + name = "defer", + overallSchema = """ + directive @defer(if: Boolean, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT + + type Query { + defer: DeferApi + } + type DeferApi { + hello: String + slowRenamedString: String @renamed(from: "slowString") + anotherSlowRenamedString: String @renamed(from: "anotherSlowString") + fastRenamedString: String @renamed(from: "fastString") + } + + """.trimIndent(), + underlyingSchema = """ + directive @defer(if: Boolean, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT + + type Query { + defer: DeferApi + } + type DeferApi { + hello: String + slowString: String + anotherSlowString: String + fastString: String + } + + """.trimIndent(), + runtimeWiring = { wiring -> + wiring + .type("Query") { type -> + type + .dataFetcher("defer") { env -> + Any() + } + } + .type("DeferApi") { type -> + type + .dataFetcher("slowString") { env -> + "this is the slow string (deferred)" + } + .dataFetcher("anotherSlowString") { env -> + "this is the other slow string (deferred)" + } + .dataFetcher("fastString") { env -> + "this is the fast string (not deferred)" + } + } + }, + ), + ), +) { + override fun makeExecutionHints(): NadelExecutionHints.Builder { + return super.makeExecutionHints() + .deferSupport { true } + } +} \ No newline at end of file diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleSeparateDefersWithTransformsSnapshot.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleSeparateDefersWithTransformsSnapshot.kt new file mode 100644 index 000000000..de6be970b --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/MultipleSeparateDefersWithTransformsSnapshot.kt @@ -0,0 +1,146 @@ +// @formatter:off +package graphql.nadel.tests.next.fixtures.defer.transforms + +import graphql.nadel.tests.next.ExpectedNadelResult +import graphql.nadel.tests.next.ExpectedServiceCall +import graphql.nadel.tests.next.TestSnapshot +import graphql.nadel.tests.next.listOfJsonStrings +import kotlin.Suppress +import kotlin.collections.List +import kotlin.collections.listOf + +private suspend fun main() { + graphql.nadel.tests.next.update() +} + +/** + * This class is generated. Do NOT modify. + * + * Refer to [graphql.nadel.tests.next.UpdateTestSnapshots + */ +@Suppress("unused") +public class MultipleSeparateDefersWithTransformsSnapshot : TestSnapshot() { + override val calls: List = listOf( + ExpectedServiceCall( + service = "defer", + query = """ + | { + | defer { + | rename__fastRenamedString__fastString: fastString + | __typename__rename__fastRenamedString: __typename + | ... @defer { + | rename__slowRenamedString__slowString: slowString + | __typename__rename__slowRenamedString: __typename + | } + | ... @defer { + | rename__anotherSlowRenamedString__anotherSlowString: anotherSlowString + | __typename__rename__anotherSlowRenamedString: __typename + | } + | } + | } + """.trimMargin(), + variables = "{}", + result = """ + | { + | "data": { + | "defer": { + | "rename__fastRenamedString__fastString": "this is the fast string (not deferred)", + | "__typename__rename__fastRenamedString": "DeferApi" + | } + | }, + | "hasNext": true + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + """ + | { + | "hasNext": false, + | "incremental": [ + | { + | "path": [ + | "defer" + | ], + | "data": { + | "slowRenamedString": "this is the slow string (deferred)" + | } + | } + | ] + | } + """.trimMargin(), + """ + | { + | "hasNext": true, + | "incremental": [ + | { + | "path": [ + | "defer" + | ], + | "data": { + | "anotherSlowRenamedString": "this is the other slow string (deferred)" + | } + | } + | ] + | } + """.trimMargin(), + ), + ), + ) + + /** + * ```json + * { + * "data": { + * "defer": { + * "fastRenamedString": "this is the fast string (not deferred)", + * "anotherSlowRenamedString": "this is the other slow string (deferred)", + * "slowRenamedString": "this is the slow string (deferred)" + * } + * } + * } + * ``` + */ + override val result: ExpectedNadelResult = ExpectedNadelResult( + result = """ + | { + | "data": { + | "defer": { + | "fastRenamedString": "this is the fast string (not deferred)" + | } + | }, + | "hasNext": true + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + """ + | { + | "hasNext": false, + | "incremental": [ + | { + | "path": [ + | "defer" + | ], + | "data": { + | "slowRenamedString": "this is the slow string (deferred)" + | } + | } + | ] + | } + """.trimMargin(), + """ + | { + | "hasNext": true, + | "incremental": [ + | { + | "path": [ + | "defer" + | ], + | "data": { + | "anotherSlowRenamedString": "this is the other slow string (deferred)" + | } + | } + | ] + | } + """.trimMargin(), + ), + ) +} diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/QueryContainingDeferButNonDeferredFieldIsRenamedTest.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/QueryContainingDeferButNonDeferredFieldIsRenamedTest.kt new file mode 100644 index 000000000..7fcae6d7c --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/QueryContainingDeferButNonDeferredFieldIsRenamedTest.kt @@ -0,0 +1,69 @@ +package graphql.nadel.tests.next.fixtures.defer.transforms + +import graphql.nadel.NadelExecutionHints +import graphql.nadel.tests.next.NadelIntegrationTest + +open class QueryContainingDeferButNonDeferredFieldIsRenamedTest : NadelIntegrationTest( + query = """ + query { + defer { + overallString + ...@defer { + slow + } + } + } + """.trimIndent(), + services = listOf( + Service( + name = "defer", + overallSchema = """ + directive @defer(if: Boolean, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT + + type Query { + defer: DeferApi + } + type DeferApi { + overallString: String @renamed(from: "underlyingString") + slow: String + } + + """.trimIndent(), + underlyingSchema = """ + directive @defer(if: Boolean, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT + + type Query { + defer: DeferApi + } + type DeferApi { + underlyingString: String + slow: String + } + + """.trimIndent(), + runtimeWiring = { wiring -> + wiring + .type("Query") { type -> + type + .dataFetcher("defer") { env -> + Any() + } + } + .type("DeferApi") { type -> + type + .dataFetcher("underlyingString") { env -> + "this is the (non-deferred) renamed string" + } + .dataFetcher("slow") { env -> + "this is the deferred string" + } + } + }, + ), + ), +) { + override fun makeExecutionHints(): NadelExecutionHints.Builder { + return super.makeExecutionHints() + .deferSupport { true } + } +} \ No newline at end of file diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/QueryContainingDeferButNonDeferredFieldIsRenamedTestSnapshot.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/QueryContainingDeferButNonDeferredFieldIsRenamedTestSnapshot.kt new file mode 100644 index 000000000..973c588fa --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/QueryContainingDeferButNonDeferredFieldIsRenamedTestSnapshot.kt @@ -0,0 +1,110 @@ +// @formatter:off +package graphql.nadel.tests.next.fixtures.defer.transforms + +import graphql.nadel.tests.next.ExpectedNadelResult +import graphql.nadel.tests.next.ExpectedServiceCall +import graphql.nadel.tests.next.TestSnapshot +import graphql.nadel.tests.next.listOfJsonStrings +import kotlin.Suppress +import kotlin.collections.List +import kotlin.collections.listOf + +private suspend fun main() { + graphql.nadel.tests.next.update() +} + +/** + * This class is generated. Do NOT modify. + * + * Refer to [graphql.nadel.tests.next.UpdateTestSnapshots + */ +@Suppress("unused") +public class QueryContainingDeferButNonDeferredFieldIsRenamedTestSnapshot : TestSnapshot() { + override val calls: List = listOf( + ExpectedServiceCall( + service = "defer", + query = """ + | { + | defer { + | rename__overallString__underlyingString: underlyingString + | __typename__rename__overallString: __typename + | ... @defer { + | slow + | } + | } + | } + """.trimMargin(), + variables = "{}", + result = """ + | { + | "data": { + | "defer": { + | "rename__overallString__underlyingString": "this is the (non-deferred) renamed string", + | "__typename__rename__overallString": "DeferApi" + | } + | }, + | "hasNext": true + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + """ + | { + | "hasNext": false, + | "incremental": [ + | { + | "path": [ + | "defer" + | ], + | "data": { + | "slow": "this is the deferred string" + | } + | } + | ] + | } + """.trimMargin(), + ), + ), + ) + + /** + * ```json + * { + * "data": { + * "defer": { + * "overallString": "this is the (non-deferred) renamed string", + * "slow": "this is the deferred string" + * } + * } + * } + * ``` + */ + override val result: ExpectedNadelResult = ExpectedNadelResult( + result = """ + | { + | "data": { + | "defer": { + | "overallString": "this is the (non-deferred) renamed string" + | } + | }, + | "hasNext": true + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + """ + | { + | "hasNext": false, + | "incremental": [ + | { + | "path": [ + | "defer" + | ], + | "data": { + | "slow": "this is the deferred string" + | } + | } + | ] + | } + """.trimMargin(), + ), + ) +} diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/RenameInnerFieldInsideDefer.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/RenameInnerFieldInsideDefer.kt new file mode 100644 index 000000000..c78bc24ab --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/RenameInnerFieldInsideDefer.kt @@ -0,0 +1,79 @@ +package graphql.nadel.tests.next.fixtures.defer.transforms + +import graphql.nadel.NadelExecutionHints +import graphql.nadel.tests.next.NadelIntegrationTest + +open class RenameInnerFieldInsideDefer : NadelIntegrationTest( + query = """ + query { + defer { + ...@defer(if: true) { + user { + firstName + } + } + } + } + """.trimIndent(), + services = listOf( + Service( + name = "defer", + overallSchema = """ + directive @defer(if: Boolean, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT + + type Query { + defer: DeferApi + } + type DeferApi { + user: User + } + type User { + firstName: String @renamed(from: "name") + lastName: String + } + + """.trimIndent(), + underlyingSchema = """ + directive @defer(if: Boolean, label: String) on FRAGMENT_SPREAD | INLINE_FRAGMENT + + type Query { + defer: DeferApi + } + type DeferApi { + user: User + } + type User { + name: String + lastName: String + } + + """.trimIndent(), + runtimeWiring = { wiring -> + wiring + .type("Query") { type -> + type + .dataFetcher("defer") { env -> + Any() + } + } + .type("DeferApi") { type -> + type + .dataFetcher("user") { env -> + Any() + } + } + .type("User") { type -> + type + .dataFetcher("name") { env -> + "Steven" + } + } + }, + ), + ), +) { + override fun makeExecutionHints(): NadelExecutionHints.Builder { + return super.makeExecutionHints() + .deferSupport { true } + } +} \ No newline at end of file diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/RenameInnerFieldInsideDeferSnapshot.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/RenameInnerFieldInsideDeferSnapshot.kt new file mode 100644 index 000000000..99889e1ba --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/defer/transforms/RenameInnerFieldInsideDeferSnapshot.kt @@ -0,0 +1,111 @@ +// @formatter:off +package graphql.nadel.tests.next.fixtures.defer.transforms + +import graphql.nadel.tests.next.ExpectedNadelResult +import graphql.nadel.tests.next.ExpectedServiceCall +import graphql.nadel.tests.next.TestSnapshot +import graphql.nadel.tests.next.listOfJsonStrings +import kotlin.Suppress +import kotlin.collections.List +import kotlin.collections.listOf + +private suspend fun main() { + graphql.nadel.tests.next.update() +} + +/** + * This class is generated. Do NOT modify. + * + * Refer to [graphql.nadel.tests.next.UpdateTestSnapshots + */ +@Suppress("unused") +public class RenameInnerFieldInsideDeferSnapshot : TestSnapshot() { + override val calls: List = listOf( + ExpectedServiceCall( + service = "defer", + query = """ + | { + | defer { + | ... @defer { + | user { + | rename__firstName__name: name + | __typename__rename__firstName: __typename + | } + | } + | } + | } + """.trimMargin(), + variables = "{}", + result = """ + | { + | "data": { + | "defer": {} + | }, + | "hasNext": true + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + """ + | { + | "hasNext": false, + | "incremental": [ + | { + | "path": [ + | "defer" + | ], + | "data": { + | "user": { + | "firstName": "Steven" + | } + | } + | } + | ] + | } + """.trimMargin(), + ), + ), + ) + + /** + * ```json + * { + * "data": { + * "defer": { + * "user": { + * "firstName": "Steven" + * } + * } + * } + * } + * ``` + */ + override val result: ExpectedNadelResult = ExpectedNadelResult( + result = """ + | { + | "data": { + | "defer": {} + | }, + | "hasNext": true + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + """ + | { + | "hasNext": false, + | "incremental": [ + | { + | "path": [ + | "defer" + | ], + | "data": { + | "user": { + | "firstName": "Steven" + | } + | } + | } + | ] + | } + """.trimMargin(), + ), + ) +} diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/hydration/defer/HydrationDeferInRenamedField.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/hydration/defer/HydrationDeferInRenamedField.kt index 27a2335a0..97f95f60b 100644 --- a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/hydration/defer/HydrationDeferInRenamedField.kt +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/hydration/defer/HydrationDeferInRenamedField.kt @@ -20,6 +20,21 @@ class HydrationDeferInRenamedFieldTest : BaseHydrationDeferInRenamedFieldTest( """.trimIndent(), ) +class HydrationRenamedFieldInDeferTest : BaseHydrationDeferInRenamedFieldTest( + query = """ + query { + ... @defer { + issueByKey(key: "GQLGW-1") { # Renamed + key + assignee { + name + } + } + } + } + """.trimIndent(), +) + class HydrationDeferInRenamedFieldUsingRenamedFieldTest : BaseHydrationDeferInRenamedFieldTest( query = """ query { @@ -59,6 +74,27 @@ class HydrationDeferInRenamedFieldAndNestedHydrationsTest : BaseHydrationDeferIn """.trimIndent(), ) +class RenamedFieldInsideNestedHydrationsInsideDeferTest : BaseHydrationDeferInRenamedFieldTest( + query = """ + query { + issueByKey(key: "GQLGW-1") { # Renamed + key + ... @defer { + self { # Hydrate + self { # Hydrate + self { # Hydrate + assigneeV2 { # Renamed + name + } + } + } + } + } + } + } + """.trimIndent(), +) + abstract class BaseHydrationDeferInRenamedFieldTest( @Language("GraphQL") query: String, diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/hydration/defer/HydrationRenamedFieldInDeferTestSnapshot.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/hydration/defer/HydrationRenamedFieldInDeferTestSnapshot.kt new file mode 100644 index 000000000..a1c9b8b80 --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/hydration/defer/HydrationRenamedFieldInDeferTestSnapshot.kt @@ -0,0 +1,133 @@ +// @formatter:off +package graphql.nadel.tests.next.fixtures.hydration.defer + +import graphql.nadel.tests.next.ExpectedNadelResult +import graphql.nadel.tests.next.ExpectedServiceCall +import graphql.nadel.tests.next.TestSnapshot +import graphql.nadel.tests.next.listOfJsonStrings +import kotlin.Suppress +import kotlin.collections.List +import kotlin.collections.listOf + +private suspend fun main() { + graphql.nadel.tests.next.update() +} + +/** + * This class is generated. Do NOT modify. + * + * Refer to [graphql.nadel.tests.next.UpdateTestSnapshots + */ +@Suppress("unused") +public class HydrationRenamedFieldInDeferTestSnapshot : TestSnapshot() { + override val calls: List = listOf( + ExpectedServiceCall( + service = "issues", + query = """ + | { + | ... @defer { + | rename__issueByKey__getIssueByKey: getIssueByKey(key: "GQLGW-1") { + | key + | hydration__assignee__assigneeId: assigneeId + | __typename__hydration__assignee: __typename + | } + | } + | } + """.trimMargin(), + variables = "{}", + result = """ + | { + | "data": {}, + | "hasNext": true + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + """ + | { + | "hasNext": false, + | "incremental": [ + | { + | "path": [], + | "data": { + | "issueByKey": { + | "key": "GQLGW-1", + | "assignee": { + | "name": "Franklin" + | } + | } + | } + | } + | ] + | } + """.trimMargin(), + ), + ), + ExpectedServiceCall( + service = "users", + query = """ + | { + | userById(id: "ari:cloud:identity::user/1") { + | name + | } + | } + """.trimMargin(), + variables = "{}", + result = """ + | { + | "data": { + | "userById": { + | "name": "Franklin" + | } + | } + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + ), + ), + ) + + /** + * ```json + * { + * "data": { + * "issueByKey": { + * "key": "GQLGW-1", + * "assignee": { + * "name": "Franklin" + * } + * } + * } + * } + * ``` + */ + override val result: ExpectedNadelResult = ExpectedNadelResult( + result = """ + | { + | "data": { + | "issueByKey": null + | }, + | "hasNext": true + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + """ + | { + | "hasNext": false, + | "incremental": [ + | { + | "path": [], + | "data": { + | "issueByKey": { + | "key": "GQLGW-1", + | "assignee": { + | "name": "Franklin" + | } + | } + | } + | } + | ] + | } + """.trimMargin(), + ), + ) +} diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/hydration/defer/RenamedFieldInsideNestedHydrationsInsideDeferTestSnapshot.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/hydration/defer/RenamedFieldInsideNestedHydrationsInsideDeferTestSnapshot.kt new file mode 100644 index 000000000..731073a09 --- /dev/null +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/hydration/defer/RenamedFieldInsideNestedHydrationsInsideDeferTestSnapshot.kt @@ -0,0 +1,203 @@ +// @formatter:off +package graphql.nadel.tests.next.fixtures.hydration.defer + +import graphql.nadel.tests.next.ExpectedNadelResult +import graphql.nadel.tests.next.ExpectedServiceCall +import graphql.nadel.tests.next.TestSnapshot +import graphql.nadel.tests.next.listOfJsonStrings +import kotlin.Suppress +import kotlin.collections.List +import kotlin.collections.listOf + +private suspend fun main() { + graphql.nadel.tests.next.update() +} + +/** + * This class is generated. Do NOT modify. + * + * Refer to [graphql.nadel.tests.next.UpdateTestSnapshots + */ +@Suppress("unused") +public class RenamedFieldInsideNestedHydrationsInsideDeferTestSnapshot : TestSnapshot() { + override val calls: List = listOf( + ExpectedServiceCall( + service = "issues", + query = """ + | { + | rename__issueById__getIssueById: getIssueById(id: "1") { + | hydration__assigneeV2__assigneeId: assigneeId + | __typename__hydration__assigneeV2: __typename + | } + | } + """.trimMargin(), + variables = "{}", + result = """ + | { + | "data": { + | "rename__issueById__getIssueById": { + | "hydration__assigneeV2__assigneeId": "ari:cloud:identity::user/1", + | "__typename__hydration__assigneeV2": "Issue" + | } + | } + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + ), + ), + ExpectedServiceCall( + service = "issues", + query = """ + | { + | rename__issueById__getIssueById: getIssueById(id: "1") { + | hydration__self__id: id + | __typename__hydration__self: __typename + | } + | } + """.trimMargin(), + variables = "{}", + result = """ + | { + | "data": { + | "rename__issueById__getIssueById": { + | "hydration__self__id": "1", + | "__typename__hydration__self": "Issue" + | } + | } + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + ), + ), + ExpectedServiceCall( + service = "issues", + query = """ + | { + | rename__issueById__getIssueById: getIssueById(id: "1") { + | hydration__self__id: id + | __typename__hydration__self: __typename + | } + | } + """.trimMargin(), + variables = "{}", + result = """ + | { + | "data": { + | "rename__issueById__getIssueById": { + | "hydration__self__id": "1", + | "__typename__hydration__self": "Issue" + | } + | } + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + ), + ), + ExpectedServiceCall( + service = "issues", + query = """ + | { + | rename__issueByKey__getIssueByKey: getIssueByKey(key: "GQLGW-1") { + | key + | hydration__self__id: id + | __typename__hydration__self: __typename + | } + | } + """.trimMargin(), + variables = "{}", + result = """ + | { + | "data": { + | "rename__issueByKey__getIssueByKey": { + | "key": "GQLGW-1", + | "hydration__self__id": "1", + | "__typename__hydration__self": "Issue" + | } + | } + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + ), + ), + ExpectedServiceCall( + service = "users", + query = """ + | { + | rename__quickUser__user_fast: user_fast(id: "ari:cloud:identity::user/1") { + | name + | } + | } + """.trimMargin(), + variables = "{}", + result = """ + | { + | "data": { + | "rename__quickUser__user_fast": { + | "name": "SPEED" + | } + | } + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + ), + ), + ) + + /** + * ```json + * { + * "data": { + * "issueByKey": { + * "key": "GQLGW-1", + * "self": { + * "self": { + * "self": { + * "assigneeV2": { + * "name": "SPEED" + * } + * } + * } + * } + * } + * } + * } + * ``` + */ + override val result: ExpectedNadelResult = ExpectedNadelResult( + result = """ + | { + | "data": { + | "issueByKey": { + | "key": "GQLGW-1" + | } + | }, + | "hasNext": true + | } + """.trimMargin(), + delayedResults = listOfJsonStrings( + """ + | { + | "hasNext": false, + | "incremental": [ + | { + | "path": [ + | "issueByKey" + | ], + | "data": { + | "self": { + | "self": { + | "self": { + | "assigneeV2": { + | "name": "SPEED" + | } + | } + | } + | } + | } + | } + | ] + | } + """.trimMargin(), + ), + ) +}