diff --git a/lib/src/main/java/graphql/nadel/NadelSchemas.kt b/lib/src/main/java/graphql/nadel/NadelSchemas.kt index b8d2eda1f..f106c8b0e 100644 --- a/lib/src/main/java/graphql/nadel/NadelSchemas.kt +++ b/lib/src/main/java/graphql/nadel/NadelSchemas.kt @@ -202,11 +202,11 @@ data class NadelSchemas( val underlyingSchemaGenerator = UnderlyingSchemaGenerator() return builder.overallSchemaReaders.map { (serviceName, reader) -> - val nadelDefinitions = SchemaUtil.parseSchemaDefinitions( + val schemaDefinitions = SchemaUtil.parseSchemaDefinitions( reader, captureSourceLocation = captureSourceLocation, ) - val nadelDefinitionRegistry = NadelDefinitionRegistry.from(nadelDefinitions) + val typeDefinitionRegistry = NadelTypeDefinitionRegistry.from(schemaDefinitions) // Builder should enforce non-null entry val underlyingSchema = underlyingSchemaGenerator.buildUnderlyingSchema( @@ -217,7 +217,7 @@ data class NadelSchemas( val serviceExecution = serviceExecutionFactory.getServiceExecution(serviceName) - Service(serviceName, underlyingSchema, serviceExecution, nadelDefinitionRegistry) + Service(serviceName, underlyingSchema, serviceExecution, typeDefinitionRegistry) } } diff --git a/lib/src/main/java/graphql/nadel/NadelDefinitionRegistry.kt b/lib/src/main/java/graphql/nadel/NadelTypeDefinitionRegistry.kt similarity index 95% rename from lib/src/main/java/graphql/nadel/NadelDefinitionRegistry.kt rename to lib/src/main/java/graphql/nadel/NadelTypeDefinitionRegistry.kt index b104355c1..bbb1ba915 100644 --- a/lib/src/main/java/graphql/nadel/NadelDefinitionRegistry.kt +++ b/lib/src/main/java/graphql/nadel/NadelTypeDefinitionRegistry.kt @@ -12,7 +12,7 @@ import java.util.Collections * Alternative to [graphql.schema.idl.TypeDefinitionRegistry] but is more generic * and tailored to Nadel specific operations to build the overall schema. */ -class NadelDefinitionRegistry { +class NadelTypeDefinitionRegistry { private val _definitions: MutableList = ArrayList() private val definitionsByClass = LinkedHashMap, MutableList>() private val definitionsByName = LinkedHashMap>() @@ -101,8 +101,8 @@ class NadelDefinitionRegistry { companion object { @JvmStatic - fun from(definitions: List): NadelDefinitionRegistry { - val registry = NadelDefinitionRegistry() + fun from(definitions: List): NadelTypeDefinitionRegistry { + val registry = NadelTypeDefinitionRegistry() definitions.forEach(registry::add) return registry } diff --git a/lib/src/main/java/graphql/nadel/Service.kt b/lib/src/main/java/graphql/nadel/Service.kt index 52569ad4e..b3e906a1f 100644 --- a/lib/src/main/java/graphql/nadel/Service.kt +++ b/lib/src/main/java/graphql/nadel/Service.kt @@ -16,7 +16,7 @@ open class Service( /** * These are the GraphQL definitions that a service contributes to the OVERALL schema. */ - val definitionRegistry: NadelDefinitionRegistry, + val definitionRegistry: NadelTypeDefinitionRegistry, ) { override fun toString(): String { return "Service{name='$name'}" diff --git a/lib/src/main/java/graphql/nadel/definition/NadelDefinition.kt b/lib/src/main/java/graphql/nadel/definition/NadelDefinition.kt deleted file mode 100644 index 6633d6a5d..000000000 --- a/lib/src/main/java/graphql/nadel/definition/NadelDefinition.kt +++ /dev/null @@ -1,3 +0,0 @@ -package graphql.nadel.definition - -interface NadelDefinition diff --git a/lib/src/main/java/graphql/nadel/definition/NadelInstructionDefinition.kt b/lib/src/main/java/graphql/nadel/definition/NadelInstructionDefinition.kt new file mode 100644 index 000000000..eb2fc95d8 --- /dev/null +++ b/lib/src/main/java/graphql/nadel/definition/NadelInstructionDefinition.kt @@ -0,0 +1,3 @@ +package graphql.nadel.definition + +interface NadelInstructionDefinition diff --git a/lib/src/main/java/graphql/nadel/definition/NadelSchemaMemberCoordinates.kt b/lib/src/main/java/graphql/nadel/definition/NadelSchemaMemberCoordinates.kt index e884ccf61..03e9be697 100644 --- a/lib/src/main/java/graphql/nadel/definition/NadelSchemaMemberCoordinates.kt +++ b/lib/src/main/java/graphql/nadel/definition/NadelSchemaMemberCoordinates.kt @@ -1,5 +1,6 @@ package graphql.nadel.definition +import graphql.nadel.engine.blueprint.NadelSchemaTraverserElement import graphql.schema.GraphQLDirective import graphql.schema.GraphQLEnumType import graphql.schema.GraphQLFieldsContainer @@ -168,3 +169,14 @@ fun GraphQLNamedType.coordinates(): NadelSchemaMemberCoordinates.Type { else -> throw IllegalArgumentException(javaClass.name) } } + +internal fun NadelSchemaTraverserElement.Type.coordinates(): NadelSchemaMemberCoordinates.Type { + return when (this) { + is NadelSchemaTraverserElement.EnumType -> NadelSchemaMemberCoordinates.Enum(node.name) + is NadelSchemaTraverserElement.InputObjectType -> NadelSchemaMemberCoordinates.InputObject(node.name) + is NadelSchemaTraverserElement.ScalarType -> NadelSchemaMemberCoordinates.Scalar(node.name) + is NadelSchemaTraverserElement.InterfaceType -> NadelSchemaMemberCoordinates.Interface(node.name) + is NadelSchemaTraverserElement.ObjectType -> NadelSchemaMemberCoordinates.Object(node.name) + is NadelSchemaTraverserElement.UnionType -> NadelSchemaMemberCoordinates.Union(node.name) + } +} 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 db186a6be..65b509022 100644 --- a/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationDefinition.kt +++ b/lib/src/main/java/graphql/nadel/definition/hydration/NadelHydrationDefinition.kt @@ -4,11 +4,10 @@ import graphql.language.ArrayValue import graphql.language.DirectiveDefinition import graphql.language.FieldDefinition import graphql.language.ObjectValue -import graphql.nadel.definition.NadelDefinition +import graphql.nadel.definition.NadelInstructionDefinition import graphql.nadel.definition.hydration.NadelHydrationDefinition.Keyword import graphql.nadel.engine.util.JsonMap import graphql.nadel.engine.util.parseDefinition -import graphql.nadel.validation.NadelValidationContext import graphql.schema.GraphQLAppliedDirective import graphql.schema.GraphQLFieldDefinition @@ -16,28 +15,16 @@ fun FieldDefinition.hasHydratedDefinition(): Boolean { return hasDirective(Keyword.hydrated) } -context(NadelValidationContext) -@Deprecated("Mistake to use this inside validation", level = DeprecationLevel.ERROR) -fun GraphQLFieldDefinition.hasHydratedDefinition(): Nothing { - throw UnsupportedOperationException() -} - fun GraphQLFieldDefinition.hasHydratedDefinition(): Boolean { return hasAppliedDirective(Keyword.hydrated) } -context(NadelValidationContext) -@Deprecated("Mistake to use this inside validation", level = DeprecationLevel.ERROR) -fun GraphQLFieldDefinition.parseHydrationDefinitions(): Nothing { - throw UnsupportedOperationException() -} - fun GraphQLFieldDefinition.parseHydrationDefinitions(): List { return getAppliedDirectives(Keyword.hydrated) .map(::NadelHydrationDefinitionImpl) } -interface NadelHydrationDefinition : NadelDefinition { +interface NadelHydrationDefinition : NadelInstructionDefinition { companion object { val directiveDefinition = parseDefinition( // language=GraphQL 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 9c0627afd..2ba1d469d 100644 --- a/lib/src/main/java/graphql/nadel/definition/partition/NadelPartitionDefinition.kt +++ b/lib/src/main/java/graphql/nadel/definition/partition/NadelPartitionDefinition.kt @@ -2,7 +2,7 @@ package graphql.nadel.definition.partition import graphql.language.DirectiveDefinition import graphql.language.FieldDefinition -import graphql.nadel.definition.NadelDefinition +import graphql.nadel.definition.NadelInstructionDefinition import graphql.nadel.definition.partition.NadelPartitionDefinition.Keyword import graphql.nadel.engine.util.parseDefinition import graphql.schema.GraphQLAppliedDirective @@ -10,7 +10,7 @@ import graphql.schema.GraphQLFieldDefinition class NadelPartitionDefinition( private val appliedDirective: GraphQLAppliedDirective, -) : NadelDefinition { +) : NadelInstructionDefinition { companion object { val directiveDefinition = parseDefinition( // language=GraphQL 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 c7a0478d0..4dc0b3ab1 100644 --- a/lib/src/main/java/graphql/nadel/definition/renamed/NadelRenamedDefinition.kt +++ b/lib/src/main/java/graphql/nadel/definition/renamed/NadelRenamedDefinition.kt @@ -2,7 +2,7 @@ package graphql.nadel.definition.renamed import graphql.language.DirectiveDefinition import graphql.language.DirectivesContainer -import graphql.nadel.definition.NadelDefinition +import graphql.nadel.definition.NadelInstructionDefinition import graphql.nadel.definition.renamed.NadelRenamedDefinition.Keyword import graphql.nadel.engine.util.parseDefinition import graphql.nadel.validation.NadelValidationContext @@ -11,7 +11,7 @@ import graphql.schema.GraphQLDirectiveContainer import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLNamedType -sealed class NadelRenamedDefinition : NadelDefinition { +sealed class NadelRenamedDefinition : NadelInstructionDefinition { companion object { val directiveDefinition = parseDefinition( // language=GraphQL @@ -48,12 +48,6 @@ sealed class NadelRenamedDefinition : NadelDefinition { } } -context(NadelValidationContext) -@Deprecated("Mistake to use this inside validation", level = DeprecationLevel.ERROR) -fun GraphQLDirectiveContainer.hasRenameDefinition(): Nothing { - throw UnsupportedOperationException() -} - fun GraphQLDirectiveContainer.hasRenameDefinition(): Boolean { return hasAppliedDirective(Keyword.renamed) } @@ -62,12 +56,6 @@ fun DirectivesContainer<*>.hasRenameDefinition(): Boolean { return hasDirective(Keyword.renamed) } -context(NadelValidationContext) -@Deprecated("Mistake to use this inside validation", level = DeprecationLevel.ERROR) -fun GraphQLFieldDefinition.parseRenamedOrNull(): Nothing { - throw UnsupportedOperationException() -} - fun GraphQLFieldDefinition.parseRenamedOrNull(): NadelRenamedDefinition.Field? { val directive = getAppliedDirective(Keyword.renamed) ?: return null @@ -75,12 +63,6 @@ fun GraphQLFieldDefinition.parseRenamedOrNull(): NadelRenamedDefinition.Field? { return NadelRenamedDefinition.Field(directive) } -context(NadelValidationContext) -@Deprecated("Mistake to use this inside validation", level = DeprecationLevel.ERROR) -fun GraphQLNamedType.parseRenamedOrNull(): Nothing { - throw UnsupportedOperationException() -} - fun GraphQLNamedType.parseRenamedOrNull(): NadelRenamedDefinition.Type? { val directive = (this as GraphQLDirectiveContainer).getAppliedDirective(Keyword.renamed) ?: return null diff --git a/lib/src/main/java/graphql/nadel/definition/virtualType/NadelVirtualTypeDefinition.kt b/lib/src/main/java/graphql/nadel/definition/virtualType/NadelVirtualTypeDefinition.kt index 25c46b044..22b3c3b08 100644 --- a/lib/src/main/java/graphql/nadel/definition/virtualType/NadelVirtualTypeDefinition.kt +++ b/lib/src/main/java/graphql/nadel/definition/virtualType/NadelVirtualTypeDefinition.kt @@ -2,7 +2,7 @@ package graphql.nadel.definition.virtualType import graphql.language.DirectiveDefinition import graphql.language.DirectivesContainer -import graphql.nadel.definition.NadelDefinition +import graphql.nadel.definition.NadelInstructionDefinition import graphql.nadel.engine.util.parseDefinition import graphql.schema.GraphQLDirectiveContainer import graphql.schema.GraphQLSchemaElement @@ -16,7 +16,7 @@ internal fun DirectivesContainer<*>.hasVirtualTypeDefinition(): Boolean { return hasDirective(NadelVirtualTypeDefinition.directiveDefinition.name) } -internal class NadelVirtualTypeDefinition : NadelDefinition { +internal class NadelVirtualTypeDefinition : NadelInstructionDefinition { companion object { val directiveDefinition = parseDefinition( // language=GraphQL diff --git a/lib/src/main/java/graphql/nadel/engine/blueprint/IntrospectionService.kt b/lib/src/main/java/graphql/nadel/engine/blueprint/IntrospectionService.kt index 726ff108d..80b6e1b3f 100644 --- a/lib/src/main/java/graphql/nadel/engine/blueprint/IntrospectionService.kt +++ b/lib/src/main/java/graphql/nadel/engine/blueprint/IntrospectionService.kt @@ -4,7 +4,7 @@ import graphql.ExecutionInput import graphql.GraphQL import graphql.GraphqlErrorHelper.toSpecification import graphql.language.AstPrinter -import graphql.nadel.NadelDefinitionRegistry +import graphql.nadel.NadelTypeDefinitionRegistry import graphql.nadel.Service import graphql.nadel.ServiceExecution import graphql.nadel.ServiceExecutionParameters @@ -24,7 +24,7 @@ import java.util.concurrent.CompletableFuture internal class IntrospectionService( schema: GraphQLSchema, introspectionRunnerFactory: NadelIntrospectionRunnerFactory, -) : Service(name, schema, introspectionRunnerFactory.make(schema), NadelDefinitionRegistry()) { +) : Service(name, schema, introspectionRunnerFactory.make(schema), NadelTypeDefinitionRegistry()) { companion object { const val name = "__introspection" } diff --git a/lib/src/main/java/graphql/nadel/schema/OverallSchemaGenerator.kt b/lib/src/main/java/graphql/nadel/schema/OverallSchemaGenerator.kt index e89f277cd..df010fe62 100644 --- a/lib/src/main/java/graphql/nadel/schema/OverallSchemaGenerator.kt +++ b/lib/src/main/java/graphql/nadel/schema/OverallSchemaGenerator.kt @@ -7,7 +7,7 @@ import graphql.language.ObjectTypeDefinition.newObjectTypeDefinition import graphql.language.ScalarTypeDefinition.newScalarTypeDefinition import graphql.language.SchemaDefinition import graphql.language.SourceLocation -import graphql.nadel.NadelDefinitionRegistry +import graphql.nadel.NadelTypeDefinitionRegistry import graphql.nadel.NadelOperationKind import graphql.nadel.util.AnyNamedNode import graphql.nadel.util.AnySDLDefinition @@ -22,7 +22,7 @@ import graphql.schema.idl.WiringFactory internal class OverallSchemaGenerator { fun buildOverallSchema( - serviceRegistries: List, + serviceRegistries: List, wiringFactory: WiringFactory, ): GraphQLSchema { val schemaGenerator = SchemaGenerator() @@ -33,7 +33,7 @@ internal class OverallSchemaGenerator { return schemaGenerator.makeExecutableSchema(createTypeRegistry(serviceRegistries), runtimeWiring) } - private fun createTypeRegistry(serviceRegistries: List): TypeDefinitionRegistry { + private fun createTypeRegistry(serviceRegistries: List): TypeDefinitionRegistry { val topLevelFields = NadelOperationKind.entries .associateWith { mutableListOf() @@ -128,7 +128,7 @@ internal class OverallSchemaGenerator { private fun collectTypes( topLevelFields: Map>, allDefinitions: MutableList, - definitionRegistry: NadelDefinitionRegistry, + definitionRegistry: NadelTypeDefinitionRegistry, ) { val opDefinitions = definitionRegistry.operationMap val opTypeNames: MutableSet = HashSet(3) diff --git a/lib/src/main/java/graphql/nadel/validation/NadelAssignableTypeValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelAssignableTypeValidation.kt new file mode 100644 index 000000000..87447db54 --- /dev/null +++ b/lib/src/main/java/graphql/nadel/validation/NadelAssignableTypeValidation.kt @@ -0,0 +1,59 @@ +package graphql.nadel.validation + +import graphql.nadel.engine.util.unwrapAll +import graphql.schema.GraphQLType + +class NadelAssignableTypeValidation internal constructor( + private val typeWrappingValidation: NadelTypeWrappingValidation, +) { + context(NadelValidationContext) + fun isOutputTypeAssignable( + overallType: GraphQLType, + underlyingType: GraphQLType, + ): Boolean { + // Note: the (supplied) value comes from the underlying service, and that value needs to fit the (required) overall field's type + return isTypeAssignable( + suppliedType = underlyingType, + requiredType = overallType, + // Compare underlying type names + suppliedTypeName = underlyingType.unwrapAll().name, + requiredTypeName = instructionDefinitions.getUnderlyingTypeName(overallType.unwrapAll()), + ) + } + + context(NadelValidationContext) + fun isInputTypeAssignable( + overallType: GraphQLType, + underlyingType: GraphQLType, + ): Boolean { + // Note: the (supplied) value comes from the user (overall schema) and needs to be assigned to the (required) underlying type + return isTypeAssignable( + suppliedType = overallType, + requiredType = underlyingType, + // Compare underlying type names + suppliedTypeName = instructionDefinitions.getUnderlyingTypeName(overallType.unwrapAll()), + requiredTypeName = underlyingType.unwrapAll().name, + ) + } + + context(NadelValidationContext) + fun isTypeAssignable( + suppliedType: GraphQLType, + requiredType: GraphQLType, + suppliedTypeName: String, + requiredTypeName: String, + ): Boolean { + return suppliedTypeName == requiredTypeName && isTypeWrappingValid(suppliedType, requiredType) + } + + private fun isTypeWrappingValid( + suppliedType: GraphQLType, + requiredType: GraphQLType, + ): Boolean { + return typeWrappingValidation.isTypeWrappingValid( + lhs = suppliedType, + rhs = requiredType, + rule = NadelTypeWrappingValidation.Rule.LHS_MUST_BE_STRICTER_OR_SAME, + ) + } +} diff --git a/lib/src/main/java/graphql/nadel/validation/NadelFieldValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelFieldValidation.kt index fb8e4052d..0f67503fc 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelFieldValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelFieldValidation.kt @@ -1,26 +1,29 @@ package graphql.nadel.validation -import graphql.nadel.engine.util.unwrapAll +import graphql.nadel.definition.renamed.NadelRenamedDefinition +import graphql.nadel.engine.blueprint.NadelDeepRenameFieldInstruction +import graphql.nadel.engine.blueprint.NadelFieldInstruction +import graphql.nadel.engine.blueprint.NadelRenameFieldInstruction +import graphql.nadel.engine.transform.query.NadelQueryPath +import graphql.nadel.engine.util.getFieldAt +import graphql.nadel.engine.util.makeFieldCoordinates +import graphql.nadel.validation.NadelSchemaValidationError.CannotRenameHydratedField +import graphql.nadel.validation.NadelSchemaValidationError.CannotRenamePartitionedField import graphql.nadel.validation.NadelSchemaValidationError.IncompatibleArgumentInputType import graphql.nadel.validation.NadelSchemaValidationError.IncompatibleFieldOutputType import graphql.nadel.validation.NadelSchemaValidationError.MissingArgumentOnUnderlying +import graphql.nadel.validation.NadelSchemaValidationError.MissingRename import graphql.nadel.validation.NadelSchemaValidationError.MissingUnderlyingField -import graphql.nadel.validation.NadelTypeWrappingValidation.Rule.LHS_MUST_BE_LOOSER_OR_SAME import graphql.nadel.validation.hydration.NadelHydrationValidation import graphql.nadel.validation.util.NadelCombinedTypeUtil.getFieldsThatServiceContributed -import graphql.schema.GraphQLArgument import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLNamedSchemaElement -import graphql.schema.GraphQLOutputType class NadelFieldValidation internal constructor( private val hydrationValidation: NadelHydrationValidation, + private val partitionValidation: NadelPartitionValidation, + private val assignableTypeValidation: NadelAssignableTypeValidation, ) { - private val renameValidation = NadelRenameValidation(this) - private val inputValidation = NadelInputValidation() - private val partitionValidation = NadelPartitionValidation() - private val typeWrappingValidation = NadelTypeWrappingValidation() - context(NadelValidationContext) fun validate( schemaElement: NadelServiceSchemaElement.FieldsContainer, @@ -58,9 +61,9 @@ class NadelFieldValidation internal constructor( parent: NadelServiceSchemaElement.FieldsContainer, overallField: GraphQLFieldDefinition, ): NadelSchemaValidationResult { - return if (isRenamed(parent, overallField)) { - renameValidation.validate(parent, overallField) - } else if (isHydrated(parent, overallField)) { + return if (instructionDefinitions.isRenamed(parent, overallField)) { + validateRename(parent, overallField) + } else if (instructionDefinitions.isHydrated(parent, overallField)) { hydrationValidation.validate(parent, overallField) } else { val underlyingField = parent.underlying.getField(overallField.name) @@ -88,14 +91,12 @@ class NadelFieldValidation internal constructor( if (underlyingArg == null) { MissingArgumentOnUnderlying(parent, overallField, underlyingField, overallArg) } else { - if (isUnwrappedArgTypeSame(overallArg, underlyingArg)) { - inputValidation - .validate( - parent = parent, - overallField = overallField, - overallInputArgument = overallArg, - underlyingInputArgument = underlyingArg - ) + val isArgumentTypeAssignable = assignableTypeValidation.isInputTypeAssignable( + overallType = overallArg.type, + underlyingType = underlyingArg.type + ) + if (isArgumentTypeAssignable) { + ok() } else { IncompatibleArgumentInputType( parentType = parent, @@ -115,13 +116,61 @@ class NadelFieldValidation internal constructor( } context(NadelValidationContext) - private fun isUnwrappedArgTypeSame( - overallArg: GraphQLArgument, - underlyingArg: GraphQLArgument, - ): Boolean { - val overallArgTypeUnwrapped = overallArg.type.unwrapAll() - val underlyingArgTypeUnwrapped = underlyingArg.type.unwrapAll() - return getUnderlyingTypeName(overallArgTypeUnwrapped) == underlyingArgTypeUnwrapped.name + private fun validateRename( + parent: NadelServiceSchemaElement.FieldsContainer, + overallField: GraphQLFieldDefinition, + ): NadelSchemaValidationResult { + if (instructionDefinitions.isHydrated(parent, overallField)) { + return CannotRenameHydratedField(parent, overallField) + } + + if (instructionDefinitions.isPartitioned(parent, overallField)) { + return CannotRenamePartitionedField(parent, overallField) + } + + val rename = instructionDefinitions.getRenamedOrNull(parent, overallField) + ?: return ok() + + val underlyingField = parent.underlying.getFieldAt(rename.from) + ?: return MissingRename(parent, overallField, rename) + + // In theory, this should have .onError { return it } but the schema has too many violations… + val result = validate(parent, overallField, underlyingField) + + return if (parent is NadelServiceSchemaElement.Object) { + results( + result, + NadelValidatedFieldResult( + service = parent.service, + fieldInstruction = makeRenameFieldInstruction(parent, overallField, rename) + ), + ) + } else { + result + } + } + + context(NadelValidationContext) + private fun makeRenameFieldInstruction( + parent: NadelServiceSchemaElement.FieldsContainer, + overallField: GraphQLFieldDefinition, + rename: NadelRenamedDefinition.Field, + ): NadelFieldInstruction { + val location = makeFieldCoordinates( + parentType = parent.overall, + field = overallField, + ) + + val underlyingName = rename.from.singleOrNull() + ?: return NadelDeepRenameFieldInstruction( + location = location, + queryPathToField = NadelQueryPath(rename.from), + ) + + return NadelRenameFieldInstruction( + location = location, + underlyingName = underlyingName, + ) } context(NadelValidationContext) @@ -130,8 +179,12 @@ class NadelFieldValidation internal constructor( overallField: GraphQLFieldDefinition, underlyingField: GraphQLFieldDefinition, ): NadelSchemaValidationResult { - // This checks whether the output type e.g. name or List or NonNull wrappings are valid - return if (isOutputTypeValid(overallType = overallField.type, underlyingType = underlyingField.type)) { + val isUnderlyingTypeAssignable = assignableTypeValidation.isOutputTypeAssignable( + overallType = overallField.type, + underlyingType = underlyingField.type, + ) + + return if (isUnderlyingTypeAssignable) { ok() } else { results( @@ -140,25 +193,6 @@ class NadelFieldValidation internal constructor( } } - /** - * It checks whether the type name and type wrappings e.g. [graphql.schema.GraphQLNonNull] make sense. - */ - context(NadelValidationContext) - private fun isOutputTypeValid( - overallType: GraphQLOutputType, - underlyingType: GraphQLOutputType, - ): Boolean { - val isTypeWrappingValid = typeWrappingValidation - .isTypeWrappingValid( - lhs = overallType, - rhs = underlyingType, - rule = LHS_MUST_BE_LOOSER_OR_SAME, - ) - - return isTypeWrappingValid - && getUnderlyingTypeName(overallType.unwrapAll()) == underlyingType.unwrapAll().name - } - context(NadelValidationContext) private fun isCombinedType(type: GraphQLNamedSchemaElement): Boolean { return type.name in combinedTypeNames diff --git a/lib/src/main/java/graphql/nadel/validation/NadelInputValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelInputObjectValidation.kt similarity index 54% rename from lib/src/main/java/graphql/nadel/validation/NadelInputValidation.kt rename to lib/src/main/java/graphql/nadel/validation/NadelInputObjectValidation.kt index fe9345366..01febeba9 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelInputValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelInputObjectValidation.kt @@ -1,19 +1,13 @@ package graphql.nadel.validation import graphql.nadel.engine.util.strictAssociateBy -import graphql.nadel.engine.util.unwrapAll -import graphql.nadel.validation.NadelSchemaValidationError.IncompatibleArgumentInputType import graphql.nadel.validation.NadelSchemaValidationError.IncompatibleFieldInputType import graphql.nadel.validation.NadelSchemaValidationError.MissingUnderlyingInputField -import graphql.schema.GraphQLArgument -import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLInputObjectField -import graphql.schema.GraphQLInputType -import graphql.schema.GraphQLUnmodifiedType - -class NadelInputValidation internal constructor() { - private val typeWrappingValidation = NadelTypeWrappingValidation() +class NadelInputObjectValidation internal constructor( + private val assignableTypeValidation: NadelAssignableTypeValidation, +) { context(NadelValidationContext) fun validate( schemaElement: NadelServiceSchemaElement.InputObject, @@ -61,46 +55,15 @@ class NadelInputValidation internal constructor() { overallInputField: GraphQLInputObjectField, underlyingInputField: GraphQLInputObjectField, ): NadelSchemaValidationResult { - return if (!isInputTypeValid(overallInputField.type, underlyingInputField.type)) { - IncompatibleFieldInputType(parent, overallInputField, underlyingInputField) - } else { - ok() - } - } + val isTypeAssignable = assignableTypeValidation.isInputTypeAssignable( + overallType = overallInputField.type, + underlyingType = underlyingInputField.type + ) - context(NadelValidationContext) - fun validate( - parent: NadelServiceSchemaElement.FieldsContainer, - overallField: GraphQLFieldDefinition, - overallInputArgument: GraphQLArgument, - underlyingInputArgument: GraphQLArgument, - ): NadelSchemaValidationResult { - return if (!isInputTypeValid(overallInputArgument.type, underlyingInputArgument.type)) { - IncompatibleArgumentInputType(parent, overallField, overallInputArgument, underlyingInputArgument) - } else { + return if (isTypeAssignable) { ok() + } else { + IncompatibleFieldInputType(parent, overallInputField, underlyingInputField) } } - - context(NadelValidationContext) - private fun isInputTypeValid( - overallType: GraphQLInputType, - underlyingType: GraphQLInputType, - ): Boolean { - val typeWrappingValid = typeWrappingValidation.isTypeWrappingValid( - lhs = overallType, - rhs = underlyingType, - rule = NadelTypeWrappingValidation.Rule.LHS_MUST_BE_STRICTER_OR_SAME, - ) - - return typeWrappingValid && isInputTypeNameValid(overallType.unwrapAll(), underlyingType.unwrapAll()) - } - - context(NadelValidationContext) - private fun isInputTypeNameValid( - overallType: GraphQLUnmodifiedType, - underlyingType: GraphQLUnmodifiedType, - ): Boolean { - return getUnderlyingTypeName(overallType) == underlyingType.name - } } diff --git a/lib/src/main/java/graphql/nadel/validation/NadelDefinitionParser.kt b/lib/src/main/java/graphql/nadel/validation/NadelInstructionDefinitionParser.kt similarity index 83% rename from lib/src/main/java/graphql/nadel/validation/NadelDefinitionParser.kt rename to lib/src/main/java/graphql/nadel/validation/NadelInstructionDefinitionParser.kt index 04239e95f..cb5669b42 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelDefinitionParser.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelInstructionDefinitionParser.kt @@ -1,6 +1,6 @@ package graphql.nadel.validation -import graphql.nadel.definition.NadelDefinition +import graphql.nadel.definition.NadelInstructionDefinition import graphql.nadel.definition.NadelSchemaMemberCoordinates import graphql.nadel.definition.coordinates import graphql.nadel.definition.hydration.parseHydrationDefinitions @@ -13,14 +13,14 @@ import graphql.nadel.engine.blueprint.NadelSchemaTraverserElement import graphql.nadel.engine.blueprint.NadelSchemaTraverserVisitor import graphql.schema.GraphQLSchema -internal class NadelDefinitionParser( +internal class NadelInstructionDefinitionParser( private val hook: NadelSchemaValidationHook, private val idHydrationDefinitionParser: NadelIdHydrationDefinitionParser, ) { fun parse( engineSchema: GraphQLSchema, - ): NadelValidationInterimResult>> { - val definitions = mutableMapOf>() + ): NadelValidationInterimResult { + val definitionMap = mutableMapOf>() val errors = mutableListOf() NadelSchemaTraverser() @@ -77,8 +77,8 @@ internal class NadelDefinitionParser( parent.coordinates().field(node.name) } - fun addAll(defs: List) { - definitions.computeIfAbsent(coords) { mutableListOf() }.addAll(defs) + fun addAll(defs: List) { + definitionMap.computeIfAbsent(coords) { mutableListOf() }.addAll(defs) } // todo: I think we can clean this up if we make all these extend from a new super NadelFieldDefinitionParser @@ -162,22 +162,14 @@ internal class NadelDefinitionParser( } private fun visitType(element: NadelSchemaTraverserElement.Type) { - val coordinates = when (element) { - is NadelSchemaTraverserElement.EnumType -> element.node.coordinates() - is NadelSchemaTraverserElement.InputObjectType -> element.node.coordinates() - is NadelSchemaTraverserElement.ScalarType -> element.node.coordinates() - is NadelSchemaTraverserElement.InterfaceType -> element.node.coordinates() - is NadelSchemaTraverserElement.ObjectType -> element.node.coordinates() - is NadelSchemaTraverserElement.UnionType -> element.node.coordinates() - } - + val coordinates = element.coordinates() val type = element.node type.parseRenamedOrNull()?.also { renamed -> - definitions.computeIfAbsent(coordinates) { mutableListOf() }.add(renamed) + definitionMap.computeIfAbsent(coordinates) { mutableListOf() }.add(renamed) } if (type.hasVirtualTypeDefinition()) { - definitions.computeIfAbsent(coordinates) { mutableListOf() } + definitionMap.computeIfAbsent(coordinates) { mutableListOf() } .add(NadelVirtualTypeDefinition()) } } @@ -185,7 +177,7 @@ internal class NadelDefinitionParser( ) return if (errors.isEmpty()) { - NadelValidationInterimResult.Success.of(definitions) + NadelValidationInterimResult.Success.of(NadelInstructionDefinitionRegistry(definitionMap)) } else { NadelValidationInterimResult.Error.of(NadelSchemaValidationResults(errors)) } diff --git a/lib/src/main/java/graphql/nadel/validation/NadelInstructionDefinitionRegistry.kt b/lib/src/main/java/graphql/nadel/validation/NadelInstructionDefinitionRegistry.kt new file mode 100644 index 000000000..bdab070f3 --- /dev/null +++ b/lib/src/main/java/graphql/nadel/validation/NadelInstructionDefinitionRegistry.kt @@ -0,0 +1,168 @@ +package graphql.nadel.validation + +import graphql.nadel.definition.NadelInstructionDefinition +import graphql.nadel.definition.NadelSchemaMemberCoordinates +import graphql.nadel.definition.coordinates +import graphql.nadel.definition.hydration.NadelHydrationDefinition +import graphql.nadel.definition.partition.NadelPartitionDefinition +import graphql.nadel.definition.renamed.NadelRenamedDefinition +import graphql.schema.GraphQLFieldDefinition +import graphql.schema.GraphQLFieldsContainer +import graphql.schema.GraphQLNamedType + +data class NadelInstructionDefinitionRegistry( + private val definitions: Map>, +) { + fun isHydrated( + container: GraphQLFieldsContainer, + field: GraphQLFieldDefinition, + ): Boolean { + return getHydrationDefinitions(container, field).any() + } + + fun isHydrated( + container: NadelServiceSchemaElement.FieldsContainer, + field: GraphQLFieldDefinition, + ): Boolean { + return getHydrationDefinitions(coords(container, field)).any() + } + + fun getHydrationDefinitions( + container: NadelServiceSchemaElement.FieldsContainer, + field: GraphQLFieldDefinition, + ): Sequence { + return getHydrationDefinitions(coords(container, field)) + } + + fun getHydrationDefinitions( + container: GraphQLFieldsContainer, + field: GraphQLFieldDefinition, + ): Sequence { + return getHydrationDefinitions(container.coordinates().field(field.name)) + } + + fun getHydrationDefinitions( + coords: NadelSchemaMemberCoordinates.Field, + ): Sequence { + val definitions = definitions[coords] + ?: return emptySequence() + + return definitions.asSequence() + .filterIsInstance() + } + + fun isRenamed( + container: NadelServiceSchemaElement.Type, + ): Boolean { + return getRenamedOrNull(container.overall) != null + } + + fun isRenamed( + container: GraphQLFieldsContainer, + field: GraphQLFieldDefinition, + ): Boolean { + return getRenamedOrNull(container, field) != null + } + + fun isRenamed( + container: NadelServiceSchemaElement.FieldsContainer, + field: GraphQLFieldDefinition, + ): Boolean { + return getRenamedOrNull(coords(container, field)) != null + } + + fun getRenamedOrNull( + container: GraphQLFieldsContainer, + field: GraphQLFieldDefinition, + ): NadelRenamedDefinition.Field? { + val coords = container.coordinates().field(field.name) + return getRenamedOrNull(coords) + } + + fun getRenamedOrNull( + container: NadelServiceSchemaElement.FieldsContainer, + field: GraphQLFieldDefinition, + ): NadelRenamedDefinition.Field? { + return getRenamedOrNull(coords(container, field)) + } + + fun getRenamedOrNull(coords: NadelSchemaMemberCoordinates.Field): NadelRenamedDefinition.Field? { + val definitions = definitions[coords] + ?: return null + + return definitions.asSequence() + .filterIsInstance() + .firstOrNull() + } + + fun getRenamedOrNull( + container: GraphQLNamedType, + ): NadelRenamedDefinition.Type? { + return getRenamedOrNull(container.coordinates()) + } + + fun getRenamedOrNull(coords: NadelSchemaMemberCoordinates.Type): NadelRenamedDefinition.Type? { + val definitions = definitions[coords] + ?: return null + + return definitions.asSequence() + .filterIsInstance() + .firstOrNull() + } + + fun getUnderlyingTypeName(type: GraphQLNamedType): String { + return getRenamedOrNull(type)?.from ?: type.name + } + + fun isPartitioned( + container: GraphQLFieldsContainer, + field: GraphQLFieldDefinition, + ): Boolean { + return getPartitionedOrNull(container, field) != null + } + + fun isPartitioned( + container: NadelServiceSchemaElement.FieldsContainer, + field: GraphQLFieldDefinition, + ): Boolean { + return getPartitionedOrNull(coords(container, field)) != null + } + + fun getPartitionedOrNull( + container: NadelServiceSchemaElement.FieldsContainer, + field: GraphQLFieldDefinition, + ): NadelPartitionDefinition? { + return getPartitionedOrNull(coords(container, field)) + } + + fun getPartitionedOrNull( + container: GraphQLFieldsContainer, + field: GraphQLFieldDefinition, + ): NadelPartitionDefinition? { + val coords = container.coordinates().field(field.name) + return getPartitionedOrNull(coords) + } + + fun getPartitionedOrNull( + coords: NadelSchemaMemberCoordinates.Field, + ): NadelPartitionDefinition? { + val definitions = definitions[coords] + ?: return null + + return definitions.asSequence() + .filterIsInstance() + .firstOrNull() + } + + private fun coords( + container: NadelServiceSchemaElement.FieldsContainer, + field: GraphQLFieldDefinition, + ): NadelSchemaMemberCoordinates.Field { + val containerCoords = when (container) { + is NadelServiceSchemaElement.Interface -> NadelSchemaMemberCoordinates.Interface(container.overall.name) + is NadelServiceSchemaElement.Object -> NadelSchemaMemberCoordinates.Object(container.overall.name) + } + + return containerCoords.field(field.name) + } +} diff --git a/lib/src/main/java/graphql/nadel/validation/NadelPartitionValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelPartitionValidation.kt index b3fa3b686..d84d7719a 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelPartitionValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelPartitionValidation.kt @@ -25,10 +25,10 @@ internal class NadelPartitionValidation { parent: NadelServiceSchemaElement.FieldsContainer, overallField: GraphQLFieldDefinition, ): NadelSchemaValidationResult { - val partition = getPartitionedOrNull(parent, overallField) + val partition = instructionDefinitions.getPartitionedOrNull(parent, overallField) ?: return ok() - if (isHydrated(parent, overallField)) { + if (instructionDefinitions.isHydrated(parent, overallField)) { return 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 deleted file mode 100644 index 72178da46..000000000 --- a/lib/src/main/java/graphql/nadel/validation/NadelRenameValidation.kt +++ /dev/null @@ -1,75 +0,0 @@ -package graphql.nadel.validation - -import graphql.nadel.definition.renamed.NadelRenamedDefinition -import graphql.nadel.engine.blueprint.NadelDeepRenameFieldInstruction -import graphql.nadel.engine.blueprint.NadelFieldInstruction -import graphql.nadel.engine.blueprint.NadelRenameFieldInstruction -import graphql.nadel.engine.transform.query.NadelQueryPath -import graphql.nadel.engine.util.getFieldAt -import graphql.nadel.engine.util.makeFieldCoordinates -import graphql.nadel.validation.NadelSchemaValidationError.CannotRenameHydratedField -import graphql.nadel.validation.NadelSchemaValidationError.CannotRenamePartitionedField -import graphql.nadel.validation.NadelSchemaValidationError.MissingRename -import graphql.schema.GraphQLFieldDefinition -import graphql.schema.GraphQLObjectType - -internal class NadelRenameValidation( - private val fieldValidation: NadelFieldValidation, -) { - context(NadelValidationContext) - fun validate( - parent: NadelServiceSchemaElement.FieldsContainer, - overallField: GraphQLFieldDefinition, - ): NadelSchemaValidationResult { - if (isHydrated(parent, overallField)) { - return CannotRenameHydratedField(parent, overallField) - } - - if (isPartitioned(parent, overallField)) { - return CannotRenamePartitionedField(parent, overallField) - } - - val rename = getRenamedOrNull(parent, overallField) - ?: return ok() - - val underlyingField = parent.underlying.getFieldAt(rename.from) - ?: return MissingRename(parent, overallField, rename) - - val result = fieldValidation.validate(parent, overallField, underlyingField) - - return if (parent.overall is GraphQLObjectType) { - results( - result, - NadelValidatedFieldResult( - service = parent.service, - fieldInstruction = makeRenameFieldInstruction(parent, overallField, rename) - ), - ) - } else { - result - } - } - - context(NadelValidationContext) - private fun makeRenameFieldInstruction( - parent: NadelServiceSchemaElement.FieldsContainer, - overallField: GraphQLFieldDefinition, - rename: NadelRenamedDefinition.Field, - ): NadelFieldInstruction { - val location = makeFieldCoordinates( - parentType = parent.overall, - field = overallField, - ) - - val underlyingName = rename.from.singleOrNull() - ?: return NadelDeepRenameFieldInstruction( - location = location, - queryPathToField = NadelQueryPath(rename.from), - ) - - return NadelRenameFieldInstruction( - location = location, - underlyingName = underlyingName, - ) - } -} diff --git a/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidation.kt index 156f6c163..b7ed84f6d 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidation.kt @@ -23,13 +23,13 @@ import graphql.schema.GraphQLUnionType class NadelSchemaValidation internal constructor( private val fieldValidation: NadelFieldValidation, - private val inputValidation: NadelInputValidation, + private val inputValidation: NadelInputObjectValidation, private val unionValidation: NadelUnionValidation, private val enumValidation: NadelEnumValidation, private val interfaceValidation: NadelInterfaceValidation, private val namespaceValidation: NadelNamespaceValidation, private val virtualTypeValidation: NadelVirtualTypeValidation, - private val definitionParser: NadelDefinitionParser, + private val instructionDefinitionParser: NadelInstructionDefinitionParser, private val hook: NadelSchemaValidationHook, ) { fun validate( @@ -51,7 +51,7 @@ class NadelSchemaValidation internal constructor( val operationTypes = getOperationTypeNames(engineSchema) val namespaceTypes = getNamespaceOperationTypes(engineSchema) - val definitions = definitionParser.parse(engineSchema) + val instructionDefinitions = instructionDefinitionParser.parse(engineSchema) .onError { return it } val context = NadelValidationContext( @@ -61,7 +61,7 @@ class NadelSchemaValidation internal constructor( hydrationUnions = getHydrationUnions(engineSchema), namespaceTypeNames = namespaceTypes, combinedTypeNames = namespaceTypes + operationTypes.map { it.name }, - definitions = definitions, + instructionDefinitions = instructionDefinitions, hook = hook, ) diff --git a/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationFactory.kt b/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationFactory.kt index 63deb7820..fd1bd6573 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationFactory.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationFactory.kt @@ -1,58 +1,75 @@ package graphql.nadel.validation +import graphql.nadel.validation.hydration.NadelHydrationArgumentTypeValidation +import graphql.nadel.validation.hydration.NadelHydrationArgumentValidation +import graphql.nadel.validation.hydration.NadelHydrationConditionValidation +import graphql.nadel.validation.hydration.NadelHydrationSourceFieldValidation import graphql.nadel.validation.hydration.NadelHydrationValidation +import graphql.nadel.validation.hydration.NadelHydrationVirtualTypeValidation abstract class NadelSchemaValidationFactory { fun create(): NadelSchemaValidation { + val definitionParser = NadelInstructionDefinitionParser( + hook = hook, + idHydrationDefinitionParser = idHydrationDefinitionParser, + ) + return NadelSchemaValidation( fieldValidation = fieldValidation, - inputValidation = inputValidation, + inputValidation = inputObjectValidation, unionValidation = unionValidation, enumValidation = enumValidation, interfaceValidation = interfaceValidation, namespaceValidation = namespaceValidation, virtualTypeValidation = virtualTypeValidation, - definitionParser = NadelDefinitionParser( - hook = hook, - idHydrationDefinitionParser = NadelIdHydrationDefinitionParser(), - ), + instructionDefinitionParser = definitionParser, hook = hook, ) } - protected val hydrationValidation: NadelHydrationValidation by lazy { - NadelHydrationValidation() - } + private val idHydrationDefinitionParser = NadelIdHydrationDefinitionParser() - protected val fieldValidation: NadelFieldValidation by lazy { - NadelFieldValidation(hydrationValidation) - } + private val partitionValidation = NadelPartitionValidation() - protected val virtualTypeValidation: NadelVirtualTypeValidation by lazy { - NadelVirtualTypeValidation(hydrationValidation) - } + private val typeWrappingValidation = NadelTypeWrappingValidation() - protected val inputValidation: NadelInputValidation by lazy { - NadelInputValidation() - } + private val assignableTypeValidation = NadelAssignableTypeValidation(typeWrappingValidation) - protected val unionValidation: NadelUnionValidation by lazy { - NadelUnionValidation() - } + private val inputObjectValidation = NadelInputObjectValidation(assignableTypeValidation) - protected val enumValidation: NadelEnumValidation by lazy { - NadelEnumValidation() - } + private val hydrationArgumentTypeValidation = NadelHydrationArgumentTypeValidation(typeWrappingValidation) + private val hydrationArgumentValidation = NadelHydrationArgumentValidation(hydrationArgumentTypeValidation) + private val hydrationConditionValidation = NadelHydrationConditionValidation() + private val hydrationSourceFieldValidation = NadelHydrationSourceFieldValidation() + private val hydrationVirtualTypeValidation = NadelHydrationVirtualTypeValidation() - protected val interfaceValidation: NadelInterfaceValidation by lazy { - NadelInterfaceValidation() - } + private val hydrationValidation = NadelHydrationValidation( + argumentValidation = hydrationArgumentValidation, + conditionValidation = hydrationConditionValidation, + sourceFieldValidation = hydrationSourceFieldValidation, + virtualTypeValidation = hydrationVirtualTypeValidation, + ) - protected val namespaceValidation: NadelNamespaceValidation by lazy { - NadelNamespaceValidation() - } + private val fieldValidation = NadelFieldValidation( + hydrationValidation = hydrationValidation, + partitionValidation = partitionValidation, + assignableTypeValidation = assignableTypeValidation, + ) + + private val virtualTypeValidation = NadelVirtualTypeValidation( + hydrationValidation = hydrationValidation, + assignableTypeValidation = assignableTypeValidation, + ) + + private val unionValidation = NadelUnionValidation() + + private val enumValidation = NadelEnumValidation() + + private val interfaceValidation = NadelInterfaceValidation() + + private val namespaceValidation = NadelNamespaceValidation() - protected open val hook: NadelSchemaValidationHook + open val hook: NadelSchemaValidationHook get() = object : NadelSchemaValidationHook() { } diff --git a/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationHook.kt b/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationHook.kt index db024c9db..d54e1e1f5 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationHook.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelSchemaValidationHook.kt @@ -1,6 +1,6 @@ package graphql.nadel.validation -import graphql.nadel.definition.NadelDefinition +import graphql.nadel.definition.NadelInstructionDefinition import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLFieldsContainer import graphql.schema.GraphQLSchema @@ -10,7 +10,7 @@ abstract class NadelSchemaValidationHook { engineSchema: GraphQLSchema, parent: GraphQLFieldsContainer, field: GraphQLFieldDefinition, - ): List { + ): List { return emptyList() } } diff --git a/lib/src/main/java/graphql/nadel/validation/NadelTypeValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelTypeValidation.kt index d2f4ac963..b2512a6d6 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelTypeValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelTypeValidation.kt @@ -18,7 +18,7 @@ import graphql.schema.GraphQLUnionType internal class NadelTypeValidation( private val fieldValidation: NadelFieldValidation, - private val inputValidation: NadelInputValidation, + private val inputValidation: NadelInputObjectValidation, private val unionValidation: NadelUnionValidation, private val enumValidation: NadelEnumValidation, private val interfaceValidation: NadelInterfaceValidation, @@ -261,16 +261,4 @@ internal class NadelTypeValidation( private fun isOperationType(typeName: String): Boolean { return engineSchema.operationTypes.any { it.name == typeName } } - - context(NadelValidationContext) - private fun visitElement(schemaElement: NadelServiceSchemaElement): Boolean { - val schemaElementRef = schemaElement.toRef() - - return if (schemaElementRef in visitedTypes) { - false // Already visited - } else { - visitedTypes.add(schemaElementRef) - true - } - } } diff --git a/lib/src/main/java/graphql/nadel/validation/NadelValidationContext.kt b/lib/src/main/java/graphql/nadel/validation/NadelValidationContext.kt index f179c41fe..75f38f3c5 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelValidationContext.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelValidationContext.kt @@ -1,16 +1,7 @@ package graphql.nadel.validation import graphql.nadel.Service -import graphql.nadel.definition.NadelDefinition -import graphql.nadel.definition.NadelSchemaMemberCoordinates -import graphql.nadel.definition.coordinates -import graphql.nadel.definition.hydration.NadelHydrationDefinition -import graphql.nadel.definition.partition.NadelPartitionDefinition -import graphql.nadel.definition.renamed.NadelRenamedDefinition import graphql.schema.FieldCoordinates -import graphql.schema.GraphQLFieldDefinition -import graphql.schema.GraphQLFieldsContainer -import graphql.schema.GraphQLNamedType import graphql.schema.GraphQLSchema data class NadelValidationContext internal constructor( @@ -20,173 +11,16 @@ data class NadelValidationContext internal constructor( val hydrationUnions: Set, val namespaceTypeNames: Set, val combinedTypeNames: Set, - val definitions: Map>, + val instructionDefinitions: NadelInstructionDefinitionRegistry, val hook: NadelSchemaValidationHook, ) { - internal val visitedTypes: MutableSet = hashSetOf() -} - -context(NadelValidationContext) -fun isHydrated( - container: GraphQLFieldsContainer, - field: GraphQLFieldDefinition, -): Boolean { - return getHydrationDefinitions(container, field).any() -} - -context(NadelValidationContext) -fun isHydrated( - container: NadelServiceSchemaElement.FieldsContainer, - field: GraphQLFieldDefinition, -): Boolean { - return getHydrationDefinitions(coords(container, field)).any() -} - -context(NadelValidationContext) -fun getHydrationDefinitions( - container: NadelServiceSchemaElement.FieldsContainer, - field: GraphQLFieldDefinition, -): Sequence { - return getHydrationDefinitions(coords(container, field)) -} - -context(NadelValidationContext) -fun getHydrationDefinitions( - container: GraphQLFieldsContainer, - field: GraphQLFieldDefinition, -): Sequence { - return getHydrationDefinitions(container.coordinates().field(field.name)) -} - -context(NadelValidationContext) -fun getHydrationDefinitions( - coords: NadelSchemaMemberCoordinates.Field, -): Sequence { - val definitions = definitions[coords] - ?: return emptySequence() - - return definitions.asSequence() - .filterIsInstance() -} - -context(NadelValidationContext) -fun isRenamed( - container: GraphQLFieldsContainer, - field: GraphQLFieldDefinition, -): Boolean { - return getRenamedOrNull(container, field) != null -} - -context(NadelValidationContext) -fun isRenamed( - container: NadelServiceSchemaElement.FieldsContainer, - field: GraphQLFieldDefinition, -): Boolean { - return getRenamedOrNull(coords(container, field)) != null -} - -context(NadelValidationContext) -fun getRenamedOrNull( - container: GraphQLFieldsContainer, - field: GraphQLFieldDefinition, -): NadelRenamedDefinition.Field? { - val coords = container.coordinates().field(field.name) - return getRenamedOrNull(coords) -} - -context(NadelValidationContext) -fun getRenamedOrNull( - container: NadelServiceSchemaElement.FieldsContainer, - field: GraphQLFieldDefinition, -): NadelRenamedDefinition.Field? { - return getRenamedOrNull(coords(container, field)) -} - -context(NadelValidationContext) -fun getRenamedOrNull(coords: NadelSchemaMemberCoordinates.Field): NadelRenamedDefinition.Field? { - val definitions = definitions[coords] - ?: return null - - return definitions.asSequence() - .filterIsInstance() - .firstOrNull() -} - -context(NadelValidationContext) -fun getRenamedOrNull( - container: GraphQLNamedType, -): NadelRenamedDefinition.Type? { - return getRenamedOrNull(container.coordinates()) -} - -context(NadelValidationContext) -fun getRenamedOrNull(coords: NadelSchemaMemberCoordinates.Type): NadelRenamedDefinition.Type? { - val definitions = definitions[coords] - ?: return null - - return definitions.asSequence() - .filterIsInstance() - .firstOrNull() -} - -context(NadelValidationContext) -fun getUnderlyingTypeName(type: GraphQLNamedType): String { - return getRenamedOrNull(type)?.from ?: type.name -} - -context(NadelValidationContext) -fun isPartitioned( - container: GraphQLFieldsContainer, - field: GraphQLFieldDefinition, -): Boolean { - return getPartitionedOrNull(container, field) != null -} - -context(NadelValidationContext) -fun isPartitioned( - container: NadelServiceSchemaElement.FieldsContainer, - field: GraphQLFieldDefinition, -): Boolean { - return getPartitionedOrNull(coords(container, field)) != null -} - -context(NadelValidationContext) -fun getPartitionedOrNull( - container: NadelServiceSchemaElement.FieldsContainer, - field: GraphQLFieldDefinition, -): NadelPartitionDefinition? { - return getPartitionedOrNull(coords(container, field)) -} - -context(NadelValidationContext) -fun getPartitionedOrNull( - container: GraphQLFieldsContainer, - field: GraphQLFieldDefinition, -): NadelPartitionDefinition? { - val coords = container.coordinates().field(field.name) - return getPartitionedOrNull(coords) -} - -context(NadelValidationContext) -fun getPartitionedOrNull( - coords: NadelSchemaMemberCoordinates.Field, -): NadelPartitionDefinition? { - val definitions = definitions[coords] - ?: return null - - return definitions.asSequence() - .filterIsInstance() - .firstOrNull() -} - -private fun coords( - container: NadelServiceSchemaElement.FieldsContainer, - field: GraphQLFieldDefinition, -): NadelSchemaMemberCoordinates.Field { - val containerCoords = when (container) { - is NadelServiceSchemaElement.Interface -> NadelSchemaMemberCoordinates.Interface(container.overall.name) - is NadelServiceSchemaElement.Object -> NadelSchemaMemberCoordinates.Object(container.overall.name) + private val visitedTypes: MutableSet = hashSetOf() + + /** + * @return true to visit + */ + fun visitElement(schemaElement: NadelServiceSchemaElement): Boolean { + // Returns true if the element was added i.e. haven't visited before + return visitedTypes.add(schemaElement.toRef()) } - - return containerCoords.field(field.name) } diff --git a/lib/src/main/java/graphql/nadel/validation/NadelVirtualTypeValidation.kt b/lib/src/main/java/graphql/nadel/validation/NadelVirtualTypeValidation.kt index 8cc7e8a02..cb92d12e0 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelVirtualTypeValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelVirtualTypeValidation.kt @@ -3,8 +3,6 @@ package graphql.nadel.validation import graphql.nadel.Service import graphql.nadel.definition.virtualType.hasVirtualTypeDefinition import graphql.nadel.engine.util.unwrapAll -import graphql.nadel.validation.NadelTypeWrappingValidation.Rule.LHS_MUST_BE_LOOSER_OR_SAME -import graphql.nadel.validation.NadelTypeWrappingValidation.Rule.LHS_MUST_BE_STRICTER_OR_SAME import graphql.nadel.validation.hydration.NadelHydrationValidation import graphql.schema.GraphQLArgument import graphql.schema.GraphQLFieldDefinition @@ -23,9 +21,8 @@ private class NadelVirtualTypeValidationContext { class NadelVirtualTypeValidation internal constructor( private val hydrationValidation: NadelHydrationValidation, + private val assignableTypeValidation: NadelAssignableTypeValidation, ) { - private val typeWrappingValidation = NadelTypeWrappingValidation() - context(NadelValidationContext) fun validate( schemaElement: NadelServiceSchemaElement.VirtualType, @@ -43,6 +40,10 @@ class NadelVirtualTypeValidation internal constructor( return ok() } + if (instructionDefinitions.isRenamed(schemaElement)) { + return NadelVirtualTypeIllegalRenameError(schemaElement) + } + if (schemaElement.overall is GraphQLObjectType && schemaElement.underlying is GraphQLObjectType) { return validateType( service = schemaElement.service, @@ -78,7 +79,7 @@ class NadelVirtualTypeValidation internal constructor( backingType: GraphQLObjectType, ): NadelSchemaValidationResult { return virtualType.fields.map { virtualField -> - if (isRenamed(virtualType, virtualField)) { + if (instructionDefinitions.isRenamed(virtualType, virtualField)) { NadelVirtualTypeRenameFieldError( type = NadelServiceSchemaElement.VirtualType( service = service, @@ -87,7 +88,7 @@ class NadelVirtualTypeValidation internal constructor( ), virtualField = virtualField, ) - } else if (isHydrated(virtualType, virtualField)) { + } else if (instructionDefinitions.isHydrated(virtualType, virtualField)) { hydrationValidation.validate( parent = NadelServiceSchemaElement.Object( service = service, @@ -164,23 +165,20 @@ class NadelVirtualTypeValidation internal constructor( ) } - if (virtualFieldUnwrappedOutputType.name == backingFieldUnwrappedOutputType.name) { - val isTypeWrappingValid = typeWrappingValidation.isTypeWrappingValid( - lhs = virtualField.type, - rhs = backingField.type, - rule = LHS_MUST_BE_LOOSER_OR_SAME, - ) - - if (isTypeWrappingValid) { - return ok() - } - } - - return NadelVirtualTypeIncompatibleFieldOutputTypeError( - parent = parent, - virtualField = virtualField, + val isOutputTypeAssignable = isOutputTypeAssignable( backingField = backingField, + virtualField = virtualField, ) + + return if (isOutputTypeAssignable) { + ok() + } else { + NadelVirtualTypeIncompatibleFieldOutputTypeError( + parent = parent, + virtualField = virtualField, + backingField = backingField, + ) + } } /** @@ -227,25 +225,19 @@ class NadelVirtualTypeValidation internal constructor( virtualFieldArgument: GraphQLArgument, backingFieldArgument: GraphQLArgument, ): NadelSchemaValidationResult { - if (virtualFieldArgument.type.unwrapAll().name == backingFieldArgument.type.unwrapAll().name) { - val isTypeWrappingValid = typeWrappingValidation.isTypeWrappingValid( - lhs = virtualFieldArgument.type, - rhs = backingFieldArgument.type, - rule = LHS_MUST_BE_STRICTER_OR_SAME, - ) + val isInputTypeAssignable = isInputTypeAssignable(virtualFieldArgument, backingFieldArgument) - if (isTypeWrappingValid) { - return ok() - } + return if (isInputTypeAssignable) { + ok() + } else { + NadelVirtualTypeIncompatibleFieldArgumentError( + type = parent, + virtualField = virtualField, + backingField = backingField, + virtualFieldArgument = virtualFieldArgument, + backingFieldArgument = backingFieldArgument, + ) } - - return NadelVirtualTypeIncompatibleFieldArgumentError( - type = parent, - virtualField = virtualField, - backingField = backingField, - virtualFieldArgument = virtualFieldArgument, - backingFieldArgument = backingFieldArgument, - ) } /** @@ -272,4 +264,39 @@ class NadelVirtualTypeValidation internal constructor( } }.toResult() } + + context(NadelValidationContext, NadelVirtualTypeValidationContext) + private fun isInputTypeAssignable( + virtualFieldArgument: GraphQLArgument, + backingFieldArgument: GraphQLArgument, + ): Boolean { + val suppliedType = virtualFieldArgument.type + val requiredType = backingFieldArgument.type + + return assignableTypeValidation.isTypeAssignable( + suppliedType = suppliedType, + requiredType = requiredType, + // Note: we do not check for renames here, types must be used 1-1 + suppliedTypeName = suppliedType.unwrapAll().name, + requiredTypeName = requiredType.unwrapAll().name, + ) + } + + context(NadelValidationContext, NadelVirtualTypeValidationContext) + private fun isOutputTypeAssignable( + backingField: GraphQLFieldDefinition, + virtualField: GraphQLFieldDefinition, + ): Boolean { + // Note: the (supplied) value comes from the backing service, and that value needs to fit the (required) virtual field's type + val suppliedType = backingField.type + val requiredType = virtualField.type + + return assignableTypeValidation.isTypeAssignable( + suppliedType = suppliedType, + requiredType = requiredType, + // Note: we do not check for renames here, types must be used 1-1 + suppliedTypeName = suppliedType.unwrapAll().name, + requiredTypeName = requiredType.unwrapAll().name, + ) + } } diff --git a/lib/src/main/java/graphql/nadel/validation/NadelVirtualTypeValidationError.kt b/lib/src/main/java/graphql/nadel/validation/NadelVirtualTypeValidationError.kt index 658e7bc19..342b3a5bd 100644 --- a/lib/src/main/java/graphql/nadel/validation/NadelVirtualTypeValidationError.kt +++ b/lib/src/main/java/graphql/nadel/validation/NadelVirtualTypeValidationError.kt @@ -14,6 +14,15 @@ data class NadelVirtualTypeIllegalTypeError( get() = type.overall } +data class NadelVirtualTypeIllegalRenameError( + val type: NadelServiceSchemaElement.VirtualType, +) : NadelSchemaValidationError { + override val message: String = "Virtual types cannot be renamed" + + override val subject: GraphQLNamedSchemaElement + get() = type.overall +} + data class NadelVirtualTypeMissingBackingFieldError( val type: NadelServiceSchemaElement.VirtualType, val virtualField: GraphQLFieldDefinition, diff --git a/lib/src/main/java/graphql/nadel/validation/hydration/NadelHydrationArgumentTypeValidation.kt b/lib/src/main/java/graphql/nadel/validation/hydration/NadelHydrationArgumentTypeValidation.kt index c1fc4a2d5..412520945 100644 --- a/lib/src/main/java/graphql/nadel/validation/hydration/NadelHydrationArgumentTypeValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/hydration/NadelHydrationArgumentTypeValidation.kt @@ -59,9 +59,9 @@ internal sealed class NadelHydrationArgumentTypeValidationResult { ) : Error() } -internal class NadelHydrationArgumentTypeValidation { - private val typeWrappingValidation = NadelTypeWrappingValidation() - +internal class NadelHydrationArgumentTypeValidation( + private val typeWrappingValidation: NadelTypeWrappingValidation, +) { fun isAssignable( isBatchHydration: Boolean, hydrationArgumentDefinition: NadelHydrationArgumentDefinition, diff --git a/lib/src/main/java/graphql/nadel/validation/hydration/NadelHydrationArgumentValidation.kt b/lib/src/main/java/graphql/nadel/validation/hydration/NadelHydrationArgumentValidation.kt index 187fe03f3..78bdfab54 100644 --- a/lib/src/main/java/graphql/nadel/validation/hydration/NadelHydrationArgumentValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/hydration/NadelHydrationArgumentValidation.kt @@ -48,7 +48,9 @@ private data class NadelHydrationArgumentValidationContext( val isBatchHydration: Boolean, ) -internal class NadelHydrationArgumentValidation { +internal class NadelHydrationArgumentValidation( + private val hydrationArgumentTypeValidation: NadelHydrationArgumentTypeValidation, +) { context(NadelValidationContext, NadelHydrationValidationContext) fun validateArguments( isBatchHydration: Boolean, @@ -296,7 +298,7 @@ internal class NadelHydrationArgumentValidation { ): NadelSchemaValidationResult { val backingFieldArg = backingField.getArgument(hydrationArgumentDefinition.name) - NadelHydrationArgumentTypeValidation() + hydrationArgumentTypeValidation .isAssignable( isBatchHydration = isBatchHydration, hydrationArgumentDefinition = hydrationArgumentDefinition, diff --git a/lib/src/main/java/graphql/nadel/validation/hydration/NadelHydrationValidation.kt b/lib/src/main/java/graphql/nadel/validation/hydration/NadelHydrationValidation.kt index e3bc5ac1a..d015dcda8 100644 --- a/lib/src/main/java/graphql/nadel/validation/hydration/NadelHydrationValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/hydration/NadelHydrationValidation.kt @@ -46,8 +46,6 @@ import graphql.nadel.validation.NadelValidationContext import graphql.nadel.validation.NadelValidationInterimResult import graphql.nadel.validation.NadelValidationInterimResult.Error.Companion.asInterimError import graphql.nadel.validation.NadelValidationInterimResult.Success.Companion.asInterimSuccess -import graphql.nadel.validation.getHydrationDefinitions -import graphql.nadel.validation.isRenamed import graphql.nadel.validation.ok import graphql.nadel.validation.onError import graphql.nadel.validation.onErrorCast @@ -71,22 +69,23 @@ internal data class NadelHydrationValidationContext( val backingField: GraphQLFieldDefinition, ) -class NadelHydrationValidation internal constructor() { - private val argumentValidation = NadelHydrationArgumentValidation() - private val conditionValidation = NadelHydrationConditionValidation() - private val sourceFieldValidation = NadelHydrationSourceFieldValidation() - private val virtualTypeValidation = NadelHydrationVirtualTypeValidation() +class NadelHydrationValidation internal constructor( + private val argumentValidation: NadelHydrationArgumentValidation, + private val conditionValidation: NadelHydrationConditionValidation, + private val sourceFieldValidation: NadelHydrationSourceFieldValidation, + private val virtualTypeValidation: NadelHydrationVirtualTypeValidation, +) { context(NadelValidationContext) fun validate( parent: NadelServiceSchemaElement.FieldsContainer, virtualField: GraphQLFieldDefinition, ): NadelSchemaValidationResult { - if (isRenamed(parent, virtualField)) { + if (instructionDefinitions.isRenamed(parent, virtualField)) { return CannotRenameHydratedField(parent, virtualField) } - val hydrations = getHydrationDefinitions(parent, virtualField).toList() + val hydrations = instructionDefinitions.getHydrationDefinitions(parent, virtualField).toList() if (hydrations.isEmpty()) { error("Don't invoke hydration validation if there is no hydration silly") } diff --git a/lib/src/main/java/graphql/nadel/validation/hydration/NadelHydrationVirtualTypeValidation.kt b/lib/src/main/java/graphql/nadel/validation/hydration/NadelHydrationVirtualTypeValidation.kt index 0aec1490a..50b3207b0 100644 --- a/lib/src/main/java/graphql/nadel/validation/hydration/NadelHydrationVirtualTypeValidation.kt +++ b/lib/src/main/java/graphql/nadel/validation/hydration/NadelHydrationVirtualTypeValidation.kt @@ -6,7 +6,6 @@ import graphql.nadel.engine.util.unwrapAll import graphql.nadel.validation.NadelValidationContext import graphql.nadel.validation.NadelValidationInterimResult import graphql.nadel.validation.NadelValidationInterimResult.Success.Companion.asInterimSuccess -import graphql.nadel.validation.isHydrated import graphql.nadel.validation.onError import graphql.nadel.validation.onErrorCast import graphql.schema.GraphQLFieldDefinition @@ -98,7 +97,7 @@ internal class NadelHydrationVirtualTypeValidation { if (virtualObjectType is GraphQLFieldsContainer && backingObjectType is GraphQLFieldsContainer) { return virtualObjectType.fields .flatMap { virtualField -> - if (isHydrated(virtualObjectType, virtualField)) { + if (instructionDefinitions.isHydrated(virtualObjectType, virtualField)) { emptyMapping } else { val backingField = backingObjectType.getField(virtualField.name) 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 30766e07a..3e3349a44 100644 --- a/lib/src/main/java/graphql/nadel/validation/util/NadelGetReachableTypes.kt +++ b/lib/src/main/java/graphql/nadel/validation/util/NadelGetReachableTypes.kt @@ -11,7 +11,6 @@ import graphql.nadel.engine.util.getFieldAt import graphql.nadel.engine.util.makeFieldCoordinates import graphql.nadel.engine.util.unwrapAll import graphql.nadel.validation.NadelValidationContext -import graphql.nadel.validation.getHydrationDefinitions import graphql.nadel.validation.util.NadelSchemaUtil.getUnderlyingType import graphql.schema.GraphQLDirectiveContainer import graphql.schema.GraphQLFieldDefinition @@ -126,7 +125,7 @@ private class NadelReferencedTypeVisitor( } } - val hydrations = getHydrationDefinitions(parent, node) + val hydrations = instructionDefinitions.getHydrationDefinitions(parent, node) if (hydrations.any()) { visitHydratedFieldDefinition(node, hydrations) // Never continue traversing on a hydrated field, we have special handling for that in visitHydratedFieldDefinition 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 807c602fb..aa89f843e 100644 --- a/lib/src/main/java/graphql/nadel/validation/util/NadelSchemaUtil.kt +++ b/lib/src/main/java/graphql/nadel/validation/util/NadelSchemaUtil.kt @@ -3,13 +3,12 @@ package graphql.nadel.validation.util import graphql.language.OperationDefinition import graphql.nadel.Service import graphql.nadel.validation.NadelValidationContext -import graphql.nadel.validation.getUnderlyingTypeName import graphql.schema.GraphQLNamedType internal object NadelSchemaUtil { context(NadelValidationContext) fun getUnderlyingType(overallType: GraphQLNamedType, service: Service): GraphQLNamedType? { - return service.underlyingSchema.getType(getUnderlyingTypeName(overallType)) as GraphQLNamedType? + return service.underlyingSchema.getType(instructionDefinitions.getUnderlyingTypeName(overallType)) as GraphQLNamedType? } fun isOperation(type: GraphQLNamedType): Boolean { diff --git a/lib/src/test/kotlin/graphql/nadel/NadelSchemasTest.kt b/lib/src/test/kotlin/graphql/nadel/NadelSchemasTest.kt index 1af895902..2b1c0a650 100644 --- a/lib/src/test/kotlin/graphql/nadel/NadelSchemasTest.kt +++ b/lib/src/test/kotlin/graphql/nadel/NadelSchemasTest.kt @@ -13,7 +13,7 @@ import io.kotest.core.spec.style.DescribeSpec import org.junit.jupiter.api.assertThrows import kotlin.test.assertTrue -val NadelDefinitionRegistry.typeNames: Set +val NadelTypeDefinitionRegistry.typeNames: Set get() = definitions .asSequence() .filterIsInstance>() diff --git a/lib/src/test/kotlin/graphql/nadel/validation/NadelValidationDefinitionsTest.kt b/lib/src/test/kotlin/graphql/nadel/validation/NadelValidationDefinitionsTest.kt new file mode 100644 index 000000000..210565c25 --- /dev/null +++ b/lib/src/test/kotlin/graphql/nadel/validation/NadelValidationDefinitionsTest.kt @@ -0,0 +1,44 @@ +package graphql.nadel.validation + +import com.tngtech.archunit.base.DescribedPredicate +import com.tngtech.archunit.base.DescribedPredicate.not +import com.tngtech.archunit.core.domain.JavaClass +import com.tngtech.archunit.core.domain.JavaMethodCall +import com.tngtech.archunit.core.importer.ClassFileImporter +import com.tngtech.archunit.core.importer.ImportOption +import com.tngtech.archunit.lang.conditions.ArchConditions.callMethodWhere +import com.tngtech.archunit.lang.conditions.ArchConditions.not +import com.tngtech.archunit.lang.syntax.ArchRuleDefinition.classes +import kotlin.test.Test + +class NadelValidationDefinitionsTest { + @Test + fun `do not use parse definitions functions in validation`() { + val importedClasses = ClassFileImporter() + .withImportOption(ImportOption.DoNotIncludeTests()) + .importPackages("graphql.nadel.validation") + + val rule = classes() + .that( + not( + JavaClass.Predicates.belongTo( + JavaClass.Predicates.simpleNameEndingWith("DefinitionParser") + ), + ), + ) + .should( + not( + callMethodWhere( + object : DescribedPredicate("definition parse methods") { + override fun test(invocation: JavaMethodCall): Boolean { + return invocation.targetOwner.getPackage().name.startsWith("graphql.nadel.definition") + && invocation.target.name.startsWith("parse") + } + } + ), + ), + ) + + rule.check(importedClasses) + } +} diff --git a/lib/src/test/kotlin/graphql/nadel/validation/NadelVirtualTypeValidationTest.kt b/lib/src/test/kotlin/graphql/nadel/validation/NadelVirtualTypeValidationTest.kt index 1913b8bf4..8179154d5 100644 --- a/lib/src/test/kotlin/graphql/nadel/validation/NadelVirtualTypeValidationTest.kt +++ b/lib/src/test/kotlin/graphql/nadel/validation/NadelVirtualTypeValidationTest.kt @@ -376,7 +376,7 @@ class NadelVirtualTypeValidationTest { } @Test - fun `can reference `() { + fun `can use original field output type in virtual type`() { // Given val fixture = makeFixture( overallSchema = mapOf( diff --git a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/schema/CustomHydrationDirectiveTest.kt b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/schema/CustomHydrationDirectiveTest.kt index 9d9c1eaae..d4747494f 100644 --- a/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/schema/CustomHydrationDirectiveTest.kt +++ b/test/src/test/kotlin/graphql/nadel/tests/next/fixtures/schema/CustomHydrationDirectiveTest.kt @@ -3,7 +3,7 @@ package graphql.nadel.tests.next.fixtures.schema import graphql.language.Value import graphql.nadel.Nadel import graphql.nadel.NadelExecutionHints -import graphql.nadel.definition.NadelDefinition +import graphql.nadel.definition.NadelInstructionDefinition import graphql.nadel.definition.hydration.NadelBatchObjectIdentifiedByDefinition import graphql.nadel.definition.hydration.NadelHydrationArgumentDefinition import graphql.nadel.definition.hydration.NadelHydrationConditionDefinition @@ -304,7 +304,7 @@ class CustomHydrationDirectiveTest : NadelIntegrationTest( engineSchema: GraphQLSchema, parent: GraphQLFieldsContainer, field: GraphQLFieldDefinition, - ): List { + ): List { return field .appliedDirectives .mapNotNull {