From 10dc8ee62965c02c6bca323fd6516814fdadb303 Mon Sep 17 00:00:00 2001 From: Richard Musiol Date: Fri, 10 Mar 2017 15:52:56 +0100 Subject: [PATCH] added support for directive declarations in schema --- graphql_test.go | 12 +++--- internal/schema/meta.go | 13 +++++++ internal/schema/schema.go | 52 ++++++++++++++++++++++++- introspection/introspection.go | 69 ++++++++++++++-------------------- 4 files changed, 99 insertions(+), 47 deletions(-) diff --git a/graphql_test.go b/graphql_test.go index 66b8047785b..9fcfd214eef 100644 --- a/graphql_test.go +++ b/graphql_test.go @@ -1135,8 +1135,8 @@ func TestIntrospection(t *testing.T) { "__schema": { "directives": [ { - "name": "skip", - "description": "Directs the executor to skip this field or fragment when the ` + "`" + `if` + "`" + ` argument is true.", + "name": "include", + "description": "Directs the executor to include this field or fragment only when the ` + "`" + `if` + "`" + ` argument is true.", "locations": [ "FIELD", "FRAGMENT_SPREAD", @@ -1145,7 +1145,7 @@ func TestIntrospection(t *testing.T) { "args": [ { "name": "if", - "description": "Skipped when true.", + "description": "Included when true.", "type": { "kind": "NON_NULL", "ofType": { @@ -1157,8 +1157,8 @@ func TestIntrospection(t *testing.T) { ] }, { - "name": "include", - "description": "Directs the executor to include this field or fragment only when the ` + "`" + `if` + "`" + ` argument is true.", + "name": "skip", + "description": "Directs the executor to skip this field or fragment when the ` + "`" + `if` + "`" + ` argument is true.", "locations": [ "FIELD", "FRAGMENT_SPREAD", @@ -1167,7 +1167,7 @@ func TestIntrospection(t *testing.T) { "args": [ { "name": "if", - "description": "Included when true.", + "description": "Skipped when true.", "type": { "kind": "NON_NULL", "ofType": { diff --git a/internal/schema/meta.go b/internal/schema/meta.go index f456c224505..6673049f6e5 100644 --- a/internal/schema/meta.go +++ b/internal/schema/meta.go @@ -12,6 +12,7 @@ func init() { "Boolean": &Scalar{"Boolean", "The `Boolean` scalar type represents `true` or `false`."}, "ID": &Scalar{"ID", "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID."}, }, + Directives: make(map[string]*Directive), } if err := Meta.Parse(metaSrc); err != nil { panic(err) @@ -19,6 +20,18 @@ func init() { } var metaSrc = ` + # Directs the executor to include this field or fragment only when the ` + "`" + `if` + "`" + ` argument is true. + directive @include( + # Included when true. + if: Boolean! + ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + + # Directs the executor to skip this field or fragment when the ` + "`" + `if` + "`" + ` argument is true. + directive @skip( + # Skipped when true. + if: Boolean! + ) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT + # A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document. # # In some cases, you need to provide options to alter GraphQL's execution behavior diff --git a/internal/schema/schema.go b/internal/schema/schema.go index a691f9df845..b36d22c5217 100644 --- a/internal/schema/schema.go +++ b/internal/schema/schema.go @@ -13,6 +13,7 @@ import ( type Schema struct { EntryPoints map[string]NamedType Types map[string]NamedType + Directives map[string]*Directive entryPointNames map[string]string objects []*Object @@ -77,6 +78,14 @@ type InputObject struct { common.InputMap } +type Directive struct { + Name string + Desc string + Locs []string + Args map[string]*common.InputValue + ArgOrder []string +} + func (*Scalar) Kind() string { return "SCALAR" } func (*Object) Kind() string { return "OBJECT" } func (*Interface) Kind() string { return "INTERFACE" } @@ -116,10 +125,14 @@ func New() *Schema { s := &Schema{ entryPointNames: make(map[string]string), Types: make(map[string]NamedType), + Directives: make(map[string]*Directive), } for n, t := range Meta.Types { s.Types[n] = t } + for n, d := range Meta.Directives { + s.Directives[n] = d + } return s } @@ -142,6 +155,15 @@ func (s *Schema) Parse(schemaString string) error { return err } } + for _, d := range s.Directives { + for _, arg := range d.Args { + t, err := common.ResolveType(arg.Type, s.Resolve) + if err != nil { + return err + } + arg.Type = t + } + } s.EntryPoints = make(map[string]NamedType) for key, name := range s.entryPointNames { @@ -268,8 +290,12 @@ func parseSchema(s *Schema, l *lexer.Lexer) { case "scalar": name := l.ConsumeIdent() s.Types[name] = &Scalar{Name: name, Desc: desc} + case "directive": + directive := parseDirectiveDecl(l) + directive.Desc = desc + s.Directives[directive.Name] = directive default: - l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "schema", "type", "enum", "interface", "union", "input" or "scalar"`, x)) + l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "schema", "type", "enum", "interface", "union", "input", "scalar" or "directive"`, x)) } } } @@ -341,6 +367,30 @@ func parseEnumDecl(l *lexer.Lexer) *Enum { return enum } +func parseDirectiveDecl(l *lexer.Lexer) *Directive { + d := &Directive{} + d.Args = make(map[string]*common.InputValue) + l.ConsumeToken('@') + d.Name = l.ConsumeIdent() + l.ConsumeToken('(') + for l.Peek() != ')' { + v := common.ParseInputValue(l) + d.Args[v.Name] = v + d.ArgOrder = append(d.ArgOrder, v.Name) + } + l.ConsumeToken(')') + l.ConsumeKeyword("on") + for { + loc := l.ConsumeIdent() + d.Locs = append(d.Locs, loc) + if l.Peek() != '|' { + break + } + l.ConsumeToken('|') + } + return d +} + func parseFields(l *lexer.Lexer) (map[string]*Field, []string) { fields := make(map[string]*Field) var fieldOrder []string diff --git a/introspection/introspection.go b/introspection/introspection.go index 320ad25bb0c..061ab3b52e0 100644 --- a/introspection/introspection.go +++ b/introspection/introspection.go @@ -24,9 +24,23 @@ func (r *Schema) Types() []*Type { } sort.Strings(names) - var l []*Type - for _, name := range names { - l = append(l, &Type{r.schema.Types[name]}) + l := make([]*Type, len(names)) + for i, name := range names { + l[i] = &Type{r.schema.Types[name]} + } + return l +} + +func (r *Schema) Directives() []*Directive { + var names []string + for name := range r.schema.Directives { + names = append(names, name) + } + sort.Strings(names) + + l := make([]*Directive, len(names)) + for i, name := range names { + l[i] = &Directive{r.schema.Directives[name]} } return l } @@ -55,35 +69,6 @@ func (r *Schema) SubscriptionType() *Type { return &Type{t} } -func (r *Schema) Directives() []*Directive { - return []*Directive{ - { - name: "skip", - description: "Directs the executor to skip this field or fragment when the `if` argument is true.", - locations: []string{"FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"}, - args: []*InputValue{ - {&common.InputValue{ - Name: "if", - Desc: "Skipped when true.", - Type: &common.NonNull{OfType: r.schema.Types["Boolean"]}, - }}, - }, - }, - { - name: "include", - description: "Directs the executor to include this field or fragment only when the `if` argument is true.", - locations: []string{"FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"}, - args: []*InputValue{ - {&common.InputValue{ - Name: "if", - Desc: "Included when true.", - Type: &common.NonNull{OfType: r.schema.Types["Boolean"]}, - }}, - }, - }, - } -} - type Type struct { typ common.Type } @@ -291,24 +276,28 @@ func (r *EnumValue) DeprecationReason() *string { } type Directive struct { - name string - description string - locations []string - args []*InputValue + directive *schema.Directive } func (r *Directive) Name() string { - return r.name + return r.directive.Name } func (r *Directive) Description() *string { - return &r.description + if r.directive.Desc == "" { + return nil + } + return &r.directive.Desc } func (r *Directive) Locations() []string { - return r.locations + return r.directive.Locs } func (r *Directive) Args() []*InputValue { - return r.args + l := make([]*InputValue, len(r.directive.ArgOrder)) + for i, name := range r.directive.ArgOrder { + l[i] = &InputValue{r.directive.Args[name]} + } + return l }