From d74dc37ea452f8e1a2d63a57ce4cab52c1b7ec66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sergiy=20=F0=9F=87=BA=F0=9F=87=A6?= <818351+devsergiy@users.noreply.github.com> Date: Fri, 28 Feb 2025 17:49:43 +0200 Subject: [PATCH] fix: fix validation of variables used in nested fields of type list of an input object (#1099) fix validation if lists with the variable value nested in the input object; merge valid arguments rule with the values validation rule closes #1096 --- v2/pkg/astprinter/astprinter_test.go | 2 +- .../operation_rule_valid_arguments.go | 63 +------ v2/pkg/astvalidation/operation_rule_values.go | 87 ++++----- v2/pkg/astvalidation/operation_validation.go | 1 - .../operation_validation_test.go | 175 +++++++++++++----- .../reference/testsgo/harness_test.go | 2 +- 6 files changed, 178 insertions(+), 152 deletions(-) diff --git a/v2/pkg/astprinter/astprinter_test.go b/v2/pkg/astprinter/astprinter_test.go index d57b81ae8..fd51945a4 100644 --- a/v2/pkg/astprinter/astprinter_test.go +++ b/v2/pkg/astprinter/astprinter_test.go @@ -974,7 +974,7 @@ type __Field { deprecationReason: String } -"""ValidArguments provided to FieldSelections or Directives and the input fields of an +"""Arguments provided to FieldSelections or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value. """ diff --git a/v2/pkg/astvalidation/operation_rule_valid_arguments.go b/v2/pkg/astvalidation/operation_rule_valid_arguments.go index 0608211e0..3eb742479 100644 --- a/v2/pkg/astvalidation/operation_rule_valid_arguments.go +++ b/v2/pkg/astvalidation/operation_rule_valid_arguments.go @@ -2,70 +2,19 @@ package astvalidation import ( "bytes" - "fmt" "github.com/wundergraph/graphql-go-tools/v2/pkg/ast" - "github.com/wundergraph/graphql-go-tools/v2/pkg/astvisitor" "github.com/wundergraph/graphql-go-tools/v2/pkg/operationreport" ) -// ValidArguments validates if arguments are valid: values and variables has compatible types -// deep variables comparison is handled by Values -func ValidArguments() Rule { - return func(walker *astvisitor.Walker) { - visitor := validArgumentsVisitor{ - Walker: walker, - } - walker.RegisterEnterDocumentVisitor(&visitor) - walker.RegisterEnterArgumentVisitor(&visitor) - } -} - -type validArgumentsVisitor struct { - *astvisitor.Walker - operation, definition *ast.Document -} - -func (v *validArgumentsVisitor) EnterDocument(operation, definition *ast.Document) { - v.operation = operation - v.definition = definition -} - -func (v *validArgumentsVisitor) EnterArgument(ref int) { - definitionRef, exists := v.ArgumentInputValueDefinition(ref) - - if !exists { - return - } - - value := v.operation.ArgumentValue(ref) - v.validateIfValueSatisfiesInputFieldDefinition(value, definitionRef) -} - -func (v *validArgumentsVisitor) validateIfValueSatisfiesInputFieldDefinition(value ast.Value, inputValueDefinitionRef int) { +func (v *valuesVisitor) validateIfValueSatisfiesInputFieldDefinition(value ast.Value, inputValueDefinitionRef int) { var ( satisfied bool operationTypeRef int variableDefinitionRef int ) - switch value.Kind { - case ast.ValueKindVariable: - satisfied, operationTypeRef, variableDefinitionRef = v.variableValueSatisfiesInputValueDefinition(value.Ref, inputValueDefinitionRef) - case ast.ValueKindEnum, - ast.ValueKindNull, - ast.ValueKindBoolean, - ast.ValueKindInteger, - ast.ValueKindString, - ast.ValueKindFloat, - ast.ValueKindObject, - ast.ValueKindList: - // this types of values are covered by Values() / valuesVisitor - return - default: - v.StopWithInternalErr(fmt.Errorf("validateIfValueSatisfiesInputFieldDefinition: not implemented for value.Kind: %s", value.Kind)) - return - } + satisfied, operationTypeRef, variableDefinitionRef = v.variableValueSatisfiesInputValueDefinition(value.Ref, inputValueDefinitionRef) if satisfied { return @@ -95,7 +44,7 @@ func (v *validArgumentsVisitor) validateIfValueSatisfiesInputFieldDefinition(val v.StopWithExternalErr(operationreport.ErrVariableTypeDoesntSatisfyInputValueDefinition(printedValue, actualTypeName, expectedTypeName, value.Position, v.operation.VariableDefinitions[variableDefinitionRef].VariableValue.Position)) } -func (v *validArgumentsVisitor) variableValueSatisfiesInputValueDefinition(variableValue, inputValueDefinition int) (satisfies bool, operationTypeRef int, variableDefRef int) { +func (v *valuesVisitor) variableValueSatisfiesInputValueDefinition(variableValue, inputValueDefinition int) (satisfies bool, operationTypeRef int, variableDefRef int) { variableDefinitionRef, exists := v.variableDefinition(variableValue) if !exists { return false, ast.InvalidRef, variableDefinitionRef @@ -110,7 +59,7 @@ func (v *validArgumentsVisitor) variableValueSatisfiesInputValueDefinition(varia return v.operationTypeSatisfiesDefinitionType(operationTypeRef, definitionTypeRef, hasDefaultValue), operationTypeRef, variableDefinitionRef } -func (v *validArgumentsVisitor) variableDefinition(variableValueRef int) (ref int, exists bool) { +func (v *valuesVisitor) variableDefinition(variableValueRef int) (ref int, exists bool) { variableName := v.operation.VariableValueNameBytes(variableValueRef) if v.Ancestors[0].Kind == ast.NodeKindOperationDefinition { @@ -127,11 +76,11 @@ func (v *validArgumentsVisitor) variableDefinition(variableValueRef int) (ref in return ast.InvalidRef, false } -func (v *validArgumentsVisitor) validDefaultValue(value ast.DefaultValue) bool { +func (v *valuesVisitor) validDefaultValue(value ast.DefaultValue) bool { return value.IsDefined && value.Value.Kind != ast.ValueKindNull } -func (v *validArgumentsVisitor) operationTypeSatisfiesDefinitionType(operationTypeRef int, definitionTypeRef int, hasDefaultValue bool) bool { +func (v *valuesVisitor) operationTypeSatisfiesDefinitionType(operationTypeRef int, definitionTypeRef int, hasDefaultValue bool) bool { opKind := v.operation.Types[operationTypeRef].TypeKind defKind := v.definition.Types[definitionTypeRef].TypeKind diff --git a/v2/pkg/astvalidation/operation_rule_values.go b/v2/pkg/astvalidation/operation_rule_values.go index 3b6d7b5a3..3cf00b0e5 100644 --- a/v2/pkg/astvalidation/operation_rule_values.go +++ b/v2/pkg/astvalidation/operation_rule_values.go @@ -45,27 +45,24 @@ func (v *valuesVisitor) EnterVariableDefinition(ref int) { func (v *valuesVisitor) EnterArgument(ref int) { - definition, exists := v.ArgumentInputValueDefinition(ref) + definitionRef, exists := v.ArgumentInputValueDefinition(ref) if !exists { return } value := v.operation.ArgumentValue(ref) if value.Kind == ast.ValueKindVariable { - variableName := v.operation.VariableValueNameBytes(value.Ref) - variableDefinition, exists := v.operation.VariableDefinitionByNameAndOperation(v.Ancestors[0].Ref, variableName) + _, exists := v.variableDefinition(value.Ref) if !exists { - operationName := v.operation.OperationDefinitionNameBytes(v.Ancestors[0].Ref) - v.StopWithExternalErr(operationreport.ErrVariableNotDefinedOnOperation(variableName, operationName)) + v.StopWithExternalErr(operationreport.ErrVariableNotDefinedOnOperation(v.operation.VariableValueNameBytes(value.Ref), []byte(""))) return } - if !v.operation.VariableDefinitions[variableDefinition].DefaultValue.IsDefined { - return // variable has no default value, deep type check not required - } - value = v.operation.VariableDefinitions[variableDefinition].DefaultValue.Value + + v.validateIfValueSatisfiesInputFieldDefinition(value, definitionRef) + return } - v.valueSatisfiesInputValueDefinitionType(value, v.definition.InputValueDefinitions[definition].Type) + v.valueSatisfiesInputValueDefinitionType(value, v.definition.InputValueDefinitions[definitionRef].Type) } func (v *valuesVisitor) valueSatisfiesOperationType(value ast.Value, operationTypeRef int) bool { @@ -160,35 +157,41 @@ func (v *valuesVisitor) valueSatisfiesInputValueDefinitionType(value ast.Value, case ast.TypeKindNamed: return v.valuesSatisfiesNamedType(value, definitionTypeRef) case ast.TypeKindList: - return v.valueSatisfiesListType(value, definitionTypeRef, v.definition.Types[definitionTypeRef].OfType) + return v.valueSatisfiesListType(value, definitionTypeRef) default: v.handleTypeError(value, definitionTypeRef) return false } } +func (v *valuesVisitor) variableValueSatisfiesDefinitionType(value ast.Value, definitionTypeRef int) bool { + variableDefinitionRef, variableTypeRef, _, ok := v.operationVariableType(value.Ref) + if !ok { + v.handleTypeError(value, definitionTypeRef) + return false + } + + hasDefaultValue := v.operation.VariableDefinitionHasDefaultValue(variableDefinitionRef) + + if !v.operationTypeSatisfiesDefinitionType(variableTypeRef, definitionTypeRef, hasDefaultValue) { + v.handleVariableHasIncompatibleTypeError(value, definitionTypeRef) + return false + } + + if hasDefaultValue { + return v.valueSatisfiesInputValueDefinitionType(v.operation.VariableDefinitions[variableDefinitionRef].DefaultValue.Value, definitionTypeRef) + } + + return true +} + func (v *valuesVisitor) valuesSatisfiesNonNullType(value ast.Value, definitionTypeRef int) bool { switch value.Kind { case ast.ValueKindNull: v.handleUnexpectedNullError(value, definitionTypeRef) return false case ast.ValueKindVariable: - variableDefinitionRef, variableTypeRef, _, ok := v.operationVariableType(value.Ref) - if !ok { - v.handleTypeError(value, definitionTypeRef) - return false - } - - if v.operation.VariableDefinitionHasDefaultValue(variableDefinitionRef) { - return v.valueSatisfiesInputValueDefinitionType(v.operation.VariableDefinitions[variableDefinitionRef].DefaultValue.Value, definitionTypeRef) - } - - importedDefinitionType := v.importer.ImportType(definitionTypeRef, v.definition, v.operation) - if !v.operation.TypesAreEqualDeep(importedDefinitionType, variableTypeRef) { - v.handleVariableHasIncompatibleTypeError(value, definitionTypeRef) - return false - } - return true + return v.variableValueSatisfiesDefinitionType(value, definitionTypeRef) } return v.valueSatisfiesInputValueDefinitionType(value, v.definition.Types[definitionTypeRef].OfType) } @@ -209,27 +212,11 @@ func (v *valuesVisitor) valuesSatisfiesNamedType(value ast.Value, definitionType return v.valueSatisfiesTypeDefinitionNode(value, definitionTypeRef, node) } -func (v *valuesVisitor) valueSatisfiesListType(value ast.Value, definitionTypeRef int, listItemType int) bool { +func (v *valuesVisitor) valueSatisfiesListType(value ast.Value, definitionTypeRef int) bool { + listItemType := v.definition.Types[definitionTypeRef].OfType if value.Kind == ast.ValueKindVariable { - variableDefinitionRef, actualType, _, ok := v.operationVariableType(value.Ref) - if !ok { - v.handleTypeError(value, definitionTypeRef) - return false - } - - if v.operation.VariableDefinitionHasDefaultValue(variableDefinitionRef) { - return v.valueSatisfiesInputValueDefinitionType(v.operation.VariableDefinitions[variableDefinitionRef].DefaultValue.Value, definitionTypeRef) - } - - expectedType := v.importer.ImportType(listItemType, v.definition, v.operation) - actualType = v.operation.ResolveUnderlyingType(actualType) - - if !v.operation.TypesAreEqualDeep(expectedType, actualType) { - v.handleVariableHasIncompatibleTypeError(value, definitionTypeRef) - return false - } - return true + return v.variableValueSatisfiesDefinitionType(value, definitionTypeRef) } if value.Kind == ast.ValueKindNull { @@ -259,7 +246,6 @@ func (v *valuesVisitor) valueSatisfiesListType(value ast.Value, definitionTypeRe return valid } - func (v *valuesVisitor) valueSatisfiesTypeDefinitionNode(value ast.Value, definitionTypeRef int, node ast.Node) bool { switch node.Kind { case ast.NodeKindEnumTypeDefinition: @@ -587,11 +573,16 @@ func (v *valuesVisitor) handleVariableHasIncompatibleTypeError(value ast.Value, return } - variableDefinitionRef, _, actualTypeName, ok := v.operationVariableType(value.Ref) + variableDefinitionRef, variableTypeRef, _, ok := v.operationVariableType(value.Ref) if !ok { return } + actualTypeName, err := v.operation.PrintTypeBytes(variableTypeRef, nil) + if v.HandleInternalErr(err) { + return + } + v.Report.AddExternalError(operationreport.ErrVariableTypeDoesntSatisfyInputValueDefinition( printedValue, actualTypeName, diff --git a/v2/pkg/astvalidation/operation_validation.go b/v2/pkg/astvalidation/operation_validation.go index fa1546c73..b33c80b2e 100644 --- a/v2/pkg/astvalidation/operation_validation.go +++ b/v2/pkg/astvalidation/operation_validation.go @@ -39,7 +39,6 @@ func DefaultOperationValidator(options ...Option) *OperationValidator { validator.RegisterRule(FieldSelections(opts)) validator.RegisterRule(FieldSelectionMerging()) validator.RegisterRule(KnownArguments()) - validator.RegisterRule(ValidArguments()) validator.RegisterRule(Values()) validator.RegisterRule(ArgumentUniqueness()) validator.RegisterRule(RequiredArguments()) diff --git a/v2/pkg/astvalidation/operation_validation_test.go b/v2/pkg/astvalidation/operation_validation_test.go index 821f6a61f..13f9a030b 100644 --- a/v2/pkg/astvalidation/operation_validation_test.go +++ b/v2/pkg/astvalidation/operation_validation_test.go @@ -2118,7 +2118,7 @@ func TestExecutionValidation(t *testing.T) { doesKnowCommand(dogCommand: $catCommand) } }`, - ValidArguments(), Invalid, withValidationErrors(`Variable "$catCommand" of type "CatCommand" used in position expecting type "DogCommand!".`)) + Values(), Invalid, withValidationErrors(`Variable "$catCommand" of type "CatCommand" used in position expecting type "DogCommand!".`)) }) t.Run("117 variant", func(t *testing.T) { run(t, `query argOnRequiredArg($dogCommand: CatCommand) { @@ -2128,7 +2128,7 @@ func TestExecutionValidation(t *testing.T) { } } }`, - ValidArguments(), Invalid, withValidationErrors(`Variable "$dogCommand" of type "CatCommand" used in position expecting type "DogCommand!".`)) + Values(), Invalid, withValidationErrors(`Variable "$dogCommand" of type "CatCommand" used in position expecting type "DogCommand!".`)) }) t.Run("117 variant", func(t *testing.T) { run(t, ` query argOnRequiredArg($booleanArg: Boolean) { @@ -2139,7 +2139,7 @@ func TestExecutionValidation(t *testing.T) { fragment argOnOptional on Dog { isHousetrained(atOtherHomes: $booleanArg) @include(if: true) }`, - ValidArguments(), Valid) + Values(), Valid) }) t.Run("117 variant", func(t *testing.T) { run(t, ` @@ -2151,7 +2151,7 @@ func TestExecutionValidation(t *testing.T) { fragment argOnOptional on Dog { isHousetrained(atOtherHomes: $booleanArg) @include(if: $booleanArg) }`, - ValidArguments(), Valid) + Values(), Valid) }) t.Run("117 variant", func(t *testing.T) { run(t, ` @@ -2163,7 +2163,7 @@ func TestExecutionValidation(t *testing.T) { fragment argOnOptional on Dog { isHousetrained(atOtherHomes: $booleanArg) @include(if: $booleanArg) }`, - ValidArguments(), Invalid, withValidationErrors(`Variable "$booleanArg" of type "Boolean" used in position expecting type "Boolean!".`)) + Values(), Invalid, withValidationErrors(`Variable "$booleanArg" of type "Boolean" used in position expecting type "Boolean!".`)) }) t.Run("117 variant", func(t *testing.T) { run(t, ` query argOnRequiredArg($booleanArg: Boolean!) { @@ -2176,7 +2176,7 @@ func TestExecutionValidation(t *testing.T) { isHousetrained(atOtherHomes: $booleanArg) @include(if: $booleanArg) } }`, - ValidArguments(), Valid) + Values(), Valid) }) t.Run("117 variant", func(t *testing.T) { run(t, ` query argOnRequiredArg($intArg: Integer) { @@ -2187,7 +2187,7 @@ func TestExecutionValidation(t *testing.T) { fragment argOnOptional on Dog { isHousetrained(atOtherHomes: $intArg) @include(if: true) }`, - ValidArguments(), Invalid, withValidationErrors(`Variable "$intArg" of type "Integer" used in position expecting type "Boolean".`)) + Values(), Invalid, withValidationErrors(`Variable "$intArg" of type "Integer" used in position expecting type "Boolean".`)) }) t.Run("117 variant", func(t *testing.T) { run(t, ` query argOnRequiredArg($intArg: Integer) { @@ -2198,7 +2198,7 @@ func TestExecutionValidation(t *testing.T) { fragment argOnOptional on Dog { isHousetrained(atOtherHomes: $intArg) @include(if: true) }`, - ValidArguments(), Invalid, withValidationErrors(`Variable "$intArg" of type "Integer" used in position expecting type "Boolean".`)) + Values(), Invalid, withValidationErrors(`Variable "$intArg" of type "Integer" used in position expecting type "Boolean".`)) }) t.Run("117 variant", func(t *testing.T) { run(t, ` query argOnRequiredArg($intArg: Integer) { @@ -2211,7 +2211,7 @@ func TestExecutionValidation(t *testing.T) { fragment argOnOptional on Dog { isHousetrained(atOtherHomes: $intArg) @include(if: true) }`, - ValidArguments(), Invalid, withValidationErrors(`Variable "$intArg" of type "Integer" used in position expecting type "Boolean".`)) + Values(), Invalid, withValidationErrors(`Variable "$intArg" of type "Integer" used in position expecting type "Boolean".`)) }) t.Run("118", func(t *testing.T) { run(t, ` @@ -2251,7 +2251,7 @@ func TestExecutionValidation(t *testing.T) { fragment multipleArgsReverseOrder on ValidArguments { multipleReqs(y: 2, x: 1) }`, - ValidArguments(), Valid) + Values(), Valid) }) t.Run("undefined arg", func(t *testing.T) { run(t, ` { @@ -3729,7 +3729,7 @@ func TestExecutionValidation(t *testing.T) { booleanArgField(booleanArg: $intArg) } }`, - ValidArguments(), Invalid, withValidationErrors(`Variable "$intArg" of type "Int" used in position expecting type "Boolean".`)) + Values(), Invalid, withValidationErrors(`Variable "$intArg" of type "Int" used in position expecting type "Boolean".`)) }) t.Run("170", func(t *testing.T) { run(t, `query booleanListCannotGoIntoBoolean($booleanListArg: [Boolean]) { @@ -3737,7 +3737,7 @@ func TestExecutionValidation(t *testing.T) { booleanArgField(booleanArg: $booleanListArg) } }`, - ValidArguments(), Invalid, withValidationErrors(`Variable "$booleanListArg" of type "[Boolean]" used in position expecting type "Boolean".`)) + Values(), Invalid, withValidationErrors(`Variable "$booleanListArg" of type "[Boolean]" used in position expecting type "Boolean".`)) }) t.Run("171", func(t *testing.T) { run(t, `query booleanArgQuery($booleanArg: Boolean) { @@ -3745,7 +3745,7 @@ func TestExecutionValidation(t *testing.T) { nonNullBooleanArgField(nonNullBooleanArg: $booleanArg) } }`, - ValidArguments(), Invalid, withValidationErrors(`Variable "$booleanArg" of type "Boolean" used in position expecting type "Boolean!".`)) + Values(), Invalid, withValidationErrors(`Variable "$booleanArg" of type "Boolean" used in position expecting type "Boolean!".`)) }) // Non-null types are compatible with nullable types. t.Run("172", func(t *testing.T) { @@ -3754,7 +3754,7 @@ func TestExecutionValidation(t *testing.T) { nonNullListOfBooleanArgField(nonNullListOfBooleanArg: $nonNullListOfBoolean) } }`, - ValidArguments(), Valid) + Values(), Valid) }) t.Run("172 variant", func(t *testing.T) { run(t, `query listToList($listOfBoolean: [Boolean]) { @@ -3762,7 +3762,7 @@ func TestExecutionValidation(t *testing.T) { listOfBooleanArgField(listOfBooleanArg: $listOfBoolean) } }`, - ValidArguments(), Valid) + Values(), Valid) }) t.Run("172 variant", func(t *testing.T) { run(t, `query listOfNonNullToList($listOfNonNullBoolean: [Boolean!]) { @@ -3770,7 +3770,7 @@ func TestExecutionValidation(t *testing.T) { listOfBooleanArgField(listOfBooleanArg: $listOfNonNullBoolean) } }`, - ValidArguments(), Valid) + Values(), Valid) }) t.Run("172 variant", func(t *testing.T) { run(t, `query nonNullListOfNonNullToList($nonNullListOfNonNullBoolean: [Boolean!]!) { @@ -3778,7 +3778,7 @@ func TestExecutionValidation(t *testing.T) { listOfBooleanArgField(listOfBooleanArg: $nonNullListOfNonNullBoolean) } }`, - ValidArguments(), Valid) + Values(), Valid) }) t.Run("172 variant", func(t *testing.T) { run(t, `query nonNullListToListLiteral { @@ -3812,7 +3812,7 @@ func TestExecutionValidation(t *testing.T) { listOfNonNullBooleanArgField(listOfNonNullBooleanArg: $listOfBoolean) } }`, - ValidArguments(), Invalid, withValidationErrors(`Variable "$listOfBoolean" of type "[Boolean]" used in position expecting type "[Boolean!]"`)) + Values(), Invalid, withValidationErrors(`Variable "$listOfBoolean" of type "[Boolean]" used in position expecting type "[Boolean!]"`)) }) t.Run("172 variant", func(t *testing.T) { run(t, `query nonNullListToListOfNonNull($nonNullListOfBoolean: [Boolean]!) { @@ -3820,7 +3820,7 @@ func TestExecutionValidation(t *testing.T) { listOfNonNullBooleanArgField(listOfNonNullBooleanArg: $nonNullListOfBoolean) } }`, - ValidArguments(), Invalid, withValidationErrors(`Variable "$nonNullListOfBoolean" of type "[Boolean]!" used in position expecting type "[Boolean!]"`)) + Values(), Invalid, withValidationErrors(`Variable "$nonNullListOfBoolean" of type "[Boolean]!" used in position expecting type "[Boolean!]"`)) }) t.Run("172 variant", func(t *testing.T) { run(t, `query listOfNonNullToNonNullList($listOfNonNullBoolean: [Boolean!]) { @@ -3828,7 +3828,7 @@ func TestExecutionValidation(t *testing.T) { nonNullListOfBooleanArgField(nonNullListOfBooleanArg: $listOfNonNullBoolean) } }`, - ValidArguments(), Invalid, withValidationErrors(`Variable "$listOfNonNullBoolean" of type "[Boolean!]" used in position expecting type "[Boolean]!"`)) + Values(), Invalid, withValidationErrors(`Variable "$listOfNonNullBoolean" of type "[Boolean!]" used in position expecting type "[Boolean]!"`)) }) t.Run("173", func(t *testing.T) { run(t, `query listToNonNullList($listOfBoolean: [Boolean]) { @@ -3836,7 +3836,7 @@ func TestExecutionValidation(t *testing.T) { nonNullListOfBooleanArgField(nonNullListOfBooleanArg: $listOfBoolean) } }`, - ValidArguments(), Invalid, withValidationErrors(`Variable "$listOfBoolean" of type "[Boolean]" used in position expecting type "[Boolean]!"`)) + Values(), Invalid, withValidationErrors(`Variable "$listOfBoolean" of type "[Boolean]" used in position expecting type "[Boolean]!"`)) }) t.Run("174", func(t *testing.T) { run(t, `query booleanArgQueryWithDefault($booleanArg: Boolean) { @@ -3844,7 +3844,7 @@ func TestExecutionValidation(t *testing.T) { nonNullBooleanWithDefaultArgField(nonNullBooleanWithDefaultArg: $booleanArg) } }`, - ValidArguments(), Valid) + Values(), Valid) }) t.Run("175", func(t *testing.T) { run(t, `query booleanArgQueryWithDefault($booleanArg: Boolean = true) { @@ -3852,7 +3852,7 @@ func TestExecutionValidation(t *testing.T) { nonNullBooleanArgField(nonNullBooleanArg: $booleanArg) } }`, - ValidArguments(), Valid, withDisableNormalization()) + Values(), Valid, withDisableNormalization()) }) t.Run("complex values", func(t *testing.T) { runWithDefinition(t, wundergraphSchema, ` @@ -3867,7 +3867,7 @@ func TestExecutionValidation(t *testing.T) { } } } - `, ValidArguments(), Valid) + `, Values(), Valid) }) t.Run("complex values", func(t *testing.T) { runWithDefinition(t, wundergraphSchema, ` @@ -3949,28 +3949,115 @@ func TestExecutionValidation(t *testing.T) { )) }) - t.Run("complex nested optionalListOfOptionalStrings of type [String] should accept more restrictive type [String!]!", func(t *testing.T) { - runWithDefinition(t, ` - scalar String + t.Run("nested variable with a list type validation", func(t *testing.T) { + definition := ` + scalar String - schema { - query: Query - } + schema { + query: Query + } - type Query { - nested(input: NestedInput): String - } + type Query { + nested(input: Input): String + nested2(input: Input2): String + nested3(input: Input3): String + nested4(input: Input4): String + } - input NestedInput { - optionalListOfOptionalStrings: [String] - } - `, ` - query Q($a: [String!]!) { - nested(input: { - optionalListOfOptionalStrings: $a - }) - } - `, Values(), Valid) + input Input { + list: [String] + } + + input Input2 { + list: [String!] + } + + input Input3 { + list: [String]! + } + + input Input4 { + list: [String!]! + }` + + t.Run("[String]", func(t *testing.T) { + t.Run("[String] -> [String]", func(t *testing.T) { + runWithDefinition(t, definition, `query Q($a: [String]) { nested(input: {list: $a})}`, Values(), Valid) + }) + + t.Run("[String]! -> [String]", func(t *testing.T) { + runWithDefinition(t, definition, `query Q($a: [String]!) { nested(input: {list: $a})}`, Values(), Valid) + }) + + t.Run("[String!] -> [String]", func(t *testing.T) { + runWithDefinition(t, definition, `query Q($a: [String!]) { nested(input: {list: $a})}`, Values(), Valid) + }) + + t.Run("[String!]! -> [String]", func(t *testing.T) { + runWithDefinition(t, definition, `query Q($a: [String!]!) { nested(input: {list: $a})}`, Values(), Valid) + }) + }) + + t.Run("[String!]", func(t *testing.T) { + t.Run("[String] -> [String!]", func(t *testing.T) { + runWithDefinition(t, definition, `query Q($a: [String]) { nested2(input: {list: $a})}`, Values(), Invalid, + withValidationErrors(`Variable "$a" of type "[String]" used in position expecting type "[String!]"`)) + }) + + t.Run("[String]! -> [String!]", func(t *testing.T) { + runWithDefinition(t, definition, `query Q($a: [String]!) { nested2(input: {list: $a})}`, Values(), Invalid, + withValidationErrors(`Variable "$a" of type "[String]!" used in position expecting type "[String!]"`)) + }) + + t.Run("[String!] -> [String!]", func(t *testing.T) { + runWithDefinition(t, definition, `query Q($a: [String!]) { nested2(input: {list: $a})}`, Values(), Valid) + }) + + t.Run("[String!]! -> [String!]", func(t *testing.T) { + runWithDefinition(t, definition, `query Q($a: [String!]!) { nested2(input: {list: $a})}`, Values(), Valid) + }) + }) + + t.Run("[String]!", func(t *testing.T) { + t.Run("[String] -> [String]!", func(t *testing.T) { + runWithDefinition(t, definition, `query Q($a: [String]) { nested3(input: {list: $a})}`, Values(), Invalid, + withValidationErrors(`Variable "$a" of type "[String]" used in position expecting type "[String]!"`)) + }) + + t.Run("[String]! -> [String]!", func(t *testing.T) { + runWithDefinition(t, definition, `query Q($a: [String]!) { nested3(input: {list: $a})}`, Values(), Valid) + }) + + t.Run("[String!] -> [String]!", func(t *testing.T) { + runWithDefinition(t, definition, `query Q($a: [String!]) { nested3(input: {list: $a})}`, Values(), Invalid, + withValidationErrors(`Variable "$a" of type "[String!]" used in position expecting type "[String]!"`)) + }) + + t.Run("[String!]! -> [String]!", func(t *testing.T) { + runWithDefinition(t, definition, `query Q($a: [String!]!) { nested3(input: {list: $a})}`, Values(), Valid) + }) + }) + + t.Run("[String!]!", func(t *testing.T) { + t.Run("[String] -> [String!]!", func(t *testing.T) { + runWithDefinition(t, definition, `query Q($a: [String]) { nested4(input: {list: $a})}`, Values(), Invalid, + withValidationErrors(`Variable "$a" of type "[String]" used in position expecting type "[String!]!"`)) + }) + + t.Run("[String]! -> [String!]!", func(t *testing.T) { + runWithDefinition(t, definition, `query Q($a: [String]!) { nested4(input: {list: $a})}`, Values(), Invalid, + withValidationErrors(`Variable "$a" of type "[String]!" used in position expecting type "[String!]!"`)) + }) + + t.Run("[String!] -> [String!]!", func(t *testing.T) { + runWithDefinition(t, definition, `query Q($a: [String!]) { nested4(input: {list: $a})}`, Values(), Invalid, + withValidationErrors(`Variable "$a" of type "[String!]" used in position expecting type "[String!]!"`)) + }) + + t.Run("[String!]! -> [String!]!", func(t *testing.T) { + runWithDefinition(t, definition, `query Q($a: [String!]!) { nested4(input: {list: $a})}`, Values(), Valid) + }) + }) }) }) }) @@ -4870,7 +4957,7 @@ type __Field { deprecationReason: String } -"""ValidArguments provided to FieldSelections or Directives and the input fields of an +"""Arguments provided to FieldSelections or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value. """ diff --git a/v2/pkg/astvalidation/reference/testsgo/harness_test.go b/v2/pkg/astvalidation/reference/testsgo/harness_test.go index 00566f097..6f18dbf01 100644 --- a/v2/pkg/astvalidation/reference/testsgo/harness_test.go +++ b/v2/pkg/astvalidation/reference/testsgo/harness_test.go @@ -86,7 +86,7 @@ var rulesMap = map[string][]astvalidation.Rule{ ValuesOfCorrectTypeRule: {astvalidation.Values()}, VariablesAreInputTypesRule: {astvalidation.VariablesAreInputTypes()}, KnownTypeNamesOperationRule: {astvalidation.VariablesAreInputTypes(), astvalidation.Fragments()}, - VariablesInAllowedPositionRule: {astvalidation.ValidArguments(), astvalidation.Values()}, + VariablesInAllowedPositionRule: {astvalidation.Values()}, // fragments rules FragmentsOnCompositeTypesRule: {astvalidation.Fragments()},