diff --git a/cmd/api.go b/cmd/api.go index 6b4780bd..0d7ea768 100644 --- a/cmd/api.go +++ b/cmd/api.go @@ -21,6 +21,7 @@ var body string var prettyPrint bool var autoPaginate int = 0 var port int +var verbose bool var generateCount int @@ -89,6 +90,7 @@ func init() { apiCmd.PersistentFlags().StringArrayVarP(&queryParameters, "query-params", "q", nil, "Available multiple times. Passes in query parameters to endpoints using the format of `key=value`.") apiCmd.PersistentFlags().StringVarP(&body, "body", "b", "", "Passes a body to the request. Alteratively supports CURL-like references to files using the format of `@data,json`.") + apiCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Whether to display HTTP request and header information above the response of the API call.") // default here is false to enable -p commands to toggle off without explicitly defining -p=false as -p false will not work. The below commands invert the bool to pass the true default. Deprecated, so marking as hidden in favor of the unformatted flag. apiCmd.PersistentFlags().BoolVarP(&prettyPrint, "pretty-print", "p", false, "Whether to pretty-print API requests. Default is true.") @@ -127,9 +129,9 @@ func cmdRun(cmd *cobra.Command, args []string) error { } if cmd.Name() == "get" && cmd.PersistentFlags().Lookup("autopaginate").Changed { - return api.NewRequest(cmd.Name(), path, queryParameters, []byte(body), !prettyPrint, &autoPaginate) + return api.NewRequest(cmd.Name(), path, queryParameters, []byte(body), !prettyPrint, &autoPaginate, verbose) } else { - return api.NewRequest(cmd.Name(), path, queryParameters, []byte(body), !prettyPrint, nil) // only set on when the user changed the flag + return api.NewRequest(cmd.Name(), path, queryParameters, []byte(body), !prettyPrint, nil, verbose) // only set on when the user changed the flag } } diff --git a/internal/api/api.go b/internal/api/api.go index d166961c..aab77f39 100644 --- a/internal/api/api.go +++ b/internal/api/api.go @@ -30,11 +30,18 @@ type clientInformation struct { } // NewRequest is used to request data from the Twitch API using a HTTP GET request- this function is a wrapper for the apiRequest function that handles the network call -func NewRequest(method string, path string, queryParameters []string, body []byte, prettyPrint bool, autopaginate *int) error { +func NewRequest(method string, path string, queryParameters []string, body []byte, prettyPrint bool, autopaginate *int, verbose bool) error { var data models.APIResponse var err error var cursor string + var requestMethod string + var requestPath string + var requestHeaders http.Header + var responseHeaders http.Header + var responseStatusCode int + var protocol string + isExtensionsLiveEndpoint := false // https://github.com/twitchdev/twitch-cli/issues/157 data.Data = make([]interface{}, 0) @@ -130,6 +137,13 @@ func NewRequest(method string, path string, queryParameters []string, body []byt if runCounter == 1 { data = apiResponse + + requestMethod = resp.HttpMethod + requestPath = resp.RequestPath + responseStatusCode = resp.StatusCode + requestHeaders = resp.RequestHeaders + responseHeaders = resp.ResponseHeaders + protocol = resp.HttpVersion } if resp.StatusCode > 299 || resp.StatusCode < 200 { @@ -210,6 +224,10 @@ func NewRequest(method string, path string, queryParameters []string, body []byt // since Command Prompt/Powershell don't support coloring, will pretty print without colors if runtime.GOOS == "windows" { s, _ := json.MarshalIndent(obj, "", " ") + + if verbose { + printVerboseHeaders(requestMethod, requestPath, requestHeaders, responseHeaders, responseStatusCode, protocol) + } if data.Error == "" { fmt.Println(string(s)) } else { @@ -226,6 +244,10 @@ func NewRequest(method string, path string, queryParameters []string, body []byt if err != nil { return err } + + if verbose { + printVerboseHeaders(requestMethod, requestPath, requestHeaders, responseHeaders, responseStatusCode, protocol) + } if data.Error == "" { fmt.Println(string(s)) } else { @@ -234,6 +256,9 @@ func NewRequest(method string, path string, queryParameters []string, body []byt return nil } + if verbose { + printVerboseHeaders(requestMethod, requestPath, requestHeaders, responseHeaders, responseStatusCode, protocol) + } if data.Error == "" { fmt.Println(string(d)) } else { @@ -297,3 +322,31 @@ func GetClientInformation() (clientInformation, error) { return clientInformation{Token: token, ClientID: clientID}, nil } + +func printVerboseHeaders(method string, path string, requestHeaders http.Header, responseHeaders http.Header, responseStatusCode int, protocol string) { + fmt.Printf("* Using %v\n", protocol) + fmt.Printf("> %v %v\n", method, path) + for key, value := range requestHeaders { + for _, v := range value { + if strings.EqualFold(key, "authorization") { + parts := strings.Split(v, " ") + if len(parts) > 1 { + fmt.Printf("> %v: %v *****\n", key, parts[0]) + } else { + fmt.Printf("> %v: *****\n", key) + } + } else { + fmt.Printf("> %v: %v\n", key, v) + } + } + } + + fmt.Printf("\n") + fmt.Printf("< %v %v %v\n", protocol, responseStatusCode, http.StatusText(responseStatusCode)) + for key, value := range responseHeaders { + for _, v := range value { + fmt.Printf("< %v: %v\n", key, v) + } + } + fmt.Printf("\n") +} diff --git a/internal/api/api_request.go b/internal/api/api_request.go index 03b84db7..3e175341 100644 --- a/internal/api/api_request.go +++ b/internal/api/api_request.go @@ -6,6 +6,7 @@ import ( "bytes" "fmt" "io/ioutil" + "net/http" "time" "github.com/twitchdev/twitch-cli/internal/request" @@ -19,6 +20,12 @@ type apiRequestParameters struct { type apiRequestResponse struct { StatusCode int Body []byte + + HttpMethod string + RequestPath string + RequestHeaders http.Header + ResponseHeaders http.Header + HttpVersion string } func apiRequest(method string, url string, payload []byte, p apiRequestParameters) (apiRequestResponse, error) { @@ -54,5 +61,11 @@ func apiRequest(method string, url string, payload []byte, p apiRequestParameter return apiRequestResponse{ StatusCode: resp.StatusCode, Body: body, + + HttpMethod: req.Method, + RequestPath: req.URL.RequestURI(), + RequestHeaders: req.Header, + ResponseHeaders: resp.Header, + HttpVersion: req.Proto, }, nil } diff --git a/internal/api/api_test.go b/internal/api/api_test.go index bd0a781c..a81fff92 100644 --- a/internal/api/api_test.go +++ b/internal/api/api_test.go @@ -56,17 +56,17 @@ func TestNewRequest(t *testing.T) { defaultAutoPaginate := 0 // tests for normal get requests - NewRequest("GET", "", []string{"test=1", "test=2"}, nil, true, nil) - NewRequest("GET", "", []string{"test=1", "test=2"}, nil, false, &defaultAutoPaginate) + NewRequest("GET", "", []string{"test=1", "test=2"}, nil, true, nil, false) + NewRequest("GET", "", []string{"test=1", "test=2"}, nil, false, &defaultAutoPaginate, false) // testing cursors autopagination - NewRequest("GET", "/cursor", []string{"test=1", "test=2"}, nil, false, &defaultAutoPaginate) + NewRequest("GET", "/cursor", []string{"test=1", "test=2"}, nil, false, &defaultAutoPaginate, false) // testing 204 no-content apis - NewRequest("POST", "/nocontent", []string{"test=1", "test=2"}, nil, false, nil) + NewRequest("POST", "/nocontent", []string{"test=1", "test=2"}, nil, false, nil, false) // testing 500 errors - NewRequest("GET", "/error", []string{"test=1", "test=2"}, nil, false, &defaultAutoPaginate) + NewRequest("GET", "/error", []string{"test=1", "test=2"}, nil, false, &defaultAutoPaginate, false) } func TestValidOptions(t *testing.T) {