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>
| ... | ... |
@@ -76,6 +76,52 @@ func GetHTTPErrorStatusCode(err error) int {
|
| 76 | 76 |
return statusCode |
| 77 | 77 |
} |
| 78 | 78 |
|
| 79 |
+// FromStatusCode creates an errdef error, based on the provided status-code |
|
| 80 |
+func FromStatusCode(err error, statusCode int) error {
|
|
| 81 |
+ if err == nil {
|
|
| 82 |
+ return err |
|
| 83 |
+ } |
|
| 84 |
+ switch statusCode {
|
|
| 85 |
+ case http.StatusNotFound: |
|
| 86 |
+ err = errdefs.NotFound(err) |
|
| 87 |
+ case http.StatusBadRequest: |
|
| 88 |
+ err = errdefs.InvalidParameter(err) |
|
| 89 |
+ case http.StatusConflict: |
|
| 90 |
+ err = errdefs.Conflict(err) |
|
| 91 |
+ case http.StatusUnauthorized: |
|
| 92 |
+ err = errdefs.Unauthorized(err) |
|
| 93 |
+ case http.StatusServiceUnavailable: |
|
| 94 |
+ err = errdefs.Unavailable(err) |
|
| 95 |
+ case http.StatusForbidden: |
|
| 96 |
+ err = errdefs.Forbidden(err) |
|
| 97 |
+ case http.StatusNotModified: |
|
| 98 |
+ err = errdefs.NotModified(err) |
|
| 99 |
+ case http.StatusNotImplemented: |
|
| 100 |
+ err = errdefs.NotImplemented(err) |
|
| 101 |
+ case http.StatusInternalServerError: |
|
| 102 |
+ if !errdefs.IsSystem(err) && !errdefs.IsUnknown(err) && !errdefs.IsDataLoss(err) && !errdefs.IsDeadline(err) && !errdefs.IsCancelled(err) {
|
|
| 103 |
+ err = errdefs.System(err) |
|
| 104 |
+ } |
|
| 105 |
+ default: |
|
| 106 |
+ logrus.WithFields(logrus.Fields{
|
|
| 107 |
+ "module": "api", |
|
| 108 |
+ "status_code": fmt.Sprintf("%d", statusCode),
|
|
| 109 |
+ }).Debugf("FIXME: Got an status-code for which error does not match any expected type!!!: %d", statusCode)
|
|
| 110 |
+ |
|
| 111 |
+ switch {
|
|
| 112 |
+ case statusCode >= 200 && statusCode < 400: |
|
| 113 |
+ // it's a client error |
|
| 114 |
+ case statusCode >= 400 && statusCode < 500: |
|
| 115 |
+ err = errdefs.InvalidParameter(err) |
|
| 116 |
+ case statusCode >= 500 && statusCode < 600: |
|
| 117 |
+ err = errdefs.System(err) |
|
| 118 |
+ default: |
|
| 119 |
+ err = errdefs.Unknown(err) |
|
| 120 |
+ } |
|
| 121 |
+ } |
|
| 122 |
+ return err |
|
| 123 |
+} |
|
| 124 |
+ |
|
| 79 | 125 |
func apiVersionSupportsJSONErrors(version string) bool {
|
| 80 | 126 |
const firstAPIVersionWithJSONErrors = "1.23" |
| 81 | 127 |
return version == "" || versions.GreaterThan(version, firstAPIVersionWithJSONErrors) |
| 82 | 128 |
new file mode 100644 |
| ... | ... |
@@ -0,0 +1,93 @@ |
| 0 |
+package httputils |
|
| 1 |
+ |
|
| 2 |
+import ( |
|
| 3 |
+ "fmt" |
|
| 4 |
+ "net/http" |
|
| 5 |
+ "testing" |
|
| 6 |
+ |
|
| 7 |
+ "github.com/docker/docker/errdefs" |
|
| 8 |
+ "gotest.tools/assert" |
|
| 9 |
+) |
|
| 10 |
+ |
|
| 11 |
+func TestFromStatusCode(t *testing.T) {
|
|
| 12 |
+ testErr := fmt.Errorf("some error occurred")
|
|
| 13 |
+ |
|
| 14 |
+ testCases := []struct {
|
|
| 15 |
+ err error |
|
| 16 |
+ status int |
|
| 17 |
+ check func(error) bool |
|
| 18 |
+ }{
|
|
| 19 |
+ {
|
|
| 20 |
+ err: testErr, |
|
| 21 |
+ status: http.StatusNotFound, |
|
| 22 |
+ check: errdefs.IsNotFound, |
|
| 23 |
+ }, |
|
| 24 |
+ {
|
|
| 25 |
+ err: testErr, |
|
| 26 |
+ status: http.StatusBadRequest, |
|
| 27 |
+ check: errdefs.IsInvalidParameter, |
|
| 28 |
+ }, |
|
| 29 |
+ {
|
|
| 30 |
+ err: testErr, |
|
| 31 |
+ status: http.StatusConflict, |
|
| 32 |
+ check: errdefs.IsConflict, |
|
| 33 |
+ }, |
|
| 34 |
+ {
|
|
| 35 |
+ err: testErr, |
|
| 36 |
+ status: http.StatusUnauthorized, |
|
| 37 |
+ check: errdefs.IsUnauthorized, |
|
| 38 |
+ }, |
|
| 39 |
+ {
|
|
| 40 |
+ err: testErr, |
|
| 41 |
+ status: http.StatusServiceUnavailable, |
|
| 42 |
+ check: errdefs.IsUnavailable, |
|
| 43 |
+ }, |
|
| 44 |
+ {
|
|
| 45 |
+ err: testErr, |
|
| 46 |
+ status: http.StatusForbidden, |
|
| 47 |
+ check: errdefs.IsForbidden, |
|
| 48 |
+ }, |
|
| 49 |
+ {
|
|
| 50 |
+ err: testErr, |
|
| 51 |
+ status: http.StatusNotModified, |
|
| 52 |
+ check: errdefs.IsNotModified, |
|
| 53 |
+ }, |
|
| 54 |
+ {
|
|
| 55 |
+ err: testErr, |
|
| 56 |
+ status: http.StatusNotImplemented, |
|
| 57 |
+ check: errdefs.IsNotImplemented, |
|
| 58 |
+ }, |
|
| 59 |
+ {
|
|
| 60 |
+ err: testErr, |
|
| 61 |
+ status: http.StatusInternalServerError, |
|
| 62 |
+ check: errdefs.IsSystem, |
|
| 63 |
+ }, |
|
| 64 |
+ {
|
|
| 65 |
+ err: errdefs.Unknown(testErr), |
|
| 66 |
+ status: http.StatusInternalServerError, |
|
| 67 |
+ check: errdefs.IsUnknown, |
|
| 68 |
+ }, |
|
| 69 |
+ {
|
|
| 70 |
+ err: errdefs.DataLoss(testErr), |
|
| 71 |
+ status: http.StatusInternalServerError, |
|
| 72 |
+ check: errdefs.IsDataLoss, |
|
| 73 |
+ }, |
|
| 74 |
+ {
|
|
| 75 |
+ err: errdefs.Deadline(testErr), |
|
| 76 |
+ status: http.StatusInternalServerError, |
|
| 77 |
+ check: errdefs.IsDeadline, |
|
| 78 |
+ }, |
|
| 79 |
+ {
|
|
| 80 |
+ err: errdefs.Cancelled(testErr), |
|
| 81 |
+ status: http.StatusInternalServerError, |
|
| 82 |
+ check: errdefs.IsCancelled, |
|
| 83 |
+ }, |
|
| 84 |
+ } |
|
| 85 |
+ |
|
| 86 |
+ for _, tc := range testCases {
|
|
| 87 |
+ t.Run(http.StatusText(tc.status), func(t *testing.T) {
|
|
| 88 |
+ err := FromStatusCode(tc.err, tc.status) |
|
| 89 |
+ assert.Check(t, tc.check(err), "unexpected error-type %T", err) |
|
| 90 |
+ }) |
|
| 91 |
+ } |
|
| 92 |
+} |