Add support for client that uses GET as transport mechanism (#186)

Current implementation always uses POST as the transport mechanism. Adding GET support enables usage of GET queries for caching simply via URL.

Some notes:
- I left the existing API for creating a new client as is, but the implementation could be much cleaner by introducing some sort of configuration struct when creating a new client
- The construction of the query parameters follows the logic from Apollo's client implementation, which can be found here https://github.com/apollographql/apollo-client/blob/8beb4820edc6352996e08f7f73bde3573f1eb666/src/link/http/rewriteURIForGET.ts
- Updated integration tests to use both sets of clients. Updating the tests to use a test suite would be cleaner
This commit is contained in:
salman-rb
2022-04-13 16:16:27 -07:00
committed by GitHub
parent b2422452a1
commit 39a980ab4e
10 changed files with 798 additions and 335 deletions
+1
View File
@@ -29,6 +29,7 @@ When releasing a new version:
- genqlient can now run as a portable binary (i.e. without a local checkout of the repository or `go run`).
- You can now enable `use_extensions` in the configuration file, to receive extensions returned by the GraphQL API server. Generated functions will return extensions as `map[string]interface{}`, if enabled.
- You can now use `graphql.NewClientUsingGet` to create a client that uses query parameters to pass the query to the GraphQL API server.
### Bug fixes:
+28
View File
@@ -8,6 +8,34 @@ This document describes common questions about genqlient, and provides an index
There's a [doc for that](INTRODUCTION.md)!
## … use GET requests instead of POST requests?
You can use `graphql.NewClientUsingGet` to create a client that will use query parameters to create the request. For example:
```go
ctx := context.Background()
client := graphql.NewClientUsingGet("https://api.github.com/graphql", http.DefaultClient)
resp, err := getUser(ctx, client, "benjaminjkraft")
fmt.Println(resp.User.Name, err)
```
This request will be sent via an HTTP GET request, with the query, operation name and variables encoded in the URL.
For example, if the query is defined as:
```graphql
query getUser($login: String!) {
user(login: $login) {
name
}
}
```
The URL requested will be:
`https://api.github.com/graphql?operationName%3DgetUser%26query%3D%0Aquery%20getUser(%24login%3A%20String!)%20%7B%0A%20%20user(login%3A%20%24login)%20%7B%0A%20%20%20%20name%0A%20%20%7D%0A%7D%0A%26variables%3D%7B%22login%22%3A%22benjaminjkraft%22%7D`
The client does not support mutations, and will return an error if passed a request that attempts one.
### … use an API that requires authentication?
When you call `graphql.NewClient`, pass in an HTTP client that adds whatever authentication headers you need (typically by wrapping the client's `Transport`). For example:
+93 -8
View File
@@ -4,9 +4,12 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"github.com/vektah/gqlparser/v2/gqlerror"
)
@@ -53,10 +56,30 @@ type client struct {
// The typical method of adding authentication headers is to wrap the client's
// Transport to add those headers. See example/caller.go for an example.
func NewClient(endpoint string, httpClient Doer) Client {
return newClient(endpoint, httpClient, http.MethodPost)
}
// NewClientUsingGet returns a Client which makes requests to the given endpoint,
// suitable for most users.
//
// The client makes GET requests to the given GraphQL endpoint using a GET query,
// with the query, operation name and variables encoded as URL parameters.
// It will use the given http client, or http.DefaultClient if a nil client is passed.
//
// The client does not support mutations, and will return an error if passed a request
// that attempts one.
//
// The typical method of adding authentication headers is to wrap the client's
// Transport to add those headers. See example/caller.go for an example.
func NewClientUsingGet(endpoint string, httpClient Doer) Client {
return newClient(endpoint, httpClient, http.MethodGet)
}
func newClient(endpoint string, httpClient Doer, method string) Client {
if httpClient == nil || httpClient == (*http.Client)(nil) {
httpClient = http.DefaultClient
}
return &client{httpClient, endpoint, http.MethodPost}
return &client{httpClient, endpoint, method}
}
// Doer encapsulates the methods from *http.Client needed by Client.
@@ -100,15 +123,14 @@ type Response struct {
}
func (c *client) MakeRequest(ctx context.Context, req *Request, resp *Response) error {
body, err := json.Marshal(req)
if err != nil {
return err
var httpReq *http.Request
var err error
if c.method == http.MethodGet {
httpReq, err = c.createGetRequest(req)
} else {
httpReq, err = c.createPostRequest(req)
}
httpReq, err := http.NewRequest(
c.method,
c.endpoint,
bytes.NewReader(body))
if err != nil {
return err
}
@@ -142,3 +164,66 @@ func (c *client) MakeRequest(ctx context.Context, req *Request, resp *Response)
}
return nil
}
func (c *client) createPostRequest(req *Request) (*http.Request, error) {
body, err := json.Marshal(req)
if err != nil {
return nil, err
}
httpReq, err := http.NewRequest(
c.method,
c.endpoint,
bytes.NewReader(body))
if err != nil {
return nil, err
}
return httpReq, nil
}
func (c *client) createGetRequest(req *Request) (*http.Request, error) {
parsedURL, err := url.Parse(c.endpoint)
if err != nil {
return nil, err
}
queryParams := parsedURL.Query()
queryUpdated := false
if req.Query != "" {
if strings.HasPrefix(strings.TrimSpace(req.Query), "mutation") {
return nil, errors.New("client does not support mutations")
}
queryParams.Set("query", req.Query)
queryUpdated = true
}
if req.OpName != "" {
queryParams.Set("operationName", req.OpName)
queryUpdated = true
}
if req.Variables != nil {
variables, variablesErr := json.Marshal(req.Variables)
if variablesErr != nil {
return nil, variablesErr
}
queryParams.Set("variables", string(variables))
queryUpdated = true
}
if queryUpdated {
parsedURL.RawQuery = queryParams.Encode()
}
httpReq, err := http.NewRequest(
c.method,
parsedURL.String(),
http.NoBody)
if err != nil {
return nil, err
}
return httpReq, nil
}
+68
View File
@@ -598,6 +598,13 @@ type MoreUserFieldsHair struct {
// GetColor returns MoreUserFieldsHair.Color, and is useful for accessing the field via an interface.
func (v *MoreUserFieldsHair) GetColor() string { return v.Color }
type NewUser struct {
Name string `json:"name"`
}
// GetName returns NewUser.Name, and is useful for accessing the field via an interface.
func (v *NewUser) GetName() string { return v.Name }
// QueryFragment includes the GraphQL fields of Query requested by the fragment QueryFragment.
type QueryFragment struct {
Beings []QueryFragmentBeingsBeing `json:"-"`
@@ -991,6 +998,14 @@ func (v *UserFields) __premarshalJSON() (*__premarshalUserFields, error) {
return &retval, nil
}
// __createUserInput is used internally by genqlient
type __createUserInput struct {
User NewUser `json:"user"`
}
// GetUser returns __createUserInput.User, and is useful for accessing the field via an interface.
func (v *__createUserInput) GetUser() NewUser { return v.User }
// __queryWithCustomMarshalInput is used internally by genqlient
type __queryWithCustomMarshalInput struct {
Date time.Time `json:"-"`
@@ -1290,6 +1305,26 @@ type __queryWithVariablesInput struct {
// GetId returns __queryWithVariablesInput.Id, and is useful for accessing the field via an interface.
func (v *__queryWithVariablesInput) GetId() string { return v.Id }
// createUserCreateUser includes the requested fields of the GraphQL type User.
type createUserCreateUser struct {
Id string `json:"id"`
Name string `json:"name"`
}
// GetId returns createUserCreateUser.Id, and is useful for accessing the field via an interface.
func (v *createUserCreateUser) GetId() string { return v.Id }
// GetName returns createUserCreateUser.Name, and is useful for accessing the field via an interface.
func (v *createUserCreateUser) GetName() string { return v.Name }
// createUserResponse is returned by createUser on success.
type createUserResponse struct {
CreateUser createUserCreateUser `json:"createUser"`
}
// GetCreateUser returns createUserResponse.CreateUser, and is useful for accessing the field via an interface.
func (v *createUserResponse) GetCreateUser() createUserCreateUser { return v.CreateUser }
// failingQueryMeUser includes the requested fields of the GraphQL type User.
type failingQueryMeUser struct {
Id string `json:"id"`
@@ -3043,6 +3078,39 @@ type simpleQueryResponse struct {
// GetMe returns simpleQueryResponse.Me, and is useful for accessing the field via an interface.
func (v *simpleQueryResponse) GetMe() simpleQueryMeUser { return v.Me }
func createUser(
ctx context.Context,
client graphql.Client,
user NewUser,
) (*createUserResponse, map[string]interface{}, error) {
req := &graphql.Request{
OpName: "createUser",
Query: `
mutation createUser ($user: NewUser!) {
createUser(input: $user) {
id
name
}
}
`,
Variables: &__createUserInput{
User: user,
},
}
var err error
var data createUserResponse
resp := &graphql.Response{Data: &data}
err = client.MakeRequest(
ctx,
req,
resp,
)
return &data, resp.Extensions, err
}
func failingQuery(
ctx context.Context,
client graphql.Client,
+376 -324
View File
@@ -25,14 +25,35 @@ func TestSimpleQuery(t *testing.T) {
ctx := context.Background()
server := server.RunServer()
defer server.Close()
client := newRoundtripClient(t, server.URL)
clients := newRoundtripClients(t, server.URL)
resp, _, err := simpleQuery(ctx, client)
for _, client := range clients {
resp, _, err := simpleQuery(ctx, client)
require.NoError(t, err)
assert.Equal(t, "1", resp.Me.Id)
assert.Equal(t, "Yours Truly", resp.Me.Name)
assert.Equal(t, 17, resp.Me.LuckyNumber)
}
}
func TestMutation(t *testing.T) {
_ = `# @genqlient
mutation createUser($user: NewUser!) { createUser(input: $user) { id name } }`
ctx := context.Background()
server := server.RunServer()
defer server.Close()
postClient := newRoundtripClient(t, server.URL)
getClient := newRoundtripGetClient(t, server.URL)
resp, _, err := createUser(ctx, postClient, NewUser{Name: "Jack"})
require.NoError(t, err)
assert.Equal(t, "5", resp.CreateUser.Id)
assert.Equal(t, "Jack", resp.CreateUser.Name)
assert.Equal(t, "1", resp.Me.Id)
assert.Equal(t, "Yours Truly", resp.Me.Name)
assert.Equal(t, 17, resp.Me.LuckyNumber)
_, _, err = createUser(ctx, getClient, NewUser{Name: "Jill"})
require.Errorf(t, err, "client does not support mutations")
}
func TestServerError(t *testing.T) {
@@ -42,30 +63,34 @@ func TestServerError(t *testing.T) {
ctx := context.Background()
server := server.RunServer()
defer server.Close()
client := newRoundtripClient(t, server.URL)
clients := newRoundtripClients(t, server.URL)
resp, _, err := failingQuery(ctx, client)
// As long as we get some response back, we should still return a full
// response -- and indeed in this case it should even have another field
// (which didn't err) set.
assert.Error(t, err)
assert.NotNil(t, resp)
assert.Equal(t, "1", resp.Me.Id)
for _, client := range clients {
resp, _, err := failingQuery(ctx, client)
// As long as we get some response back, we should still return a full
// response -- and indeed in this case it should even have another field
// (which didn't err) set.
assert.Error(t, err)
assert.NotNil(t, resp)
assert.Equal(t, "1", resp.Me.Id)
}
}
func TestNetworkError(t *testing.T) {
ctx := context.Background()
client := newRoundtripClient(t, "https://nothing.invalid/graphql")
clients := newRoundtripClients(t, "https://nothing.invalid/graphql")
resp, _, err := failingQuery(ctx, client)
// As we guarantee in the README, even on network error you always get a
// non-nil response; this is so you can write e.g.
// resp, err := failingQuery(ctx)
// return resp.Me.Id, err
// without a bunch of extra ceremony.
assert.Error(t, err)
assert.NotNil(t, resp)
assert.Equal(t, new(failingQueryResponse), resp)
for _, client := range clients {
resp, _, err := failingQuery(ctx, client)
// As we guarantee in the README, even on network error you always get a
// non-nil response; this is so you can write e.g.
// resp, err := failingQuery(ctx)
// return resp.Me.Id, err
// without a bunch of extra ceremony.
assert.Error(t, err)
assert.NotNil(t, resp)
assert.Equal(t, new(failingQueryResponse), resp)
}
}
func TestVariables(t *testing.T) {
@@ -80,19 +105,24 @@ func TestVariables(t *testing.T) {
// this right in Go (without adding `pointer: true` just for this purpose),
// and unmarshal(marshal(resp)) == resp should still hold, so we don't
// worry about it.
client := graphql.NewClient(server.URL, http.DefaultClient)
clients := []graphql.Client{
graphql.NewClient(server.URL, http.DefaultClient),
graphql.NewClientUsingGet(server.URL, http.DefaultClient),
}
resp, _, err := queryWithVariables(ctx, client, "2")
require.NoError(t, err)
for _, client := range clients {
resp, _, err := queryWithVariables(ctx, client, "2")
require.NoError(t, err)
assert.Equal(t, "2", resp.User.Id)
assert.Equal(t, "Raven", resp.User.Name)
assert.Equal(t, -1, resp.User.LuckyNumber)
assert.Equal(t, "2", resp.User.Id)
assert.Equal(t, "Raven", resp.User.Name)
assert.Equal(t, -1, resp.User.LuckyNumber)
resp, _, err = queryWithVariables(ctx, client, "374892379482379")
require.NoError(t, err)
resp, _, err = queryWithVariables(ctx, client, "374892379482379")
require.NoError(t, err)
assert.Zero(t, resp.User)
assert.Zero(t, resp.User)
}
}
func TestExtensions(t *testing.T) {
@@ -102,12 +132,14 @@ func TestExtensions(t *testing.T) {
ctx := context.Background()
server := server.RunServer()
defer server.Close()
client := newRoundtripClient(t, server.URL)
clients := newRoundtripClients(t, server.URL)
_, extensions, err := simpleQueryExt(ctx, client)
require.NoError(t, err)
assert.NotNil(t, extensions)
assert.Equal(t, extensions["foobar"], "test")
for _, client := range clients {
_, extensions, err := simpleQueryExt(ctx, client)
require.NoError(t, err)
assert.NotNil(t, extensions)
assert.Equal(t, extensions["foobar"], "test")
}
}
func TestOmitempty(t *testing.T) {
@@ -119,22 +151,24 @@ func TestOmitempty(t *testing.T) {
ctx := context.Background()
server := server.RunServer()
defer server.Close()
client := newRoundtripClient(t, server.URL)
clients := newRoundtripClients(t, server.URL)
resp, _, err := queryWithOmitempty(ctx, client, "2")
require.NoError(t, err)
for _, client := range clients {
resp, _, err := queryWithOmitempty(ctx, client, "2")
require.NoError(t, err)
assert.Equal(t, "2", resp.User.Id)
assert.Equal(t, "Raven", resp.User.Name)
assert.Equal(t, -1, resp.User.LuckyNumber)
assert.Equal(t, "2", resp.User.Id)
assert.Equal(t, "Raven", resp.User.Name)
assert.Equal(t, -1, resp.User.LuckyNumber)
// should return default user, not the user with ID ""
resp, _, err = queryWithOmitempty(ctx, client, "")
require.NoError(t, err)
// should return default user, not the user with ID ""
resp, _, err = queryWithOmitempty(ctx, client, "")
require.NoError(t, err)
assert.Equal(t, "1", resp.User.Id)
assert.Equal(t, "Yours Truly", resp.User.Name)
assert.Equal(t, 17, resp.User.LuckyNumber)
assert.Equal(t, "1", resp.User.Id)
assert.Equal(t, "Yours Truly", resp.User.Name)
assert.Equal(t, 17, resp.User.LuckyNumber)
}
}
func TestCustomMarshal(t *testing.T) {
@@ -146,24 +180,26 @@ func TestCustomMarshal(t *testing.T) {
ctx := context.Background()
server := server.RunServer()
defer server.Close()
client := newRoundtripClient(t, server.URL)
clients := newRoundtripClients(t, server.URL)
resp, _, err := queryWithCustomMarshal(ctx, client,
time.Date(2025, time.January, 1, 12, 34, 56, 789, time.UTC))
require.NoError(t, err)
for _, client := range clients {
resp, _, err := queryWithCustomMarshal(ctx, client,
time.Date(2025, time.January, 1, 12, 34, 56, 789, time.UTC))
require.NoError(t, err)
assert.Len(t, resp.UsersBornOn, 1)
user := resp.UsersBornOn[0]
assert.Equal(t, "1", user.Id)
assert.Equal(t, "Yours Truly", user.Name)
assert.Equal(t,
time.Date(2025, time.January, 1, 0, 0, 0, 0, time.UTC),
user.Birthdate)
assert.Len(t, resp.UsersBornOn, 1)
user := resp.UsersBornOn[0]
assert.Equal(t, "1", user.Id)
assert.Equal(t, "Yours Truly", user.Name)
assert.Equal(t,
time.Date(2025, time.January, 1, 0, 0, 0, 0, time.UTC),
user.Birthdate)
resp, _, err = queryWithCustomMarshal(ctx, client,
time.Date(2021, time.January, 1, 12, 34, 56, 789, time.UTC))
require.NoError(t, err)
assert.Len(t, resp.UsersBornOn, 0)
resp, _, err = queryWithCustomMarshal(ctx, client,
time.Date(2021, time.January, 1, 12, 34, 56, 789, time.UTC))
require.NoError(t, err)
assert.Len(t, resp.UsersBornOn, 0)
}
}
func TestCustomMarshalSlice(t *testing.T) {
@@ -175,24 +211,26 @@ func TestCustomMarshalSlice(t *testing.T) {
ctx := context.Background()
server := server.RunServer()
defer server.Close()
client := newRoundtripClient(t, server.URL)
clients := newRoundtripClients(t, server.URL)
resp, _, err := queryWithCustomMarshalSlice(ctx, client,
[]time.Time{time.Date(2025, time.January, 1, 12, 34, 56, 789, time.UTC)})
require.NoError(t, err)
for _, client := range clients {
resp, _, err := queryWithCustomMarshalSlice(ctx, client,
[]time.Time{time.Date(2025, time.January, 1, 12, 34, 56, 789, time.UTC)})
require.NoError(t, err)
assert.Len(t, resp.UsersBornOnDates, 1)
user := resp.UsersBornOnDates[0]
assert.Equal(t, "1", user.Id)
assert.Equal(t, "Yours Truly", user.Name)
assert.Equal(t,
time.Date(2025, time.January, 1, 0, 0, 0, 0, time.UTC),
user.Birthdate)
assert.Len(t, resp.UsersBornOnDates, 1)
user := resp.UsersBornOnDates[0]
assert.Equal(t, "1", user.Id)
assert.Equal(t, "Yours Truly", user.Name)
assert.Equal(t,
time.Date(2025, time.January, 1, 0, 0, 0, 0, time.UTC),
user.Birthdate)
resp, _, err = queryWithCustomMarshalSlice(ctx, client,
[]time.Time{time.Date(2021, time.January, 1, 12, 34, 56, 789, time.UTC)})
require.NoError(t, err)
assert.Len(t, resp.UsersBornOnDates, 0)
resp, _, err = queryWithCustomMarshalSlice(ctx, client,
[]time.Time{time.Date(2021, time.January, 1, 12, 34, 56, 789, time.UTC)})
require.NoError(t, err)
assert.Len(t, resp.UsersBornOnDates, 0)
}
}
func TestCustomMarshalOptional(t *testing.T) {
@@ -209,28 +247,30 @@ func TestCustomMarshalOptional(t *testing.T) {
ctx := context.Background()
server := server.RunServer()
defer server.Close()
client := newRoundtripClient(t, server.URL)
clients := newRoundtripClients(t, server.URL)
date := time.Date(2025, time.January, 1, 12, 34, 56, 789, time.UTC)
resp, _, err := queryWithCustomMarshalOptional(ctx, client, &date, nil)
require.NoError(t, err)
for _, client := range clients {
date := time.Date(2025, time.January, 1, 12, 34, 56, 789, time.UTC)
resp, _, err := queryWithCustomMarshalOptional(ctx, client, &date, nil)
require.NoError(t, err)
assert.Len(t, resp.UserSearch, 1)
user := resp.UserSearch[0]
assert.Equal(t, "1", user.Id)
assert.Equal(t, "Yours Truly", user.Name)
assert.Equal(t,
time.Date(2025, time.January, 1, 0, 0, 0, 0, time.UTC),
user.Birthdate)
assert.Len(t, resp.UserSearch, 1)
user := resp.UserSearch[0]
assert.Equal(t, "1", user.Id)
assert.Equal(t, "Yours Truly", user.Name)
assert.Equal(t,
time.Date(2025, time.January, 1, 0, 0, 0, 0, time.UTC),
user.Birthdate)
id := "2"
resp, _, err = queryWithCustomMarshalOptional(ctx, client, nil, &id)
require.NoError(t, err)
assert.Len(t, resp.UserSearch, 1)
user = resp.UserSearch[0]
assert.Equal(t, "2", user.Id)
assert.Equal(t, "Raven", user.Name)
assert.Zero(t, user.Birthdate)
id := "2"
resp, _, err = queryWithCustomMarshalOptional(ctx, client, nil, &id)
require.NoError(t, err)
assert.Len(t, resp.UserSearch, 1)
user = resp.UserSearch[0]
assert.Equal(t, "2", user.Id)
assert.Equal(t, "Raven", user.Name)
assert.Zero(t, user.Birthdate)
}
}
func TestInterfaceNoFragments(t *testing.T) {
@@ -243,58 +283,60 @@ func TestInterfaceNoFragments(t *testing.T) {
ctx := context.Background()
server := server.RunServer()
defer server.Close()
client := newRoundtripClient(t, server.URL)
clients := newRoundtripClients(t, server.URL)
resp, _, err := queryWithInterfaceNoFragments(ctx, client, "1")
require.NoError(t, err)
for _, client := range clients {
resp, _, err := queryWithInterfaceNoFragments(ctx, client, "1")
require.NoError(t, err)
// We should get the following response:
// me: User{Id: 1, Name: "Yours Truly"},
// being: User{Id: 1, Name: "Yours Truly"},
// We should get the following response:
// me: User{Id: 1, Name: "Yours Truly"},
// being: User{Id: 1, Name: "Yours Truly"},
assert.Equal(t, "1", resp.Me.Id)
assert.Equal(t, "Yours Truly", resp.Me.Name)
assert.Equal(t, "1", resp.Me.Id)
assert.Equal(t, "Yours Truly", resp.Me.Name)
// Check fields both via interface and via type-assertion:
assert.Equal(t, "User", resp.Being.GetTypename())
assert.Equal(t, "1", resp.Being.GetId())
assert.Equal(t, "Yours Truly", resp.Being.GetName())
// Check fields both via interface and via type-assertion:
assert.Equal(t, "User", resp.Being.GetTypename())
assert.Equal(t, "1", resp.Being.GetId())
assert.Equal(t, "Yours Truly", resp.Being.GetName())
user, ok := resp.Being.(*queryWithInterfaceNoFragmentsBeingUser)
require.Truef(t, ok, "got %T, not User", resp.Being)
assert.Equal(t, "1", user.Id)
assert.Equal(t, "Yours Truly", user.Name)
user, ok := resp.Being.(*queryWithInterfaceNoFragmentsBeingUser)
require.Truef(t, ok, "got %T, not User", resp.Being)
assert.Equal(t, "1", user.Id)
assert.Equal(t, "Yours Truly", user.Name)
resp, _, err = queryWithInterfaceNoFragments(ctx, client, "3")
require.NoError(t, err)
resp, _, err = queryWithInterfaceNoFragments(ctx, client, "3")
require.NoError(t, err)
// We should get the following response:
// me: User{Id: 1, Name: "Yours Truly"},
// being: Animal{Id: 3, Name: "Fido"},
// We should get the following response:
// me: User{Id: 1, Name: "Yours Truly"},
// being: Animal{Id: 3, Name: "Fido"},
assert.Equal(t, "1", resp.Me.Id)
assert.Equal(t, "Yours Truly", resp.Me.Name)
assert.Equal(t, "1", resp.Me.Id)
assert.Equal(t, "Yours Truly", resp.Me.Name)
assert.Equal(t, "Animal", resp.Being.GetTypename())
assert.Equal(t, "3", resp.Being.GetId())
assert.Equal(t, "Fido", resp.Being.GetName())
assert.Equal(t, "Animal", resp.Being.GetTypename())
assert.Equal(t, "3", resp.Being.GetId())
assert.Equal(t, "Fido", resp.Being.GetName())
animal, ok := resp.Being.(*queryWithInterfaceNoFragmentsBeingAnimal)
require.Truef(t, ok, "got %T, not Animal", resp.Being)
assert.Equal(t, "3", animal.Id)
assert.Equal(t, "Fido", animal.Name)
animal, ok := resp.Being.(*queryWithInterfaceNoFragmentsBeingAnimal)
require.Truef(t, ok, "got %T, not Animal", resp.Being)
assert.Equal(t, "3", animal.Id)
assert.Equal(t, "Fido", animal.Name)
resp, _, err = queryWithInterfaceNoFragments(ctx, client, "4757233945723")
require.NoError(t, err)
resp, _, err = queryWithInterfaceNoFragments(ctx, client, "4757233945723")
require.NoError(t, err)
// We should get the following response:
// me: User{Id: 1, Name: "Yours Truly"},
// being: null
// We should get the following response:
// me: User{Id: 1, Name: "Yours Truly"},
// being: null
assert.Equal(t, "1", resp.Me.Id)
assert.Equal(t, "Yours Truly", resp.Me.Name)
assert.Equal(t, "1", resp.Me.Id)
assert.Equal(t, "Yours Truly", resp.Me.Name)
assert.Nil(t, resp.Being)
assert.Nil(t, resp.Being)
}
}
func TestInterfaceListField(t *testing.T) {
@@ -306,39 +348,41 @@ func TestInterfaceListField(t *testing.T) {
ctx := context.Background()
server := server.RunServer()
defer server.Close()
client := newRoundtripClient(t, server.URL)
clients := newRoundtripClients(t, server.URL)
resp, _, err := queryWithInterfaceListField(ctx, client,
[]string{"1", "3", "12847394823"})
require.NoError(t, err)
for _, client := range clients {
resp, _, err := queryWithInterfaceListField(ctx, client,
[]string{"1", "3", "12847394823"})
require.NoError(t, err)
require.Len(t, resp.Beings, 3)
require.Len(t, resp.Beings, 3)
// We should get the following three beings:
// User{Id: 1, Name: "Yours Truly"},
// Animal{Id: 3, Name: "Fido"},
// null
// We should get the following three beings:
// User{Id: 1, Name: "Yours Truly"},
// Animal{Id: 3, Name: "Fido"},
// null
// Check fields both via interface and via type-assertion:
assert.Equal(t, "User", resp.Beings[0].GetTypename())
assert.Equal(t, "1", resp.Beings[0].GetId())
assert.Equal(t, "Yours Truly", resp.Beings[0].GetName())
// Check fields both via interface and via type-assertion:
assert.Equal(t, "User", resp.Beings[0].GetTypename())
assert.Equal(t, "1", resp.Beings[0].GetId())
assert.Equal(t, "Yours Truly", resp.Beings[0].GetName())
user, ok := resp.Beings[0].(*queryWithInterfaceListFieldBeingsUser)
require.Truef(t, ok, "got %T, not User", resp.Beings[0])
assert.Equal(t, "1", user.Id)
assert.Equal(t, "Yours Truly", user.Name)
user, ok := resp.Beings[0].(*queryWithInterfaceListFieldBeingsUser)
require.Truef(t, ok, "got %T, not User", resp.Beings[0])
assert.Equal(t, "1", user.Id)
assert.Equal(t, "Yours Truly", user.Name)
assert.Equal(t, "Animal", resp.Beings[1].GetTypename())
assert.Equal(t, "3", resp.Beings[1].GetId())
assert.Equal(t, "Fido", resp.Beings[1].GetName())
assert.Equal(t, "Animal", resp.Beings[1].GetTypename())
assert.Equal(t, "3", resp.Beings[1].GetId())
assert.Equal(t, "Fido", resp.Beings[1].GetName())
animal, ok := resp.Beings[1].(*queryWithInterfaceListFieldBeingsAnimal)
require.Truef(t, ok, "got %T, not Animal", resp.Beings[1])
assert.Equal(t, "3", animal.Id)
assert.Equal(t, "Fido", animal.Name)
animal, ok := resp.Beings[1].(*queryWithInterfaceListFieldBeingsAnimal)
require.Truef(t, ok, "got %T, not Animal", resp.Beings[1])
assert.Equal(t, "3", animal.Id)
assert.Equal(t, "Fido", animal.Name)
assert.Nil(t, resp.Beings[2])
assert.Nil(t, resp.Beings[2])
}
}
func TestInterfaceListPointerField(t *testing.T) {
@@ -353,34 +397,36 @@ func TestInterfaceListPointerField(t *testing.T) {
ctx := context.Background()
server := server.RunServer()
defer server.Close()
client := newRoundtripClient(t, server.URL)
clients := newRoundtripClients(t, server.URL)
resp, _, err := queryWithInterfaceListPointerField(ctx, client,
[]string{"1", "3", "12847394823"})
require.NoError(t, err)
for _, client := range clients {
resp, _, err := queryWithInterfaceListPointerField(ctx, client,
[]string{"1", "3", "12847394823"})
require.NoError(t, err)
require.Len(t, resp.Beings, 3)
require.Len(t, resp.Beings, 3)
// Check fields both via interface and via type-assertion:
assert.Equal(t, "User", (*resp.Beings[0]).GetTypename())
assert.Equal(t, "1", (*resp.Beings[0]).GetId())
assert.Equal(t, "Yours Truly", (*resp.Beings[0]).GetName())
// Check fields both via interface and via type-assertion:
assert.Equal(t, "User", (*resp.Beings[0]).GetTypename())
assert.Equal(t, "1", (*resp.Beings[0]).GetId())
assert.Equal(t, "Yours Truly", (*resp.Beings[0]).GetName())
user, ok := (*resp.Beings[0]).(*queryWithInterfaceListPointerFieldBeingsUser)
require.Truef(t, ok, "got %T, not User", *resp.Beings[0])
assert.Equal(t, "1", user.Id)
assert.Equal(t, "Yours Truly", user.Name)
user, ok := (*resp.Beings[0]).(*queryWithInterfaceListPointerFieldBeingsUser)
require.Truef(t, ok, "got %T, not User", *resp.Beings[0])
assert.Equal(t, "1", user.Id)
assert.Equal(t, "Yours Truly", user.Name)
assert.Equal(t, "Animal", (*resp.Beings[1]).GetTypename())
assert.Equal(t, "3", (*resp.Beings[1]).GetId())
assert.Equal(t, "Fido", (*resp.Beings[1]).GetName())
assert.Equal(t, "Animal", (*resp.Beings[1]).GetTypename())
assert.Equal(t, "3", (*resp.Beings[1]).GetId())
assert.Equal(t, "Fido", (*resp.Beings[1]).GetName())
animal, ok := (*resp.Beings[1]).(*queryWithInterfaceListPointerFieldBeingsAnimal)
require.Truef(t, ok, "got %T, not Animal", resp.Beings[1])
assert.Equal(t, "3", animal.Id)
assert.Equal(t, "Fido", animal.Name)
animal, ok := (*resp.Beings[1]).(*queryWithInterfaceListPointerFieldBeingsAnimal)
require.Truef(t, ok, "got %T, not Animal", resp.Beings[1])
assert.Equal(t, "3", animal.Id)
assert.Equal(t, "Fido", animal.Name)
assert.Nil(t, resp.Beings[2])
assert.Nil(t, resp.Beings[2])
}
}
func TestFragments(t *testing.T) {
@@ -407,62 +453,64 @@ func TestFragments(t *testing.T) {
ctx := context.Background()
server := server.RunServer()
defer server.Close()
client := newRoundtripClient(t, server.URL)
clients := newRoundtripClients(t, server.URL)
resp, _, err := queryWithFragments(ctx, client, []string{"1", "3", "12847394823"})
require.NoError(t, err)
for _, client := range clients {
resp, _, err := queryWithFragments(ctx, client, []string{"1", "3", "12847394823"})
require.NoError(t, err)
require.Len(t, resp.Beings, 3)
require.Len(t, resp.Beings, 3)
// We should get the following three beings:
// User{Id: 1, Name: "Yours Truly"},
// Animal{Id: 3, Name: "Fido"},
// null
// We should get the following three beings:
// User{Id: 1, Name: "Yours Truly"},
// Animal{Id: 3, Name: "Fido"},
// null
// Check fields both via interface and via type-assertion when possible
// User has, in total, the fields: __typename id name luckyNumber.
assert.Equal(t, "User", resp.Beings[0].GetTypename())
assert.Equal(t, "1", resp.Beings[0].GetId())
assert.Equal(t, "Yours Truly", resp.Beings[0].GetName())
// (hair and luckyNumber we need to cast for)
// Check fields both via interface and via type-assertion when possible
// User has, in total, the fields: __typename id name luckyNumber.
assert.Equal(t, "User", resp.Beings[0].GetTypename())
assert.Equal(t, "1", resp.Beings[0].GetId())
assert.Equal(t, "Yours Truly", resp.Beings[0].GetName())
// (hair and luckyNumber we need to cast for)
user, ok := resp.Beings[0].(*queryWithFragmentsBeingsUser)
require.Truef(t, ok, "got %T, not User", resp.Beings[0])
assert.Equal(t, "1", user.Id)
assert.Equal(t, "Yours Truly", user.Name)
assert.Equal(t, "Black", user.Hair.Color)
assert.Equal(t, 17, user.LuckyNumber)
user, ok := resp.Beings[0].(*queryWithFragmentsBeingsUser)
require.Truef(t, ok, "got %T, not User", resp.Beings[0])
assert.Equal(t, "1", user.Id)
assert.Equal(t, "Yours Truly", user.Name)
assert.Equal(t, "Black", user.Hair.Color)
assert.Equal(t, 17, user.LuckyNumber)
// Animal has, in total, the fields:
// __typename
// id
// species
// owner {
// id
// name
// ... on User { luckyNumber }
// }
assert.Equal(t, "Animal", resp.Beings[1].GetTypename())
assert.Equal(t, "3", resp.Beings[1].GetId())
// (hair, species, and owner.* we have to cast for)
// Animal has, in total, the fields:
// __typename
// id
// species
// owner {
// id
// name
// ... on User { luckyNumber }
// }
assert.Equal(t, "Animal", resp.Beings[1].GetTypename())
assert.Equal(t, "3", resp.Beings[1].GetId())
// (hair, species, and owner.* we have to cast for)
animal, ok := resp.Beings[1].(*queryWithFragmentsBeingsAnimal)
require.Truef(t, ok, "got %T, not Animal", resp.Beings[1])
assert.Equal(t, "3", animal.Id)
assert.Equal(t, SpeciesDog, animal.Species)
assert.True(t, animal.Hair.HasHair)
animal, ok := resp.Beings[1].(*queryWithFragmentsBeingsAnimal)
require.Truef(t, ok, "got %T, not Animal", resp.Beings[1])
assert.Equal(t, "3", animal.Id)
assert.Equal(t, SpeciesDog, animal.Species)
assert.True(t, animal.Hair.HasHair)
assert.Equal(t, "1", animal.Owner.GetId())
assert.Equal(t, "Yours Truly", animal.Owner.GetName())
// (luckyNumber we have to cast for, again)
assert.Equal(t, "1", animal.Owner.GetId())
assert.Equal(t, "Yours Truly", animal.Owner.GetName())
// (luckyNumber we have to cast for, again)
owner, ok := animal.Owner.(*queryWithFragmentsBeingsAnimalOwnerUser)
require.Truef(t, ok, "got %T, not User", animal.Owner)
assert.Equal(t, "1", owner.Id)
assert.Equal(t, "Yours Truly", owner.Name)
assert.Equal(t, 17, owner.LuckyNumber)
owner, ok := animal.Owner.(*queryWithFragmentsBeingsAnimalOwnerUser)
require.Truef(t, ok, "got %T, not User", animal.Owner)
assert.Equal(t, "1", owner.Id)
assert.Equal(t, "Yours Truly", owner.Name)
assert.Equal(t, 17, owner.LuckyNumber)
assert.Nil(t, resp.Beings[2])
assert.Nil(t, resp.Beings[2])
}
}
func TestNamedFragments(t *testing.T) {
@@ -500,73 +548,75 @@ func TestNamedFragments(t *testing.T) {
ctx := context.Background()
server := server.RunServer()
defer server.Close()
client := newRoundtripClient(t, server.URL)
clients := newRoundtripClients(t, server.URL)
resp, _, err := queryWithNamedFragments(ctx, client, []string{"1", "3", "12847394823"})
require.NoError(t, err)
for _, client := range clients {
resp, _, err := queryWithNamedFragments(ctx, client, []string{"1", "3", "12847394823"})
require.NoError(t, err)
require.Len(t, resp.Beings, 3)
require.Len(t, resp.Beings, 3)
// We should get the following three beings:
// User{Id: 1, Name: "Yours Truly"},
// Animal{Id: 3, Name: "Fido"},
// null
// We should get the following three beings:
// User{Id: 1, Name: "Yours Truly"},
// Animal{Id: 3, Name: "Fido"},
// null
// Check fields both via interface and via type-assertion when possible
// User has, in total, the fields: __typename id luckyNumber.
assert.Equal(t, "User", resp.Beings[0].GetTypename())
assert.Equal(t, "1", resp.Beings[0].GetId())
// (luckyNumber, hair we need to cast for)
// Check fields both via interface and via type-assertion when possible
// User has, in total, the fields: __typename id luckyNumber.
assert.Equal(t, "User", resp.Beings[0].GetTypename())
assert.Equal(t, "1", resp.Beings[0].GetId())
// (luckyNumber, hair we need to cast for)
user, ok := resp.Beings[0].(*queryWithNamedFragmentsBeingsUser)
require.Truef(t, ok, "got %T, not User", resp.Beings[0])
assert.Equal(t, "1", user.Id)
assert.Equal(t, "1", user.UserFields.Id)
assert.Equal(t, "1", user.UserFields.MoreUserFields.Id)
assert.Equal(t, "1", user.UserFields.LuckyFieldsUser.MoreUserFields.Id)
// on UserFields, but we should be able to access directly via embedding:
assert.Equal(t, 17, user.LuckyNumber)
assert.Equal(t, "Black", user.Hair.Color)
assert.Equal(t, "Black", user.UserFields.MoreUserFields.Hair.Color)
assert.Equal(t, "Black", user.UserFields.LuckyFieldsUser.MoreUserFields.Hair.Color)
user, ok := resp.Beings[0].(*queryWithNamedFragmentsBeingsUser)
require.Truef(t, ok, "got %T, not User", resp.Beings[0])
assert.Equal(t, "1", user.Id)
assert.Equal(t, "1", user.UserFields.Id)
assert.Equal(t, "1", user.UserFields.MoreUserFields.Id)
assert.Equal(t, "1", user.UserFields.LuckyFieldsUser.MoreUserFields.Id)
// on UserFields, but we should be able to access directly via embedding:
assert.Equal(t, 17, user.LuckyNumber)
assert.Equal(t, "Black", user.Hair.Color)
assert.Equal(t, "Black", user.UserFields.MoreUserFields.Hair.Color)
assert.Equal(t, "Black", user.UserFields.LuckyFieldsUser.MoreUserFields.Hair.Color)
// Animal has, in total, the fields:
// __typename
// id
// hair { hasHair }
// owner { id luckyNumber }
assert.Equal(t, "Animal", resp.Beings[1].GetTypename())
assert.Equal(t, "3", resp.Beings[1].GetId())
// (hair.* and owner.* we have to cast for)
// Animal has, in total, the fields:
// __typename
// id
// hair { hasHair }
// owner { id luckyNumber }
assert.Equal(t, "Animal", resp.Beings[1].GetTypename())
assert.Equal(t, "3", resp.Beings[1].GetId())
// (hair.* and owner.* we have to cast for)
animal, ok := resp.Beings[1].(*queryWithNamedFragmentsBeingsAnimal)
require.Truef(t, ok, "got %T, not Animal", resp.Beings[1])
// Check that we filled in *both* ID fields:
assert.Equal(t, "3", animal.Id)
assert.Equal(t, "3", animal.AnimalFields.Id)
// on AnimalFields:
assert.True(t, animal.Hair.HasHair)
assert.Equal(t, "1", animal.Owner.GetId())
// (luckyNumber we have to cast for, again)
animal, ok := resp.Beings[1].(*queryWithNamedFragmentsBeingsAnimal)
require.Truef(t, ok, "got %T, not Animal", resp.Beings[1])
// Check that we filled in *both* ID fields:
assert.Equal(t, "3", animal.Id)
assert.Equal(t, "3", animal.AnimalFields.Id)
// on AnimalFields:
assert.True(t, animal.Hair.HasHair)
assert.Equal(t, "1", animal.Owner.GetId())
// (luckyNumber we have to cast for, again)
owner, ok := animal.Owner.(*AnimalFieldsOwnerUser)
require.Truef(t, ok, "got %T, not User", animal.Owner)
// Check that we filled in *both* ID fields:
assert.Equal(t, "1", owner.Id)
assert.Equal(t, "1", owner.UserFields.Id)
assert.Equal(t, "1", owner.UserFields.MoreUserFields.Id)
assert.Equal(t, "1", owner.UserFields.LuckyFieldsUser.MoreUserFields.Id)
// on UserFields:
assert.Equal(t, 17, owner.LuckyNumber)
assert.Equal(t, "Black", owner.UserFields.MoreUserFields.Hair.Color)
assert.Equal(t, "Black", owner.UserFields.LuckyFieldsUser.MoreUserFields.Hair.Color)
owner, ok := animal.Owner.(*AnimalFieldsOwnerUser)
require.Truef(t, ok, "got %T, not User", animal.Owner)
// Check that we filled in *both* ID fields:
assert.Equal(t, "1", owner.Id)
assert.Equal(t, "1", owner.UserFields.Id)
assert.Equal(t, "1", owner.UserFields.MoreUserFields.Id)
assert.Equal(t, "1", owner.UserFields.LuckyFieldsUser.MoreUserFields.Id)
// on UserFields:
assert.Equal(t, 17, owner.LuckyNumber)
assert.Equal(t, "Black", owner.UserFields.MoreUserFields.Hair.Color)
assert.Equal(t, "Black", owner.UserFields.LuckyFieldsUser.MoreUserFields.Hair.Color)
// Lucky-based fields we can also get by casting to the fragment-interface.
luckyOwner, ok := animal.Owner.(LuckyFields)
require.Truef(t, ok, "got %T, not Lucky", animal.Owner)
assert.Equal(t, 17, luckyOwner.GetLuckyNumber())
// Lucky-based fields we can also get by casting to the fragment-interface.
luckyOwner, ok := animal.Owner.(LuckyFields)
require.Truef(t, ok, "got %T, not Lucky", animal.Owner)
assert.Equal(t, 17, luckyOwner.GetLuckyNumber())
assert.Nil(t, resp.Beings[2])
assert.Nil(t, resp.Beings[2])
}
}
func TestFlatten(t *testing.T) {
@@ -629,54 +679,56 @@ func TestFlatten(t *testing.T) {
ctx := context.Background()
server := server.RunServer()
defer server.Close()
client := newRoundtripClient(t, server.URL)
clients := newRoundtripClients(t, server.URL)
resp, _, err := queryWithFlatten(ctx, client, []string{"1", "3", "12847394823"})
require.NoError(t, err)
for _, client := range clients {
resp, _, err := queryWithFlatten(ctx, client, []string{"1", "3", "12847394823"})
require.NoError(t, err)
require.Len(t, resp.Beings, 3)
require.Len(t, resp.Beings, 3)
// We should get the following three beings:
// User{Id: 1, Name: "Yours Truly"},
// Animal{Id: 3, Name: "Fido"},
// null
// We should get the following three beings:
// User{Id: 1, Name: "Yours Truly"},
// Animal{Id: 3, Name: "Fido"},
// null
// Check fields both via interface and via type-assertion when possible
// User has, in total, the fields: __typename id luckyNumber.
assert.Equal(t, "User", resp.Beings[0].GetTypename())
assert.Equal(t, "1", resp.Beings[0].GetId())
// (luckyNumber we need to cast for)
// Check fields both via interface and via type-assertion when possible
// User has, in total, the fields: __typename id luckyNumber.
assert.Equal(t, "User", resp.Beings[0].GetTypename())
assert.Equal(t, "1", resp.Beings[0].GetId())
// (luckyNumber we need to cast for)
user, ok := resp.Beings[0].(*QueryFragmentBeingsUser)
require.Truef(t, ok, "got %T, not User", resp.Beings[0])
assert.Equal(t, "1", user.Id)
assert.Equal(t, 17, user.InnerLuckyFieldsUser.LuckyNumber)
user, ok := resp.Beings[0].(*QueryFragmentBeingsUser)
require.Truef(t, ok, "got %T, not User", resp.Beings[0])
assert.Equal(t, "1", user.Id)
assert.Equal(t, 17, user.InnerLuckyFieldsUser.LuckyNumber)
// Animal has, in total, the fields:
// __typename
// id
// owner { id name ... on User { friends { id name } } }
assert.Equal(t, "Animal", resp.Beings[1].GetTypename())
assert.Equal(t, "3", resp.Beings[1].GetId())
// (owner.* we have to cast for)
// Animal has, in total, the fields:
// __typename
// id
// owner { id name ... on User { friends { id name } } }
assert.Equal(t, "Animal", resp.Beings[1].GetTypename())
assert.Equal(t, "3", resp.Beings[1].GetId())
// (owner.* we have to cast for)
animal, ok := resp.Beings[1].(*QueryFragmentBeingsAnimal)
require.Truef(t, ok, "got %T, not Animal", resp.Beings[1])
assert.Equal(t, "3", animal.Id)
// on AnimalFields:
assert.Equal(t, "1", animal.Owner.GetId())
assert.Equal(t, "Yours Truly", animal.Owner.GetName())
// (friends.* we have to cast for, again)
animal, ok := resp.Beings[1].(*QueryFragmentBeingsAnimal)
require.Truef(t, ok, "got %T, not Animal", resp.Beings[1])
assert.Equal(t, "3", animal.Id)
// on AnimalFields:
assert.Equal(t, "1", animal.Owner.GetId())
assert.Equal(t, "Yours Truly", animal.Owner.GetName())
// (friends.* we have to cast for, again)
owner, ok := animal.Owner.(*InnerBeingFieldsUser)
require.Truef(t, ok, "got %T, not User", animal.Owner)
assert.Equal(t, "1", owner.Id)
assert.Equal(t, "Yours Truly", owner.Name)
assert.Len(t, owner.Friends, 1)
assert.Equal(t, "2", owner.Friends[0].Id)
assert.Equal(t, "Raven", owner.Friends[0].Name)
owner, ok := animal.Owner.(*InnerBeingFieldsUser)
require.Truef(t, ok, "got %T, not User", animal.Owner)
assert.Equal(t, "1", owner.Id)
assert.Equal(t, "Yours Truly", owner.Name)
assert.Len(t, owner.Friends, 1)
assert.Equal(t, "2", owner.Friends[0].Id)
assert.Equal(t, "Raven", owner.Friends[0].Name)
assert.Nil(t, resp.Beings[2])
assert.Nil(t, resp.Beings[2])
}
}
func TestGeneratedCode(t *testing.T) {
+16 -1
View File
@@ -101,10 +101,25 @@ func (c *roundtripClient) MakeRequest(ctx context.Context, req *graphql.Request,
return nil
}
func newRoundtripClients(t *testing.T, endpoint string) []graphql.Client {
return []graphql.Client{newRoundtripClient(t, endpoint), newRoundtripGetClient(t, endpoint)}
}
func newRoundtripClient(t *testing.T, endpoint string) graphql.Client {
transport := &lastResponseTransport{wrapped: http.DefaultTransport}
httpClient := &http.Client{Transport: transport}
return &roundtripClient{
wrapped: graphql.NewClient(endpoint, &http.Client{Transport: transport}),
wrapped: graphql.NewClient(endpoint, httpClient),
transport: transport,
t: t,
}
}
func newRoundtripGetClient(t *testing.T, endpoint string) graphql.Client {
transport := &lastResponseTransport{wrapped: http.DefaultTransport}
httpClient := &http.Client{Transport: transport}
return &roundtripClient{
wrapped: graphql.NewClientUsingGet(endpoint, httpClient),
transport: transport,
t: t,
}
+8
View File
@@ -12,6 +12,10 @@ type Query {
fail: Boolean
}
type Mutation {
createUser(input: NewUser!): User!
}
type User implements Being & Lucky {
id: ID!
name: String!
@@ -21,6 +25,10 @@ type User implements Being & Lucky {
friends: [User!]!
}
input NewUser {
name: String!
}
type Hair { color: String } # silly name to confuse the name-generator
type Animal implements Being {
+171
View File
@@ -35,6 +35,7 @@ type Config struct {
}
type ResolverRoot interface {
Mutation() MutationResolver
Query() QueryResolver
}
@@ -58,6 +59,10 @@ type ComplexityRoot struct {
Color func(childComplexity int) int
}
Mutation struct {
CreateUser func(childComplexity int, input NewUser) int
}
Query struct {
Being func(childComplexity int, id string) int
Beings func(childComplexity int, ids []string) int
@@ -80,6 +85,9 @@ type ComplexityRoot struct {
}
}
type MutationResolver interface {
CreateUser(ctx context.Context, input NewUser) (*User, error)
}
type QueryResolver interface {
Me(ctx context.Context) (*User, error)
User(ctx context.Context, id *string) (*User, error)
@@ -156,6 +164,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Hair.Color(childComplexity), true
case "Mutation.createUser":
if e.complexity.Mutation.CreateUser == nil {
break
}
args, err := ec.field_Mutation_createUser_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.CreateUser(childComplexity, args["input"].(NewUser)), true
case "Query.being":
if e.complexity.Query.Being == nil {
break
@@ -316,6 +336,20 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
var buf bytes.Buffer
data.MarshalGQL(&buf)
return &graphql.Response{
Data: buf.Bytes(),
}
}
case ast.Mutation:
return func(ctx context.Context) *graphql.Response {
if !first {
return nil
}
first = false
data := ec._Mutation(ctx, rc.Operation.SelectionSet)
var buf bytes.Buffer
data.MarshalGQL(&buf)
return &graphql.Response{
Data: buf.Bytes(),
}
@@ -360,6 +394,10 @@ type Query {
fail: Boolean
}
type Mutation {
createUser(input: NewUser!): User!
}
type User implements Being & Lucky {
id: ID!
name: String!
@@ -369,6 +407,10 @@ type User implements Being & Lucky {
friends: [User!]!
}
input NewUser {
name: String!
}
type Hair { color: String } # silly name to confuse the name-generator
type Animal implements Being {
@@ -402,6 +444,21 @@ var parsedSchema = gqlparser.MustLoadSchema(sources...)
// region ***************************** args.gotpl *****************************
func (ec *executionContext) field_Mutation_createUser_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 NewUser
if tmp, ok := rawArgs["input"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("input"))
arg0, err = ec.unmarshalNNewUser2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐNewUser(ctx, tmp)
if err != nil {
return nil, err
}
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
@@ -805,6 +862,48 @@ func (ec *executionContext) _Hair_color(ctx context.Context, field graphql.Colle
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_createUser(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_createUser_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
fc.Args = args
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().CreateUser(rctx, args["input"].(NewUser))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*User)
fc.Result = res
return ec.marshalNUser2ᚖgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐUser(ctx, field.Selections, res)
}
func (ec *executionContext) _Query_me(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@@ -2609,6 +2708,29 @@ func (ec *executionContext) ___Type_specifiedByURL(ctx context.Context, field gr
// region **************************** input.gotpl *****************************
func (ec *executionContext) unmarshalInputNewUser(ctx context.Context, obj interface{}) (NewUser, error) {
var it NewUser
asMap := map[string]interface{}{}
for k, v := range obj.(map[string]interface{}) {
asMap[k] = v
}
for k, v := range asMap {
switch k {
case "name":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("name"))
it.Name, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
// endregion **************************** input.gotpl *****************************
// region ************************** interface.gotpl ***************************
@@ -2780,6 +2902,46 @@ func (ec *executionContext) _Hair(ctx context.Context, sel ast.SelectionSet, obj
return out
}
var mutationImplementors = []string{"Mutation"}
func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, mutationImplementors)
ctx = graphql.WithFieldContext(ctx, &graphql.FieldContext{
Object: "Mutation",
})
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
innerCtx := graphql.WithRootFieldContext(ctx, &graphql.RootFieldContext{
Object: field.Name,
Field: field,
})
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("Mutation")
case "createUser":
innerFunc := func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_createUser(ctx, field)
}
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, innerFunc)
if out.Values[i] == graphql.Null {
invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var queryImplementors = []string{"Query"}
func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) graphql.Marshaler {
@@ -3670,6 +3832,11 @@ func (ec *executionContext) marshalNInt2int(ctx context.Context, sel ast.Selecti
return res
}
func (ec *executionContext) unmarshalNNewUser2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐNewUser(ctx context.Context, v interface{}) (NewUser, error) {
res, err := ec.unmarshalInputNewUser(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) unmarshalNSpecies2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐSpecies(ctx context.Context, v interface{}) (Species, error) {
var res Species
err := res.UnmarshalGQL(v)
@@ -3695,6 +3862,10 @@ func (ec *executionContext) marshalNString2string(ctx context.Context, sel ast.S
return res
}
func (ec *executionContext) marshalNUser2githubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐUser(ctx context.Context, sel ast.SelectionSet, v User) graphql.Marshaler {
return ec._User(ctx, sel, &v)
}
func (ec *executionContext) marshalNUser2ᚕᚖgithubᚗcomᚋKhanᚋgenqlientᚋinternalᚋintegrationᚋserverᚐUserᚄ(ctx context.Context, sel ast.SelectionSet, v []*User) graphql.Marshaler {
ret := make(graphql.Array, len(v))
var wg sync.WaitGroup
@@ -34,6 +34,10 @@ type Hair struct {
Color *string `json:"color"`
}
type NewUser struct {
Name string `json:"name"`
}
type User struct {
ID string `json:"id"`
Name string `json:"name"`
+33 -2
View File
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http/httptest"
"strconv"
"github.com/99designs/gqlgen/graphql"
"github.com/99designs/gqlgen/graphql/handler"
@@ -73,6 +74,24 @@ func beingByID(id string) Being {
return nil
}
func getNewID() string {
maxID := 0
for _, user := range users {
intID, _ := strconv.Atoi(user.ID)
if intID > maxID {
maxID = intID
}
}
for _, animal := range animals {
intID, _ := strconv.Atoi(animal.ID)
if intID > maxID {
maxID = intID
}
}
newID := maxID + 1
return strconv.Itoa(newID)
}
func (r *queryResolver) Me(ctx context.Context) (*User, error) {
return userByID("1"), nil
}
@@ -129,9 +148,16 @@ func (r *queryResolver) Fail(ctx context.Context) (*bool, error) {
return &f, fmt.Errorf("oh no")
}
func (m mutationResolver) CreateUser(ctx context.Context, input NewUser) (*User, error) {
newUser := User{ID: getNewID(), Name: input.Name, Friends: []*User{}}
users = append(users, &newUser)
return &newUser, nil
}
func RunServer() *httptest.Server {
gqlgenServer := handler.New(NewExecutableSchema(Config{Resolvers: &resolver{}}))
gqlgenServer.AddTransport(transport.POST{})
gqlgenServer.AddTransport(transport.GET{})
gqlgenServer.AroundResponses(func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response {
graphql.RegisterExtension(ctx, "foobar", "test")
return next(ctx)
@@ -140,10 +166,15 @@ func RunServer() *httptest.Server {
}
type (
resolver struct{}
queryResolver struct{}
resolver struct{}
queryResolver struct{}
mutationResolver struct{}
)
func (r *resolver) Mutation() MutationResolver {
return &mutationResolver{}
}
func (r *resolver) Query() QueryResolver { return &queryResolver{} }
//go:generate go run github.com/99designs/gqlgen