Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Extensions in Response #334

Merged
merged 2 commits into from
Sep 17, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 28 additions & 16 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,49 +73,61 @@ func (p *Client) mkRequest(query string, options ...Option) Request {
return r
}

type ResponseData struct {
Data interface{}
Errors json.RawMessage
Extensions map[string]interface{}
}

func (p *Client) Post(query string, response interface{}, options ...Option) (resperr error) {
respDataRaw, resperr := p.RawPost(query, options...)
if resperr != nil {
return resperr
}

// we want to unpack even if there is an error, so we can see partial responses
unpackErr := unpack(respDataRaw.Data, response)

if respDataRaw.Errors != nil {
return RawJsonError{respDataRaw.Errors}
}
return unpackErr
}

func (p *Client) RawPost(query string, options ...Option) (*ResponseData, error) {
r := p.mkRequest(query, options...)
requestBody, err := json.Marshal(r)
if err != nil {
return fmt.Errorf("encode: %s", err.Error())
return nil, fmt.Errorf("encode: %s", err.Error())
}

rawResponse, err := p.client.Post(p.url, "application/json", bytes.NewBuffer(requestBody))
if err != nil {
return fmt.Errorf("post: %s", err.Error())
return nil, fmt.Errorf("post: %s", err.Error())
}
defer func() {
_ = rawResponse.Body.Close()
}()

if rawResponse.StatusCode >= http.StatusBadRequest {
responseBody, _ := ioutil.ReadAll(rawResponse.Body)
return fmt.Errorf("http %d: %s", rawResponse.StatusCode, responseBody)
return nil, fmt.Errorf("http %d: %s", rawResponse.StatusCode, responseBody)
}

responseBody, err := ioutil.ReadAll(rawResponse.Body)
if err != nil {
return fmt.Errorf("read: %s", err.Error())
return nil, fmt.Errorf("read: %s", err.Error())
}

// decode it into map string first, let mapstructure do the final decode
// because it can be much stricter about unknown fields.
respDataRaw := struct {
Data interface{}
Errors json.RawMessage
}{}
respDataRaw := &ResponseData{}
err = json.Unmarshal(responseBody, &respDataRaw)
if err != nil {
return fmt.Errorf("decode: %s", err.Error())
return nil, fmt.Errorf("decode: %s", err.Error())
}

// we want to unpack even if there is an error, so we can see partial responses
unpackErr := unpack(respDataRaw.Data, response)

if respDataRaw.Errors != nil {
return RawJsonError{respDataRaw.Errors}
}
return unpackErr
return respDataRaw, nil
}

type RawJsonError struct {
Expand Down
2 changes: 1 addition & 1 deletion codegen/templates/data.go

Large diffs are not rendered by default.

16 changes: 9 additions & 7 deletions codegen/templates/generated.gotpl
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,9 @@ func (e *executableSchema) Query(ctx context.Context, op *ast.OperationDefinitio
})

return &graphql.Response{
Data: buf,
Errors: ec.Errors,
}
Data: buf,
Errors: ec.Errors,
Extensions: ec.Extensions, }
{{- else }}
return graphql.ErrorResponse(ctx, "queries are not supported")
{{- end }}
Expand All @@ -146,8 +146,9 @@ func (e *executableSchema) Mutation(ctx context.Context, op *ast.OperationDefini
})

return &graphql.Response{
Data: buf,
Errors: ec.Errors,
Data: buf,
Errors: ec.Errors,
Extensions: ec.Extensions,
}
{{- else }}
return graphql.ErrorResponse(ctx, "mutations are not supported")
Expand Down Expand Up @@ -181,8 +182,9 @@ func (e *executableSchema) Subscription(ctx context.Context, op *ast.OperationDe
}

return &graphql.Response{
Data: buf,
Errors: ec.Errors,
Data: buf,
Errors: ec.Errors,
Extensions: ec.Extensions,
}
}
{{- else }}
Expand Down
11 changes: 6 additions & 5 deletions codegen/testserver/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions codegen/testserver/generated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"testing"
"time"

"github.com/99designs/gqlgen/graphql"

"github.com/99designs/gqlgen/client"
"github.com/99designs/gqlgen/handler"
"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -110,6 +112,25 @@ func TestGeneratedServer(t *testing.T) {
})
}

func TestResponseExtension(t *testing.T) {
srv := httptest.NewServer(handler.GraphQL(
NewExecutableSchema(Config{
Resolvers: &testResolver{},
}),
handler.RequestMiddleware(func(ctx context.Context, next func(ctx context.Context) []byte) []byte {
rctx := graphql.GetRequestContext(ctx)
if err := rctx.RegisterExtension("example", "value"); err != nil {
panic(err)
}
return next(ctx)
}),
))
c := client.New(srv.URL)

raw, _ := c.RawPost(`query { valid }`)
require.Equal(t, raw.Extensions["example"], "value")
}

type testResolver struct {
tick chan string
}
Expand Down
16 changes: 9 additions & 7 deletions example/chat/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions example/config/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions example/dataloader/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions example/scalars/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions example/selection/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions example/starwars/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions example/todo/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 21 additions & 2 deletions graphql/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ type RequestContext struct {
DirectiveMiddleware FieldMiddleware
RequestMiddleware RequestMiddleware

errorsMu sync.Mutex
Errors gqlerror.List
errorsMu sync.Mutex
Errors gqlerror.List
extensionsMu sync.Mutex
Extensions map[string]interface{}
}

func DefaultResolverMiddleware(ctx context.Context, next Resolver) (res interface{}, err error) {
Expand Down Expand Up @@ -176,3 +178,20 @@ func AddError(ctx context.Context, err error) {
func AddErrorf(ctx context.Context, format string, args ...interface{}) {
GetRequestContext(ctx).Errorf(ctx, format, args...)
}

// RegisterExtension registers an extension, returns error if extension has already been registered
func (c *RequestContext) RegisterExtension(key string, value interface{}) error {
c.extensionsMu.Lock()
defer c.extensionsMu.Unlock()

if c.Extensions == nil {
c.Extensions = make(map[string]interface{})
}

if _, ok := c.Extensions[key]; ok {
return fmt.Errorf("extension already registered for key %s", key)
}

c.Extensions[key] = value
return nil
}
5 changes: 3 additions & 2 deletions graphql/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import (
)

type Response struct {
Data json.RawMessage `json:"data"`
Errors gqlerror.List `json:"errors,omitempty"`
Data json.RawMessage `json:"data"`
Errors gqlerror.List `json:"errors,omitempty"`
Extensions map[string]interface{} `json:"extensions,omitempty"`
}

func ErrorResponse(ctx context.Context, messagef string, args ...interface{}) *Response {
Expand Down
6 changes: 3 additions & 3 deletions integration/generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.