From 8632753a6219518df212d6519f43d9d4666f8d8c Mon Sep 17 00:00:00 2001 From: Richard Musiol Date: Wed, 22 Mar 2017 14:28:36 +0100 Subject: [PATCH] validation: ScalarLeafs --- internal/query/query.go | 2 + internal/tests/testdata/export.js | 2 +- internal/tests/testdata/tests.json | 124 +++++++++++++++++++++++++++++ internal/validation/validation.go | 20 +++++ 4 files changed, 147 insertions(+), 1 deletion(-) diff --git a/internal/query/query.go b/internal/query/query.go index 4df5c96ae5b..792559c5df4 100644 --- a/internal/query/query.go +++ b/internal/query/query.go @@ -55,6 +55,7 @@ type NamedFragment struct { type SelectionSet struct { Selections []Selection + Loc errors.Location } type Selection interface { @@ -164,6 +165,7 @@ func parseFragment(l *lexer.Lexer) *NamedFragment { func parseSelectionSet(l *lexer.Lexer) *SelectionSet { sel := &SelectionSet{} + sel.Loc = l.Location() l.ConsumeToken('{') for l.Peek() != '}' { sel.Selections = append(sel.Selections, parseSelection(l)) diff --git a/internal/tests/testdata/export.js b/internal/tests/testdata/export.js index 8a3921e0312..35688178d45 100644 --- a/internal/tests/testdata/export.js +++ b/internal/tests/testdata/export.js @@ -71,7 +71,7 @@ require('./src/validation/__tests__/LoneAnonymousOperation-test'); // require('./src/validation/__tests__/OverlappingFieldsCanBeMerged-test'); // require('./src/validation/__tests__/PossibleFragmentSpreads-test'); require('./src/validation/__tests__/ProvidedNonNullArguments-test'); -// require('./src/validation/__tests__/ScalarLeafs-test'); +require('./src/validation/__tests__/ScalarLeafs-test'); require('./src/validation/__tests__/UniqueArgumentNames-test'); // require('./src/validation/__tests__/UniqueDirectivesPerLocation-test'); // require('./src/validation/__tests__/UniqueFragmentNames-test'); diff --git a/internal/tests/testdata/tests.json b/internal/tests/testdata/tests.json index 1638a42329e..8c170a01fc8 100644 --- a/internal/tests/testdata/tests.json +++ b/internal/tests/testdata/tests.json @@ -1781,6 +1781,130 @@ } ] }, + { + "name": "Validate: Scalar leafs/valid scalar selection", + "rule": "ScalarLeafs", + "query": "\n fragment scalarSelection on Dog {\n barks\n }\n ", + "errors": [] + }, + { + "name": "Validate: Scalar leafs/object type missing selection", + "rule": "ScalarLeafs", + "query": "\n query directQueryOnObjectWithoutSubFields {\n human\n }\n ", + "errors": [ + { + "message": "Field \"human\" of type \"Human\" must have a selection of subfields. Did you mean \"human { ... }\"?", + "locations": [ + { + "line": 3, + "column": 9 + } + ] + } + ] + }, + { + "name": "Validate: Scalar leafs/interface type missing selection", + "rule": "ScalarLeafs", + "query": "\n {\n human { pets }\n }\n ", + "errors": [ + { + "message": "Field \"pets\" of type \"[Pet]\" must have a selection of subfields. Did you mean \"pets { ... }\"?", + "locations": [ + { + "line": 3, + "column": 17 + } + ] + } + ] + }, + { + "name": "Validate: Scalar leafs/valid scalar selection with args", + "rule": "ScalarLeafs", + "query": "\n fragment scalarSelectionWithArgs on Dog {\n doesKnowCommand(dogCommand: SIT)\n }\n ", + "errors": [] + }, + { + "name": "Validate: Scalar leafs/scalar selection not allowed on Boolean", + "rule": "ScalarLeafs", + "query": "\n fragment scalarSelectionsNotAllowedOnBoolean on Dog {\n barks { sinceWhen }\n }\n ", + "errors": [ + { + "message": "Field \"barks\" must not have a selection since type \"Boolean\" has no subfields.", + "locations": [ + { + "line": 3, + "column": 15 + } + ] + } + ] + }, + { + "name": "Validate: Scalar leafs/scalar selection not allowed on Enum", + "rule": "ScalarLeafs", + "query": "\n fragment scalarSelectionsNotAllowedOnEnum on Cat {\n furColor { inHexdec }\n }\n ", + "errors": [ + { + "message": "Field \"furColor\" must not have a selection since type \"FurColor\" has no subfields.", + "locations": [ + { + "line": 3, + "column": 18 + } + ] + } + ] + }, + { + "name": "Validate: Scalar leafs/scalar selection not allowed with args", + "rule": "ScalarLeafs", + "query": "\n fragment scalarSelectionsNotAllowedWithArgs on Dog {\n doesKnowCommand(dogCommand: SIT) { sinceWhen }\n }\n ", + "errors": [ + { + "message": "Field \"doesKnowCommand\" must not have a selection since type \"Boolean\" has no subfields.", + "locations": [ + { + "line": 3, + "column": 42 + } + ] + } + ] + }, + { + "name": "Validate: Scalar leafs/Scalar selection not allowed with directives", + "rule": "ScalarLeafs", + "query": "\n fragment scalarSelectionsNotAllowedWithDirectives on Dog {\n name @include(if: true) { isAlsoHumanName }\n }\n ", + "errors": [ + { + "message": "Field \"name\" must not have a selection since type \"String\" has no subfields.", + "locations": [ + { + "line": 3, + "column": 33 + } + ] + } + ] + }, + { + "name": "Validate: Scalar leafs/Scalar selection not allowed with directives and args", + "rule": "ScalarLeafs", + "query": "\n fragment scalarSelectionsNotAllowedWithDirectivesAndArgs on Dog {\n doesKnowCommand(dogCommand: SIT) @include(if: true) { sinceWhen }\n }\n ", + "errors": [ + { + "message": "Field \"doesKnowCommand\" must not have a selection since type \"Boolean\" has no subfields.", + "locations": [ + { + "line": 3, + "column": 61 + } + ] + } + ] + }, { "name": "Validate: Unique argument names/no arguments on field", "rule": "UniqueArgumentNames", diff --git a/internal/validation/validation.go b/internal/validation/validation.go index 79752a57a3d..39cb429c0f7 100644 --- a/internal/validation/validation.go +++ b/internal/validation/validation.go @@ -119,6 +119,13 @@ func (c *context) validateSelection(sel query.Selection, t common.Type) { var ft common.Type if f != nil { ft = f.Type + sf := hasSubfields(ft) + if sf && sel.SelSet == nil { + c.addErr(sel.Loc, "ScalarLeafs", "Field %q of type %q must have a selection of subfields. Did you mean \"%s { ... }\"?", sel.Name, ft, sel.Name) + } + if !sf && sel.SelSet != nil { + c.addErr(sel.SelSet.Loc, "ScalarLeafs", "Field %q must not have a selection since type %q has no subfields.", sel.Name, ft) + } } if sel.SelSet != nil { c.validateSelectionSet(sel.SelSet, ft) @@ -370,6 +377,19 @@ func canBeInput(t common.Type) bool { } } +func hasSubfields(t common.Type) bool { + switch t := t.(type) { + case *schema.Object, *schema.Interface, *schema.Union: + return true + case *common.List: + return hasSubfields(t.OfType) + case *common.NonNull: + return hasSubfields(t.OfType) + default: + return false + } +} + func stringify(v interface{}) string { switch v := v.(type) { case *lexer.Literal: