Skip to content

Commit 13da187

Browse files
Merge branch 'graphql-go:master' into master
2 parents 99f92b9 + f96ffdd commit 13da187

File tree

8 files changed

+185
-33
lines changed

8 files changed

+185
-33
lines changed

.circleci/config.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
defaults: &defaults
2+
working_directory: /go/src/github.com/graphql-go/handler
3+
steps:
4+
- checkout
5+
- run: go get -v -t -d ./...
6+
- run: go test ./...
7+
8+
version: 2
9+
jobs:
10+
golang:1.8.7:
11+
<<: *defaults
12+
docker:
13+
- image: circleci/golang:1.8.7
14+
golang:1.9.7:
15+
<<: *defaults
16+
docker:
17+
- image: circleci/golang:1.9.7
18+
golang:latest:
19+
<<: *defaults
20+
docker:
21+
- image: circleci/golang:latest
22+
coveralls:
23+
working_directory: /go/src/github.com/graphql-go/handler
24+
docker:
25+
- image: circleci/golang:latest
26+
steps:
27+
- checkout
28+
- run: go get -v -t -d ./...
29+
- run: go get github.com/mattn/goveralls
30+
- run: go test -v -cover -race -coverprofile=coverage.out
31+
- run: /go/bin/goveralls -coverprofile=coverage.out -service=circle-ci -repotoken $COVERALLS_TOKEN
32+
33+
workflows:
34+
version: 2
35+
build:
36+
jobs:
37+
- golang:1.8.7
38+
- golang:1.9.7
39+
- golang:latest
40+
- coveralls

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ _testmain.go
2323
*.test
2424
*.prof
2525
*.swp
26+
.idea

.travis.yml

Lines changed: 0 additions & 12 deletions
This file was deleted.

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
# graphql-go-handler [![Build Status](https://travis-ci.org/graphql-go/handler.svg)](https://travis-ci.org/graphql-go/handler) [![GoDoc](https://godoc.org/graphql-go/handler?status.svg)](https://godoc.org/github.com/graphql-go/handler) [![Coverage Status](https://coveralls.io/repos/graphql-go/handler/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-go/handler?branch=master) [![Join the chat at https://gitter.im/graphql-go/graphql](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/graphql-go/graphql?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
1+
# graphql-go-handler [![CircleCI](https://circleci.com/gh/graphql-go/handler.svg?style=svg)](https://circleci.com/gh/graphql-go/handler) [![GoDoc](https://godoc.org/graphql-go/handler?status.svg)](https://godoc.org/github.com/graphql-go/handler) [![Coverage Status](https://coveralls.io/repos/graphql-go/handler/badge.svg?branch=master&service=github)](https://coveralls.io/github/graphql-go/handler?branch=master) [![Join the chat at https://gitter.im/graphql-go/graphql](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/graphql-go/graphql?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
2+
23

34
Golang HTTP.Handler for [graphl-go](https://github.com/graphql-go/graphql)
45

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/graphql-go/handler
2+
3+
go 1.14

graphcoolPlayground.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func renderPlayground(w http.ResponseWriter, r *http.Request) {
2424

2525
d := playgroundData{
2626
PlaygroundVersion: graphcoolPlaygroundVersion,
27-
Endpoint: "/graphql",
27+
Endpoint: r.URL.Path,
2828
SubscriptionEndpoint: fmt.Sprintf("ws://%v/subscriptions", r.Host),
2929
SetTitle: true,
3030
}

handler.go

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"github.com/graphql-go/graphql"
1111

1212
"context"
13+
14+
"github.com/graphql-go/graphql/gqlerrors"
1315
)
1416

1517
const (
@@ -19,13 +21,18 @@ const (
1921
ContentTypeMultipartFormData = "multipart/form-data"
2022
)
2123

24+
type ResultCallbackFn func(ctx context.Context, params *graphql.Params, result *graphql.Result, responseBody []byte)
25+
2226
type Handler struct {
23-
Schema *graphql.Schema
24-
pretty bool
25-
graphiql bool
26-
playground bool
27-
rootObjectFn RootObjectFn
27+
Schema *graphql.Schema
28+
pretty bool
29+
graphiql bool
30+
playground bool
31+
rootObjectFn RootObjectFn
32+
resultCallbackFn ResultCallbackFn
33+
formatErrorFn func(err error) gqlerrors.FormattedError
2834
}
35+
2936
type RequestOptions struct {
3037
Query string `json:"query" url:"query" schema:"query"`
3138
Variables map[string]interface{} `json:"variables" url:"variables" schema:"variables"`
@@ -63,7 +70,7 @@ func NewRequestOptions(r *http.Request) *RequestOptions {
6370
return reqOpt
6471
}
6572

66-
if r.Method != "POST" {
73+
if r.Method != http.MethodPost {
6774
return &RequestOptions{}
6875
}
6976

@@ -143,6 +150,14 @@ func (h *Handler) ContextHandler(ctx context.Context, w http.ResponseWriter, r *
143150
}
144151
result := graphql.Do(params)
145152

153+
if formatErrorFn := h.formatErrorFn; formatErrorFn != nil && len(result.Errors) > 0 {
154+
formatted := make([]gqlerrors.FormattedError, len(result.Errors))
155+
for i, formattedError := range result.Errors {
156+
formatted[i] = formatErrorFn(formattedError.OriginalError())
157+
}
158+
result.Errors = formatted
159+
}
160+
146161
if h.graphiql {
147162
acceptHeader := r.Header.Get("Accept")
148163
_, raw := r.URL.Query()["raw"]
@@ -164,17 +179,22 @@ func (h *Handler) ContextHandler(ctx context.Context, w http.ResponseWriter, r *
164179
// use proper JSON Header
165180
w.Header().Add("Content-Type", "application/json; charset=utf-8")
166181

182+
var buff []byte
167183
if h.pretty {
168184
w.WriteHeader(http.StatusOK)
169-
buff, _ := json.MarshalIndent(result, "", "\t")
185+
buff, _ = json.MarshalIndent(result, "", "\t")
170186

171187
w.Write(buff)
172188
} else {
173189
w.WriteHeader(http.StatusOK)
174-
buff, _ := json.Marshal(result)
190+
buff, _ = json.Marshal(result)
175191

176192
w.Write(buff)
177193
}
194+
195+
if h.resultCallbackFn != nil {
196+
h.resultCallbackFn(ctx, &params, result, buff)
197+
}
178198
}
179199

180200
// ServeHTTP provides an entrypoint into executing graphQL queries.
@@ -186,11 +206,13 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
186206
type RootObjectFn func(ctx context.Context, r *http.Request) map[string]interface{}
187207

188208
type Config struct {
189-
Schema *graphql.Schema
190-
Pretty bool
191-
GraphiQL bool
192-
Playground bool
193-
RootObjectFn RootObjectFn
209+
Schema *graphql.Schema
210+
Pretty bool
211+
GraphiQL bool
212+
Playground bool
213+
RootObjectFn RootObjectFn
214+
ResultCallbackFn ResultCallbackFn
215+
FormatErrorFn func(err error) gqlerrors.FormattedError
194216
}
195217

196218
func NewConfig() *Config {
@@ -206,15 +228,18 @@ func New(p *Config) *Handler {
206228
if p == nil {
207229
p = NewConfig()
208230
}
231+
209232
if p.Schema == nil {
210233
panic("undefined GraphQL schema")
211234
}
212235

213236
return &Handler{
214-
Schema: p.Schema,
215-
pretty: p.Pretty,
216-
graphiql: p.GraphiQL,
217-
playground: p.Playground,
218-
rootObjectFn: p.RootObjectFn,
237+
Schema: p.Schema,
238+
pretty: p.Pretty,
239+
graphiql: p.GraphiQL,
240+
playground: p.Playground,
241+
rootObjectFn: p.RootObjectFn,
242+
resultCallbackFn: p.ResultCallbackFn,
243+
formatErrorFn: p.FormatErrorFn,
219244
}
220245
}

handler_test.go

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"context"
1414

1515
"github.com/graphql-go/graphql"
16+
"github.com/graphql-go/graphql/gqlerrors"
17+
"github.com/graphql-go/graphql/language/location"
1618
"github.com/graphql-go/graphql/testutil"
1719
"github.com/graphql-go/handler"
1820
)
@@ -93,12 +95,23 @@ func TestHandler_BasicQuery_Pretty(t *testing.T) {
9395
},
9496
},
9597
}
96-
queryString := `query=query HeroNameQuery { hero { name } }`
98+
queryString := `query=query HeroNameQuery { hero { name } }&operationName=HeroNameQuery`
9799
req, _ := http.NewRequest("GET", fmt.Sprintf("/graphql?%v", queryString), nil)
98100

101+
callbackCalled := false
99102
h := handler.New(&handler.Config{
100103
Schema: &testutil.StarWarsSchema,
101104
Pretty: true,
105+
ResultCallbackFn: func(ctx context.Context, params *graphql.Params, result *graphql.Result, responseBody []byte) {
106+
callbackCalled = true
107+
if params.OperationName != "HeroNameQuery" {
108+
t.Fatalf("OperationName passed to callback was not HeroNameQuery: %v", params.OperationName)
109+
}
110+
111+
if result.HasErrors() {
112+
t.Fatalf("unexpected graphql result errors")
113+
}
114+
},
102115
})
103116
result, resp := executeTest(t, h, req)
104117
if resp.Code != http.StatusOK {
@@ -107,6 +120,9 @@ func TestHandler_BasicQuery_Pretty(t *testing.T) {
107120
if !reflect.DeepEqual(result, expected) {
108121
t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
109122
}
123+
if !callbackCalled {
124+
t.Fatalf("ResultCallbackFn was not called when it should have been")
125+
}
110126
}
111127

112128
func TestHandler_BasicQuery_Ugly(t *testing.T) {
@@ -196,3 +212,81 @@ func TestHandler_BasicQuery_WithRootObjFn(t *testing.T) {
196212
t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
197213
}
198214
}
215+
216+
type customError struct {
217+
message string
218+
}
219+
220+
func (e customError) Error() string {
221+
return fmt.Sprintf("%s", e.message)
222+
}
223+
224+
func TestHandler_BasicQuery_WithFormatErrorFn(t *testing.T) {
225+
resolverError := customError{message: "resolver error"}
226+
myNameQuery := graphql.NewObject(graphql.ObjectConfig{
227+
Name: "Query",
228+
Fields: graphql.Fields{
229+
"name": &graphql.Field{
230+
Name: "name",
231+
Type: graphql.String,
232+
Resolve: func(p graphql.ResolveParams) (interface{}, error) {
233+
return nil, resolverError
234+
},
235+
},
236+
},
237+
})
238+
myNameSchema, err := graphql.NewSchema(graphql.SchemaConfig{
239+
Query: myNameQuery,
240+
})
241+
if err != nil {
242+
t.Fatal(err)
243+
}
244+
245+
customFormattedError := gqlerrors.FormattedError{
246+
Message: resolverError.Error(),
247+
Locations: []location.SourceLocation{
248+
location.SourceLocation{
249+
Line: 1,
250+
Column: 2,
251+
},
252+
},
253+
Path: []interface{}{"name"},
254+
}
255+
256+
expected := &graphql.Result{
257+
Data: map[string]interface{}{
258+
"name": nil,
259+
},
260+
Errors: []gqlerrors.FormattedError{customFormattedError},
261+
}
262+
263+
queryString := `query={name}`
264+
req, _ := http.NewRequest("GET", fmt.Sprintf("/graphql?%v", queryString), nil)
265+
266+
formatErrorFnCalled := false
267+
h := handler.New(&handler.Config{
268+
Schema: &myNameSchema,
269+
Pretty: true,
270+
FormatErrorFn: func(err error) gqlerrors.FormattedError {
271+
formatErrorFnCalled = true
272+
var formatted gqlerrors.FormattedError
273+
switch err := err.(type) {
274+
case *gqlerrors.Error:
275+
formatted = gqlerrors.FormatError(err)
276+
default:
277+
t.Fatalf("unexpected error type: %v", reflect.TypeOf(err))
278+
}
279+
return formatted
280+
},
281+
})
282+
result, resp := executeTest(t, h, req)
283+
if resp.Code != http.StatusOK {
284+
t.Fatalf("unexpected server response %v", resp.Code)
285+
}
286+
if !formatErrorFnCalled {
287+
t.Fatalf("FormatErrorFn was not called when it should have been")
288+
}
289+
if !reflect.DeepEqual(result, expected) {
290+
t.Fatalf("wrong result, graphql result diff: %v", testutil.Diff(expected, result))
291+
}
292+
}

0 commit comments

Comments
 (0)