From b641cd3181b488dd56de4449a2d1c8c694e52f78 Mon Sep 17 00:00:00 2001 From: Mathew bowersox Date: Tue, 16 Jul 2019 12:21:12 -0400 Subject: [PATCH 1/9] initial commit of working new features --- .gitignore | 1 + LICENSE => APACHE_LICENSE | 0 BSD_LICENSE | 27 +++++++ field.go | 59 +++++++++++++++ generate.go | 149 ++++++++++++++++++++++++++++++++++++++ go.mod | 9 +++ go.sum | 6 ++ graphql.go | 27 ++++++- graphql_json_test.go | 39 +++++++++- 9 files changed, 313 insertions(+), 4 deletions(-) rename LICENSE => APACHE_LICENSE (100%) create mode 100644 BSD_LICENSE create mode 100644 field.go create mode 100644 generate.go create mode 100644 go.mod create mode 100644 go.sum diff --git a/.gitignore b/.gitignore index 59f1f3e..b6f9f79 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 .glide/ .idea/ +vendor diff --git a/LICENSE b/APACHE_LICENSE similarity index 100% rename from LICENSE rename to APACHE_LICENSE diff --git a/BSD_LICENSE b/BSD_LICENSE new file mode 100644 index 0000000..23b7e3e --- /dev/null +++ b/BSD_LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2012 The jflect Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of jflect. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/field.go b/field.go new file mode 100644 index 0000000..af4edb1 --- /dev/null +++ b/field.go @@ -0,0 +1,59 @@ +package graphql + +import ( + "fmt" + "unicode" +) +// this code was derived from the jflect project https://github.com/mrosset/jflect/ and subject to the BSD_LICENSE terms in the accompanied BSD_LICENCE file +// Field data type +type Field struct { + name string + gtype string + tag string +} + +// Simplifies Field construction +func NewField(name, gtype string, body ...byte) Field { + if gtype == "struct" { + gtype = fmt.Sprintf("%s {%s}", gtype, body) + } + return Field{goField(name), gtype, goTag(name)} +} + +// Provides Sorter interface so we can keep field order +type FieldSort []Field + +func (s FieldSort) Len() int { return len(s) } + +func (s FieldSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func (s FieldSort) Less(i, j int) bool { + return s[i].name < s[j].name +} + +// Return lower_case json fields to camel case fields. +func goField(jf string) string { + mkUpper := true + gf := "" + for _, c := range jf { + if mkUpper { + c = unicode.ToUpper(c) + mkUpper = false + } + if c == '_' { + mkUpper = true + continue + } + if c == '-' { + mkUpper = true + continue + } + gf += string(c) + } + return fmt.Sprintf("%s", gf) +} + +// Returns the json tag from a json field. +func goTag(jf string) string { + return fmt.Sprintf("`json:\"%s\"`", jf) +} \ No newline at end of file diff --git a/generate.go b/generate.go new file mode 100644 index 0000000..fbe5104 --- /dev/null +++ b/generate.go @@ -0,0 +1,149 @@ +package graphql + +import ( + "bytes" + "encoding/json" + "errors" + "flag" + "fmt" + "go/format" + "io" + glog "log" + "os" + "sort" +) + +// this code was derived from the jflect project https://github.com/mrosset/jflect/ and subject to the BSD_LICENSE terms in the accompanied BSD_LICENCE file +// the code was derfived from thew jflect +// TODO: write proper Usage and README +var ( + log = glog.New(os.Stderr, "", glog.Lshortfile) + fstruct = flag.String("s", "Foo", "struct name for json object") + debug = false + ErrNotValidSyntax = errors.New("Json reflection is not valid Go syntax") +) + +/*func main() { + flag.Parse() + err := read(os.Stdin, os.Stdout) + if err != nil { + log.Fatal(err) + } +}*/ + +func read(r io.Reader, w io.Writer) error { + var v interface{} + err := json.NewDecoder(r).Decode(&v) + if err != nil { + log.Println(err) + return err + } + buf := new(bytes.Buffer) + // Open struct + b, err := xreflect(v) + if err != nil { + log.Println(err) + return err + } + field := NewField(*fstruct, "struct", b...) + fmt.Fprintf(buf, "type %s %s", field.name, field.gtype) + if debug { + os.Stdout.WriteString("*********DEBUG***********") + os.Stdout.Write(buf.Bytes()) + os.Stdout.WriteString("*********DEBUG***********") + } + // Pass through gofmt for uniform formatting, and weak syntax check. + b, err = format.Source(buf.Bytes()) + if err != nil { + log.Println(err) + fmt.Println("Final Go Code") + fmt.Println() + os.Stderr.Write(buf.Bytes()) + fmt.Println() + return ErrNotValidSyntax + } + w.Write(b) + return nil +} + +func xreflect(v interface{}) ([]byte, error) { + var ( + buf = new(bytes.Buffer) + ) + fields := []Field{} + switch root := v.(type) { + case map[string]interface{}: + for key, val := range root { + switch j := val.(type) { + case nil: + // FIXME: sometimes json service will return nil even though the type is string. + // go can not convert string to nil and vs versa. Can we assume its a string? + continue + case float64: + fields = append(fields, NewField(key, "int")) + case map[string]interface{}: + // If type is map[string]interface{} then we have nested object, Recurse + o, err := xreflect(j) + if err != nil { + log.Println(err) + return nil, err + } + fields = append(fields, NewField(key, "struct", o...)) + case []interface{}: + gtype, err := sliceType(j) + if err != nil { + log.Println(err) + return nil, err + } + fields = append(fields, NewField(key, gtype)) + default: + fields = append(fields, NewField(key, fmt.Sprintf("%T", val))) + } + } + default: + return nil, fmt.Errorf("%T: unexpected type", root) + } + // Sort and write field buffer last to keep order and formatting. + sort.Sort(FieldSort(fields)) + for _, f := range fields { + fmt.Fprintf(buf, "%s %s %s\n", f.name, f.gtype, f.tag) + } + return buf.Bytes(), nil +} + +// if all entries in j are the same type, return slice of that type +func sliceType(j []interface{}) (string, error) { + dft := "[]interface{}" + if len(j) == 0 { + return dft, nil + } + var t, t2 string + for _, v := range j { + switch v.(type) { + case string: + t2 = "[]string" + case float64: + t2 = "[]int" + case map[string]interface{}: + t2 = "[]struct" + default: + // something else, just return default + return dft, nil + } + if t != "" && t != t2 { + return dft, nil + } + t = t2 + } + + if t == "[]struct" { + o, err := xreflect(j[0]) + if err != nil { + log.Println(err) + return "", err + } + f := NewField("", "struct", o...) + t = "[]" + f.gtype + } + return t, nil +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..cd3e6b2 --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module github.com/mathew-bowersox/graphql + +go 1.12 + +require ( + github.com/matryer/is v1.2.0 + github.com/pkg/errors v0.8.1 + github.com/str1ngs/jflect v0.0.0-20160330193300-13c4f307d0c9 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..a63616e --- /dev/null +++ b/go.sum @@ -0,0 +1,6 @@ +github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/str1ngs/jflect v0.0.0-20160330193300-13c4f307d0c9 h1:xNkcdpumJI0Xq7NIiHzPjUqQ8Alkk6aSNHKwrbwM2ro= +github.com/str1ngs/jflect v0.0.0-20160330193300-13c4f307d0c9/go.mod h1:L/2GZcnl0rlI9kjndy7PliIaWYvLGLKt6QGFGDxW9Tg= diff --git a/graphql.go b/graphql.go index 05c29b7..0901844 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. @@ -50,6 +52,8 @@ type Client struct { // closeReq will close the request body immediately allowing for reuse of client closeReq bool + outputRawJson bool + generateStruct bool // Log is called with various debug information. // To log to standard out, use: @@ -133,10 +137,27 @@ func (c *Client) runWithJSON(ctx context.Context, req *Request, resp interface{} } defer res.Body.Close() var buf bytes.Buffer + var fmted bytes.Buffer if _, err := io.Copy(&buf, res.Body); err != nil { return errors.Wrap(err, "reading body") } - c.logf("<< %s", buf.String()) + + if c.outputRawJson { + _ = json.Indent(&fmted,buf.Bytes(),""," ") + c.logf("%s", fmted.String()) + } + if c.generateStruct{ + var b bytes.Buffer + bufCopy :=bytes.NewBuffer(buf.Bytes()) + err = read(bufCopy,&b) + if err != nil { + return errors.Wrap(err, "while generating struct") + } + c.logf("%s",&b) + + } + + 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) diff --git a/graphql_json_test.go b/graphql_json_test.go index a973d2d..a0e3643 100644 --- a/graphql_json_test.go +++ b/graphql_json_test.go @@ -11,6 +11,13 @@ import ( "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 +38,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 +47,37 @@ func TestDoJSON(t *testing.T) { is.Equal(responseData["something"], "yes") } +func TestSimpleJsonStructGeneration(t *testing.T) { + is := is.New(t) + var calls int + 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, `{ + "data": { + "something": "yes" + } + }`) + })) + defer srv.Close() + + ctx := context.Background() + client := NewClient(srv.URL) + client.Log = func(s string) { log.Println(s) } + client.outputRawJson = true + client.generateStruct = true + 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 From 922c00fdf608fe7eff33eb5ec0e83b4ae2ed9502 Mon Sep 17 00:00:00 2001 From: Mathew bowersox Date: Tue, 16 Jul 2019 14:27:19 -0400 Subject: [PATCH 2/9] refactoring per marwans suggestions --- graphql.go | 16 ++++++---------- graphql_json_test.go | 3 +-- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/graphql.go b/graphql.go index 0901844..ab45d1b 100644 --- a/graphql.go +++ b/graphql.go @@ -49,11 +49,9 @@ type Client struct { endpoint string httpClient *http.Client useMultipartForm bool - // closeReq will close the request body immediately allowing for reuse of client closeReq bool - outputRawJson bool - generateStruct bool + GenerateStruct bool // Log is called with various debug information. // To log to standard out, use: @@ -141,15 +139,13 @@ func (c *Client) runWithJSON(ctx context.Context, req *Request, resp interface{} if _, err := io.Copy(&buf, res.Body); err != nil { return errors.Wrap(err, "reading body") } + _ = json.Indent(&fmted, buf.Bytes(), "", " ") + c.logf("%s", fmted.String()) - if c.outputRawJson { - _ = json.Indent(&fmted,buf.Bytes(),""," ") - c.logf("%s", fmted.String()) - } - if c.generateStruct{ + if c.GenerateStruct { var b bytes.Buffer - bufCopy :=bytes.NewBuffer(buf.Bytes()) - err = read(bufCopy,&b) + // bufCopy :=bytes.NewBuffer(buf.Bytes()) + err = read(&fmted, &b) if err != nil { return errors.Wrap(err, "while generating struct") } diff --git a/graphql_json_test.go b/graphql_json_test.go index a0e3643..cdf0941 100644 --- a/graphql_json_test.go +++ b/graphql_json_test.go @@ -67,8 +67,7 @@ func TestSimpleJsonStructGeneration(t *testing.T) { ctx := context.Background() client := NewClient(srv.URL) client.Log = func(s string) { log.Println(s) } - client.outputRawJson = true - client.generateStruct = true + client.GenerateStruct = true ctx, cancel := context.WithTimeout(ctx, 1*time.Second) defer cancel() responseData := SimpleResponse{} From 7e96a866205eafbb70f89dddd12d11d0bb9c3330 Mon Sep 17 00:00:00 2001 From: Mathew bowersox Date: Wed, 17 Jul 2019 12:11:02 -0400 Subject: [PATCH 3/9] added hook for processing json results and tested with modified jflect library --- .gitignore | 16 ----- field.go | 59 ----------------- generate.go | 149 ------------------------------------------- go.mod | 9 --- go.sum | 6 -- graphql.go | 10 +-- graphql_json_test.go | 9 ++- 7 files changed, 13 insertions(+), 245 deletions(-) delete mode 100644 .gitignore delete mode 100644 field.go delete mode 100644 generate.go delete mode 100644 go.mod delete mode 100644 go.sum diff --git a/.gitignore b/.gitignore deleted file mode 100644 index b6f9f79..0000000 --- a/.gitignore +++ /dev/null @@ -1,16 +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/ -vendor diff --git a/field.go b/field.go deleted file mode 100644 index af4edb1..0000000 --- a/field.go +++ /dev/null @@ -1,59 +0,0 @@ -package graphql - -import ( - "fmt" - "unicode" -) -// this code was derived from the jflect project https://github.com/mrosset/jflect/ and subject to the BSD_LICENSE terms in the accompanied BSD_LICENCE file -// Field data type -type Field struct { - name string - gtype string - tag string -} - -// Simplifies Field construction -func NewField(name, gtype string, body ...byte) Field { - if gtype == "struct" { - gtype = fmt.Sprintf("%s {%s}", gtype, body) - } - return Field{goField(name), gtype, goTag(name)} -} - -// Provides Sorter interface so we can keep field order -type FieldSort []Field - -func (s FieldSort) Len() int { return len(s) } - -func (s FieldSort) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -func (s FieldSort) Less(i, j int) bool { - return s[i].name < s[j].name -} - -// Return lower_case json fields to camel case fields. -func goField(jf string) string { - mkUpper := true - gf := "" - for _, c := range jf { - if mkUpper { - c = unicode.ToUpper(c) - mkUpper = false - } - if c == '_' { - mkUpper = true - continue - } - if c == '-' { - mkUpper = true - continue - } - gf += string(c) - } - return fmt.Sprintf("%s", gf) -} - -// Returns the json tag from a json field. -func goTag(jf string) string { - return fmt.Sprintf("`json:\"%s\"`", jf) -} \ No newline at end of file diff --git a/generate.go b/generate.go deleted file mode 100644 index fbe5104..0000000 --- a/generate.go +++ /dev/null @@ -1,149 +0,0 @@ -package graphql - -import ( - "bytes" - "encoding/json" - "errors" - "flag" - "fmt" - "go/format" - "io" - glog "log" - "os" - "sort" -) - -// this code was derived from the jflect project https://github.com/mrosset/jflect/ and subject to the BSD_LICENSE terms in the accompanied BSD_LICENCE file -// the code was derfived from thew jflect -// TODO: write proper Usage and README -var ( - log = glog.New(os.Stderr, "", glog.Lshortfile) - fstruct = flag.String("s", "Foo", "struct name for json object") - debug = false - ErrNotValidSyntax = errors.New("Json reflection is not valid Go syntax") -) - -/*func main() { - flag.Parse() - err := read(os.Stdin, os.Stdout) - if err != nil { - log.Fatal(err) - } -}*/ - -func read(r io.Reader, w io.Writer) error { - var v interface{} - err := json.NewDecoder(r).Decode(&v) - if err != nil { - log.Println(err) - return err - } - buf := new(bytes.Buffer) - // Open struct - b, err := xreflect(v) - if err != nil { - log.Println(err) - return err - } - field := NewField(*fstruct, "struct", b...) - fmt.Fprintf(buf, "type %s %s", field.name, field.gtype) - if debug { - os.Stdout.WriteString("*********DEBUG***********") - os.Stdout.Write(buf.Bytes()) - os.Stdout.WriteString("*********DEBUG***********") - } - // Pass through gofmt for uniform formatting, and weak syntax check. - b, err = format.Source(buf.Bytes()) - if err != nil { - log.Println(err) - fmt.Println("Final Go Code") - fmt.Println() - os.Stderr.Write(buf.Bytes()) - fmt.Println() - return ErrNotValidSyntax - } - w.Write(b) - return nil -} - -func xreflect(v interface{}) ([]byte, error) { - var ( - buf = new(bytes.Buffer) - ) - fields := []Field{} - switch root := v.(type) { - case map[string]interface{}: - for key, val := range root { - switch j := val.(type) { - case nil: - // FIXME: sometimes json service will return nil even though the type is string. - // go can not convert string to nil and vs versa. Can we assume its a string? - continue - case float64: - fields = append(fields, NewField(key, "int")) - case map[string]interface{}: - // If type is map[string]interface{} then we have nested object, Recurse - o, err := xreflect(j) - if err != nil { - log.Println(err) - return nil, err - } - fields = append(fields, NewField(key, "struct", o...)) - case []interface{}: - gtype, err := sliceType(j) - if err != nil { - log.Println(err) - return nil, err - } - fields = append(fields, NewField(key, gtype)) - default: - fields = append(fields, NewField(key, fmt.Sprintf("%T", val))) - } - } - default: - return nil, fmt.Errorf("%T: unexpected type", root) - } - // Sort and write field buffer last to keep order and formatting. - sort.Sort(FieldSort(fields)) - for _, f := range fields { - fmt.Fprintf(buf, "%s %s %s\n", f.name, f.gtype, f.tag) - } - return buf.Bytes(), nil -} - -// if all entries in j are the same type, return slice of that type -func sliceType(j []interface{}) (string, error) { - dft := "[]interface{}" - if len(j) == 0 { - return dft, nil - } - var t, t2 string - for _, v := range j { - switch v.(type) { - case string: - t2 = "[]string" - case float64: - t2 = "[]int" - case map[string]interface{}: - t2 = "[]struct" - default: - // something else, just return default - return dft, nil - } - if t != "" && t != t2 { - return dft, nil - } - t = t2 - } - - if t == "[]struct" { - o, err := xreflect(j[0]) - if err != nil { - log.Println(err) - return "", err - } - f := NewField("", "struct", o...) - t = "[]" + f.gtype - } - return t, nil -} \ No newline at end of file diff --git a/go.mod b/go.mod deleted file mode 100644 index cd3e6b2..0000000 --- a/go.mod +++ /dev/null @@ -1,9 +0,0 @@ -module github.com/mathew-bowersox/graphql - -go 1.12 - -require ( - github.com/matryer/is v1.2.0 - github.com/pkg/errors v0.8.1 - github.com/str1ngs/jflect v0.0.0-20160330193300-13c4f307d0c9 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index a63616e..0000000 --- a/go.sum +++ /dev/null @@ -1,6 +0,0 @@ -github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= -github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/str1ngs/jflect v0.0.0-20160330193300-13c4f307d0c9 h1:xNkcdpumJI0Xq7NIiHzPjUqQ8Alkk6aSNHKwrbwM2ro= -github.com/str1ngs/jflect v0.0.0-20160330193300-13c4f307d0c9/go.mod h1:L/2GZcnl0rlI9kjndy7PliIaWYvLGLKt6QGFGDxW9Tg= diff --git a/graphql.go b/graphql.go index ab45d1b..c10b17d 100644 --- a/graphql.go +++ b/graphql.go @@ -51,7 +51,8 @@ type Client struct { useMultipartForm bool // closeReq will close the request body immediately allowing for reuse of client closeReq bool - GenerateStruct bool + // allow clients access to raw result for post processing such as struct literal generation + ProcessResult func(r io.Reader) error // Log is called with various debug information. // To log to standard out, use: @@ -142,15 +143,14 @@ func (c *Client) runWithJSON(ctx context.Context, req *Request, resp interface{} _ = json.Indent(&fmted, buf.Bytes(), "", " ") c.logf("%s", fmted.String()) - if c.GenerateStruct { + if c.ProcessResult != nil { var b bytes.Buffer // bufCopy :=bytes.NewBuffer(buf.Bytes()) - err = read(&fmted, &b) + err = c.ProcessResult(&fmted) if err != nil { - return errors.Wrap(err, "while generating struct") + return errors.Wrap(err, "while processing json result") } c.logf("%s",&b) - } diff --git a/graphql_json_test.go b/graphql_json_test.go index cdf0941..3902d7d 100644 --- a/graphql_json_test.go +++ b/graphql_json_test.go @@ -2,12 +2,15 @@ package graphql import ( "context" + "github.com/mathew-bowersox/jflect" "io" "io/ioutil" "net/http" "net/http/httptest" + "os" "testing" "time" + "log" "github.com/matryer/is" ) @@ -67,7 +70,11 @@ func TestSimpleJsonStructGeneration(t *testing.T) { ctx := context.Background() client := NewClient(srv.URL) client.Log = func(s string) { log.Println(s) } - client.GenerateStruct = true + strNme := "Results" + client.ProcessResult = func (r io.Reader) error { + err := generate.Generate(r, os.Stdout, &strNme) + return err + } ctx, cancel := context.WithTimeout(ctx, 1*time.Second) defer cancel() responseData := SimpleResponse{} From b66935f5ea97a9916f0862e284a0388d18e75b41 Mon Sep 17 00:00:00 2001 From: Mathew bowersox Date: Wed, 17 Jul 2019 14:59:15 -0400 Subject: [PATCH 4/9] refactoring tests --- graphql.go | 27 ++++++++++++++------------- graphql_json_test.go | 41 +++++++++++++++++++++++------------------ 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/graphql.go b/graphql.go index c10b17d..872798b 100644 --- a/graphql.go +++ b/graphql.go @@ -52,7 +52,8 @@ type Client struct { // 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 - ProcessResult func(r io.Reader) error + ProcessResult func(r io.Reader) error + IndentLoggedJson bool // Log is called with various debug information. // To log to standard out, use: @@ -135,24 +136,24 @@ func (c *Client) runWithJSON(ctx context.Context, req *Request, resp interface{} return err } defer res.Body.Close() + // var buf bytes.Buffer - var fmted bytes.Buffer if _, err := io.Copy(&buf, res.Body); err != nil { return errors.Wrap(err, "reading body") } - _ = json.Indent(&fmted, buf.Bytes(), "", " ") - c.logf("%s", fmted.String()) - if c.ProcessResult != nil { - var b bytes.Buffer - // bufCopy :=bytes.NewBuffer(buf.Bytes()) - err = c.ProcessResult(&fmted) - if err != nil { - return errors.Wrap(err, "while processing json result") - } - c.logf("%s",&b) + // suport processResult Hook and tie indenting of json as well as post processing hooks to presence of client supplied logging function + + var fmted bytes.Buffer + if c.IndentLoggedJson { + _ = json.Indent(&fmted, buf.Bytes(), "", " ") + c.logf("%s", fmted.String()) } + 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 { @@ -255,7 +256,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 3902d7d..29a0d90 100644 --- a/graphql_json_test.go +++ b/graphql_json_test.go @@ -1,18 +1,16 @@ package graphql import ( + "bytes" "context" - "github.com/mathew-bowersox/jflect" + "github.com/matryer/is" "io" "io/ioutil" + "log" "net/http" "net/http/httptest" - "os" "testing" "time" - "log" - - "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 @@ -50,31 +48,41 @@ func TestDoJSON(t *testing.T) { is.Equal(responseData["something"], "yes") } -func TestSimpleJsonStructGeneration(t *testing.T) { +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, `{ - "data": { - "something": "yes" - } - }`) + 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) } - strNme := "Results" - client.ProcessResult = func (r io.Reader) error { + client.IndentLoggedJson = true + + /* + 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 - } + }*/ + 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{} @@ -82,8 +90,6 @@ func TestSimpleJsonStructGeneration(t *testing.T) { is.NoErr(err) } - - func TestDoJSONServerError(t *testing.T) { is := is.New(t) var calls int @@ -175,7 +181,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++ From 3afbef5328d37923a961040efb2ca7bdeed90c37 Mon Sep 17 00:00:00 2001 From: Mathew bowersox Date: Wed, 17 Jul 2019 16:06:38 -0400 Subject: [PATCH 5/9] refactoring tests --- graphql.go | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/graphql.go b/graphql.go index 872798b..c0b623e 100644 --- a/graphql.go +++ b/graphql.go @@ -142,17 +142,22 @@ func (c *Client) runWithJSON(ctx context.Context, req *Request, resp interface{} return errors.Wrap(err, "reading body") } - // suport processResult Hook and tie indenting of json as well as post processing hooks to presence of client supplied logging function + // suport indenting var fmted bytes.Buffer if c.IndentLoggedJson { _ = json.Indent(&fmted, buf.Bytes(), "", " ") c.logf("%s", fmted.String()) + } else { + c.logf("results: %s", buf.String()) } - err = c.ProcessResult(bytes.NewBuffer(buf.Bytes())) - if err != nil { - return errors.Wrap(err, "while processing json result") + // 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 { From e811c1ac911448cb5bc1a2b6d802c40fcb07eed4 Mon Sep 17 00:00:00 2001 From: Mathew bowersox Date: Wed, 17 Jul 2019 16:20:34 -0400 Subject: [PATCH 6/9] refactoring tests --- BSD_LICENSE | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 BSD_LICENSE diff --git a/BSD_LICENSE b/BSD_LICENSE deleted file mode 100644 index 23b7e3e..0000000 --- a/BSD_LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2012 The jflect Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of jflect. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file From 4d4c8143aec91bbab3503f73cea7c2118ade7ae0 Mon Sep 17 00:00:00 2001 From: Mathew bowersox Date: Thu, 18 Jul 2019 12:28:21 -0400 Subject: [PATCH 7/9] code cleanup and comments --- graphql.go | 5 ++--- graphql_json_test.go | 7 +++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/graphql.go b/graphql.go index c0b623e..f7d8021 100644 --- a/graphql.go +++ b/graphql.go @@ -142,10 +142,9 @@ func (c *Client) runWithJSON(ctx context.Context, req *Request, resp interface{} return errors.Wrap(err, "reading body") } - // suport indenting - - var fmted bytes.Buffer + // support indenting if c.IndentLoggedJson { + var fmted bytes.Buffer _ = json.Indent(&fmted, buf.Bytes(), "", " ") c.logf("%s", fmted.String()) } else { diff --git a/graphql_json_test.go b/graphql_json_test.go index 29a0d90..648644f 100644 --- a/graphql_json_test.go +++ b/graphql_json_test.go @@ -68,15 +68,18 @@ func TestProcessResultFunc(t *testing.T) { client.IndentLoggedJson = true /* - slightly modified fork of the jflect command line tool to allow for usage as an api + 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 + // 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) From b0cf620187ae24523f458ba03fb5d763c7f1408a Mon Sep 17 00:00:00 2001 From: Mathew bowersox Date: Thu, 18 Jul 2019 12:41:03 -0400 Subject: [PATCH 8/9] documented new features --- README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/README.md b/README.md index 2836c7e..d83f92c 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 + 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 From a16f712c2c083c6f4229e73dcfeed05c1b18ec0b Mon Sep 17 00:00:00 2001 From: Mathew bowersox Date: Thu, 18 Jul 2019 12:47:07 -0400 Subject: [PATCH 9/9] documented new features --- README.md | 2 +- graphql.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d83f92c..89b153a 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ For usage example see method TestProcessResultFunc in file graphql_json_test.go client := NewClient(srv.URL) // enable / disable logging client.Log = func(s string) { log.Println(s) } - // we like our json pretty + // we like our json pretty so this feature was added client.IndentLoggedJson = true /* diff --git a/graphql.go b/graphql.go index f7d8021..9ada20f 100644 --- a/graphql.go +++ b/graphql.go @@ -51,14 +51,14 @@ type Client struct { 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 + // 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 - IndentLoggedJson bool - // 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.