1package api
2
3import (
4	"bytes"
5	"fmt"
6	"io"
7	"io/ioutil"
8	"net/http"
9
10	"github.com/hashicorp/vault/sdk/helper/jsonutil"
11)
12
13// Response is a raw response that wraps an HTTP response.
14type Response struct {
15	*http.Response
16}
17
18// DecodeJSON will decode the response body to a JSON structure. This
19// will consume the response body, but will not close it. Close must
20// still be called.
21func (r *Response) DecodeJSON(out interface{}) error {
22	return jsonutil.DecodeJSONFromReader(r.Body, out)
23}
24
25// Error returns an error response if there is one. If there is an error,
26// this will fully consume the response body, but will not close it. The
27// body must still be closed manually.
28func (r *Response) Error() error {
29	// 200 to 399 are okay status codes. 429 is the code for health status of
30	// standby nodes.
31	if (r.StatusCode >= 200 && r.StatusCode < 400) || r.StatusCode == 429 {
32		return nil
33	}
34
35	// We have an error. Let's copy the body into our own buffer first,
36	// so that if we can't decode JSON, we can at least copy it raw.
37	bodyBuf := &bytes.Buffer{}
38	if _, err := io.Copy(bodyBuf, r.Body); err != nil {
39		return err
40	}
41
42	r.Body.Close()
43	r.Body = ioutil.NopCloser(bodyBuf)
44
45	// Build up the error object
46	respErr := &ResponseError{
47		HTTPMethod: r.Request.Method,
48		URL:        r.Request.URL.String(),
49		StatusCode: r.StatusCode,
50	}
51
52	// Decode the error response if we can. Note that we wrap the bodyBuf
53	// in a bytes.Reader here so that the JSON decoder doesn't move the
54	// read pointer for the original buffer.
55	var resp ErrorResponse
56	if err := jsonutil.DecodeJSON(bodyBuf.Bytes(), &resp); err != nil {
57		// Store the fact that we couldn't decode the errors
58		respErr.RawError = true
59		respErr.Errors = []string{bodyBuf.String()}
60	} else {
61		// Store the decoded errors
62		respErr.Errors = resp.Errors
63	}
64
65	return respErr
66}
67
68// ErrorResponse is the raw structure of errors when they're returned by the
69// HTTP API.
70type ErrorResponse struct {
71	Errors []string
72}
73
74// ResponseError is the error returned when Vault responds with an error or
75// non-success HTTP status code. If a request to Vault fails because of a
76// network error a different error message will be returned. ResponseError gives
77// access to the underlying errors and status code.
78type ResponseError struct {
79	// HTTPMethod is the HTTP method for the request (PUT, GET, etc).
80	HTTPMethod string
81
82	// URL is the URL of the request.
83	URL string
84
85	// StatusCode is the HTTP status code.
86	StatusCode int
87
88	// RawError marks that the underlying error messages returned by Vault were
89	// not parsable. The Errors slice will contain the raw response body as the
90	// first and only error string if this value is set to true.
91	RawError bool
92
93	// Errors are the underlying errors returned by Vault.
94	Errors []string
95}
96
97// Error returns a human-readable error string for the response error.
98func (r *ResponseError) Error() string {
99	errString := "Errors"
100	if r.RawError {
101		errString = "Raw Message"
102	}
103
104	var errBody bytes.Buffer
105	errBody.WriteString(fmt.Sprintf(
106		"Error making API request.\n\n"+
107			"URL: %s %s\n"+
108			"Code: %d. %s:\n\n",
109		r.HTTPMethod, r.URL, r.StatusCode, errString))
110
111	if r.RawError && len(r.Errors) == 1 {
112		errBody.WriteString(r.Errors[0])
113	} else {
114		for _, err := range r.Errors {
115			errBody.WriteString(fmt.Sprintf("* %s", err))
116		}
117	}
118
119	return errBody.String()
120}
121