From 4205311bfb1901e428987b8189096a817a45c14b Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Mon, 21 Oct 2024 16:43:59 +1100 Subject: [PATCH 1/9] Refactor hydration code --- .../dsl/NadelHydrationConditionDefinition.kt | 39 ++- .../nadel/dsl/NadelHydrationDefinition.kt | 19 -- .../NadelExecutionBlueprintFactory.kt | 85 +++--- .../NadelVirtualTypeBlueprintFactory.kt | 5 +- .../directives/NadelHydrationDefinition.kt | 180 +++++++++++++ .../directives/NadelHydrationDirective.kt | 109 -------- .../directives/NadelVirtualTypeDirective.kt | 4 +- .../graphql/nadel/engine/util/GraphQLUtil.kt | 2 +- .../graphql/nadel/schema/NadelDirectives.kt | 248 +----------------- .../NadelHydrationArgumentValidation.kt | 2 +- .../NadelHydrationConditionValidation.kt | 17 +- .../validation/NadelHydrationValidation.kt | 1 - .../validation/NadelSchemaValidationError.kt | 1 - 13 files changed, 275 insertions(+), 437 deletions(-) delete mode 100644 lib/src/main/java/graphql/nadel/dsl/NadelHydrationDefinition.kt create mode 100644 lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelHydrationDefinition.kt delete mode 100644 lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelHydrationDirective.kt diff --git a/lib/src/main/java/graphql/nadel/dsl/NadelHydrationConditionDefinition.kt b/lib/src/main/java/graphql/nadel/dsl/NadelHydrationConditionDefinition.kt index e8853e322..845ab029b 100644 --- a/lib/src/main/java/graphql/nadel/dsl/NadelHydrationConditionDefinition.kt +++ b/lib/src/main/java/graphql/nadel/dsl/NadelHydrationConditionDefinition.kt @@ -1,8 +1,45 @@ package graphql.nadel.dsl +import graphql.nadel.engine.util.JsonMap + data class NadelHydrationConditionDefinition( val result: NadelHydrationResultConditionDefinition, -) +) { + companion object { + internal fun from( + conditionObject: JsonMap, + ): NadelHydrationConditionDefinition? { + @Suppress("UNCHECKED_CAST") + val result = conditionObject[Keyword.result] as Map? + ?: return null + + val sourceField = result[Keyword.sourceField]!! as String + + @Suppress("UNCHECKED_CAST") + val predicate = result[Keyword.predicate]!! as Map + + return NadelHydrationConditionDefinition( + result = NadelHydrationResultConditionDefinition( + pathToSourceField = sourceField.split("."), + predicate = NadelHydrationConditionPredicateDefinition( + equals = predicate[Keyword.equals], + startsWith = predicate[Keyword.startsWith] as String?, + matches = predicate[Keyword.matches] as String?, + ), + ), + ) + } + } + + object Keyword { + const val result = "result" + const val sourceField = "sourceField" + const val predicate = "predicate" + const val equals = "equals" + const val startsWith = "startsWith" + const val matches = "matches" + } +} data class NadelHydrationResultConditionDefinition( val pathToSourceField: List, diff --git a/lib/src/main/java/graphql/nadel/dsl/NadelHydrationDefinition.kt b/lib/src/main/java/graphql/nadel/dsl/NadelHydrationDefinition.kt deleted file mode 100644 index 2ada11b90..000000000 --- a/lib/src/main/java/graphql/nadel/dsl/NadelHydrationDefinition.kt +++ /dev/null @@ -1,19 +0,0 @@ -package graphql.nadel.dsl - -data class NadelHydrationDefinition( - val serviceName: String, - val pathToActorField: List, - val arguments: List, - val objectIdentifier: String?, - val objectIdentifiers: List?, - val isObjectMatchByIndex: Boolean, - val isBatched: Boolean, - val batchSize: Int, - val timeout: Int, - val condition: NadelHydrationResultConditionDefinition? -) { - data class ObjectIdentifier( - val sourceId: String, - val resultId: String, - ) -} 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 39de73574..5c48db489 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,10 @@ import graphql.language.FieldDefinition import graphql.language.ImplementingTypeDefinition import graphql.nadel.Service import graphql.nadel.dsl.FieldMappingDefinition -import graphql.nadel.dsl.NadelHydrationDefinition -import graphql.nadel.dsl.RemoteArgumentSource import graphql.nadel.dsl.TypeMappingDefinition +import graphql.nadel.engine.blueprint.directives.NadelHydrationArgumentDefinition +import graphql.nadel.engine.blueprint.directives.NadelHydrationDefinition +import graphql.nadel.engine.blueprint.directives.getHydrationDefinitions import graphql.nadel.engine.blueprint.directives.isVirtualType import graphql.nadel.engine.blueprint.hydration.NadelBatchHydrationMatchStrategy import graphql.nadel.engine.blueprint.hydration.NadelHydrationActorInputDef @@ -190,7 +191,7 @@ private class Factory( .flatMap { field -> when (val mappingDefinition = getFieldMappingDefinition(field)) { null -> { - getUnderlyingServiceHydrations(field) + field.getHydrationDefinitions() .map { makeHydrationFieldInstruction(type, field, it) } + makePartitionInstruction(type, field) } else -> when (mappingDefinition.inputPath.size) { @@ -221,20 +222,20 @@ private class Factory( hydratedFieldDef: GraphQLFieldDefinition, hydration: NadelHydrationDefinition, ): NadelFieldInstruction { - val hydrationActorService = services.single { it.name == hydration.serviceName } - val queryPathToActorField = hydration.pathToActorField - val actorFieldDef = engineSchema.queryType.getFieldAt(queryPathToActorField)!! - val actorFieldContainer = engineSchema.queryType.getFieldContainerAt(queryPathToActorField)!! + val pathToBackingField = hydration.backingField + val backingFieldContainer = engineSchema.queryType.getFieldContainerAt(pathToBackingField)!! + val backingFieldDef = engineSchema.queryType.getFieldAt(pathToBackingField)!! + val hydrationActorService = coordinatesToService[makeFieldCoordinates(backingFieldContainer, backingFieldDef)]!! - if (hydration.isBatched || /*deprecated*/ actorFieldDef.type.unwrapNonNull().isList) { - require(actorFieldDef.type.unwrapNonNull().isList) { "Batched hydration at '$queryPathToActorField' requires a list output type" } + if (hydration.isBatched || /*deprecated*/ backingFieldDef.type.unwrapNonNull().isList) { + require(backingFieldDef.type.unwrapNonNull().isList) { "Batched hydration at '$pathToBackingField' requires a list output type" } return makeBatchHydrationFieldInstruction( parentType = hydratedFieldParentType, hydratedFieldDef = hydratedFieldDef, hydration = hydration, actorService = hydrationActorService, - actorFieldDef = actorFieldDef, - actorFieldContainer = actorFieldContainer + actorFieldDef = backingFieldDef, + actorFieldContainer = backingFieldContainer ) } @@ -244,21 +245,22 @@ private class Factory( hydration = hydration, hydratedFieldParentType = hydratedFieldParentType, hydratedFieldDef = hydratedFieldDef, - actorFieldDef = actorFieldDef, + actorFieldDef = backingFieldDef, ) + return NadelHydrationFieldInstruction( location = makeFieldCoordinates(hydratedFieldParentType, hydratedFieldDef), hydratedFieldDef = hydratedFieldDef, actorService = hydrationActorService, - queryPathToActorField = NadelQueryPath(queryPathToActorField), - actorFieldDef = actorFieldDef, - actorFieldContainer = actorFieldContainer, + queryPathToActorField = NadelQueryPath(pathToBackingField), + actorFieldDef = backingFieldDef, + actorFieldContainer = backingFieldContainer, actorInputValueDefs = hydrationArgs, timeout = hydration.timeout, hydrationStrategy = getHydrationStrategy( hydratedFieldParentType = hydratedFieldParentType, hydratedFieldDef = hydratedFieldDef, - actorFieldDef = actorFieldDef, + actorFieldDef = backingFieldDef, actorInputValueDefs = hydrationArgs, ), virtualTypeContext = virtualTypeBlueprintFactory.makeVirtualTypeContext( @@ -291,34 +293,35 @@ private class Factory( } private fun getHydrationCondition(hydration: NadelHydrationDefinition): NadelHydrationCondition? { - if (hydration.condition == null) { - return null - } - if (hydration.condition.predicate.equals != null) { - return when (val expectedValue = hydration.condition.predicate.equals) { + val resultCondition = hydration.condition?.result + ?: return null + + if (resultCondition.predicate.equals != null) { + return when (val expectedValue = resultCondition.predicate.equals) { is BigInteger -> NadelHydrationCondition.LongResultEquals( - fieldPath = NadelQueryPath(hydration.condition.pathToSourceField), + fieldPath = NadelQueryPath(resultCondition.pathToSourceField), value = expectedValue.longValueExact(), ) is String -> NadelHydrationCondition.StringResultEquals( - fieldPath = NadelQueryPath(hydration.condition.pathToSourceField), + fieldPath = NadelQueryPath(resultCondition.pathToSourceField), value = expectedValue ) else -> error("Unexpected type for equals predicate in conditional hydration") } } - if (hydration.condition.predicate.startsWith != null) { + if (resultCondition.predicate.startsWith != null) { return NadelHydrationCondition.StringResultStartsWith( - fieldPath = NadelQueryPath(hydration.condition.pathToSourceField), - prefix = hydration.condition.predicate.startsWith + fieldPath = NadelQueryPath(resultCondition.pathToSourceField), + prefix = resultCondition.predicate.startsWith ) } - if (hydration.condition.predicate.matches != null) { + if (resultCondition.predicate.matches != null) { return NadelHydrationCondition.StringResultMatches( - fieldPath = NadelQueryPath(hydration.condition.pathToSourceField), - regex = hydration.condition.predicate.matches.toRegex() + fieldPath = NadelQueryPath(resultCondition.pathToSourceField), + regex = resultCondition.predicate.matches.toRegex() ) } + error("A conditional hydration is defined but doesnt have any predicate") } @@ -372,14 +375,14 @@ private class Factory( ): NadelFieldInstruction { val location = makeFieldCoordinates(parentType, hydratedFieldDef) - val batchSize = hydration.batchSize ?: 50 + val batchSize = hydration.batchSize val hydrationArgs = getHydrationArguments(hydration, parentType, hydratedFieldDef, actorFieldDef) - val matchStrategy = if (hydration.isObjectMatchByIndex) { + val matchStrategy = if (hydration.isIndexed) { NadelBatchHydrationMatchStrategy.MatchIndex - } else if (hydration.objectIdentifiers?.isNotEmpty() == true) { + } else if (hydration.inputIdentifiedBy?.isNotEmpty() == true) { NadelBatchHydrationMatchStrategy.MatchObjectIdentifiers( - hydration.objectIdentifiers.map { objectIdentifier -> + hydration.inputIdentifiedBy!!.map { objectIdentifier -> NadelBatchHydrationMatchStrategy.MatchObjectIdentifier( sourceId = NadelQueryPath( objectIdentifier.sourceId.split("."), @@ -396,7 +399,7 @@ private class Factory( .filterIsInstance() .single() .queryPathToField, - resultId = hydration.objectIdentifier!!, + resultId = hydration.identifiedBy!!, ) } @@ -406,7 +409,7 @@ private class Factory( location = location, hydratedFieldDef = hydratedFieldDef, actorService = actorService, - queryPathToActorField = NadelQueryPath(hydration.pathToActorField), + queryPathToActorField = NadelQueryPath(hydration.backingField), actorInputValueDefs = hydrationArgs, timeout = hydration.timeout, batchSize = batchSize, @@ -543,8 +546,8 @@ private class Factory( actorFieldDef: GraphQLFieldDefinition, ): List { return hydration.arguments.map { remoteArgDef -> - val valueSource = when (val argSourceType = remoteArgDef.remoteArgumentSource) { - is RemoteArgumentSource.FieldArgument -> { + val valueSource = when (val argSourceType = remoteArgDef.value) { + is NadelHydrationArgumentDefinition.ValueSource.FieldArgument -> { val argumentName = argSourceType.argumentName val argumentDef = hydratedFieldDef.getArgument(argumentName) ?: error("No argument '$argumentName' on field ${hydratedFieldParentType.name}.${hydratedFieldDef.name}") @@ -563,7 +566,7 @@ private class Factory( defaultValue = defaultValue, ) } - is RemoteArgumentSource.ObjectField -> { + is NadelHydrationArgumentDefinition.ValueSource.ObjectField -> { // Ugh code still uses underlying schema, we need to pull these up to the overall schema val typeToLookAt = if (hydratedFieldParentType.isVirtualType()) { hydratedFieldParentType @@ -579,7 +582,7 @@ private class Factory( ?: error("No field defined at: ${hydratedFieldParentType.name}.${pathToField.joinToString(".")}"), ) } - is RemoteArgumentSource.StaticArgument -> { + is NadelHydrationArgumentDefinition.ValueSource.StaticArgument -> { NadelHydrationActorInputDef.ValueSource.StaticValue( value = argSourceType.staticValue, ) @@ -620,10 +623,6 @@ private class Factory( return NadelDirectives.createFieldMapping(field) } - private fun getUnderlyingServiceHydrations(field: GraphQLFieldDefinition): List { - return NadelDirectives.createUnderlyingServiceHydration(field, engineSchema) - } - private fun deriveUnderlyingBlueprints( typeRenameInstructions: List, ): Map { diff --git a/lib/src/main/java/graphql/nadel/engine/blueprint/NadelVirtualTypeBlueprintFactory.kt b/lib/src/main/java/graphql/nadel/engine/blueprint/NadelVirtualTypeBlueprintFactory.kt index fa6bd5c5a..c07de520a 100644 --- a/lib/src/main/java/graphql/nadel/engine/blueprint/NadelVirtualTypeBlueprintFactory.kt +++ b/lib/src/main/java/graphql/nadel/engine/blueprint/NadelVirtualTypeBlueprintFactory.kt @@ -1,7 +1,6 @@ package graphql.nadel.engine.blueprint -import graphql.nadel.engine.blueprint.directives.getHydratedOrNull -import graphql.nadel.engine.blueprint.directives.isHydration +import graphql.nadel.engine.blueprint.directives.getHydrationDefinitions import graphql.nadel.engine.util.getFieldAt import graphql.nadel.engine.util.unwrapAll import graphql.schema.GraphQLFieldDefinition @@ -50,7 +49,7 @@ internal class NadelVirtualTypeBlueprintFactory { private fun createTypeMappings( virtualFieldDef: GraphQLFieldDefinition, ): List { - val hydration = virtualFieldDef.getHydratedOrNull() + val hydration = virtualFieldDef.getHydrationDefinitions().firstOrNull() ?: return emptyList() // We should use the non-null method once we delete @hydratedFrom val backingFieldDef = engineSchema.queryType.getFieldAt(hydration.backingField)!! val backingType = backingFieldDef.type.unwrapAll() as? GraphQLObjectType diff --git a/lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelHydrationDefinition.kt b/lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelHydrationDefinition.kt new file mode 100644 index 000000000..36e35e08b --- /dev/null +++ b/lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelHydrationDefinition.kt @@ -0,0 +1,180 @@ +package graphql.nadel.engine.blueprint.directives + +import graphql.language.ArrayValue +import graphql.language.ObjectField +import graphql.language.ObjectValue +import graphql.language.StringValue +import graphql.nadel.dsl.NadelHydrationConditionDefinition +import graphql.nadel.engine.blueprint.directives.NadelHydrationDefinition.Keyword +import graphql.nadel.engine.util.JsonMap +import graphql.nadel.util.AnyAstValue +import graphql.schema.GraphQLAppliedDirective +import graphql.schema.GraphQLFieldDefinition + +internal fun GraphQLFieldDefinition.hasHydration(): Boolean { + return hasAppliedDirective(Keyword.hydrated) +} + +internal fun GraphQLFieldDefinition.getHydrationDefinitions(): List { + return getAppliedDirectives(Keyword.hydrated) + .map(::NadelHydrationDefinition) +} + +/** + * ```graphql + * "This allows you to hydrate new values into fields" + * directive @hydrated( + * "The target service" + * service: String! + * "The target top level field" + * field: String! + * "How to identify matching results" + * identifiedBy: String! = "id" + * "How to identify matching results" + * inputIdentifiedBy: [NadelBatchObjectIdentifiedBy!]! = [] + * "Are results indexed" + * indexed: Boolean! = false + * "Is querying batched" + * batched: Boolean! = false + * "The batch size" + * batchSize: Int! = 200 + * "The timeout to use when completing hydration" + * timeout: Int! = -1 + * "The arguments to the hydrated field" + * arguments: [NadelHydrationArgument!]! + * "Specify a condition for the hydration to activate" + * when: NadelHydrationCondition + * ) repeatable on FIELD_DEFINITION + * ``` + */ +internal class NadelHydrationDefinition( + private val appliedDirective: GraphQLAppliedDirective, +) { + val backingField: List + get() = appliedDirective.getArgument(Keyword.field).getValue().split(".") + + val identifiedBy: String? + get() = appliedDirective.getArgument(Keyword.identifiedBy).getValue() + + val isIndexed: Boolean + get() = appliedDirective.getArgument(Keyword.indexed).getValue() + + val isBatched: Boolean + get() = appliedDirective.getArgument(Keyword.batched).getValue() + + val batchSize: Int + get() = appliedDirective.getArgument(Keyword.batchSize).getValue() + + val arguments: List + get() = appliedDirective.getArgument(Keyword.arguments).getValue() + .values + .map { + NadelHydrationArgumentDefinition(it as ObjectValue) + } + + val condition: NadelHydrationConditionDefinition? + get() = appliedDirective.getArgument(Keyword.`when`).getValue() + ?.let { + NadelHydrationConditionDefinition.from(it) + } + + val timeout: Int + get() = appliedDirective.getArgument(Keyword.timeout).getValue() + + val inputIdentifiedBy: List? + get() = appliedDirective.getArgument(Keyword.inputIdentifiedBy).getValue() + .values + .map { + NadelBatchObjectIdentifiedByDefinition(it as ObjectValue) + } + + internal object Keyword { + const val hydrated = "hydrated" + const val field = "field" + const val identifiedBy = "identifiedBy" + const val indexed = "indexed" + const val batched = "batched" + const val batchSize = "batchSize" + const val arguments = "arguments" + const val timeout = "timeout" + const val `when` = "when" + const val inputIdentifiedBy = "inputIdentifiedBy" + } +} + +internal class NadelBatchObjectIdentifiedByDefinition( + private val objectValue: ObjectValue, +) { + val sourceId: String + get() = (objectValue.getObjectField(Keyword.sourceId).value as StringValue).value + val resultId: String + get() = (objectValue.getObjectField(Keyword.resultId).value as StringValue).value + + internal object Keyword { + const val sourceId = "sourceId" + const val resultId = "resultId" + } +} + +/** + * Argument belonging to [NadelHydrationDefinition.arguments] + */ +internal class NadelHydrationArgumentDefinition( + private val argumentObject: ObjectValue, +) { + /** + * Name of the backing field's argument. + */ + val name: String + get() = (argumentObject.getObjectField(Keyword.name).value as StringValue).value + + /** + * Value to support to the backing field's argument at runtime. + */ + val value: ValueSource + get() = ValueSource.from(argumentObject.getObjectField(Keyword.value).value) + + internal object Keyword { + const val name = "name" + const val value = "value" + } + + internal sealed class ValueSource { + data class ObjectField( + val pathToField: List, + ) : ValueSource() + + data class FieldArgument( + val argumentName: String, + ) : ValueSource() + + data class StaticArgument( + val staticValue: AnyAstValue, + ) : ValueSource() + + companion object { + fun from(astValue: AnyAstValue): ValueSource { + return if (astValue is StringValue && astValue.value.startsWith("$")) { + val command = astValue.value.substringBefore(".") + val values = astValue.value.substringAfter(".").split('.') + + when (command) { + "\$source" -> ObjectField( + pathToField = values, + ) + "\$argument" -> FieldArgument( + argumentName = values.single(), + ) + else -> StaticArgument(staticValue = astValue) + } + } else { + StaticArgument(staticValue = astValue) + } + } + } + } +} + +internal fun ObjectValue.getObjectField(name: String): ObjectField { + return objectFields.first { it.name == name } +} diff --git a/lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelHydrationDirective.kt b/lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelHydrationDirective.kt deleted file mode 100644 index daed2779a..000000000 --- a/lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelHydrationDirective.kt +++ /dev/null @@ -1,109 +0,0 @@ -package graphql.nadel.engine.blueprint.directives - -import graphql.language.ArrayValue -import graphql.language.ObjectField -import graphql.language.ObjectValue -import graphql.language.StringValue -import graphql.nadel.util.AnyAstValue -import graphql.schema.GraphQLAppliedDirective -import graphql.schema.GraphQLFieldDefinition - -internal fun GraphQLFieldDefinition.isHydration(): Boolean { - return hasAppliedDirective(NadelHydrationDirective.SyntaxConstant.hydrated) -} - -internal fun GraphQLFieldDefinition.getHydratedOrNull(): NadelHydrationDirective? { - val appliedDirective = getAppliedDirective(NadelHydrationDirective.SyntaxConstant.hydrated) - ?: return null - return NadelHydrationDirective(appliedDirective) -} - -internal fun GraphQLFieldDefinition.getHydrated(): NadelHydrationDirective { - return getHydratedOrNull()!! -} - -/** - * ```graphql - * "This allows you to hydrate new values into fields" - * directive @hydrated( - * "The target service" - * service: String! - * "The target top level field" - * field: String! - * "How to identify matching results" - * identifiedBy: String! = "id" - * "How to identify matching results" - * inputIdentifiedBy: [NadelBatchObjectIdentifiedBy!]! = [] - * "Are results indexed" - * indexed: Boolean! = false - * "Is querying batched" - * batched: Boolean! = false - * "The batch size" - * batchSize: Int! = 200 - * "The timeout to use when completing hydration" - * timeout: Int! = -1 - * "The arguments to the hydrated field" - * arguments: [NadelHydrationArgument!]! - * "Specify a condition for the hydration to activate" - * when: NadelHydrationCondition - * ) repeatable on FIELD_DEFINITION - * ``` - */ -internal class NadelHydrationDirective( - private val appliedDirective: GraphQLAppliedDirective, -) { - val backingField: List - get() = appliedDirective.getArgument(SyntaxConstant.field).getValue().split(".") - - val identifiedBy: String - get() = appliedDirective.getArgument(SyntaxConstant.identifiedBy).getValue() - - val isIndexed: Boolean - get() = appliedDirective.getArgument(SyntaxConstant.indexed).getValue() - - val isBatched: Boolean - get() = appliedDirective.getArgument(SyntaxConstant.batched).getValue() - - val batchSize: Boolean - get() = appliedDirective.getArgument(SyntaxConstant.batchSize).getValue() - - val arguments: List - get() = appliedDirective.getArgument(SyntaxConstant.arguments).getValue() - .values - .map { - NadelHydrationArgumentDefinition(it as ObjectValue) - } - - object SyntaxConstant { - const val hydrated = "hydrated" - const val field = "field" - const val identifiedBy = "identifiedBy" - const val indexed = "indexed" - const val batched = "batched" - const val batchSize = "batchSize" - const val arguments = "arguments" - } -} - -/** - * Argument belonging to [NadelHydrationDirective.arguments] - */ -internal class NadelHydrationArgumentDefinition( - private val argumentObject: ObjectValue, -) { - /** - * Name of the backing field's argument. - */ - val name: String - get() = (argumentObject.getObjectField("name").value as StringValue).value - - /** - * Value to support to the backing field's argument at runtime. - */ - val value: AnyAstValue - get() = argumentObject.getObjectField("name").value -} - -internal fun ObjectValue.getObjectField(name: String): ObjectField { - return objectFields.first { it.name == name } -} diff --git a/lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelVirtualTypeDirective.kt b/lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelVirtualTypeDirective.kt index fbfa50b73..19e8cd9f7 100644 --- a/lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelVirtualTypeDirective.kt +++ b/lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelVirtualTypeDirective.kt @@ -3,11 +3,11 @@ package graphql.nadel.engine.blueprint.directives import graphql.schema.GraphQLDirectiveContainer internal fun GraphQLDirectiveContainer.isVirtualType(): Boolean { - return hasAppliedDirective(NadelVirtualTypeDirective.SyntaxConstant.virtualType) + return hasAppliedDirective(NadelVirtualTypeDirective.Keyword.virtualType) } internal class NadelVirtualTypeDirective { - object SyntaxConstant { + object Keyword { const val virtualType = "virtualType" } } diff --git a/lib/src/main/java/graphql/nadel/engine/util/GraphQLUtil.kt b/lib/src/main/java/graphql/nadel/engine/util/GraphQLUtil.kt index 4c808e1d7..2e90b0d4a 100644 --- a/lib/src/main/java/graphql/nadel/engine/util/GraphQLUtil.kt +++ b/lib/src/main/java/graphql/nadel/engine/util/GraphQLUtil.kt @@ -374,7 +374,7 @@ fun makeFieldCoordinates(typeName: String, fieldName: String): FieldCoordinates return FieldCoordinates.coordinates(typeName, fieldName) } -fun makeFieldCoordinates(parentType: GraphQLObjectType, field: GraphQLFieldDefinition): FieldCoordinates { +fun makeFieldCoordinates(parentType: GraphQLFieldsContainer, field: GraphQLFieldDefinition): FieldCoordinates { return makeFieldCoordinates(typeName = parentType.name, fieldName = field.name) } diff --git a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt index 7da65b61a..b5c4f5b59 100644 --- a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt +++ b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt @@ -2,31 +2,24 @@ package graphql.nadel.schema import graphql.GraphQLContext import graphql.execution.ValuesResolver -import graphql.language.ArrayValue import graphql.language.DirectiveDefinition import graphql.language.EnumTypeDefinition import graphql.language.InputObjectTypeDefinition -import graphql.language.ObjectValue import graphql.language.SDLDefinition -import graphql.language.StringValue -import graphql.language.Value import graphql.nadel.dsl.FieldMappingDefinition import graphql.nadel.dsl.NadelHydrationConditionDefinition import graphql.nadel.dsl.NadelHydrationConditionPredicateDefinition -import graphql.nadel.dsl.NadelHydrationDefinition import graphql.nadel.dsl.NadelHydrationResultConditionDefinition import graphql.nadel.dsl.NadelPartitionDefinition import graphql.nadel.dsl.RemoteArgumentDefinition -import graphql.nadel.dsl.RemoteArgumentSource import graphql.nadel.dsl.TypeMappingDefinition +import graphql.nadel.engine.blueprint.directives.NadelHydrationDefinition import graphql.nadel.engine.util.singleOfType import graphql.parser.Parser import graphql.schema.GraphQLAppliedDirective import graphql.schema.GraphQLAppliedDirectiveArgument import graphql.schema.GraphQLDirectiveContainer -import graphql.schema.GraphQLEnumType import graphql.schema.GraphQLFieldDefinition -import graphql.schema.GraphQLSchema import java.util.Locale /** @@ -170,53 +163,6 @@ object NadelDirectives { """.trimIndent(), ) - val nadelHydrationTemplateEnumDefinition = parseDefinition( - // language=GraphQL - """ - enum NadelHydrationTemplate { - NADEL_PLACEHOLDER - } - """.trimIndent(), - ) - - val hydratedFromDirectiveDefinition = parseDefinition( - // language=GraphQL - """ - "This allows you to hydrate new values into fields" - directive @hydratedFrom( - "The arguments to the hydrated field" - arguments: [NadelHydrationFromArgument!]! = [] - "The hydration template to use" - template: NadelHydrationTemplate! - ) repeatable on FIELD_DEFINITION - """.trimIndent(), - ) - - val hydratedTemplateDirectiveDefinition = parseDefinition( - // language=GraphQL - """ - "This template directive provides common values to hydrated fields" - directive @hydratedTemplate( - "The target service" - service: String! - "The target top level field" - field: String! - "How to identify matching results" - identifiedBy: String! = "id" - "How to identify matching results" - inputIdentifiedBy: [NadelBatchObjectIdentifiedBy!]! = [] - "Are results indexed" - indexed: Boolean = false - "Is querying batched" - batched: Boolean = false - "The batch size" - batchSize: Int = 200 - "The timeout in milliseconds" - timeout: Int = -1 - ) on ENUM_VALUE - """.trimIndent(), - ) - val partitionDirectiveDefinition = parseDefinition( // language=GraphQL """ @@ -228,173 +174,6 @@ object NadelDirectives { """.trimIndent() ) - internal fun createUnderlyingServiceHydration( - fieldDefinition: GraphQLFieldDefinition, - overallSchema: GraphQLSchema, - ): List { - val hydrations = fieldDefinition.getAppliedDirectives(hydratedDirectiveDefinition.name) - .map { directive -> - val arguments = createRemoteArgs(directive.getArgument("arguments").argumentValue.value as ArrayValue) - - val inputIdentifiedBy = directive.getArgument("inputIdentifiedBy") - val identifiedByValues = resolveArgumentValue>(inputIdentifiedBy) - val identifiedBy = createObjectIdentifiers(identifiedByValues) - - val conditionalHydration = directive.getArgument("when") - ?.let { - buildConditionalHydrationObject(it)?.result - } - - buildHydrationParameters(directive, arguments, identifiedBy, conditionalHydration) - } - - val templatedHydrations = fieldDefinition.getAppliedDirectives(hydratedFromDirectiveDefinition.name) - .map { directive -> - createTemplatedUnderlyingServiceHydration(directive, overallSchema) - } - - return hydrations + templatedHydrations - } - - private fun buildHydrationParameters( - directive: GraphQLAppliedDirective, - arguments: List, - identifiedBy: List, - conditionalHydration: NadelHydrationResultConditionDefinition? = null, - ): NadelHydrationDefinition { - val service = getDirectiveValue(directive, "service") - val fieldNames = getDirectiveValue(directive, "field").split('.') - val objectIdentifier = getDirectiveValue(directive, "identifiedBy") - val objectIndexed = getDirectiveValue(directive, "indexed") - - // Note: this is not properly implemented yet, so the value does not matter - val batched = false // getDirectiveValue(directive, "batched", Boolean.class, false); - - val batchSize = getDirectiveValue(directive, "batchSize") - val timeout = getDirectiveValue(directive, "timeout") - - require(fieldNames.isNotEmpty()) - - // nominally this should be some other data class that's not an AST element - // but history is what it is, and it's an AST element that's' really a data class - return NadelHydrationDefinition( - service, - fieldNames, - arguments, - objectIdentifier, - identifiedBy, - objectIndexed, - batched, - batchSize, - timeout, - conditionalHydration - ) - } - - private fun createTemplatedUnderlyingServiceHydration( - hydratedFromDirective: GraphQLAppliedDirective, - overallSchema: GraphQLSchema, - ): NadelHydrationDefinition { - val template = hydratedFromDirective.getArgument("template") - val enumTargetName = resolveArgumentValue(template) - val templateEnumType = overallSchema.getTypeAs("NadelHydrationTemplate") - requireNotNull(templateEnumType) { "There MUST be a enum called NadelHydrationTemplate" } - val enumValue = templateEnumType.getValue(enumTargetName) - requireNotNull(enumValue) { - "There MUST be a enum value in NadelHydrationTemplate called '${enumTargetName}'" - } - val templateDirective = enumValue.getAppliedDirective(hydratedTemplateDirectiveDefinition.name) - requireNotNull(templateDirective) { - "The enum value '${enumTargetName}' in NadelHydrationTemplate must have a directive called '${hydratedTemplateDirectiveDefinition.name}'" - } - - val graphQLArgument = hydratedFromDirective.getArgument("arguments") - val argumentValues = resolveArgumentValue>(graphQLArgument) - val arguments = createTemplatedHydratedArgs(argumentValues) - return buildHydrationParameters( - templateDirective, - arguments, - emptyList() - ) - } - - private fun createRemoteArgs(arguments: ArrayValue): List { - return arguments.values - .map { arg -> - @Suppress("UNCHECKED_CAST") // trust GraphQL type system and caller - 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) - } - } - - private fun createObjectIdentifiers(arguments: List): List { - fun Map.requireArgument(key: String): String { - return requireNotNull(this[key]) { - "${nadelBatchObjectIdentifiedByDefinition.name} definition requires '$key' to be not-null" - } - } - return arguments.map { arg -> - @Suppress("UNCHECKED_CAST") // trust GraphQL type system and caller - val argMap = arg as MutableMap - val sourceId = argMap.requireArgument("sourceId") - val resultId = argMap.requireArgument("resultId") - NadelHydrationDefinition.ObjectIdentifier(sourceId, resultId) - } - } - - private fun createRemoteArgumentSource(value: Value<*>): RemoteArgumentSource { - return if (value is StringValue) { - val values = value.value.split('.') - - when (values.first()) { - "\$source" -> RemoteArgumentSource.ObjectField( - pathToField = values.subList(1, values.size), - ) - "\$argument" -> RemoteArgumentSource.FieldArgument( - argumentName = values.subList(1, values.size).single(), - ) - else -> RemoteArgumentSource.StaticArgument(staticValue = value) - } - } else { - RemoteArgumentSource.StaticArgument(staticValue = value) - } - } - - private fun createTemplatedHydratedArgs(arguments: List): List { - val inputObjectTypeName = nadelHydrationFromArgumentDefinition.name - val valueFromFieldKey = "valueFromField" - val valueFromArgKey = "valueFromArg" - - return arguments.map { arg -> - @Suppress("UNCHECKED_CAST") // trust graphQL type system and caller - val argMap = arg as Map - - val remoteArgName = requireNotNull(argMap["name"]) { - "$inputObjectTypeName requires 'name' to be not-null" - } - - val remoteArgFieldValue = argMap[valueFromFieldKey] - val remoteArgArgValue = argMap[valueFromArgKey] - - val remoteArgumentSource = if (remoteArgFieldValue != null && remoteArgArgValue != null) { - throw IllegalArgumentException("$inputObjectTypeName can not have both $valueFromFieldKey and $valueFromArgKey set") - } else { - 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) - } - } - internal fun createFieldMapping(fieldDefinition: GraphQLFieldDefinition): FieldMappingDefinition? { val directive = fieldDefinition.getAppliedDirective(renamedDirectiveDefinition.name) ?: return null @@ -440,31 +219,6 @@ object NadelDirectives { ) as T } - private fun buildConditionalHydrationObject( - conditionArgument: GraphQLAppliedDirectiveArgument, - ): NadelHydrationConditionDefinition? { - @Suppress("UNCHECKED_CAST") - val result = conditionArgument.getValue>() - ?.get("result") as Map? - ?: return null - - val sourceField = result["sourceField"]!! as String - - @Suppress("UNCHECKED_CAST") - val predicate = result["predicate"]!! as Map - - return NadelHydrationConditionDefinition( - result = NadelHydrationResultConditionDefinition( - pathToSourceField = sourceField.split("."), - predicate = NadelHydrationConditionPredicateDefinition( - equals = predicate["equals"], - startsWith = predicate["startsWith"] as String?, - matches = predicate["matches"] as String?, - ), - ), - ) - } - private inline fun > parseDefinition(sdl: String): T { return Parser.parse(sdl).definitions.singleOfType() } diff --git a/lib/src/main/java/graphql/nadel/validation/NadelHydrationArgumentValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelHydrationArgumentValidation.kt index fea0bbe85..bf221e1c8 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelHydrationArgumentValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelHydrationArgumentValidation.kt @@ -1,9 +1,9 @@ package graphql.nadel.validation import graphql.Scalars -import graphql.nadel.dsl.NadelHydrationDefinition import graphql.nadel.dsl.RemoteArgumentDefinition import graphql.nadel.dsl.RemoteArgumentSource +import graphql.nadel.engine.blueprint.directives.NadelHydrationDefinition import graphql.nadel.engine.util.isList import graphql.nadel.engine.util.isNonNull import graphql.nadel.engine.util.unwrapNonNull diff --git a/lib/src/main/java/graphql/nadel/validation/NadelHydrationConditionValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelHydrationConditionValidation.kt index e71802870..b38b5d973 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelHydrationConditionValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelHydrationConditionValidation.kt @@ -1,9 +1,9 @@ package graphql.nadel.validation import graphql.Scalars -import graphql.nadel.dsl.NadelHydrationDefinition import graphql.nadel.dsl.NadelHydrationResultConditionDefinition -import graphql.nadel.dsl.RemoteArgumentSource +import graphql.nadel.engine.blueprint.directives.NadelHydrationArgumentDefinition +import graphql.nadel.engine.blueprint.directives.NadelHydrationDefinition import graphql.nadel.engine.util.getFieldAt import graphql.nadel.engine.util.unwrapAll import graphql.nadel.engine.util.unwrapNonNull @@ -19,11 +19,10 @@ internal class NadelHydrationConditionValidation { overallField: GraphQLFieldDefinition, hydration: NadelHydrationDefinition, ): NadelSchemaValidationError? { - if (hydration.condition == null) { - return null - } + val resultCondition = hydration.condition?.result + ?: return null - val pathToConditionSourceField = hydration.condition.pathToSourceField + val pathToConditionSourceField = resultCondition.pathToSourceField val conditionSourceField: GraphQLFieldDefinition = (parent.overall as GraphQLFieldsContainer).getFieldAt(pathToConditionSourceField) ?: return NadelSchemaValidationError.HydrationConditionSourceFieldDoesNotExist( @@ -33,8 +32,8 @@ internal class NadelHydrationConditionValidation { val sourceInputField = hydration.arguments .asSequence() - .map { it.remoteArgumentSource } - .filterIsInstance() + .map { it.value } + .filterIsInstance() .map { it.pathToField } .single() @@ -56,7 +55,7 @@ internal class NadelHydrationConditionValidation { overallField = overallField, pathToConditionSourceField = pathToConditionSourceField, conditionSourceFieldType = conditionSourceFieldType, - condition = hydration.condition, + condition = resultCondition, ) } diff --git a/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt index c0217818f..7f3cb2c41 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt @@ -2,7 +2,6 @@ package graphql.nadel.validation import graphql.GraphQLContext import graphql.nadel.Service -import graphql.nadel.dsl.NadelHydrationDefinition import graphql.nadel.dsl.RemoteArgumentDefinition import graphql.nadel.dsl.RemoteArgumentSource import graphql.nadel.engine.blueprint.directives.isVirtualType diff --git a/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt b/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt index e2c368fa6..cef088990 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt @@ -6,7 +6,6 @@ import graphql.GraphqlErrorBuilder import graphql.language.InputValueDefinition import graphql.nadel.Service import graphql.nadel.dsl.FieldMappingDefinition -import graphql.nadel.dsl.NadelHydrationDefinition import graphql.nadel.dsl.RemoteArgumentDefinition import graphql.nadel.dsl.RemoteArgumentSource import graphql.nadel.engine.util.makeFieldCoordinates From 0bb005674988da0a03ea6cf1bcfead98ed283bc9 Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Mon, 21 Oct 2024 17:02:50 +1100 Subject: [PATCH 2/9] Refactor hydration definitions --- .../graphql/nadel/dsl/RemoteArgumentSource.kt | 21 +++ .../directives/NadelHydrationDefinition.kt | 25 ++-- .../nadel/schema/OverallSchemaGenerator.kt | 3 - .../NadelHydrationArgumentValidation.kt | 21 ++- .../validation/NadelHydrationValidation.kt | 45 +++--- .../validation/NadelSchemaValidationError.kt | 56 +++---- .../validation/util/NadelBuiltInTypes.kt | 22 +-- .../nadel/validation/util/NadelSchemaUtil.kt | 13 +- .../nadel/schema/NadelDirectivesTest.kt | 141 ++---------------- .../NadelHydrationArgumentValidationTest.kt | 18 +-- .../NadelHydrationValidationTest.kt | 80 ++-------- ...NadelPolymorphicHydrationValidationTest.kt | 2 +- 12 files changed, 128 insertions(+), 319 deletions(-) diff --git a/lib/src/main/java/graphql/nadel/dsl/RemoteArgumentSource.kt b/lib/src/main/java/graphql/nadel/dsl/RemoteArgumentSource.kt index 7f295e2b9..1bea86212 100644 --- a/lib/src/main/java/graphql/nadel/dsl/RemoteArgumentSource.kt +++ b/lib/src/main/java/graphql/nadel/dsl/RemoteArgumentSource.kt @@ -3,14 +3,35 @@ package graphql.nadel.dsl import graphql.nadel.util.AnyAstValue sealed class RemoteArgumentSource { + @Deprecated( + replaceWith = ReplaceWith( + "NadelHydrationArgumentDefinition.ValueSource.ObjectField", + imports = ["graphql.nadel.engine.blueprint.directives.NadelHydrationArgumentDefinition"] + ), + message = "Do not use", + ) data class ObjectField( val pathToField: List, ) : RemoteArgumentSource() + @Deprecated( + replaceWith = ReplaceWith( + "NadelHydrationArgumentDefinition.ValueSource.FieldArgument", + imports = ["graphql.nadel.engine.blueprint.directives.NadelHydrationArgumentDefinition"] + ), + message = "Do not use", + ) data class FieldArgument( val argumentName: String, ) : RemoteArgumentSource() + @Deprecated( + replaceWith = ReplaceWith( + "NadelHydrationArgumentDefinition.ValueSource.StaticArgument", + imports = ["graphql.nadel.engine.blueprint.directives.NadelHydrationArgumentDefinition"] + ), + message = "Do not use", + ) data class StaticArgument( val staticValue: AnyAstValue, ) : RemoteArgumentSource() diff --git a/lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelHydrationDefinition.kt b/lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelHydrationDefinition.kt index 36e35e08b..6b1d31c06 100644 --- a/lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelHydrationDefinition.kt +++ b/lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelHydrationDefinition.kt @@ -1,6 +1,7 @@ package graphql.nadel.engine.blueprint.directives import graphql.language.ArrayValue +import graphql.language.FieldDefinition import graphql.language.ObjectField import graphql.language.ObjectValue import graphql.language.StringValue @@ -11,11 +12,15 @@ import graphql.nadel.util.AnyAstValue import graphql.schema.GraphQLAppliedDirective import graphql.schema.GraphQLFieldDefinition -internal fun GraphQLFieldDefinition.hasHydration(): Boolean { +fun FieldDefinition.hasHydration(): Boolean { + return hasDirective(Keyword.hydrated) +} + +fun GraphQLFieldDefinition.hasHydration(): Boolean { return hasAppliedDirective(Keyword.hydrated) } -internal fun GraphQLFieldDefinition.getHydrationDefinitions(): List { +fun GraphQLFieldDefinition.getHydrationDefinitions(): List { return getAppliedDirectives(Keyword.hydrated) .map(::NadelHydrationDefinition) } @@ -47,7 +52,7 @@ internal fun GraphQLFieldDefinition.getHydrationDefinitions(): List @@ -66,7 +71,7 @@ internal class NadelHydrationDefinition( get() = appliedDirective.getArgument(Keyword.batchSize).getValue() val arguments: List - get() = appliedDirective.getArgument(Keyword.arguments).getValue() + get() = (appliedDirective.getArgument(Keyword.arguments).argumentValue.value as ArrayValue) .values .map { NadelHydrationArgumentDefinition(it as ObjectValue) @@ -82,9 +87,9 @@ internal class NadelHydrationDefinition( get() = appliedDirective.getArgument(Keyword.timeout).getValue() val inputIdentifiedBy: List? - get() = appliedDirective.getArgument(Keyword.inputIdentifiedBy).getValue() - .values - .map { + get() = (appliedDirective.getArgument(Keyword.inputIdentifiedBy).argumentValue.value as ArrayValue?) + ?.values + ?.map { NadelBatchObjectIdentifiedByDefinition(it as ObjectValue) } @@ -102,7 +107,7 @@ internal class NadelHydrationDefinition( } } -internal class NadelBatchObjectIdentifiedByDefinition( +class NadelBatchObjectIdentifiedByDefinition( private val objectValue: ObjectValue, ) { val sourceId: String @@ -119,7 +124,7 @@ internal class NadelBatchObjectIdentifiedByDefinition( /** * Argument belonging to [NadelHydrationDefinition.arguments] */ -internal class NadelHydrationArgumentDefinition( +class NadelHydrationArgumentDefinition( private val argumentObject: ObjectValue, ) { /** @@ -139,7 +144,7 @@ internal class NadelHydrationArgumentDefinition( const val value = "value" } - internal sealed class ValueSource { + sealed class ValueSource { data class ObjectField( val pathToField: List, ) : ValueSource() diff --git a/lib/src/main/java/graphql/nadel/schema/OverallSchemaGenerator.kt b/lib/src/main/java/graphql/nadel/schema/OverallSchemaGenerator.kt index c4613b783..de102eaec 100644 --- a/lib/src/main/java/graphql/nadel/schema/OverallSchemaGenerator.kt +++ b/lib/src/main/java/graphql/nadel/schema/OverallSchemaGenerator.kt @@ -67,9 +67,6 @@ internal class OverallSchemaGenerator { addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.hiddenDirectiveDefinition) addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.nadelHydrationFromArgumentDefinition) addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.nadelBatchObjectIdentifiedByDefinition) - addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.nadelHydrationTemplateEnumDefinition) - addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.hydratedFromDirectiveDefinition) - addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.hydratedTemplateDirectiveDefinition) addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.nadelHydrationResultFieldPredicateDefinition) addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.nadelHydrationResultConditionDefinition) addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.nadelHydrationConditionDefinition) diff --git a/lib/src/main/java/graphql/nadel/validation/NadelHydrationArgumentValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelHydrationArgumentValidation.kt index bf221e1c8..53c20cd4a 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelHydrationArgumentValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelHydrationArgumentValidation.kt @@ -1,8 +1,7 @@ package graphql.nadel.validation import graphql.Scalars -import graphql.nadel.dsl.RemoteArgumentDefinition -import graphql.nadel.dsl.RemoteArgumentSource +import graphql.nadel.engine.blueprint.directives.NadelHydrationArgumentDefinition import graphql.nadel.engine.blueprint.directives.NadelHydrationDefinition import graphql.nadel.engine.util.isList import graphql.nadel.engine.util.isNonNull @@ -24,7 +23,7 @@ internal class NadelHydrationArgumentValidation { actorFieldArgType: GraphQLInputType, parent: NadelServiceSchemaElement, overallField: GraphQLFieldDefinition, - remoteArg: RemoteArgumentDefinition, + remoteArg: NadelHydrationArgumentDefinition, hydration: NadelHydrationDefinition, isBatchHydration: Boolean, actorFieldName: String, @@ -102,13 +101,13 @@ internal class NadelHydrationArgumentValidation { actorFieldArgType: GraphQLType, parent: NadelServiceSchemaElement, overallField: GraphQLFieldDefinition, - remoteArg: RemoteArgumentDefinition, + remoteArg: NadelHydrationArgumentDefinition, hydration: NadelHydrationDefinition, actorFieldName: String, ): NadelSchemaValidationError? { // need to check null compatibility - val remoteArgumentSource = remoteArg.remoteArgumentSource - if (remoteArgumentSource !is RemoteArgumentSource.ObjectField && actorFieldArgType.isNonNull && !hydrationSourceType.isNonNull) { + val remoteArgumentSource = remoteArg.value + if (remoteArgumentSource !is NadelHydrationArgumentDefinition.ValueSource.ObjectField && actorFieldArgType.isNonNull && !hydrationSourceType.isNonNull) { // source must be at least as strict as field argument return NadelSchemaValidationError.IncompatibleHydrationArgumentType( parent, @@ -145,7 +144,7 @@ internal class NadelHydrationArgumentValidation { ) } // object feed into inputObject (i.e. hydrating with a $source object) - else if (remoteArgumentSource is RemoteArgumentSource.ObjectField && unwrappedHydrationSourceType is GraphQLObjectType && unwrappedActorFieldArgType is GraphQLInputObjectType) { + else if (remoteArgumentSource is NadelHydrationArgumentDefinition.ValueSource.ObjectField && unwrappedHydrationSourceType is GraphQLObjectType && unwrappedActorFieldArgType is GraphQLInputObjectType) { validateInputObjectArg( unwrappedHydrationSourceType, unwrappedActorFieldArgType, @@ -157,7 +156,7 @@ internal class NadelHydrationArgumentValidation { ) } // inputObject feed into inputObject (i.e. hydrating with an $argument object) - else if (remoteArgumentSource is RemoteArgumentSource.FieldArgument && unwrappedHydrationSourceType is GraphQLInputObjectType && unwrappedActorFieldArgType is GraphQLInputObjectType) { + else if (remoteArgumentSource is NadelHydrationArgumentDefinition.ValueSource.FieldArgument && unwrappedHydrationSourceType is GraphQLInputObjectType && unwrappedActorFieldArgType is GraphQLInputObjectType) { if (unwrappedHydrationSourceType.name != unwrappedActorFieldArgType.name) { NadelSchemaValidationError.IncompatibleHydrationArgumentType( parent, @@ -204,7 +203,7 @@ internal class NadelHydrationArgumentValidation { actorFieldArgType: GraphQLType, parent: NadelServiceSchemaElement, overallField: GraphQLFieldDefinition, - remoteArg: RemoteArgumentDefinition, + remoteArg: NadelHydrationArgumentDefinition, actorFieldName: String, ): NadelSchemaValidationError? { val unwrappedHydrationSourceType = hydrationSourceType.unwrapNonNull() @@ -249,7 +248,7 @@ internal class NadelHydrationArgumentValidation { actorFieldArgType: GraphQLType, parent: NadelServiceSchemaElement, overallField: GraphQLFieldDefinition, - remoteArg: RemoteArgumentDefinition, + remoteArg: NadelHydrationArgumentDefinition, hydration: NadelHydrationDefinition, actorFieldName: String, ): NadelSchemaValidationError? { @@ -282,7 +281,7 @@ internal class NadelHydrationArgumentValidation { actorFieldArgType: GraphQLInputObjectType, parent: NadelServiceSchemaElement, overallField: GraphQLFieldDefinition, - remoteArg: RemoteArgumentDefinition, + remoteArg: NadelHydrationArgumentDefinition, hydration: NadelHydrationDefinition, actorFieldName: String, ): NadelSchemaValidationError? { diff --git a/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt index 7f3cb2c41..ea6cf06a1 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt @@ -2,9 +2,11 @@ package graphql.nadel.validation import graphql.GraphQLContext import graphql.nadel.Service -import graphql.nadel.dsl.RemoteArgumentDefinition import graphql.nadel.dsl.RemoteArgumentSource import graphql.nadel.engine.blueprint.directives.isVirtualType +import graphql.nadel.engine.blueprint.directives.NadelHydrationArgumentDefinition +import graphql.nadel.engine.blueprint.directives.NadelHydrationDefinition +import graphql.nadel.engine.blueprint.directives.getHydrationDefinitions import graphql.nadel.engine.util.getFieldAt import graphql.nadel.engine.util.getFieldsAlong import graphql.nadel.engine.util.isList @@ -19,14 +21,12 @@ import graphql.nadel.validation.NadelSchemaValidationError.DuplicatedHydrationAr import graphql.nadel.validation.NadelSchemaValidationError.FieldWithPolymorphicHydrationMustReturnAUnion import graphql.nadel.validation.NadelSchemaValidationError.HydrationFieldMustBeNullable import graphql.nadel.validation.NadelSchemaValidationError.MissingHydrationActorField -import graphql.nadel.validation.NadelSchemaValidationError.MissingHydrationActorService import graphql.nadel.validation.NadelSchemaValidationError.MissingHydrationArgumentValueSource 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 import graphql.schema.GraphQLDirectiveContainer import graphql.schema.GraphQLFieldDefinition @@ -57,7 +57,7 @@ internal class NadelHydrationValidation( ) } - val hydrations = getHydrations(overallField, overallSchema) + val hydrations = overallField.getHydrationDefinitions() if (hydrations.isEmpty()) { error("Don't invoke hydration validation if there is no hydration silly") } @@ -86,13 +86,7 @@ internal class NadelHydrationValidation( hydration: NadelHydrationDefinition, hasMoreThanOneHydration: Boolean, ): List { - if (hydration.serviceName !in services) { - return listOf( - MissingHydrationActorService(parent, overallField, hydration), - ) - } - - val actorField = overallSchema.queryType.getFieldAt(hydration.pathToActorField) + val actorField = overallSchema.queryType.getFieldAt(hydration.backingField) ?: return listOf( MissingHydrationActorField(parent, overallField, hydration), ) @@ -109,18 +103,13 @@ internal class NadelHydrationValidation( ): List { // e.g. context.jiraComment val pathToSourceInputField = hydration.arguments - .map { arg -> arg.remoteArgumentSource } + .map { arg -> arg.value } .singleOfTypeOrNull() ?.pathToField ?: return emptyList() // Ignore this, checked elsewhere - // Nothing to check - if (hydration.objectIdentifiers == null) { - return emptyList() - } - // Find offending object identifiers and generate errors - return hydration.objectIdentifiers + return (hydration.inputIdentifiedBy ?: return emptyList()) .asSequence() .filterNot { identifier -> // e.g. context.jiraComment.id @@ -167,8 +156,8 @@ internal class NadelHydrationValidation( hydration .arguments .asSequence() - .map { it.remoteArgumentSource } - .filterIsInstance() + .map { it.value } + .filterIsInstance() .map { it.pathToField } } .toList() @@ -204,7 +193,7 @@ internal class NadelHydrationValidation( hydrations: List, ): List { // todo: or maybe just don't allow polymorphic index hydration - val (indexCount, nonIndexCount) = hydrations.partitionCount { it.isObjectMatchByIndex } + val (indexCount, nonIndexCount) = hydrations.partitionCount { it.isIndexed } if (indexCount > 0 && nonIndexCount > 0) { return listOf( NadelSchemaValidationError.MixedIndexHydration(parent, overallField), @@ -215,7 +204,7 @@ internal class NadelHydrationValidation( } private fun isBatched(hydration: NadelHydrationDefinition): Boolean { - val actorFieldDef = overallSchema.queryType.getFieldAt(hydration.pathToActorField) + val actorFieldDef = overallSchema.queryType.getFieldAt(hydration.backingField) return hydration.isBatched || /*deprecated*/ actorFieldDef?.type?.unwrapNonNull()?.isList == true } @@ -328,7 +317,7 @@ internal class NadelHydrationValidation( val batchHydrationArgumentErrors: List = when { isBatchHydration -> { val numberOfSourceArgs = - hydration.arguments.count { it.remoteArgumentSource is RemoteArgumentSource.ObjectField } + hydration.arguments.count { it.value is NadelHydrationArgumentDefinition.ValueSource.ObjectField } when { numberOfSourceArgs > 1 -> listOf(MultipleSourceArgsInBatchHydration(parent, overallField)) @@ -348,15 +337,15 @@ internal class NadelHydrationValidation( private fun getRemoteArgErrors( parent: NadelServiceSchemaElement, overallField: GraphQLFieldDefinition, - remoteArgDef: RemoteArgumentDefinition, + remoteArgDef: NadelHydrationArgumentDefinition, actorField: GraphQLFieldDefinition, hydration: NadelHydrationDefinition, ): List { - val remoteArgSource = remoteArgDef.remoteArgumentSource + val remoteArgSource = remoteArgDef.value val actorFieldArg = actorField.getArgument(remoteArgDef.name) val isBatchHydration = actorField.type.unwrapNonNull().isList return when (remoteArgSource) { - is RemoteArgumentSource.ObjectField -> { + is NadelHydrationArgumentDefinition.ValueSource.ObjectField -> { val field = (parent.underlying as GraphQLFieldsContainer).getFieldAt(remoteArgSource.pathToField) if (field == null) { listOf( @@ -382,7 +371,7 @@ internal class NadelHydrationValidation( ) } } - is RemoteArgumentSource.FieldArgument -> { + is NadelHydrationArgumentDefinition.ValueSource.FieldArgument -> { val argument = overallField.getArgument(remoteArgSource.argumentName) if (argument == null) { listOf(MissingHydrationArgumentValueSource(parent, overallField, remoteArgSource)) @@ -403,7 +392,7 @@ internal class NadelHydrationValidation( ) } } - is RemoteArgumentSource.StaticArgument -> { + is NadelHydrationArgumentDefinition.ValueSource.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 cef088990..ff62f261c 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt @@ -6,8 +6,9 @@ import graphql.GraphqlErrorBuilder import graphql.language.InputValueDefinition import graphql.nadel.Service import graphql.nadel.dsl.FieldMappingDefinition -import graphql.nadel.dsl.RemoteArgumentDefinition -import graphql.nadel.dsl.RemoteArgumentSource +import graphql.nadel.engine.blueprint.directives.NadelBatchObjectIdentifiedByDefinition +import graphql.nadel.engine.blueprint.directives.NadelHydrationArgumentDefinition +import graphql.nadel.engine.blueprint.directives.NadelHydrationDefinition import graphql.nadel.engine.util.makeFieldCoordinates import graphql.nadel.engine.util.unwrapAll import graphql.nadel.schema.NadelDirectives @@ -223,22 +224,6 @@ sealed interface NadelSchemaValidationError { override val subject = overallValue } - data class MissingHydrationActorService( - val parentType: NadelServiceSchemaElement, - val overallField: GraphQLFieldDefinition, - val hydration: NadelHydrationDefinition, - ) : NadelSchemaValidationError { - val service: Service get() = parentType.service - - override val message = run { - val of = makeFieldCoordinates(parentType.overall.name, overallField.name) - val s = hydration.serviceName - "Field $of tried to hydrate from non-existent service $s" - } - - override val subject = overallField - } - data class MissingHydrationActorField( val parentType: NadelServiceSchemaElement, val overallField: GraphQLFieldDefinition, @@ -248,9 +233,8 @@ sealed interface NadelSchemaValidationError { override val message = run { val of = makeFieldCoordinates(parentType.overall.name, overallField.name) - val s = hydration.serviceName - val af = hydration.pathToActorField.joinToString(separator = ".") - "Field $of tried to hydrate from non-existent field Query.$af on service $s" + val af = hydration.backingField.joinToString(separator = ".") + "Field $of tried to hydrate from non-existent field Query.$af" } override val subject = overallField @@ -306,7 +290,7 @@ sealed interface NadelSchemaValidationError { data class MissingHydrationFieldValueSource( val parentType: NadelServiceSchemaElement, val overallField: GraphQLFieldDefinition, - val remoteArgSource: RemoteArgumentSource.ObjectField, + val remoteArgSource: NadelHydrationArgumentDefinition.ValueSource.ObjectField, ) : NadelSchemaValidationError { val service: Service get() = parentType.service @@ -323,7 +307,7 @@ sealed interface NadelSchemaValidationError { data class MissingHydrationArgumentValueSource( val parentType: NadelServiceSchemaElement, val overallField: GraphQLFieldDefinition, - val remoteArgSource: RemoteArgumentSource.FieldArgument, + val remoteArgSource: NadelHydrationArgumentDefinition.ValueSource.FieldArgument, ) : NadelSchemaValidationError { val service: Service get() = parentType.service @@ -346,9 +330,8 @@ sealed interface NadelSchemaValidationError { override val message = run { val of = makeFieldCoordinates(parentType.overall.name, overallField.name) - val s = hydration.serviceName - val af = hydration.pathToActorField.joinToString(separator = ".") - "Hydration on field $of references non-existent argument $argument on hydration actor $s.Query.$af" + val af = hydration.backingField.joinToString(separator = ".") + "Hydration on field $of references non-existent argument $argument on hydration actor Query.$af" } override val subject = overallField @@ -357,7 +340,7 @@ sealed interface NadelSchemaValidationError { data class IncompatibleHydrationArgumentType( val parentType: NadelServiceSchemaElement, val overallField: GraphQLFieldDefinition, - val remoteArg: RemoteArgumentDefinition, + val remoteArg: NadelHydrationArgumentDefinition, val hydrationType: GraphQLType, val actorArgInputType: GraphQLType, val actorFieldName: String, @@ -404,7 +387,7 @@ sealed interface NadelSchemaValidationError { data class StaticArgIsNotAssignable( val parentType: NadelServiceSchemaElement, val overallField: GraphQLFieldDefinition, - val remoteArg: RemoteArgumentDefinition, + val remoteArg: NadelHydrationArgumentDefinition, val actorArgInputType: GraphQLType, val actorFieldName: String, ) : NadelSchemaValidationError { @@ -489,7 +472,7 @@ sealed interface NadelSchemaValidationError { data class IncompatibleFieldInHydratedInputObject( val parentType: NadelServiceSchemaElement, val overallField: GraphQLFieldDefinition, - val remoteArg: RemoteArgumentDefinition, + val remoteArg: NadelHydrationArgumentDefinition, val actorFieldName: String, ) : NadelSchemaValidationError { val service: Service get() = parentType.service @@ -497,7 +480,7 @@ sealed interface NadelSchemaValidationError { override val message = run { val hydrationArgName = remoteArg.name val of = makeFieldCoordinates(parentType.overall.name, overallField.name) - val pathToField = (remoteArg.remoteArgumentSource as RemoteArgumentSource.ObjectField).pathToField + val pathToField = (remoteArg.value as NadelHydrationArgumentDefinition.ValueSource.ObjectField).pathToField val remoteArgSource = "${parentType.underlying.name}.${pathToField.joinToString(separator = ".")}" "Field \"$of\" tried to hydrate using the actor field \"$actorFieldName\" and argument \"$hydrationArgName\"." + @@ -511,7 +494,7 @@ sealed interface NadelSchemaValidationError { data class MissingFieldInHydratedInputObject( val parentType: NadelServiceSchemaElement, val overallField: GraphQLFieldDefinition, - val remoteArg: RemoteArgumentDefinition, + val remoteArg: NadelHydrationArgumentDefinition, val missingFieldName: String, val actorFieldName: String, ) : NadelSchemaValidationError { @@ -520,7 +503,7 @@ sealed interface NadelSchemaValidationError { override val message = run { val of = makeFieldCoordinates(parentType.overall.name, overallField.name) val hydrationArgName = remoteArg.name - val pathToField = (remoteArg.remoteArgumentSource as RemoteArgumentSource.ObjectField).pathToField + val pathToField = (remoteArg.value as NadelHydrationArgumentDefinition.ValueSource.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" + @@ -540,9 +523,8 @@ sealed interface NadelSchemaValidationError { override val message = run { val of = makeFieldCoordinates(parentType.overall.name, overallField.name) - val s = hydration.serviceName - val af = hydration.pathToActorField.joinToString(separator = ".") - "Hydration on field $of is missing the required argument $argument on hydration actor $s.Query.$af" + val af = hydration.backingField.joinToString(separator = ".") + "Hydration on field $of is missing the required argument $argument on hydration actor Query.$af" } override val subject = overallField @@ -684,7 +666,7 @@ sealed interface NadelSchemaValidationError { data class DuplicatedHydrationArgument( val parentType: NadelServiceSchemaElement, val overallField: GraphQLFieldDefinition, - val duplicates: List, + val duplicates: List, ) : NadelSchemaValidationError { val service: Service get() = parentType.service @@ -728,7 +710,7 @@ sealed interface NadelSchemaValidationError { val type: NadelServiceSchemaElement, val field: GraphQLFieldDefinition, val pathToSourceInputField: List, - val offendingObjectIdentifier: NadelHydrationDefinition.ObjectIdentifier, + val offendingObjectIdentifier: NadelBatchObjectIdentifiedByDefinition, ) : NadelSchemaValidationError { val service: Service get() = type.service diff --git a/lib/src/main/java/graphql/nadel/validation/util/NadelBuiltInTypes.kt b/lib/src/main/java/graphql/nadel/validation/util/NadelBuiltInTypes.kt index 26494e1d4..181b105b2 100644 --- a/lib/src/main/java/graphql/nadel/validation/util/NadelBuiltInTypes.kt +++ b/lib/src/main/java/graphql/nadel/validation/util/NadelBuiltInTypes.kt @@ -10,15 +10,12 @@ import graphql.nadel.schema.NadelDirectives.deferDirectiveDefinition import graphql.nadel.schema.NadelDirectives.dynamicServiceDirectiveDefinition import graphql.nadel.schema.NadelDirectives.hiddenDirectiveDefinition import graphql.nadel.schema.NadelDirectives.hydratedDirectiveDefinition -import graphql.nadel.schema.NadelDirectives.hydratedFromDirectiveDefinition -import graphql.nadel.schema.NadelDirectives.hydratedTemplateDirectiveDefinition -import graphql.nadel.schema.NadelDirectives.nadelHydrationArgumentDefinition import graphql.nadel.schema.NadelDirectives.nadelBatchObjectIdentifiedByDefinition -import graphql.nadel.schema.NadelDirectives.nadelHydrationFromArgumentDefinition -import graphql.nadel.schema.NadelDirectives.nadelHydrationTemplateEnumDefinition +import graphql.nadel.schema.NadelDirectives.nadelHydrationArgumentDefinition import graphql.nadel.schema.NadelDirectives.nadelHydrationConditionDefinition -import graphql.nadel.schema.NadelDirectives.nadelHydrationResultFieldPredicateDefinition +import graphql.nadel.schema.NadelDirectives.nadelHydrationFromArgumentDefinition import graphql.nadel.schema.NadelDirectives.nadelHydrationResultConditionDefinition +import graphql.nadel.schema.NadelDirectives.nadelHydrationResultFieldPredicateDefinition import graphql.nadel.schema.NadelDirectives.namespacedDirectiveDefinition import graphql.nadel.schema.NadelDirectives.partitionDirectiveDefinition import graphql.nadel.schema.NadelDirectives.renamedDirectiveDefinition @@ -33,9 +30,9 @@ object NadelBuiltInTypes { ) val builtInScalarNames = builtInScalars - .asSequence() - .map { it.name } - .toSet() + .mapTo(LinkedHashSet()) { + it.name + } val builtInDirectiveSyntaxTypeNames = sequenceOf( renamedDirectiveDefinition, @@ -49,16 +46,13 @@ object NadelBuiltInTypes { nadelHydrationFromArgumentDefinition, nadelBatchObjectIdentifiedByDefinition, - nadelHydrationTemplateEnumDefinition, - hydratedFromDirectiveDefinition, - hydratedTemplateDirectiveDefinition, nadelHydrationResultFieldPredicateDefinition, nadelHydrationResultConditionDefinition, nadelHydrationConditionDefinition, - ).map { + ).mapTo(LinkedHashSet()) { it.name - }.toSet() + } val allNadelBuiltInTypeNames = builtInScalarNames + builtInDirectiveSyntaxTypeNames } diff --git a/lib/src/main/java/graphql/nadel/validation/util/NadelSchemaUtil.kt b/lib/src/main/java/graphql/nadel/validation/util/NadelSchemaUtil.kt index f7ea09a2d..2d4e2b7cb 100644 --- a/lib/src/main/java/graphql/nadel/validation/util/NadelSchemaUtil.kt +++ b/lib/src/main/java/graphql/nadel/validation/util/NadelSchemaUtil.kt @@ -6,6 +6,9 @@ import graphql.nadel.Service import graphql.nadel.dsl.FieldMappingDefinition import graphql.nadel.dsl.NadelHydrationDefinition import graphql.nadel.engine.util.operationTypes +import graphql.nadel.engine.blueprint.directives.NadelHydrationDefinition +import graphql.nadel.engine.blueprint.directives.getHydrationDefinitions +import graphql.nadel.engine.blueprint.directives.hasHydration import graphql.nadel.schema.NadelDirectives import graphql.nadel.util.NamespacedUtil import graphql.schema.GraphQLDirectiveContainer @@ -14,23 +17,17 @@ import graphql.schema.GraphQLNamedOutputType import graphql.schema.GraphQLNamedType import graphql.schema.GraphQLSchema -object NadelSchemaUtil { +internal object NadelSchemaUtil { fun getUnderlyingType(overallType: GraphQLNamedType, service: Service): GraphQLNamedType? { return service.underlyingSchema.getType(getRenamedFrom(overallType) ?: overallType.name) as GraphQLNamedType? } - fun getHydrations(field: GraphQLFieldDefinition, overallSchema: GraphQLSchema): List { - return NadelDirectives.createUnderlyingServiceHydration(field, overallSchema) - } - fun hasHydration(field: GraphQLFieldDefinition): Boolean { return hasHydration(field.definition!!) } fun hasHydration(def: FieldDefinition): Boolean { - val hydratedPresent = def.hasDirective(NadelDirectives.hydratedDirectiveDefinition.name) - val hydratedFromPresent = def.hasDirective(NadelDirectives.hydratedFromDirectiveDefinition.name) - return hydratedPresent || hydratedFromPresent + return def.hasHydration() } fun getRename(field: GraphQLFieldDefinition): FieldMappingDefinition? { diff --git a/lib/src/test/kotlin/graphql/nadel/schema/NadelDirectivesTest.kt b/lib/src/test/kotlin/graphql/nadel/schema/NadelDirectivesTest.kt index a86cc39ee..2f4bae8e5 100644 --- a/lib/src/test/kotlin/graphql/nadel/schema/NadelDirectivesTest.kt +++ b/lib/src/test/kotlin/graphql/nadel/schema/NadelDirectivesTest.kt @@ -1,17 +1,15 @@ package graphql.nadel.schema import graphql.language.AstPrinter -import graphql.nadel.dsl.RemoteArgumentSource +import graphql.nadel.engine.blueprint.directives.NadelHydrationArgumentDefinition +import graphql.nadel.engine.blueprint.directives.getHydrationDefinitions import graphql.nadel.schema.NadelDirectives.hydratedDirectiveDefinition -import graphql.nadel.schema.NadelDirectives.hydratedFromDirectiveDefinition -import graphql.nadel.schema.NadelDirectives.hydratedTemplateDirectiveDefinition -import graphql.nadel.schema.NadelDirectives.nadelHydrationArgumentDefinition import graphql.nadel.schema.NadelDirectives.nadelBatchObjectIdentifiedByDefinition -import graphql.nadel.schema.NadelDirectives.nadelHydrationFromArgumentDefinition -import graphql.nadel.schema.NadelDirectives.nadelHydrationTemplateEnumDefinition +import graphql.nadel.schema.NadelDirectives.nadelHydrationArgumentDefinition import graphql.nadel.schema.NadelDirectives.nadelHydrationConditionDefinition -import graphql.nadel.schema.NadelDirectives.nadelHydrationResultFieldPredicateDefinition +import graphql.nadel.schema.NadelDirectives.nadelHydrationFromArgumentDefinition import graphql.nadel.schema.NadelDirectives.nadelHydrationResultConditionDefinition +import graphql.nadel.schema.NadelDirectives.nadelHydrationResultFieldPredicateDefinition import graphql.nadel.schema.NadelDirectives.partitionDirectiveDefinition import graphql.schema.GraphQLSchema import graphql.schema.idl.RuntimeWiring @@ -25,15 +23,15 @@ import kotlin.test.assertTrue private const val source = "$" + "source" private const val argument = "$" + "argument" +/** + * todo: move these tests + */ class NadelDirectivesTest : DescribeSpec({ val commonDefs = """ ${AstPrinter.printAst(hydratedDirectiveDefinition)} ${AstPrinter.printAst(nadelHydrationArgumentDefinition)} ${AstPrinter.printAst(nadelBatchObjectIdentifiedByDefinition)} ${AstPrinter.printAst(nadelHydrationFromArgumentDefinition)} - ${AstPrinter.printAst(nadelHydrationTemplateEnumDefinition)} - ${AstPrinter.printAst(hydratedFromDirectiveDefinition)} - ${AstPrinter.printAst(hydratedTemplateDirectiveDefinition)} ${AstPrinter.printAst(nadelHydrationConditionDefinition)} ${AstPrinter.printAst(nadelHydrationResultFieldPredicateDefinition)} ${AstPrinter.printAst(nadelHydrationResultConditionDefinition)} @@ -82,134 +80,23 @@ class NadelDirectivesTest : DescribeSpec({ val field = schema.queryType.getField("field") // when - val hydration = NadelDirectives.createUnderlyingServiceHydration(field, schema).single() + val hydration = field.getHydrationDefinitions().single() // then - assert(hydration.serviceName == "IssueService") - assert(hydration.pathToActorField == listOf("jira", "issueById")) + assert(hydration.backingField == listOf("jira", "issueById")) assert(hydration.batchSize == 50) assert(hydration.timeout == 100) assert(hydration.arguments.size == 2) assertTrue(hydration.arguments[0].name == "fieldVal") - val firstArgumentSource = hydration.arguments[0].remoteArgumentSource - assertTrue(firstArgumentSource is RemoteArgumentSource.ObjectField) + val firstArgumentSource = hydration.arguments[0].value + assertTrue(firstArgumentSource is NadelHydrationArgumentDefinition.ValueSource.ObjectField) assertTrue(firstArgumentSource.pathToField == listOf("namespace", "issueId")) assertTrue(hydration.arguments[1].name == "argVal") - val secondArgumentSource = hydration.arguments[1].remoteArgumentSource - assertTrue(secondArgumentSource is RemoteArgumentSource.FieldArgument) + val secondArgumentSource = hydration.arguments[1].value + assertTrue(secondArgumentSource is NadelHydrationArgumentDefinition.ValueSource.FieldArgument) assertTrue(secondArgumentSource.argumentName == "cloudId") } } - - describe("@hydratedFrom") { - it("can parse") { - val schema = getSchema( - """ - extend enum NadelHydrationTemplate { - JIRA @hydratedTemplate( - service: "IssueService" - field: "jira.issueById" - batchSize: 50 - timeout: 100 - ) - } - type Query { - field: String - @hydratedFrom( - template: JIRA - arguments: [ - {name: "fieldVal" valueFromField: "namespace.issueId"} - {name: "argVal" valueFromArg: "cloudId"} - # for legacy confusion reasons - {name: "fieldValLegacy" valueFromField: "$source.namespace.issueId"} - {name: "argValLegacy" valueFromArg: "$argument.cloudId"} - ] - ) - } - """.trimIndent() - ) - - val fieldDef = schema.queryType.getFieldDefinition("field") - - // when - val hydration = NadelDirectives.createUnderlyingServiceHydration(fieldDef, schema).single() - - // then - assert(hydration.serviceName == "IssueService") - assert(hydration.pathToActorField == listOf("jira", "issueById")) - assert(hydration.batchSize == 50) - assert(hydration.timeout == 100) - assert(hydration.arguments.size == 4) - 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") { - data class Input(val arguments: String, val error: String) - - withData( - // none - Input( - arguments = """{name: "fieldVal"}""", - error = "NadelHydrationFromArgument requires one of valueFromField or valueFromArg to be set", - ), - // both - Input( - arguments = """{name: "fieldVal" valueFromField: "issueId" valueFromArg: "cloudId"}""", - error = "NadelHydrationFromArgument can not have both valueFromField and valueFromArg set", - ), - ) { (arguments, error) -> - // given - val schema = getSchema( - """ - extend enum NadelHydrationTemplate { - JIRA @hydratedTemplate( - service: "IssueService" - field: "jira.issueById" - batchSize: 50 - timeout: 100 - ) - } - type Query { - field: String - @hydratedFrom( - template: JIRA - arguments: [$arguments] - ) - } - """.trimIndent() - ) - val fieldDef = schema.queryType.getField("field") - - // when - val ex = assertThrows { - NadelDirectives.createUnderlyingServiceHydration(fieldDef, schema).single() - } - - // then - assert(ex.message == error) - } - } - } }) diff --git a/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationArgumentValidationTest.kt b/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationArgumentValidationTest.kt index caee43f38..15a493723 100644 --- a/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationArgumentValidationTest.kt +++ b/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationArgumentValidationTest.kt @@ -1,6 +1,6 @@ package graphql.nadel.validation -import graphql.nadel.dsl.RemoteArgumentSource +import graphql.nadel.engine.blueprint.directives.NadelHydrationArgumentDefinition import graphql.nadel.validation.NadelSchemaValidationError.IncompatibleFieldInHydratedInputObject import graphql.nadel.validation.NadelSchemaValidationError.IncompatibleHydrationArgumentType import graphql.nadel.validation.NadelSchemaValidationError.MissingFieldInHydratedInputObject @@ -81,7 +81,7 @@ class NadelHydrationArgumentValidationTest : DescribeSpec({ assert(GraphQLTypeUtil.simplePrint(error.actorArgInputType) == "Int") // supplied hydration for arg: assert(error.parentType.underlying.name == "Issue") - val remoteArgumentSource = error.remoteArg.remoteArgumentSource as RemoteArgumentSource.ObjectField + val remoteArgumentSource = error.remoteArg.value as NadelHydrationArgumentDefinition.ValueSource.ObjectField assert(remoteArgumentSource.pathToField.joinToString(separator = ".") == "creator") assert(GraphQLTypeUtil.simplePrint(error.hydrationType) == "ID!") } @@ -370,7 +370,7 @@ class NadelHydrationArgumentValidationTest : DescribeSpec({ assert(error.remoteArg.name == "id") assert(GraphQLTypeUtil.simplePrint(error.actorArgInputType) == "ID!") // supplied hydration for arg: - val remoteArgumentSource = error.remoteArg.remoteArgumentSource as RemoteArgumentSource.FieldArgument + val remoteArgumentSource = error.remoteArg.value as NadelHydrationArgumentDefinition.ValueSource.FieldArgument assert(remoteArgumentSource.argumentName == "creatorId") assert(GraphQLTypeUtil.simplePrint(error.hydrationType) == "ID") } @@ -551,7 +551,7 @@ class NadelHydrationArgumentValidationTest : DescribeSpec({ assert(GraphQLTypeUtil.simplePrint(error.actorArgInputType) == "[[String!]!]!") // supplied hydration for arg: assert(error.parentType.underlying.name == "Issue") - val remoteArgumentSource = error.remoteArg.remoteArgumentSource as RemoteArgumentSource.ObjectField + val remoteArgumentSource = error.remoteArg.value as NadelHydrationArgumentDefinition.ValueSource.ObjectField assert(remoteArgumentSource.pathToField.joinToString(separator = ".") == "creators") assert(GraphQLTypeUtil.simplePrint(error.hydrationType) == "[[Int!]!]!") } @@ -706,7 +706,7 @@ class NadelHydrationArgumentValidationTest : DescribeSpec({ assert(error.remoteArg.name == "name") // supplied hydration for arg: assert(error.parentType.underlying.name == "Issue") - val remoteArgumentSource = error.remoteArg.remoteArgumentSource as RemoteArgumentSource.ObjectField + val remoteArgumentSource = error.remoteArg.value as NadelHydrationArgumentDefinition.ValueSource.ObjectField assert(remoteArgumentSource.pathToField.joinToString(separator = ".") == "creator") } @@ -953,7 +953,7 @@ class NadelHydrationArgumentValidationTest : DescribeSpec({ assert(error.remoteArg.name == "userInfo") // supplied hydration for arg: assert(error.parentType.underlying.name == "Issue") - val remoteArgumentSource = error.remoteArg.remoteArgumentSource as RemoteArgumentSource.ObjectField + val remoteArgumentSource = error.remoteArg.value as NadelHydrationArgumentDefinition.ValueSource.ObjectField assert(remoteArgumentSource.pathToField.joinToString(separator = ".") == "creator") } @@ -1107,7 +1107,7 @@ class NadelHydrationArgumentValidationTest : DescribeSpec({ assert(GraphQLTypeUtil.simplePrint(error.actorArgInputType) == "[FullNameInput]!") // supplied hydration for arg: assert(error.parentType.underlying.name == "Issue") - val remoteArgumentSource = error.remoteArg.remoteArgumentSource as RemoteArgumentSource.ObjectField + val remoteArgumentSource = error.remoteArg.value as NadelHydrationArgumentDefinition.ValueSource.ObjectField assert(remoteArgumentSource.pathToField.joinToString(separator = ".") == "creators") assert(GraphQLTypeUtil.simplePrint(error.hydrationType) == "[FullName]!") @@ -1262,7 +1262,7 @@ class NadelHydrationArgumentValidationTest : DescribeSpec({ assert(GraphQLTypeUtil.simplePrint(error.actorArgInputType) == "[UserInput]") // supplied hydration for arg: assert(error.parentType.underlying.name == "Issue") - val remoteArgumentSource = error.remoteArg.remoteArgumentSource as RemoteArgumentSource.ObjectField + val remoteArgumentSource = error.remoteArg.value as NadelHydrationArgumentDefinition.ValueSource.ObjectField assert(remoteArgumentSource.pathToField.joinToString(separator = ".") == "creators") assert(GraphQLTypeUtil.simplePrint(error.hydrationType) == "[UserRef]") } @@ -1571,7 +1571,7 @@ class NadelHydrationArgumentValidationTest : DescribeSpec({ assert(GraphQLTypeUtil.simplePrint(error.actorArgInputType) == "SomeOtherEnumType") // supplied hydration for arg: assert(error.parentType.underlying.name == "Issue") - val remoteArgumentSource = error.remoteArg.remoteArgumentSource as RemoteArgumentSource.ObjectField + val remoteArgumentSource = error.remoteArg.value as NadelHydrationArgumentDefinition.ValueSource.ObjectField assert(remoteArgumentSource.pathToField.joinToString(separator = ".") == "providerType") assert(GraphQLTypeUtil.simplePrint(error.hydrationType) == "ProviderType") } diff --git a/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationValidationTest.kt b/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationValidationTest.kt index c54da69c9..fb1726be5 100644 --- a/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationValidationTest.kt +++ b/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationValidationTest.kt @@ -6,7 +6,6 @@ import graphql.nadel.validation.NadelSchemaValidationError.DuplicatedHydrationAr import graphql.nadel.validation.NadelSchemaValidationError.HydrationFieldMustBeNullable import graphql.nadel.validation.NadelSchemaValidationError.HydrationIncompatibleOutputType import graphql.nadel.validation.NadelSchemaValidationError.MissingHydrationActorField -import graphql.nadel.validation.NadelSchemaValidationError.MissingHydrationActorService import graphql.nadel.validation.NadelSchemaValidationError.MissingHydrationArgumentValueSource import graphql.nadel.validation.NadelSchemaValidationError.MissingHydrationFieldValueSource import graphql.nadel.validation.NadelSchemaValidationError.MissingRequiredHydrationActorFieldArgument @@ -134,8 +133,8 @@ class NadelHydrationValidationTest : DescribeSpec({ it("fails when batch hydration with no \$source args") { val fixture = NadelValidationTestFixture( - overallSchema = mapOf( - "issues" to """ + overallSchema = mapOf( + "issues" to """ type Query { issue: JiraIssue } @@ -151,7 +150,7 @@ class NadelHydrationValidationTest : DescribeSpec({ ) } """.trimIndent(), - "users" to """ + "users" to """ type Query { users(id: ID!, siteId: ID!): [User] } @@ -160,9 +159,9 @@ class NadelHydrationValidationTest : DescribeSpec({ name: String! } """.trimIndent(), - ), - underlyingSchema = mapOf( - "issues" to """ + ), + underlyingSchema = mapOf( + "issues" to """ type Query { issue: Issue } @@ -171,7 +170,7 @@ class NadelHydrationValidationTest : DescribeSpec({ creator: ID! } """.trimIndent(), - "users" to """ + "users" to """ type Query { users(id: ID!, siteId: ID!): [User] } @@ -180,7 +179,7 @@ class NadelHydrationValidationTest : DescribeSpec({ name: String! } """.trimIndent(), - ), + ), ) val errors = validate(fixture) @@ -352,7 +351,6 @@ class NadelHydrationValidationTest : DescribeSpec({ assert(errors.size == 1) val error = errors.assertSingleOfType() assert(error.service.name == "issues") - assert(error.hydration.serviceName == "users") assert(error.overallField.name == "creator") assert(error.parentType.overall.name == "Issue") } @@ -473,66 +471,6 @@ class NadelHydrationValidationTest : DescribeSpec({ assert(errors.map { it.message }.isEmpty()) } - it("fails if hydration actor service does not exist") { - val fixture = NadelValidationTestFixture( - overallSchema = mapOf( - "issues" to """ - type Query { - issue: JiraIssue - } - type JiraIssue @renamed(from: "Issue") { - id: ID! - } - """.trimIndent(), - "users" to """ - type User { - id: ID! - name: String! - } - extend type JiraIssue { - creator: User @hydrated( - service: "userService" - field: "user" - arguments: [ - {name: "id", value: "$source.creator"} - ] - ) - } - """.trimIndent(), - ), - underlyingSchema = mapOf( - "issues" to """ - type Query { - issue: Issue - } - type Issue { - id: ID! - creator: ID! - } - """.trimIndent(), - "users" to """ - type Query { - user(id: ID!): User - } - type User { - id: ID! - name: String! - } - """.trimIndent(), - ), - ) - - val errors = validate(fixture) - assert(errors.map { it.message }.isNotEmpty()) - - val error = errors.assertSingleOfType() - assert(error.parentType.overall.name == "JiraIssue") - assert(error.parentType.underlying.name == "Issue") - assert(error.overallField.name == "creator") - assert(error.subject == error.overallField) - assert(error.hydration.serviceName == "userService") - } - it("fails if hydrated field is not nullable") { val fixture = NadelValidationTestFixture( overallSchema = mapOf( @@ -642,7 +580,7 @@ class NadelHydrationValidationTest : DescribeSpec({ assert(error.parentType.overall.name == "Issue") assert(error.parentType.underlying.name == "Issue") assert(error.overallField.name == "creator") - assert(error.hydration.pathToActorField == listOf("userById")) + assert(error.hydration.backingField == listOf("userById")) assert(error.overallField == error.subject) } diff --git a/lib/src/test/kotlin/graphql/nadel/validation/NadelPolymorphicHydrationValidationTest.kt b/lib/src/test/kotlin/graphql/nadel/validation/NadelPolymorphicHydrationValidationTest.kt index c7d42d75b..426d94edd 100644 --- a/lib/src/test/kotlin/graphql/nadel/validation/NadelPolymorphicHydrationValidationTest.kt +++ b/lib/src/test/kotlin/graphql/nadel/validation/NadelPolymorphicHydrationValidationTest.kt @@ -185,7 +185,7 @@ class NadelPolymorphicHydrationValidationTest : DescribeSpec({ assert(error.service.name == "issues") assert(error.parentType.overall.name == "Issue") assert(error.overallField.name == "creator") - assert(error.hydration.pathToActorField == listOf("internalUser")) + assert(error.hydration.backingField == listOf("internalUser")) } it("fails if a mix of batched and non-batched hydrations is used") { From 91a00109ebfb79ae23a68667674c84aadd36747b Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Mon, 21 Oct 2024 20:34:34 +1100 Subject: [PATCH 3/9] Replace all old dsl package with directive wrapper classes --- .../NadelBatchObjectIdentifiedByDefinition.kt | 19 ++++ .../NadelHydrationArgumentDefinition.kt | 65 ++++++++++++++ .../NadelHydrationConditionDefinition.kt | 8 +- .../hydration}/NadelHydrationDefinition.kt | 89 +------------------ .../hydration}/PartitionDefinition.kt | 0 .../renamed/NadelRenamedDefinition.kt | 55 ++++++++++++ .../virtualType}/NadelVirtualTypeDirective.kt | 2 +- .../nadel/dsl/FieldMappingDefinition.kt | 5 -- .../nadel/dsl/RemoteArgumentDefinition.kt | 6 -- .../graphql/nadel/dsl/RemoteArgumentSource.kt | 38 -------- .../nadel/dsl/TypeMappingDefinition.kt | 6 -- .../NadelExecutionBlueprintFactory.kt | 65 +++++--------- .../NadelVirtualTypeBlueprintFactory.kt | 5 +- .../graphql/nadel/schema/NadelDirectives.kt | 26 +----- .../java/graphql/nadel/util/GraphQLUtil.kt | 6 ++ .../nadel/validation/NadelFieldValidation.kt | 9 +- .../NadelHydrationArgumentValidation.kt | 4 +- .../NadelHydrationConditionValidation.kt | 6 +- .../validation/NadelHydrationValidation.kt | 13 ++- .../nadel/validation/NadelRenameValidation.kt | 12 +-- .../validation/NadelSchemaValidationError.kt | 12 +-- .../validation/util/NadelGetReachableTypes.kt | 3 +- .../nadel/validation/util/NadelSchemaUtil.kt | 42 ++------- .../nadel/schema/NadelDirectivesTest.kt | 6 +- .../NadelHydrationArgumentValidationTest.kt | 2 +- .../tests/next/UnderlyingSchemaGenerator.kt | 7 +- 26 files changed, 220 insertions(+), 291 deletions(-) create mode 100644 lib/src/main/java/graphql/nadel/definition/hydration/NadelBatchObjectIdentifiedByDefinition.kt create mode 100644 lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationArgumentDefinition.kt rename lib/src/main/java/graphql/nadel/{dsl => definition/hydration}/NadelHydrationConditionDefinition.kt (92%) rename lib/src/main/java/graphql/nadel/{engine/blueprint/directives => definition/hydration}/NadelHydrationDefinition.kt (54%) rename lib/src/main/java/graphql/nadel/{dsl => definition/hydration}/PartitionDefinition.kt (100%) create mode 100644 lib/src/main/java/graphql/nadel/definition/renamed/NadelRenamedDefinition.kt rename lib/src/main/java/graphql/nadel/{engine/blueprint/directives => definition/virtualType}/NadelVirtualTypeDirective.kt (86%) delete mode 100644 lib/src/main/java/graphql/nadel/dsl/FieldMappingDefinition.kt delete mode 100644 lib/src/main/java/graphql/nadel/dsl/RemoteArgumentDefinition.kt delete mode 100644 lib/src/main/java/graphql/nadel/dsl/RemoteArgumentSource.kt delete mode 100644 lib/src/main/java/graphql/nadel/dsl/TypeMappingDefinition.kt diff --git a/lib/src/main/java/graphql/nadel/definition/hydration/NadelBatchObjectIdentifiedByDefinition.kt b/lib/src/main/java/graphql/nadel/definition/hydration/NadelBatchObjectIdentifiedByDefinition.kt new file mode 100644 index 000000000..0b9bf5dc2 --- /dev/null +++ b/lib/src/main/java/graphql/nadel/definition/hydration/NadelBatchObjectIdentifiedByDefinition.kt @@ -0,0 +1,19 @@ +package graphql.nadel.definition.hydration + +import graphql.language.ObjectValue +import graphql.language.StringValue +import graphql.nadel.util.getObjectField + +class NadelBatchObjectIdentifiedByDefinition( + private val objectValue: ObjectValue, +) { + val sourceId: String + get() = (objectValue.getObjectField(Keyword.sourceId).value as StringValue).value + val resultId: String + get() = (objectValue.getObjectField(Keyword.resultId).value as StringValue).value + + internal object Keyword { + const val sourceId = "sourceId" + const val resultId = "resultId" + } +} diff --git a/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationArgumentDefinition.kt b/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationArgumentDefinition.kt new file mode 100644 index 000000000..307d9e5c5 --- /dev/null +++ b/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationArgumentDefinition.kt @@ -0,0 +1,65 @@ +package graphql.nadel.definition.hydration + +import graphql.language.ObjectValue +import graphql.language.StringValue +import graphql.nadel.util.AnyAstValue +import graphql.nadel.util.getObjectField + +/** + * Argument belonging to [NadelHydrationDefinition.arguments] + */ +class NadelHydrationArgumentDefinition( + private val argumentObject: ObjectValue, +) { + /** + * Name of the backing field's argument. + */ + val name: String + get() = (argumentObject.getObjectField(Keyword.name).value as StringValue).value + + /** + * Value to support to the backing field's argument at runtime. + */ + val value: ValueSource + get() = ValueSource.from(argumentObject.getObjectField(Keyword.value).value) + + internal object Keyword { + const val name = "name" + const val value = "value" + } + + sealed class ValueSource { + data class ObjectField( + val pathToField: List, + ) : ValueSource() + + data class FieldArgument( + val argumentName: String, + ) : ValueSource() + + data class StaticArgument( + val staticValue: AnyAstValue, + ) : ValueSource() + + companion object { + fun from(astValue: AnyAstValue): ValueSource { + return if (astValue is StringValue && astValue.value.startsWith("$")) { + val command = astValue.value.substringBefore(".") + val values = astValue.value.substringAfter(".").split('.') + + when (command) { + "\$source" -> ObjectField( + pathToField = values, + ) + "\$argument" -> FieldArgument( + argumentName = values.single(), + ) + else -> StaticArgument(staticValue = astValue) + } + } else { + StaticArgument(staticValue = astValue) + } + } + } + } +} diff --git a/lib/src/main/java/graphql/nadel/dsl/NadelHydrationConditionDefinition.kt b/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationConditionDefinition.kt similarity index 92% rename from lib/src/main/java/graphql/nadel/dsl/NadelHydrationConditionDefinition.kt rename to lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationConditionDefinition.kt index 845ab029b..2921e578f 100644 --- a/lib/src/main/java/graphql/nadel/dsl/NadelHydrationConditionDefinition.kt +++ b/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationConditionDefinition.kt @@ -1,4 +1,4 @@ -package graphql.nadel.dsl +package graphql.nadel.definition.hydration import graphql.nadel.engine.util.JsonMap @@ -41,11 +41,17 @@ data class NadelHydrationConditionDefinition( } } +/** + * What field should match what value. + */ data class NadelHydrationResultConditionDefinition( val pathToSourceField: List, val predicate: NadelHydrationConditionPredicateDefinition, ) +/** + * How a given value must match. + */ data class NadelHydrationConditionPredicateDefinition( val equals: Any?, val startsWith: String?, diff --git a/lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelHydrationDefinition.kt b/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationDefinition.kt similarity index 54% rename from lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelHydrationDefinition.kt rename to lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationDefinition.kt index 6b1d31c06..45b1755fc 100644 --- a/lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelHydrationDefinition.kt +++ b/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationDefinition.kt @@ -1,22 +1,18 @@ -package graphql.nadel.engine.blueprint.directives +package graphql.nadel.definition.hydration import graphql.language.ArrayValue import graphql.language.FieldDefinition -import graphql.language.ObjectField import graphql.language.ObjectValue -import graphql.language.StringValue -import graphql.nadel.dsl.NadelHydrationConditionDefinition -import graphql.nadel.engine.blueprint.directives.NadelHydrationDefinition.Keyword +import graphql.nadel.definition.hydration.NadelHydrationDefinition.Keyword import graphql.nadel.engine.util.JsonMap -import graphql.nadel.util.AnyAstValue import graphql.schema.GraphQLAppliedDirective import graphql.schema.GraphQLFieldDefinition -fun FieldDefinition.hasHydration(): Boolean { +fun FieldDefinition.isHydrated(): Boolean { return hasDirective(Keyword.hydrated) } -fun GraphQLFieldDefinition.hasHydration(): Boolean { +fun GraphQLFieldDefinition.isHydrated(): Boolean { return hasAppliedDirective(Keyword.hydrated) } @@ -106,80 +102,3 @@ class NadelHydrationDefinition( const val inputIdentifiedBy = "inputIdentifiedBy" } } - -class NadelBatchObjectIdentifiedByDefinition( - private val objectValue: ObjectValue, -) { - val sourceId: String - get() = (objectValue.getObjectField(Keyword.sourceId).value as StringValue).value - val resultId: String - get() = (objectValue.getObjectField(Keyword.resultId).value as StringValue).value - - internal object Keyword { - const val sourceId = "sourceId" - const val resultId = "resultId" - } -} - -/** - * Argument belonging to [NadelHydrationDefinition.arguments] - */ -class NadelHydrationArgumentDefinition( - private val argumentObject: ObjectValue, -) { - /** - * Name of the backing field's argument. - */ - val name: String - get() = (argumentObject.getObjectField(Keyword.name).value as StringValue).value - - /** - * Value to support to the backing field's argument at runtime. - */ - val value: ValueSource - get() = ValueSource.from(argumentObject.getObjectField(Keyword.value).value) - - internal object Keyword { - const val name = "name" - const val value = "value" - } - - sealed class ValueSource { - data class ObjectField( - val pathToField: List, - ) : ValueSource() - - data class FieldArgument( - val argumentName: String, - ) : ValueSource() - - data class StaticArgument( - val staticValue: AnyAstValue, - ) : ValueSource() - - companion object { - fun from(astValue: AnyAstValue): ValueSource { - return if (astValue is StringValue && astValue.value.startsWith("$")) { - val command = astValue.value.substringBefore(".") - val values = astValue.value.substringAfter(".").split('.') - - when (command) { - "\$source" -> ObjectField( - pathToField = values, - ) - "\$argument" -> FieldArgument( - argumentName = values.single(), - ) - else -> StaticArgument(staticValue = astValue) - } - } else { - StaticArgument(staticValue = astValue) - } - } - } - } -} - -internal fun ObjectValue.getObjectField(name: String): ObjectField { - return objectFields.first { it.name == name } -} diff --git a/lib/src/main/java/graphql/nadel/dsl/PartitionDefinition.kt b/lib/src/main/java/graphql/nadel/definition/hydration/PartitionDefinition.kt similarity index 100% rename from lib/src/main/java/graphql/nadel/dsl/PartitionDefinition.kt rename to lib/src/main/java/graphql/nadel/definition/hydration/PartitionDefinition.kt diff --git a/lib/src/main/java/graphql/nadel/definition/renamed/NadelRenamedDefinition.kt b/lib/src/main/java/graphql/nadel/definition/renamed/NadelRenamedDefinition.kt new file mode 100644 index 000000000..2574700ec --- /dev/null +++ b/lib/src/main/java/graphql/nadel/definition/renamed/NadelRenamedDefinition.kt @@ -0,0 +1,55 @@ +package graphql.nadel.definition.renamed + +import graphql.language.DirectivesContainer +import graphql.nadel.definition.renamed.NadelRenamedDefinition.Keyword +import graphql.nadel.schema.NadelDirectives +import graphql.schema.GraphQLAppliedDirective +import graphql.schema.GraphQLDirectiveContainer +import graphql.schema.GraphQLFieldDefinition +import graphql.schema.GraphQLNamedType + +sealed class NadelRenamedDefinition { + class Type( + private val appliedDirective: GraphQLAppliedDirective, + ) : NadelRenamedDefinition() { + val from: String + get() = appliedDirective.getArgument(Keyword.from).getValue() + } + + class Field( + private val appliedDirective: GraphQLAppliedDirective, + ) : NadelRenamedDefinition() { + val from: List + get() = rawFrom.split(".") + + val rawFrom: String + get() = appliedDirective.getArgument(Keyword.from).getValue() + } + + object Keyword { + const val renamed = "renamed" + const val from = "from" + } +} + +fun GraphQLDirectiveContainer.isRenamed(): Boolean { + return hasAppliedDirective(Keyword.renamed) +} + +fun DirectivesContainer<*>.isRenamed(): Boolean { + return hasDirective(NadelDirectives.renamedDirectiveDefinition.name) +} + +fun GraphQLFieldDefinition.getRenamedOrNull(): NadelRenamedDefinition.Field? { + val directive = getAppliedDirective(Keyword.renamed) + ?: return null + + return NadelRenamedDefinition.Field(directive) +} + +fun GraphQLNamedType.getRenamedOrNull(): NadelRenamedDefinition.Type? { + val directive = (this as GraphQLDirectiveContainer).getAppliedDirective(Keyword.renamed) + ?: return null + + return NadelRenamedDefinition.Type(directive) +} diff --git a/lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelVirtualTypeDirective.kt b/lib/src/main/java/graphql/nadel/definition/virtualType/NadelVirtualTypeDirective.kt similarity index 86% rename from lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelVirtualTypeDirective.kt rename to lib/src/main/java/graphql/nadel/definition/virtualType/NadelVirtualTypeDirective.kt index 19e8cd9f7..c3fe1adfa 100644 --- a/lib/src/main/java/graphql/nadel/engine/blueprint/directives/NadelVirtualTypeDirective.kt +++ b/lib/src/main/java/graphql/nadel/definition/virtualType/NadelVirtualTypeDirective.kt @@ -1,4 +1,4 @@ -package graphql.nadel.engine.blueprint.directives +package graphql.nadel.definition.virtualType import graphql.schema.GraphQLDirectiveContainer diff --git a/lib/src/main/java/graphql/nadel/dsl/FieldMappingDefinition.kt b/lib/src/main/java/graphql/nadel/dsl/FieldMappingDefinition.kt deleted file mode 100644 index aa81874ba..000000000 --- a/lib/src/main/java/graphql/nadel/dsl/FieldMappingDefinition.kt +++ /dev/null @@ -1,5 +0,0 @@ -package graphql.nadel.dsl - -data class FieldMappingDefinition( - val inputPath: List, -) diff --git a/lib/src/main/java/graphql/nadel/dsl/RemoteArgumentDefinition.kt b/lib/src/main/java/graphql/nadel/dsl/RemoteArgumentDefinition.kt deleted file mode 100644 index a287b698d..000000000 --- a/lib/src/main/java/graphql/nadel/dsl/RemoteArgumentDefinition.kt +++ /dev/null @@ -1,6 +0,0 @@ -package graphql.nadel.dsl - -data class RemoteArgumentDefinition( - val name: String, - val remoteArgumentSource: RemoteArgumentSource, -) diff --git a/lib/src/main/java/graphql/nadel/dsl/RemoteArgumentSource.kt b/lib/src/main/java/graphql/nadel/dsl/RemoteArgumentSource.kt deleted file mode 100644 index 1bea86212..000000000 --- a/lib/src/main/java/graphql/nadel/dsl/RemoteArgumentSource.kt +++ /dev/null @@ -1,38 +0,0 @@ -package graphql.nadel.dsl - -import graphql.nadel.util.AnyAstValue - -sealed class RemoteArgumentSource { - @Deprecated( - replaceWith = ReplaceWith( - "NadelHydrationArgumentDefinition.ValueSource.ObjectField", - imports = ["graphql.nadel.engine.blueprint.directives.NadelHydrationArgumentDefinition"] - ), - message = "Do not use", - ) - data class ObjectField( - val pathToField: List, - ) : RemoteArgumentSource() - - @Deprecated( - replaceWith = ReplaceWith( - "NadelHydrationArgumentDefinition.ValueSource.FieldArgument", - imports = ["graphql.nadel.engine.blueprint.directives.NadelHydrationArgumentDefinition"] - ), - message = "Do not use", - ) - data class FieldArgument( - val argumentName: String, - ) : RemoteArgumentSource() - - @Deprecated( - replaceWith = ReplaceWith( - "NadelHydrationArgumentDefinition.ValueSource.StaticArgument", - imports = ["graphql.nadel.engine.blueprint.directives.NadelHydrationArgumentDefinition"] - ), - message = "Do not use", - ) - data class StaticArgument( - val staticValue: AnyAstValue, - ) : RemoteArgumentSource() -} diff --git a/lib/src/main/java/graphql/nadel/dsl/TypeMappingDefinition.kt b/lib/src/main/java/graphql/nadel/dsl/TypeMappingDefinition.kt deleted file mode 100644 index 688168678..000000000 --- a/lib/src/main/java/graphql/nadel/dsl/TypeMappingDefinition.kt +++ /dev/null @@ -1,6 +0,0 @@ -package graphql.nadel.dsl - -data class TypeMappingDefinition( - val underlyingName: String, - val overallName: String, -) 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 5c48db489..578533eba 100644 --- a/lib/src/main/java/graphql/nadel/engine/blueprint/NadelExecutionBlueprintFactory.kt +++ b/lib/src/main/java/graphql/nadel/engine/blueprint/NadelExecutionBlueprintFactory.kt @@ -9,11 +9,11 @@ import graphql.language.EnumTypeDefinition import graphql.language.FieldDefinition import graphql.language.ImplementingTypeDefinition import graphql.nadel.Service -import graphql.nadel.dsl.FieldMappingDefinition -import graphql.nadel.dsl.TypeMappingDefinition -import graphql.nadel.engine.blueprint.directives.NadelHydrationArgumentDefinition -import graphql.nadel.engine.blueprint.directives.NadelHydrationDefinition -import graphql.nadel.engine.blueprint.directives.getHydrationDefinitions +import graphql.nadel.definition.hydration.NadelHydrationArgumentDefinition +import graphql.nadel.definition.hydration.NadelHydrationDefinition +import graphql.nadel.definition.hydration.getHydrationDefinitions +import graphql.nadel.definition.renamed.NadelRenamedDefinition +import graphql.nadel.definition.renamed.getRenamedOrNull import graphql.nadel.engine.blueprint.directives.isVirtualType import graphql.nadel.engine.blueprint.hydration.NadelBatchHydrationMatchStrategy import graphql.nadel.engine.blueprint.hydration.NadelHydrationActorInputDef @@ -36,12 +36,11 @@ import graphql.nadel.engine.util.mapFrom import graphql.nadel.engine.util.strictAssociateBy import graphql.nadel.engine.util.unwrapAll import graphql.nadel.engine.util.unwrapNonNull -import graphql.nadel.schema.NadelDirectives import graphql.nadel.util.AnyAstValue import graphql.schema.FieldCoordinates -import graphql.schema.GraphQLDirectiveContainer import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLFieldsContainer +import graphql.schema.GraphQLNamedType import graphql.schema.GraphQLObjectType import graphql.schema.GraphQLScalarType import graphql.schema.GraphQLSchema @@ -189,14 +188,14 @@ private class Factory( .asSequence() // Get the field mapping def .flatMap { field -> - when (val mappingDefinition = getFieldMappingDefinition(field)) { + when (val renamedDefinition = field.getRenamedOrNull()) { null -> { field.getHydrationDefinitions() .map { makeHydrationFieldInstruction(type, field, it) } + makePartitionInstruction(type, field) } - else -> when (mappingDefinition.inputPath.size) { - 1 -> listOf(makeRenameInstruction(type, field, mappingDefinition)) - else -> listOf(makeDeepRenameFieldInstruction(type, field, mappingDefinition)) + else -> when (renamedDefinition.from.size) { + 1 -> listOf(makeRenameInstruction(type, field, renamedDefinition)) + else -> listOf(makeDeepRenameFieldInstruction(type, field, renamedDefinition)) } } } @@ -207,13 +206,13 @@ private class Factory( private fun makeDeepRenameFieldInstruction( parentType: GraphQLObjectType, field: GraphQLFieldDefinition, - mappingDefinition: FieldMappingDefinition, + renamedDefinition: NadelRenamedDefinition.Field, ): NadelFieldInstruction { val location = makeFieldCoordinates(parentType, field) return NadelDeepRenameFieldInstruction( location, - NadelQueryPath(mappingDefinition.inputPath), + NadelQueryPath(renamedDefinition.from), ) } @@ -266,7 +265,7 @@ private class Factory( virtualTypeContext = virtualTypeBlueprintFactory.makeVirtualTypeContext( engineSchema = engineSchema, containerType = hydratedFieldParentType, - virtualFieldDef = hydratedFieldDef + virtualFieldDef = hydratedFieldDef, ), sourceFields = getHydrationSourceFields(hydrationArgs, condition), condition = condition, @@ -478,11 +477,11 @@ private class Factory( private fun makeRenameInstruction( parentType: GraphQLObjectType, field: GraphQLFieldDefinition, - mappingDefinition: FieldMappingDefinition, + renamedDefinition: NadelRenamedDefinition.Field, ): NadelRenameFieldInstruction { return NadelRenameFieldInstruction( location = makeFieldCoordinates(parentType, field), - underlyingName = mappingDefinition.inputPath.single(), + underlyingName = renamedDefinition.from.single(), ) } @@ -504,38 +503,23 @@ private class Factory( private fun makeTypeRenameInstructions(): Sequence { return engineSchema.typeMap.values .asSequence() - .filterIsInstance() .mapNotNull(this::makeTypeRenameInstruction) } - private fun makeTypeRenameInstruction(type: GraphQLDirectiveContainer): NadelTypeRenameInstruction? { - return when (type.definition) { - else -> when (val typeMappingDef = createTypeMapping(type)) { - null -> null - else -> makeTypeRenameInstruction(typeMappingDef) - } - } - } - - private fun createTypeMapping(type: GraphQLDirectiveContainer): TypeMappingDefinition? { + private fun makeTypeRenameInstruction(overallType: GraphQLNamedType): NadelTypeRenameInstruction? { // Fixes bug with jsw schema // These don't really mean anything anyway as these cannot be used as fragment type conditions // And we don't have variables in normalized queries so they can't be var types - if (type is GraphQLScalarType) { + if (overallType is GraphQLScalarType) { return null } - return NadelDirectives.createTypeMapping(type) - } - - private fun makeTypeRenameInstruction(typeMappingDefinition: TypeMappingDefinition): NadelTypeRenameInstruction { - val overallName = typeMappingDefinition.overallName + val renamed = overallType.getRenamedOrNull() ?: return null return NadelTypeRenameInstruction( - service = definitionNamesToService[overallName] - ?: error("Unable to determine what service owns type: $overallName"), - overallName = overallName, - underlyingName = typeMappingDefinition.underlyingName, + service = definitionNamesToService[overallType.name]!!, + overallName = overallType.name, + underlyingName = renamed.from, ) } @@ -607,8 +591,7 @@ private class Factory( overallType: GraphQLObjectType, childField: GraphQLFieldDefinition, ): GraphQLObjectType? { - val renameInstruction = makeTypeRenameInstruction(overallType as? GraphQLDirectiveContainer ?: return null) - val underlyingName = renameInstruction?.underlyingName ?: overallType.name + val underlyingName = overallType.getRenamedOrNull()?.from ?: overallType.name val fieldCoordinates = makeFieldCoordinates(overallType, childField) @@ -619,10 +602,6 @@ private class Factory( return service.underlyingSchema.getTypeAs(underlyingName) } - private fun getFieldMappingDefinition(field: GraphQLFieldDefinition): FieldMappingDefinition? { - return NadelDirectives.createFieldMapping(field) - } - private fun deriveUnderlyingBlueprints( typeRenameInstructions: List, ): Map { diff --git a/lib/src/main/java/graphql/nadel/engine/blueprint/NadelVirtualTypeBlueprintFactory.kt b/lib/src/main/java/graphql/nadel/engine/blueprint/NadelVirtualTypeBlueprintFactory.kt index c07de520a..34cfb884d 100644 --- a/lib/src/main/java/graphql/nadel/engine/blueprint/NadelVirtualTypeBlueprintFactory.kt +++ b/lib/src/main/java/graphql/nadel/engine/blueprint/NadelVirtualTypeBlueprintFactory.kt @@ -1,6 +1,6 @@ package graphql.nadel.engine.blueprint -import graphql.nadel.engine.blueprint.directives.getHydrationDefinitions +import graphql.nadel.definition.hydration.getHydrationDefinitions import graphql.nadel.engine.util.getFieldAt import graphql.nadel.engine.util.unwrapAll import graphql.schema.GraphQLFieldDefinition @@ -49,8 +49,7 @@ internal class NadelVirtualTypeBlueprintFactory { private fun createTypeMappings( virtualFieldDef: GraphQLFieldDefinition, ): List { - val hydration = virtualFieldDef.getHydrationDefinitions().firstOrNull() - ?: return emptyList() // We should use the non-null method once we delete @hydratedFrom + val hydration = virtualFieldDef.getHydrationDefinitions().first() val backingFieldDef = engineSchema.queryType.getFieldAt(hydration.backingField)!! val backingType = backingFieldDef.type.unwrapAll() as? GraphQLObjectType ?: return emptyList() diff --git a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt index b5c4f5b59..d27066eb8 100644 --- a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt +++ b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt @@ -3,22 +3,13 @@ package graphql.nadel.schema import graphql.GraphQLContext import graphql.execution.ValuesResolver import graphql.language.DirectiveDefinition -import graphql.language.EnumTypeDefinition import graphql.language.InputObjectTypeDefinition import graphql.language.SDLDefinition -import graphql.nadel.dsl.FieldMappingDefinition -import graphql.nadel.dsl.NadelHydrationConditionDefinition -import graphql.nadel.dsl.NadelHydrationConditionPredicateDefinition -import graphql.nadel.dsl.NadelHydrationResultConditionDefinition import graphql.nadel.dsl.NadelPartitionDefinition -import graphql.nadel.dsl.RemoteArgumentDefinition -import graphql.nadel.dsl.TypeMappingDefinition -import graphql.nadel.engine.blueprint.directives.NadelHydrationDefinition import graphql.nadel.engine.util.singleOfType import graphql.parser.Parser import graphql.schema.GraphQLAppliedDirective import graphql.schema.GraphQLAppliedDirectiveArgument -import graphql.schema.GraphQLDirectiveContainer import graphql.schema.GraphQLFieldDefinition import java.util.Locale @@ -174,22 +165,7 @@ object NadelDirectives { """.trimIndent() ) - internal fun createFieldMapping(fieldDefinition: GraphQLFieldDefinition): FieldMappingDefinition? { - val directive = fieldDefinition.getAppliedDirective(renamedDirectiveDefinition.name) - ?: return null - val fromValue = getDirectiveValue(directive, "from") - - return FieldMappingDefinition(inputPath = fromValue.split('.')) - } - - internal fun createTypeMapping(directivesContainer: GraphQLDirectiveContainer): TypeMappingDefinition? { - val directive = directivesContainer.getAppliedDirective(renamedDirectiveDefinition.name) - ?: return null - val from = getDirectiveValue(directive, "from") - - return TypeMappingDefinition(underlyingName = from, overallName = directivesContainer.name) - } - + @Deprecated(message = "To be replaced with directive wrapper class") internal fun createPartitionDefinition(fieldDefinition: GraphQLFieldDefinition): NadelPartitionDefinition? { val directive = fieldDefinition.getAppliedDirective(partitionDirectiveDefinition.name) ?: return null diff --git a/lib/src/main/java/graphql/nadel/util/GraphQLUtil.kt b/lib/src/main/java/graphql/nadel/util/GraphQLUtil.kt index 15277a6fc..03df81842 100644 --- a/lib/src/main/java/graphql/nadel/util/GraphQLUtil.kt +++ b/lib/src/main/java/graphql/nadel/util/GraphQLUtil.kt @@ -7,7 +7,9 @@ import graphql.language.InputObjectTypeExtensionDefinition import graphql.language.InterfaceTypeExtensionDefinition import graphql.language.NamedNode import graphql.language.Node +import graphql.language.ObjectField import graphql.language.ObjectTypeExtensionDefinition +import graphql.language.ObjectValue import graphql.language.SDLDefinition import graphql.language.SDLNamedDefinition import graphql.language.ScalarTypeExtensionDefinition @@ -37,3 +39,7 @@ val AnyAstNode.isExtensionDef: Boolean || this is SchemaExtensionDefinition || this is UnionTypeExtensionDefinition } + +internal fun ObjectValue.getObjectField(name: String): ObjectField { + return objectFields.first { it.name == name } +} diff --git a/lib/src/main/java/graphql/nadel/validation/NadelFieldValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelFieldValidation.kt index 44cf8e64e..d6b360db9 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelFieldValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelFieldValidation.kt @@ -1,15 +1,14 @@ package graphql.nadel.validation import graphql.nadel.Service +import graphql.nadel.definition.hydration.isHydrated +import graphql.nadel.definition.renamed.isRenamed import graphql.nadel.engine.util.strictAssociateBy import graphql.nadel.engine.util.unwrapAll import graphql.nadel.validation.NadelSchemaValidationError.MissingArgumentOnUnderlying import graphql.nadel.validation.NadelSchemaValidationError.MissingUnderlyingField import graphql.nadel.validation.util.NadelCombinedTypeUtil.getFieldsThatServiceContributed import graphql.nadel.validation.util.NadelCombinedTypeUtil.isCombinedType -import graphql.nadel.validation.util.NadelSchemaUtil.hasHydration -import graphql.nadel.validation.util.NadelSchemaUtil.hasPartition -import graphql.nadel.validation.util.NadelSchemaUtil.hasRename import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLFieldsContainer import graphql.schema.GraphQLNamedSchemaElement @@ -68,9 +67,9 @@ internal class NadelFieldValidation( overallField: GraphQLFieldDefinition, underlyingFieldsByName: Map, ): List { - return if (hasRename(overallField)) { + return if (overallField.isRenamed()) { renameValidation.validate(parent, overallField) - } else if (hasHydration(overallField)) { + } else if (overallField.isHydrated()) { hydrationValidation.validate(parent, overallField) } else { val underlyingField = underlyingFieldsByName[overallField.name] diff --git a/lib/src/main/java/graphql/nadel/validation/NadelHydrationArgumentValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelHydrationArgumentValidation.kt index 53c20cd4a..4bdd15efa 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelHydrationArgumentValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelHydrationArgumentValidation.kt @@ -1,8 +1,8 @@ package graphql.nadel.validation import graphql.Scalars -import graphql.nadel.engine.blueprint.directives.NadelHydrationArgumentDefinition -import graphql.nadel.engine.blueprint.directives.NadelHydrationDefinition +import graphql.nadel.definition.hydration.NadelHydrationArgumentDefinition +import graphql.nadel.definition.hydration.NadelHydrationDefinition import graphql.nadel.engine.util.isList import graphql.nadel.engine.util.isNonNull import graphql.nadel.engine.util.unwrapNonNull diff --git a/lib/src/main/java/graphql/nadel/validation/NadelHydrationConditionValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelHydrationConditionValidation.kt index b38b5d973..837d9618a 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelHydrationConditionValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelHydrationConditionValidation.kt @@ -1,9 +1,9 @@ package graphql.nadel.validation import graphql.Scalars -import graphql.nadel.dsl.NadelHydrationResultConditionDefinition -import graphql.nadel.engine.blueprint.directives.NadelHydrationArgumentDefinition -import graphql.nadel.engine.blueprint.directives.NadelHydrationDefinition +import graphql.nadel.definition.hydration.NadelHydrationResultConditionDefinition +import graphql.nadel.definition.hydration.NadelHydrationArgumentDefinition +import graphql.nadel.definition.hydration.NadelHydrationDefinition import graphql.nadel.engine.util.getFieldAt import graphql.nadel.engine.util.unwrapAll import graphql.nadel.engine.util.unwrapNonNull diff --git a/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt index ea6cf06a1..b5dc485c7 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt @@ -2,11 +2,11 @@ package graphql.nadel.validation import graphql.GraphQLContext import graphql.nadel.Service -import graphql.nadel.dsl.RemoteArgumentSource +import graphql.nadel.definition.hydration.NadelHydrationArgumentDefinition +import graphql.nadel.definition.hydration.NadelHydrationDefinition +import graphql.nadel.definition.hydration.getHydrationDefinitions +import graphql.nadel.definition.renamed.isRenamed import graphql.nadel.engine.blueprint.directives.isVirtualType -import graphql.nadel.engine.blueprint.directives.NadelHydrationArgumentDefinition -import graphql.nadel.engine.blueprint.directives.NadelHydrationDefinition -import graphql.nadel.engine.blueprint.directives.getHydrationDefinitions import graphql.nadel.engine.util.getFieldAt import graphql.nadel.engine.util.getFieldsAlong import graphql.nadel.engine.util.isList @@ -27,7 +27,6 @@ import graphql.nadel.validation.NadelSchemaValidationError.MissingRequiredHydrat import graphql.nadel.validation.NadelSchemaValidationError.MultipleSourceArgsInBatchHydration import graphql.nadel.validation.NadelSchemaValidationError.NoSourceArgsInBatchHydration import graphql.nadel.validation.NadelSchemaValidationError.NonExistentHydrationActorFieldArgument -import graphql.nadel.validation.util.NadelSchemaUtil.hasRename import graphql.schema.GraphQLDirectiveContainer import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLFieldsContainer @@ -51,7 +50,7 @@ internal class NadelHydrationValidation( parent: NadelServiceSchemaElement, overallField: GraphQLFieldDefinition, ): List { - if (hasRename(overallField)) { + if (overallField.isRenamed()) { return listOf( CannotRenameHydratedField(parent, overallField), ) @@ -104,7 +103,7 @@ internal class NadelHydrationValidation( // e.g. context.jiraComment val pathToSourceInputField = hydration.arguments .map { arg -> arg.value } - .singleOfTypeOrNull() + .singleOfTypeOrNull() ?.pathToField ?: return emptyList() // Ignore this, checked elsewhere diff --git a/lib/src/main/java/graphql/nadel/validation/NadelRenameValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelRenameValidation.kt index 210367e60..89783f733 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelRenameValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelRenameValidation.kt @@ -1,11 +1,11 @@ package graphql.nadel.validation +import graphql.nadel.definition.hydration.isHydrated +import graphql.nadel.definition.renamed.getRenamedOrNull import graphql.nadel.engine.util.getFieldAt -import graphql.nadel.validation.NadelSchemaValidationError.CannotRenamePartitionedField import graphql.nadel.validation.NadelSchemaValidationError.CannotRenameHydratedField +import graphql.nadel.validation.NadelSchemaValidationError.CannotRenamePartitionedField import graphql.nadel.validation.NadelSchemaValidationError.MissingRename -import graphql.nadel.validation.util.NadelSchemaUtil.getRename -import graphql.nadel.validation.util.NadelSchemaUtil.hasHydration import graphql.nadel.validation.util.NadelSchemaUtil.hasPartition import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLFieldsContainer @@ -17,7 +17,7 @@ internal class NadelRenameValidation( parent: NadelServiceSchemaElement, overallField: GraphQLFieldDefinition, ): List { - if (hasHydration(overallField)) { + if (overallField.isHydrated()) { return listOf( CannotRenameHydratedField(parent, overallField), ) @@ -29,13 +29,13 @@ internal class NadelRenameValidation( ) } - val rename = getRename(overallField) + val rename = overallField.getRenamedOrNull() return if (rename == null) { listOf() } else { val underlyingFieldContainer = parent.underlying as GraphQLFieldsContainer - val underlyingField = underlyingFieldContainer.getFieldAt(rename.inputPath) + val underlyingField = underlyingFieldContainer.getFieldAt(rename.from) if (underlyingField == null) { listOf( MissingRename(parent, overallField, rename), diff --git a/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt b/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt index ff62f261c..5b088cd40 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt @@ -5,10 +5,10 @@ import graphql.GraphQLError import graphql.GraphqlErrorBuilder import graphql.language.InputValueDefinition import graphql.nadel.Service -import graphql.nadel.dsl.FieldMappingDefinition -import graphql.nadel.engine.blueprint.directives.NadelBatchObjectIdentifiedByDefinition -import graphql.nadel.engine.blueprint.directives.NadelHydrationArgumentDefinition -import graphql.nadel.engine.blueprint.directives.NadelHydrationDefinition +import graphql.nadel.definition.hydration.NadelBatchObjectIdentifiedByDefinition +import graphql.nadel.definition.hydration.NadelHydrationArgumentDefinition +import graphql.nadel.definition.hydration.NadelHydrationDefinition +import graphql.nadel.definition.renamed.NadelRenamedDefinition import graphql.nadel.engine.util.makeFieldCoordinates import graphql.nadel.engine.util.unwrapAll import graphql.nadel.schema.NadelDirectives @@ -533,13 +533,13 @@ sealed interface NadelSchemaValidationError { data class MissingRename( val parentType: NadelServiceSchemaElement, val overallField: GraphQLFieldDefinition, - val rename: FieldMappingDefinition, + val rename: NadelRenamedDefinition, ) : NadelSchemaValidationError { val service: Service get() = parentType.service override val message = run { val of = makeFieldCoordinates(parentType.overall.name, overallField.name) - val uf = "${parentType.underlying.name}.${rename.inputPath.joinToString(separator = ".")}" + val uf = "${parentType.underlying.name}.${rename.from.joinToString(separator = ".")}" val s = service.name "Overall field $of defines rename but underlying field $uf on service $s doesn't exist" } diff --git a/lib/src/main/java/graphql/nadel/validation/util/NadelGetReachableTypes.kt b/lib/src/main/java/graphql/nadel/validation/util/NadelGetReachableTypes.kt index edd2166ca..e9607beb9 100644 --- a/lib/src/main/java/graphql/nadel/validation/util/NadelGetReachableTypes.kt +++ b/lib/src/main/java/graphql/nadel/validation/util/NadelGetReachableTypes.kt @@ -8,7 +8,6 @@ import graphql.nadel.engine.util.unwrapAll import graphql.nadel.validation.util.NadelCombinedTypeUtil.getFieldsThatServiceContributed import graphql.nadel.validation.util.NadelCombinedTypeUtil.isCombinedType import graphql.nadel.validation.util.NadelSchemaUtil.getUnderlyingType -import graphql.nadel.validation.util.NadelSchemaUtil.hasHydration import graphql.schema.GraphQLAppliedDirective import graphql.schema.GraphQLArgument import graphql.schema.GraphQLCompositeType @@ -95,7 +94,7 @@ internal fun getReachableTypeNames( node: GraphQLFieldDefinition, context: TraverserContext, ): TraversalControl { - return if (hasHydration(node)) { + return if (node.isHydrated()) { // Do not collect output type, hydrations do not require the type to be defined in the service ABORT } else { diff --git a/lib/src/main/java/graphql/nadel/validation/util/NadelSchemaUtil.kt b/lib/src/main/java/graphql/nadel/validation/util/NadelSchemaUtil.kt index 2d4e2b7cb..e3a07a28d 100644 --- a/lib/src/main/java/graphql/nadel/validation/util/NadelSchemaUtil.kt +++ b/lib/src/main/java/graphql/nadel/validation/util/NadelSchemaUtil.kt @@ -3,58 +3,26 @@ package graphql.nadel.validation.util import graphql.language.FieldDefinition import graphql.language.OperationDefinition import graphql.nadel.Service -import graphql.nadel.dsl.FieldMappingDefinition -import graphql.nadel.dsl.NadelHydrationDefinition -import graphql.nadel.engine.util.operationTypes -import graphql.nadel.engine.blueprint.directives.NadelHydrationDefinition -import graphql.nadel.engine.blueprint.directives.getHydrationDefinitions -import graphql.nadel.engine.blueprint.directives.hasHydration +import graphql.nadel.definition.renamed.getRenamedOrNull import graphql.nadel.schema.NadelDirectives -import graphql.nadel.util.NamespacedUtil -import graphql.schema.GraphQLDirectiveContainer import graphql.schema.GraphQLFieldDefinition -import graphql.schema.GraphQLNamedOutputType import graphql.schema.GraphQLNamedType -import graphql.schema.GraphQLSchema internal object NadelSchemaUtil { fun getUnderlyingType(overallType: GraphQLNamedType, service: Service): GraphQLNamedType? { - return service.underlyingSchema.getType(getRenamedFrom(overallType) ?: overallType.name) as GraphQLNamedType? - } - - fun hasHydration(field: GraphQLFieldDefinition): Boolean { - return hasHydration(field.definition!!) - } - - fun hasHydration(def: FieldDefinition): Boolean { - return def.hasHydration() - } - - fun getRename(field: GraphQLFieldDefinition): FieldMappingDefinition? { - return NadelDirectives.createFieldMapping(field) - } - - fun hasRename(field: GraphQLFieldDefinition): Boolean { - return hasRename(field.definition!!) - } - - fun hasRename(def: FieldDefinition): Boolean { - return def.hasDirective(NadelDirectives.renamedDirectiveDefinition.name) + return service.underlyingSchema.getType(getUnderlyingName(overallType)) as GraphQLNamedType? } fun getUnderlyingName(type: GraphQLNamedType): String { - return getRenamedFrom(type) ?: type.name - } - - fun getRenamedFrom(type: GraphQLNamedType): String? { - val asDirectivesContainer = type as? GraphQLDirectiveContainer ?: return null - return NadelDirectives.createTypeMapping(asDirectivesContainer)?.underlyingName + return type.getRenamedOrNull()?.from ?: type.name } + @Deprecated(message = "To be replaced with directive wrapper class and extensions") fun hasPartition(field: GraphQLFieldDefinition): Boolean { return hasPartition(field.definition!!) } + @Deprecated(message = "To be replaced with directive wrapper class and extensions") fun hasPartition(def: FieldDefinition): Boolean { return def.hasDirective(NadelDirectives.partitionDirectiveDefinition.name) } diff --git a/lib/src/test/kotlin/graphql/nadel/schema/NadelDirectivesTest.kt b/lib/src/test/kotlin/graphql/nadel/schema/NadelDirectivesTest.kt index 2f4bae8e5..63102fc37 100644 --- a/lib/src/test/kotlin/graphql/nadel/schema/NadelDirectivesTest.kt +++ b/lib/src/test/kotlin/graphql/nadel/schema/NadelDirectivesTest.kt @@ -1,8 +1,8 @@ package graphql.nadel.schema import graphql.language.AstPrinter -import graphql.nadel.engine.blueprint.directives.NadelHydrationArgumentDefinition -import graphql.nadel.engine.blueprint.directives.getHydrationDefinitions +import graphql.nadel.definition.hydration.NadelHydrationArgumentDefinition +import graphql.nadel.definition.hydration.getHydrationDefinitions import graphql.nadel.schema.NadelDirectives.hydratedDirectiveDefinition import graphql.nadel.schema.NadelDirectives.nadelBatchObjectIdentifiedByDefinition import graphql.nadel.schema.NadelDirectives.nadelHydrationArgumentDefinition @@ -16,8 +16,6 @@ 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" diff --git a/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationArgumentValidationTest.kt b/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationArgumentValidationTest.kt index 15a493723..a6e33fd02 100644 --- a/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationArgumentValidationTest.kt +++ b/lib/src/test/kotlin/graphql/nadel/validation/NadelHydrationArgumentValidationTest.kt @@ -1,6 +1,6 @@ package graphql.nadel.validation -import graphql.nadel.engine.blueprint.directives.NadelHydrationArgumentDefinition +import graphql.nadel.definition.hydration.NadelHydrationArgumentDefinition import graphql.nadel.validation.NadelSchemaValidationError.IncompatibleFieldInHydratedInputObject import graphql.nadel.validation.NadelSchemaValidationError.IncompatibleHydrationArgumentType import graphql.nadel.validation.NadelSchemaValidationError.MissingFieldInHydratedInputObject diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/UnderlyingSchemaGenerator.kt b/test/src/test/kotlin/graphql/nadel/tests/next/UnderlyingSchemaGenerator.kt index f0d622eaf..2f0e94a99 100644 --- a/test/src/test/kotlin/graphql/nadel/tests/next/UnderlyingSchemaGenerator.kt +++ b/test/src/test/kotlin/graphql/nadel/tests/next/UnderlyingSchemaGenerator.kt @@ -24,6 +24,7 @@ import graphql.language.Type import graphql.language.TypeDefinition import graphql.language.UnionTypeDefinition import graphql.language.UnionTypeExtensionDefinition +import graphql.nadel.definition.renamed.isRenamed import graphql.nadel.engine.util.emptyOrSingle import graphql.nadel.engine.util.unwrapAll import graphql.nadel.schema.NadelDirectives @@ -47,7 +48,7 @@ fun makeUnderlyingSchema(overallSchema: String): String { .children .filterIsInstance>() .filter { - it.hasRenamed() + it.isRenamed() } .associate { it.name to it.getRenamedFrom() @@ -260,10 +261,6 @@ private fun transformEnumTypeDefinition(type: EnumTypeDefinition): EnumTypeDefin } } -private fun DirectivesContainer<*>.hasRenamed(): Boolean { - return hasDirective(NadelDirectives.renamedDirectiveDefinition.name) -} - private fun DirectivesContainer<*>.getRenamedFromOrNull(): String? { return getDirectives(NadelDirectives.renamedDirectiveDefinition.name) ?.emptyOrSingle() From 2aef2d2be484431ba923eaec5805ab89b40eba2f Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Tue, 22 Oct 2024 10:51:30 +1100 Subject: [PATCH 4/9] Move definitions around --- .../NadelBatchObjectIdentifiedByDefinition.kt | 15 +++ .../NadelHydrationArgumentDefinition.kt | 15 +++ .../NadelHydrationConditionDefinition.kt | 99 +++++++++++---- .../hydration/NadelHydrationDefinition.kt | 60 ++++----- .../renamed/NadelRenamedDefinition.kt | 18 ++- .../graphql/nadel/engine/util/GraphQLUtil.kt | 4 + .../graphql/nadel/schema/NadelDirectives.kt | 114 +++--------------- .../nadel/schema/OverallSchemaGenerator.kt | 1 - .../validation/util/NadelBuiltInTypes.kt | 2 - .../nadel/schema/NadelDirectivesTest.kt | 2 - 10 files changed, 173 insertions(+), 157 deletions(-) diff --git a/lib/src/main/java/graphql/nadel/definition/hydration/NadelBatchObjectIdentifiedByDefinition.kt b/lib/src/main/java/graphql/nadel/definition/hydration/NadelBatchObjectIdentifiedByDefinition.kt index 0b9bf5dc2..741bcc4a6 100644 --- a/lib/src/main/java/graphql/nadel/definition/hydration/NadelBatchObjectIdentifiedByDefinition.kt +++ b/lib/src/main/java/graphql/nadel/definition/hydration/NadelBatchObjectIdentifiedByDefinition.kt @@ -1,12 +1,27 @@ package graphql.nadel.definition.hydration +import graphql.language.InputObjectTypeDefinition import graphql.language.ObjectValue import graphql.language.StringValue +import graphql.nadel.engine.util.parseDefinition import graphql.nadel.util.getObjectField class NadelBatchObjectIdentifiedByDefinition( private val objectValue: ObjectValue, ) { + companion object { + val inputValueDefinition = parseDefinition( + // language=GraphQL + """ + "This is required by batch hydration to understand how to pull out objects from the batched result" + input NadelBatchObjectIdentifiedBy { + sourceId: String! + resultId: String! + } + """.trimIndent(), + ) + } + val sourceId: String get() = (objectValue.getObjectField(Keyword.sourceId).value as StringValue).value val resultId: String diff --git a/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationArgumentDefinition.kt b/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationArgumentDefinition.kt index 307d9e5c5..78a6460da 100644 --- a/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationArgumentDefinition.kt +++ b/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationArgumentDefinition.kt @@ -1,7 +1,9 @@ package graphql.nadel.definition.hydration +import graphql.language.InputObjectTypeDefinition import graphql.language.ObjectValue import graphql.language.StringValue +import graphql.nadel.engine.util.parseDefinition import graphql.nadel.util.AnyAstValue import graphql.nadel.util.getObjectField @@ -11,6 +13,19 @@ import graphql.nadel.util.getObjectField class NadelHydrationArgumentDefinition( private val argumentObject: ObjectValue, ) { + companion object { + val inputValueDefinition = parseDefinition( + // language=GraphQL + """ + "This allows you to hydrate new values into fields" + input NadelHydrationArgument { + name: String! + value: JSON! + } + """.trimIndent(), + ) + } + /** * Name of the backing field's argument. */ diff --git a/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationConditionDefinition.kt b/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationConditionDefinition.kt index 2921e578f..0e901e603 100644 --- a/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationConditionDefinition.kt +++ b/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationConditionDefinition.kt @@ -1,43 +1,37 @@ package graphql.nadel.definition.hydration +import graphql.language.InputObjectTypeDefinition import graphql.nadel.engine.util.JsonMap +import graphql.nadel.engine.util.parseDefinition data class NadelHydrationConditionDefinition( val result: NadelHydrationResultConditionDefinition, ) { companion object { - internal fun from( + val inputObjectDefinition = parseDefinition( + """ + "Specify a condition for the hydration to activate" + input NadelHydrationCondition { + result: NadelHydrationResultCondition! + } + """.trimIndent(), + ) + + fun from( conditionObject: JsonMap, ): NadelHydrationConditionDefinition? { @Suppress("UNCHECKED_CAST") - val result = conditionObject[Keyword.result] as Map? + val resultObject = conditionObject[Keyword.result] as Map? ?: return null - val sourceField = result[Keyword.sourceField]!! as String - - @Suppress("UNCHECKED_CAST") - val predicate = result[Keyword.predicate]!! as Map - return NadelHydrationConditionDefinition( - result = NadelHydrationResultConditionDefinition( - pathToSourceField = sourceField.split("."), - predicate = NadelHydrationConditionPredicateDefinition( - equals = predicate[Keyword.equals], - startsWith = predicate[Keyword.startsWith] as String?, - matches = predicate[Keyword.matches] as String?, - ), - ), + result = NadelHydrationResultConditionDefinition.from(resultObject), ) } } object Keyword { const val result = "result" - const val sourceField = "sourceField" - const val predicate = "predicate" - const val equals = "equals" - const val startsWith = "startsWith" - const val matches = "matches" } } @@ -46,14 +40,71 @@ data class NadelHydrationConditionDefinition( */ data class NadelHydrationResultConditionDefinition( val pathToSourceField: List, - val predicate: NadelHydrationConditionPredicateDefinition, -) + val predicate: NadelHydrationResultFieldPredicateDefinition, +) { + companion object { + val inputObjectDefinition = parseDefinition( + // language=GraphQL + """ + "Specify a condition for the hydration to activate based on the result" + input NadelHydrationResultCondition { + sourceField: String! + predicate: NadelHydrationResultFieldPredicate! + } + """.trimIndent(), + ) + + fun from(resultObject: Map): NadelHydrationResultConditionDefinition { + val sourceField = resultObject[Keyword.sourceField]!! as String + + @Suppress("UNCHECKED_CAST") + val predicateObject = resultObject[Keyword.predicate]!! as Map + + return NadelHydrationResultConditionDefinition( + pathToSourceField = sourceField.split("."), + predicate = NadelHydrationResultFieldPredicateDefinition.from(predicateObject), + ) + } + } + + object Keyword { + const val sourceField = "sourceField" + const val predicate = "predicate" + } +} /** * How a given value must match. */ -data class NadelHydrationConditionPredicateDefinition( +data class NadelHydrationResultFieldPredicateDefinition( val equals: Any?, val startsWith: String?, val matches: String?, -) +) { + companion object { + val inputValueDefinition = parseDefinition( + // language=GraphQL + """ + input NadelHydrationResultFieldPredicate @oneOf { + startsWith: String + equals: JSON + matches: String + } + """.trimIndent(), + ) + + fun from(predicateObject: Map): NadelHydrationResultFieldPredicateDefinition { + return NadelHydrationResultFieldPredicateDefinition( + equals = predicateObject[Keyword.equals], + startsWith = predicateObject[Keyword.startsWith] as String?, + matches = predicateObject[Keyword.matches] as String?, + ) + } + } + + object Keyword { + const val equals = "equals" + const val startsWith = "startsWith" + const val matches = "matches" + } +} diff --git a/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationDefinition.kt b/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationDefinition.kt index 45b1755fc..1afd2daf5 100644 --- a/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationDefinition.kt +++ b/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationDefinition.kt @@ -1,10 +1,12 @@ package graphql.nadel.definition.hydration import graphql.language.ArrayValue +import graphql.language.DirectiveDefinition import graphql.language.FieldDefinition import graphql.language.ObjectValue import graphql.nadel.definition.hydration.NadelHydrationDefinition.Keyword import graphql.nadel.engine.util.JsonMap +import graphql.nadel.engine.util.parseDefinition import graphql.schema.GraphQLAppliedDirective import graphql.schema.GraphQLFieldDefinition @@ -21,36 +23,40 @@ fun GraphQLFieldDefinition.getHydrationDefinitions(): List( + // language=GraphQL + """ + "This allows you to hydrate new values into fields" + directive @hydrated( + "The target service" + service: String! + "The target top level field" + field: String! + "How to identify matching results" + identifiedBy: String! = "id" + "How to identify matching results" + inputIdentifiedBy: [NadelBatchObjectIdentifiedBy!]! = [] + "Are results indexed" + indexed: Boolean! = false + "Is querying batched" + batched: Boolean! = false + "The batch size" + batchSize: Int! = 200 + "The timeout to use when completing hydration" + timeout: Int! = -1 + "The arguments to the hydrated field" + arguments: [NadelHydrationArgument!]! + "Specify a condition for the hydration to activate" + when: NadelHydrationCondition + ) repeatable on FIELD_DEFINITION + """.trimIndent(), + ) + } + val backingField: List get() = appliedDirective.getArgument(Keyword.field).getValue().split(".") diff --git a/lib/src/main/java/graphql/nadel/definition/renamed/NadelRenamedDefinition.kt b/lib/src/main/java/graphql/nadel/definition/renamed/NadelRenamedDefinition.kt index 2574700ec..4f606218a 100644 --- a/lib/src/main/java/graphql/nadel/definition/renamed/NadelRenamedDefinition.kt +++ b/lib/src/main/java/graphql/nadel/definition/renamed/NadelRenamedDefinition.kt @@ -1,14 +1,28 @@ package graphql.nadel.definition.renamed +import graphql.language.DirectiveDefinition import graphql.language.DirectivesContainer import graphql.nadel.definition.renamed.NadelRenamedDefinition.Keyword -import graphql.nadel.schema.NadelDirectives +import graphql.nadel.engine.util.parseDefinition import graphql.schema.GraphQLAppliedDirective import graphql.schema.GraphQLDirectiveContainer import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLNamedType sealed class NadelRenamedDefinition { + companion object{ + val directiveDefinition = parseDefinition( + // language=GraphQL + """ + "This allows you to rename a type or field in the overall schema" + directive @renamed( + "The type to be renamed" + from: String! + ) on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | INPUT_OBJECT | SCALAR | ENUM + """.trimIndent(), + ) + } + class Type( private val appliedDirective: GraphQLAppliedDirective, ) : NadelRenamedDefinition() { @@ -37,7 +51,7 @@ fun GraphQLDirectiveContainer.isRenamed(): Boolean { } fun DirectivesContainer<*>.isRenamed(): Boolean { - return hasDirective(NadelDirectives.renamedDirectiveDefinition.name) + return hasDirective(Keyword.renamed) } fun GraphQLFieldDefinition.getRenamedOrNull(): NadelRenamedDefinition.Field? { diff --git a/lib/src/main/java/graphql/nadel/engine/util/GraphQLUtil.kt b/lib/src/main/java/graphql/nadel/engine/util/GraphQLUtil.kt index 2e90b0d4a..d78d37143 100644 --- a/lib/src/main/java/graphql/nadel/engine/util/GraphQLUtil.kt +++ b/lib/src/main/java/graphql/nadel/engine/util/GraphQLUtil.kt @@ -54,6 +54,7 @@ import graphql.normalized.ExecutableNormalizedOperationToAstCompiler import graphql.normalized.ExecutableNormalizedOperationToAstCompiler.CompilerResult import graphql.normalized.NormalizedInputValue import graphql.normalized.VariablePredicate +import graphql.parser.Parser import graphql.schema.FieldCoordinates import graphql.schema.GraphQLCodeRegistry import graphql.schema.GraphQLFieldDefinition @@ -647,3 +648,6 @@ internal fun ExecutableNormalizedField.getFieldDefinitionSequence( } } +internal inline fun > parseDefinition(sdl: String): T { + return Parser.parse(sdl).definitions.singleOfType() +} diff --git a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt index d27066eb8..1abf57c69 100644 --- a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt +++ b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt @@ -12,6 +12,14 @@ import graphql.schema.GraphQLAppliedDirective import graphql.schema.GraphQLAppliedDirectiveArgument import graphql.schema.GraphQLFieldDefinition import java.util.Locale +import graphql.nadel.definition.hydration.NadelBatchObjectIdentifiedByDefinition +import graphql.nadel.definition.hydration.NadelHydrationArgumentDefinition +import graphql.nadel.definition.hydration.NadelHydrationConditionDefinition +import graphql.nadel.definition.hydration.NadelHydrationDefinition +import graphql.nadel.definition.hydration.NadelHydrationResultConditionDefinition +import graphql.nadel.definition.hydration.NadelHydrationResultFieldPredicateDefinition +import graphql.nadel.definition.renamed.NadelRenamedDefinition +import graphql.nadel.engine.util.parseDefinition /** * If you update this file please add to NadelBuiltInTypes @@ -24,99 +32,19 @@ object NadelDirectives { """.trimIndent(), ) - val renamedDirectiveDefinition = parseDefinition( - // language=GraphQL - """ - "This allows you to rename a type or field in the overall schema" - directive @renamed( - "The type to be renamed" - from: String! - ) on FIELD_DEFINITION | OBJECT | INTERFACE | UNION | INPUT_OBJECT | SCALAR | ENUM - """.trimIndent(), - ) + val renamedDirectiveDefinition = NadelRenamedDefinition.directiveDefinition - val nadelBatchObjectIdentifiedByDefinition = parseDefinition( - // language=GraphQL - """ - "This is required by batch hydration to understand how to pull out objects from the batched result" - input NadelBatchObjectIdentifiedBy { - sourceId: String! - resultId: String! - } - """.trimIndent(), - ) + val nadelBatchObjectIdentifiedByDefinition = NadelBatchObjectIdentifiedByDefinition.inputValueDefinition - val nadelHydrationArgumentDefinition = parseDefinition( - // language=GraphQL - """ - "This allows you to hydrate new values into fields" - input NadelHydrationArgument { - name: String! - value: JSON! - } - """.trimIndent(), - ) + val nadelHydrationArgumentDefinition = NadelHydrationArgumentDefinition.inputValueDefinition - val nadelHydrationResultFieldPredicateDefinition = parseDefinition( - // language=GraphQL - """ - input NadelHydrationResultFieldPredicate @oneOf { - startsWith: String - equals: JSON - matches: String - } - """.trimIndent(), - ) + val nadelHydrationResultFieldPredicateDefinition = NadelHydrationResultFieldPredicateDefinition.inputValueDefinition - val nadelHydrationResultConditionDefinition = parseDefinition( - // language=GraphQL - """ - "Specify a condition for the hydration to activate based on the result" - input NadelHydrationResultCondition { - sourceField: String! - predicate: NadelHydrationResultFieldPredicate! - } - """.trimIndent(), - ) + val nadelHydrationResultConditionDefinition = NadelHydrationResultConditionDefinition.inputObjectDefinition - val nadelHydrationConditionDefinition = parseDefinition( - // language=GraphQL - """ - "Specify a condition for the hydration to activate" - input NadelHydrationCondition { - result: NadelHydrationResultCondition! - } - """.trimIndent(), - ) + val nadelHydrationConditionDefinition = NadelHydrationConditionDefinition.inputObjectDefinition - val hydratedDirectiveDefinition = parseDefinition( - // language=GraphQL - """ - "This allows you to hydrate new values into fields" - directive @hydrated( - "The target service" - service: String! - "The target top level field" - field: String! - "How to identify matching results" - identifiedBy: String! = "id" - "How to identify matching results" - inputIdentifiedBy: [NadelBatchObjectIdentifiedBy!]! = [] - "Are results indexed" - indexed: Boolean! = false - "Is querying batched" - batched: Boolean! = false - "The batch size" - batchSize: Int! = 200 - "The timeout to use when completing hydration" - timeout: Int! = -1 - "The arguments to the hydrated field" - arguments: [NadelHydrationArgument!]! - "Specify a condition for the hydration to activate" - when: NadelHydrationCondition - ) repeatable on FIELD_DEFINITION - """.trimIndent(), - ) + val hydratedDirectiveDefinition = NadelHydrationDefinition.directiveDefinition val dynamicServiceDirectiveDefinition = parseDefinition( // language=GraphQL @@ -142,18 +70,6 @@ object NadelDirectives { """.trimIndent(), ) - val nadelHydrationFromArgumentDefinition = parseDefinition( - // language=GraphQL - """ - "This allows you to hydrate new values into fields with the @hydratedFrom directive" - input NadelHydrationFromArgument { - name: String! - valueFromField: String - valueFromArg: String - } - """.trimIndent(), - ) - val partitionDirectiveDefinition = parseDefinition( // language=GraphQL """ diff --git a/lib/src/main/java/graphql/nadel/schema/OverallSchemaGenerator.kt b/lib/src/main/java/graphql/nadel/schema/OverallSchemaGenerator.kt index de102eaec..8b60a92b4 100644 --- a/lib/src/main/java/graphql/nadel/schema/OverallSchemaGenerator.kt +++ b/lib/src/main/java/graphql/nadel/schema/OverallSchemaGenerator.kt @@ -65,7 +65,6 @@ internal class OverallSchemaGenerator { addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.hydratedDirectiveDefinition) addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.renamedDirectiveDefinition) addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.hiddenDirectiveDefinition) - addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.nadelHydrationFromArgumentDefinition) addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.nadelBatchObjectIdentifiedByDefinition) addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.nadelHydrationResultFieldPredicateDefinition) addIfNotPresent(overallRegistry, allDefinitions, NadelDirectives.nadelHydrationResultConditionDefinition) diff --git a/lib/src/main/java/graphql/nadel/validation/util/NadelBuiltInTypes.kt b/lib/src/main/java/graphql/nadel/validation/util/NadelBuiltInTypes.kt index 181b105b2..7f78917fe 100644 --- a/lib/src/main/java/graphql/nadel/validation/util/NadelBuiltInTypes.kt +++ b/lib/src/main/java/graphql/nadel/validation/util/NadelBuiltInTypes.kt @@ -13,7 +13,6 @@ import graphql.nadel.schema.NadelDirectives.hydratedDirectiveDefinition import graphql.nadel.schema.NadelDirectives.nadelBatchObjectIdentifiedByDefinition import graphql.nadel.schema.NadelDirectives.nadelHydrationArgumentDefinition import graphql.nadel.schema.NadelDirectives.nadelHydrationConditionDefinition -import graphql.nadel.schema.NadelDirectives.nadelHydrationFromArgumentDefinition import graphql.nadel.schema.NadelDirectives.nadelHydrationResultConditionDefinition import graphql.nadel.schema.NadelDirectives.nadelHydrationResultFieldPredicateDefinition import graphql.nadel.schema.NadelDirectives.namespacedDirectiveDefinition @@ -44,7 +43,6 @@ object NadelBuiltInTypes { deferDirectiveDefinition, partitionDirectiveDefinition, - nadelHydrationFromArgumentDefinition, nadelBatchObjectIdentifiedByDefinition, nadelHydrationResultFieldPredicateDefinition, diff --git a/lib/src/test/kotlin/graphql/nadel/schema/NadelDirectivesTest.kt b/lib/src/test/kotlin/graphql/nadel/schema/NadelDirectivesTest.kt index 63102fc37..16a8e0e7c 100644 --- a/lib/src/test/kotlin/graphql/nadel/schema/NadelDirectivesTest.kt +++ b/lib/src/test/kotlin/graphql/nadel/schema/NadelDirectivesTest.kt @@ -7,7 +7,6 @@ import graphql.nadel.schema.NadelDirectives.hydratedDirectiveDefinition import graphql.nadel.schema.NadelDirectives.nadelBatchObjectIdentifiedByDefinition import graphql.nadel.schema.NadelDirectives.nadelHydrationArgumentDefinition import graphql.nadel.schema.NadelDirectives.nadelHydrationConditionDefinition -import graphql.nadel.schema.NadelDirectives.nadelHydrationFromArgumentDefinition import graphql.nadel.schema.NadelDirectives.nadelHydrationResultConditionDefinition import graphql.nadel.schema.NadelDirectives.nadelHydrationResultFieldPredicateDefinition import graphql.nadel.schema.NadelDirectives.partitionDirectiveDefinition @@ -29,7 +28,6 @@ class NadelDirectivesTest : DescribeSpec({ ${AstPrinter.printAst(hydratedDirectiveDefinition)} ${AstPrinter.printAst(nadelHydrationArgumentDefinition)} ${AstPrinter.printAst(nadelBatchObjectIdentifiedByDefinition)} - ${AstPrinter.printAst(nadelHydrationFromArgumentDefinition)} ${AstPrinter.printAst(nadelHydrationConditionDefinition)} ${AstPrinter.printAst(nadelHydrationResultFieldPredicateDefinition)} ${AstPrinter.printAst(nadelHydrationResultConditionDefinition)} From 9dd34e9f13de232bdbe562588f979635f4fd67ae Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Tue, 22 Oct 2024 10:55:19 +1100 Subject: [PATCH 5/9] Fix build --- .../nadel/engine/blueprint/NadelExecutionBlueprintFactory.kt | 2 +- .../java/graphql/nadel/validation/NadelHydrationValidation.kt | 2 +- .../graphql/nadel/validation/NadelSchemaValidationError.kt | 4 ++-- .../main/java/graphql/nadel/validation/NadelTypeValidation.kt | 2 +- .../graphql/nadel/validation/util/NadelGetReachableTypes.kt | 3 ++- 5 files changed, 7 insertions(+), 6 deletions(-) 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 578533eba..82bd6c96e 100644 --- a/lib/src/main/java/graphql/nadel/engine/blueprint/NadelExecutionBlueprintFactory.kt +++ b/lib/src/main/java/graphql/nadel/engine/blueprint/NadelExecutionBlueprintFactory.kt @@ -14,7 +14,7 @@ import graphql.nadel.definition.hydration.NadelHydrationDefinition import graphql.nadel.definition.hydration.getHydrationDefinitions import graphql.nadel.definition.renamed.NadelRenamedDefinition import graphql.nadel.definition.renamed.getRenamedOrNull -import graphql.nadel.engine.blueprint.directives.isVirtualType +import graphql.nadel.definition.virtualType.isVirtualType import graphql.nadel.engine.blueprint.hydration.NadelBatchHydrationMatchStrategy import graphql.nadel.engine.blueprint.hydration.NadelHydrationActorInputDef import graphql.nadel.engine.blueprint.hydration.NadelHydrationActorInputDef.ValueSource.FieldResultValue diff --git a/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt index b5dc485c7..d5fed51eb 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelHydrationValidation.kt @@ -6,7 +6,7 @@ import graphql.nadel.definition.hydration.NadelHydrationArgumentDefinition import graphql.nadel.definition.hydration.NadelHydrationDefinition import graphql.nadel.definition.hydration.getHydrationDefinitions import graphql.nadel.definition.renamed.isRenamed -import graphql.nadel.engine.blueprint.directives.isVirtualType +import graphql.nadel.definition.virtualType.isVirtualType import graphql.nadel.engine.util.getFieldAt import graphql.nadel.engine.util.getFieldsAlong import graphql.nadel.engine.util.isList diff --git a/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt b/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt index 5b088cd40..0b1d99702 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationError.kt @@ -533,13 +533,13 @@ sealed interface NadelSchemaValidationError { data class MissingRename( val parentType: NadelServiceSchemaElement, val overallField: GraphQLFieldDefinition, - val rename: NadelRenamedDefinition, + val rename: NadelRenamedDefinition.Field, ) : NadelSchemaValidationError { val service: Service get() = parentType.service override val message = run { val of = makeFieldCoordinates(parentType.overall.name, overallField.name) - val uf = "${parentType.underlying.name}.${rename.from.joinToString(separator = ".")}" + val uf = "${parentType.underlying.name}.${rename.rawFrom}" val s = service.name "Overall field $of defines rename but underlying field $uf on service $s doesn't exist" } diff --git a/lib/src/main/java/graphql/nadel/validation/NadelTypeValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelTypeValidation.kt index f71af0c42..50d262652 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelTypeValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelTypeValidation.kt @@ -4,7 +4,7 @@ import graphql.Scalars.GraphQLID import graphql.Scalars.GraphQLString import graphql.language.UnionTypeDefinition import graphql.nadel.Service -import graphql.nadel.engine.blueprint.directives.isVirtualType +import graphql.nadel.definition.virtualType.isVirtualType import graphql.nadel.engine.util.AnyNamedNode import graphql.nadel.engine.util.all import graphql.nadel.engine.util.isExtensionDef diff --git a/lib/src/main/java/graphql/nadel/validation/util/NadelGetReachableTypes.kt b/lib/src/main/java/graphql/nadel/validation/util/NadelGetReachableTypes.kt index e9607beb9..7937d6d3d 100644 --- a/lib/src/main/java/graphql/nadel/validation/util/NadelGetReachableTypes.kt +++ b/lib/src/main/java/graphql/nadel/validation/util/NadelGetReachableTypes.kt @@ -2,7 +2,8 @@ package graphql.nadel.validation.util import graphql.language.UnionTypeDefinition import graphql.nadel.Service -import graphql.nadel.engine.blueprint.directives.isVirtualType +import graphql.nadel.definition.hydration.isHydrated +import graphql.nadel.definition.virtualType.isVirtualType import graphql.nadel.engine.util.AnySDLNamedDefinition import graphql.nadel.engine.util.unwrapAll import graphql.nadel.validation.util.NadelCombinedTypeUtil.getFieldsThatServiceContributed From 88ee0cc9ab22353bb7990af3ab243da538ebe9fc Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Thu, 31 Oct 2024 18:05:51 +1100 Subject: [PATCH 6/9] Migrate partition --- .../hydration/PartitionDefinition.kt | 5 -- .../partition/NadelPartitionDefinition.kt | 48 ++++++++++++++++ .../renamed/NadelRenamedDefinition.kt | 2 +- .../NadelExecutionBlueprintFactory.kt | 4 +- .../graphql/nadel/schema/NadelDirectives.kt | 55 ++----------------- .../validation/NadelPartitionValidation.kt | 8 +-- .../nadel/validation/NadelRenameValidation.kt | 4 +- .../nadel/validation/util/NadelSchemaUtil.kt | 13 ----- 8 files changed, 62 insertions(+), 77 deletions(-) delete mode 100644 lib/src/main/java/graphql/nadel/definition/hydration/PartitionDefinition.kt create mode 100644 lib/src/main/java/graphql/nadel/definition/partition/NadelPartitionDefinition.kt diff --git a/lib/src/main/java/graphql/nadel/definition/hydration/PartitionDefinition.kt b/lib/src/main/java/graphql/nadel/definition/hydration/PartitionDefinition.kt deleted file mode 100644 index 21e927c74..000000000 --- a/lib/src/main/java/graphql/nadel/definition/hydration/PartitionDefinition.kt +++ /dev/null @@ -1,5 +0,0 @@ -package graphql.nadel.dsl - -data class NadelPartitionDefinition( - val pathToPartitionArg: List, -) diff --git a/lib/src/main/java/graphql/nadel/definition/partition/NadelPartitionDefinition.kt b/lib/src/main/java/graphql/nadel/definition/partition/NadelPartitionDefinition.kt new file mode 100644 index 000000000..c5896aba2 --- /dev/null +++ b/lib/src/main/java/graphql/nadel/definition/partition/NadelPartitionDefinition.kt @@ -0,0 +1,48 @@ +package graphql.nadel.definition.partition + +import graphql.language.DirectiveDefinition +import graphql.language.DirectivesContainer +import graphql.nadel.definition.partition.NadelPartitionDefinition.Keyword +import graphql.nadel.engine.util.parseDefinition +import graphql.schema.GraphQLAppliedDirective +import graphql.schema.GraphQLFieldDefinition + +class NadelPartitionDefinition( + private val appliedDirective: GraphQLAppliedDirective, +) { + companion object { + val directiveDefinition = parseDefinition( + // language=GraphQL + """ + "This allows you to partition a field" + directive @partition( + "The path to the split point" + pathToPartitionArg: [String!]! + ) on FIELD_DEFINITION + """.trimIndent(), + ) + } + + val pathToPartitionArg: List + get() = appliedDirective.getArgument(Keyword.pathToPartitionArg).getValue() + + object Keyword { + const val partition = "partition" + const val pathToPartitionArg = "pathToPartitionArg" + } +} + +fun GraphQLFieldDefinition.isPartitioned(): Boolean { + return hasAppliedDirective(Keyword.partition) +} + +fun DirectivesContainer<*>.isPartitioned(): Boolean { + return hasDirective(Keyword.partition) +} + +fun GraphQLFieldDefinition.getPartitionOrNull(): NadelPartitionDefinition? { + val directive = getAppliedDirective(Keyword.partition) + ?: return null + + return NadelPartitionDefinition(directive) +} diff --git a/lib/src/main/java/graphql/nadel/definition/renamed/NadelRenamedDefinition.kt b/lib/src/main/java/graphql/nadel/definition/renamed/NadelRenamedDefinition.kt index 4f606218a..a8581a98b 100644 --- a/lib/src/main/java/graphql/nadel/definition/renamed/NadelRenamedDefinition.kt +++ b/lib/src/main/java/graphql/nadel/definition/renamed/NadelRenamedDefinition.kt @@ -10,7 +10,7 @@ import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLNamedType sealed class NadelRenamedDefinition { - companion object{ + companion object { val directiveDefinition = parseDefinition( // language=GraphQL """ 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 82bd6c96e..ee19b844e 100644 --- a/lib/src/main/java/graphql/nadel/engine/blueprint/NadelExecutionBlueprintFactory.kt +++ b/lib/src/main/java/graphql/nadel/engine/blueprint/NadelExecutionBlueprintFactory.kt @@ -12,6 +12,7 @@ import graphql.nadel.Service import graphql.nadel.definition.hydration.NadelHydrationArgumentDefinition import graphql.nadel.definition.hydration.NadelHydrationDefinition import graphql.nadel.definition.hydration.getHydrationDefinitions +import graphql.nadel.definition.partition.getPartitionOrNull import graphql.nadel.definition.renamed.NadelRenamedDefinition import graphql.nadel.definition.renamed.getRenamedOrNull import graphql.nadel.definition.virtualType.isVirtualType @@ -36,6 +37,7 @@ import graphql.nadel.engine.util.mapFrom import graphql.nadel.engine.util.strictAssociateBy import graphql.nadel.engine.util.unwrapAll import graphql.nadel.engine.util.unwrapNonNull +import graphql.nadel.schema.NadelDirectives import graphql.nadel.util.AnyAstValue import graphql.schema.FieldCoordinates import graphql.schema.GraphQLFieldDefinition @@ -489,7 +491,7 @@ private class Factory( parentType: GraphQLObjectType, field: GraphQLFieldDefinition, ): List { - val partitionDefinition = NadelDirectives.createPartitionDefinition(field) + val partitionDefinition = field.getPartitionOrNull() ?: return emptyList() return listOf( diff --git a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt index 1abf57c69..61c2ba23c 100644 --- a/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt +++ b/lib/src/main/java/graphql/nadel/schema/NadelDirectives.kt @@ -1,25 +1,17 @@ package graphql.nadel.schema -import graphql.GraphQLContext -import graphql.execution.ValuesResolver import graphql.language.DirectiveDefinition -import graphql.language.InputObjectTypeDefinition import graphql.language.SDLDefinition -import graphql.nadel.dsl.NadelPartitionDefinition -import graphql.nadel.engine.util.singleOfType -import graphql.parser.Parser -import graphql.schema.GraphQLAppliedDirective -import graphql.schema.GraphQLAppliedDirectiveArgument -import graphql.schema.GraphQLFieldDefinition -import java.util.Locale import graphql.nadel.definition.hydration.NadelBatchObjectIdentifiedByDefinition import graphql.nadel.definition.hydration.NadelHydrationArgumentDefinition import graphql.nadel.definition.hydration.NadelHydrationConditionDefinition import graphql.nadel.definition.hydration.NadelHydrationDefinition import graphql.nadel.definition.hydration.NadelHydrationResultConditionDefinition import graphql.nadel.definition.hydration.NadelHydrationResultFieldPredicateDefinition +import graphql.nadel.definition.partition.NadelPartitionDefinition import graphql.nadel.definition.renamed.NadelRenamedDefinition -import graphql.nadel.engine.util.parseDefinition +import graphql.nadel.engine.util.singleOfType +import graphql.parser.Parser /** * If you update this file please add to NadelBuiltInTypes @@ -70,46 +62,7 @@ object NadelDirectives { """.trimIndent(), ) - val partitionDirectiveDefinition = parseDefinition( - // language=GraphQL - """ - "This allows you to partition a field" - directive @partition( - "The path to the split point" - pathToPartitionArg: [String!]! - ) on FIELD_DEFINITION - """.trimIndent() - ) - - @Deprecated(message = "To be replaced with directive wrapper class") - internal fun createPartitionDefinition(fieldDefinition: GraphQLFieldDefinition): NadelPartitionDefinition? { - val directive = fieldDefinition.getAppliedDirective(partitionDirectiveDefinition.name) - ?: return null - val pathToPartitionArg = getDirectiveValue>(directive, "pathToPartitionArg") - - return NadelPartitionDefinition(pathToPartitionArg) - } - - private inline fun getDirectiveValue( - directive: GraphQLAppliedDirective, - name: String, - ): T { - val argument = directive.getArgument(name) - ?: throw IllegalStateException("The @${directive.name} directive argument '$name' argument MUST be present") - - val value = resolveArgumentValue(argument) - return T::class.java.cast(value) - } - - private fun resolveArgumentValue(graphQLArgument: GraphQLAppliedDirectiveArgument): T { - @Suppress("UNCHECKED_CAST") // Trust caller. Can't do much - return ValuesResolver.valueToInternalValue( - graphQLArgument.argumentValue, - graphQLArgument.type, - GraphQLContext.getDefault(), - Locale.getDefault() - ) as T - } + val partitionDirectiveDefinition = NadelPartitionDefinition.directiveDefinition private inline fun > parseDefinition(sdl: String): T { return Parser.parse(sdl).definitions.singleOfType() diff --git a/lib/src/main/java/graphql/nadel/validation/NadelPartitionValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelPartitionValidation.kt index d27dce39c..91d9a344f 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelPartitionValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelPartitionValidation.kt @@ -1,5 +1,7 @@ package graphql.nadel.validation +import graphql.nadel.definition.hydration.isHydrated +import graphql.nadel.definition.partition.isPartitioned import graphql.nadel.engine.util.isList import graphql.nadel.engine.util.unwrapNonNull import graphql.nadel.schema.NadelDirectives @@ -10,8 +12,6 @@ import graphql.nadel.validation.NadelSchemaValidationError.PartitionAppliedToFie import graphql.nadel.validation.NadelSchemaValidationError.PartitionAppliedToSubscriptionField import graphql.nadel.validation.NadelSchemaValidationError.PartitionAppliedToUnsupportedField import graphql.nadel.validation.util.NadelSchemaUtil -import graphql.nadel.validation.util.NadelSchemaUtil.hasHydration -import graphql.nadel.validation.util.NadelSchemaUtil.hasPartition import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLInputObjectType import graphql.schema.GraphQLObjectType @@ -26,11 +26,11 @@ internal class NadelPartitionValidation( parent: NadelServiceSchemaElement, overallField: GraphQLFieldDefinition, ): List { - if (!hasPartition(overallField)) { + if (!overallField.isPartitioned()) { return emptyList() } - if (hasHydration(overallField)) { + if (overallField.isHydrated()) { return listOf( CannotPartitionHydratedField(parent, overallField), ) diff --git a/lib/src/main/java/graphql/nadel/validation/NadelRenameValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelRenameValidation.kt index 89783f733..818706293 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelRenameValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelRenameValidation.kt @@ -1,12 +1,12 @@ package graphql.nadel.validation import graphql.nadel.definition.hydration.isHydrated +import graphql.nadel.definition.partition.isPartitioned import graphql.nadel.definition.renamed.getRenamedOrNull import graphql.nadel.engine.util.getFieldAt import graphql.nadel.validation.NadelSchemaValidationError.CannotRenameHydratedField import graphql.nadel.validation.NadelSchemaValidationError.CannotRenamePartitionedField import graphql.nadel.validation.NadelSchemaValidationError.MissingRename -import graphql.nadel.validation.util.NadelSchemaUtil.hasPartition import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLFieldsContainer @@ -23,7 +23,7 @@ internal class NadelRenameValidation( ) } - if (hasPartition(overallField)) { + if (overallField.isPartitioned()) { return listOf( CannotRenamePartitionedField(parent, overallField), ) diff --git a/lib/src/main/java/graphql/nadel/validation/util/NadelSchemaUtil.kt b/lib/src/main/java/graphql/nadel/validation/util/NadelSchemaUtil.kt index e3a07a28d..30bf6c7a0 100644 --- a/lib/src/main/java/graphql/nadel/validation/util/NadelSchemaUtil.kt +++ b/lib/src/main/java/graphql/nadel/validation/util/NadelSchemaUtil.kt @@ -1,11 +1,8 @@ package graphql.nadel.validation.util -import graphql.language.FieldDefinition import graphql.language.OperationDefinition import graphql.nadel.Service import graphql.nadel.definition.renamed.getRenamedOrNull -import graphql.nadel.schema.NadelDirectives -import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLNamedType internal object NadelSchemaUtil { @@ -17,16 +14,6 @@ internal object NadelSchemaUtil { return type.getRenamedOrNull()?.from ?: type.name } - @Deprecated(message = "To be replaced with directive wrapper class and extensions") - fun hasPartition(field: GraphQLFieldDefinition): Boolean { - return hasPartition(field.definition!!) - } - - @Deprecated(message = "To be replaced with directive wrapper class and extensions") - fun hasPartition(def: FieldDefinition): Boolean { - return def.hasDirective(NadelDirectives.partitionDirectiveDefinition.name) - } - fun isOperation(type: GraphQLNamedType): Boolean { return type.name.equals(OperationDefinition.Operation.QUERY.toString(), ignoreCase = true) || type.name.equals(OperationDefinition.Operation.MUTATION.toString(), ignoreCase = true) From efa2d7ffe1fdb08161c4459f79077115bd48b178 Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Thu, 31 Oct 2024 18:10:05 +1100 Subject: [PATCH 7/9] Fix ext receiver --- .../nadel/definition/partition/NadelPartitionDefinition.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/main/java/graphql/nadel/definition/partition/NadelPartitionDefinition.kt b/lib/src/main/java/graphql/nadel/definition/partition/NadelPartitionDefinition.kt index c5896aba2..5d0fa110d 100644 --- a/lib/src/main/java/graphql/nadel/definition/partition/NadelPartitionDefinition.kt +++ b/lib/src/main/java/graphql/nadel/definition/partition/NadelPartitionDefinition.kt @@ -2,6 +2,7 @@ package graphql.nadel.definition.partition import graphql.language.DirectiveDefinition import graphql.language.DirectivesContainer +import graphql.language.FieldDefinition import graphql.nadel.definition.partition.NadelPartitionDefinition.Keyword import graphql.nadel.engine.util.parseDefinition import graphql.schema.GraphQLAppliedDirective @@ -36,7 +37,7 @@ fun GraphQLFieldDefinition.isPartitioned(): Boolean { return hasAppliedDirective(Keyword.partition) } -fun DirectivesContainer<*>.isPartitioned(): Boolean { +fun FieldDefinition.isPartitioned(): Boolean { return hasDirective(Keyword.partition) } From 8a370d1885c627760e5c15b560910ec0e55d55b1 Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Mon, 4 Nov 2024 14:07:10 +1100 Subject: [PATCH 8/9] Fix NPE --- .../nadel/definition/hydration/NadelHydrationDefinition.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationDefinition.kt b/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationDefinition.kt index 1afd2daf5..9a2036c07 100644 --- a/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationDefinition.kt +++ b/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationDefinition.kt @@ -67,7 +67,7 @@ class NadelHydrationDefinition( get() = appliedDirective.getArgument(Keyword.indexed).getValue() val isBatched: Boolean - get() = appliedDirective.getArgument(Keyword.batched).getValue() + get() = appliedDirective.getArgument(Keyword.batched)?.getValue() == true val batchSize: Int get() = appliedDirective.getArgument(Keyword.batchSize).getValue() From 2cc2cc7ec034eb7e996af12ef85fe83e31fd6d77 Mon Sep 17 00:00:00 2001 From: Franklin Wang Date: Mon, 4 Nov 2024 14:21:14 +1100 Subject: [PATCH 9/9] Delete old tests --- .../basic-hydration-from-directive-based.yml | 128 ---------- ...om-direct-query-with-a-synthetic-field.yml | 231 ------------------ 2 files changed, 359 deletions(-) delete mode 100644 test/src/test/resources/fixtures/new hydration/basic-hydration-from-directive-based.yml delete mode 100644 test/src/test/resources/fixtures/new hydration/batched-hydration-from-direct-query-with-a-synthetic-field.yml diff --git a/test/src/test/resources/fixtures/new hydration/basic-hydration-from-directive-based.yml b/test/src/test/resources/fixtures/new hydration/basic-hydration-from-directive-based.yml deleted file mode 100644 index 14bfb22fb..000000000 --- a/test/src/test/resources/fixtures/new hydration/basic-hydration-from-directive-based.yml +++ /dev/null @@ -1,128 +0,0 @@ -name: "basic hydration from directive based" -enabled: true -# language=GraphQL -overallSchema: - service2: | - type Query { - barById(id: ID): Bar - } - type Bar { - id: ID - name: String - } - service1: | - type Query { - foo: Foo - } - extend enum NadelHydrationTemplate { - SERVICE2_TEMPLATE @hydratedTemplate( - service: "service2" - field: "barById" - ) - } - - type Foo { - id: ID - bar: Bar @hydratedFrom( - template: SERVICE2_TEMPLATE - arguments: [ - {name: "id" valueFromField: "barId"} - ] - ) - barLongerInput: Bar @hydratedFrom( - template: SERVICE2_TEMPLATE - arguments: [ - {name: "id" valueFromField: "fooDetails.externalBarId"} - ] - ) - } -# language=GraphQL -underlyingSchema: - service2: | - type Bar { - id: ID - name: String - } - - type Query { - barById(id: ID): Bar - } - service1: | - type Foo { - barId: ID - fooDetails: FooDetails - id: ID - } - - type FooDetails { - externalBarId: ID - } - - type Query { - foo: Foo - } -# language=GraphQL -query: | - query { - foo { - bar { - name - } - } - } -variables: { } -serviceCalls: - - serviceName: "service1" - request: - # language=GraphQL - 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: - # language=GraphQL - 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/new hydration/batched-hydration-from-direct-query-with-a-synthetic-field.yml b/test/src/test/resources/fixtures/new hydration/batched-hydration-from-direct-query-with-a-synthetic-field.yml deleted file mode 100644 index ed7261174..000000000 --- a/test/src/test/resources/fixtures/new hydration/batched-hydration-from-direct-query-with-a-synthetic-field.yml +++ /dev/null @@ -1,231 +0,0 @@ -name: "batched hydration from direct query with a synthetic field" -enabled: true -# language=GraphQL -overallSchema: - # language=GraphQL - service2: | - type Query { - users: UsersQuery - } - type UsersQuery { - usersByIds(id: [ID]): [User] - } - type User { - id: ID - } - # language=GraphQL - service1: | - type Query { - issues: [Issue] - } - extend enum NadelHydrationTemplate { - USER_TEMPLATE @hydratedTemplate( - service: "service2" - field: "users.usersByIds" - identifiedBy: "id" - batchSize: 3 - ) - } - - type Issue { - id: ID - authors: [User] @hydratedFrom( - template: USER_TEMPLATE - arguments: [ - {name: "id" valueFromField: "authorIds"} - ] - ) - } -# language=GraphQL -underlyingSchema: - # language=GraphQL - service2: | - type Query { - users: UsersQuery - } - - type User { - id: ID - name: String - } - - type UsersQuery { - usersByIds(id: [ID]): [User] - } - # language=GraphQL - service1: | - type Issue { - authorIds: [ID] - id: ID - } - - type Query { - issues: [Issue] - } -# language=GraphQL -query: | - query { - issues { - id - authors { - id - } - } - } -variables: { } -serviceCalls: - - serviceName: "service1" - request: - # language=GraphQL - query: | - query { - issues { - __typename__batch_hydration__authors: __typename - batch_hydration__authors__authorIds: authorIds - id - } - } - variables: { } - # language=JSON - response: |- - { - "data": { - "issues": [ - { - "__typename__batch_hydration__authors": "Issue", - "batch_hydration__authors__authorIds": [ - "USER-1", - "USER-2" - ], - "id": "ISSUE-1" - }, - { - "__typename__batch_hydration__authors": "Issue", - "batch_hydration__authors__authorIds": [ - "USER-3" - ], - "id": "ISSUE-2" - }, - { - "__typename__batch_hydration__authors": "Issue", - "batch_hydration__authors__authorIds": [ - "USER-2", - "USER-4", - "USER-5" - ], - "id": "ISSUE-3" - } - ] - }, - "extensions": {} - } - - serviceName: "service2" - request: - # language=GraphQL - query: | - query { - users { - usersByIds(id: ["USER-4", "USER-5"]) { - id - batch_hydration__authors__id: id - } - } - } - variables: { } - # language=JSON - response: |- - { - "data": { - "users": { - "usersByIds": [ - { - "id": "USER-4", - "batch_hydration__authors__id": "USER-4" - }, - { - "id": "USER-5", - "batch_hydration__authors__id": "USER-5" - } - ] - } - }, - "extensions": {} - } - - serviceName: "service2" - request: - # language=GraphQL - query: | - query { - users { - usersByIds(id: ["USER-1", "USER-2", "USER-3"]) { - id - batch_hydration__authors__id: id - } - } - } - variables: { } - # language=JSON - response: |- - { - "data": { - "users": { - "usersByIds": [ - { - "id": "USER-1", - "batch_hydration__authors__id": "USER-1" - }, - { - "id": "USER-2", - "batch_hydration__authors__id": "USER-2" - }, - { - "id": "USER-3", - "batch_hydration__authors__id": "USER-3" - } - ] - } - }, - "extensions": {} - } -# language=JSON -response: |- - { - "data": { - "issues": [ - { - "id": "ISSUE-1", - "authors": [ - { - "id": "USER-1" - }, - { - "id": "USER-2" - } - ] - }, - { - "id": "ISSUE-2", - "authors": [ - { - "id": "USER-3" - } - ] - }, - { - "id": "ISSUE-3", - "authors": [ - { - "id": "USER-2" - }, - { - "id": "USER-4" - }, - { - "id": "USER-5" - } - ] - } - ] - }, - "extensions": {} - }