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

Automatically determine scalar types. #4

Merged
merged 1 commit into from
Nov 27, 2017
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Usage
Construct a GraphQL client, specifying the GraphQL server URL. Then, you can use it to make GraphQL queries and mutations.

```Go
client := graphql.NewClient("https://example.com/graphql", nil, nil)
client := graphql.NewClient("https://example.com/graphql", nil)
// Use client...
```

Expand All @@ -41,7 +41,7 @@ func main() {
)
httpClient := oauth2.NewClient(context.Background(), src)

client := graphql.NewClient("https://example.com/graphql", httpClient, nil)
client := graphql.NewClient("https://example.com/graphql", httpClient)
// Use client...
```

Expand Down
2 changes: 1 addition & 1 deletion example/graphqldev/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func run() error {
mux := http.NewServeMux()
mux.Handle("/query", &relay.Handler{Schema: schema})

client := graphql.NewClient("/query", &http.Client{Transport: localRoundTripper{handler: mux}}, nil)
client := graphql.NewClient("/query", &http.Client{Transport: localRoundTripper{handler: mux}})

/*
query {
Expand Down
15 changes: 3 additions & 12 deletions graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"encoding/json"
"fmt"
"net/http"
"reflect"

"github.com/shurcooL/go/ctxhttp"
"github.com/shurcooL/graphql/internal/jsonutil"
Expand All @@ -16,25 +15,17 @@ import (
type Client struct {
url string // GraphQL server URL.
httpClient *http.Client

qctx *queryContext
}

// NewClient creates a GraphQL client targeting the specified GraphQL server URL.
// If httpClient is nil, then http.DefaultClient is used.
// scalars optionally specifies types that are scalars (this matters
// when constructing queries from types, scalars are never expanded).
func NewClient(url string, httpClient *http.Client, scalars []reflect.Type) *Client {
func NewClient(url string, httpClient *http.Client) *Client {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

apicompat: breaking change parameter types changed

if httpClient == nil {
httpClient = http.DefaultClient
}
return &Client{
url: url,
httpClient: httpClient,

qctx: &queryContext{
Scalars: scalars,
},
}
}

Expand All @@ -57,9 +48,9 @@ func (c *Client) do(ctx context.Context, op operationType, v interface{}, variab
var query string
switch op {
case queryOperation:
query = constructQuery(c.qctx, v, variables)
query = constructQuery(v, variables)
case mutationOperation:
query = constructMutation(c.qctx, v, variables)
query = constructMutation(v, variables)
}
in := struct {
Query string `json:"query"`
Expand Down
36 changes: 16 additions & 20 deletions query.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@ package graphql

import (
"bytes"
"encoding/json"
"io"
"reflect"
"sort"

"github.com/shurcooL/graphql/ident"
)

func constructQuery(qctx *queryContext, v interface{}, variables map[string]interface{}) string {
query := qctx.Query(v)
func constructQuery(v interface{}, variables map[string]interface{}) string {
query := query(v)
if variables != nil {
return "query(" + queryArguments(variables) + ")" + query
}
return query
}

func constructMutation(qctx *queryContext, v interface{}, variables map[string]interface{}) string {
query := qctx.Query(v)
func constructMutation(v interface{}, variables map[string]interface{}) string {
query := query(v)
if variables != nil {
return "mutation(" + queryArguments(variables) + ")" + query
}
Expand Down Expand Up @@ -58,33 +59,26 @@ func queryArguments(variables map[string]interface{}) string {
return s
}

type queryContext struct {
// Scalars are Go types that map to GraphQL scalars, and therefore we don't want to expand them.
Scalars []reflect.Type
}

// Query uses writeQuery to recursively construct
// query uses writeQuery to recursively construct
// a minified query string from the provided struct v.
//
// E.g., struct{Foo Int, BarBaz *Boolean} -> "{foo,barBaz}".
func (c *queryContext) Query(v interface{}) string {
func query(v interface{}) string {
var buf bytes.Buffer
c.writeQuery(&buf, reflect.TypeOf(v), false)
writeQuery(&buf, reflect.TypeOf(v), false)
return buf.String()
}

// writeQuery writes a minified query for t to w. If inline is true,
// the struct fields of t are inlined into parent struct.
func (c *queryContext) writeQuery(w io.Writer, t reflect.Type, inline bool) {
func writeQuery(w io.Writer, t reflect.Type, inline bool) {
switch t.Kind() {
case reflect.Ptr, reflect.Slice:
c.writeQuery(w, t.Elem(), false)
writeQuery(w, t.Elem(), false)
case reflect.Struct:
// Special handling of scalar struct types. Don't expand them.
for _, scalar := range c.Scalars {
if t == scalar {
return
}
// If the type implements json.Unmarshaler, it's a scalar. Don't expand it.
if reflect.PtrTo(t).Implements(jsonUnmarshaler) {
return
}
if !inline {
io.WriteString(w, "{")
Expand All @@ -103,10 +97,12 @@ func (c *queryContext) writeQuery(w io.Writer, t reflect.Type, inline bool) {
io.WriteString(w, ident.ParseMixedCaps(f.Name).ToLowerCamelCase())
}
}
c.writeQuery(w, f.Type, inlineField)
writeQuery(w, f.Type, inlineField)
}
if !inline {
io.WriteString(w, "}")
}
}
}

var jsonUnmarshaler = reflect.TypeOf((*json.Unmarshaler)(nil)).Elem()
24 changes: 15 additions & 9 deletions query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package graphql

import (
"net/url"
"reflect"
"testing"
"time"
)
Expand Down Expand Up @@ -217,15 +216,20 @@ func TestConstructQuery(t *testing.T) {
}(),
want: `{actor{login,avatarUrl,url},createdAt,... on IssueComment{body},currentTitle,previousTitle,label{name,color}}`,
},
{
inV: struct {
Viewer struct {
Login string
CreatedAt time.Time
ID interface{}
DatabaseID int
}
}{},
want: `{viewer{login,createdAt,id,databaseId}}`,
},
}
for _, tc := range tests {
qctx := &queryContext{
Scalars: []reflect.Type{
reflect.TypeOf(DateTime{}),
reflect.TypeOf(URI{}),
},
}
got := constructQuery(qctx, tc.inV, tc.inVariables)
got := constructQuery(tc.inV, tc.inVariables)
if got != tc.want {
t.Errorf("\ngot: %q\nwant: %q\n", got, tc.want)
}
Expand Down Expand Up @@ -260,7 +264,7 @@ func TestConstructMutation(t *testing.T) {
},
}
for _, tc := range tests {
got := constructMutation(&queryContext{}, tc.inV, tc.inVariables)
got := constructMutation(tc.inV, tc.inVariables)
if got != tc.want {
t.Errorf("\ngot: %q\nwant: %q\n", got, tc.want)
}
Expand Down Expand Up @@ -311,6 +315,8 @@ type (
URI struct{ *url.URL }
)

func (u *URI) UnmarshalJSON(data []byte) error { panic("mock implementation") }

// IssueState represents the possible states of an issue.
type IssueState string

Expand Down