Skip to content

Commit

Permalink
Merge branch 'master' into vendor
Browse files Browse the repository at this point in the history
  • Loading branch information
chris-ramon committed Feb 20, 2017
2 parents 0793a37 + b5f1338 commit a9d1bb9
Show file tree
Hide file tree
Showing 7 changed files with 345 additions and 31 deletions.
40 changes: 40 additions & 0 deletions examples/httpdynamic/README.md
@@ -0,0 +1,40 @@
Basically, if we have `data.json` like this:

[
{ "id": "1", "name": "Dan" },
{ "id": "2", "name": "Lee" },
{ "id": "3", "name": "Nick" }
]

... and `go run main.go`, we can query records:

$ curl -g 'http://localhost:8080/graphql?query={user(name:"Dan"){id}}'
{"data":{"user":{"id":"1"}}}

... now let's give Dan a surname:

[
{ "id": "1", "name": "Dan", "surname": "Jones" },
{ "id": "2", "name": "Lee" },
{ "id": "3", "name": "Nick" }
]

... and kick the server:

kill -SIGUSR1 52114

And ask for Dan's surname:

$ curl -g 'http://localhost:8080/graphql?query={user(name:"Dan"){id,surname}}'
{"data":{"user":{"id":"1","surname":"Jones"}}}

... or ask Jones's name and ID:

$ curl -g 'http://localhost:8080/graphql?query={user(surname:"Jones"){id,name}}'
{"data":{"user":{"id":"1","name":"Dan"}}}

If you look at `main.go`, the file is not field-aware. That is, all it knows is
how to work with `[]map[string]string` type.

With this, we are not that far from exposing dynamic fields and filters which
fully depend on what we have stored, all without changing our tooling.
15 changes: 15 additions & 0 deletions examples/httpdynamic/data.json
@@ -0,0 +1,15 @@
[
{
"id": "1",
"name": "Dan",
"surname": "Jones"
},
{
"id": "2",
"name": "Lee"
},
{
"id": "3",
"name": "Nick"
}
]
138 changes: 138 additions & 0 deletions examples/httpdynamic/main.go
@@ -0,0 +1,138 @@
package main

import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"

"github.com/graphql-go/graphql"
)

/*****************************************************************************/
/* Shared data variables to allow dynamic reloads
/*****************************************************************************/

var schema graphql.Schema

const jsonDataFile = "data.json"

func handleSIGUSR1(c chan os.Signal) {
for {
<-c
fmt.Printf("Caught SIGUSR1. Reloading %s\n", jsonDataFile)
err := importJSONDataFromFile(jsonDataFile)
if err != nil {
fmt.Printf("Error: %s\n", err.Error())
return
}
}
}

func filterUser(data []map[string]interface{}, args map[string]interface{}) map[string]interface{} {
for _, user := range data {
for k, v := range args {
if user[k] != v {
goto nextuser
}
return user
}

nextuser:
}
return nil
}

func executeQuery(query string, schema graphql.Schema) *graphql.Result {
result := graphql.Do(graphql.Params{
Schema: schema,
RequestString: query,
})
if len(result.Errors) > 0 {
fmt.Printf("wrong result, unexpected errors: %v\n", result.Errors)
}
return result
}

func importJSONDataFromFile(fileName string) error {
content, err := ioutil.ReadFile(fileName)
if err != nil {
return err
}

var data []map[string]interface{}

err = json.Unmarshal(content, &data)
if err != nil {
return err
}

fields := make(graphql.Fields)
args := make(graphql.FieldConfigArgument)
for _, item := range data {
for k := range item {
fields[k] = &graphql.Field{
Type: graphql.String,
}
args[k] = &graphql.ArgumentConfig{
Type: graphql.String,
}
}
}

var userType = graphql.NewObject(
graphql.ObjectConfig{
Name: "User",
Fields: fields,
},
)

var queryType = graphql.NewObject(
graphql.ObjectConfig{
Name: "Query",
Fields: graphql.Fields{
"user": &graphql.Field{
Type: userType,
Args: args,
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
return filterUser(data, p.Args), nil
},
},
},
})

schema, _ = graphql.NewSchema(
graphql.SchemaConfig{
Query: queryType,
},
)

return nil
}

func main() {
// Catch SIGUSR1 and reload the data file
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGUSR1)
go handleSIGUSR1(c)

err := importJSONDataFromFile(jsonDataFile)
if err != nil {
fmt.Printf("Error: %s\n", err.Error())
return
}

http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) {
result := executeQuery(r.URL.Query()["query"][0], schema)
json.NewEncoder(w).Encode(result)
})

fmt.Println("Now server is running on port 8080")
fmt.Println("Test with Get : curl -g 'http://localhost:8080/graphql?query={user(name:\"Dan\"){id,surname}}'")
fmt.Printf("Reload json file : kill -SIGUSR1 %s\n", strconv.Itoa(os.Getpid()))
http.ListenAndServe(":8080", nil)
}
3 changes: 3 additions & 0 deletions examples/todo/README.md
Expand Up @@ -25,4 +25,7 @@ curl -g 'http://localhost:8080/graphql?query=mutation+_{createTodo(text:"My+new+
// To get a list of ToDo items
curl -g 'http://localhost:8080/graphql?query={todoList{id,text,done}}'
// To update a ToDo
curl -g 'http://localhost:8080/graphql?query=mutation+_{updateTodo(id:"b",text:"My+new+todo+updated",done:true){id,text,done}}'
```
36 changes: 34 additions & 2 deletions examples/todo/main.go
Expand Up @@ -2,6 +2,7 @@ package main

import (
"encoding/json"
"errors"
"fmt"
"math/rand"
"net/http"
Expand Down Expand Up @@ -95,6 +96,38 @@ var rootMutation = graphql.NewObject(graphql.ObjectConfig{
return newTodo, nil
},
},
/*
curl -g 'http://localhost:8080/graphql?query=mutation+_{updateTodo(text:"My+new+todo+updated"){id,text,done}}'
*/
"updateTodo": &graphql.Field{
Type: todoType,
Args: graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
"text": &graphql.ArgumentConfig{
Type: graphql.String,
},
"done": &graphql.ArgumentConfig{
Type: graphql.Boolean,
},
},
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
if idQuery, ok := p.Args["id"].(string); ok {
for _, todo := range TodoList {
if todo.ID == idQuery {
text, _ := p.Args["text"].(string)
done, _ := p.Args["done"].(bool)
todo.Text = text
todo.Done = done
return todo, nil
}
}
return nil, errors.New("could not find todo with that ID")
}
return nil, errors.New("could not get id from params")
},
},
},
})

Expand Down Expand Up @@ -171,15 +204,14 @@ func executeQuery(query string, schema graphql.Schema) *graphql.Result {
}

func main() {

http.HandleFunc("/graphql", func(w http.ResponseWriter, r *http.Request) {
result := executeQuery(r.URL.Query()["query"][0], schema)
json.NewEncoder(w).Encode(result)
})

fmt.Println("Now server is running on port 8080")
fmt.Println("Get single todo: curl -g 'http://localhost:8080/graphql?query={todo(id:\"b\"){id,text,done}}'")
fmt.Println("Create new todo: curl -g 'http://localhost:8080/graphql?query=mutation+_{createTodo(text:\"My+new+todo\"){id,text,done}}'")
fmt.Println("Update a todo: curl -g 'http://localhost:8080/graphql?query=mutation+_{updateTodo(id:\"b\",text:\"My+new+todo+updated\",done:true){id,text,done}}'")
fmt.Println("Load todo list: curl -g 'http://localhost:8080/graphql?query={todoList{id,text,done}}'")
http.ListenAndServe(":8080", nil)
}
90 changes: 61 additions & 29 deletions executor.go
Expand Up @@ -24,40 +24,72 @@ type ExecuteParams struct {
}

func Execute(p ExecuteParams) (result *Result) {
result = &Result{}

exeContext, err := buildExecutionContext(BuildExecutionCtxParams{
Schema: p.Schema,
Root: p.Root,
AST: p.AST,
OperationName: p.OperationName,
Args: p.Args,
Errors: nil,
Result: result,
Context: p.Context,
})

if err != nil {
result.Errors = append(result.Errors, gqlerrors.FormatError(err))
return
// Use background context if no context was provided
ctx := p.Context
if ctx == nil {
ctx = context.Background()
}

defer func() {
if r := recover(); r != nil {
var err error
if r, ok := r.(error); ok {
err = gqlerrors.FormatError(r)
resultChannel := make(chan *Result)

go func(out chan<- *Result, done <-chan struct{}) {
result := &Result{}

exeContext, err := buildExecutionContext(BuildExecutionCtxParams{
Schema: p.Schema,
Root: p.Root,
AST: p.AST,
OperationName: p.OperationName,
Args: p.Args,
Errors: nil,
Result: result,
Context: p.Context,
})

if err != nil {
result.Errors = append(result.Errors, gqlerrors.FormatError(err))
select {
case out <- result:
case <-done:
}
exeContext.Errors = append(exeContext.Errors, gqlerrors.FormatError(err))
result.Errors = exeContext.Errors
return
}
}()

return executeOperation(ExecuteOperationParams{
ExecutionContext: exeContext,
Root: p.Root,
Operation: exeContext.Operation,
})
defer func() {
if r := recover(); r != nil {
var err error
if r, ok := r.(error); ok {
err = gqlerrors.FormatError(r)
}
exeContext.Errors = append(exeContext.Errors, gqlerrors.FormatError(err))
result.Errors = exeContext.Errors
select {
case out <- result:
case <-done:
}
}
}()

result = executeOperation(ExecuteOperationParams{
ExecutionContext: exeContext,
Root: p.Root,
Operation: exeContext.Operation,
})
select {
case out <- result:
case <-done:
}

}(resultChannel, ctx.Done())

select {
case <-ctx.Done():
result = &Result{}
result.Errors = append(result.Errors, gqlerrors.FormatError(ctx.Err()))
case r := <-resultChannel:
result = r
}
return
}

type BuildExecutionCtxParams struct {
Expand Down

0 comments on commit a9d1bb9

Please sign in to comment.