diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 59f1f3e..0000000 --- a/.gitignore +++ /dev/null @@ -1,15 +0,0 @@ -# Binaries for programs and plugins -*.exe -*.dll -*.so -*.dylib - -# Test binary, build with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 -.glide/ -.idea/ diff --git a/LICENSE b/APACHE_LICENSE similarity index 100% rename from LICENSE rename to APACHE_LICENSE diff --git a/README.md b/README.md index 2836c7e..89b153a 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/graphql.go b/graphql.go index 05c29b7..9ada20f 100644 --- a/graphql.go +++ b/graphql.go @@ -28,6 +28,9 @@ // 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 ( @@ -35,11 +38,10 @@ import ( "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. @@ -47,14 +49,16 @@ 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. @@ -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) @@ -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 diff --git a/graphql_json_test.go b/graphql_json_test.go index a973d2d..648644f 100644 --- a/graphql_json_test.go +++ b/graphql_json_test.go @@ -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) @@ -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{} @@ -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 @@ -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++