From 369e031037f9c09c66b98285686c2ecb7362da95 Mon Sep 17 00:00:00 2001 From: Sam Christen Oliphant <127764018+Sam-tesouro@users.noreply.github.com> Date: Tue, 31 Dec 2024 14:56:19 -0500 Subject: [PATCH] feat: add an option to omit variables content in the variables validator error messages (#934) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit per discussion at https://github.com/wundergraph/cosmo/issues/1182 --------- Co-authored-by: Sergiy πŸ‡ΊπŸ‡¦ <818351+devsergiy@users.noreply.github.com> Co-authored-by: spetrunin --- .../variablesvalidation.go | 115 ++--- .../variablesvalidation_test.go | 481 ++++++++++++++++-- 2 files changed, 491 insertions(+), 105 deletions(-) diff --git a/v2/pkg/variablesvalidation/variablesvalidation.go b/v2/pkg/variablesvalidation/variablesvalidation.go index b054f6f62..5115f359b 100644 --- a/v2/pkg/variablesvalidation/variablesvalidation.go +++ b/v2/pkg/variablesvalidation/variablesvalidation.go @@ -3,6 +3,7 @@ package variablesvalidation import ( "bytes" "fmt" + "github.com/wundergraph/graphql-go-tools/v2/pkg/apollocompatibility" "github.com/wundergraph/graphql-go-tools/v2/pkg/errorcodes" "github.com/wundergraph/graphql-go-tools/v2/pkg/federation" @@ -23,11 +24,35 @@ func (e *InvalidVariableError) Error() string { return e.Message } -func newInvalidVariableError(message string, isApolloCompatibilityMode bool) *InvalidVariableError { +func (v *variablesVisitor) invalidValueIfAllowed(variableContent string) string { + if v.opts.DisableExposingVariablesContent { + return "" + } + + return fmt.Sprintf(": %s", variableContent) +} + +func (v *variablesVisitor) invalidEnumValueIfAllowed(variableContent string) string { + if v.opts.DisableExposingVariablesContent { + return "" + } + + return fmt.Sprintf("\"%s\" ", variableContent) +} + +func (v *variablesVisitor) invalidValueMessage(variableName, variableContent string) string { + if v.opts.DisableExposingVariablesContent { + return fmt.Sprintf(`Variable "$%s" got invalid value`, variableName) + } + + return fmt.Sprintf(`Variable "$%s" got invalid value %s`, variableName, variableContent) +} + +func (v *variablesVisitor) newInvalidVariableError(message string) *InvalidVariableError { err := &InvalidVariableError{ Message: message, } - if isApolloCompatibilityMode { + if v.opts.ApolloCompatibilityFlags.ReplaceInvalidVarError { err.ExtensionCode = errorcodes.BadUserInput } return err @@ -39,15 +64,16 @@ type VariablesValidator struct { } type VariablesValidatorOptions struct { - ApolloCompatibilityFlags apollocompatibility.Flags + ApolloCompatibilityFlags apollocompatibility.Flags + DisableExposingVariablesContent bool } func NewVariablesValidator(options VariablesValidatorOptions) *VariablesValidator { walker := astvisitor.NewWalker(8) visitor := &variablesVisitor{ - variables: &astjson.JSON{}, - walker: &walker, - apolloCompatibilityFlags: options.ApolloCompatibilityFlags, + variables: &astjson.JSON{}, + walker: &walker, + opts: options, } walker.RegisterEnterVariableDefinitionVisitor(visitor) return &VariablesValidator{ @@ -81,7 +107,7 @@ type variablesVisitor struct { currentVariableName []byte currentVariableJsonNodeRef int path []pathItem - apolloCompatibilityFlags apollocompatibility.Flags + opts VariablesValidatorOptions } func (v *variablesVisitor) renderPath() string { @@ -189,10 +215,7 @@ func (v *variablesVisitor) renderVariableRequiredError(variableName []byte, type v.err = err return } - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" of required type "%s" was not provided.`, string(variableName), out.String()), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + v.err = v.newInvalidVariableError(fmt.Sprintf(`Variable "$%s" of required type "%s" was not provided.`, string(variableName), out.String())) } func (v *variablesVisitor) renderVariableInvalidObjectTypeError(typeName []byte, variablesNode astjson.Node) { @@ -203,10 +226,7 @@ func (v *variablesVisitor) renderVariableInvalidObjectTypeError(typeName []byte, return } variableContent := out.String() - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s; Expected type "%s" to be an object.`, string(v.currentVariableName), variableContent, string(typeName)), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + v.err = v.newInvalidVariableError(fmt.Sprintf(`%s; Expected type "%s" to be an object.`, v.invalidValueMessage(string(v.currentVariableName), variableContent), string(typeName))) } func (v *variablesVisitor) renderVariableRequiredNotProvidedError(fieldName []byte, typeRef int) { @@ -223,10 +243,7 @@ func (v *variablesVisitor) renderVariableRequiredNotProvidedError(fieldName []by v.err = err return } - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s; Field "%s" of required type "%s" was not provided.`, string(v.currentVariableName), variableContent, string(fieldName), out.String()), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + v.err = v.newInvalidVariableError(fmt.Sprintf(`%s; Field "%s" of required type "%s" was not provided.`, v.invalidValueMessage(string(v.currentVariableName), variableContent), string(fieldName), out.String())) } func (v *variablesVisitor) renderVariableInvalidNestedTypeError(actualJsonNodeRef int, expectedType ast.NodeKind, expectedTypeName []byte, expectedList bool) { @@ -247,53 +264,26 @@ func (v *variablesVisitor) renderVariableInvalidNestedTypeError(actualJsonNodeRe case ast.NodeKindScalarTypeDefinition: switch typeName { case "String": - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s%s; String cannot represent a non string value: %s`, variableName, invalidValue, path, invalidValue), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + v.err = v.newInvalidVariableError(fmt.Sprintf(`%s%s; String cannot represent a non string value%s`, v.invalidValueMessage(variableName, invalidValue), path, v.invalidValueIfAllowed(invalidValue))) case "Int": - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Int cannot represent non-integer value: %s`, variableName, invalidValue, path, invalidValue), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + v.err = v.newInvalidVariableError(fmt.Sprintf(`%s%s; Int cannot represent non-integer value%s`, v.invalidValueMessage(variableName, invalidValue), path, v.invalidValueIfAllowed(invalidValue))) case "Float": - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Float cannot represent non numeric value: %s`, variableName, invalidValue, path, invalidValue), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + v.err = v.newInvalidVariableError(fmt.Sprintf(`%s%s; Float cannot represent non numeric value%s`, v.invalidValueMessage(variableName, invalidValue), path, v.invalidValueIfAllowed(invalidValue))) case "Boolean": - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Boolean cannot represent a non boolean value: %s`, variableName, invalidValue, path, invalidValue), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + v.err = v.newInvalidVariableError(fmt.Sprintf(`%s%s; Boolean cannot represent a non boolean value%s`, v.invalidValueMessage(variableName, invalidValue), path, v.invalidValueIfAllowed(invalidValue))) case "ID": - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s%s; ID cannot represent value: %s`, variableName, invalidValue, path, invalidValue), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + v.err = v.newInvalidVariableError(fmt.Sprintf(`%s%s; ID cannot represent a non-string and non-integer value%s`, v.invalidValueMessage(variableName, invalidValue), path, v.invalidValueIfAllowed(invalidValue))) default: - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Expected type "%s" to be a scalar.`, variableName, invalidValue, path, typeName), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + v.err = v.newInvalidVariableError(fmt.Sprintf(`%s%s; Expected type "%s" to be a scalar.`, v.invalidValueMessage(variableName, invalidValue), path, typeName)) } case ast.NodeKindInputObjectTypeDefinition: if expectedList { - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Got input type "%s", want: "[%s]"`, variableName, invalidValue, path, typeName, typeName), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + v.err = v.newInvalidVariableError(fmt.Sprintf(`%s%s; Got input type "%s", want: "[%s]"`, v.invalidValueMessage(variableName, invalidValue), path, typeName, typeName)) } else { - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Expected type "%s" to be an input object.`, variableName, invalidValue, path, typeName), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + v.err = v.newInvalidVariableError(fmt.Sprintf(`%s%s; Expected type "%s" to be an input object.`, v.invalidValueMessage(variableName, invalidValue), path, typeName)) } case ast.NodeKindEnumTypeDefinition: - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Enum "%s" cannot represent non-string value: %s.`, variableName, invalidValue, path, typeName, invalidValue), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + v.err = v.newInvalidVariableError(fmt.Sprintf(`%s%s; Enum "%s" cannot represent non-string value%s.`, v.invalidValueMessage(variableName, invalidValue), path, typeName, v.invalidValueIfAllowed(invalidValue))) } } @@ -307,10 +297,7 @@ func (v *variablesVisitor) renderVariableFieldNotDefinedError(fieldName []byte, } invalidValue := buf.String() path := v.renderPath() - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s at "%s"; Field "%s" is not defined by type "%s".`, variableName, invalidValue, path, string(fieldName), string(typeName)), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + v.err = v.newInvalidVariableError(fmt.Sprintf(`%s at "%s"; Field "%s" is not defined by type "%s".`, v.invalidValueMessage(variableName, invalidValue), path, string(fieldName), string(typeName))) } func (v *variablesVisitor) renderVariableEnumValueDoesNotExistError(typeName []byte, enumValue []byte) { @@ -326,10 +313,7 @@ func (v *variablesVisitor) renderVariableEnumValueDoesNotExistError(typeName []b if len(v.path) > 1 { path = fmt.Sprintf(` at "%s"`, v.renderPath()) } - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value %s%s; Value "%s" does not exist in "%s" enum.`, variableName, invalidValue, path, string(enumValue), string(typeName)), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + v.err = v.newInvalidVariableError(fmt.Sprintf(`%s%s; Value %sdoes not exist in "%s" enum.`, v.invalidValueMessage(variableName, invalidValue), path, v.invalidEnumValueIfAllowed(string(enumValue)), string(typeName))) } func (v *variablesVisitor) renderVariableInvalidNullError(variableName []byte, typeRef int) { @@ -340,10 +324,7 @@ func (v *variablesVisitor) renderVariableInvalidNullError(variableName []byte, t return } typeName := buf.String() - v.err = newInvalidVariableError( - fmt.Sprintf(`Variable "$%s" got invalid value null; Expected non-nullable type "%s" not to be null.`, string(variableName), typeName), - v.apolloCompatibilityFlags.ReplaceInvalidVarError, - ) + v.err = v.newInvalidVariableError(fmt.Sprintf(`Variable "$%s" got invalid value null; Expected non-nullable type "%s" not to be null.`, string(variableName), typeName)) } func (v *variablesVisitor) traverseFieldDefinitionType(fieldTypeDefinitionNodeKind ast.NodeKind, fieldName ast.ByteSlice, typeRef int, fieldVariablesJsonNodeRef int, inputFieldRef int) { diff --git a/v2/pkg/variablesvalidation/variablesvalidation_test.go b/v2/pkg/variablesvalidation/variablesvalidation_test.go index de562c05e..39a710ed9 100644 --- a/v2/pkg/variablesvalidation/variablesvalidation_test.go +++ b/v2/pkg/variablesvalidation/variablesvalidation_test.go @@ -1,12 +1,14 @@ package variablesvalidation import ( + "testing" + "github.com/wundergraph/graphql-go-tools/v2/pkg/apollocompatibility" "github.com/wundergraph/graphql-go-tools/v2/pkg/errorcodes" - "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/wundergraph/graphql-go-tools/v2/pkg/astnormalization" "github.com/wundergraph/graphql-go-tools/v2/pkg/operationreport" @@ -48,6 +50,17 @@ func TestVariablesValidation(t *testing.T) { require.NoError(t, err) }) + t.Run("unprovided required input fields without default values produce validation errors #1 - with variable content", func(t *testing.T) { + tc := testCase{ + schema: inputSchema, + operation: `query Foo($input: SelfUnsatisfiedInput!) { unsatisfied(input: $input) }`, + variables: `{ "input": { } }`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value {}; Field "nested" of required type "NestedSelfSatisfiedInput!" was not provided.`, err.Error()) + }) + t.Run("unprovided required input fields without default values produce validation errors #1", func(t *testing.T) { tc := testCase{ schema: inputSchema, @@ -56,7 +69,18 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$input" got invalid value {}; Field "nested" of required type "NestedSelfSatisfiedInput!" was not provided.`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value; Field "nested" of required type "NestedSelfSatisfiedInput!" was not provided.`, err.Error()) + }) + + t.Run("unprovided required input fields without default values produce validation errors #2 - with variable content", func(t *testing.T) { + tc := testCase{ + schema: inputSchema, + operation: `query Foo($input: SelfUnsatisfiedInput!) { unsatisfied(input: $input) }`, + variables: `{ "input": { "nested": { }, "value": "string" } }`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value {"nested":{},"value":"string"}; Field "secondNested" of required type "NestedSelfSatisfiedInput!" was not provided.`, err.Error()) }) t.Run("unprovided required input fields without default values produce validation errors #2", func(t *testing.T) { @@ -67,7 +91,7 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$input" got invalid value {"nested":{},"value":"string"}; Field "secondNested" of required type "NestedSelfSatisfiedInput!" was not provided.`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value; Field "secondNested" of required type "NestedSelfSatisfiedInput!" was not provided.`, err.Error()) }) t.Run("provided but empty nested required inputs with default values do not produce validation errors", func(t *testing.T) { @@ -100,6 +124,17 @@ func TestVariablesValidation(t *testing.T) { require.NoError(t, err) }) + t.Run("nested argument is value instead of list - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `type Query { hello(input: Input): String } input Input { bar: [String]! }`, + operation: `query Foo($input: Input) { hello(input: $input) }`, + variables: `{"input":{"bar":"world"}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.NotNil(t, err) + assert.Equal(t, `Variable "$input" got invalid value "world" at "input.bar"; Got input type "String", want: "[String]"`, err.Error()) + }) + t.Run("nested argument is value instead of list", func(t *testing.T) { tc := testCase{ schema: `type Query { hello(input: Input): String } input Input { bar: [String]! }`, @@ -108,7 +143,18 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.NotNil(t, err) - assert.Equal(t, `Variable "$input" got invalid value "world" at "input.bar"; Got input type "String", want: "[String]"`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value at "input.bar"; Got input type "String", want: "[String]"`, err.Error()) + }) + + t.Run("nested enum argument is value instead of list - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `type Query { hello(input: Input): String } input Input { bar: [MyNum]! } enum MyNum { ONE TWO }`, + operation: `query Foo($input: Input) { hello(input: $input) }`, + variables: `{"input":{"bar":"ONE"}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.NotNil(t, err) + assert.Equal(t, `Variable "$input" got invalid value "ONE" at "input.bar"; Got input type "MyNum", want: "[MyNum]"`, err.Error()) }) t.Run("nested enum argument is value instead of list", func(t *testing.T) { @@ -119,7 +165,7 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.NotNil(t, err) - assert.Equal(t, `Variable "$input" got invalid value "ONE" at "input.bar"; Got input type "MyNum", want: "[MyNum]"`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value at "input.bar"; Got input type "MyNum", want: "[MyNum]"`, err.Error()) }) t.Run("required field argument of custom scalar type not provided", func(t *testing.T) { @@ -144,6 +190,17 @@ func TestVariablesValidation(t *testing.T) { assert.Equal(t, `Variable "$bar" got invalid value null; Expected non-nullable type "Baz!" not to be null.`, err.Error()) }) + t.Run("required nested field field argument of custom scalar not provided - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `type Query { hello(arg: Foo!): String } input Foo { bar: Baz! } scalar Baz`, + operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, + variables: `{"bar":{}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + assert.NotNil(t, err) + assert.Equal(t, `Variable "$bar" got invalid value {}; Field "bar" of required type "Baz!" was not provided.`, err.Error()) + }) + t.Run("required nested field field argument of custom scalar not provided", func(t *testing.T) { tc := testCase{ schema: `type Query { hello(arg: Foo!): String } input Foo { bar: Baz! } scalar Baz`, @@ -152,7 +209,18 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) assert.NotNil(t, err) - assert.Equal(t, `Variable "$bar" got invalid value {}; Field "bar" of required type "Baz!" was not provided.`, err.Error()) + assert.Equal(t, `Variable "$bar" got invalid value; Field "bar" of required type "Baz!" was not provided.`, err.Error()) + }) + + t.Run("required nested field field argument of custom scalar was null - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `type Query { hello(arg: Foo!): String } input Foo { bar: Baz! } scalar Baz`, + operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, + variables: `{"bar":{"bar":null}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + assert.NotNil(t, err) + assert.Equal(t, `Variable "$bar" got invalid value {"bar":null}; Field "bar" of required type "Baz!" was not provided.`, err.Error()) }) t.Run("required nested field field argument of custom scalar was null", func(t *testing.T) { @@ -163,7 +231,7 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) assert.NotNil(t, err) - assert.Equal(t, `Variable "$bar" got invalid value {"bar":null}; Field "bar" of required type "Baz!" was not provided.`, err.Error()) + assert.Equal(t, `Variable "$bar" got invalid value; Field "bar" of required type "Baz!" was not provided.`, err.Error()) }) t.Run("required field argument provided with default value", func(t *testing.T) { @@ -274,6 +342,17 @@ func TestVariablesValidation(t *testing.T) { assert.Equal(t, `Variable "$bar" of required type "[String]!" was not provided.`, err.Error()) }) + t.Run("wrong Boolean value for input object field - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, + variables: `{"bar":true}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value true; Expected type "Foo" to be an object.`, err.Error()) + }) + t.Run("wrong Boolean value for input object field", func(t *testing.T) { tc := testCase{ schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, @@ -282,7 +361,18 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$bar" got invalid value true; Expected type "Foo" to be an object.`, err.Error()) + assert.Equal(t, `Variable "$bar" got invalid value; Expected type "Foo" to be an object.`, err.Error()) + }) + + t.Run("wrong Integer value for input object field - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, + variables: `{"bar":123}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value 123; Expected type "Foo" to be an object.`, err.Error()) }) t.Run("wrong Integer value for input object field", func(t *testing.T) { @@ -293,7 +383,18 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$bar" got invalid value 123; Expected type "Foo" to be an object.`, err.Error()) + assert.Equal(t, `Variable "$bar" got invalid value; Expected type "Foo" to be an object.`, err.Error()) + }) + + t.Run("required field on present input object not provided - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, + variables: `{"bar":{}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value {}; Field "bar" of required type "String!" was not provided.`, err.Error()) }) t.Run("required field on present input object not provided", func(t *testing.T) { @@ -304,7 +405,7 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$bar" got invalid value {}; Field "bar" of required type "String!" was not provided.`, err.Error()) + assert.Equal(t, `Variable "$bar" got invalid value; Field "bar" of required type "String!" was not provided.`, err.Error()) }) t.Run("required field on present input object provided with correct type", func(t *testing.T) { @@ -317,6 +418,17 @@ func TestVariablesValidation(t *testing.T) { require.NoError(t, err) }) + t.Run("required field on present input object provided with wrong type - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($input: Foo!) { hello(arg: $input) }`, + variables: `{"input":{"bar":123}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value 123 at "input.bar"; String cannot represent a non string value: 123`, err.Error()) + }) + t.Run("required field on present input object provided with wrong type", func(t *testing.T) { tc := testCase{ schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, @@ -325,7 +437,18 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$input" got invalid value 123 at "input.bar"; String cannot represent a non string value: 123`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value at "input.bar"; String cannot represent a non string value`, err.Error()) + }) + + t.Run("required field on present input object not provided - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($input: Foo!) { hello(arg: $input) }`, + variables: `{"input":{}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value {}; Field "bar" of required type "String!" was not provided.`, err.Error()) }) t.Run("required field on present input object not provided", func(t *testing.T) { @@ -336,7 +459,18 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$input" got invalid value {}; Field "bar" of required type "String!" was not provided.`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value; Field "bar" of required type "String!" was not provided.`, err.Error()) + }) + + t.Run("required string field on input object provided with null - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($input: Foo!) { hello(arg: $input) }`, + variables: `{"input":{"bar":null}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value {"bar":null}; Field "bar" of required type "String!" was not provided.`, err.Error()) }) t.Run("required string field on input object provided with null", func(t *testing.T) { @@ -347,7 +481,18 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$input" got invalid value {"bar":null}; Field "bar" of required type "String!" was not provided.`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value; Field "bar" of required type "String!" was not provided.`, err.Error()) + }) + + t.Run("required string field on input object provided with Int - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($input: Foo!) { hello(arg: $input) }`, + variables: `{"input":{"bar":123}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value 123 at "input.bar"; String cannot represent a non string value: 123`, err.Error()) }) t.Run("required string field on input object provided with Int", func(t *testing.T) { @@ -358,7 +503,18 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$input" got invalid value 123 at "input.bar"; String cannot represent a non string value: 123`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value at "input.bar"; String cannot represent a non string value`, err.Error()) + }) + + t.Run("required string field on input object provided with Float - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($input: Foo!) { hello(arg: $input) }`, + variables: `{"input":{"bar":123.456}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value 123.456 at "input.bar"; String cannot represent a non string value: 123.456`, err.Error()) }) t.Run("required string field on input object provided with Float", func(t *testing.T) { @@ -369,7 +525,18 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$input" got invalid value 123.456 at "input.bar"; String cannot represent a non string value: 123.456`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value at "input.bar"; String cannot represent a non string value`, err.Error()) + }) + + t.Run("required string field on input object provided with Boolean - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($input: Foo!) { hello(arg: $input) }`, + variables: `{"input":{"bar":true}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value true at "input.bar"; String cannot represent a non string value: true`, err.Error()) }) t.Run("required string field on input object provided with Boolean", func(t *testing.T) { @@ -380,7 +547,18 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$input" got invalid value true at "input.bar"; String cannot represent a non string value: true`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value at "input.bar"; String cannot represent a non string value`, err.Error()) + }) + + t.Run("required string field on nested input object not provided - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `input Foo { bar: String! } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, + operation: `query Foo($input: Bar!) { hello(arg: $input) }`, + variables: `{"input":{"foo":{}}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value {"foo":{}}; Field "bar" of required type "String!" was not provided.`, err.Error()) }) t.Run("required string field on nested input object not provided", func(t *testing.T) { @@ -391,7 +569,18 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$input" got invalid value {"foo":{}}; Field "bar" of required type "String!" was not provided.`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value; Field "bar" of required type "String!" was not provided.`, err.Error()) + }) + + t.Run("required string field on nested input object provided with null - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `input Foo { bar: String! } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, + operation: `query Foo($input: Bar!) { hello(arg: $input) }`, + variables: `{"input":{"foo":{"bar":null}}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value {"foo":{"bar":null}}; Field "bar" of required type "String!" was not provided.`, err.Error()) }) t.Run("required string field on nested input object provided with null", func(t *testing.T) { @@ -402,7 +591,18 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$input" got invalid value {"foo":{"bar":null}}; Field "bar" of required type "String!" was not provided.`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value; Field "bar" of required type "String!" was not provided.`, err.Error()) + }) + + t.Run("required string field on nested input object provided with Int - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `input Foo { bar: String! } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, + operation: `query Foo($input: Bar!) { hello(arg: $input) }`, + variables: `{"input":{"foo":{"bar":123}}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value 123 at "input.foo.bar"; String cannot represent a non string value: 123`, err.Error()) }) t.Run("required string field on nested input object provided with Int", func(t *testing.T) { @@ -413,7 +613,18 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$input" got invalid value 123 at "input.foo.bar"; String cannot represent a non string value: 123`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value at "input.foo.bar"; String cannot represent a non string value`, err.Error()) + }) + + t.Run("required string field on nested input object array provided with Int - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `input Foo { bar: String! } input Bar { foo: [Foo!]! } type Query { hello(arg: Bar!): String }`, + operation: `query Foo($input: Bar!) { hello(arg: $input) }`, + variables: `{"input":{"foo":[{"bar":123}]}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value 123 at "input.foo.[0].bar"; String cannot represent a non string value: 123`, err.Error()) }) t.Run("required string field on nested input object array provided with Int", func(t *testing.T) { @@ -424,7 +635,18 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$input" got invalid value 123 at "input.foo.[0].bar"; String cannot represent a non string value: 123`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value at "input.foo.[0].bar"; String cannot represent a non string value`, err.Error()) + }) + + t.Run("required string field on nested input object array index 1 provided with Int - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `input Foo { bar: String! } input Bar { foo: [Foo!]! } type Query { hello(arg: Bar!): String }`, + operation: `query Foo($input: Bar!) { hello(arg: $input) }`, + variables: `{"input":{"foo":[{"bar":"hello"},{"bar":123}]}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value 123 at "input.foo.[1].bar"; String cannot represent a non string value: 123`, err.Error()) }) t.Run("required string field on nested input object array index 1 provided with Int", func(t *testing.T) { @@ -435,7 +657,18 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$input" got invalid value 123 at "input.foo.[1].bar"; String cannot represent a non string value: 123`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value at "input.foo.[1].bar"; String cannot represent a non string value`, err.Error()) + }) + + t.Run("non existing field on nested input object - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `input Foo { bar: String! } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, + operation: `query Foo($input: Bar!) { hello(arg: $input) }`, + variables: `{"input":{"foo":{"bar":"hello","baz":"world"}}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value {"foo":{"bar":"hello","baz":"world"}} at "input.foo"; Field "baz" is not defined by type "Foo".`, err.Error()) }) t.Run("non existing field on nested input object", func(t *testing.T) { @@ -446,7 +679,7 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$input" got invalid value {"foo":{"bar":"hello","baz":"world"}} at "input.foo"; Field "baz" is not defined by type "Foo".`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value at "input.foo"; Field "baz" is not defined by type "Foo".`, err.Error()) }) t.Run("required enum argument provided with correct value", func(t *testing.T) { @@ -459,6 +692,17 @@ func TestVariablesValidation(t *testing.T) { require.NoError(t, err) }) + t.Run("required enum argument provided with wrong value - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `enum Foo { BAR } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, + variables: `{"bar":"BAZ"}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value "BAZ"; Value "BAZ" does not exist in "Foo" enum.`, err.Error()) + }) + t.Run("required enum argument provided with wrong value", func(t *testing.T) { tc := testCase{ schema: `enum Foo { BAR } type Query { hello(arg: Foo!): String }`, @@ -467,7 +711,18 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$bar" got invalid value "BAZ"; Value "BAZ" does not exist in "Foo" enum.`, err.Error()) + assert.Equal(t, `Variable "$bar" got invalid value; Value does not exist in "Foo" enum.`, err.Error()) + }) + + t.Run("required enum argument provided with Int value - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `enum Foo { BAR } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, + variables: `{"bar":123}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value 123; Enum "Foo" cannot represent non-string value: 123.`, err.Error()) }) t.Run("required enum argument provided with Int value", func(t *testing.T) { @@ -478,7 +733,7 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$bar" got invalid value 123; Enum "Foo" cannot represent non-string value: 123.`, err.Error()) + assert.Equal(t, `Variable "$bar" got invalid value; Enum "Foo" cannot represent non-string value.`, err.Error()) }) t.Run("required enum argument provided with null", func(t *testing.T) { @@ -492,6 +747,17 @@ func TestVariablesValidation(t *testing.T) { assert.Equal(t, `Variable "$bar" got invalid value null; Expected non-nullable type "Foo!" not to be null.`, err.Error()) }) + t.Run("required nested enum argument provided with null - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `enum Foo { BAR } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, + operation: `query Foo($input: Bar!) { hello(arg: $input) }`, + variables: `{"input":{"foo":null}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value {"foo":null}; Field "foo" of required type "Foo!" was not provided.`, err.Error()) + }) + t.Run("required nested enum argument provided with null", func(t *testing.T) { tc := testCase{ schema: `enum Foo { BAR } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, @@ -500,7 +766,7 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$input" got invalid value {"foo":null}; Field "foo" of required type "Foo!" was not provided.`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value; Field "foo" of required type "Foo!" was not provided.`, err.Error()) }) t.Run("required nested enum argument provided with correct value", func(t *testing.T) { @@ -513,6 +779,17 @@ func TestVariablesValidation(t *testing.T) { require.NoError(t, err) }) + t.Run("required nested enum argument provided with wrong value - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `enum Foo { BAR } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, + operation: `query Foo($input: Bar!) { hello(arg: $input) }`, + variables: `{"input":{"foo":"BAZ"}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value {"foo":"BAZ"} at "input.foo"; Value "BAZ" does not exist in "Foo" enum.`, err.Error()) + }) + t.Run("required nested enum argument provided with wrong value", func(t *testing.T) { tc := testCase{ schema: `enum Foo { BAR } input Bar { foo: Foo! } type Query { hello(arg: Bar!): String }`, @@ -521,7 +798,7 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$input" got invalid value {"foo":"BAZ"} at "input.foo"; Value "BAZ" does not exist in "Foo" enum.`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value at "input.foo"; Value does not exist in "Foo" enum.`, err.Error()) }) t.Run("optional enum argument provided with null", func(t *testing.T) { @@ -544,6 +821,17 @@ func TestVariablesValidation(t *testing.T) { require.NoError(t, err) }) + t.Run("optional nested enum argument provided with incorrect value - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `enum Foo { BAR } input Bar { foo: Foo } type Query { hello(arg: Bar): String }`, + operation: `query Foo($input: Bar) { hello(arg: $input) }`, + variables: `{"input":{"foo":"BAZ"}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value {"foo":"BAZ"} at "input.foo"; Value "BAZ" does not exist in "Foo" enum.`, err.Error()) + }) + t.Run("optional nested enum argument provided with incorrect value", func(t *testing.T) { tc := testCase{ schema: `enum Foo { BAR } input Bar { foo: Foo } type Query { hello(arg: Bar): String }`, @@ -552,7 +840,7 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$input" got invalid value {"foo":"BAZ"} at "input.foo"; Value "BAZ" does not exist in "Foo" enum.`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value at "input.foo"; Value does not exist in "Foo" enum.`, err.Error()) }) t.Run("optional enum argument provided with correct value", func(t *testing.T) { @@ -565,6 +853,17 @@ func TestVariablesValidation(t *testing.T) { require.NoError(t, err) }) + t.Run("optional enum argument provided with wrong value - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `enum Foo { BAR } type Query { hello(arg: Foo): String }`, + operation: `query Foo($bar: Foo) { hello(arg: $bar) }`, + variables: `{"bar":"BAZ"}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value "BAZ"; Value "BAZ" does not exist in "Foo" enum.`, err.Error()) + }) + t.Run("optional enum argument provided with wrong value", func(t *testing.T) { tc := testCase{ schema: `enum Foo { BAR } type Query { hello(arg: Foo): String }`, @@ -573,7 +872,7 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$bar" got invalid value "BAZ"; Value "BAZ" does not exist in "Foo" enum.`, err.Error()) + assert.Equal(t, `Variable "$bar" got invalid value; Value does not exist in "Foo" enum.`, err.Error()) }) t.Run("required string list field argument provided with null", func(t *testing.T) { @@ -587,6 +886,18 @@ func TestVariablesValidation(t *testing.T) { assert.Equal(t, `Variable "$bar" got invalid value null; Expected non-nullable type "[String]!" not to be null.`, err.Error()) }) + t.Run("required string list field argument provided with non list Int value - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `type Query { hello(arg: [String]!): String }`, + operation: `query Foo($bar: [String]!) { hello(arg: $bar) }`, + variables: `{"bar":123}`, + withNormalization: true, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value 123 at "bar.[0]"; String cannot represent a non string value: 123`, err.Error()) + }) + t.Run("required string list field argument provided with non list Int value", func(t *testing.T) { tc := testCase{ schema: `type Query { hello(arg: [String]!): String }`, @@ -596,7 +907,7 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$bar" got invalid value 123 at "bar.[0]"; String cannot represent a non string value: 123`, err.Error()) + assert.Equal(t, `Variable "$bar" got invalid value at "bar.[0]"; String cannot represent a non string value`, err.Error()) }) t.Run("required string argument on input object list provided with correct value", func(t *testing.T) { @@ -617,6 +928,17 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value at "bar.[0].bar"; String cannot represent a non string value`, err.Error()) + }) + + t.Run("required string argument on input object list provided with wrong value - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: [Foo!]!): String }`, + operation: `query Foo($bar: [Foo!]!) { hello(arg: $bar) }`, + variables: `{"bar":[{"bar":123}]}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) assert.Equal(t, `Variable "$bar" got invalid value 123 at "bar.[0].bar"; String cannot represent a non string value: 123`, err.Error()) }) @@ -628,7 +950,18 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$bar" got invalid value ["hello"]; String cannot represent a non string value: ["hello"]`, err.Error()) + assert.Equal(t, `Variable "$bar" got invalid value; String cannot represent a non string value`, err.Error()) + }) + + t.Run("required input object list field argument provided with non list Int value - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: [Foo!]!): String }`, + operation: `query Foo($bar: [Foo!]!) { hello(arg: $bar) }`, + variables: `{"bar":123}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value 123; Expected type "Foo" to be an object.`, err.Error()) }) t.Run("required input object list field argument provided with non list Int value", func(t *testing.T) { @@ -639,7 +972,18 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$bar" got invalid value 123; Expected type "Foo" to be an object.`, err.Error()) + assert.Equal(t, `Variable "$bar" got invalid value; Expected type "Foo" to be an object.`, err.Error()) + }) + + t.Run("required input object field argument provided with list input object value - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `input Foo { bar: String! } type Query { hello(arg: Foo!): String }`, + operation: `query Foo($bar: Foo!) { hello(arg: $bar) }`, + variables: `{"bar":[{"bar":"hello"}]}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value [{"bar":"hello"}]; Expected type "Foo" to be an object.`, err.Error()) }) t.Run("required input object field argument provided with list input object value", func(t *testing.T) { @@ -650,7 +994,19 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$bar" got invalid value [{"bar":"hello"}]; Expected type "Foo" to be an object.`, err.Error()) + assert.Equal(t, `Variable "$bar" got invalid value; Expected type "Foo" to be an object.`, err.Error()) + }) + + t.Run("required enum list argument provided with non list Int value - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `enum Foo { BAR } type Query { hello(arg: [Foo]!): String }`, + operation: `query Foo($bar: [Foo]!) { hello(arg: $bar) }`, + variables: `{"bar":123}`, + withNormalization: true, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value 123 at "bar.[0]"; Enum "Foo" cannot represent non-string value: 123.`, err.Error()) }) t.Run("required enum list argument provided with non list Int value", func(t *testing.T) { @@ -662,7 +1018,19 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$bar" got invalid value 123 at "bar.[0]"; Enum "Foo" cannot represent non-string value: 123.`, err.Error()) + assert.Equal(t, `Variable "$bar" got invalid value at "bar.[0]"; Enum "Foo" cannot represent non-string value.`, err.Error()) + }) + + t.Run("required string list field argument provided with Int - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `type Query { hello(arg: [String]!): String }`, + operation: `query Foo($bar: [String]!) { hello(arg: $bar) }`, + variables: `{"bar":123}`, + withNormalization: true, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$bar" got invalid value 123 at "bar.[0]"; String cannot represent a non string value: 123`, err.Error()) }) t.Run("required string list field argument provided with Int", func(t *testing.T) { @@ -674,7 +1042,7 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$bar" got invalid value 123 at "bar.[0]"; String cannot represent a non string value: 123`, err.Error()) + assert.Equal(t, `Variable "$bar" got invalid value at "bar.[0]"; String cannot represent a non string value`, err.Error()) }) t.Run("optional nested list argument provided with null", func(t *testing.T) { @@ -697,6 +1065,17 @@ func TestVariablesValidation(t *testing.T) { require.NoError(t, err) }) + t.Run("optional nested list argument provided with empty list and missing Int - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `input Foo { bars : [String] bat: Int! } type Query { hello(arg: Foo): String }`, + operation: `query Foo($input: Foo) { hello(arg: $input) }`, + variables: `{"input":{"bars":[]}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value {"bars":[]}; Field "bat" of required type "Int!" was not provided.`, err.Error()) + }) + t.Run("optional nested list argument provided with empty list and missing Int", func(t *testing.T) { tc := testCase{ schema: `input Foo { bars : [String] bat: Int! } type Query { hello(arg: Foo): String }`, @@ -705,7 +1084,18 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$input" got invalid value {"bars":[]}; Field "bat" of required type "Int!" was not provided.`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value; Field "bat" of required type "Int!" was not provided.`, err.Error()) + }) + + t.Run("optional nested field is null followed by required nested field of wrong type - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `input Foo { bar: String! } input Bar { foo: Foo bat: Int! } type Query { hello(arg: Bar): String }`, + operation: `query Foo($input: Bar) { hello(arg: $input) }`, + variables: `{"input":{"foo":null,"bat":"hello"}}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value "hello" at "input.bat"; Int cannot represent non-integer value: "hello"`, err.Error()) }) t.Run("optional nested field is null followed by required nested field of wrong type", func(t *testing.T) { @@ -716,7 +1106,7 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$input" got invalid value "hello" at "input.bat"; Int cannot represent non-integer value: "hello"`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value at "input.bat"; Int cannot represent non-integer value`, err.Error()) }) t.Run("input field is a double nested list", func(t *testing.T) { @@ -739,6 +1129,17 @@ func TestVariablesValidation(t *testing.T) { require.NoError(t, err) }) + t.Run("triple nested value into variable of double nested list type - with variable content", func(t *testing.T) { + tc := testCase{ + schema: `type Query { hello(filter: [[String]]): String }`, + operation: `query Foo($input: [[String]]) { hello(filter: $input) }`, + variables: `{"input":[[["value"]]]}`, + } + err := runTestWithVariablesContentEnabled(t, tc) + require.Error(t, err) + assert.Equal(t, `Variable "$input" got invalid value ["value"] at "input.[0].[0]"; String cannot represent a non string value: ["value"]`, err.Error()) + }) + t.Run("triple nested value into variable of double nested list type", func(t *testing.T) { tc := testCase{ schema: `type Query { hello(filter: [[String]]): String }`, @@ -747,7 +1148,7 @@ func TestVariablesValidation(t *testing.T) { } err := runTest(t, tc) require.Error(t, err) - assert.Equal(t, `Variable "$input" got invalid value ["value"] at "input.[0].[0]"; String cannot represent a non string value: ["value"]`, err.Error()) + assert.Equal(t, `Variable "$input" got invalid value at "input.[0].[0]"; String cannot represent a non string value`, err.Error()) }) t.Run("null into non required list value", func(t *testing.T) { @@ -803,7 +1204,11 @@ type testCase struct { } func runTest(t *testing.T, tc testCase) error { - return runTestWithOptions(t, tc, VariablesValidatorOptions{}) + return runTestWithOptions(t, tc, VariablesValidatorOptions{DisableExposingVariablesContent: true}) +} + +func runTestWithVariablesContentEnabled(t *testing.T, tc testCase) error { + return runTestWithOptions(t, tc, VariablesValidatorOptions{DisableExposingVariablesContent: false}) } func runTestWithOptions(t *testing.T, tc testCase, options VariablesValidatorOptions) error {