From 0a7a37d1a7a6a8c8e84ac4b3d0f427ea3c55892c Mon Sep 17 00:00:00 2001 From: Richard Musiol Date: Mon, 31 Oct 2016 12:52:31 +0100 Subject: [PATCH] more flexible API for creating a schema --- graphql.go | 44 +++++++++++++++++------ internal/exec/exec.go | 32 ++++------------- internal/exec/introspection.go | 10 +++--- internal/exec/scalar.go | 66 ++++++++++++++++++++++++++++++++++ internal/schema/schema.go | 62 ++++++++++++-------------------- 5 files changed, 132 insertions(+), 82 deletions(-) create mode 100644 internal/exec/scalar.go diff --git a/graphql.go b/graphql.go index 8637a8bfc09..30fa13cb36a 100644 --- a/graphql.go +++ b/graphql.go @@ -13,27 +13,49 @@ import ( "github.com/neelance/graphql-go/internal/schema" ) -type Schema struct { +func ParseSchema(schemaString string, resolver interface{}) (*Schema, error) { + b, err := New().Parse(schemaString) + if err != nil { + return nil, err + } + return b.ApplyResolver(resolver) +} + +type SchemaBuilder struct { schema *schema.Schema - exec *exec.Exec } -func ParseSchema(schemaString string, resolver interface{}) (*Schema, error) { - s, err := schema.Parse(schemaString) - if err != nil { +func New() *SchemaBuilder { + s := schema.New() + exec.AddBuiltinScalars(s) + return &SchemaBuilder{ + schema: s, + } +} + +func (b *SchemaBuilder) Parse(schemaString string) (*SchemaBuilder, error) { + if err := b.schema.Parse(schemaString); err != nil { return nil, err } + return b, nil +} - e, err2 := exec.Make(s, resolver) +func (b *SchemaBuilder) ApplyResolver(resolver interface{}) (*Schema, error) { + e, err2 := exec.Make(b.schema, resolver) if err2 != nil { return nil, err2 } return &Schema{ - schema: s, + schema: b.schema, exec: e, }, nil } +type Schema struct { + schema *schema.Schema + exec *exec.Exec +} + type Response struct { Data interface{} `json:"data,omitempty"` Errors []*errors.QueryError `json:"errors,omitempty"` @@ -70,13 +92,13 @@ func (s *Schema) Exec(ctx context.Context, queryString string, operationName str } func SchemaToJSON(schemaString string) ([]byte, error) { - s, err := schema.Parse(schemaString) - if err != nil { + s := schema.New() + if err := s.Parse(schemaString); err != nil { return nil, err } - result, err2 := exec.IntrospectSchema(s) - if err2 != nil { + result, err := exec.IntrospectSchema(s) + if err != nil { return nil, err } diff --git a/internal/exec/exec.go b/internal/exec/exec.go index 54fc60548c2..0fa5219115a 100644 --- a/internal/exec/exec.go +++ b/internal/exec/exec.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "log" - "math" "reflect" "runtime" "strings" @@ -93,14 +92,6 @@ func makeExec(target *iExec, s *schema.Schema, t common.Type, resolverType refle return nil } -var scalarTypes = map[string]reflect.Type{ - "Int": reflect.TypeOf(int32(0)), - "Float": reflect.TypeOf(float64(0)), - "String": reflect.TypeOf(""), - "Boolean": reflect.TypeOf(true), - "ID": reflect.TypeOf(""), -} - func makeExec2(s *schema.Schema, t common.Type, resolverType reflect.Type, typeRefMap map[typeRefMapKey]*typeRef) (iExec, error) { var nonNull bool t, nonNull = unwrapNonNull(t) @@ -112,11 +103,11 @@ func makeExec2(s *schema.Schema, t common.Type, resolverType reflect.Type, typeR } switch t := t.(type) { - case *schema.Scalar: + case *scalar: if !nonNull { resolverType = resolverType.Elem() } - scalarType := scalarTypes[t.Name] + scalarType := t.reflectType if resolverType != scalarType { return nil, fmt.Errorf("expected %s, got %s", scalarType, resolverType) } @@ -291,8 +282,8 @@ func makeInputObjectExec(s *schema.Schema, obj *common.InputMap, typ reflect.Typ return nil } switch ft := ft.(type) { - case *schema.Scalar: - want := scalarTypes[ft.Name] + case *scalar: + want := ft.reflectType if !nonNull { want = reflect.PtrTo(want) } @@ -300,11 +291,10 @@ func makeInputObjectExec(s *schema.Schema, obj *common.InputMap, typ reflect.Typ return nil, err } fe.exec = &scalarInputExec{ - scalar: ft, nonNull: nonNull, } case *schema.Enum: - want := scalarTypes["String"] + want := reflect.TypeOf("string") if !nonNull { want = reflect.PtrTo(want) } @@ -312,7 +302,6 @@ func makeInputObjectExec(s *schema.Schema, obj *common.InputMap, typ reflect.Typ return nil, err } fe.exec = &scalarInputExec{ - scalar: &schema.Scalar{Name: "String"}, nonNull: nonNull, } case *schema.InputObject: @@ -472,14 +461,8 @@ func coerceInputObject(io *common.InputMap, variables map[string]interface{}) (m 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 *scalar: + return t.coerceInput(value) case *schema.InputObject: return coerceInputObject(&t.InputMap, value.(map[string]interface{})) } @@ -724,7 +707,6 @@ func (e *inputObjectExec) eval(value interface{}) reflect.Value { } type scalarInputExec struct { - scalar *schema.Scalar nonNull bool } diff --git a/internal/exec/introspection.go b/internal/exec/introspection.go index 92389efe85b..f8d4f240715 100644 --- a/internal/exec/introspection.go +++ b/internal/exec/introspection.go @@ -18,12 +18,10 @@ var schemaExec iExec var typeExec iExec func init() { - { - var err *errors.QueryError - metaSchema, err = schema.Parse(metaSchemaSrc) - if err != nil { - panic(err) - } + metaSchema = schema.New() + AddBuiltinScalars(metaSchema) + if err := metaSchema.Parse(metaSchemaSrc); err != nil { + panic(err) } { diff --git a/internal/exec/scalar.go b/internal/exec/scalar.go new file mode 100644 index 00000000000..dbb6cb680e2 --- /dev/null +++ b/internal/exec/scalar.go @@ -0,0 +1,66 @@ +package exec + +import ( + "math" + "reflect" + + "github.com/neelance/graphql-go/errors" + "github.com/neelance/graphql-go/internal/schema" +) + +type scalar struct { + name string + reflectType reflect.Type + coerceInput func(input interface{}) (interface{}, *errors.QueryError) +} + +func (*scalar) Kind() string { return "SCALAR" } +func (t *scalar) TypeName() string { return t.name } + +var builtinScalars = []*scalar{ + &scalar{ + name: "Int", + reflectType: reflect.TypeOf(int32(0)), + coerceInput: func(input interface{}) (interface{}, *errors.QueryError) { + i := input.(int) + if i < math.MinInt32 || i > math.MaxInt32 { + return nil, errors.Errorf("not a 32-bit integer: %d", i) + } + return int32(i), nil + }, + }, + &scalar{ + name: "Float", + reflectType: reflect.TypeOf(float64(0)), + coerceInput: func(input interface{}) (interface{}, *errors.QueryError) { + return input, nil // TODO + }, + }, + &scalar{ + name: "String", + reflectType: reflect.TypeOf(""), + coerceInput: func(input interface{}) (interface{}, *errors.QueryError) { + return input, nil // TODO + }, + }, + &scalar{ + name: "Boolean", + reflectType: reflect.TypeOf(true), + coerceInput: func(input interface{}) (interface{}, *errors.QueryError) { + return input, nil // TODO + }, + }, + &scalar{ + name: "ID", + reflectType: reflect.TypeOf(""), + coerceInput: func(input interface{}) (interface{}, *errors.QueryError) { + return input, nil // TODO + }, + }, +} + +func AddBuiltinScalars(s *schema.Schema) { + for _, scalar := range builtinScalars { + s.Types[scalar.name] = scalar + } +} diff --git a/internal/schema/schema.go b/internal/schema/schema.go index 9f72cc577e4..9b3235c5c97 100644 --- a/internal/schema/schema.go +++ b/internal/schema/schema.go @@ -28,10 +28,6 @@ type NamedType interface { TypeName() string } -type Scalar struct { - Name string -} - type Object struct { Name string Interfaces []*Interface @@ -65,14 +61,12 @@ type InputObject struct { common.InputMap } -func (*Scalar) Kind() string { return "SCALAR" } func (*Object) Kind() string { return "OBJECT" } func (*Interface) Kind() string { return "INTERFACE" } func (*Union) Kind() string { return "UNION" } func (*Enum) Kind() string { return "ENUM" } func (*InputObject) Kind() string { return "INPUT_OBJECT" } -func (t *Scalar) TypeName() string { return t.Name } func (t *Object) TypeName() string { return t.Name } func (t *Interface) TypeName() string { return t.Name } func (t *Union) TypeName() string { return t.Name } @@ -85,23 +79,30 @@ type Field struct { Type common.Type } -func Parse(schemaString string) (s *Schema, err *errors.QueryError) { +func New() *Schema { + return &Schema{ + entryPointNames: make(map[string]string), + Types: make(map[string]NamedType), + } +} + +func (s *Schema) Parse(schemaString string) *errors.QueryError { sc := &scanner.Scanner{ Mode: scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanStrings, } sc.Init(strings.NewReader(schemaString)) l := lexer.New(sc) - err = l.CatchSyntaxError(func() { - s = parseSchema(l) + err := l.CatchSyntaxError(func() { + parseSchema(s, l) }) if err != nil { - return nil, err + return err } for _, t := range s.Types { if err := resolveNamedType(s, t); err != nil { - return nil, err + return err } } @@ -110,7 +111,7 @@ func Parse(schemaString string) (s *Schema, err *errors.QueryError) { t, ok := s.Types[name] if !ok { if !ok { - return nil, errors.Errorf("type %q not found", name) + return errors.Errorf("type %q not found", name) } } s.EntryPoints[key] = t @@ -121,11 +122,11 @@ func Parse(schemaString string) (s *Schema, err *errors.QueryError) { for i, intfName := range obj.interfaceNames { t, ok := s.Types[intfName] if !ok { - return nil, errors.Errorf("interface %q not found", intfName) + return errors.Errorf("interface %q not found", intfName) } intf, ok := t.(*Interface) if !ok { - return nil, errors.Errorf("type %q is not an interface", intfName) + return errors.Errorf("type %q is not an interface", intfName) } obj.Interfaces[i] = intf intf.PossibleTypes = append(intf.PossibleTypes, obj) @@ -137,23 +138,21 @@ func Parse(schemaString string) (s *Schema, err *errors.QueryError) { for i, name := range union.typeNames { t, ok := s.Types[name] if !ok { - return nil, errors.Errorf("object type %q not found", name) + return errors.Errorf("object type %q not found", name) } obj, ok := t.(*Object) if !ok { - return nil, errors.Errorf("type %q is not an object", name) + return errors.Errorf("type %q is not an object", name) } union.PossibleTypes[i] = obj } } - return s, nil + return nil } func resolveNamedType(s *Schema, t NamedType) *errors.QueryError { switch t := t.(type) { - case *Scalar: - // nothing case *Object: for _, f := range t.Fields { if err := resolveField(s, f); err != nil { @@ -166,14 +165,10 @@ func resolveNamedType(s *Schema, t NamedType) *errors.QueryError { return err } } - case *Union: - // nothing - case *Enum: - // nothing case *InputObject: - resolveInputObject(s, &t.InputMap) - default: - panic("unreachable") + if err := resolveInputObject(s, &t.InputMap); err != nil { + return err + } } return nil } @@ -198,18 +193,7 @@ func resolveInputObject(s *Schema, io *common.InputMap) *errors.QueryError { return nil } -func parseSchema(l *lexer.Lexer) *Schema { - s := &Schema{ - entryPointNames: make(map[string]string), - Types: map[string]NamedType{ - "Int": &Scalar{Name: "Int"}, - "Float": &Scalar{Name: "Float"}, - "String": &Scalar{Name: "String"}, - "Boolean": &Scalar{Name: "Boolean"}, - "ID": &Scalar{Name: "ID"}, - }, - } - +func parseSchema(s *Schema, l *lexer.Lexer) { for l.Peek() != scanner.EOF { switch x := l.ConsumeIdent(); x { case "schema": @@ -242,8 +226,6 @@ func parseSchema(l *lexer.Lexer) *Schema { l.SyntaxError(fmt.Sprintf(`unexpected %q, expecting "schema", "type", "enum", "interface", "union" or "input"`, x)) } } - - return s } func parseObjectDecl(l *lexer.Lexer) *Object {