-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
582 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
|
||
schema { | ||
query: Query | ||
mutation: Mutation | ||
} | ||
|
||
type Query { | ||
todo(id: Integer!): Todo | ||
lastTodo: Todo | ||
todos: [Todos!]! | ||
} | ||
|
||
type Mutation { | ||
createTodo(text: String!): Todo! | ||
updateTodo(id: Integer!, text: String!): Todo! | ||
} | ||
|
||
type Todo @go(type:"github.com/99designs/graphql-go/example/todo.Todo") { | ||
id: ID! | ||
text: String! | ||
done: Boolean! | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"log" | ||
"net/http" | ||
|
||
"fmt" | ||
|
||
"github.com/vektah/graphql-go/example/todo" | ||
"github.com/vektah/graphql-go/example/todo/todoresolver" | ||
) | ||
|
||
func main() { | ||
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.Write(page) | ||
})) | ||
|
||
resolver := todo.NewResolver() | ||
|
||
http.HandleFunc("/query", func(w http.ResponseWriter, r *http.Request) { | ||
var params struct { | ||
Query string `json:"query"` | ||
OperationName string `json:"operationName"` | ||
Variables map[string]interface{} `json:"variables"` | ||
} | ||
if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { | ||
http.Error(w, err.Error(), http.StatusBadRequest) | ||
return | ||
} | ||
|
||
response := todoresolver.ExecuteRequest(resolver, params.Query, params.OperationName, params.Variables) | ||
fmt.Println(string(response.Data)) | ||
responseJSON, err := json.Marshal(response) | ||
if err != nil { | ||
http.Error(w, err.Error(), http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
w.Header().Set("Content-Type", "application/json") | ||
w.Write(responseJSON) | ||
}) | ||
|
||
log.Fatal(http.ListenAndServe(":8080", nil)) | ||
} | ||
|
||
var page = []byte(` | ||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.10.2/graphiql.css" /> | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/1.1.0/fetch.min.js"></script> | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.4/react.min.js"></script> | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.4/react-dom.min.js"></script> | ||
<script src="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.10.2/graphiql.js"></script> | ||
</head> | ||
<body style="width: 100%; height: 100%; margin: 0; overflow: hidden;"> | ||
<div id="graphiql" style="height: 100vh;">Loading...</div> | ||
<script> | ||
function graphQLFetcher(graphQLParams) { | ||
return fetch("/query", { | ||
method: "post", | ||
body: JSON.stringify(graphQLParams), | ||
credentials: "include", | ||
}).then(function (response) { | ||
return response.text(); | ||
}).then(function (responseBody) { | ||
try { | ||
return JSON.parse(responseBody); | ||
} catch (error) { | ||
return responseBody; | ||
} | ||
}); | ||
} | ||
ReactDOM.render( | ||
React.createElement(GraphiQL, {fetcher: graphQLFetcher}), | ||
document.getElementById("graphiql") | ||
); | ||
</script> | ||
</body> | ||
</html> | ||
`) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
////go:generate graphgen -schema ./schema.graphql | ||
// | ||
package todo | ||
|
||
import ( | ||
"errors" | ||
) | ||
|
||
type Todo struct { | ||
ID int | ||
Text string | ||
Done bool | ||
UserID int | ||
} | ||
|
||
type TodoResolver struct { | ||
todos []*Todo | ||
lastID int | ||
} | ||
|
||
func NewResolver() *TodoResolver { | ||
return &TodoResolver{ | ||
todos: []*Todo{ | ||
{ID: 1, Text: "A todo not to forget", Done: false, UserID: 1}, | ||
{ID: 2, Text: "This is the most important", Done: false, UserID: 1}, | ||
{ID: 3, Text: "Please do this or else", Done: false, UserID: 1}, | ||
}, | ||
lastID: 3, | ||
} | ||
} | ||
|
||
func (r *TodoResolver) Query_todo(id int) (*Todo, error) { | ||
for _, todo := range r.todos { | ||
if todo.ID == id { | ||
return todo, nil | ||
} | ||
} | ||
return nil, errors.New("not found") | ||
} | ||
|
||
func (r *TodoResolver) Query_lastTodo() (*Todo, error) { | ||
if len(r.todos) == 0 { | ||
return nil, errors.New("not found") | ||
} | ||
return r.todos[len(r.todos)-1], nil | ||
} | ||
|
||
func (r *TodoResolver) Query_todos() ([]*Todo, error) { | ||
return r.todos, nil | ||
} | ||
|
||
func (r *TodoResolver) Mutation_createTodo(text string) (Todo, error) { | ||
newID := r.id() | ||
|
||
newTodo := Todo{ | ||
ID: newID, | ||
Text: text, | ||
Done: false, | ||
} | ||
|
||
r.todos = append(r.todos, &newTodo) | ||
|
||
return newTodo, nil | ||
} | ||
|
||
func (r *TodoResolver) Mutation_updateTodo(id int, done bool) (*Todo, error) { | ||
var affectedTodo *Todo | ||
|
||
for i := 0; i < len(r.todos); i++ { | ||
if r.todos[i].ID == id { | ||
r.todos[i].Done = done | ||
affectedTodo = r.todos[i] | ||
break | ||
} | ||
} | ||
return affectedTodo, errors.New("not found") | ||
} | ||
|
||
func (r *TodoResolver) id() int { | ||
r.lastID++ | ||
return r.lastID | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
package todoresolver | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
|
||
"github.com/vektah/graphql-go/errors" | ||
"github.com/vektah/graphql-go/internal/query" | ||
"github.com/vektah/graphql-go/internal/validation" | ||
"github.com/vektah/graphql-go/jsonw" | ||
) | ||
|
||
type ExecutionContext struct { | ||
variables map[string]interface{} | ||
errors []*errors.QueryError | ||
resolvers Resolvers | ||
} | ||
|
||
type Type interface { | ||
GetField(field string) Type | ||
Execute(ec *ExecutionContext, object interface{}, field string, arguments map[string]interface{}, sels []query.Selection) jsonw.Encodable | ||
} | ||
|
||
func (c *ExecutionContext) errorf(format string, args ...interface{}) { | ||
c.errors = append(c.errors, errors.Errorf(format, args...)) | ||
} | ||
|
||
func (c *ExecutionContext) error(err error) { | ||
c.errors = append(c.errors, errors.Errorf("%s", err.Error())) | ||
} | ||
|
||
func getOperation(document *query.Document, operationName string) (*query.Operation, error) { | ||
if len(document.Operations) == 0 { | ||
return nil, fmt.Errorf("no operations in query document") | ||
} | ||
|
||
if operationName == "" { | ||
if len(document.Operations) > 1 { | ||
return nil, fmt.Errorf("more than one operation in query document and no operation name given") | ||
} | ||
for _, op := range document.Operations { | ||
return op, nil // return the one and only operation | ||
} | ||
} | ||
|
||
op := document.Operations.Get(operationName) | ||
if op == nil { | ||
return nil, fmt.Errorf("no operation with name %q", operationName) | ||
} | ||
return op, nil | ||
} | ||
|
||
func ExecuteRequest(resolvers Resolvers, document string, operationName string, variables map[string]interface{}) *jsonw.Response { | ||
doc, qErr := query.Parse(document) | ||
if qErr != nil { | ||
return &jsonw.Response{Errors: []*errors.QueryError{qErr}} | ||
} | ||
|
||
errs := validation.Validate(parsedSchema, doc) | ||
if len(errs) != 0 { | ||
return &jsonw.Response{Errors: errs} | ||
} | ||
|
||
op, err := getOperation(doc, operationName) | ||
if err != nil { | ||
return &jsonw.Response{Errors: []*errors.QueryError{errors.Errorf("%s", err)}} | ||
} | ||
|
||
// TODO: variable coercion? | ||
|
||
c := ExecutionContext{ | ||
variables: variables, | ||
resolvers: resolvers, | ||
} | ||
|
||
var rootType Type = queryType{} | ||
|
||
if op.Type == query.Mutation { | ||
rootType = mutationType{} | ||
} | ||
|
||
// TODO: parallelize if query. | ||
data := c.executeSelectionSet(op.Selections, rootType, nil) | ||
b := &bytes.Buffer{} | ||
data.JSON(b) | ||
return &jsonw.Response{ | ||
Data: b.Bytes(), | ||
Errors: c.errors, | ||
} | ||
} | ||
|
||
func (c *ExecutionContext) executeSelectionSet(sel []query.Selection, objectType Type, objectValue interface{}) jsonw.Encodable { | ||
groupedFieldSet := c.collectFields(objectType, sel, map[string]interface{}{}) | ||
fmt.Println("ESS grouped selections") | ||
for _, s := range groupedFieldSet { | ||
fmt.Println(s.Alias) | ||
} | ||
resultMap := jsonw.Map{} | ||
|
||
for _, collectedField := range groupedFieldSet { | ||
//fieldType := objectType.GetField(collectedField.Name) | ||
//if fieldType == nil { | ||
// continue | ||
//} | ||
resultMap.Set(collectedField.Alias, objectType.Execute(c, objectValue, collectedField.Name, map[string]interface{}{}, collectedField.Selections)) | ||
} | ||
return resultMap | ||
} | ||
|
||
type CollectedField struct { | ||
Alias string | ||
Name string | ||
Selections []query.Selection | ||
} | ||
|
||
func findField(c *[]CollectedField, alias string, name string) *CollectedField { | ||
for i, cf := range *c { | ||
if cf.Alias == alias { | ||
return &(*c)[i] | ||
} | ||
} | ||
|
||
*c = append(*c, CollectedField{Alias: alias, Name: name}) | ||
return &(*c)[len(*c)-1] | ||
} | ||
|
||
func (c *ExecutionContext) collectFields(objectType Type, selSet []query.Selection, visited map[string]interface{}) []CollectedField { | ||
var groupedFields []CollectedField | ||
|
||
// TODO: Basically everything. | ||
for _, sel := range selSet { | ||
switch sel := sel.(type) { | ||
case *query.Field: | ||
f := findField(&groupedFields, sel.Alias.Name, sel.Name.Name) | ||
f.Selections = append(f.Selections, sel.Selections...) | ||
default: | ||
panic("Unsupported!") | ||
} | ||
} | ||
|
||
return groupedFields | ||
} |
Oops, something went wrong.