From 49ecee51556c8ab861f712df02c07d5bd772e87c Mon Sep 17 00:00:00 2001 From: Steven Barker Date: Mon, 21 Aug 2023 11:40:21 +1200 Subject: [PATCH 01/15] add static args test --- .../basic-hydration-with-static-args.yml | 105 ++++++++++++++++++ .../fixtures/hydration/basic-hydration.yml | 6 - 2 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 test/src/test/resources/fixtures/hydration/basic-hydration-with-static-args.yml diff --git a/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-args.yml b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-args.yml new file mode 100644 index 000000000..ae1b16204 --- /dev/null +++ b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-args.yml @@ -0,0 +1,105 @@ +name: "basic hydration with static args" +enabled: true +overallSchema: + service2: | + type Query { + barById(id: ID, private: Boolean): Bar + } + type Bar { + id: ID + name: String + private: Boolean + } + service1: | + type Query { + foo: Foo + } + type Foo { + id: ID + bar: Bar @hydrated(service: "service2" field: "barById" arguments: [{ + name: "id" value: "$source.barId" + name: "private" value: true + }]) + } + +underlyingSchema: + service2: | + type Bar { + id: ID + name: String + } + + type Query { + barById(id: ID): Bar + } + service1: | + type Foo { + barId: ID + id: ID + } + + type Query { + foo: Foo + } +query: | + query { + foo { + bar { + name + } + } + } +variables: { } +serviceCalls: + - serviceName: "service1" + request: + query: | + query { + foo { + __typename__hydration__bar: __typename + hydration__bar__barId: barId + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "foo": { + "hydration__bar__barId": "barId", + "__typename__hydration__bar": "Foo" + } + }, + "extensions": {} + } + - serviceName: "service2" + request: + query: | + query { + barById(id: "barId") { + name + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "barById": { + "name": "Bar1" + } + }, + "extensions": {} + } +# language=JSON +response: |- + { + "data": { + "foo": { + "bar": { + "name": "Bar1" + } + } + }, + "extensions": {} + } diff --git a/test/src/test/resources/fixtures/hydration/basic-hydration.yml b/test/src/test/resources/fixtures/hydration/basic-hydration.yml index b19efd2ff..87ab4558a 100644 --- a/test/src/test/resources/fixtures/hydration/basic-hydration.yml +++ b/test/src/test/resources/fixtures/hydration/basic-hydration.yml @@ -16,7 +16,6 @@ overallSchema: type Foo { id: ID bar: Bar @hydrated(service: "service2" field: "barById" arguments: [{name: "id" value: "$source.barId"}]) - barLongerInput: Bar @hydrated(service: "service2" field: "barById" arguments: [{name: "id" value: "$source.fooDetails.externalBarId"}]) } underlyingSchema: service2: | @@ -31,14 +30,9 @@ underlyingSchema: service1: | type Foo { barId: ID - fooDetails: FooDetails id: ID } - type FooDetails { - externalBarId: ID - } - type Query { foo: Foo } From ae478f45103b4ee2c999f4bf183e944c9de35fd5 Mon Sep 17 00:00:00 2001 From: Steven Barker Date: Tue, 29 Aug 2023 14:59:33 +1200 Subject: [PATCH 02/15] update static args test --- .../basic-hydration-with-static-args.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-args.yml b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-args.yml index ae1b16204..8386e0f4c 100644 --- a/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-args.yml +++ b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-args.yml @@ -3,12 +3,11 @@ enabled: true overallSchema: service2: | type Query { - barById(id: ID, private: Boolean): Bar + barById(id: ID): Bar } type Bar { id: ID name: String - private: Boolean } service1: | type Query { @@ -16,12 +15,9 @@ overallSchema: } type Foo { id: ID - bar: Bar @hydrated(service: "service2" field: "barById" arguments: [{ - name: "id" value: "$source.barId" - name: "private" value: true - }]) + bar: Bar @hydrated(service: "service2" field: "barById" arguments: [{name: "id" value: "barId"}]) + barLongerInput: Bar @hydrated(service: "service2" field: "barById" arguments: [{name: "id" value: "$source.fooDetails.externalBarId"}]) } - underlyingSchema: service2: | type Bar { @@ -35,9 +31,14 @@ underlyingSchema: service1: | type Foo { barId: ID + fooDetails: FooDetails id: ID } + type FooDetails { + externalBarId: ID + } + type Query { foo: Foo } @@ -102,4 +103,4 @@ response: |- } }, "extensions": {} - } + } \ No newline at end of file From 3bd713d6d12c9e2bfd49f47f689f86870a7c620f Mon Sep 17 00:00:00 2001 From: Steven Barker Date: Wed, 6 Sep 2023 14:23:17 +1200 Subject: [PATCH 03/15] static args work --- .../graphql/nadel/dsl/RemoteArgumentSource.kt | 6 ++- .../NadelExecutionBlueprintFactory.kt | 10 +++- .../hydration/NadelHydrationActorInputDef.kt | 22 +++++++- .../hydration/NadelHydrationInputBuilder.kt | 12 +++++ .../batch/NadelBatchHydrationInputBuilder.kt | 16 +++++- .../graphql/nadel/schema/NadelDirectives.kt | 53 +++++++++++++------ 6 files changed, 98 insertions(+), 21 deletions(-) diff --git a/lib/src/main/java/graphql/nadel/dsl/RemoteArgumentSource.kt b/lib/src/main/java/graphql/nadel/dsl/RemoteArgumentSource.kt index bf62333ac..60d54b73c 100644 --- a/lib/src/main/java/graphql/nadel/dsl/RemoteArgumentSource.kt +++ b/lib/src/main/java/graphql/nadel/dsl/RemoteArgumentSource.kt @@ -1,13 +1,17 @@ package graphql.nadel.dsl +import graphql.language.Value + // todo this should be a union or sealed class thing data class RemoteArgumentSource( val argumentName: String?, // for OBJECT_FIELD val pathToField: List?, - val sourceType: SourceType?, + val staticValue: Value<*>?, + val sourceType: SourceType, ) { enum class SourceType { ObjectField, FieldArgument, + StaticArgument } } 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 512415f82..51513b29d 100644 --- a/lib/src/main/java/graphql/nadel/engine/blueprint/NadelExecutionBlueprintFactory.kt +++ b/lib/src/main/java/graphql/nadel/engine/blueprint/NadelExecutionBlueprintFactory.kt @@ -8,10 +8,12 @@ import graphql.Scalars.GraphQLString import graphql.language.EnumTypeDefinition import graphql.language.FieldDefinition import graphql.language.ImplementingTypeDefinition +import graphql.language.StringValue import graphql.nadel.Service import graphql.nadel.dsl.FieldMappingDefinition import graphql.nadel.dsl.RemoteArgumentSource.SourceType.FieldArgument import graphql.nadel.dsl.RemoteArgumentSource.SourceType.ObjectField +import graphql.nadel.dsl.RemoteArgumentSource.SourceType.StaticArgument import graphql.nadel.dsl.TypeMappingDefinition import graphql.nadel.dsl.UnderlyingServiceHydration import graphql.nadel.engine.blueprint.hydration.NadelBatchHydrationMatchStrategy @@ -260,6 +262,7 @@ private class Factory( when (it.valueSource) { is NadelHydrationActorInputDef.ValueSource.ArgumentValue -> null is FieldResultValue -> it.valueSource.queryPathToField + is NadelHydrationActorInputDef.ValueSource.StaticValue -> null //check this (but should be right as only need fields for FieldResultValue ($source) } }, ) @@ -358,6 +361,7 @@ private class Factory( when (val hydrationValueSource: NadelHydrationActorInputDef.ValueSource = it.valueSource) { is NadelHydrationActorInputDef.ValueSource.ArgumentValue -> emptyList() is FieldResultValue -> selectSourceFieldQueryPaths(hydrationValueSource) + is NadelHydrationActorInputDef.ValueSource.StaticValue -> emptyList() //check this (but should be right as only need fields for FieldResultValue ($source) } }).toSet() @@ -483,7 +487,11 @@ private class Factory( ?: error("No field defined at: ${hydratedFieldParentType.name}.${pathToField.joinToString(".")}"), ) } - else -> error("Unsupported remote argument source type: '$argSourceType'") + StaticArgument -> { + NadelHydrationActorInputDef.ValueSource.StaticValue( + value = remoteArgDef.remoteArgumentSource.staticValue!! + ) + } } NadelHydrationActorInputDef( diff --git a/lib/src/main/java/graphql/nadel/engine/blueprint/hydration/NadelHydrationActorInputDef.kt b/lib/src/main/java/graphql/nadel/engine/blueprint/hydration/NadelHydrationActorInputDef.kt index e803ff340..70a086e51 100644 --- a/lib/src/main/java/graphql/nadel/engine/blueprint/hydration/NadelHydrationActorInputDef.kt +++ b/lib/src/main/java/graphql/nadel/engine/blueprint/hydration/NadelHydrationActorInputDef.kt @@ -1,5 +1,6 @@ package graphql.nadel.engine.blueprint.hydration +import graphql.language.Value import graphql.nadel.engine.transform.query.NadelQueryPath import graphql.normalized.NormalizedInputValue import graphql.schema.GraphQLArgument @@ -23,7 +24,7 @@ data class NadelHydrationActorInputDef( * } * ``` */ - data class FieldResultValue( + data class FieldResultValue( // $source val queryPathToField: NadelQueryPath, val fieldDefinition: GraphQLFieldDefinition, ) : ValueSource() @@ -39,11 +40,28 @@ data class NadelHydrationActorInputDef( * } * ``` */ - data class ArgumentValue( + data class ArgumentValue( // $args val argumentName: String, val argumentDefinition: GraphQLArgument, val defaultValue: NormalizedInputValue?, ) : ValueSource() + + /** + * Represents a static argument value, which is hardcoded in the source code. e.g. + * + * ```graphql + * type Issue { + * id: ID! # Value used as argument to issueId + * owner: User @hydrated(from: ["issueOwner"], args: [ + * {name: "issueId" value: "issue123"} + * ]) + * } + * ``` + */ + data class StaticValue ( + val value: Value<*>, + ): ValueSource() + } } diff --git a/lib/src/main/java/graphql/nadel/engine/transform/hydration/NadelHydrationInputBuilder.kt b/lib/src/main/java/graphql/nadel/engine/transform/hydration/NadelHydrationInputBuilder.kt index cac95e5c8..ee386fb52 100644 --- a/lib/src/main/java/graphql/nadel/engine/transform/hydration/NadelHydrationInputBuilder.kt +++ b/lib/src/main/java/graphql/nadel/engine/transform/hydration/NadelHydrationInputBuilder.kt @@ -1,6 +1,7 @@ package graphql.nadel.engine.transform.hydration import graphql.language.NullValue +import graphql.language.Value import graphql.nadel.engine.blueprint.NadelHydrationFieldInstruction import graphql.nadel.engine.blueprint.hydration.NadelHydrationActorInputDef import graphql.nadel.engine.blueprint.hydration.NadelHydrationActorInputDef.ValueSource @@ -127,6 +128,7 @@ internal class NadelHydrationInputBuilder private constructor( inputDef, value = getResultValue(valueSource), ) + is ValueSource.StaticValue -> makeInputValue(inputDef, valueSource as Value<*>) } } @@ -140,6 +142,16 @@ internal class NadelHydrationInputBuilder private constructor( ) } + private fun makeInputValue( + inputDef: NadelHydrationActorInputDef, + value: Value<*>, + ): NormalizedInputValue { + return makeNormalizedInputValue( + type = inputDef.actorArgumentDef.type, + value = value, + ) + } + private fun getArgumentValue( valueSource: ValueSource.ArgumentValue, ): NormalizedInputValue? { diff --git a/lib/src/main/java/graphql/nadel/engine/transform/hydration/batch/NadelBatchHydrationInputBuilder.kt b/lib/src/main/java/graphql/nadel/engine/transform/hydration/batch/NadelBatchHydrationInputBuilder.kt index e6c51ed9b..5abdd3925 100644 --- a/lib/src/main/java/graphql/nadel/engine/transform/hydration/batch/NadelBatchHydrationInputBuilder.kt +++ b/lib/src/main/java/graphql/nadel/engine/transform/hydration/batch/NadelBatchHydrationInputBuilder.kt @@ -10,6 +10,7 @@ import graphql.nadel.engine.transform.result.json.JsonNodeExtractor import graphql.nadel.engine.util.emptyOrSingle import graphql.nadel.engine.util.flatten import graphql.nadel.engine.util.javaValueToAstValue +import graphql.nadel.engine.util.makeNormalizedInputValue import graphql.nadel.engine.util.mapFrom import graphql.nadel.hooks.ServiceExecutionHooks import graphql.normalized.ExecutableNormalizedField @@ -44,7 +45,7 @@ internal object NadelBatchHydrationInputBuilder { instruction.actorInputValueDefs.mapNotNull { actorFieldArg -> when (val valueSource = actorFieldArg.valueSource) { is NadelHydrationActorInputDef.ValueSource.ArgumentValue -> { - val argValue = hydrationField.normalizedArguments[valueSource.argumentName] + val argValue: NormalizedInputValue? = hydrationField.normalizedArguments[valueSource.argumentName] ?: valueSource.defaultValue if (argValue != null) { actorFieldArg to argValue @@ -54,8 +55,19 @@ internal object NadelBatchHydrationInputBuilder { } // These are batch values, ignore them is NadelHydrationActorInputDef.ValueSource.FieldResultValue -> null + is NadelHydrationActorInputDef.ValueSource.StaticValue -> { + val staticValue: NormalizedInputValue? = makeNormalizedInputValue( + type = actorFieldArg.actorArgumentDef.type, + value = valueSource.value, + ) + if (staticValue != null) { + actorFieldArg to staticValue + } else { + null + } + } } - }, + } ) } diff --git a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt index ded3f939d..7b6a6aabf 100644 --- a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt +++ b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt @@ -11,6 +11,7 @@ import graphql.language.EnumTypeDefinition.newEnumTypeDefinition import graphql.language.EnumValueDefinition.newEnumValueDefinition import graphql.language.InputObjectTypeDefinition.newInputObjectDefinition import graphql.language.StringValue +import graphql.language.Value import graphql.nadel.dsl.FieldMappingDefinition import graphql.nadel.dsl.RemoteArgumentDefinition import graphql.nadel.dsl.RemoteArgumentSource @@ -38,6 +39,7 @@ import graphql.schema.GraphQLEnumType import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLSchema import java.util.Locale +import kotlin.reflect.typeOf /** * If you update this file please add to NadelBuiltInTypes @@ -343,7 +345,7 @@ object NadelDirectives { val argMap = arg as Map val remoteArgName = argMap.requireArgument("name") val remoteArgValue = argMap.requireArgument("value") - val remoteArgumentSource = createRemoteArgumentSource(remoteArgValue) + val remoteArgumentSource = createRemoteArgumentSource(StringValue(remoteArgValue)) RemoteArgumentDefinition(remoteArgName, remoteArgumentSource) } } @@ -363,21 +365,40 @@ object NadelDirectives { } } - private fun createRemoteArgumentSource(value: String): RemoteArgumentSource { - val values = listFromDottedString(value) - - return when (values.first()) { - "\$source" -> RemoteArgumentSource( + private fun createRemoteArgumentSource(value: Value<*>): RemoteArgumentSource { //needs to be graphql.language "Value" + if (value is StringValue) { + val values = listFromDottedString(value.toString()) + return when (values.first()) { + "\$source" -> RemoteArgumentSource( + argumentName = null, + pathToField = values.subList(1, values.size), + staticValue = null, + sourceType = SourceType.ObjectField, + ) + + "\$argument" -> RemoteArgumentSource( + argumentName = values.subList(1, values.size).single(), + pathToField = null, + staticValue = null, + sourceType = SourceType.FieldArgument, + ) + + else -> + RemoteArgumentSource( + argumentName = null, + pathToField = null, + staticValue = value, + sourceType = SourceType.StaticArgument, + ) + } + } + else { + return RemoteArgumentSource( argumentName = null, - pathToField = values.subList(1, values.size), - sourceType = SourceType.ObjectField, - ) - "\$argument" -> RemoteArgumentSource( - argumentName = values.subList(1, values.size).single(), pathToField = null, - sourceType = SourceType.FieldArgument, - ) - else -> throw IllegalArgumentException("$value must begin with \$source. or \$argument.") + staticValue = value, + sourceType = SourceType.StaticArgument, + ) } } @@ -420,12 +441,14 @@ object NadelDirectives { var argumentName: String? = null var path: List? = null + var value: Value<*>? = null when (argumentType) { SourceType.ObjectField -> path = values SourceType.FieldArgument -> argumentName = values.single() + SourceType.StaticArgument -> value = value } - return RemoteArgumentSource(argumentName, path, argumentType) + return RemoteArgumentSource(argumentName, path, value, argumentType) } fun createFieldMapping(fieldDefinition: GraphQLFieldDefinition): FieldMappingDefinition? { From 92c70416219d44988785f5eb94d75769c2df96af Mon Sep 17 00:00:00 2001 From: Steven Barker Date: Thu, 7 Sep 2023 16:07:31 +1200 Subject: [PATCH 04/15] change value --- lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt index 7b6a6aabf..547416103 100644 --- a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt +++ b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt @@ -367,7 +367,7 @@ object NadelDirectives { private fun createRemoteArgumentSource(value: Value<*>): RemoteArgumentSource { //needs to be graphql.language "Value" if (value is StringValue) { - val values = listFromDottedString(value.toString()) + val values = listFromDottedString(value.value) return when (values.first()) { "\$source" -> RemoteArgumentSource( argumentName = null, From ff205f8198d29c33aaf8205d91cb47c33a87acb4 Mon Sep 17 00:00:00 2001 From: Steven Barker Date: Wed, 13 Sep 2023 13:33:42 +1200 Subject: [PATCH 05/15] add static args logic --- lib/build.gradle.kts | 3 + .../hydration/NadelHydrationInputBuilder.kt | 4 +- .../graphql/nadel/schema/NadelDirectives.kt | 4 +- .../validation/NadelHydrationValidation.kt | 14 ++- .../nadel/tests/hooks/HydrationDetailsHook.kt | 8 ++ ...asic-hydration-with-static-arg-integer.yml | 98 +++++++++++++++++++ ...basic-hydration-with-static-arg-number.yml | 98 +++++++++++++++++++ .../basic-hydration-with-static-args.yml | 8 -- 8 files changed, 225 insertions(+), 12 deletions(-) create mode 100644 test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-integer.yml create mode 100644 test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-number.yml diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts index 9be8f3bf8..0969c5738 100644 --- a/lib/build.gradle.kts +++ b/lib/build.gradle.kts @@ -13,6 +13,9 @@ val slf4jVersion = "1.7.25" dependencies { api("com.graphql-java:graphql-java:$graphqlJavaVersion") implementation("org.slf4j:slf4j-api:$slf4jVersion") + implementation("com.graphql-java:graphql-java-extended-scalars:21.0") { + exclude("com.graphql-java", "graphql-java") + } api(kotlin("stdlib")) api(kotlin("reflect")) diff --git a/lib/src/main/java/graphql/nadel/engine/transform/hydration/NadelHydrationInputBuilder.kt b/lib/src/main/java/graphql/nadel/engine/transform/hydration/NadelHydrationInputBuilder.kt index ee386fb52..1fbb8a4d2 100644 --- a/lib/src/main/java/graphql/nadel/engine/transform/hydration/NadelHydrationInputBuilder.kt +++ b/lib/src/main/java/graphql/nadel/engine/transform/hydration/NadelHydrationInputBuilder.kt @@ -1,6 +1,7 @@ package graphql.nadel.engine.transform.hydration import graphql.language.NullValue +import graphql.language.StringValue import graphql.language.Value import graphql.nadel.engine.blueprint.NadelHydrationFieldInstruction import graphql.nadel.engine.blueprint.hydration.NadelHydrationActorInputDef @@ -9,6 +10,7 @@ import graphql.nadel.engine.blueprint.hydration.NadelHydrationStrategy import graphql.nadel.engine.transform.artificial.NadelAliasHelper import graphql.nadel.engine.transform.result.json.JsonNode import graphql.nadel.engine.transform.result.json.JsonNodeExtractor +import graphql.nadel.engine.util.AnyAstValue import graphql.nadel.engine.util.emptyOrSingle import graphql.nadel.engine.util.flatten import graphql.nadel.engine.util.javaValueToAstValue @@ -128,7 +130,7 @@ internal class NadelHydrationInputBuilder private constructor( inputDef, value = getResultValue(valueSource), ) - is ValueSource.StaticValue -> makeInputValue(inputDef, valueSource as Value<*>) + is ValueSource.StaticValue -> makeInputValue(inputDef, valueSource.value) } } diff --git a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt index 547416103..602487025 100644 --- a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt +++ b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt @@ -1,5 +1,6 @@ package graphql.nadel.schema +import graphql.scalars.ExtendedScalars import graphql.GraphQLContext import graphql.Scalars import graphql.Scalars.GraphQLString @@ -32,6 +33,7 @@ import graphql.nadel.util.onInterface import graphql.nadel.util.onObject import graphql.nadel.util.onScalar import graphql.nadel.util.onUnion +import graphql.scalars.`object`.JsonScalar import graphql.schema.GraphQLAppliedDirective import graphql.schema.GraphQLAppliedDirectiveArgument import graphql.schema.GraphQLDirectiveContainer @@ -74,7 +76,7 @@ object NadelDirectives { ) .inputValueDefinition( name = "value", - type = nonNull(GraphQLString), + type = nonNull(ExtendedScalars.Json), ) .build() diff --git a/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt index 5b4564064..efbdf1175 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt @@ -25,9 +25,11 @@ import graphql.nadel.validation.util.NadelSchemaUtil.getHydrations import graphql.nadel.validation.util.NadelSchemaUtil.hasRename import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLFieldsContainer +import graphql.schema.GraphQLInputType import graphql.schema.GraphQLInterfaceType import graphql.schema.GraphQLNamedOutputType import graphql.schema.GraphQLSchema +import graphql.schema.GraphQLType import graphql.schema.GraphQLUnionType internal class NadelHydrationValidation( @@ -215,7 +217,9 @@ internal class NadelHydrationValidation( MissingHydrationFieldValueSource(parent, overallField, remoteArgSource) } else { // TODO: check argument type is correct - null + val fieldOutputType = field.type + hydrationArgTypesMatch(fieldOutputType) + } } @@ -225,7 +229,8 @@ internal class NadelHydrationValidation( MissingHydrationArgumentValueSource(parent, overallField, remoteArgSource) } else { // TODO: check argument type is correct - null + val hydrationArgType = argument.type + hydrationArgTypesMatch(hydrationArgType) } } @@ -234,4 +239,9 @@ internal class NadelHydrationValidation( } } } + + private fun hydrationArgTypesMatch(type: GraphQLType): NadelSchemaValidationError? { //getHydrationArgumentErrors + // if type matches, return null, otherwise return error + return null + } } diff --git a/test/src/test/kotlin/graphql/nadel/tests/hooks/HydrationDetailsHook.kt b/test/src/test/kotlin/graphql/nadel/tests/hooks/HydrationDetailsHook.kt index dec09bd51..c898cc998 100644 --- a/test/src/test/kotlin/graphql/nadel/tests/hooks/HydrationDetailsHook.kt +++ b/test/src/test/kotlin/graphql/nadel/tests/hooks/HydrationDetailsHook.kt @@ -29,6 +29,14 @@ class `basic-hydration` : HydrationDetailsHook() { } } @UseHook +class `basic-hydration-with-static-args` : HydrationDetailsHook() { + override fun assertHydrationDetails(actualHydrationDetails: ServiceExecutionHydrationDetails) { + assert(actualHydrationDetails.hydrationActorField.toString() == "Query.barById") + assert(actualHydrationDetails.hydrationSourceField.toString() == "Foo.bar") + assert(actualHydrationDetails.hydrationSourceService.name == "service1") + } +} +@UseHook class `batch-hydration-with-renamed-actor-field` : HydrationDetailsHook() { override fun assertHydrationDetails(actualHydrationDetails: ServiceExecutionHydrationDetails) { assert(actualHydrationDetails.hydrationActorField.toString() == "Query.barsByIdOverall") diff --git a/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-integer.yml b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-integer.yml new file mode 100644 index 000000000..da6fa7415 --- /dev/null +++ b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-integer.yml @@ -0,0 +1,98 @@ +name: "basic hydration with static args" +enabled: true +overallSchema: + service2: | + type Query { + barById(id: ID): Bar + } + type Bar { + id: ID + name: String + } + service1: | + type Query { + foo: Foo + } + type Foo { + id: ID + bar: Bar @hydrated(service: "service2" field: "barById" arguments: [{name: "id" value: "barId"}]) + } +underlyingSchema: + service2: | + type Bar { + id: ID + name: String + } + + type Query { + barById(id: ID): Bar + } + service1: | + type Foo { + barId: ID + id: ID + } + + type Query { + foo: Foo + } +query: | + query { + foo { + bar { + name + } + } + } +variables: { } +serviceCalls: + - serviceName: "service1" + request: + query: | + query { + foo { + __typename__hydration__bar: __typename + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "foo": { + "__typename__hydration__bar": "Foo" + } + }, + "extensions": {} + } + - serviceName: "service2" + request: + query: | + query { + barById(id: "barId") { + name + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "barById": { + "name": "Bar1" + } + }, + "extensions": {} + } +# language=JSON +response: |- + { + "data": { + "foo": { + "bar": { + "name": "Bar1" + } + } + }, + "extensions": {} + } \ No newline at end of file diff --git a/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-number.yml b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-number.yml new file mode 100644 index 000000000..5f89d22db --- /dev/null +++ b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-number.yml @@ -0,0 +1,98 @@ +name: "basic hydration with static arg number" +enabled: true +overallSchema: + service2: | + type Query { + barById(id: Int): Bar + } + type Bar { + id: Int + name: String + } + service1: | + type Query { + foo: Foo + } + type Foo { + id: ID + bar: Bar @hydrated(service: "service2" field: "barById" arguments: [{name: "id" value: 12345}]) + } +underlyingSchema: + service2: | + type Bar { + id: Int + name: String + } + + type Query { + barById(id: Int): Bar + } + service1: | + type Foo { + barId: Int + id: ID + } + + type Query { + foo: Foo + } +query: | + query { + foo { + bar { + name + } + } + } +variables: { } +serviceCalls: + - serviceName: "service1" + request: + query: | + query { + foo { + __typename__hydration__bar: __typename + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "foo": { + "__typename__hydration__bar": "Foo" + } + }, + "extensions": {} + } + - serviceName: "service2" + request: + query: | + query { + barById(id: 12345) { + name + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "barById": { + "name": "Bar12345" + } + }, + "extensions": {} + } +# language=JSON +response: |- + { + "data": { + "foo": { + "bar": { + "name": "Bar12345" + } + } + }, + "extensions": {} + } \ No newline at end of file diff --git a/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-args.yml b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-args.yml index 8386e0f4c..da6fa7415 100644 --- a/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-args.yml +++ b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-args.yml @@ -16,7 +16,6 @@ overallSchema: type Foo { id: ID bar: Bar @hydrated(service: "service2" field: "barById" arguments: [{name: "id" value: "barId"}]) - barLongerInput: Bar @hydrated(service: "service2" field: "barById" arguments: [{name: "id" value: "$source.fooDetails.externalBarId"}]) } underlyingSchema: service2: | @@ -31,14 +30,9 @@ underlyingSchema: service1: | type Foo { barId: ID - fooDetails: FooDetails id: ID } - type FooDetails { - externalBarId: ID - } - type Query { foo: Foo } @@ -58,7 +52,6 @@ serviceCalls: query { foo { __typename__hydration__bar: __typename - hydration__bar__barId: barId } } variables: { } @@ -67,7 +60,6 @@ serviceCalls: { "data": { "foo": { - "hydration__bar__barId": "barId", "__typename__hydration__bar": "Foo" } }, From 181f1c4badcc8c3ccd849fcf8495b679b7547d63 Mon Sep 17 00:00:00 2001 From: Steven Barker Date: Thu, 14 Sep 2023 12:49:40 +1200 Subject: [PATCH 06/15] adding JSON type to tests --- .../nadel/tests/GatewaySchemaWiringFactory.kt | 7 ++ ...asic-hydration-with-static-arg-integer.yml | 98 ------------------- 2 files changed, 7 insertions(+), 98 deletions(-) delete mode 100644 test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-integer.yml diff --git a/test/src/test/kotlin/graphql/nadel/tests/GatewaySchemaWiringFactory.kt b/test/src/test/kotlin/graphql/nadel/tests/GatewaySchemaWiringFactory.kt index 41eb712a8..9ee3dc213 100644 --- a/test/src/test/kotlin/graphql/nadel/tests/GatewaySchemaWiringFactory.kt +++ b/test/src/test/kotlin/graphql/nadel/tests/GatewaySchemaWiringFactory.kt @@ -59,11 +59,18 @@ class GatewaySchemaWiringFactory : NeverWiringFactory() { .coercing(Scalars.GraphQLString.coercing) .build() +// private val jsonScalar = GraphQLScalarType.newScalar() +// .name("JSON") +// .description("JSON Scalar type") +// .coercing(ExtendedScalars.Json.coercing) +// .build() + private val defaultScalars = mapOf( urlScalar.name to urlScalar, ExtendedScalars.Json.name to ExtendedScalars.Json, ExtendedScalars.GraphQLLong.name to ExtendedScalars.GraphQLLong, dateTimeScalar.name to dateTimeScalar, +// jsonScalar.name to jsonScalar, ) } } diff --git a/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-integer.yml b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-integer.yml deleted file mode 100644 index da6fa7415..000000000 --- a/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-integer.yml +++ /dev/null @@ -1,98 +0,0 @@ -name: "basic hydration with static args" -enabled: true -overallSchema: - service2: | - type Query { - barById(id: ID): Bar - } - type Bar { - id: ID - name: String - } - service1: | - type Query { - foo: Foo - } - type Foo { - id: ID - bar: Bar @hydrated(service: "service2" field: "barById" arguments: [{name: "id" value: "barId"}]) - } -underlyingSchema: - service2: | - type Bar { - id: ID - name: String - } - - type Query { - barById(id: ID): Bar - } - service1: | - type Foo { - barId: ID - id: ID - } - - type Query { - foo: Foo - } -query: | - query { - foo { - bar { - name - } - } - } -variables: { } -serviceCalls: - - serviceName: "service1" - request: - query: | - query { - foo { - __typename__hydration__bar: __typename - } - } - variables: { } - # language=JSON - response: |- - { - "data": { - "foo": { - "__typename__hydration__bar": "Foo" - } - }, - "extensions": {} - } - - serviceName: "service2" - request: - query: | - query { - barById(id: "barId") { - name - } - } - variables: { } - # language=JSON - response: |- - { - "data": { - "barById": { - "name": "Bar1" - } - }, - "extensions": {} - } -# language=JSON -response: |- - { - "data": { - "foo": { - "bar": { - "name": "Bar1" - } - } - }, - "extensions": {} - } \ No newline at end of file From 9a5859563dbe0e2e0560d95469c02bdc9eddd60a Mon Sep 17 00:00:00 2001 From: Steven Barker Date: Thu, 14 Sep 2023 21:47:25 +1200 Subject: [PATCH 07/15] tidy up static args code --- .../blueprint/NadelExecutionBlueprintFactory.kt | 4 ++-- .../hydration/NadelHydrationActorInputDef.kt | 4 ++-- .../java/graphql/nadel/schema/NadelDirectives.kt | 2 +- .../nadel/validation/NadelHydrationValidation.kt | 16 +++------------- .../nadel/tests/GatewaySchemaWiringFactory.kt | 7 ------- .../nadel/tests/hooks/HydrationDetailsHook.kt | 9 +-------- ... basic-hydration-with-static-arg-integer.yml} | 2 +- 7 files changed, 10 insertions(+), 34 deletions(-) rename test/src/test/resources/fixtures/hydration/{basic-hydration-with-static-arg-number.yml => basic-hydration-with-static-arg-integer.yml} (96%) 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 51513b29d..34b6b586e 100644 --- a/lib/src/main/java/graphql/nadel/engine/blueprint/NadelExecutionBlueprintFactory.kt +++ b/lib/src/main/java/graphql/nadel/engine/blueprint/NadelExecutionBlueprintFactory.kt @@ -262,7 +262,7 @@ private class Factory( when (it.valueSource) { is NadelHydrationActorInputDef.ValueSource.ArgumentValue -> null is FieldResultValue -> it.valueSource.queryPathToField - is NadelHydrationActorInputDef.ValueSource.StaticValue -> null //check this (but should be right as only need fields for FieldResultValue ($source) + is NadelHydrationActorInputDef.ValueSource.StaticValue -> null } }, ) @@ -361,7 +361,7 @@ private class Factory( when (val hydrationValueSource: NadelHydrationActorInputDef.ValueSource = it.valueSource) { is NadelHydrationActorInputDef.ValueSource.ArgumentValue -> emptyList() is FieldResultValue -> selectSourceFieldQueryPaths(hydrationValueSource) - is NadelHydrationActorInputDef.ValueSource.StaticValue -> emptyList() //check this (but should be right as only need fields for FieldResultValue ($source) + is NadelHydrationActorInputDef.ValueSource.StaticValue -> emptyList() } }).toSet() diff --git a/lib/src/main/java/graphql/nadel/engine/blueprint/hydration/NadelHydrationActorInputDef.kt b/lib/src/main/java/graphql/nadel/engine/blueprint/hydration/NadelHydrationActorInputDef.kt index 70a086e51..4c33a0faf 100644 --- a/lib/src/main/java/graphql/nadel/engine/blueprint/hydration/NadelHydrationActorInputDef.kt +++ b/lib/src/main/java/graphql/nadel/engine/blueprint/hydration/NadelHydrationActorInputDef.kt @@ -24,7 +24,7 @@ data class NadelHydrationActorInputDef( * } * ``` */ - data class FieldResultValue( // $source + data class FieldResultValue( val queryPathToField: NadelQueryPath, val fieldDefinition: GraphQLFieldDefinition, ) : ValueSource() @@ -40,7 +40,7 @@ data class NadelHydrationActorInputDef( * } * ``` */ - data class ArgumentValue( // $args + data class ArgumentValue( val argumentName: String, val argumentDefinition: GraphQLArgument, val defaultValue: NormalizedInputValue?, diff --git a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt index 602487025..7b030c08c 100644 --- a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt +++ b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt @@ -367,7 +367,7 @@ object NadelDirectives { } } - private fun createRemoteArgumentSource(value: Value<*>): RemoteArgumentSource { //needs to be graphql.language "Value" + private fun createRemoteArgumentSource(value: Value<*>): RemoteArgumentSource { if (value is StringValue) { val values = listFromDottedString(value.value) return when (values.first()) { diff --git a/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt index efbdf1175..d4e93468e 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt @@ -25,11 +25,9 @@ import graphql.nadel.validation.util.NadelSchemaUtil.getHydrations import graphql.nadel.validation.util.NadelSchemaUtil.hasRename import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLFieldsContainer -import graphql.schema.GraphQLInputType import graphql.schema.GraphQLInterfaceType import graphql.schema.GraphQLNamedOutputType import graphql.schema.GraphQLSchema -import graphql.schema.GraphQLType import graphql.schema.GraphQLUnionType internal class NadelHydrationValidation( @@ -217,9 +215,7 @@ internal class NadelHydrationValidation( MissingHydrationFieldValueSource(parent, overallField, remoteArgSource) } else { // TODO: check argument type is correct - val fieldOutputType = field.type - hydrationArgTypesMatch(fieldOutputType) - + null } } @@ -229,8 +225,7 @@ internal class NadelHydrationValidation( MissingHydrationArgumentValueSource(parent, overallField, remoteArgSource) } else { // TODO: check argument type is correct - val hydrationArgType = argument.type - hydrationArgTypesMatch(hydrationArgType) + null } } @@ -239,9 +234,4 @@ internal class NadelHydrationValidation( } } } - - private fun hydrationArgTypesMatch(type: GraphQLType): NadelSchemaValidationError? { //getHydrationArgumentErrors - // if type matches, return null, otherwise return error - return null - } -} +} \ No newline at end of file diff --git a/test/src/test/kotlin/graphql/nadel/tests/GatewaySchemaWiringFactory.kt b/test/src/test/kotlin/graphql/nadel/tests/GatewaySchemaWiringFactory.kt index 9ee3dc213..41eb712a8 100644 --- a/test/src/test/kotlin/graphql/nadel/tests/GatewaySchemaWiringFactory.kt +++ b/test/src/test/kotlin/graphql/nadel/tests/GatewaySchemaWiringFactory.kt @@ -59,18 +59,11 @@ class GatewaySchemaWiringFactory : NeverWiringFactory() { .coercing(Scalars.GraphQLString.coercing) .build() -// private val jsonScalar = GraphQLScalarType.newScalar() -// .name("JSON") -// .description("JSON Scalar type") -// .coercing(ExtendedScalars.Json.coercing) -// .build() - private val defaultScalars = mapOf( urlScalar.name to urlScalar, ExtendedScalars.Json.name to ExtendedScalars.Json, ExtendedScalars.GraphQLLong.name to ExtendedScalars.GraphQLLong, dateTimeScalar.name to dateTimeScalar, -// jsonScalar.name to jsonScalar, ) } } diff --git a/test/src/test/kotlin/graphql/nadel/tests/hooks/HydrationDetailsHook.kt b/test/src/test/kotlin/graphql/nadel/tests/hooks/HydrationDetailsHook.kt index c898cc998..81224cf60 100644 --- a/test/src/test/kotlin/graphql/nadel/tests/hooks/HydrationDetailsHook.kt +++ b/test/src/test/kotlin/graphql/nadel/tests/hooks/HydrationDetailsHook.kt @@ -28,14 +28,7 @@ class `basic-hydration` : HydrationDetailsHook() { assert(actualHydrationDetails.hydrationSourceService.name == "service1") } } -@UseHook -class `basic-hydration-with-static-args` : HydrationDetailsHook() { - override fun assertHydrationDetails(actualHydrationDetails: ServiceExecutionHydrationDetails) { - assert(actualHydrationDetails.hydrationActorField.toString() == "Query.barById") - assert(actualHydrationDetails.hydrationSourceField.toString() == "Foo.bar") - assert(actualHydrationDetails.hydrationSourceService.name == "service1") - } -} + @UseHook class `batch-hydration-with-renamed-actor-field` : HydrationDetailsHook() { override fun assertHydrationDetails(actualHydrationDetails: ServiceExecutionHydrationDetails) { diff --git a/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-number.yml b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-integer.yml similarity index 96% rename from test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-number.yml rename to test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-integer.yml index 5f89d22db..024c93cf6 100644 --- a/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-number.yml +++ b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-integer.yml @@ -1,4 +1,4 @@ -name: "basic hydration with static arg number" +name: "basic hydration with static arg integer" enabled: true overallSchema: service2: | From 745d88f0532f27e883e96507a808ad31de1f300f Mon Sep 17 00:00:00 2001 From: Steven Barker Date: Sun, 24 Sep 2023 16:16:58 +1300 Subject: [PATCH 08/15] add json scalar type to type registry --- .../graphql/nadel/schema/NadelDirectives.kt | 18 ++++++++---------- .../graphql/nadel/schema/NeverWiringFactory.kt | 4 ++++ .../nadel/schema/OverallSchemaGenerator.kt | 5 +++++ .../nadel/schema/NadelDirectivesTest.kt | 8 ++++++-- 4 files changed, 23 insertions(+), 12 deletions(-) diff --git a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt index 7b030c08c..2f609441e 100644 --- a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt +++ b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt @@ -11,6 +11,7 @@ import graphql.language.DirectiveDefinition.newDirectiveDefinition import graphql.language.EnumTypeDefinition.newEnumTypeDefinition import graphql.language.EnumValueDefinition.newEnumValueDefinition import graphql.language.InputObjectTypeDefinition.newInputObjectDefinition +import graphql.language.ObjectValue import graphql.language.StringValue import graphql.language.Value import graphql.nadel.dsl.FieldMappingDefinition @@ -33,7 +34,6 @@ import graphql.nadel.util.onInterface import graphql.nadel.util.onObject import graphql.nadel.util.onScalar import graphql.nadel.util.onUnion -import graphql.scalars.`object`.JsonScalar import graphql.schema.GraphQLAppliedDirective import graphql.schema.GraphQLAppliedDirectiveArgument import graphql.schema.GraphQLDirectiveContainer @@ -41,7 +41,6 @@ import graphql.schema.GraphQLEnumType import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLSchema import java.util.Locale -import kotlin.reflect.typeOf /** * If you update this file please add to NadelBuiltInTypes @@ -255,8 +254,7 @@ object NadelDirectives { val hydrations = fieldDefinition.getAppliedDirectives(hydratedDirectiveDefinition.name) .asSequence() .map { directive -> - val argumentValues = resolveArgumentValue>(directive.getArgument("arguments")) - val arguments = createRemoteArgs(argumentValues) + val arguments = createRemoteArgs(directive.getArgument("arguments").argumentValue.value as ArrayValue) val inputIdentifiedBy = directive.getArgument("inputIdentifiedBy") val identifiedByValues = resolveArgumentValue>(inputIdentifiedBy) @@ -334,20 +332,20 @@ object NadelDirectives { ) } - private fun createRemoteArgs(arguments: List): List { + private fun createRemoteArgs(arguments: ArrayValue): List { fun Map.requireArgument(key: String): String { return requireNotNull(this[key]) { "${nadelHydrationArgumentDefinition.name} definition requires '$key' to be not-null" } } - return arguments + return arguments.values .map { arg -> @Suppress("UNCHECKED_CAST") // trust GraphQL type system and caller - val argMap = arg as Map - val remoteArgName = argMap.requireArgument("name") - val remoteArgValue = argMap.requireArgument("value") - val remoteArgumentSource = createRemoteArgumentSource(StringValue(remoteArgValue)) + val argMap = arg as ObjectValue + val remoteArgName = (argMap.objectFields.single { it.name == "name" }.value as StringValue).value + val remoteArgValue = argMap.objectFields.single { it.name == "value" }.value + val remoteArgumentSource = createRemoteArgumentSource(remoteArgValue) RemoteArgumentDefinition(remoteArgName, remoteArgumentSource) } } diff --git a/lib/src/main/java/graphql/nadel/schema/NeverWiringFactory.kt b/lib/src/main/java/graphql/nadel/schema/NeverWiringFactory.kt index 2bf4d5add..d8522616d 100644 --- a/lib/src/main/java/graphql/nadel/schema/NeverWiringFactory.kt +++ b/lib/src/main/java/graphql/nadel/schema/NeverWiringFactory.kt @@ -1,6 +1,7 @@ package graphql.nadel.schema import graphql.Assert.assertShouldNeverHappen +import graphql.scalars.ExtendedScalars import graphql.schema.Coercing import graphql.schema.DataFetcher import graphql.schema.GraphQLScalarType @@ -24,6 +25,9 @@ open class NeverWiringFactory : WiringFactory { override fun getScalar(environment: ScalarWiringEnvironment): GraphQLScalarType? { val scalarName = environment.scalarTypeDefinition.name + if (scalarName == ExtendedScalars.Json.name){ + return ExtendedScalars.Json + } return GraphQLScalarType .newScalar() .name(scalarName) diff --git a/lib/src/main/java/graphql/nadel/schema/OverallSchemaGenerator.kt b/lib/src/main/java/graphql/nadel/schema/OverallSchemaGenerator.kt index ffc49aef8..29bd4be9f 100644 --- a/lib/src/main/java/graphql/nadel/schema/OverallSchemaGenerator.kt +++ b/lib/src/main/java/graphql/nadel/schema/OverallSchemaGenerator.kt @@ -4,6 +4,7 @@ import graphql.GraphQLException import graphql.language.FieldDefinition import graphql.language.ObjectTypeDefinition import graphql.language.ObjectTypeDefinition.newObjectTypeDefinition +import graphql.language.ScalarTypeDefinition.newScalarTypeDefinition import graphql.language.SchemaDefinition import graphql.language.SourceLocation import graphql.nadel.NadelDefinitionRegistry @@ -12,6 +13,7 @@ import graphql.nadel.util.AnyNamedNode import graphql.nadel.util.AnySDLDefinition import graphql.nadel.util.AnySDLNamedDefinition import graphql.nadel.util.isExtensionDef +import graphql.scalars.ExtendedScalars import graphql.schema.GraphQLSchema import graphql.schema.idl.RuntimeWiring import graphql.schema.idl.SchemaGenerator @@ -70,6 +72,9 @@ internal class OverallSchemaGenerator { addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.nadelHydrationTemplateEnumDefinition) addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.hydratedFromDirectiveDefinition) addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.hydratedTemplateDirectiveDefinition) + addIfNotPresent(overallRegistry, allDefinitions, newScalarTypeDefinition() + .name(ExtendedScalars.Json.name) + .build()) for (definition in allDefinitions) { val error = overallRegistry.add(definition) diff --git a/lib/src/test/kotlin/graphql/nadel/schema/NadelDirectivesTest.kt b/lib/src/test/kotlin/graphql/nadel/schema/NadelDirectivesTest.kt index 32a43ace2..f057b25b7 100644 --- a/lib/src/test/kotlin/graphql/nadel/schema/NadelDirectivesTest.kt +++ b/lib/src/test/kotlin/graphql/nadel/schema/NadelDirectivesTest.kt @@ -10,7 +10,8 @@ import graphql.nadel.schema.NadelDirectives.nadelHydrationComplexIdentifiedBy import graphql.nadel.schema.NadelDirectives.nadelHydrationFromArgumentDefinition import graphql.nadel.schema.NadelDirectives.nadelHydrationTemplateEnumDefinition import graphql.schema.GraphQLSchema -import graphql.schema.idl.RuntimeWiring.MOCKED_WIRING +import graphql.schema.idl.MockedWiringFactory +import graphql.schema.idl.RuntimeWiring import graphql.schema.idl.SchemaGenerator import graphql.schema.idl.SchemaParser import io.kotest.core.spec.style.DescribeSpec @@ -29,11 +30,14 @@ class NadelDirectivesTest : DescribeSpec({ ${AstPrinter.printAst(nadelHydrationTemplateEnumDefinition)} ${AstPrinter.printAst(hydratedFromDirectiveDefinition)} ${AstPrinter.printAst(hydratedTemplateDirectiveDefinition)} + scalar JSON """ fun getSchema(schemaText: String): GraphQLSchema { val typeDefs = SchemaParser().parse(commonDefs + "\n" + schemaText) - return SchemaGenerator().makeExecutableSchema(typeDefs, MOCKED_WIRING) + return SchemaGenerator().makeExecutableSchema(typeDefs, RuntimeWiring + .newRuntimeWiring() + .wiringFactory(NeverWiringFactory()).build()) } describe("@hydrated") { From 0a9512780bd254dc66f9429618637df4245590ee Mon Sep 17 00:00:00 2001 From: Steven Barker Date: Sun, 24 Sep 2023 18:00:42 +1300 Subject: [PATCH 09/15] add JSON type to test assertions --- lib/src/test/kotlin/graphql/nadel/NadelSchemasTest.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/src/test/kotlin/graphql/nadel/NadelSchemasTest.kt b/lib/src/test/kotlin/graphql/nadel/NadelSchemasTest.kt index c81c5afd0..f853fd240 100644 --- a/lib/src/test/kotlin/graphql/nadel/NadelSchemasTest.kt +++ b/lib/src/test/kotlin/graphql/nadel/NadelSchemasTest.kt @@ -73,7 +73,7 @@ class NadelSchemasTest : DescribeSpec({ .build() // then - assert(schemas.engineSchema.userTypeNames == setOf("World", "Echo", "Query")) + assert(schemas.engineSchema.userTypeNames == setOf("World", "Echo", "Query", "JSON")) val testService = schemas.services.single() assert(testService.underlyingSchema.userTypeNames == setOf("World", "Echo", "Query", "Food")) } @@ -149,7 +149,7 @@ class NadelSchemasTest : DescribeSpec({ .build() // then - assert(schemas.engineSchema.userTypeNames == setOf("World", "Echo", "Query")) + assert(schemas.engineSchema.userTypeNames == setOf("World", "Echo", "Query", "JSON")) val testService = schemas.services.single() assert(testService.underlyingSchema.userTypeNames == setOf("World", "Echo", "Query", "Food")) } @@ -211,7 +211,7 @@ class NadelSchemasTest : DescribeSpec({ .build() // then - assert(schemas.engineSchema.userTypeNames == setOf("World", "Echo", "Query", "Issue")) + assert(schemas.engineSchema.userTypeNames == setOf("World", "Echo", "Query", "Issue", "JSON")) val issueService = schemas.services.single { it.name == "issue" } assert(issueService.underlyingSchema.userTypeNames == setOf("Query", "Issue")) @@ -272,7 +272,7 @@ class NadelSchemasTest : DescribeSpec({ .build() // then - assert(schemas.engineSchema.userTypeNames == setOf("World", "Echo", "Query", "Issue")) + assert(schemas.engineSchema.userTypeNames == setOf("World", "Echo", "Query", "Issue", "JSON")) } it("does not validate the schemas") { @@ -321,7 +321,7 @@ class NadelSchemasTest : DescribeSpec({ .build() // then - assert(schemas.engineSchema.userTypeNames == setOf("Query", "World", "Echo", "Issue")) + assert(schemas.engineSchema.userTypeNames == setOf("Query", "World", "Echo", "Issue", "JSON")) val testService = schemas.services.first { it.name == "test" } assert(testService.definitionRegistry.typeNames == setOf("Query", "Echo", "World")) From 95728d0f16d4643b5f575ec58a57f8d608a2ee85 Mon Sep 17 00:00:00 2001 From: Steven Barker Date: Mon, 25 Sep 2023 14:39:15 +1000 Subject: [PATCH 10/15] add more static arg tests --- .../basic-hydration-with-static-arg-array.yml | 114 ++++++++++++++++++ ...asic-hydration-with-static-arg-boolean.yml | 100 +++++++++++++++ .../basic-hydration-with-static-arg-float.yml | 100 +++++++++++++++ 3 files changed, 314 insertions(+) create mode 100644 test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-array.yml create mode 100644 test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-boolean.yml create mode 100644 test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-float.yml diff --git a/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-array.yml b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-array.yml new file mode 100644 index 000000000..7281c05cb --- /dev/null +++ b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-array.yml @@ -0,0 +1,114 @@ +name: "basic hydration with static arg array" +enabled: true +overallSchema: + service2: | + type Query { + barsByIds(ids: [ID]): [Bar] + } + type Bar { + id: ID + name: String + } + service1: | + type Query { + foo: Foo + } + type Foo { + id: ID + bars: [Bar] @hydrated(service: "service2" field: "barsByIds" arguments: [{name: "ids" value: ["barId1", "barId2", "barId3"]}]) + } +underlyingSchema: + service2: | + type Bar { + id: ID + name: String + } + + type Query { + barsByIds(ids: [ID]): [Bar] + } + service1: | + type Foo { + barId: ID + id: ID + } + + type Query { + foo: Foo + } +query: | + query { + foo { + bars { + name + } + } + } +variables: { } +serviceCalls: + - serviceName: "service1" + request: + query: | + query { + foo { + __typename__hydration__bar: __typename + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "foo": { + "__typename__hydration__bar": "Foo" + } + }, + "extensions": {} + } + - serviceName: "service2" + request: + query: | + query { + barsByIds(ids: ["barId1", "barId2", "barId3"]) { + name + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "barsByIds": [ + { + "name": "Bar1" + }, + { + "name": "Bar2" + }, + { + "name": "Bar3" + } + ] + }, + "extensions": {} + } +# language=JSON +response: |- + { + "data": { + "foo": { + "bars": [ + { + "name": "Bar1234567" + }, + { + "name": "Bar2" + }, + { + "name": "Bar3" + } + ] + } + }, + "extensions": {} + } \ No newline at end of file diff --git a/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-boolean.yml b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-boolean.yml new file mode 100644 index 000000000..b5e5b40f0 --- /dev/null +++ b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-boolean.yml @@ -0,0 +1,100 @@ +name: "basic hydration with static arg boolean" +enabled: true +overallSchema: + service2: | + type Query { + barWithSomeAttribute(someAttribute: Boolean): Bar + } + type Bar { + id: ID + name: String + someAttribute: Boolean + } + service1: | + type Query { + foo: Foo + } + type Foo { + id: ID + bar: Bar @hydrated(service: "service2" field: "barWithSomeAttribute" arguments: [{name: "someAttribute" value: true}]) + } +underlyingSchema: + service2: | + type Bar { + id: ID + name: String + someAttribute: Boolean + } + + type Query { + barWithSomeAttribute(someAttribute: Boolean): Bar + } + service1: | + type Foo { + barId: ID + id: ID + } + + type Query { + foo: Foo + } +query: | + query { + foo { + bar { + name + } + } + } +variables: { } +serviceCalls: + - serviceName: "service1" + request: + query: | + query { + foo { + __typename__hydration__bar: __typename + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "foo": { + "__typename__hydration__bar": "Foo" + } + }, + "extensions": {} + } + - serviceName: "service2" + request: + query: | + query { + barWithSomeAttribute(someAttribute: true) { + name + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "barWithSomeAttribute": { + "name": "Bar12345" + } + }, + "extensions": {} + } +# language=JSON +response: |- + { + "data": { + "foo": { + "bar": { + "name": "Bar12345" + } + } + }, + "extensions": {} + } \ No newline at end of file diff --git a/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-float.yml b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-float.yml new file mode 100644 index 000000000..2dff00fcc --- /dev/null +++ b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-float.yml @@ -0,0 +1,100 @@ +name: "basic hydration with static arg float" +enabled: true +overallSchema: + service2: | + type Query { + barWithSomeFloat(someFloat: Float): Bar + } + type Bar { + id: ID + name: String + someFloat: Float + } + service1: | + type Query { + foo: Foo + } + type Foo { + id: ID + bar: Bar @hydrated(service: "service2" field: "barWithSomeFloat" arguments: [{name: "someFloat" value: 123.45}]) + } +underlyingSchema: + service2: | + type Bar { + id: ID + name: String + someFloat: Float + } + + type Query { + barWithSomeFloat(someFloat: Float): Bar + } + service1: | + type Foo { + barId: ID + id: ID + } + + type Query { + foo: Foo + } +query: | + query { + foo { + bar { + name + } + } + } +variables: { } +serviceCalls: + - serviceName: "service1" + request: + query: | + query { + foo { + __typename__hydration__bar: __typename + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "foo": { + "__typename__hydration__bar": "Foo" + } + }, + "extensions": {} + } + - serviceName: "service2" + request: + query: | + query { + barWithSomeFloat(someFloat: 123.45) { + name + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "barWithSomeFloat": { + "name": "Bar12345" + } + }, + "extensions": {} + } +# language=JSON +response: |- + { + "data": { + "foo": { + "bar": { + "name": "Bar12345" + } + } + }, + "extensions": {} + } \ No newline at end of file From 39fe797c4e19549e5ec739a29b4a78f671b7657d Mon Sep 17 00:00:00 2001 From: Steven Barker Date: Mon, 25 Sep 2023 14:41:13 +1000 Subject: [PATCH 11/15] remove array test (for now) --- .../basic-hydration-with-static-arg-array.yml | 114 ------------------ 1 file changed, 114 deletions(-) delete mode 100644 test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-array.yml diff --git a/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-array.yml b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-array.yml deleted file mode 100644 index 7281c05cb..000000000 --- a/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-array.yml +++ /dev/null @@ -1,114 +0,0 @@ -name: "basic hydration with static arg array" -enabled: true -overallSchema: - service2: | - type Query { - barsByIds(ids: [ID]): [Bar] - } - type Bar { - id: ID - name: String - } - service1: | - type Query { - foo: Foo - } - type Foo { - id: ID - bars: [Bar] @hydrated(service: "service2" field: "barsByIds" arguments: [{name: "ids" value: ["barId1", "barId2", "barId3"]}]) - } -underlyingSchema: - service2: | - type Bar { - id: ID - name: String - } - - type Query { - barsByIds(ids: [ID]): [Bar] - } - service1: | - type Foo { - barId: ID - id: ID - } - - type Query { - foo: Foo - } -query: | - query { - foo { - bars { - name - } - } - } -variables: { } -serviceCalls: - - serviceName: "service1" - request: - query: | - query { - foo { - __typename__hydration__bar: __typename - } - } - variables: { } - # language=JSON - response: |- - { - "data": { - "foo": { - "__typename__hydration__bar": "Foo" - } - }, - "extensions": {} - } - - serviceName: "service2" - request: - query: | - query { - barsByIds(ids: ["barId1", "barId2", "barId3"]) { - name - } - } - variables: { } - # language=JSON - response: |- - { - "data": { - "barsByIds": [ - { - "name": "Bar1" - }, - { - "name": "Bar2" - }, - { - "name": "Bar3" - } - ] - }, - "extensions": {} - } -# language=JSON -response: |- - { - "data": { - "foo": { - "bars": [ - { - "name": "Bar1234567" - }, - { - "name": "Bar2" - }, - { - "name": "Bar3" - } - ] - } - }, - "extensions": {} - } \ No newline at end of file From c5b001441ac4a6efafb8f5ca5087a88ab0e4fec9 Mon Sep 17 00:00:00 2001 From: Steven Barker Date: Wed, 4 Oct 2023 23:31:42 +1300 Subject: [PATCH 12/15] add extra validation and more complex test --- .../batch/NadelBatchHydrationInputBuilder.kt | 8 +- .../validation/NadelHydrationValidation.kt | 3 + .../validation/NadelSchemaValidationError.kt | 14 ++ .../NadelHydrationValidationTest.kt | 57 +++++++ .../basic-hydration-with-static-arg-array.yml | 103 +++++++++++++ ...hydration-with-static-arg-object-array.yml | 140 ++++++++++++++++++ ...basic-hydration-with-static-arg-object.yml | 120 +++++++++++++++ 7 files changed, 439 insertions(+), 6 deletions(-) create mode 100644 test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-array.yml create mode 100644 test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-object-array.yml create mode 100644 test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-object.yml diff --git a/lib/src/main/java/graphql/nadel/engine/transform/hydration/batch/NadelBatchHydrationInputBuilder.kt b/lib/src/main/java/graphql/nadel/engine/transform/hydration/batch/NadelBatchHydrationInputBuilder.kt index 5abdd3925..31cd20105 100644 --- a/lib/src/main/java/graphql/nadel/engine/transform/hydration/batch/NadelBatchHydrationInputBuilder.kt +++ b/lib/src/main/java/graphql/nadel/engine/transform/hydration/batch/NadelBatchHydrationInputBuilder.kt @@ -56,15 +56,11 @@ internal object NadelBatchHydrationInputBuilder { // These are batch values, ignore them is NadelHydrationActorInputDef.ValueSource.FieldResultValue -> null is NadelHydrationActorInputDef.ValueSource.StaticValue -> { - val staticValue: NormalizedInputValue? = makeNormalizedInputValue( + val staticValue: NormalizedInputValue = makeNormalizedInputValue( type = actorFieldArg.actorArgumentDef.type, value = valueSource.value, ) - if (staticValue != null) { - actorFieldArg to staticValue - } else { - null - } + actorFieldArg to staticValue } } } diff --git a/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt index d4e93468e..32a1a9bc0 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt @@ -20,6 +20,7 @@ import graphql.nadel.validation.NadelSchemaValidationError.MissingHydrationArgum import graphql.nadel.validation.NadelSchemaValidationError.MissingHydrationFieldValueSource import graphql.nadel.validation.NadelSchemaValidationError.MissingRequiredHydrationActorFieldArgument import graphql.nadel.validation.NadelSchemaValidationError.MultipleSourceArgsInBatchHydration +import graphql.nadel.validation.NadelSchemaValidationError.NoSourceArgsInBatchHydration import graphql.nadel.validation.NadelSchemaValidationError.NonExistentHydrationActorFieldArgument import graphql.nadel.validation.util.NadelSchemaUtil.getHydrations import graphql.nadel.validation.util.NadelSchemaUtil.hasRename @@ -193,6 +194,8 @@ internal class NadelHydrationValidation( when { numberOfSourceArgs > 1 -> listOf(MultipleSourceArgsInBatchHydration(parent, overallField)) + numberOfSourceArgs == 0 -> + listOf(NoSourceArgsInBatchHydration(parent, overallField)) else -> emptyList() } diff --git a/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt b/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt index e429c2d88..102fb5953 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt @@ -417,6 +417,20 @@ sealed interface NadelSchemaValidationError { override val subject = overallField } + data class NoSourceArgsInBatchHydration( + val parentType: NadelServiceSchemaElement, + val overallField: GraphQLFieldDefinition, + ) : NadelSchemaValidationError { + val service: Service get() = parentType.service + + override val message = run { + val fieldCoordinates = makeFieldCoordinates(parentType.overall.name, overallField.name) + "No \$source.xxx arguments for batch hydration. Field: $fieldCoordinates" + } + + override val subject = overallField + } + data class MissingArgumentOnUnderlying( val parentType: NadelServiceSchemaElement, val overallField: GraphQLFieldDefinition, diff --git a/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationValidationTest.kt b/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationValidationTest.kt index ea19204df..e2a5dd2c4 100644 --- a/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationValidationTest.kt +++ b/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationValidationTest.kt @@ -132,6 +132,63 @@ class NadelHydrationValidationTest : DescribeSpec({ assert(errors.single().message == "Multiple \$source.xxx arguments are not supported for batch hydration. Field: JiraIssue.creator") } + it("fails when batch hydration with no \$source args") { + val fixture = NadelValidationTestFixture( + overallSchema = mapOf( + "issues" to """ + type Query { + issue: JiraIssue + } + type JiraIssue @renamed(from: "Issue") { + id: ID! + creator(siteId: ID!): User @hydrated( + service: "users" + field: "users" + arguments: [ + {name: "id", value: "creatorId123"} + {name: "siteId", value: "$argument.siteId"} + ] + ) + } + """.trimIndent(), + "users" to """ + type Query { + users(id: ID!, siteId: ID!): [User] + } + type User { + id: ID! + name: String! + } + """.trimIndent(), + ), + underlyingSchema = mapOf( + "issues" to """ + type Query { + issue: Issue + } + type Issue { + id: ID! + creator: ID! + } + """.trimIndent(), + "users" to """ + type Query { + users(id: ID!, siteId: ID!): [User] + } + type User { + id: ID! + name: String! + } + """.trimIndent(), + ), + ) + + val errors = validate(fixture) + assert(errors.size == 1) + assert(errors.single() is NadelSchemaValidationError.NoSourceArgsInBatchHydration) + assert(errors.single().message == "No \$source.xxx arguments for batch hydration. Field: JiraIssue.creator") + } + it("passes when batch hydration with a single \$source arg and an \$argument arg") { val fixture = NadelValidationTestFixture( overallSchema = mapOf( diff --git a/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-array.yml b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-array.yml new file mode 100644 index 000000000..54344dea6 --- /dev/null +++ b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-array.yml @@ -0,0 +1,103 @@ +name: "basic hydration with static arg array" +enabled: true +overallSchema: + service2: | + type Query { + barById(id: ID, friendIds: [ID]): Bar + } + type Bar { + id: ID + name: String + } + service1: | + type Query { + foo: Foo + } + type Foo { + id: ID + bar: Bar @hydrated(service: "service2" field: "barById" arguments: [ + { name: "id" value: "$source.id" }, + { name: "friendIds" value: ["barId2", "barId3", "barId4"] } + ]) + } +underlyingSchema: + service2: | + type Bar { + id: ID + name: String + } + + type Query { + barById(id: ID, friendIds: [ID]): Bar + } + service1: | + type Foo { + barId: ID + id: ID + } + + type Query { + foo: Foo + } +query: | + query { + foo { + bar { + name + } + } + } +variables: { } +serviceCalls: + - serviceName: "service1" + request: + query: | + query { + foo { + __typename__hydration__bar: __typename + hydration__bar__id: id + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "foo": { + "__typename__hydration__bar": "Foo", + "hydration__bar__id": "barId" + } + }, + "extensions": {} + } + - serviceName: "service2" + request: + query: | + query { + barById(id: "barId", friendIds: ["barId2", "barId3", "barId4"]) { + name + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "barById": { + "name": "Bar1" + } + }, + "extensions": {} + } +# language=JSON +response: |- + { + "data": { + "foo": { + "bar": { + "name": "Bar1" + } + } + }, + "extensions": {} + } \ No newline at end of file diff --git a/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-object-array.yml b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-object-array.yml new file mode 100644 index 000000000..fca0cc125 --- /dev/null +++ b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-object-array.yml @@ -0,0 +1,140 @@ +name: "basic hydration with static arg object array" +enabled: true +overallSchema: + service2: | + type Query { + barById(id: ID, friends: [FullName]): Bar + } + type Bar { + id: ID + name: String + } + input FullName { + firstName: String + lastName: String + } + service1: | + type Query { + foo: Foo + } + type Foo { + id: ID + bar: Bar @hydrated(service: "service2" field: "barById" arguments: [ + { name: "id" value: "$source.id" }, + { name: "friends" + value: [ + { + firstName: "first" + lastName: "last" + }, + { + firstName: "first2" + lastName: "last2" + }, + { + firstName: "first3" + lastName: "last3" + } + ] + } + ]) + } +underlyingSchema: + service2: | + type Bar { + id: ID + name: String + } + + type Query { + barById(id: ID, friends: [FullName]): Bar + } + + input FullName { + firstName: String + lastName: String + } + service1: | + type Foo { + barId: ID + id: ID + } + + type Query { + foo: Foo + } +query: | + query { + foo { + bar { + name + } + } + } +variables: { } +serviceCalls: + - serviceName: "service1" + request: + query: | + query { + foo { + __typename__hydration__bar: __typename + hydration__bar__id: id + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "foo": { + "__typename__hydration__bar": "Foo", + "hydration__bar__id": "barId" + } + }, + "extensions": {} + } + - serviceName: "service2" + request: + query: | + query { + barById(id: "barId", friends: [ + { + firstName: "first" + lastName: "last" + }, + { + firstName: "first2" + lastName: "last2" + }, + { + firstName: "first3" + lastName: "last3" + } + ]) { + name + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "barById": { + "name": "Bar1" + } + }, + "extensions": {} + } +# language=JSON +response: |- + { + "data": { + "foo": { + "bar": { + "name": "Bar1" + } + } + }, + "extensions": {} + } \ No newline at end of file diff --git a/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-object.yml b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-object.yml new file mode 100644 index 000000000..00cf956bb --- /dev/null +++ b/test/src/test/resources/fixtures/hydration/basic-hydration-with-static-arg-object.yml @@ -0,0 +1,120 @@ +name: "basic hydration with static arg object" +enabled: true +overallSchema: + service2: | + type Query { + barById(id: ID, fullName: FullName): Bar + } + type Bar { + id: ID + name: String + } + input FullName { + firstName: String + lastName: String + } + service1: | + type Query { + foo: Foo + } + type Foo { + id: ID + bar: Bar @hydrated(service: "service2" field: "barById" arguments: [ + { name: "id" value: "$source.id" }, + { name: "fullName" + value: { + firstName: "first" + lastName: "last" + } + } + ]) + } +underlyingSchema: + service2: | + type Bar { + id: ID + name: String + } + + type Query { + barById(id: ID, fullName: FullName): Bar + } + + input FullName { + firstName: String + lastName: String + } + service1: | + type Foo { + barId: ID + id: ID + } + + type Query { + foo: Foo + } +query: | + query { + foo { + bar { + name + } + } + } +variables: { } +serviceCalls: + - serviceName: "service1" + request: + query: | + query { + foo { + __typename__hydration__bar: __typename + hydration__bar__id: id + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "foo": { + "__typename__hydration__bar": "Foo", + "hydration__bar__id": "barId" + } + }, + "extensions": {} + } + - serviceName: "service2" + request: + query: | + query { + barById(id: "barId", fullName: { + firstName: "first" + lastName: "last" + }) { + name + } + } + variables: { } + # language=JSON + response: |- + { + "data": { + "barById": { + "name": "Bar1" + } + }, + "extensions": {} + } +# language=JSON +response: |- + { + "data": { + "foo": { + "bar": { + "name": "Bar1" + } + } + }, + "extensions": {} + } \ No newline at end of file From e3ff70e3eb4a60f03f1fed2a6e014dc2775de069 Mon Sep 17 00:00:00 2001 From: Steven Barker Date: Wed, 4 Oct 2023 23:46:41 +1300 Subject: [PATCH 13/15] changing basic hydration file back to original --- .../test/resources/fixtures/hydration/basic-hydration.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test/src/test/resources/fixtures/hydration/basic-hydration.yml b/test/src/test/resources/fixtures/hydration/basic-hydration.yml index 87ab4558a..6aa8242c6 100644 --- a/test/src/test/resources/fixtures/hydration/basic-hydration.yml +++ b/test/src/test/resources/fixtures/hydration/basic-hydration.yml @@ -16,6 +16,7 @@ overallSchema: type Foo { id: ID bar: Bar @hydrated(service: "service2" field: "barById" arguments: [{name: "id" value: "$source.barId"}]) + barLongerInput: Bar @hydrated(service: "service2" field: "barById" arguments: [{name: "id" value: "$source.fooDetails.externalBarId"}]) } underlyingSchema: service2: | @@ -30,9 +31,14 @@ underlyingSchema: service1: | type Foo { barId: ID + fooDetails: FooDetails id: ID } + type FooDetails { + externalBarId: ID + } + type Query { foo: Foo } @@ -97,4 +103,4 @@ response: |- } }, "extensions": {} - } + } \ No newline at end of file From 9eeb347f22567c0f69e05de62f3cf56d595c02ce Mon Sep 17 00:00:00 2001 From: sbarker2 <109047597+sbarker2@users.noreply.github.com> Date: Tue, 10 Oct 2023 10:03:02 +1300 Subject: [PATCH 14/15] Apply suggestions from code review Co-authored-by: Franklin Wang <9077461+gnawf@users.noreply.github.com> --- .../hydration/NadelHydrationActorInputDef.kt | 7 +++---- .../hydration/NadelHydrationInputBuilder.kt | 1 - .../batch/NadelBatchHydrationInputBuilder.kt | 2 +- .../graphql/nadel/schema/NadelDirectives.kt | 20 +++++++++---------- .../nadel/schema/NeverWiringFactory.kt | 2 +- 5 files changed, 14 insertions(+), 18 deletions(-) diff --git a/lib/src/main/java/graphql/nadel/engine/blueprint/hydration/NadelHydrationActorInputDef.kt b/lib/src/main/java/graphql/nadel/engine/blueprint/hydration/NadelHydrationActorInputDef.kt index 4c33a0faf..cd1594c36 100644 --- a/lib/src/main/java/graphql/nadel/engine/blueprint/hydration/NadelHydrationActorInputDef.kt +++ b/lib/src/main/java/graphql/nadel/engine/blueprint/hydration/NadelHydrationActorInputDef.kt @@ -51,17 +51,16 @@ data class NadelHydrationActorInputDef( * * ```graphql * type Issue { - * id: ID! # Value used as argument to issueId + * id: ID! * owner: User @hydrated(from: ["issueOwner"], args: [ * {name: "issueId" value: "issue123"} * ]) * } * ``` */ - data class StaticValue ( + data class StaticValue( val value: Value<*>, - ): ValueSource() - + ) : ValueSource() } } diff --git a/lib/src/main/java/graphql/nadel/engine/transform/hydration/NadelHydrationInputBuilder.kt b/lib/src/main/java/graphql/nadel/engine/transform/hydration/NadelHydrationInputBuilder.kt index 1fbb8a4d2..78c47eede 100644 --- a/lib/src/main/java/graphql/nadel/engine/transform/hydration/NadelHydrationInputBuilder.kt +++ b/lib/src/main/java/graphql/nadel/engine/transform/hydration/NadelHydrationInputBuilder.kt @@ -1,7 +1,6 @@ package graphql.nadel.engine.transform.hydration import graphql.language.NullValue -import graphql.language.StringValue import graphql.language.Value import graphql.nadel.engine.blueprint.NadelHydrationFieldInstruction import graphql.nadel.engine.blueprint.hydration.NadelHydrationActorInputDef diff --git a/lib/src/main/java/graphql/nadel/engine/transform/hydration/batch/NadelBatchHydrationInputBuilder.kt b/lib/src/main/java/graphql/nadel/engine/transform/hydration/batch/NadelBatchHydrationInputBuilder.kt index 31cd20105..f99315acb 100644 --- a/lib/src/main/java/graphql/nadel/engine/transform/hydration/batch/NadelBatchHydrationInputBuilder.kt +++ b/lib/src/main/java/graphql/nadel/engine/transform/hydration/batch/NadelBatchHydrationInputBuilder.kt @@ -63,7 +63,7 @@ internal object NadelBatchHydrationInputBuilder { actorFieldArg to staticValue } } - } + }, ) } diff --git a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt index 2f609441e..1ce4a261e 100644 --- a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt +++ b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt @@ -366,7 +366,7 @@ object NadelDirectives { } private fun createRemoteArgumentSource(value: Value<*>): RemoteArgumentSource { - if (value is StringValue) { + if (value is StringValue) { val values = listFromDottedString(value.value) return when (values.first()) { "\$source" -> RemoteArgumentSource( @@ -383,22 +383,20 @@ object NadelDirectives { sourceType = SourceType.FieldArgument, ) - else -> - RemoteArgumentSource( - argumentName = null, - pathToField = null, - staticValue = value, - sourceType = SourceType.StaticArgument, - ) + else -> RemoteArgumentSource( + argumentName = null, + pathToField = null, + staticValue = value, + sourceType = SourceType.StaticArgument, + ) } - } - else { + } else { return RemoteArgumentSource( argumentName = null, pathToField = null, staticValue = value, sourceType = SourceType.StaticArgument, - ) + ) } } diff --git a/lib/src/main/java/graphql/nadel/schema/NeverWiringFactory.kt b/lib/src/main/java/graphql/nadel/schema/NeverWiringFactory.kt index d8522616d..70739f9c0 100644 --- a/lib/src/main/java/graphql/nadel/schema/NeverWiringFactory.kt +++ b/lib/src/main/java/graphql/nadel/schema/NeverWiringFactory.kt @@ -25,7 +25,7 @@ open class NeverWiringFactory : WiringFactory { override fun getScalar(environment: ScalarWiringEnvironment): GraphQLScalarType? { val scalarName = environment.scalarTypeDefinition.name - if (scalarName == ExtendedScalars.Json.name){ + if (scalarName == ExtendedScalars.Json.name) { return ExtendedScalars.Json } return GraphQLScalarType From 55d6896fdd169ebe820ed118e8f5d8e2c1cd4664 Mon Sep 17 00:00:00 2001 From: sbarker2 <109047597+sbarker2@users.noreply.github.com> Date: Tue, 10 Oct 2023 10:04:43 +1300 Subject: [PATCH 15/15] Update lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt Co-authored-by: Franklin Wang <9077461+gnawf@users.noreply.github.com> --- .../graphql/nadel/validation/NadelSchemaValidationError.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt b/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt index 102fb5953..84e2694d9 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt @@ -418,8 +418,8 @@ sealed interface NadelSchemaValidationError { } data class NoSourceArgsInBatchHydration( - val parentType: NadelServiceSchemaElement, - val overallField: GraphQLFieldDefinition, + val parentType: NadelServiceSchemaElement, + val overallField: GraphQLFieldDefinition, ) : NadelSchemaValidationError { val service: Service get() = parentType.service