influxdb/http/errors.go

139 lines
4.0 KiB
Go

package http
import (
"bytes"
"context"
"encoding/json"
stderrors "errors"
"fmt"
"io"
"net/http"
platform "github.com/influxdata/influxdb"
"github.com/pkg/errors"
)
const (
// PlatformErrorCodeHeader shows the error code of platform error.
PlatformErrorCodeHeader = "X-Platform-Error-Code"
)
// AuthzError is returned for authorization errors. When this error type is returned,
// the user can be presented with a generic "authorization failed" error, but
// the system can log the underlying AuthzError() so that operators have insight
// into what actually failed with authorization.
type AuthzError interface {
error
AuthzError() error
}
// CheckErrorStatus for status and any error in the response.
func CheckErrorStatus(code int, res *http.Response) error {
err := CheckError(res)
if err != nil {
return err
}
if res.StatusCode != code {
return fmt.Errorf("unexpected status code: %s", res.Status)
}
return nil
}
// CheckError reads the http.Response and returns an error if one exists.
// It will automatically recognize the errors returned by Influx services
// and decode the error into an internal error type. If the error cannot
// be determined in that way, it will create a generic error message.
//
// If there is no error, then this returns nil.
func CheckError(resp *http.Response) (err error) {
switch resp.StatusCode / 100 {
case 4, 5:
// We will attempt to parse this error outside of this block.
case 2:
return nil
default:
// TODO(jsternberg): Figure out what to do here?
return &platform.Error{
Code: platform.EInternal,
Msg: fmt.Sprintf("unexpected status code: %d %s", resp.StatusCode, resp.Status),
}
}
pe := new(platform.Error)
var buf bytes.Buffer
if _, err := io.Copy(&buf, resp.Body); err != nil {
return &platform.Error{
Code: platform.EInternal,
Msg: err.Error(),
}
}
parseErr := json.Unmarshal(buf.Bytes(), pe)
if parseErr != nil {
return errors.Wrap(stderrors.New(buf.String()), parseErr.Error())
}
return pe
}
// ErrorHandler is the error handler in http package.
type ErrorHandler int
// HandleHTTPError encodes err with the appropriate status code and format,
// sets the X-Platform-Error-Code headers on the response.
// We're no longer using X-Influx-Error and X-Influx-Reference.
// and sets the response status to the corresponding status code.
func (h ErrorHandler) HandleHTTPError(ctx context.Context, err error, w http.ResponseWriter) {
if err == nil {
return
}
code := platform.ErrorCode(err)
httpCode, ok := statusCodePlatformError[code]
if !ok {
httpCode = http.StatusBadRequest
}
w.Header().Set(PlatformErrorCodeHeader, code)
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(httpCode)
var e error
if pe, ok := err.(*platform.Error); ok {
e = &platform.Error{
Code: code,
Op: platform.ErrorOp(err),
Msg: platform.ErrorMessage(err),
Err: pe.Err,
}
} else {
e = &platform.Error{
Code: platform.EInternal,
Err: err,
}
}
b, _ := json.Marshal(e)
_, _ = w.Write(b)
}
// UnauthorizedError encodes a error message and status code for unauthorized access.
func UnauthorizedError(ctx context.Context, h platform.HTTPErrorHandler, w http.ResponseWriter) {
h.HandleHTTPError(ctx, &platform.Error{
Code: platform.EUnauthorized,
Msg: "unauthorized access",
}, w)
}
// statusCodePlatformError is the map convert platform.Error to error
var statusCodePlatformError = map[string]int{
platform.EInternal: http.StatusInternalServerError,
platform.EInvalid: http.StatusBadRequest,
platform.EUnprocessableEntity: http.StatusUnprocessableEntity,
platform.EEmptyValue: http.StatusBadRequest,
platform.EConflict: http.StatusUnprocessableEntity,
platform.ENotFound: http.StatusNotFound,
platform.EUnavailable: http.StatusServiceUnavailable,
platform.EForbidden: http.StatusForbidden,
platform.ETooManyRequests: http.StatusTooManyRequests,
platform.EUnauthorized: http.StatusUnauthorized,
platform.EMethodNotAllowed: http.StatusMethodNotAllowed,
}