Skip to content

Commit

Permalink
added support for directives
Browse files Browse the repository at this point in the history
  • Loading branch information
neelance committed Oct 17, 2016
1 parent 89b0665 commit e4060db
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 40 deletions.
74 changes: 50 additions & 24 deletions graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,39 +87,65 @@ func execSelectionSet(s *Schema, r *request, t *schema.Object, selSet *query.Sel
for _, sel := range selSet.Selections {
switch sel := sel.(type) {
case *query.Field:
sf := t.Fields[sel.Name]
m := resolver.Method(findMethod(resolver.Type(), sel.Name))
var in []reflect.Value
if len(sf.Parameters) != 0 {
args := reflect.New(m.Type().In(0))
for name, param := range sf.Parameters {
value, ok := sel.Arguments[name]
if !ok {
value = &query.Literal{Value: param.Default}
}
rf := args.Elem().FieldByNameFunc(func(n string) bool { return strings.EqualFold(n, name) })
switch v := value.(type) {
case *query.Variable:
rf.Set(reflect.ValueOf(r.Variables[v.Name]))
case *query.Literal:
rf.Set(reflect.ValueOf(v.Value))
default:
panic("invalid value")
}
}
in = []reflect.Value{args.Elem()}
if skipByDirective(r, sel.Directives) {
continue
}
result[sel.Alias] = exec(s, r, sf.Type, sel.SelSet, m.Call(in)[0])

execField(s, r, t, sel, resolver, result)
case *query.FragmentSpread:
if skipByDirective(r, sel.Directives) {
continue
}
execSelectionSet(s, r, t, r.Fragments[sel.Name].SelSet, resolver, result)

default:
panic("invalid type")
}
}
}

func execField(s *Schema, r *request, t *schema.Object, f *query.Field, resolver reflect.Value, result map[string]interface{}) {
sf := t.Fields[f.Name]
m := resolver.Method(findMethod(resolver.Type(), f.Name))
var in []reflect.Value
if len(sf.Parameters) != 0 {
args := reflect.New(m.Type().In(0))
for name, param := range sf.Parameters {
value, ok := f.Arguments[name]
if !ok {
value = &query.Literal{Value: param.Default}
}
rf := args.Elem().FieldByNameFunc(func(n string) bool { return strings.EqualFold(n, name) })
rf.Set(reflect.ValueOf(execValue(r, value)))
}
in = []reflect.Value{args.Elem()}
}
result[f.Alias] = exec(s, r, sf.Type, f.SelSet, m.Call(in)[0])
}

func skipByDirective(r *request, d map[string]*query.Directive) bool {
if skip, ok := d["skip"]; ok {
if execValue(r, skip.Arguments["if"]).(bool) {
return true
}
}
if include, ok := d["include"]; ok {
if !execValue(r, include.Arguments["if"]).(bool) {
return true
}
}
return false
}

func execValue(r *request, v query.Value) interface{} {
switch v := v.(type) {
case *query.Variable:
return r.Variables[v.Name]
case *query.Literal:
return v.Value
default:
panic("invalid value")
}
}

func findMethod(t reflect.Type, name string) int {
for i := 0; i < t.NumMethod(); i++ {
if strings.EqualFold(name, t.Method(i).Name) {
Expand Down
138 changes: 138 additions & 0 deletions graphql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,144 @@ var tests = []struct {
}
`,
},

{
name: "StarWarsInclude1",
schema: starWarsSchema,
resolver: &starWarsResolver{},
query: `
query Hero($episode: Episode, $withoutFriends: Boolean!) {
hero(episode: $episode) {
name
friends @skip(if: $withoutFriends) {
name
}
}
}
`,
variables: map[string]interface{}{
"episode": "JEDI",
"withoutFriends": true,
},
result: `
{
"hero": {
"name": "R2-D2"
}
}
`,
},

{
name: "StarWarsInclude2",
schema: starWarsSchema,
resolver: &starWarsResolver{},
query: `
query Hero($episode: Episode, $withoutFriends: Boolean!) {
hero(episode: $episode) {
name
friends @skip(if: $withoutFriends) {
name
}
}
}
`,
variables: map[string]interface{}{
"episode": "JEDI",
"withoutFriends": false,
},
result: `
{
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
`,
},

{
name: "StarWarsSkip1",
schema: starWarsSchema,
resolver: &starWarsResolver{},
query: `
query Hero($episode: Episode, $withFriends: Boolean!) {
hero(episode: $episode) {
name
...friendsFragment @include(if: $withFriends)
}
}
fragment friendsFragment on Character {
friends {
name
}
}
`,
variables: map[string]interface{}{
"episode": "JEDI",
"withFriends": false,
},
result: `
{
"hero": {
"name": "R2-D2"
}
}
`,
},

{
name: "StarWarsSkip2",
schema: starWarsSchema,
resolver: &starWarsResolver{},
query: `
query Hero($episode: Episode, $withFriends: Boolean!) {
hero(episode: $episode) {
name
...friendsFragment @include(if: $withFriends)
}
}
fragment friendsFragment on Character {
friends {
name
}
}
`,
variables: map[string]interface{}{
"episode": "JEDI",
"withFriends": true,
},
result: `
{
"hero": {
"name": "R2-D2",
"friends": [
{
"name": "Luke Skywalker"
},
{
"name": "Han Solo"
},
{
"name": "Leia Organa"
}
]
}
}
`,
},
}

func TestAll(t *testing.T) {
Expand Down
67 changes: 51 additions & 16 deletions internal/query/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,21 @@ type Selection interface {
}

type Field struct {
Alias string
Alias string
Name string
Arguments map[string]Value
Directives map[string]*Directive
SelSet *SelectionSet
}

type Directive struct {
Name string
Arguments map[string]Value
SelSet *SelectionSet
}

type FragmentSpread struct {
Name string
Name string
Directives map[string]*Directive
}

func (Field) isSelection() {}
Expand Down Expand Up @@ -184,7 +191,7 @@ func parseSelection(l *lexer.Lexer) Selection {

func parseField(l *lexer.Lexer) *Field {
f := &Field{
Arguments: make(map[string]Value),
Directives: make(map[string]*Directive),
}
f.Alias = l.ConsumeIdent()
f.Name = f.Alias
Expand All @@ -193,29 +200,57 @@ func parseField(l *lexer.Lexer) *Field {
f.Name = l.ConsumeIdent()
}
if l.Peek() == '(' {
l.ConsumeToken('(')
if l.Peek() != ')' {
name, value := parseArgument(l)
f.Arguments[name] = value
for l.Peek() != ')' {
l.ConsumeToken(',')
name, value := parseArgument(l)
f.Arguments[name] = value
}
}
l.ConsumeToken(')')
f.Arguments = parseArguments(l)
}
for l.Peek() == '@' {
d := parseDirective(l)
f.Directives[d.Name] = d
}
if l.Peek() == '{' {
f.SelSet = parseSelectionSet(l)
}
return f
}

func parseArguments(l *lexer.Lexer) map[string]Value {
args := make(map[string]Value)
l.ConsumeToken('(')
if l.Peek() != ')' {
name, value := parseArgument(l)
args[name] = value
for l.Peek() != ')' {
l.ConsumeToken(',')
name, value := parseArgument(l)
args[name] = value
}
}
l.ConsumeToken(')')
return args
}

func parseDirective(l *lexer.Lexer) *Directive {
d := &Directive{}
l.ConsumeToken('@')
d.Name = l.ConsumeIdent()
if l.Peek() == '(' {
d.Arguments = parseArguments(l)
}
return d
}

func parseFragmentSpread(l *lexer.Lexer) *FragmentSpread {
fs := &FragmentSpread{
Directives: make(map[string]*Directive),
}
l.ConsumeToken('.')
l.ConsumeToken('.')
l.ConsumeToken('.')
return &FragmentSpread{Name: l.ConsumeIdent()}
fs.Name = l.ConsumeIdent()
for l.Peek() == '@' {
d := parseDirective(l)
fs.Directives[d.Name] = d
}
return fs
}

func parseArgument(l *lexer.Lexer) (string, Value) {
Expand Down

0 comments on commit e4060db

Please sign in to comment.