Skip to content

Commit

Permalink
added support for directive declarations in schema
Browse files Browse the repository at this point in the history
  • Loading branch information
neelance committed Mar 10, 2017
1 parent 28028f6 commit 10dc8ee
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 47 deletions.
12 changes: 6 additions & 6 deletions graphql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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": {
Expand All @@ -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",
Expand All @@ -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": {
Expand Down
13 changes: 13 additions & 0 deletions internal/schema/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,26 @@ 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)
}
}

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
Expand Down
52 changes: 51 additions & 1 deletion internal/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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" }
Expand Down Expand Up @@ -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
}

Expand All @@ -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 {
Expand Down Expand Up @@ -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))
}
}
}
Expand Down Expand Up @@ -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
Expand Down
69 changes: 29 additions & 40 deletions introspection/introspection.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}

0 comments on commit 10dc8ee

Please sign in to comment.