Skip to content

Commit dae41bd

Browse files
authored
Merge pull request #370 from Ackar/directives
Add support for directives in schema parser
2 parents 8334863 + a579090 commit dae41bd

File tree

3 files changed

+149
-34
lines changed

3 files changed

+149
-34
lines changed

internal/common/values.go

+8-6
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ import (
66

77
// http://facebook.github.io/graphql/draft/#InputValueDefinition
88
type InputValue struct {
9-
Name Ident
10-
Type Type
11-
Default Literal
12-
Desc string
13-
Loc errors.Location
14-
TypeLoc errors.Location
9+
Name Ident
10+
Type Type
11+
Default Literal
12+
Desc string
13+
Directives DirectiveList
14+
Loc errors.Location
15+
TypeLoc errors.Location
1516
}
1617

1718
type InputValueList []*InputValue
@@ -37,6 +38,7 @@ func ParseInputValue(l *Lexer) *InputValue {
3738
l.ConsumeToken('=')
3839
p.Default = ParseLiteral(l, true)
3940
}
41+
p.Directives = ParseDirectives(l)
4042
return p
4143
}
4244

internal/schema/schema.go

+70-28
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ type NamedType interface {
7272
//
7373
// http://facebook.github.io/graphql/draft/#sec-Scalars
7474
type Scalar struct {
75-
Name string
76-
Desc string
77-
// TODO: Add a list of directives?
75+
Name string
76+
Desc string
77+
Directives common.DirectiveList
7878
}
7979

8080
// Object types represent a list of named fields, each of which yield a value of a specific type.
@@ -89,7 +89,7 @@ type Object struct {
8989
Interfaces []*Interface
9090
Fields FieldList
9191
Desc string
92-
// TODO: Add a list of directives?
92+
Directives common.DirectiveList
9393

9494
interfaceNames []string
9595
}
@@ -105,7 +105,7 @@ type Interface struct {
105105
PossibleTypes []*Object
106106
Fields FieldList // NOTE: the spec refers to this as `FieldsDefinition`.
107107
Desc string
108-
// TODO: Add a list of directives?
108+
Directives common.DirectiveList
109109
}
110110

111111
// Union types represent objects that could be one of a list of GraphQL object types, but provides no
@@ -119,7 +119,7 @@ type Union struct {
119119
Name string
120120
PossibleTypes []*Object // NOTE: the spec refers to this as `UnionMemberTypes`.
121121
Desc string
122-
// TODO: Add a list of directives?
122+
Directives common.DirectiveList
123123

124124
typeNames []string
125125
}
@@ -130,10 +130,10 @@ type Union struct {
130130
//
131131
// http://facebook.github.io/graphql/draft/#sec-Enums
132132
type Enum struct {
133-
Name string
134-
Values []*EnumValue // NOTE: the spec refers to this as `EnumValuesDefinition`.
135-
Desc string
136-
// TODO: Add a list of directives?
133+
Name string
134+
Values []*EnumValue // NOTE: the spec refers to this as `EnumValuesDefinition`.
135+
Desc string
136+
Directives common.DirectiveList
137137
}
138138

139139
// EnumValue types are unique values that may be serialized as a string: the name of the
@@ -144,7 +144,6 @@ type EnumValue struct {
144144
Name string
145145
Directives common.DirectiveList
146146
Desc string
147-
// TODO: Add a list of directives?
148147
}
149148

150149
// InputObject types define a set of input fields; the input fields are either scalars, enums, or
@@ -154,19 +153,19 @@ type EnumValue struct {
154153
//
155154
// http://facebook.github.io/graphql/draft/#sec-Input-Objects
156155
type InputObject struct {
157-
Name string
158-
Desc string
159-
Values common.InputValueList
160-
// TODO: Add a list of directives?
156+
Name string
157+
Desc string
158+
Values common.InputValueList
159+
Directives common.DirectiveList
161160
}
162161

163162
// Extension type defines a GraphQL type extension.
164163
// Schemas, Objects, Inputs and Scalars can be extended.
165164
//
166165
// https://facebook.github.io/graphql/draft/#sec-Type-System-Extensions
167166
type Extension struct {
168-
Type NamedType
169-
// TODO: Add a list of directives
167+
Type NamedType
168+
Directives common.DirectiveList
170169
}
171170

172171
// FieldsList is a list of an Object's Fields.
@@ -314,6 +313,14 @@ func (s *Schema) Parse(schemaString string, useStringDescriptions bool) error {
314313

315314
for _, obj := range s.objects {
316315
obj.Interfaces = make([]*Interface, len(obj.interfaceNames))
316+
if err := resolveDirectives(s, obj.Directives, "OBJECT"); err != nil {
317+
return err
318+
}
319+
for _, field := range obj.Fields {
320+
if err := resolveDirectives(s, field.Directives, "FIELD_DEFINITION"); err != nil {
321+
return err
322+
}
323+
}
317324
for i, intfName := range obj.interfaceNames {
318325
t, ok := s.Types[intfName]
319326
if !ok {
@@ -334,6 +341,9 @@ func (s *Schema) Parse(schemaString string, useStringDescriptions bool) error {
334341
}
335342

336343
for _, union := range s.unions {
344+
if err := resolveDirectives(s, union.Directives, "UNION"); err != nil {
345+
return err
346+
}
337347
union.PossibleTypes = make([]*Object, len(union.typeNames))
338348
for i, name := range union.typeNames {
339349
t, ok := s.Types[name]
@@ -349,8 +359,11 @@ func (s *Schema) Parse(schemaString string, useStringDescriptions bool) error {
349359
}
350360

351361
for _, enum := range s.enums {
362+
if err := resolveDirectives(s, enum.Directives, "ENUM"); err != nil {
363+
return err
364+
}
352365
for _, value := range enum.Values {
353-
if err := resolveDirectives(s, value.Directives); err != nil {
366+
if err := resolveDirectives(s, value.Directives, "ENUM_VALUE"); err != nil {
354367
return err
355368
}
356369
}
@@ -469,19 +482,29 @@ func resolveField(s *Schema, f *Field) error {
469482
return err
470483
}
471484
f.Type = t
472-
if err := resolveDirectives(s, f.Directives); err != nil {
485+
if err := resolveDirectives(s, f.Directives, "FIELD_DEFINITION"); err != nil {
473486
return err
474487
}
475488
return resolveInputObject(s, f.Args)
476489
}
477490

478-
func resolveDirectives(s *Schema, directives common.DirectiveList) error {
491+
func resolveDirectives(s *Schema, directives common.DirectiveList, loc string) error {
479492
for _, d := range directives {
480493
dirName := d.Name.Name
481494
dd, ok := s.Directives[dirName]
482495
if !ok {
483496
return errors.Errorf("directive %q not found", dirName)
484497
}
498+
validLoc := false
499+
for _, l := range dd.Locs {
500+
if l == loc {
501+
validLoc = true
502+
break
503+
}
504+
}
505+
if !validLoc {
506+
return errors.Errorf("invalid location %q for directive %q (must be one of %v)", loc, dirName, dd.Locs)
507+
}
485508
for _, arg := range d.Args {
486509
if dd.Args.Get(arg.Name.Name) == nil {
487510
return errors.Errorf("invalid argument %q for directive %q", arg.Name.Name, dirName)
@@ -554,7 +577,8 @@ func parseSchema(s *Schema, l *common.Lexer) {
554577

555578
case "scalar":
556579
name := l.ConsumeIdent()
557-
s.Types[name] = &Scalar{Name: name, Desc: desc}
580+
directives := common.ParseDirectives(l)
581+
s.Types[name] = &Scalar{Name: name, Desc: desc, Directives: directives}
558582

559583
case "directive":
560584
directive := parseDirectiveDef(l)
@@ -574,16 +598,30 @@ func parseSchema(s *Schema, l *common.Lexer) {
574598
func parseObjectDef(l *common.Lexer) *Object {
575599
object := &Object{Name: l.ConsumeIdent()}
576600

577-
if l.Peek() == scanner.Ident {
578-
l.ConsumeKeyword("implements")
601+
for {
602+
if l.Peek() == '{' {
603+
break
604+
}
579605

580-
for l.Peek() != '{' {
581-
if l.Peek() == '&' {
582-
l.ConsumeToken('&')
583-
}
606+
if l.Peek() == '@' {
607+
object.Directives = common.ParseDirectives(l)
608+
continue
609+
}
610+
611+
if l.Peek() == scanner.Ident {
612+
l.ConsumeKeyword("implements")
584613

585-
object.interfaceNames = append(object.interfaceNames, l.ConsumeIdent())
614+
for l.Peek() != '{' && l.Peek() != '@' {
615+
if l.Peek() == '&' {
616+
l.ConsumeToken('&')
617+
}
618+
619+
object.interfaceNames = append(object.interfaceNames, l.ConsumeIdent())
620+
}
621+
continue
586622
}
623+
624+
l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "implements", "directive" or "{"`, l.Peek()))
587625
}
588626

589627
l.ConsumeToken('{')
@@ -596,6 +634,7 @@ func parseObjectDef(l *common.Lexer) *Object {
596634
func parseInterfaceDef(l *common.Lexer) *Interface {
597635
i := &Interface{Name: l.ConsumeIdent()}
598636

637+
i.Directives = common.ParseDirectives(l)
599638
l.ConsumeToken('{')
600639
i.Fields = parseFieldsDef(l)
601640
l.ConsumeToken('}')
@@ -606,6 +645,7 @@ func parseInterfaceDef(l *common.Lexer) *Interface {
606645
func parseUnionDef(l *common.Lexer) *Union {
607646
union := &Union{Name: l.ConsumeIdent()}
608647

648+
union.Directives = common.ParseDirectives(l)
609649
l.ConsumeToken('=')
610650
union.typeNames = []string{l.ConsumeIdent()}
611651
for l.Peek() == '|' {
@@ -619,6 +659,7 @@ func parseUnionDef(l *common.Lexer) *Union {
619659
func parseInputDef(l *common.Lexer) *InputObject {
620660
i := &InputObject{}
621661
i.Name = l.ConsumeIdent()
662+
i.Directives = common.ParseDirectives(l)
622663
l.ConsumeToken('{')
623664
for l.Peek() != '}' {
624665
i.Values = append(i.Values, common.ParseInputValue(l))
@@ -630,6 +671,7 @@ func parseInputDef(l *common.Lexer) *InputObject {
630671
func parseEnumDef(l *common.Lexer) *Enum {
631672
enum := &Enum{Name: l.ConsumeIdent()}
632673

674+
enum.Directives = common.ParseDirectives(l)
633675
l.ConsumeToken('{')
634676
for l.Peek() != '}' {
635677
v := &EnumValue{

internal/schema/schema_test.go

+71
Original file line numberDiff line numberDiff line change
@@ -782,6 +782,77 @@ Second line of the description.
782782
return nil
783783
},
784784
},
785+
{
786+
name: "Parses directives",
787+
sdl: `
788+
directive @objectdirective on OBJECT
789+
directive @fielddirective on FIELD_DEFINITION
790+
directive @enumdirective on ENUM
791+
directive @uniondirective on UNION
792+
directive @directive on SCALAR
793+
| OBJECT
794+
| FIELD_DEFINITION
795+
| ARGUMENT_DEFINITION
796+
| INTERFACE
797+
| UNION
798+
| ENUM
799+
| ENUM_VALUE
800+
| INPUT_OBJECT
801+
| INPUT_FIELD_DEFINITION
802+
803+
interface NamedEntity @directive { name: String }
804+
805+
scalar Time @directive
806+
807+
type Photo @objectdirective {
808+
id: ID! @deprecated @fielddirective
809+
}
810+
811+
type Person implements NamedEntity @objectdirective {
812+
name: String
813+
}
814+
815+
enum Direction @enumdirective {
816+
NORTH @deprecated
817+
EAST
818+
SOUTH
819+
WEST
820+
}
821+
822+
union Union @uniondirective = Photo | Person
823+
`,
824+
validateSchema: func(s *schema.Schema) error {
825+
namedEntityDirectives := s.Types["NamedEntity"].(*schema.Interface).Directives
826+
if len(namedEntityDirectives) != 1 || namedEntityDirectives[0].Name.Name != "directive" {
827+
return fmt.Errorf("missing directive on NamedEntity interface, expected @directive but got %v", namedEntityDirectives)
828+
}
829+
830+
timeDirectives := s.Types["Time"].(*schema.Scalar).Directives
831+
if len(timeDirectives) != 1 || timeDirectives[0].Name.Name != "directive" {
832+
return fmt.Errorf("missing directive on Time scalar, expected @directive but got %v", timeDirectives)
833+
}
834+
835+
photo := s.Types["Photo"].(*schema.Object)
836+
photoDirectives := photo.Directives
837+
if len(photoDirectives) != 1 || photoDirectives[0].Name.Name != "objectdirective" {
838+
return fmt.Errorf("missing directive on Time scalar, expected @objectdirective but got %v", photoDirectives)
839+
}
840+
if len(photo.Fields.Get("id").Directives) != 2 {
841+
return fmt.Errorf("expected Photo.id to have 2 directives but got %v", photoDirectives)
842+
}
843+
844+
directionDirectives := s.Types["Direction"].(*schema.Enum).Directives
845+
if len(directionDirectives) != 1 || directionDirectives[0].Name.Name != "enumdirective" {
846+
return fmt.Errorf("missing directive on Direction enum, expected @enumdirective but got %v", directionDirectives)
847+
}
848+
849+
unionDirectives := s.Types["Union"].(*schema.Union).Directives
850+
if len(unionDirectives) != 1 || unionDirectives[0].Name.Name != "uniondirective" {
851+
return fmt.Errorf("missing directive on Union union, expected @uniondirective but got %v", unionDirectives)
852+
}
853+
return nil
854+
},
855+
},
785856
} {
786857
t.Run(test.name, func(t *testing.T) {
787858
s := schema.New()

0 commit comments

Comments
 (0)