@@ -4,47 +4,78 @@ import (
44 "bytes"
55 "encoding/json"
66 "fmt"
7+ "io"
78 "io/ioutil"
89 "net/http"
9- "os"
10-
11- "github.com/github/gh-cli/context"
12- "github.com/github/gh-cli/version"
1310)
1411
15- type graphQLResponse struct {
16- Data interface {}
17- Errors []struct {
18- Message string
12+ // ClientOption represents an argument to NewClient
13+ type ClientOption = func (http.RoundTripper ) http.RoundTripper
14+
15+ // NewClient initializes a Client
16+ func NewClient (opts ... ClientOption ) * Client {
17+ tr := http .DefaultTransport
18+ for _ , opt := range opts {
19+ tr = opt (tr )
1920 }
21+ http := & http.Client {Transport : tr }
22+ client := & Client {http : http }
23+ return client
2024}
2125
22- /*
23- GraphQL: Declared as an external variable so it can be mocked in tests
26+ // AddHeader turns a RoundTripper into one that adds a request header
27+ func AddHeader (name , value string ) ClientOption {
28+ return func (tr http.RoundTripper ) http.RoundTripper {
29+ return & funcTripper {roundTrip : func (req * http.Request ) (* http.Response , error ) {
30+ req .Header .Add (name , value )
31+ return tr .RoundTrip (req )
32+ }}
33+ }
34+ }
2435
25- type repoResponse struct {
26- Repository struct {
27- CreatedAt string
36+ // VerboseLog enables request/response logging within a RoundTripper
37+ func VerboseLog (out io.Writer ) ClientOption {
38+ return func (tr http.RoundTripper ) http.RoundTripper {
39+ return & funcTripper {roundTrip : func (req * http.Request ) (* http.Response , error ) {
40+ fmt .Fprintf (out , "> %s %s\n " , req .Method , req .URL .RequestURI ())
41+ res , err := tr .RoundTrip (req )
42+ if err == nil {
43+ fmt .Fprintf (out , "< HTTP %s\n " , res .Status )
44+ }
45+ return res , err
46+ }}
2847 }
2948}
3049
31- query := `query {
32- repository(owner: "golang", name: "go") {
33- createdAt
50+ // ReplaceTripper substitutes the underlying RoundTripper with a custom one
51+ func ReplaceTripper (tr http.RoundTripper ) ClientOption {
52+ return func (http.RoundTripper ) http.RoundTripper {
53+ return tr
3454 }
35- }`
55+ }
3656
37- variables := map[string]string{}
57+ type funcTripper struct {
58+ roundTrip func (* http.Request ) (* http.Response , error )
59+ }
60+
61+ func (tr funcTripper ) RoundTrip (req * http.Request ) (* http.Response , error ) {
62+ return tr .roundTrip (req )
63+ }
3864
39- var resp repoResponse
40- err := graphql(query, map[string]string{}, &resp)
41- if err != nil {
42- panic(err)
65+ // Client facilitates making HTTP requests to the GitHub API
66+ type Client struct {
67+ http * http.Client
4368}
4469
45- fmt.Printf("%+v\n", resp)
46- */
47- var GraphQL = func (query string , variables map [string ]string , data interface {}) error {
70+ type graphQLResponse struct {
71+ Data interface {}
72+ Errors []struct {
73+ Message string
74+ }
75+ }
76+
77+ // GraphQL performs a GraphQL request and parses the response
78+ func (c Client ) GraphQL (query string , variables map [string ]interface {}, data interface {}) error {
4879 url := "https://api.github.com/graphql"
4980 reqBody , err := json .Marshal (map [string ]interface {}{"query" : query , "variables" : variables })
5081 if err != nil {
@@ -56,42 +87,31 @@ var GraphQL = func(query string, variables map[string]string, data interface{})
5687 return err
5788 }
5889
59- token , err := context .Current ().AuthToken ()
60- if err != nil {
61- return err
62- }
63-
64- req .Header .Set ("Authorization" , "token " + token )
6590 req .Header .Set ("Content-Type" , "application/json; charset=utf-8" )
66- req .Header .Set ("User-Agent" , "GitHub CLI " + version .Version )
6791
68- debugRequest (req , string (reqBody ))
69-
70- client := & http.Client {}
71- resp , err := client .Do (req )
92+ resp , err := c .http .Do (req )
7293 if err != nil {
7394 return err
7495 }
7596 defer resp .Body .Close ()
7697
77- body , err := ioutil .ReadAll (resp .Body )
78- if err != nil {
79- return err
80- }
81-
82- debugResponse (resp , string (body ))
83- return handleResponse (resp , body , data )
98+ return handleResponse (resp , data )
8499}
85100
86- func handleResponse (resp * http.Response , body [] byte , data interface {}) error {
101+ func handleResponse (resp * http.Response , data interface {}) error {
87102 success := resp .StatusCode >= 200 && resp .StatusCode < 300
88103
89104 if ! success {
90- return handleHTTPError (resp , body )
105+ return handleHTTPError (resp )
106+ }
107+
108+ body , err := ioutil .ReadAll (resp .Body )
109+ if err != nil {
110+ return err
91111 }
92112
93113 gr := & graphQLResponse {Data : data }
94- err : = json .Unmarshal (body , & gr )
114+ err = json .Unmarshal (body , & gr )
95115 if err != nil {
96116 return err
97117 }
@@ -107,12 +127,16 @@ func handleResponse(resp *http.Response, body []byte, data interface{}) error {
107127
108128}
109129
110- func handleHTTPError (resp * http.Response , body [] byte ) error {
130+ func handleHTTPError (resp * http.Response ) error {
111131 var message string
112132 var parsedBody struct {
113133 Message string `json:"message"`
114134 }
115- err := json .Unmarshal (body , & parsedBody )
135+ body , err := ioutil .ReadAll (resp .Body )
136+ if err != nil {
137+ return err
138+ }
139+ err = json .Unmarshal (body , & parsedBody )
116140 if err != nil {
117141 message = string (body )
118142 } else {
@@ -121,19 +145,3 @@ func handleHTTPError(resp *http.Response, body []byte) error {
121145
122146 return fmt .Errorf ("http error, '%s' failed (%d): '%s'" , resp .Request .URL , resp .StatusCode , message )
123147}
124-
125- func debugRequest (req * http.Request , body string ) {
126- if _ , ok := os .LookupEnv ("DEBUG" ); ! ok {
127- return
128- }
129-
130- fmt .Printf ("DEBUG: GraphQL request to %s:\n %s\n \n " , req .URL , body )
131- }
132-
133- func debugResponse (resp * http.Response , body string ) {
134- if _ , ok := os .LookupEnv ("DEBUG" ); ! ok {
135- return
136- }
137-
138- fmt .Printf ("DEBUG: GraphQL response:\n %+v\n \n %s\n \n " , resp , body )
139- }
0 commit comments