139 lines
3.8 KiB
Go
139 lines
3.8 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package api
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
)
|
|
|
|
// Response is a raw response that wraps an HTTP response.
|
|
type Response struct {
|
|
*http.Response
|
|
}
|
|
|
|
// DecodeJSON will decode the response body to a JSON structure. This
|
|
// will consume the response body, but will not close it. Close must
|
|
// still be called.
|
|
func (r *Response) DecodeJSON(out interface{}) error {
|
|
dec := json.NewDecoder(r.Body)
|
|
dec.UseNumber()
|
|
return dec.Decode(out)
|
|
}
|
|
|
|
// Error returns an error response if there is one. If there is an error,
|
|
// this will fully consume the response body, but will not close it. The
|
|
// body must still be closed manually.
|
|
func (r *Response) Error() error {
|
|
// 200 to 399 are okay status codes. 429 is the code for health status of
|
|
// standby nodes, otherwise, 429 is treated as quota limit reached.
|
|
if (r.StatusCode >= 200 && r.StatusCode < 400) || (r.StatusCode == 429 && r.Request.URL.Path == "/v1/sys/health") {
|
|
return nil
|
|
}
|
|
|
|
// We have an error. Let's copy the body into our own buffer first,
|
|
// so that if we can't decode JSON, we can at least copy it raw.
|
|
bodyBuf := &bytes.Buffer{}
|
|
if _, err := io.Copy(bodyBuf, r.Body); err != nil {
|
|
return err
|
|
}
|
|
|
|
r.Body.Close()
|
|
r.Body = ioutil.NopCloser(bodyBuf)
|
|
ns := r.Header.Get(NamespaceHeaderName)
|
|
|
|
// Build up the error object
|
|
respErr := &ResponseError{
|
|
HTTPMethod: r.Request.Method,
|
|
URL: r.Request.URL.String(),
|
|
StatusCode: r.StatusCode,
|
|
NamespacePath: ns,
|
|
}
|
|
|
|
// 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
|
|
dec := json.NewDecoder(bytes.NewReader(bodyBuf.Bytes()))
|
|
dec.UseNumber()
|
|
if err := dec.Decode(&resp); err != nil {
|
|
// 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
|
|
}
|
|
|
|
return respErr
|
|
}
|
|
|
|
// ErrorResponse is the raw structure of errors when they're returned by the
|
|
// HTTP API.
|
|
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
|
|
|
|
// Namespace path to be reported to the client if it is set to anything other
|
|
// than root
|
|
NamespacePath 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 ns string
|
|
if r.NamespacePath != "" && r.NamespacePath != "root/" {
|
|
ns = "Namespace: " + r.NamespacePath + "\n"
|
|
}
|
|
|
|
var errBody bytes.Buffer
|
|
errBody.WriteString(fmt.Sprintf(
|
|
"Error making API request.\n\n"+
|
|
ns+
|
|
"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()
|
|
}
|