Skip to content

Commit

Permalink
schema package
Browse files Browse the repository at this point in the history
  • Loading branch information
neelance committed Oct 13, 2016
1 parent 1ae71ba commit 565e59f
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 116 deletions.
143 changes: 30 additions & 113 deletions graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,128 +2,56 @@ package graphql

import (
"encoding/json"
"errors"
"reflect"
"strings"
"text/scanner"

"github.com/neelance/graphql-go/internal/lexer"
"github.com/neelance/graphql-go/internal/query"
"github.com/neelance/graphql-go/internal/schema"
)

type Schema struct {
types map[string]*object
*schema.Schema
resolver reflect.Value
}

type typ interface {
exec(schema *Schema, sel *query.SelectionSet, resolver reflect.Value) interface{}
}

type scalar struct {
}

type typeName struct {
name string
}

type object struct {
fields map[string]typ
}

func NewSchema(schema string, filename string, resolver interface{}) (res *Schema, errRes error) {
sc := &scanner.Scanner{}
sc.Filename = filename
sc.Init(strings.NewReader(schema))

defer func() {
if err := recover(); err != nil {
if err, ok := err.(lexer.SyntaxError); ok {
errRes = errors.New(string(err))
return
}
panic(err)
}
}()

s := parseSchema(lexer.New(sc))
s.resolver = reflect.ValueOf(resolver)
// TODO type check resolver
return s, nil
}

func parseSchema(l *lexer.Lexer) *Schema {
s := &Schema{
types: make(map[string]*object),
func NewSchema(schemaString string, filename string, resolver interface{}) (res *Schema, errRes error) {
s, err := schema.Parse(schemaString, filename)
if err != nil {
return nil, err
}

for l.Peek() != scanner.EOF {
switch l.ConsumeIdent() {
case "type":
name, obj := parseTypeDecl(l)
s.types[name] = obj
default:
l.SyntaxError(`"type"`)
}
}

return s
}

func parseTypeDecl(l *lexer.Lexer) (string, *object) {
typeName := l.ConsumeIdent()
l.ConsumeToken('{')

o := &object{
fields: make(map[string]typ),
}
for l.Peek() != '}' {
fieldName := l.ConsumeIdent()
l.ConsumeToken(':')
o.fields[fieldName] = parseType(l)
}
l.ConsumeToken('}')

return typeName, o
// TODO type check resolver
return &Schema{
Schema: s,
resolver: reflect.ValueOf(resolver),
}, nil
}

func parseType(l *lexer.Lexer) typ {
// TODO check args
// TODO check return type
name := l.ConsumeIdent()
if name == "String" {
return &scalar{}
}
return &typeName{
name: name,
func (s *Schema) Exec(queryString string) (res []byte, errRes error) {
q, err := query.Parse(queryString)
if err != nil {
return nil, err
}
}

func (s *Schema) Exec(queryInput string) (res []byte, errRes error) {
sc := &scanner.Scanner{}
sc.Init(strings.NewReader(queryInput))

defer func() {
if err := recover(); err != nil {
if err, ok := err.(lexer.SyntaxError); ok {
errRes = errors.New(string(err))
return
}
panic(err)
}
}()

rawRes := s.types["Query"].exec(s, query.Parse(lexer.New(sc)), s.resolver)
rawRes := exec(s, s.Types["Query"], q, s.resolver)
return json.Marshal(rawRes)
}

func (o *object) exec(schema *Schema, sel *query.SelectionSet, resolver reflect.Value) interface{} {
res := make(map[string]interface{})
for _, f := range sel.Selections {
m := findMethod(resolver.Type(), f.Name)
res[f.Name] = o.fields[f.Name].exec(schema, f.Sel, resolver.Method(m).Call(nil)[0])
func exec(s *Schema, t schema.Type, sel *query.SelectionSet, resolver reflect.Value) interface{} {
switch t := t.(type) {
case *schema.Scalar:
return resolver.Interface()
case *schema.TypeName:
return exec(s, s.Types[t.Name], sel, resolver)
case *schema.Object:
res := make(map[string]interface{})
for _, f := range sel.Selections {
m := findMethod(resolver.Type(), f.Name)
res[f.Name] = exec(s, t.Fields[f.Name], f.Sel, resolver.Method(m).Call(nil)[0])
}
return res
}
return res
return nil
}

func findMethod(t reflect.Type, name string) int {
Expand All @@ -134,14 +62,3 @@ func findMethod(t reflect.Type, name string) int {
}
return -1
}

func (s *scalar) exec(schema *Schema, sel *query.SelectionSet, resolver reflect.Value) interface{} {
if !resolver.IsValid() {
return "bad"
}
return resolver.Interface()
}

func (s *typeName) exec(schema *Schema, sel *query.SelectionSet, resolver reflect.Value) interface{} {
return schema.types[s.name].exec(schema, sel, resolver)
}
25 changes: 22 additions & 3 deletions internal/query/query.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
package query

import "github.com/neelance/graphql-go/internal/lexer"
import (
"errors"
"strings"
"text/scanner"

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

type SelectionSet struct {
Selections []*Field
}

func Parse(l *lexer.Lexer) *SelectionSet {
return parseSelectionSet(l)
func Parse(queryString string) (res *SelectionSet, errRes error) {
sc := &scanner.Scanner{}
sc.Init(strings.NewReader(queryString))

defer func() {
if err := recover(); err != nil {
if err, ok := err.(lexer.SyntaxError); ok {
errRes = errors.New(string(err))
return
}
panic(err)
}
}()

return parseSelectionSet(lexer.New(sc)), nil
}

func parseSelectionSet(l *lexer.Lexer) *SelectionSet {
Expand Down
91 changes: 91 additions & 0 deletions internal/schema/schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package schema

import (
"errors"
"strings"
"text/scanner"

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

type Schema struct {
Types map[string]*Object
}

type Type interface{}

type Scalar struct {
}

type TypeName struct {
Name string
}

type Object struct {
Fields map[string]Type
}

func Parse(schemaString string, filename string) (res *Schema, errRes error) {
sc := &scanner.Scanner{}
sc.Filename = filename
sc.Init(strings.NewReader(schemaString))

defer func() {
if err := recover(); err != nil {
if err, ok := err.(lexer.SyntaxError); ok {
errRes = errors.New(string(err))
return
}
panic(err)
}
}()

return parseSchema(lexer.New(sc)), nil
}

func parseSchema(l *lexer.Lexer) *Schema {
s := &Schema{
Types: make(map[string]*Object),
}

for l.Peek() != scanner.EOF {
switch l.ConsumeIdent() {
case "type":
name, obj := parseTypeDecl(l)
s.Types[name] = obj
default:
l.SyntaxError(`"type"`)
}
}

return s
}

func parseTypeDecl(l *lexer.Lexer) (string, *Object) {
typeName := l.ConsumeIdent()
l.ConsumeToken('{')

o := &Object{
Fields: make(map[string]Type),
}
for l.Peek() != '}' {
fieldName := l.ConsumeIdent()
l.ConsumeToken(':')
o.Fields[fieldName] = parseType(l)
}
l.ConsumeToken('}')

return typeName, o
}

func parseType(l *lexer.Lexer) Type {
// TODO check args
// TODO check return type
name := l.ConsumeIdent()
if name == "String" {
return &Scalar{}
}
return &TypeName{
Name: name,
}
}

0 comments on commit 565e59f

Please sign in to comment.