From cc3a81801f9840b5fa53620389ed904eeba509fd Mon Sep 17 00:00:00 2001 From: Alex Dadgar Date: Wed, 26 Jun 2019 11:35:08 -0700 Subject: [PATCH] API Response returns specialized error type (#6984) * Return specialized error type * Add a helper for extracting gRPC codes from errors * Fix spacing in example * Drop grpc codes * Fix storing of decoded errors --- api/response.go | 81 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 62 insertions(+), 19 deletions(-) diff --git a/api/response.go b/api/response.go index f224972d58..aed2a52e08 100644 --- a/api/response.go +++ b/api/response.go @@ -42,32 +42,27 @@ func (r *Response) Error() error { r.Body.Close() r.Body = ioutil.NopCloser(bodyBuf) + // Build up the error object + respErr := &ResponseError{ + HTTPMethod: r.Request.Method, + URL: r.Request.URL.String(), + StatusCode: r.StatusCode, + } + // Decode the error response if we can. Note that we wrap the bodyBuf // in a bytes.Reader here so that the JSON decoder doesn't move the // read pointer for the original buffer. var resp ErrorResponse if err := jsonutil.DecodeJSON(bodyBuf.Bytes(), &resp); err != nil { - // Ignore the decoding error and just drop the raw response - return fmt.Errorf( - "Error making API request.\n\n"+ - "URL: %s %s\n"+ - "Code: %d. Raw Message:\n\n%s", - r.Request.Method, r.Request.URL.String(), - r.StatusCode, bodyBuf.String()) + // Store the fact that we couldn't decode the errors + respErr.RawError = true + respErr.Errors = []string{bodyBuf.String()} + } else { + // Store the decoded errors + respErr.Errors = resp.Errors } - var errBody bytes.Buffer - errBody.WriteString(fmt.Sprintf( - "Error making API request.\n\n"+ - "URL: %s %s\n"+ - "Code: %d. Errors:\n\n", - r.Request.Method, r.Request.URL.String(), - r.StatusCode)) - for _, err := range resp.Errors { - errBody.WriteString(fmt.Sprintf("* %s", err)) - } - - return fmt.Errorf(errBody.String()) + return respErr } // ErrorResponse is the raw structure of errors when they're returned by the @@ -75,3 +70,51 @@ func (r *Response) Error() error { type ErrorResponse struct { Errors []string } + +// ResponseError is the error returned when Vault responds with an error or +// non-success HTTP status code. If a request to Vault fails because of a +// network error a different error message will be returned. ResponseError gives +// access to the underlying errors and status code. +type ResponseError struct { + // HTTPMethod is the HTTP method for the request (PUT, GET, etc). + HTTPMethod string + + // URL is the URL of the request. + URL string + + // StatusCode is the HTTP status code. + StatusCode int + + // RawError marks that the underlying error messages returned by Vault were + // not parsable. The Errors slice will contain the raw response body as the + // first and only error string if this value is set to true. + RawError bool + + // Errors are the underlying errors returned by Vault. + Errors []string +} + +// Error returns a human-readable error string for the response error. +func (r *ResponseError) Error() string { + errString := "Errors" + if r.RawError { + errString = "Raw Message" + } + + var errBody bytes.Buffer + errBody.WriteString(fmt.Sprintf( + "Error making API request.\n\n"+ + "URL: %s %s\n"+ + "Code: %d. %s:\n\n", + r.HTTPMethod, r.URL, r.StatusCode, errString)) + + if r.RawError && len(r.Errors) == 1 { + errBody.WriteString(r.Errors[0]) + } else { + for _, err := range r.Errors { + errBody.WriteString(fmt.Sprintf("* %s", err)) + } + } + + return errBody.String() +}