Add httputils.FromStatusCode()

This utility allows a client to convert an API response
back to a typed error; allowing the client to perform
different actions based on the type of error, without
having to resort to string-matching the error.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2018-12-31 13:59:59 +01:00
parent 87d593639c
commit 1af30c50ca
No known key found for this signature in database
GPG key ID: 76698F39D527CE8C
2 changed files with 139 additions and 0 deletions

View file

@ -76,6 +76,52 @@ func GetHTTPErrorStatusCode(err error) int {
return statusCode
}
// FromStatusCode creates an errdef error, based on the provided status-code
func FromStatusCode(err error, statusCode int) error {
if err == nil {
return err
}
switch statusCode {
case http.StatusNotFound:
err = errdefs.NotFound(err)
case http.StatusBadRequest:
err = errdefs.InvalidParameter(err)
case http.StatusConflict:
err = errdefs.Conflict(err)
case http.StatusUnauthorized:
err = errdefs.Unauthorized(err)
case http.StatusServiceUnavailable:
err = errdefs.Unavailable(err)
case http.StatusForbidden:
err = errdefs.Forbidden(err)
case http.StatusNotModified:
err = errdefs.NotModified(err)
case http.StatusNotImplemented:
err = errdefs.NotImplemented(err)
case http.StatusInternalServerError:
if !errdefs.IsSystem(err) && !errdefs.IsUnknown(err) && !errdefs.IsDataLoss(err) && !errdefs.IsDeadline(err) && !errdefs.IsCancelled(err) {
err = errdefs.System(err)
}
default:
logrus.WithFields(logrus.Fields{
"module": "api",
"status_code": fmt.Sprintf("%d", statusCode),
}).Debugf("FIXME: Got an status-code for which error does not match any expected type!!!: %d", statusCode)
switch {
case statusCode >= 200 && statusCode < 400:
// it's a client error
case statusCode >= 400 && statusCode < 500:
err = errdefs.InvalidParameter(err)
case statusCode >= 500 && statusCode < 600:
err = errdefs.System(err)
default:
err = errdefs.Unknown(err)
}
}
return err
}
func apiVersionSupportsJSONErrors(version string) bool {
const firstAPIVersionWithJSONErrors = "1.23"
return version == "" || versions.GreaterThan(version, firstAPIVersionWithJSONErrors)

View file

@ -0,0 +1,93 @@
package httputils
import (
"fmt"
"net/http"
"testing"
"github.com/docker/docker/errdefs"
"gotest.tools/assert"
)
func TestFromStatusCode(t *testing.T) {
testErr := fmt.Errorf("some error occurred")
testCases := []struct {
err error
status int
check func(error) bool
}{
{
err: testErr,
status: http.StatusNotFound,
check: errdefs.IsNotFound,
},
{
err: testErr,
status: http.StatusBadRequest,
check: errdefs.IsInvalidParameter,
},
{
err: testErr,
status: http.StatusConflict,
check: errdefs.IsConflict,
},
{
err: testErr,
status: http.StatusUnauthorized,
check: errdefs.IsUnauthorized,
},
{
err: testErr,
status: http.StatusServiceUnavailable,
check: errdefs.IsUnavailable,
},
{
err: testErr,
status: http.StatusForbidden,
check: errdefs.IsForbidden,
},
{
err: testErr,
status: http.StatusNotModified,
check: errdefs.IsNotModified,
},
{
err: testErr,
status: http.StatusNotImplemented,
check: errdefs.IsNotImplemented,
},
{
err: testErr,
status: http.StatusInternalServerError,
check: errdefs.IsSystem,
},
{
err: errdefs.Unknown(testErr),
status: http.StatusInternalServerError,
check: errdefs.IsUnknown,
},
{
err: errdefs.DataLoss(testErr),
status: http.StatusInternalServerError,
check: errdefs.IsDataLoss,
},
{
err: errdefs.Deadline(testErr),
status: http.StatusInternalServerError,
check: errdefs.IsDeadline,
},
{
err: errdefs.Cancelled(testErr),
status: http.StatusInternalServerError,
check: errdefs.IsCancelled,
},
}
for _, tc := range testCases {
t.Run(http.StatusText(tc.status), func(t *testing.T) {
err := FromStatusCode(tc.err, tc.status)
assert.Check(t, tc.check(err), "unexpected error-type %T", err)
})
}
}