diff --git a/lib/src/main/java/graphql/nadel/dsl/RemoteArgumentSource.kt b/lib/src/main/java/graphql/nadel/dsl/RemoteArgumentSource.kt index 60d54b73c..7f295e2b9 100644 --- a/lib/src/main/java/graphql/nadel/dsl/RemoteArgumentSource.kt +++ b/lib/src/main/java/graphql/nadel/dsl/RemoteArgumentSource.kt @@ -1,17 +1,17 @@ package graphql.nadel.dsl -import graphql.language.Value +import graphql.nadel.util.AnyAstValue -// todo this should be a union or sealed class thing -data class RemoteArgumentSource( - val argumentName: String?, // for OBJECT_FIELD - val pathToField: List?, - val staticValue: Value<*>?, - val sourceType: SourceType, -) { - enum class SourceType { - ObjectField, - FieldArgument, - StaticArgument - } +sealed class RemoteArgumentSource { + data class ObjectField( + val pathToField: List, + ) : RemoteArgumentSource() + + data class FieldArgument( + val argumentName: String, + ) : RemoteArgumentSource() + + data class StaticArgument( + val staticValue: AnyAstValue, + ) : RemoteArgumentSource() } 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 9c45cbdd3..9aa521a0e 100644 --- a/lib/src/main/java/graphql/nadel/engine/blueprint/NadelExecutionBlueprintFactory.kt +++ b/lib/src/main/java/graphql/nadel/engine/blueprint/NadelExecutionBlueprintFactory.kt @@ -10,9 +10,7 @@ import graphql.language.FieldDefinition import graphql.language.ImplementingTypeDefinition 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.RemoteArgumentSource import graphql.nadel.dsl.TypeMappingDefinition import graphql.nadel.dsl.UnderlyingServiceHydration import graphql.nadel.engine.blueprint.hydration.NadelBatchHydrationMatchStrategy @@ -485,9 +483,9 @@ private class Factory( actorFieldDef: GraphQLFieldDefinition, ): List { return hydration.arguments.map { remoteArgDef -> - val valueSource = when (val argSourceType = remoteArgDef.remoteArgumentSource.sourceType) { - FieldArgument -> { - val argumentName = remoteArgDef.remoteArgumentSource.argumentName!! + val valueSource = when (val argSourceType = remoteArgDef.remoteArgumentSource) { + is RemoteArgumentSource.FieldArgument -> { + val argumentName = argSourceType.argumentName val argumentDef = hydratedFieldDef.getArgument(argumentName) ?: error("No argument '$argumentName' on field ${hydratedFieldParentType.name}.${hydratedFieldDef.name}") val defaultValue = if (argumentDef.argumentDefaultValue.isLiteral) { @@ -500,13 +498,13 @@ private class Factory( } NadelHydrationActorInputDef.ValueSource.ArgumentValue( - argumentName = argumentName, + argumentName = argSourceType.argumentName, argumentDefinition = argumentDef, defaultValue = defaultValue, ) } - ObjectField -> { - val pathToField = remoteArgDef.remoteArgumentSource.pathToField!! + is RemoteArgumentSource.ObjectField -> { + val pathToField = argSourceType.pathToField FieldResultValue( queryPathToField = NadelQueryPath(pathToField), fieldDefinition = getUnderlyingType(hydratedFieldParentType) @@ -514,9 +512,9 @@ private class Factory( ?: error("No field defined at: ${hydratedFieldParentType.name}.${pathToField.joinToString(".")}"), ) } - StaticArgument -> { + is RemoteArgumentSource.StaticArgument -> { NadelHydrationActorInputDef.ValueSource.StaticValue( - value = remoteArgDef.remoteArgumentSource.staticValue!! + value = argSourceType.staticValue, ) } } diff --git a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt index 90a4e02a1..f132c3729 100644 --- a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt +++ b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt @@ -9,11 +9,11 @@ import graphql.language.InputObjectTypeDefinition import graphql.language.ObjectValue import graphql.language.SDLDefinition import graphql.language.StringValue +import graphql.language.TypeDefinition import graphql.language.Value import graphql.nadel.dsl.FieldMappingDefinition import graphql.nadel.dsl.RemoteArgumentDefinition import graphql.nadel.dsl.RemoteArgumentSource -import graphql.nadel.dsl.RemoteArgumentSource.SourceType import graphql.nadel.dsl.TypeMappingDefinition import graphql.nadel.dsl.UnderlyingServiceHydration import graphql.nadel.dsl.WhenConditionDefinition @@ -328,37 +328,20 @@ object NadelDirectives { } private fun createRemoteArgumentSource(value: Value<*>): RemoteArgumentSource { - if (value is StringValue) { - val values = listFromDottedString(value.value) - return when (values.first()) { - "\$source" -> RemoteArgumentSource( - argumentName = null, + return if (value is StringValue) { + val values = value.value.split('.') + + when (values.first()) { + "\$source" -> RemoteArgumentSource.ObjectField( pathToField = values.subList(1, values.size), - staticValue = null, - sourceType = SourceType.ObjectField, ) - - "\$argument" -> RemoteArgumentSource( + "\$argument" -> RemoteArgumentSource.FieldArgument( 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 -> RemoteArgumentSource.StaticArgument(staticValue = value) } } else { - return RemoteArgumentSource( - argumentName = null, - pathToField = null, - staticValue = value, - sourceType = SourceType.StaticArgument, - ) + RemoteArgumentSource.StaticArgument(staticValue = value) } } @@ -380,37 +363,20 @@ object NadelDirectives { val remoteArgumentSource = if (remoteArgFieldValue != null && remoteArgArgValue != null) { throw IllegalArgumentException("$inputObjectTypeName can not have both $valueFromFieldKey and $valueFromArgKey set") - } else if (remoteArgFieldValue != null) { - createTemplatedRemoteArgumentSource(remoteArgFieldValue, SourceType.ObjectField) - } else if (remoteArgArgValue != null) { - createTemplatedRemoteArgumentSource(remoteArgArgValue, SourceType.FieldArgument) } else { - throw IllegalArgumentException("$inputObjectTypeName requires one of $valueFromFieldKey or $valueFromArgKey to be set") + if (remoteArgFieldValue != null) { + RemoteArgumentSource.ObjectField(remoteArgFieldValue.removePrefix("\$source.").split('.')) + } else if (remoteArgArgValue != null) { + RemoteArgumentSource.FieldArgument(remoteArgArgValue.removePrefix("\$argument.")) + } else { + throw IllegalArgumentException("$inputObjectTypeName requires one of $valueFromFieldKey or $valueFromArgKey to be set") + } } RemoteArgumentDefinition(remoteArgName, remoteArgumentSource) } } - private fun createTemplatedRemoteArgumentSource(value: String, argumentType: SourceType): RemoteArgumentSource { - // for backwards compat reasons - we will allow them to specify "$source.field.name" and treat it as just "field.name" - val values = value - .removePrefix("\$source.") - .removePrefix("\$argument.") - .split('.') - - var argumentName: String? = null - var path: List? = null - var staticValue: Value<*>? = null - when (argumentType) { - SourceType.ObjectField -> path = values - SourceType.FieldArgument -> argumentName = values.single() - SourceType.StaticArgument -> staticValue = StringValue(value) - } - - return RemoteArgumentSource(argumentName, path, staticValue, argumentType) - } - fun createFieldMapping(fieldDefinition: GraphQLFieldDefinition): FieldMappingDefinition? { val directive = fieldDefinition.getAppliedDirective(renamedDirectiveDefinition.name) ?: return null @@ -427,10 +393,6 @@ object NadelDirectives { return TypeMappingDefinition(underlyingName = from, overallName = directivesContainer.name) } - private fun listFromDottedString(from: String): List { - return from.split('.').toList() - } - private inline fun getDirectiveValue( directive: GraphQLAppliedDirective, name: String, diff --git a/lib/src/main/java/graphql/nadel/validation/NadelHydrationArgumentValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelHydrationArgumentValidation.kt index 69af1cac3..9291386da 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelHydrationArgumentValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelHydrationArgumentValidation.kt @@ -2,17 +2,23 @@ package graphql.nadel.validation import graphql.Scalars import graphql.nadel.dsl.RemoteArgumentDefinition -import graphql.nadel.dsl.RemoteArgumentSource.SourceType.ObjectField -import graphql.nadel.dsl.RemoteArgumentSource.SourceType.FieldArgument +import graphql.nadel.dsl.RemoteArgumentSource import graphql.nadel.dsl.UnderlyingServiceHydration import graphql.nadel.engine.util.isList import graphql.nadel.engine.util.isNonNull import graphql.nadel.engine.util.unwrapNonNull import graphql.nadel.engine.util.unwrapOne import graphql.scalars.ExtendedScalars -import graphql.schema.* +import graphql.schema.GraphQLEnumType +import graphql.schema.GraphQLFieldDefinition +import graphql.schema.GraphQLInputObjectType +import graphql.schema.GraphQLInputType +import graphql.schema.GraphQLNamedInputType +import graphql.schema.GraphQLObjectType +import graphql.schema.GraphQLScalarType +import graphql.schema.GraphQLType -internal class NadelHydrationArgumentValidation() { +internal class NadelHydrationArgumentValidation { fun validateHydrationInputArg( hydrationSourceType: GraphQLType, actorFieldArgType: GraphQLInputType, @@ -28,7 +34,7 @@ internal class NadelHydrationArgumentValidation() { //could have ID feed into [ID] (as a possible batch hydration case). // in this case we need to unwrap the list and check types - if (isBatchHydration && (!unwrappedHydrationSourceType.isList && unwrappedActorFieldArgType.isList)) { + return if (isBatchHydration && (!unwrappedHydrationSourceType.isList && unwrappedActorFieldArgType.isList)) { val error = getHydrationInputErrors( unwrappedHydrationSourceType, unwrappedActorFieldArgType.unwrapOne(), @@ -38,8 +44,9 @@ internal class NadelHydrationArgumentValidation() { hydration, actorFieldName ) + if (error != null) { - return NadelSchemaValidationError.IncompatibleHydrationArgumentType( + NadelSchemaValidationError.IncompatibleHydrationArgumentType( parent, overallField, remoteArg, @@ -47,6 +54,8 @@ internal class NadelHydrationArgumentValidation() { actorFieldArgType, actorFieldName ) + } else { + null } } //could have [ID] feed into ID (non-batch, ManyToOne case explained in NadelHydrationStrategy.kt) @@ -62,7 +71,7 @@ internal class NadelHydrationArgumentValidation() { actorFieldName ) if (error != null) { - return NadelSchemaValidationError.IncompatibleHydrationArgumentType( + NadelSchemaValidationError.IncompatibleHydrationArgumentType( parent, overallField, remoteArg, @@ -70,6 +79,8 @@ internal class NadelHydrationArgumentValidation() { actorFieldArgType, actorFieldName ) + } else { + null } } // Otherwise we can just check the types normally @@ -84,7 +95,6 @@ internal class NadelHydrationArgumentValidation() { actorFieldName ) } - return null } private fun getHydrationInputErrors( @@ -96,10 +106,9 @@ internal class NadelHydrationArgumentValidation() { hydration: UnderlyingServiceHydration, actorFieldName: String, ): NadelSchemaValidationError? { - - //need to check null compatibility - val sourceType = remoteArg.remoteArgumentSource.sourceType - if (sourceType != ObjectField && actorFieldArgType.isNonNull && !hydrationSourceType.isNonNull) { + // need to check null compatibility + val remoteArgumentSource = remoteArg.remoteArgumentSource + if (remoteArgumentSource !is RemoteArgumentSource.ObjectField && actorFieldArgType.isNonNull && !hydrationSourceType.isNonNull) { // source must be at least as strict as field argument return NadelSchemaValidationError.IncompatibleHydrationArgumentType( parent, @@ -136,7 +145,7 @@ internal class NadelHydrationArgumentValidation() { ) } // object feed into inputObject (i.e. hydrating with a $source object) - else if (sourceType == ObjectField && unwrappedHydrationSourceType is GraphQLObjectType && unwrappedActorFieldArgType is GraphQLInputObjectType) { + else if (remoteArgumentSource is RemoteArgumentSource.ObjectField && unwrappedHydrationSourceType is GraphQLObjectType && unwrappedActorFieldArgType is GraphQLInputObjectType) { validateInputObjectArg( unwrappedHydrationSourceType, unwrappedActorFieldArgType, @@ -148,7 +157,7 @@ internal class NadelHydrationArgumentValidation() { ) } // inputObject feed into inputObject (i.e. hydrating with an $argument object) - else if (sourceType == FieldArgument && unwrappedHydrationSourceType is GraphQLInputObjectType && unwrappedActorFieldArgType is GraphQLInputObjectType) { + else if (remoteArgumentSource is RemoteArgumentSource.FieldArgument && unwrappedHydrationSourceType is GraphQLInputObjectType && unwrappedActorFieldArgType is GraphQLInputObjectType) { if (unwrappedHydrationSourceType.name != unwrappedActorFieldArgType.name) { NadelSchemaValidationError.IncompatibleHydrationArgumentType( parent, @@ -200,11 +209,12 @@ internal class NadelHydrationArgumentValidation() { ): NadelSchemaValidationError? { val unwrappedHydrationSourceType = hydrationSourceType.unwrapNonNull() val unwrappedActorFieldArgType = actorFieldArgType.unwrapNonNull() - if (unwrappedHydrationSourceType is GraphQLScalarType && unwrappedActorFieldArgType is GraphQLScalarType) { + + return if (unwrappedHydrationSourceType is GraphQLScalarType && unwrappedActorFieldArgType is GraphQLScalarType) { if (isScalarAssignable(unwrappedHydrationSourceType, unwrappedActorFieldArgType)) { - return null + null } else { - return NadelSchemaValidationError.IncompatibleHydrationArgumentType( + NadelSchemaValidationError.IncompatibleHydrationArgumentType( parent, overallField, remoteArg, @@ -213,8 +223,9 @@ internal class NadelHydrationArgumentValidation() { actorFieldName ) } + } else { + null } - return null } private fun isScalarAssignable(typeToAssign: GraphQLNamedInputType, targetType: GraphQLNamedInputType): Boolean { @@ -242,8 +253,8 @@ internal class NadelHydrationArgumentValidation() { hydration: UnderlyingServiceHydration, actorFieldName: String, ): NadelSchemaValidationError? { - var hydrationSourceFieldInnerType: GraphQLType = hydrationSourceType.unwrapNonNull().unwrapOne() - var actorFieldArgInnerType: GraphQLType = actorFieldArgType.unwrapNonNull().unwrapOne() + val hydrationSourceFieldInnerType: GraphQLType = hydrationSourceType.unwrapNonNull().unwrapOne() + val actorFieldArgInnerType: GraphQLType = actorFieldArgType.unwrapNonNull().unwrapOne() val errorExists = getHydrationInputErrors( hydrationSourceFieldInnerType, actorFieldArgInnerType, @@ -311,4 +322,4 @@ internal class NadelHydrationArgumentValidation() { } return null } -} \ No newline at end of file +} diff --git a/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt index 37d1560fe..109fcfdee 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt @@ -3,9 +3,7 @@ package graphql.nadel.validation import graphql.GraphQLContext import graphql.nadel.Service import graphql.nadel.dsl.RemoteArgumentDefinition -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.RemoteArgumentSource import graphql.nadel.dsl.UnderlyingServiceHydration import graphql.nadel.engine.util.getFieldAt import graphql.nadel.engine.util.isList @@ -105,30 +103,28 @@ internal class NadelHydrationValidation( hydrations: List, ): List { if (hydrations.size > 1) { - val anyListSourceInputField = hydrations - .any { hydration -> - val parentType = parent.underlying as GraphQLFieldsContainer + val sourceFields = hydrations + .flatMap { hydration -> hydration .arguments .asSequence() - .mapNotNull { argument -> - argument.remoteArgumentSource.pathToField - } - .any { path -> - parentType.getFieldAt(path)?.type?.unwrapNonNull()?.isList == true - } + .map { it.remoteArgumentSource } + .filterIsInstance() + } + + val parentType = parent.underlying as GraphQLFieldsContainer + val anyListSourceInputField = sourceFields + .any { argumentSource -> + parentType.getFieldAt(argumentSource.pathToField)?.type?.unwrapNonNull()?.isList == true } if (anyListSourceInputField) { - val sourceFields = hydrations - .flatMapTo(LinkedHashSet()) { hydration -> - hydration.arguments - .mapNotNull { argument -> - argument.remoteArgumentSource.pathToField - } + val uniqueSourceFieldPaths = sourceFields + .mapTo(LinkedHashSet()) { argumentSource -> + argumentSource.pathToField } - if (sourceFields.size > 1) { + if (uniqueSourceFieldPaths.size > 1) { return listOf( NadelSchemaValidationError.MultipleHydrationSourceInputFields(parent, overallField), ) @@ -264,7 +260,8 @@ internal class NadelHydrationValidation( val isBatchHydration = actorField.type.unwrapNonNull().isList val batchHydrationArgumentErrors: List = when { isBatchHydration -> { - val numberOfSourceArgs = hydration.arguments.count { it.remoteArgumentSource.sourceType == ObjectField } + val numberOfSourceArgs = + hydration.arguments.count { it.remoteArgumentSource is RemoteArgumentSource.ObjectField } when { numberOfSourceArgs > 1 -> listOf(MultipleSourceArgsInBatchHydration(parent, overallField)) @@ -291,9 +288,9 @@ internal class NadelHydrationValidation( val remoteArgSource = remoteArgDef.remoteArgumentSource val actorFieldArg = actorField.getArgument(remoteArgDef.name) val isBatchHydration = actorField.type.unwrapNonNull().isList - return when (remoteArgSource.sourceType) { - ObjectField -> { - val field = (parent.underlying as GraphQLFieldsContainer).getFieldAt(remoteArgSource.pathToField!!) + return when (remoteArgSource) { + is RemoteArgumentSource.ObjectField -> { + val field = (parent.underlying as GraphQLFieldsContainer).getFieldAt(remoteArgSource.pathToField) if (field == null) { return listOf( MissingHydrationFieldValueSource(parent, overallField, remoteArgSource) @@ -318,13 +315,12 @@ internal class NadelHydrationValidation( ) } } - - FieldArgument -> { - val argument = overallField.getArgument(remoteArgSource.argumentName!!) + is RemoteArgumentSource.FieldArgument -> { + val argument = overallField.getArgument(remoteArgSource.argumentName) if (argument == null) { return listOf(MissingHydrationArgumentValueSource(parent, overallField, remoteArgSource)) } else { - //check the input types match with hydration and actor fields + // Check the input types match with hydration and actor fields val hydrationArgType = argument.type return listOfNotNull( nadelHydrationArgumentValidation.validateHydrationInputArg( @@ -340,8 +336,7 @@ internal class NadelHydrationValidation( ) } } - - StaticArgument -> { + is RemoteArgumentSource.StaticArgument -> { val staticArg = remoteArgSource.staticValue if ( !validationUtil.isValidLiteralValue( diff --git a/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt b/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt index 6d5b1e230..394a13f1e 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt @@ -8,7 +8,6 @@ import graphql.nadel.Service import graphql.nadel.dsl.FieldMappingDefinition import graphql.nadel.dsl.RemoteArgumentDefinition import graphql.nadel.dsl.RemoteArgumentSource -import graphql.nadel.dsl.RemoteArgumentSource.SourceType.ObjectField import graphql.nadel.dsl.UnderlyingServiceHydration import graphql.nadel.engine.util.makeFieldCoordinates import graphql.nadel.engine.util.unwrapAll @@ -307,13 +306,13 @@ sealed interface NadelSchemaValidationError { data class MissingHydrationFieldValueSource( val parentType: NadelServiceSchemaElement, val overallField: GraphQLFieldDefinition, - val remoteArgSource: RemoteArgumentSource, + val remoteArgSource: RemoteArgumentSource.ObjectField, ) : NadelSchemaValidationError { val service: Service get() = parentType.service override val message = run { val of = makeFieldCoordinates(parentType.overall.name, overallField.name) - val uf = "${parentType.underlying.name}.${remoteArgSource.pathToField?.joinToString(separator = ".")}" + val uf = "${parentType.underlying.name}.${remoteArgSource.pathToField.joinToString(separator = ".")}" val s = service.name "Field $of tried to hydrate using value of non-existent underlying field $uf from service $s as an argument" } @@ -324,7 +323,7 @@ sealed interface NadelSchemaValidationError { data class MissingHydrationArgumentValueSource( val parentType: NadelServiceSchemaElement, val overallField: GraphQLFieldDefinition, - val remoteArgSource: RemoteArgumentSource, + val remoteArgSource: RemoteArgumentSource.FieldArgument, ) : NadelSchemaValidationError { val service: Service get() = parentType.service @@ -366,21 +365,9 @@ sealed interface NadelSchemaValidationError { val service: Service get() = parentType.service override val message = run { - val hydrationArgName = remoteArg.name - val of = makeFieldCoordinates(parentType.overall.name, overallField.name) - val remoteArgSource = - "${parentType.underlying.name}.${remoteArg.remoteArgumentSource.pathToField?.joinToString(separator = ".")}" - val s = service.name val ht = GraphQLTypeUtil.simplePrint(hydrationType) val at = GraphQLTypeUtil.simplePrint(actorArgInputType) - - val argumentSuppliedFromSubString = if (remoteArg.remoteArgumentSource.sourceType == ObjectField) - "the value from field \"$remoteArgSource\" from service \"$s\"" - else "a supplied argument called \"${remoteArg.remoteArgumentSource.argumentName}\"" - - "Field \"$of\" tried to hydrate using the actor field \"$actorFieldName\" and argument \"$hydrationArgName\"." + - " However, you are supplying actor field argument with $argumentSuppliedFromSubString " + - "of type $ht which is not assignable to the expected type $at" + "Hydration cannot assign type $ht to $at on argument ${remoteArg.name}" } override val subject = overallField @@ -501,8 +488,8 @@ sealed interface NadelSchemaValidationError { override val message = run { val hydrationArgName = remoteArg.name val of = makeFieldCoordinates(parentType.overall.name, overallField.name) - val remoteArgSource = - "${parentType.underlying.name}.${remoteArg.remoteArgumentSource.pathToField?.joinToString(separator = ".")}" + val pathToField = (remoteArg.remoteArgumentSource as RemoteArgumentSource.ObjectField).pathToField + val remoteArgSource = "${parentType.underlying.name}.${pathToField.joinToString(separator = ".")}" "Field \"$of\" tried to hydrate using the actor field \"$actorFieldName\" and argument \"$hydrationArgName\"." + " However, you are supplying actor field argument with the value from $remoteArgSource " + @@ -524,8 +511,8 @@ sealed interface NadelSchemaValidationError { override val message = run { val of = makeFieldCoordinates(parentType.overall.name, overallField.name) val hydrationArgName = remoteArg.name - val remoteArgSource = - "${parentType.underlying.name}.${remoteArg.remoteArgumentSource.pathToField?.joinToString(separator = ".")}" + val pathToField = (remoteArg.remoteArgumentSource as RemoteArgumentSource.ObjectField).pathToField + val remoteArgSource = "${parentType.underlying.name}.${pathToField.joinToString(separator = ".")}" val s = service.name "Field $of tried to hydrate using field \"$actorFieldName\" with argument \"$hydrationArgName\" using value from $remoteArgSource in service $s" + " but it was missing the required field $missingFieldName" diff --git a/lib/src/test/kotlin/graphql/nadel/schema/NadelDirectivesTest.kt b/lib/src/test/kotlin/graphql/nadel/schema/NadelDirectivesTest.kt index d8913d94e..e80899552 100644 --- a/lib/src/test/kotlin/graphql/nadel/schema/NadelDirectivesTest.kt +++ b/lib/src/test/kotlin/graphql/nadel/schema/NadelDirectivesTest.kt @@ -13,13 +13,13 @@ import graphql.nadel.schema.NadelDirectives.nadelWhenConditionDefinition import graphql.nadel.schema.NadelDirectives.nadelWhenConditionPredicateDefinition import graphql.nadel.schema.NadelDirectives.nadelWhenConditionResultDefinition import graphql.schema.GraphQLSchema -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 import io.kotest.datatest.withData import org.junit.jupiter.api.assertThrows +import kotlin.test.assertTrue private const val source = "$" + "source" private const val argument = "$" + "argument" @@ -41,15 +41,20 @@ class NadelDirectivesTest : DescribeSpec({ fun getSchema(schemaText: String): GraphQLSchema { val typeDefs = SchemaParser().parse(commonDefs + "\n" + schemaText) - return SchemaGenerator().makeExecutableSchema(typeDefs, RuntimeWiring - .newRuntimeWiring() - .wiringFactory(NeverWiringFactory()).build()) + return SchemaGenerator().makeExecutableSchema( + typeDefs, + RuntimeWiring + .newRuntimeWiring() + .wiringFactory(NeverWiringFactory()) + .build(), + ) } describe("@hydrated") { it("can parse") { // given - val schema = getSchema(""" + val schema = getSchema( + """ type Query { field: String @hydrated( @@ -69,7 +74,8 @@ class NadelDirectivesTest : DescribeSpec({ } ) } - """.trimIndent()) + """.trimIndent() + ) val field = schema.queryType.getField("field") @@ -83,19 +89,22 @@ class NadelDirectivesTest : DescribeSpec({ assert(hydration.timeout == 100) assert(hydration.arguments.size == 2) - assert(hydration.arguments[0].name == "fieldVal") - assert(hydration.arguments[0].remoteArgumentSource.sourceType == RemoteArgumentSource.SourceType.ObjectField) - assert(hydration.arguments[0].remoteArgumentSource.pathToField == listOf("namespace", "issueId")) + assertTrue(hydration.arguments[0].name == "fieldVal") + val firstArgumentSource = hydration.arguments[0].remoteArgumentSource + assertTrue(firstArgumentSource is RemoteArgumentSource.ObjectField) + assertTrue(firstArgumentSource.pathToField == listOf("namespace", "issueId")) - assert(hydration.arguments[1].name == "argVal") - assert(hydration.arguments[1].remoteArgumentSource.sourceType == RemoteArgumentSource.SourceType.FieldArgument) - assert(hydration.arguments[1].remoteArgumentSource.argumentName == "cloudId") + assertTrue(hydration.arguments[1].name == "argVal") + val secondArgumentSource = hydration.arguments[1].remoteArgumentSource + assertTrue(secondArgumentSource is RemoteArgumentSource.FieldArgument) + assertTrue(secondArgumentSource.argumentName == "cloudId") } } describe("@hydratedFrom") { it("can parse") { - val schema = getSchema(""" + val schema = getSchema( + """ extend enum NadelHydrationTemplate { JIRA @hydratedTemplate( service: "IssueService" @@ -117,7 +126,8 @@ class NadelDirectivesTest : DescribeSpec({ ] ) } - """.trimIndent()) + """.trimIndent() + ) val fieldDef = schema.queryType.getFieldDefinition("field") @@ -130,22 +140,27 @@ class NadelDirectivesTest : DescribeSpec({ assert(hydration.batchSize == 50) assert(hydration.timeout == 100) assert(hydration.arguments.size == 4) - - assert(hydration.arguments[0].name == "fieldVal") - assert(hydration.arguments[0].remoteArgumentSource.sourceType == RemoteArgumentSource.SourceType.ObjectField) - assert(hydration.arguments[0].remoteArgumentSource.pathToField == listOf("namespace", "issueId")) - - assert(hydration.arguments[1].name == "argVal") - assert(hydration.arguments[1].remoteArgumentSource.sourceType == RemoteArgumentSource.SourceType.FieldArgument) - assert(hydration.arguments[1].remoteArgumentSource.argumentName == "cloudId") - - assert(hydration.arguments[2].name == "fieldValLegacy") - assert(hydration.arguments[2].remoteArgumentSource.sourceType == RemoteArgumentSource.SourceType.ObjectField) - assert(hydration.arguments[2].remoteArgumentSource.pathToField == listOf("namespace", "issueId")) - - assert(hydration.arguments[3].name == "argValLegacy") - assert(hydration.arguments[3].remoteArgumentSource.sourceType == RemoteArgumentSource.SourceType.FieldArgument) - assert(hydration.arguments[3].remoteArgumentSource.argumentName == "cloudId") + val (argumentOne, argumentTwo, argumentThree, argumentFour) = hydration.arguments + + assertTrue(argumentOne.name == "fieldVal") + val remoteArgumentSourceOne = argumentOne.remoteArgumentSource + assertTrue(remoteArgumentSourceOne is RemoteArgumentSource.ObjectField) + assertTrue(remoteArgumentSourceOne.pathToField == listOf("namespace", "issueId")) + + assertTrue(argumentTwo.name == "argVal") + val remoteArgumentSourceTwo = argumentTwo.remoteArgumentSource + assertTrue(remoteArgumentSourceTwo is RemoteArgumentSource.FieldArgument) + assertTrue(remoteArgumentSourceTwo.argumentName == "cloudId") + + assertTrue(argumentThree.name == "fieldValLegacy") + val remoteArgumentSourceThree = argumentThree.remoteArgumentSource + assertTrue(remoteArgumentSourceThree is RemoteArgumentSource.ObjectField) + assertTrue(remoteArgumentSourceThree.pathToField == listOf("namespace", "issueId")) + + assertTrue(argumentFour.name == "argValLegacy") + val remoteArgumentSourceFour = argumentFour.remoteArgumentSource + assertTrue(remoteArgumentSourceFour is RemoteArgumentSource.FieldArgument) + assertTrue(remoteArgumentSourceFour.argumentName == "cloudId") } context("throws exception if valueFromField or valueFromArg are both specified or if neither are specified") { @@ -164,7 +179,8 @@ class NadelDirectivesTest : DescribeSpec({ ), ) { (arguments, error) -> // given - val schema = getSchema(""" + val schema = getSchema( + """ extend enum NadelHydrationTemplate { JIRA @hydratedTemplate( service: "IssueService" @@ -180,7 +196,8 @@ class NadelDirectivesTest : DescribeSpec({ arguments: [$arguments] ) } - """.trimIndent()) + """.trimIndent() + ) val fieldDef = schema.queryType.getField("field") // when diff --git a/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationArgumentValidationTest.kt b/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationArgumentValidationTest.kt index 473473ca4..caee43f38 100644 --- a/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationArgumentValidationTest.kt +++ b/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationArgumentValidationTest.kt @@ -1,5 +1,6 @@ package graphql.nadel.validation +import graphql.nadel.dsl.RemoteArgumentSource import graphql.nadel.validation.NadelSchemaValidationError.IncompatibleFieldInHydratedInputObject import graphql.nadel.validation.NadelSchemaValidationError.IncompatibleHydrationArgumentType import graphql.nadel.validation.NadelSchemaValidationError.MissingFieldInHydratedInputObject @@ -80,7 +81,8 @@ class NadelHydrationArgumentValidationTest : DescribeSpec({ assert(GraphQLTypeUtil.simplePrint(error.actorArgInputType) == "Int") // supplied hydration for arg: assert(error.parentType.underlying.name == "Issue") - assert(error.remoteArg.remoteArgumentSource.pathToField?.joinToString(separator = ".") == "creator") + val remoteArgumentSource = error.remoteArg.remoteArgumentSource as RemoteArgumentSource.ObjectField + assert(remoteArgumentSource.pathToField.joinToString(separator = ".") == "creator") assert(GraphQLTypeUtil.simplePrint(error.hydrationType) == "ID!") } @@ -368,8 +370,8 @@ class NadelHydrationArgumentValidationTest : DescribeSpec({ assert(error.remoteArg.name == "id") assert(GraphQLTypeUtil.simplePrint(error.actorArgInputType) == "ID!") // supplied hydration for arg: - error.remoteArg.remoteArgumentSource.argumentName - assert(error.remoteArg.remoteArgumentSource.argumentName == "creatorId") + val remoteArgumentSource = error.remoteArg.remoteArgumentSource as RemoteArgumentSource.FieldArgument + assert(remoteArgumentSource.argumentName == "creatorId") assert(GraphQLTypeUtil.simplePrint(error.hydrationType) == "ID") } @@ -549,7 +551,8 @@ class NadelHydrationArgumentValidationTest : DescribeSpec({ assert(GraphQLTypeUtil.simplePrint(error.actorArgInputType) == "[[String!]!]!") // supplied hydration for arg: assert(error.parentType.underlying.name == "Issue") - assert(error.remoteArg.remoteArgumentSource.pathToField?.joinToString(separator = ".") == "creators") + val remoteArgumentSource = error.remoteArg.remoteArgumentSource as RemoteArgumentSource.ObjectField + assert(remoteArgumentSource.pathToField.joinToString(separator = ".") == "creators") assert(GraphQLTypeUtil.simplePrint(error.hydrationType) == "[[Int!]!]!") } @@ -703,7 +706,8 @@ class NadelHydrationArgumentValidationTest : DescribeSpec({ assert(error.remoteArg.name == "name") // supplied hydration for arg: assert(error.parentType.underlying.name == "Issue") - assert(error.remoteArg.remoteArgumentSource.pathToField?.joinToString(separator = ".") == "creator") + val remoteArgumentSource = error.remoteArg.remoteArgumentSource as RemoteArgumentSource.ObjectField + assert(remoteArgumentSource.pathToField.joinToString(separator = ".") == "creator") } it("input object - validation allows a valid array nested inside object") { @@ -949,7 +953,8 @@ class NadelHydrationArgumentValidationTest : DescribeSpec({ assert(error.remoteArg.name == "userInfo") // supplied hydration for arg: assert(error.parentType.underlying.name == "Issue") - assert(error.remoteArg.remoteArgumentSource.pathToField?.joinToString(separator = ".") == "creator") + val remoteArgumentSource = error.remoteArg.remoteArgumentSource as RemoteArgumentSource.ObjectField + assert(remoteArgumentSource.pathToField.joinToString(separator = ".") == "creator") } @@ -1102,7 +1107,8 @@ class NadelHydrationArgumentValidationTest : DescribeSpec({ assert(GraphQLTypeUtil.simplePrint(error.actorArgInputType) == "[FullNameInput]!") // supplied hydration for arg: assert(error.parentType.underlying.name == "Issue") - assert(error.remoteArg.remoteArgumentSource.pathToField?.joinToString(separator = ".") == "creators") + val remoteArgumentSource = error.remoteArg.remoteArgumentSource as RemoteArgumentSource.ObjectField + assert(remoteArgumentSource.pathToField.joinToString(separator = ".") == "creators") assert(GraphQLTypeUtil.simplePrint(error.hydrationType) == "[FullName]!") } @@ -1256,7 +1262,8 @@ class NadelHydrationArgumentValidationTest : DescribeSpec({ assert(GraphQLTypeUtil.simplePrint(error.actorArgInputType) == "[UserInput]") // supplied hydration for arg: assert(error.parentType.underlying.name == "Issue") - assert(error.remoteArg.remoteArgumentSource.pathToField?.joinToString(separator = ".") == "creators") + val remoteArgumentSource = error.remoteArg.remoteArgumentSource as RemoteArgumentSource.ObjectField + assert(remoteArgumentSource.pathToField.joinToString(separator = ".") == "creators") assert(GraphQLTypeUtil.simplePrint(error.hydrationType) == "[UserRef]") } @@ -1564,7 +1571,8 @@ class NadelHydrationArgumentValidationTest : DescribeSpec({ assert(GraphQLTypeUtil.simplePrint(error.actorArgInputType) == "SomeOtherEnumType") // supplied hydration for arg: assert(error.parentType.underlying.name == "Issue") - assert(error.remoteArg.remoteArgumentSource.pathToField?.joinToString(separator = ".") == "providerType") + val remoteArgumentSource = error.remoteArg.remoteArgumentSource as RemoteArgumentSource.ObjectField + assert(remoteArgumentSource.pathToField.joinToString(separator = ".") == "providerType") assert(GraphQLTypeUtil.simplePrint(error.hydrationType) == "ProviderType") } } @@ -2524,4 +2532,4 @@ class NadelHydrationArgumentValidationTest : DescribeSpec({ assert(GraphQLTypeUtil.simplePrint(error.actorArgInputType) == "[FullNameInput]!") } } -}) \ No newline at end of file +}) diff --git a/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationValidationTest.kt b/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationValidationTest.kt index a1fa2be38..c54da69c9 100644 --- a/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationValidationTest.kt +++ b/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationValidationTest.kt @@ -702,7 +702,6 @@ class NadelHydrationValidationTest : DescribeSpec({ assert(error.parentType.underlying.name == "Issue") assert(error.overallField.name == "creator") assert(error.remoteArgSource.pathToField == listOf("creatorId")) - assert(error.remoteArgSource.argumentName == null) } it("fails if hydration argument references non existent argument") { @@ -766,7 +765,6 @@ class NadelHydrationValidationTest : DescribeSpec({ assert(error.parentType.underlying.name == "Issue") assert(error.overallField.name == "creator") assert(error.remoteArgSource.argumentName == "secrets") - assert(error.remoteArgSource.pathToField == null) } it("fails if hydration argument references non existent remote argument") { @@ -1374,4 +1372,4 @@ class NadelHydrationValidationTest : DescribeSpec({ assert(errors.map { it.message }.isEmpty()) } } -}) \ No newline at end of file +})