Browse code

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>

Sebastiaan van Stijn authored on 2018/12/31 21:59:59
Showing 2 changed files
... ...
@@ -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
+}