Skip to content

Commit

Permalink
improved type handling
Browse files Browse the repository at this point in the history
  • Loading branch information
neelance committed Oct 30, 2016
1 parent ffa9fea commit bd20a16
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 124 deletions.
8 changes: 5 additions & 3 deletions graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import (
)

type Schema struct {
exec *exec.Exec
schema *schema.Schema
exec *exec.Exec
}

func ParseSchema(schemaString string, resolver interface{}) (*Schema, error) {
Expand All @@ -28,7 +29,8 @@ func ParseSchema(schemaString string, resolver interface{}) (*Schema, error) {
return nil, err2
}
return &Schema{
exec: e,
schema: s,
exec: e,
}, nil
}

Expand All @@ -39,7 +41,7 @@ type Response struct {
}

func (s *Schema) Exec(ctx context.Context, queryString string, operationName string, variables map[string]interface{}) *Response {
document, err := query.Parse(queryString)
document, err := query.Parse(queryString, s.schema.Resolve)
if err != nil {
return &Response{
Errors: []*errors.QueryError{err},
Expand Down
32 changes: 31 additions & 1 deletion internal/common/types.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package common

import "github.com/neelance/graphql-go/internal/lexer"
import (
"github.com/neelance/graphql-go/errors"
"github.com/neelance/graphql-go/internal/lexer"
)

type Type interface {
Kind() string
Expand Down Expand Up @@ -41,3 +44,30 @@ func parseNullType(l *lexer.Lexer) Type {

return &TypeName{Name: l.ConsumeIdent()}
}

type Resolver func(name string) Type

func ResolveType(t Type, resolver Resolver) (Type, *errors.QueryError) {
switch t := t.(type) {
case *List:
ofType, err := ResolveType(t.OfType, resolver)
if err != nil {
return nil, err
}
return &List{OfType: ofType}, nil
case *NonNull:
ofType, err := ResolveType(t.OfType, resolver)
if err != nil {
return nil, err
}
return &NonNull{OfType: ofType}, nil
case *TypeName:
refT := resolver(t.Name)
if refT == nil {
return nil, errors.Errorf("type %q not found", t.Name)
}
return refT, nil
default:
panic("unreachable")
}
}
23 changes: 23 additions & 0 deletions internal/common/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,29 @@ import (
"github.com/neelance/graphql-go/internal/lexer"
)

type InputMap struct {
Fields map[string]*InputValue
FieldOrder []string
}

type InputValue struct {
Name string
Type Type
Default Value
}

func ParseInputValue(l *lexer.Lexer) *InputValue {
p := &InputValue{}
p.Name = l.ConsumeIdent()
l.ConsumeToken(':')
p.Type = ParseType(l)
if l.Peek() == '=' {
l.ConsumeToken('=')
p.Default = ParseValue(l, true)
}
return p
}

type Value interface {
Eval(vars map[string]interface{}) interface{}
}
Expand Down
85 changes: 62 additions & 23 deletions internal/exec/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"log"
"math"
"reflect"
"runtime"
"strings"
Expand Down Expand Up @@ -101,11 +102,8 @@ var scalarTypes = map[string]reflect.Type{
}

func makeExec2(s *schema.Schema, t common.Type, resolverType reflect.Type, typeRefMap map[typeRefMapKey]*typeRef) (iExec, error) {
nonNull := false
if nn, ok := t.(*common.NonNull); ok {
nonNull = true
t = nn.OfType
}
var nonNull bool
t, nonNull = unwrapNonNull(t)

if !nonNull {
if resolverType.Kind() != reflect.Ptr && resolverType.Kind() != reflect.Interface {
Expand Down Expand Up @@ -223,12 +221,12 @@ func makeFieldExec(s *schema.Schema, f *schema.Field, m reflect.Method, methodIn
}

var argsExec *inputObjectExec
if len(f.Args.InputFields) > 0 {
if len(f.Args.Fields) > 0 {
if len(in) == 0 {
return nil, fmt.Errorf("must have parameter for field arguments")
}
var err error
argsExec, err = makeInputObjectExec(&f.Args, in[0])
argsExec, err = makeInputObjectExec(s, &f.Args, in[0])
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -263,15 +261,15 @@ func makeFieldExec(s *schema.Schema, f *schema.Field, m reflect.Method, methodIn
return fe, nil
}

func makeInputObjectExec(obj *schema.InputObject, typ reflect.Type) (*inputObjectExec, error) {
func makeInputObjectExec(s *schema.Schema, obj *common.InputMap, typ reflect.Type) (*inputObjectExec, error) {
if typ.Kind() != reflect.Ptr || typ.Elem().Kind() != reflect.Struct {
return nil, fmt.Errorf("expected pointer to struct, got %s", typ)
}
structType := typ.Elem()

var fields []*inputFieldExec
defaultStruct := reflect.New(structType).Elem()
for _, f := range obj.InputFields {
for _, f := range obj.Fields {
fe := &inputFieldExec{
name: f.Name,
}
Expand All @@ -282,10 +280,8 @@ func makeInputObjectExec(obj *schema.InputObject, typ reflect.Type) (*inputObjec
}
fe.fieldIndex = sf.Index

ft := f.Type
nonNull := (f.Default != nil)
if nn, ok := ft.(*common.NonNull); ok {
ft = nn.OfType
ft, nonNull := unwrapNonNull(f.Type)
if f.Default != nil {
nonNull = true
}
expectType := func(got, want reflect.Type) error {
Expand Down Expand Up @@ -320,7 +316,7 @@ func makeInputObjectExec(obj *schema.InputObject, typ reflect.Type) (*inputObjec
nonNull: nonNull,
}
case *schema.InputObject:
e, err := makeInputObjectExec(ft, sf.Type)
e, err := makeInputObjectExec(s, &ft.InputMap, sf.Type)
if err != nil {
return nil, err
}
Expand All @@ -330,7 +326,7 @@ func makeInputObjectExec(obj *schema.InputObject, typ reflect.Type) (*inputObjec
}

if f.Default != nil {
defaultStruct.FieldByIndex(fe.fieldIndex).Set(fe.exec.eval(f.Default))
defaultStruct.FieldByIndex(fe.fieldIndex).Set(fe.exec.eval(f.Default.Eval(nil)))
}

fields = append(fields, fe)
Expand Down Expand Up @@ -402,9 +398,14 @@ func ExecuteRequest(ctx context.Context, e *Exec, document *query.Document, oper
return nil, []*errors.QueryError{err}
}

coercedVariables, err := coerceInputObject(&op.Vars, variables)
if err != nil {
return nil, []*errors.QueryError{err}
}

r := &request{
doc: document,
vars: variables,
vars: coercedVariables,
schema: e.schema,
}

Expand Down Expand Up @@ -448,6 +449,43 @@ func getOperation(document *query.Document, operationName string) (*query.Operat
return op, nil
}

func coerceInputObject(io *common.InputMap, variables map[string]interface{}) (map[string]interface{}, *errors.QueryError) {
coerced := make(map[string]interface{})
for _, iv := range io.Fields {
value, ok := variables[iv.Name]
if !ok {
if iv.Default == nil {
return nil, errors.Errorf("missing %q", iv.Name)
}
coerced[iv.Name] = iv.Default.Eval(nil)
continue
}
c, err := coerceInputValue(iv, value)
if err != nil {
return nil, err
}
coerced[iv.Name] = c
}
return coerced, nil
}

func coerceInputValue(iv *common.InputValue, value interface{}) (interface{}, *errors.QueryError) {
t, _ := unwrapNonNull(iv.Type)
switch t := t.(type) {
case *schema.Scalar:
if t.Name == "Int" {
i := value.(int)
if i < math.MinInt32 || i > math.MaxInt32 {
return nil, errors.Errorf("not a 32-bit integer: %d", i)
}
return int32(i), nil
}
case *schema.InputObject:
return coerceInputObject(&t.InputMap, value.(map[string]interface{}))
}
return value, nil
}

type iExec interface {
exec(ctx context.Context, r *request, selSet *query.SelectionSet, resolver reflect.Value, serially bool) interface{}
}
Expand Down Expand Up @@ -691,13 +729,7 @@ type scalarInputExec struct {
}

func (e *scalarInputExec) eval(value interface{}) reflect.Value {
var v reflect.Value
switch e.scalar.Name {
case "Int":
v = reflect.ValueOf(int32(value.(int)))
default:
v = reflect.ValueOf(value)
}
v := reflect.ValueOf(value)
if !e.nonNull {
p := reflect.New(v.Type())
p.Elem().Set(v)
Expand All @@ -719,3 +751,10 @@ func skipByDirective(r *request, d map[string]*query.Directive) bool {
}
return false
}

func unwrapNonNull(t common.Type) (common.Type, bool) {
if nn, ok := t.(*common.NonNull); ok {
return nn.OfType, true
}
return t, false
}
16 changes: 8 additions & 8 deletions internal/exec/introspection.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,9 +349,9 @@ func (r *typeResolver) InputFields() *[]*inputValueResolver {
return nil
}

l := make([]*inputValueResolver, len(t.InputFieldOrder))
for i, name := range t.InputFieldOrder {
l[i] = &inputValueResolver{t.InputFields[name]}
l := make([]*inputValueResolver, len(t.FieldOrder))
for i, name := range t.FieldOrder {
l[i] = &inputValueResolver{t.Fields[name]}
}
return &l
}
Expand Down Expand Up @@ -380,9 +380,9 @@ func (r *fieldResolver) Description() *string {
}

func (r *fieldResolver) Args() []*inputValueResolver {
l := make([]*inputValueResolver, len(r.field.Args.InputFieldOrder))
for i, name := range r.field.Args.InputFieldOrder {
l[i] = &inputValueResolver{r.field.Args.InputFields[name]}
l := make([]*inputValueResolver, len(r.field.Args.FieldOrder))
for i, name := range r.field.Args.FieldOrder {
l[i] = &inputValueResolver{r.field.Args.Fields[name]}
}
return l
}
Expand All @@ -400,7 +400,7 @@ func (r *fieldResolver) DeprecationReason() *string {
}

type inputValueResolver struct {
value *schema.InputValue
value *common.InputValue
}

func (r *inputValueResolver) Name() string {
Expand Down Expand Up @@ -466,7 +466,7 @@ var introspectionQuery *query.Document

func init() {
var err *errors.QueryError
introspectionQuery, err = query.Parse(introspectionQuerySrc)
introspectionQuery, err = query.Parse(introspectionQuerySrc, metaSchema.Resolve)
if err != nil {
panic(err)
}
Expand Down
8 changes: 6 additions & 2 deletions internal/lexer/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package lexer

import (
"fmt"
"math"
"strconv"
"text/scanner"

Expand Down Expand Up @@ -71,11 +72,14 @@ func (l *Lexer) ConsumeKeyword(keyword string) {
l.Consume()
}

func (l *Lexer) ConsumeInt() int {
func (l *Lexer) ConsumeInt() int32 {
text := l.sc.TokenText()
l.ConsumeToken(scanner.Int)
value, _ := strconv.Atoi(text)
return value
if value < math.MinInt32 || value > math.MaxInt32 {
l.SyntaxError(fmt.Sprintf("not a 32-bit integer: %d", value))
}
return int32(value)
}

func (l *Lexer) ConsumeFloat() float64 {
Expand Down
Loading

0 comments on commit bd20a16

Please sign in to comment.