Skip to content

Commit e38a212

Browse files
Be more precise in deciding whether to add the schema prelude (#205)
GraphQL schemas have some builtin types, like `String`. The spec says your SDL must not include those, but in practice some schemas do. (This is probably because introspection must include them, and some tools that create SDL from introspection don't know they're supposed to filter them out.) Anyway, we've since #145 had logic to handle this; we just parse with and without the prelude that defines them and see which works. The problem is that this makes for very confusing error messages if you have an invalid schema. (Or if you have a schema that you think is valid but gqlparser doesn't, which is the more common case in the wild; see for example #200.) Right now if both ways error we take the without-prelude error, which if you didn't define the builtins is just `undefined type String`; if we took the with-prelude error then if you did define the builtins you'd just get `type String defined twice`. So we actually have to be smart if we want good error messages for everyone. So in this commit we are smart: we check if your schema defines `String`, and include the prelude only if it does not. To do this I basically inlined `gqlparser.LoadSchema` (twice), so that in between parsing and validation we can check if you have `String` and if not add the prelude. This should in theory be both more efficient (we don't have to do everything twice) and give better error messages, although it's a bit more code. Fixes #175. Test plan: make check
1 parent 520532e commit e38a212

15 files changed

+143
-14
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ When releasing a new version:
3636
### Bug fixes:
3737

3838
- genqlient now explicitly rejects argument, operation, and type names which are Go keywords, rather than failing with an opaque error.
39+
- genqlient now gives better error messages if it thinks your schema is invalid.
3940

4041
## v0.4.0
4142

generate/parse.go

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@ import (
1010
"strconv"
1111
"strings"
1212

13-
"github.com/vektah/gqlparser/v2"
1413
"github.com/vektah/gqlparser/v2/ast"
15-
"github.com/vektah/gqlparser/v2/gqlerror"
1614
"github.com/vektah/gqlparser/v2/parser"
1715
"github.com/vektah/gqlparser/v2/validator"
16+
_ "github.com/vektah/gqlparser/v2/validator/rules"
1817
)
1918

2019
func getSchema(globs StringList) (*ast.Schema, error) {
@@ -32,19 +31,39 @@ func getSchema(globs StringList) (*ast.Schema, error) {
3231
sources[i] = &ast.Source{Name: filename, Input: string(text)}
3332
}
3433

35-
// Multi step schema validation
36-
// Step 1 assume schema implicitly declares types that are required by the graphql spec
37-
// Step 2 assume schema explicitly declares types that are required by the graphql spec
38-
var (
39-
schema *ast.Schema
40-
graphqlError *gqlerror.Error
41-
)
42-
schema, graphqlError = gqlparser.LoadSchema(sources...)
34+
// Ideally here we'd just call gqlparser.LoadSchema. But the schema we are
35+
// given may or may not contain the builtin types String, Int, etc. (The
36+
// spec says it shouldn't, but introspection will return those types, and
37+
// some introspection-to-SDL tools aren't smart enough to remove them.) So
38+
// we inline LoadSchema and insert some checks.
39+
document, graphqlError := parser.ParseSchemas(sources...)
4340
if graphqlError != nil {
44-
schema, graphqlError = validator.LoadSchema(sources...)
41+
// Schema doesn't even parse.
42+
return nil, errorf(nil, "invalid schema: %v", graphqlError)
43+
}
44+
45+
// Check if we have a builtin type. (String is an arbitrary choice.)
46+
hasBuiltins := false
47+
for _, def := range document.Definitions {
48+
if def.Name == "String" {
49+
hasBuiltins = true
50+
break
51+
}
52+
}
53+
54+
if !hasBuiltins {
55+
// modified from parser.ParseSchemas
56+
var preludeAST *ast.SchemaDocument
57+
preludeAST, graphqlError = parser.ParseSchema(validator.Prelude)
4558
if graphqlError != nil {
46-
return nil, errorf(nil, "invalid schema: %v", graphqlError)
59+
return nil, errorf(nil, "invalid prelude (probably a gqlparser bug): %v", graphqlError)
4760
}
61+
document.Merge(preludeAST)
62+
}
63+
64+
schema, graphqlError := validator.ValidateSchemaDocument(document)
65+
if graphqlError != nil {
66+
return nil, errorf(nil, "invalid schema: %v", graphqlError)
4867
}
4968

5069
return schema, nil
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
query MyQuery { f g }
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
type Query {
2+
f: String
3+
g: Bogus
4+
}
5+
6+
scalar Int
7+
scalar Float
8+
scalar String
9+
scalar Boolean
10+
scalar ID
11+
12+
directive @include(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
13+
directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT
14+
directive @deprecated(reason: String = "No longer supported") on FIELD_DEFINITION | ARGUMENT_DEFINITION | INPUT_FIELD_DEFINITION | ENUM_VALUE
15+
directive @specifiedBy(url: String!) on SCALAR
16+
17+
type __Schema {
18+
description: String
19+
types: [__Type!]!
20+
queryType: __Type!
21+
mutationType: __Type
22+
subscriptionType: __Type
23+
directives: [__Directive!]!
24+
}
25+
26+
type __Type {
27+
kind: __TypeKind!
28+
name: String
29+
description: String
30+
fields(includeDeprecated: Boolean = false): [__Field!]
31+
interfaces: [__Type!]
32+
possibleTypes: [__Type!]
33+
enumValues(includeDeprecated: Boolean = false): [__EnumValue!]
34+
inputFields: [__InputValue!]
35+
ofType: __Type
36+
specifiedByURL: String
37+
}
38+
39+
type __Field {
40+
name: String!
41+
description: String
42+
args: [__InputValue!]!
43+
type: __Type!
44+
isDeprecated: Boolean!
45+
deprecationReason: String
46+
}
47+
48+
type __InputValue {
49+
name: String!
50+
description: String
51+
type: __Type!
52+
defaultValue: String
53+
}
54+
55+
type __EnumValue {
56+
name: String!
57+
description: String
58+
isDeprecated: Boolean!
59+
deprecationReason: String
60+
}
61+
62+
enum __TypeKind {
63+
SCALAR
64+
OBJECT
65+
INTERFACE
66+
UNION
67+
ENUM
68+
INPUT_OBJECT
69+
LIST
70+
NON_NULL
71+
}
72+
73+
type __Directive {
74+
name: String!
75+
description: String
76+
locations: [__DirectiveLocation!]!
77+
args: [__InputValue!]!
78+
isRepeatable: Boolean!
79+
}
80+
81+
enum __DirectiveLocation {
82+
QUERY
83+
MUTATION
84+
SUBSCRIPTION
85+
FIELD
86+
FRAGMENT_DEFINITION
87+
FRAGMENT_SPREAD
88+
INLINE_FRAGMENT
89+
VARIABLE_DEFINITION
90+
SCHEMA
91+
SCALAR
92+
OBJECT
93+
FIELD_DEFINITION
94+
ARGUMENT_DEFINITION
95+
INTERFACE
96+
UNION
97+
ENUM
98+
ENUM_VALUE
99+
INPUT_OBJECT
100+
INPUT_FIELD_DEFINITION
101+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
query MyQuery { f g }
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
type Query {
2+
f: String
3+
g: Bogus
4+
}

generate/testdata/snapshots/TestGenerateErrors-InvalidSchema-go

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)