Skip to content

Commit

Permalink
Merge pull request #370 from Ackar/directives
Browse files Browse the repository at this point in the history
Add support for directives in schema parser
  • Loading branch information
pavelnikolov authored Mar 9, 2020
2 parents 8334863 + a579090 commit dae41bd
Show file tree
Hide file tree
Showing 3 changed files with 149 additions and 34 deletions.
14 changes: 8 additions & 6 deletions internal/common/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import (

// http://facebook.github.io/graphql/draft/#InputValueDefinition
type InputValue struct {
Name Ident
Type Type
Default Literal
Desc string
Loc errors.Location
TypeLoc errors.Location
Name Ident
Type Type
Default Literal
Desc string
Directives DirectiveList
Loc errors.Location
TypeLoc errors.Location
}

type InputValueList []*InputValue
Expand All @@ -37,6 +38,7 @@ func ParseInputValue(l *Lexer) *InputValue {
l.ConsumeToken('=')
p.Default = ParseLiteral(l, true)
}
p.Directives = ParseDirectives(l)
return p
}

Expand Down
98 changes: 70 additions & 28 deletions internal/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ type NamedType interface {
//
// http://facebook.github.io/graphql/draft/#sec-Scalars
type Scalar struct {
Name string
Desc string
// TODO: Add a list of directives?
Name string
Desc string
Directives common.DirectiveList
}

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

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

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

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

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

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

// Extension type defines a GraphQL type extension.
// Schemas, Objects, Inputs and Scalars can be extended.
//
// https://facebook.github.io/graphql/draft/#sec-Type-System-Extensions
type Extension struct {
Type NamedType
// TODO: Add a list of directives
Type NamedType
Directives common.DirectiveList
}

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

for _, obj := range s.objects {
obj.Interfaces = make([]*Interface, len(obj.interfaceNames))
if err := resolveDirectives(s, obj.Directives, "OBJECT"); err != nil {
return err
}
for _, field := range obj.Fields {
if err := resolveDirectives(s, field.Directives, "FIELD_DEFINITION"); err != nil {
return err
}
}
for i, intfName := range obj.interfaceNames {
t, ok := s.Types[intfName]
if !ok {
Expand All @@ -334,6 +341,9 @@ func (s *Schema) Parse(schemaString string, useStringDescriptions bool) error {
}

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

for _, enum := range s.enums {
if err := resolveDirectives(s, enum.Directives, "ENUM"); err != nil {
return err
}
for _, value := range enum.Values {
if err := resolveDirectives(s, value.Directives); err != nil {
if err := resolveDirectives(s, value.Directives, "ENUM_VALUE"); err != nil {
return err
}
}
Expand Down Expand Up @@ -469,19 +482,29 @@ func resolveField(s *Schema, f *Field) error {
return err
}
f.Type = t
if err := resolveDirectives(s, f.Directives); err != nil {
if err := resolveDirectives(s, f.Directives, "FIELD_DEFINITION"); err != nil {
return err
}
return resolveInputObject(s, f.Args)
}

func resolveDirectives(s *Schema, directives common.DirectiveList) error {
func resolveDirectives(s *Schema, directives common.DirectiveList, loc string) error {
for _, d := range directives {
dirName := d.Name.Name
dd, ok := s.Directives[dirName]
if !ok {
return errors.Errorf("directive %q not found", dirName)
}
validLoc := false
for _, l := range dd.Locs {
if l == loc {
validLoc = true
break
}
}
if !validLoc {
return errors.Errorf("invalid location %q for directive %q (must be one of %v)", loc, dirName, dd.Locs)
}
for _, arg := range d.Args {
if dd.Args.Get(arg.Name.Name) == nil {
return errors.Errorf("invalid argument %q for directive %q", arg.Name.Name, dirName)
Expand Down Expand Up @@ -554,7 +577,8 @@ func parseSchema(s *Schema, l *common.Lexer) {

case "scalar":
name := l.ConsumeIdent()
s.Types[name] = &Scalar{Name: name, Desc: desc}
directives := common.ParseDirectives(l)
s.Types[name] = &Scalar{Name: name, Desc: desc, Directives: directives}

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

if l.Peek() == scanner.Ident {
l.ConsumeKeyword("implements")
for {
if l.Peek() == '{' {
break
}

for l.Peek() != '{' {
if l.Peek() == '&' {
l.ConsumeToken('&')
}
if l.Peek() == '@' {
object.Directives = common.ParseDirectives(l)
continue
}

if l.Peek() == scanner.Ident {
l.ConsumeKeyword("implements")

object.interfaceNames = append(object.interfaceNames, l.ConsumeIdent())
for l.Peek() != '{' && l.Peek() != '@' {
if l.Peek() == '&' {
l.ConsumeToken('&')
}

object.interfaceNames = append(object.interfaceNames, l.ConsumeIdent())
}
continue
}

l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "implements", "directive" or "{"`, l.Peek()))
}

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

i.Directives = common.ParseDirectives(l)
l.ConsumeToken('{')
i.Fields = parseFieldsDef(l)
l.ConsumeToken('}')
Expand All @@ -606,6 +645,7 @@ func parseInterfaceDef(l *common.Lexer) *Interface {
func parseUnionDef(l *common.Lexer) *Union {
union := &Union{Name: l.ConsumeIdent()}

union.Directives = common.ParseDirectives(l)
l.ConsumeToken('=')
union.typeNames = []string{l.ConsumeIdent()}
for l.Peek() == '|' {
Expand All @@ -619,6 +659,7 @@ func parseUnionDef(l *common.Lexer) *Union {
func parseInputDef(l *common.Lexer) *InputObject {
i := &InputObject{}
i.Name = l.ConsumeIdent()
i.Directives = common.ParseDirectives(l)
l.ConsumeToken('{')
for l.Peek() != '}' {
i.Values = append(i.Values, common.ParseInputValue(l))
Expand All @@ -630,6 +671,7 @@ func parseInputDef(l *common.Lexer) *InputObject {
func parseEnumDef(l *common.Lexer) *Enum {
enum := &Enum{Name: l.ConsumeIdent()}

enum.Directives = common.ParseDirectives(l)
l.ConsumeToken('{')
for l.Peek() != '}' {
v := &EnumValue{
Expand Down
71 changes: 71 additions & 0 deletions internal/schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,77 @@ Second line of the description.
return nil
},
},
{
name: "Parses directives",
sdl: `
directive @objectdirective on OBJECT
directive @fielddirective on FIELD_DEFINITION
directive @enumdirective on ENUM
directive @uniondirective on UNION
directive @directive on SCALAR
| OBJECT
| FIELD_DEFINITION
| ARGUMENT_DEFINITION
| INTERFACE
| UNION
| ENUM
| ENUM_VALUE
| INPUT_OBJECT
| INPUT_FIELD_DEFINITION
interface NamedEntity @directive { name: String }
scalar Time @directive
type Photo @objectdirective {
id: ID! @deprecated @fielddirective
}
type Person implements NamedEntity @objectdirective {
name: String
}
enum Direction @enumdirective {
NORTH @deprecated
EAST
SOUTH
WEST
}
union Union @uniondirective = Photo | Person
`,
validateSchema: func(s *schema.Schema) error {
namedEntityDirectives := s.Types["NamedEntity"].(*schema.Interface).Directives
if len(namedEntityDirectives) != 1 || namedEntityDirectives[0].Name.Name != "directive" {
return fmt.Errorf("missing directive on NamedEntity interface, expected @directive but got %v", namedEntityDirectives)
}

timeDirectives := s.Types["Time"].(*schema.Scalar).Directives
if len(timeDirectives) != 1 || timeDirectives[0].Name.Name != "directive" {
return fmt.Errorf("missing directive on Time scalar, expected @directive but got %v", timeDirectives)
}

photo := s.Types["Photo"].(*schema.Object)
photoDirectives := photo.Directives
if len(photoDirectives) != 1 || photoDirectives[0].Name.Name != "objectdirective" {
return fmt.Errorf("missing directive on Time scalar, expected @objectdirective but got %v", photoDirectives)
}
if len(photo.Fields.Get("id").Directives) != 2 {
return fmt.Errorf("expected Photo.id to have 2 directives but got %v", photoDirectives)
}

directionDirectives := s.Types["Direction"].(*schema.Enum).Directives
if len(directionDirectives) != 1 || directionDirectives[0].Name.Name != "enumdirective" {
return fmt.Errorf("missing directive on Direction enum, expected @enumdirective but got %v", directionDirectives)
}

unionDirectives := s.Types["Union"].(*schema.Union).Directives
if len(unionDirectives) != 1 || unionDirectives[0].Name.Name != "uniondirective" {
return fmt.Errorf("missing directive on Union union, expected @uniondirective but got %v", unionDirectives)
}
return nil
},
},
} {
t.Run(test.name, func(t *testing.T) {
s := schema.New()
Expand Down

0 comments on commit dae41bd

Please sign in to comment.