Skip to content

Commit 2985a52

Browse files
committed
Separate multiple inherited interfaces with &
This replaces: ```graphql type Foo implements Bar, Baz { field: Type } ``` With: ```graphql type Foo implements Bar & Baz { field: Type } ``` This is more consistent with other trailing lists of values which either have an explicit separator (union members) or are prefixed with a sigil (directives). This avoids parse ambiguity in the case of an omitted field set, illustrated by [github.com/graphql/graphql-js#1166](graphql/graphql-js#1166). For now, the old method of declaration remains valid. References: - graphql/graphql-js#1169 - graphql/graphql-spec#90 - graphql/graphql-spec@32b55ed
1 parent 47a07f9 commit 2985a52

File tree

4 files changed

+89
-13
lines changed

4 files changed

+89
-13
lines changed

internal/common/lexer.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ type Ident struct {
2020
Loc errors.Location
2121
}
2222

23-
func New(sc *scanner.Scanner) *Lexer {
23+
func NewLexer(sc *scanner.Scanner) *Lexer {
2424
l := &Lexer{sc: sc}
2525
l.Consume()
2626
return l

internal/query/query.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ func Parse(queryString string) (*Document, *errors.QueryError) {
100100
}
101101
sc.Init(strings.NewReader(queryString))
102102

103-
l := common.New(sc)
103+
l := common.NewLexer(sc)
104104
var doc *Document
105105
err := l.CatchSyntaxError(func() {
106106
doc = parseDocument(l)

internal/schema/schema.go

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -154,13 +154,14 @@ func New() *Schema {
154154
return s
155155
}
156156

157+
// Parse the schema string.
157158
func (s *Schema) Parse(schemaString string) error {
158159
sc := &scanner.Scanner{
159160
Mode: scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanStrings,
160161
}
161162
sc.Init(strings.NewReader(schemaString))
162163

163-
l := common.New(sc)
164+
l := common.NewLexer(sc)
164165
err := l.CatchSyntaxError(func() {
165166
parseSchema(s, l)
166167
})
@@ -316,7 +317,7 @@ func parseSchema(s *Schema, l *common.Lexer) {
316317
}
317318
l.ConsumeToken('}')
318319
case "type":
319-
obj := parseObjectDecl(l)
320+
obj := parseObjectDeclaration(l)
320321
obj.Desc = desc
321322
s.Types[obj.Name] = obj
322323
s.objects = append(s.objects, obj)
@@ -351,22 +352,26 @@ func parseSchema(s *Schema, l *common.Lexer) {
351352
}
352353
}
353354

354-
func parseObjectDecl(l *common.Lexer) *Object {
355-
o := &Object{}
356-
o.Name = l.ConsumeIdent()
355+
func parseObjectDeclaration(l *common.Lexer) *Object {
356+
object := &Object{Name: l.ConsumeIdent()}
357+
357358
if l.Peek() == scanner.Ident {
358359
l.ConsumeKeyword("implements")
359-
for {
360-
o.interfaceNames = append(o.interfaceNames, l.ConsumeIdent())
361-
if l.Peek() == '{' {
362-
break
360+
361+
for l.Peek() != '{' {
362+
object.interfaceNames = append(object.interfaceNames, l.ConsumeIdent())
363+
364+
if l.Peek() == '&' {
365+
l.ConsumeToken('&')
363366
}
364367
}
365368
}
369+
366370
l.ConsumeToken('{')
367-
o.Fields = parseFields(l)
371+
object.Fields = parseFields(l)
368372
l.ConsumeToken('}')
369-
return o
373+
374+
return object
370375
}
371376

372377
func parseInterfaceDecl(l *common.Lexer) *Interface {

internal/schema/schema_test.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package schema
2+
3+
import (
4+
"strings"
5+
"testing"
6+
"text/scanner"
7+
8+
"github.com/graph-gophers/graphql-go/internal/common"
9+
)
10+
11+
type testCase struct {
12+
description string
13+
declaration string
14+
expected *Object
15+
}
16+
17+
func TestParseObjectDeclaration(t *testing.T) {
18+
tests := []testCase{
19+
{
20+
"allows '&' separator",
21+
"Alien implements Being & Intelligent { name: String, iq: Int }",
22+
&Object{
23+
Name: "Alien",
24+
interfaceNames: []string{"Being", "Intelligent"},
25+
},
26+
},
27+
{
28+
"allows legacy ',' separator",
29+
"Alien implements Being, Intelligent { name: String, iq: Int }",
30+
&Object{
31+
Name: "Alien",
32+
interfaceNames: []string{"Being", "Intelligent"},
33+
},
34+
},
35+
}
36+
37+
setup := func(schema string) *common.Lexer {
38+
sc := &scanner.Scanner{
39+
Mode: scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanStrings,
40+
}
41+
sc.Init(strings.NewReader(schema))
42+
return common.NewLexer(sc)
43+
}
44+
45+
for _, test := range tests {
46+
t.Run(test.description, func(t *testing.T) {
47+
lex := setup(test.declaration)
48+
var actual *Object
49+
50+
parse := func() { actual = parseObjectDeclaration(lex) }
51+
if err := lex.CatchSyntaxError(parse); err != nil {
52+
t.Fatal(err)
53+
}
54+
55+
if test.expected.Name != actual.Name {
56+
t.Errorf("wrong object name: want %q, got %q", test.expected.Name, actual.Name)
57+
}
58+
59+
if len(test.expected.interfaceNames) != len(actual.interfaceNames) {
60+
t.Fatalf("wrong number of interface names: want %s, got %s", test.expected.interfaceNames, actual.interfaceNames)
61+
}
62+
63+
for i, expectedName := range test.expected.interfaceNames {
64+
actualName := actual.interfaceNames[i]
65+
if expectedName != actualName {
66+
t.Errorf("wrong interface name: want %q, got %q", expectedName, actualName)
67+
}
68+
}
69+
})
70+
}
71+
}

0 commit comments

Comments
 (0)