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

Generic generate method #43

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
Open
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
15 changes: 0 additions & 15 deletions .gitignore

This file was deleted.

File renamed without changes.
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,36 @@ use multipart form data instead using the `UseMultipartForm` option when you cre
client := graphql.NewClient("https://machinebox.io/graphql", graphql.UseMultipartForm())
```

### Access to raw json response via user supplied function in client
For usage example see method TestProcessResultFunc in file graphql_json_test.go
```go
client := NewClient(srv.URL)
// enable / disable logging
client.Log = func(s string) { log.Println(s) }
// we like our json pretty so this feature was added
client.IndentLoggedJson = true

/*
example of a usage to code generate target response struct
// slightly modified fork of the jflect command line tool to allow for usage as an api
"github.com/mathew-bowersox/jflect"

// example of processing the results json into a struct literal
strNme := "Results"
client.ProcessResult = func (r io.Reader) error {
err := generate.Generate(r, os.Stdout, &strNme)
return err
}*/

// here we will test the supplied reader contains correct results
client.ProcessResult = func (r io.Reader) error {
b := new(bytes.Buffer)
_ ,err := io.Copy(b,r)
is.True(res == b.String())
return err
}
```

For more information, [read the godoc package documentation](http://godoc.org/github.com/machinebox/graphql) or the [blog post](https://blog.machinebox.io/a-graphql-client-library-for-go-5bffd0455878).

## Thanks
Expand Down
34 changes: 28 additions & 6 deletions graphql.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,33 +28,37 @@
// To specify your own http.Client, use the WithHTTPClient option:
// httpclient := &http.Client{}
// client := graphql.NewClient("https://machinebox.io/graphql", graphql.WithHTTPClient(httpclient))

// the code in this file is derived from the machinebox graphql project code and subject to licensing terms in included APACHE_LICENSE

package graphql

import (
"bytes"
"context"
"encoding/json"
"fmt"
"github.com/pkg/errors"
"io"
"mime/multipart"
"net/http"

"github.com/pkg/errors"
)

// Client is a client for interacting with a GraphQL API.
type Client struct {
endpoint string
httpClient *http.Client
useMultipartForm bool

// closeReq will close the request body immediately allowing for reuse of client
closeReq bool

// allow clients access to raw result for post processing such as struct literal generation, adding a query cache etc.
ProcessResult func(r io.Reader) error
// Log is called with various debug information.
// To log to standard out, use:
// client.Log = func(s string) { log.Println(s) }
Log func(s string)
// if a log function is supplied this flag will control weather json is indented or not
IndentLoggedJson bool
}

// NewClient makes a new Client capable of making GraphQL requests.
Expand Down Expand Up @@ -132,11 +136,29 @@ func (c *Client) runWithJSON(ctx context.Context, req *Request, resp interface{}
return err
}
defer res.Body.Close()
//
var buf bytes.Buffer
if _, err := io.Copy(&buf, res.Body); err != nil {
return errors.Wrap(err, "reading body")
}
c.logf("<< %s", buf.String())

// support indenting
if c.IndentLoggedJson {
var fmted bytes.Buffer
_ = json.Indent(&fmted, buf.Bytes(), "", " ")
c.logf("%s", fmted.String())
} else {
c.logf("results: %s", buf.String())
}

// support ProcessResult client supplied function if not nil
if c.ProcessResult != nil {
err = c.ProcessResult(bytes.NewBuffer(buf.Bytes()))
if err != nil {
return errors.Wrap(err, "while processing json result")
}
}

if err := json.NewDecoder(&buf).Decode(&gr); err != nil {
if res.StatusCode != http.StatusOK {
return fmt.Errorf("graphql: server returned a non-200 status code: %v", res.StatusCode)
Expand Down Expand Up @@ -238,7 +260,7 @@ func UseMultipartForm() ClientOption {
}
}

//ImmediatelyCloseReqBody will close the req body immediately after each request body is ready
// ImmediatelyCloseReqBody will close the req body immediately after each request body is ready
func ImmediatelyCloseReqBody() ClientOption {
return func(client *Client) {
client.closeReq = true
Expand Down
59 changes: 55 additions & 4 deletions graphql_json_test.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
package graphql

import (
"bytes"
"context"
"github.com/matryer/is"
"io"
"io/ioutil"
"log"
"net/http"
"net/http/httptest"
"testing"
"time"

"github.com/matryer/is"
)
// the code in this file is derived from the machinebox graphql project code and subject to licensing terms in included APACHE_LICENSE

type SimpleResponse struct {
Data struct {
Something string `json:"something"`
} `json:"data"`
}

func TestDoJSON(t *testing.T) {
is := is.New(t)
Expand All @@ -31,7 +39,6 @@ func TestDoJSON(t *testing.T) {

ctx := context.Background()
client := NewClient(srv.URL)

ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel()
var responseData map[string]interface{}
Expand All @@ -41,6 +48,51 @@ func TestDoJSON(t *testing.T) {
is.Equal(responseData["something"], "yes")
}

func TestProcessResultFunc(t *testing.T) {
is := is.New(t)
var calls int
const res = `{ "data": { "something": "yes" } }`
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls++
is.Equal(r.Method, http.MethodPost)
b, err := ioutil.ReadAll(r.Body)
is.NoErr(err)
is.Equal(string(b), `{"query":"query {}","variables":null}`+"\n")
io.WriteString(w, res)
}))
defer srv.Close()
ctx := context.Background()
client := NewClient(srv.URL)
// enable / disable logging
client.Log = func(s string) { log.Println(s) }
client.IndentLoggedJson = true

/*
example of a usage to code generate target response struct
// slightly modified fork of the jflect command line tool to allow for usage as an api
"github.com/mathew-bowersox/jflect"

// example of processing the results json into a struct literal
strNme := "Results"
client.ProcessResult = func (r io.Reader) error {
err := generate.Generate(r, os.Stdout, &strNme)
return err
}*/

// here we will test the supllied reader contains correct results
client.ProcessResult = func (r io.Reader) error {
b := new(bytes.Buffer)
_ ,err := io.Copy(b,r)
is.True(res == b.String())
return err
}
ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
defer cancel()
responseData := SimpleResponse{}
err := client.Run(ctx, &Request{q: "query {}"}, &responseData)
is.NoErr(err)
}

func TestDoJSONServerError(t *testing.T) {
is := is.New(t)
var calls int
Expand Down Expand Up @@ -132,7 +184,6 @@ func TestQueryJSON(t *testing.T) {

func TestHeader(t *testing.T) {
is := is.New(t)

var calls int
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls++
Expand Down