Skip to content

Commit

Permalink
Add GET query param support to handler
Browse files Browse the repository at this point in the history
  • Loading branch information
vektah committed Feb 8, 2018
1 parent dd9a8e4 commit ce5e38e
Show file tree
Hide file tree
Showing 9 changed files with 149 additions and 50 deletions.
2 changes: 1 addition & 1 deletion cmd/ggraphqlc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func main() {
"introspection": "github.com/vektah/graphql-go/introspection",
"jsonw": "github.com/vektah/graphql-go/jsonw",
"query": "github.com/vektah/graphql-go/query",
"relay": "github.com/vektah/graphql-go/relay",
"graphql": "github.com/vektah/graphql-go",
"schema": "github.com/vektah/graphql-go/schema",
"validation": "github.com/vektah/graphql-go/validation",
},
Expand Down
4 changes: 2 additions & 2 deletions example/starwars/gen/generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import (
time "time"

mapstructure "github.com/mitchellh/mapstructure"
graphql "github.com/vektah/graphql-go"
errors "github.com/vektah/graphql-go/errors"
starwars "github.com/vektah/graphql-go/example/starwars"
introspection "github.com/vektah/graphql-go/introspection"
jsonw "github.com/vektah/graphql-go/jsonw"
query "github.com/vektah/graphql-go/query"
relay "github.com/vektah/graphql-go/relay"
schema "github.com/vektah/graphql-go/schema"
validation "github.com/vektah/graphql-go/validation"
)
Expand All @@ -43,7 +43,7 @@ type Resolvers interface {
Query_starship(ctx context.Context, id string) (*starwars.Starship, error)
}

func NewResolver(resolvers Resolvers) relay.Resolver {
func NewResolver(resolvers Resolvers) graphql.Resolver {
return func(ctx context.Context, document string, operationName string, variables map[string]interface{}, w io.Writer) []*errors.QueryError {
doc, qErr := query.Parse(document)
if qErr != nil {
Expand Down
4 changes: 2 additions & 2 deletions example/starwars/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ import (
"log"
"net/http"

"github.com/vektah/graphql-go"
"github.com/vektah/graphql-go/example/starwars"
"github.com/vektah/graphql-go/example/starwars/gen"
"github.com/vektah/graphql-go/relay"
)

func main() {
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(page)
}))

http.Handle("/query", relay.Handler(gen.NewResolver(starwars.NewResolver())))
http.Handle("/query", graphql.Handler(gen.NewResolver(starwars.NewResolver())))

log.Fatal(http.ListenAndServe(":8080", nil))
}
Expand Down
4 changes: 2 additions & 2 deletions example/todo/gen/generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import (
time "time"

mapstructure "github.com/mitchellh/mapstructure"
graphql "github.com/vektah/graphql-go"
errors "github.com/vektah/graphql-go/errors"
todo "github.com/vektah/graphql-go/example/todo"
introspection "github.com/vektah/graphql-go/introspection"
jsonw "github.com/vektah/graphql-go/jsonw"
query "github.com/vektah/graphql-go/query"
relay "github.com/vektah/graphql-go/relay"
schema "github.com/vektah/graphql-go/schema"
validation "github.com/vektah/graphql-go/validation"
)
Expand All @@ -32,7 +32,7 @@ type Resolvers interface {
MyQuery_todos(ctx context.Context) ([]todo.Todo, error)
}

func NewResolver(resolvers Resolvers) relay.Resolver {
func NewResolver(resolvers Resolvers) graphql.Resolver {
return func(ctx context.Context, document string, operationName string, variables map[string]interface{}, w io.Writer) []*errors.QueryError {
doc, qErr := query.Parse(document)
if qErr != nil {
Expand Down
4 changes: 2 additions & 2 deletions example/todo/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,17 @@ import (
"log"
"net/http"

graphql "github.com/vektah/graphql-go"
"github.com/vektah/graphql-go/example/todo"
"github.com/vektah/graphql-go/example/todo/gen"
"github.com/vektah/graphql-go/relay"
)

func main() {
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write(page)
}))

http.Handle("/query", relay.Handler(gen.NewResolver(todo.NewResolver())))
http.Handle("/query", graphql.Handler(gen.NewResolver(todo.NewResolver())))

log.Fatal(http.ListenAndServe(":8081", nil))
}
Expand Down
60 changes: 60 additions & 0 deletions handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package graphql

import (
"context"
"encoding/json"
"io"
"net/http"

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

type Resolver func(ctx context.Context, document string, operationName string, variables map[string]interface{}, w io.Writer) []*errors.QueryError

type errorResponse struct {
Errors []*errors.QueryError `json:"errors"`
}

func Handler(resolver Resolver) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var params struct {
Query string `json:"query"`
OperationName string `json:"operationName"`
Variables map[string]interface{} `json:"variables"`
}

w.Header().Set("Content-Type", "application/json")

if r.Method == "GET" {
params.Query = r.URL.Query().Get("query")
params.OperationName = r.URL.Query().Get("operationName")

if variables := r.URL.Query().Get("variables"); variables != "" {
if err := json.Unmarshal([]byte(variables), &params.Variables); err != nil {
sendError(w, http.StatusBadRequest, []*errors.QueryError{errors.Errorf("variables could not be decoded")})
return
}
}
} else {
if err := json.NewDecoder(r.Body).Decode(&params); err != nil {
sendError(w, http.StatusBadRequest, []*errors.QueryError{errors.Errorf("json body could not be decoded")})
return
}
}

errs := resolver(r.Context(), params.Query, params.OperationName, params.Variables, w)
if errs != nil {
sendError(w, http.StatusUnprocessableEntity, errs)
}
})
}

func sendError(w http.ResponseWriter, code int, errs []*errors.QueryError) {
w.WriteHeader(code)

b, err := json.Marshal(errorResponse{Errors: errs})
if err != nil {
panic(err)
}
w.Write(b)
}
79 changes: 79 additions & 0 deletions handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package graphql

import (
"context"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/vektah/graphql-go/errors"
)

func TestHandlerPOST(t *testing.T) {
h := Handler(func(ctx context.Context, document string, operationName string, variables map[string]interface{}, w io.Writer) []*errors.QueryError {
if document == "error" {
return []*errors.QueryError{errors.Errorf("error!")}
}

w.Write([]byte(`{"data":{}}`))
return nil
})

t.Run("ok", func(t *testing.T) {
resp := doRequest(h, "POST", "/graphql", `{"query":"me{id}"}`)
assert.Equal(t, http.StatusOK, resp.Code)
assert.Equal(t, `{"data":{}}`, resp.Body.String())
})

t.Run("decode failure", func(t *testing.T) {
resp := doRequest(h, "POST", "/graphql", "notjson")
assert.Equal(t, http.StatusBadRequest, resp.Code)
assert.Equal(t, `{"errors":[{"message":"json body could not be decoded"}]}`, resp.Body.String())
})

t.Run("execution failure", func(t *testing.T) {
resp := doRequest(h, "POST", "/graphql", `{"query":"error"}`)
assert.Equal(t, http.StatusUnprocessableEntity, resp.Code)
assert.Equal(t, `{"errors":[{"message":"error!"}]}`, resp.Body.String())
})
}

func TestHandlerGET(t *testing.T) {
h := Handler(func(ctx context.Context, document string, operationName string, variables map[string]interface{}, w io.Writer) []*errors.QueryError {
if document == "error" {
return []*errors.QueryError{errors.Errorf("error!")}
}

w.Write([]byte(`{"data":{}}`))
return nil
})

t.Run("just query", func(t *testing.T) {
resp := doRequest(h, "GET", "/graphql?query=me{id}", ``)
assert.Equal(t, http.StatusOK, resp.Code)
assert.Equal(t, `{"data":{}}`, resp.Body.String())
})

t.Run("decode failure", func(t *testing.T) {
resp := doRequest(h, "GET", "/graphql?query=me{id}&variables=notjson", "")
assert.Equal(t, http.StatusBadRequest, resp.Code)
assert.Equal(t, `{"errors":[{"message":"variables could not be decoded"}]}`, resp.Body.String())
})

t.Run("execution failure", func(t *testing.T) {
resp := doRequest(h, "GET", "/graphql?query=error", "")
assert.Equal(t, http.StatusUnprocessableEntity, resp.Code)
assert.Equal(t, `{"errors":[{"message":"error!"}]}`, resp.Body.String())
})
}

func doRequest(handler http.Handler, method string, target string, body string) *httptest.ResponseRecorder {
r := httptest.NewRequest(method, target, strings.NewReader(body))
w := httptest.NewRecorder()

handler.ServeHTTP(w, r)
return w
}
40 changes: 0 additions & 40 deletions relay/relay.go

This file was deleted.

2 changes: 1 addition & 1 deletion templates/gen.gotpl
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type Resolvers interface {
}


func NewResolver(resolvers Resolvers) relay.Resolver {
func NewResolver(resolvers Resolvers) graphql.Resolver {
return func(ctx context.Context, document string, operationName string, variables map[string]interface{}, w io.Writer) []*errors.QueryError {
doc, qErr := query.Parse(document)
if qErr != nil {
Expand Down

0 comments on commit ce5e38e

Please sign in to comment.