Browse code

Move httputils error helpers to errdefs package

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

Sebastiaan van Stijn authored on 2019/02/09 23:53:29
Showing 10 changed files
1 1
deleted file mode 100644
... ...
@@ -1,202 +0,0 @@
1
-package httputils // import "github.com/docker/docker/api/server/httputils"
2
-
3
-import (
4
-	"fmt"
5
-	"net/http"
6
-
7
-	"github.com/docker/distribution/registry/api/errcode"
8
-	"github.com/docker/docker/api/types"
9
-	"github.com/docker/docker/api/types/versions"
10
-	"github.com/docker/docker/errdefs"
11
-	"github.com/gorilla/mux"
12
-	"github.com/sirupsen/logrus"
13
-	"google.golang.org/grpc/codes"
14
-	"google.golang.org/grpc/status"
15
-)
16
-
17
-type causer interface {
18
-	Cause() error
19
-}
20
-
21
-// GetHTTPErrorStatusCode retrieves status code from error message.
22
-func GetHTTPErrorStatusCode(err error) int {
23
-	if err == nil {
24
-		logrus.WithFields(logrus.Fields{"error": err}).Error("unexpected HTTP error handling")
25
-		return http.StatusInternalServerError
26
-	}
27
-
28
-	var statusCode int
29
-
30
-	// Stop right there
31
-	// Are you sure you should be adding a new error class here? Do one of the existing ones work?
32
-
33
-	// Note that the below functions are already checking the error causal chain for matches.
34
-	switch {
35
-	case errdefs.IsNotFound(err):
36
-		statusCode = http.StatusNotFound
37
-	case errdefs.IsInvalidParameter(err):
38
-		statusCode = http.StatusBadRequest
39
-	case errdefs.IsConflict(err) || errdefs.IsAlreadyExists(err):
40
-		statusCode = http.StatusConflict
41
-	case errdefs.IsUnauthorized(err):
42
-		statusCode = http.StatusUnauthorized
43
-	case errdefs.IsUnavailable(err):
44
-		statusCode = http.StatusServiceUnavailable
45
-	case errdefs.IsForbidden(err):
46
-		statusCode = http.StatusForbidden
47
-	case errdefs.IsNotModified(err):
48
-		statusCode = http.StatusNotModified
49
-	case errdefs.IsNotImplemented(err):
50
-		statusCode = http.StatusNotImplemented
51
-	case errdefs.IsSystem(err) || errdefs.IsUnknown(err) || errdefs.IsDataLoss(err) || errdefs.IsDeadline(err) || errdefs.IsCancelled(err):
52
-		statusCode = http.StatusInternalServerError
53
-	default:
54
-		statusCode = statusCodeFromGRPCError(err)
55
-		if statusCode != http.StatusInternalServerError {
56
-			return statusCode
57
-		}
58
-		statusCode = statusCodeFromDistributionError(err)
59
-		if statusCode != http.StatusInternalServerError {
60
-			return statusCode
61
-		}
62
-		if e, ok := err.(causer); ok {
63
-			return GetHTTPErrorStatusCode(e.Cause())
64
-		}
65
-
66
-		logrus.WithFields(logrus.Fields{
67
-			"module":     "api",
68
-			"error_type": fmt.Sprintf("%T", err),
69
-		}).Debugf("FIXME: Got an API for which error does not match any expected type!!!: %+v", err)
70
-	}
71
-
72
-	if statusCode == 0 {
73
-		statusCode = http.StatusInternalServerError
74
-	}
75
-
76
-	return statusCode
77
-}
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
-
125
-func apiVersionSupportsJSONErrors(version string) bool {
126
-	const firstAPIVersionWithJSONErrors = "1.23"
127
-	return version == "" || versions.GreaterThan(version, firstAPIVersionWithJSONErrors)
128
-}
129
-
130
-// MakeErrorHandler makes an HTTP handler that decodes a Docker error and
131
-// returns it in the response.
132
-func MakeErrorHandler(err error) http.HandlerFunc {
133
-	return func(w http.ResponseWriter, r *http.Request) {
134
-		statusCode := GetHTTPErrorStatusCode(err)
135
-		vars := mux.Vars(r)
136
-		if apiVersionSupportsJSONErrors(vars["version"]) {
137
-			response := &types.ErrorResponse{
138
-				Message: err.Error(),
139
-			}
140
-			WriteJSON(w, statusCode, response)
141
-		} else {
142
-			http.Error(w, status.Convert(err).Message(), statusCode)
143
-		}
144
-	}
145
-}
146
-
147
-// statusCodeFromGRPCError returns status code according to gRPC error
148
-func statusCodeFromGRPCError(err error) int {
149
-	switch status.Code(err) {
150
-	case codes.InvalidArgument: // code 3
151
-		return http.StatusBadRequest
152
-	case codes.NotFound: // code 5
153
-		return http.StatusNotFound
154
-	case codes.AlreadyExists: // code 6
155
-		return http.StatusConflict
156
-	case codes.PermissionDenied: // code 7
157
-		return http.StatusForbidden
158
-	case codes.FailedPrecondition: // code 9
159
-		return http.StatusBadRequest
160
-	case codes.Unauthenticated: // code 16
161
-		return http.StatusUnauthorized
162
-	case codes.OutOfRange: // code 11
163
-		return http.StatusBadRequest
164
-	case codes.Unimplemented: // code 12
165
-		return http.StatusNotImplemented
166
-	case codes.Unavailable: // code 14
167
-		return http.StatusServiceUnavailable
168
-	default:
169
-		if e, ok := err.(causer); ok {
170
-			return statusCodeFromGRPCError(e.Cause())
171
-		}
172
-		// codes.Canceled(1)
173
-		// codes.Unknown(2)
174
-		// codes.DeadlineExceeded(4)
175
-		// codes.ResourceExhausted(8)
176
-		// codes.Aborted(10)
177
-		// codes.Internal(13)
178
-		// codes.DataLoss(15)
179
-		return http.StatusInternalServerError
180
-	}
181
-}
182
-
183
-// statusCodeFromDistributionError returns status code according to registry errcode
184
-// code is loosely based on errcode.ServeJSON() in docker/distribution
185
-func statusCodeFromDistributionError(err error) int {
186
-	switch errs := err.(type) {
187
-	case errcode.Errors:
188
-		if len(errs) < 1 {
189
-			return http.StatusInternalServerError
190
-		}
191
-		if _, ok := errs[0].(errcode.ErrorCoder); ok {
192
-			return statusCodeFromDistributionError(errs[0])
193
-		}
194
-	case errcode.ErrorCoder:
195
-		return errs.ErrorCode().Descriptor().HTTPStatusCode
196
-	default:
197
-		if e, ok := err.(causer); ok {
198
-			return statusCodeFromDistributionError(e.Cause())
199
-		}
200
-	}
201
-	return http.StatusInternalServerError
202
-}
203 1
new file mode 100644
... ...
@@ -0,0 +1,9 @@
0
+package httputils // import "github.com/docker/docker/api/server/httputils"
1
+import "github.com/docker/docker/errdefs"
2
+
3
+// GetHTTPErrorStatusCode retrieves status code from error message.
4
+//
5
+// Deprecated: use errdefs.GetHTTPErrorStatusCode
6
+func GetHTTPErrorStatusCode(err error) int {
7
+	return errdefs.GetHTTPErrorStatusCode(err)
8
+}
0 9
deleted file mode 100644
... ...
@@ -1,93 +0,0 @@
1
-package httputils
2
-
3
-import (
4
-	"fmt"
5
-	"net/http"
6
-	"testing"
7
-
8
-	"github.com/docker/docker/errdefs"
9
-	"gotest.tools/assert"
10
-)
11
-
12
-func TestFromStatusCode(t *testing.T) {
13
-	testErr := fmt.Errorf("some error occurred")
14
-
15
-	testCases := []struct {
16
-		err    error
17
-		status int
18
-		check  func(error) bool
19
-	}{
20
-		{
21
-			err:    testErr,
22
-			status: http.StatusNotFound,
23
-			check:  errdefs.IsNotFound,
24
-		},
25
-		{
26
-			err:    testErr,
27
-			status: http.StatusBadRequest,
28
-			check:  errdefs.IsInvalidParameter,
29
-		},
30
-		{
31
-			err:    testErr,
32
-			status: http.StatusConflict,
33
-			check:  errdefs.IsConflict,
34
-		},
35
-		{
36
-			err:    testErr,
37
-			status: http.StatusUnauthorized,
38
-			check:  errdefs.IsUnauthorized,
39
-		},
40
-		{
41
-			err:    testErr,
42
-			status: http.StatusServiceUnavailable,
43
-			check:  errdefs.IsUnavailable,
44
-		},
45
-		{
46
-			err:    testErr,
47
-			status: http.StatusForbidden,
48
-			check:  errdefs.IsForbidden,
49
-		},
50
-		{
51
-			err:    testErr,
52
-			status: http.StatusNotModified,
53
-			check:  errdefs.IsNotModified,
54
-		},
55
-		{
56
-			err:    testErr,
57
-			status: http.StatusNotImplemented,
58
-			check:  errdefs.IsNotImplemented,
59
-		},
60
-		{
61
-			err:    testErr,
62
-			status: http.StatusInternalServerError,
63
-			check:  errdefs.IsSystem,
64
-		},
65
-		{
66
-			err:    errdefs.Unknown(testErr),
67
-			status: http.StatusInternalServerError,
68
-			check:  errdefs.IsUnknown,
69
-		},
70
-		{
71
-			err:    errdefs.DataLoss(testErr),
72
-			status: http.StatusInternalServerError,
73
-			check:  errdefs.IsDataLoss,
74
-		},
75
-		{
76
-			err:    errdefs.Deadline(testErr),
77
-			status: http.StatusInternalServerError,
78
-			check:  errdefs.IsDeadline,
79
-		},
80
-		{
81
-			err:    errdefs.Cancelled(testErr),
82
-			status: http.StatusInternalServerError,
83
-			check:  errdefs.IsCancelled,
84
-		},
85
-	}
86
-
87
-	for _, tc := range testCases {
88
-		t.Run(http.StatusText(tc.status), func(t *testing.T) {
89
-			err := FromStatusCode(tc.err, tc.status)
90
-			assert.Check(t, tc.check(err), "unexpected error-type %T", err)
91
-		})
92
-	}
93
-}
... ...
@@ -7,9 +7,13 @@ import (
7 7
 	"net/http"
8 8
 	"strings"
9 9
 
10
+	"github.com/docker/docker/api/types"
11
+	"github.com/docker/docker/api/types/versions"
10 12
 	"github.com/docker/docker/errdefs"
13
+	"github.com/gorilla/mux"
11 14
 	"github.com/pkg/errors"
12 15
 	"github.com/sirupsen/logrus"
16
+	"google.golang.org/grpc/status"
13 17
 )
14 18
 
15 19
 // APIVersionKey is the client's requested API version.
... ...
@@ -88,6 +92,28 @@ func VersionFromContext(ctx context.Context) string {
88 88
 	return ""
89 89
 }
90 90
 
91
+// MakeErrorHandler makes an HTTP handler that decodes a Docker error and
92
+// returns it in the response.
93
+func MakeErrorHandler(err error) http.HandlerFunc {
94
+	return func(w http.ResponseWriter, r *http.Request) {
95
+		statusCode := errdefs.GetHTTPErrorStatusCode(err)
96
+		vars := mux.Vars(r)
97
+		if apiVersionSupportsJSONErrors(vars["version"]) {
98
+			response := &types.ErrorResponse{
99
+				Message: err.Error(),
100
+			}
101
+			WriteJSON(w, statusCode, response)
102
+		} else {
103
+			http.Error(w, status.Convert(err).Message(), statusCode)
104
+		}
105
+	}
106
+}
107
+
108
+func apiVersionSupportsJSONErrors(version string) bool {
109
+	const firstAPIVersionWithJSONErrors = "1.23"
110
+	return version == "" || versions.GreaterThan(version, firstAPIVersionWithJSONErrors)
111
+}
112
+
91 113
 // matchesContentType validates the content type against the expected one
92 114
 func matchesContentType(contentType, expectedType string) bool {
93 115
 	mimetype, _, err := mime.ParseMediaType(contentType)
... ...
@@ -589,7 +589,7 @@ func (s *containerRouter) postContainersAttach(ctx context.Context, w http.Respo
589 589
 		// Remember to close stream if error happens
590 590
 		conn, _, errHijack := hijacker.Hijack()
591 591
 		if errHijack == nil {
592
-			statusCode := httputils.GetHTTPErrorStatusCode(err)
592
+			statusCode := errdefs.GetHTTPErrorStatusCode(err)
593 593
 			statusText := http.StatusText(statusCode)
594 594
 			fmt.Fprintf(conn, "HTTP/1.1 %d %s\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n%s\r\n", statusCode, statusText, err.Error())
595 595
 			httputils.CloseStreams(conn)
... ...
@@ -12,6 +12,7 @@ import (
12 12
 	"github.com/docker/docker/api/server/router"
13 13
 	"github.com/docker/docker/api/server/router/debug"
14 14
 	"github.com/docker/docker/dockerversion"
15
+	"github.com/docker/docker/errdefs"
15 16
 	"github.com/gorilla/mux"
16 17
 	"github.com/sirupsen/logrus"
17 18
 )
... ...
@@ -139,7 +140,7 @@ func (s *Server) makeHTTPHandler(handler httputils.APIFunc) http.HandlerFunc {
139 139
 		}
140 140
 
141 141
 		if err := handlerFunc(ctx, w, r, vars); err != nil {
142
-			statusCode := httputils.GetHTTPErrorStatusCode(err)
142
+			statusCode := errdefs.GetHTTPErrorStatusCode(err)
143 143
 			if statusCode >= 500 {
144 144
 				logrus.Errorf("Handler for %s %s returned error: %v", r.Method, r.URL.Path, err)
145 145
 			}
... ...
@@ -5,8 +5,8 @@ import (
5 5
 	"net/http"
6 6
 	"path"
7 7
 
8
-	"github.com/docker/docker/api/server/httputils"
9 8
 	"github.com/docker/docker/api/types"
9
+	"github.com/docker/docker/errdefs"
10 10
 )
11 11
 
12 12
 // Ping pings the server and returns the value of the "Docker-Experimental",
... ...
@@ -49,7 +49,7 @@ func parsePingResponse(cli *Client, resp serverResponse) (types.Ping, error) {
49 49
 	var ping types.Ping
50 50
 	if resp.header == nil {
51 51
 		err := cli.checkResponseErr(resp)
52
-		return ping, httputils.FromStatusCode(err, resp.statusCode)
52
+		return ping, errdefs.FromStatusCode(err, resp.statusCode)
53 53
 	}
54 54
 	ping.APIVersion = resp.header.Get("API-Version")
55 55
 	ping.OSType = resp.header.Get("OSType")
... ...
@@ -60,5 +60,5 @@ func parsePingResponse(cli *Client, resp serverResponse) (types.Ping, error) {
60 60
 		ping.BuilderVersion = types.BuilderVersion(bv)
61 61
 	}
62 62
 	err := cli.checkResponseErr(resp)
63
-	return ping, httputils.FromStatusCode(err, resp.statusCode)
63
+	return ping, errdefs.FromStatusCode(err, resp.statusCode)
64 64
 }
... ...
@@ -13,9 +13,9 @@ import (
13 13
 	"os"
14 14
 	"strings"
15 15
 
16
-	"github.com/docker/docker/api/server/httputils"
17 16
 	"github.com/docker/docker/api/types"
18 17
 	"github.com/docker/docker/api/types/versions"
18
+	"github.com/docker/docker/errdefs"
19 19
 	"github.com/pkg/errors"
20 20
 )
21 21
 
... ...
@@ -121,10 +121,10 @@ func (cli *Client) sendRequest(ctx context.Context, method, path string, query u
121 121
 	}
122 122
 	resp, err := cli.doRequest(ctx, req)
123 123
 	if err != nil {
124
-		return resp, httputils.FromStatusCode(err, resp.statusCode)
124
+		return resp, errdefs.FromStatusCode(err, resp.statusCode)
125 125
 	}
126 126
 	err = cli.checkResponseErr(resp)
127
-	return resp, httputils.FromStatusCode(err, resp.statusCode)
127
+	return resp, errdefs.FromStatusCode(err, resp.statusCode)
128 128
 }
129 129
 
130 130
 func (cli *Client) doRequest(ctx context.Context, req *http.Request) (serverResponse, error) {
131 131
new file mode 100644
... ...
@@ -0,0 +1,172 @@
0
+package errdefs // import "github.com/docker/docker/errdefs"
1
+
2
+import (
3
+	"fmt"
4
+	"net/http"
5
+
6
+	"github.com/docker/distribution/registry/api/errcode"
7
+	"github.com/sirupsen/logrus"
8
+	"google.golang.org/grpc/codes"
9
+	"google.golang.org/grpc/status"
10
+)
11
+
12
+// GetHTTPErrorStatusCode retrieves status code from error message.
13
+func GetHTTPErrorStatusCode(err error) int {
14
+	if err == nil {
15
+		logrus.WithFields(logrus.Fields{"error": err}).Error("unexpected HTTP error handling")
16
+		return http.StatusInternalServerError
17
+	}
18
+
19
+	var statusCode int
20
+
21
+	// Stop right there
22
+	// Are you sure you should be adding a new error class here? Do one of the existing ones work?
23
+
24
+	// Note that the below functions are already checking the error causal chain for matches.
25
+	switch {
26
+	case IsNotFound(err):
27
+		statusCode = http.StatusNotFound
28
+	case IsInvalidParameter(err):
29
+		statusCode = http.StatusBadRequest
30
+	case IsConflict(err) || IsAlreadyExists(err):
31
+		statusCode = http.StatusConflict
32
+	case IsUnauthorized(err):
33
+		statusCode = http.StatusUnauthorized
34
+	case IsUnavailable(err):
35
+		statusCode = http.StatusServiceUnavailable
36
+	case IsForbidden(err):
37
+		statusCode = http.StatusForbidden
38
+	case IsNotModified(err):
39
+		statusCode = http.StatusNotModified
40
+	case IsNotImplemented(err):
41
+		statusCode = http.StatusNotImplemented
42
+	case IsSystem(err) || IsUnknown(err) || IsDataLoss(err) || IsDeadline(err) || IsCancelled(err):
43
+		statusCode = http.StatusInternalServerError
44
+	default:
45
+		statusCode = statusCodeFromGRPCError(err)
46
+		if statusCode != http.StatusInternalServerError {
47
+			return statusCode
48
+		}
49
+		statusCode = statusCodeFromDistributionError(err)
50
+		if statusCode != http.StatusInternalServerError {
51
+			return statusCode
52
+		}
53
+		if e, ok := err.(causer); ok {
54
+			return GetHTTPErrorStatusCode(e.Cause())
55
+		}
56
+
57
+		logrus.WithFields(logrus.Fields{
58
+			"module":     "api",
59
+			"error_type": fmt.Sprintf("%T", err),
60
+		}).Debugf("FIXME: Got an API for which error does not match any expected type!!!: %+v", err)
61
+	}
62
+
63
+	if statusCode == 0 {
64
+		statusCode = http.StatusInternalServerError
65
+	}
66
+
67
+	return statusCode
68
+}
69
+
70
+// FromStatusCode creates an errdef error, based on the provided HTTP status-code
71
+func FromStatusCode(err error, statusCode int) error {
72
+	if err == nil {
73
+		return err
74
+	}
75
+	switch statusCode {
76
+	case http.StatusNotFound:
77
+		err = NotFound(err)
78
+	case http.StatusBadRequest:
79
+		err = InvalidParameter(err)
80
+	case http.StatusConflict:
81
+		err = Conflict(err)
82
+	case http.StatusUnauthorized:
83
+		err = Unauthorized(err)
84
+	case http.StatusServiceUnavailable:
85
+		err = Unavailable(err)
86
+	case http.StatusForbidden:
87
+		err = Forbidden(err)
88
+	case http.StatusNotModified:
89
+		err = NotModified(err)
90
+	case http.StatusNotImplemented:
91
+		err = NotImplemented(err)
92
+	case http.StatusInternalServerError:
93
+		if !IsSystem(err) && !IsUnknown(err) && !IsDataLoss(err) && !IsDeadline(err) && !IsCancelled(err) {
94
+			err = System(err)
95
+		}
96
+	default:
97
+		logrus.WithFields(logrus.Fields{
98
+			"module":      "api",
99
+			"status_code": fmt.Sprintf("%d", statusCode),
100
+		}).Debugf("FIXME: Got an status-code for which error does not match any expected type!!!: %d", statusCode)
101
+
102
+		switch {
103
+		case statusCode >= 200 && statusCode < 400:
104
+			// it's a client error
105
+		case statusCode >= 400 && statusCode < 500:
106
+			err = InvalidParameter(err)
107
+		case statusCode >= 500 && statusCode < 600:
108
+			err = System(err)
109
+		default:
110
+			err = Unknown(err)
111
+		}
112
+	}
113
+	return err
114
+}
115
+
116
+// statusCodeFromGRPCError returns status code according to gRPC error
117
+func statusCodeFromGRPCError(err error) int {
118
+	switch status.Code(err) {
119
+	case codes.InvalidArgument: // code 3
120
+		return http.StatusBadRequest
121
+	case codes.NotFound: // code 5
122
+		return http.StatusNotFound
123
+	case codes.AlreadyExists: // code 6
124
+		return http.StatusConflict
125
+	case codes.PermissionDenied: // code 7
126
+		return http.StatusForbidden
127
+	case codes.FailedPrecondition: // code 9
128
+		return http.StatusBadRequest
129
+	case codes.Unauthenticated: // code 16
130
+		return http.StatusUnauthorized
131
+	case codes.OutOfRange: // code 11
132
+		return http.StatusBadRequest
133
+	case codes.Unimplemented: // code 12
134
+		return http.StatusNotImplemented
135
+	case codes.Unavailable: // code 14
136
+		return http.StatusServiceUnavailable
137
+	default:
138
+		if e, ok := err.(causer); ok {
139
+			return statusCodeFromGRPCError(e.Cause())
140
+		}
141
+		// codes.Canceled(1)
142
+		// codes.Unknown(2)
143
+		// codes.DeadlineExceeded(4)
144
+		// codes.ResourceExhausted(8)
145
+		// codes.Aborted(10)
146
+		// codes.Internal(13)
147
+		// codes.DataLoss(15)
148
+		return http.StatusInternalServerError
149
+	}
150
+}
151
+
152
+// statusCodeFromDistributionError returns status code according to registry errcode
153
+// code is loosely based on errcode.ServeJSON() in docker/distribution
154
+func statusCodeFromDistributionError(err error) int {
155
+	switch errs := err.(type) {
156
+	case errcode.Errors:
157
+		if len(errs) < 1 {
158
+			return http.StatusInternalServerError
159
+		}
160
+		if _, ok := errs[0].(errcode.ErrorCoder); ok {
161
+			return statusCodeFromDistributionError(errs[0])
162
+		}
163
+	case errcode.ErrorCoder:
164
+		return errs.ErrorCode().Descriptor().HTTPStatusCode
165
+	default:
166
+		if e, ok := err.(causer); ok {
167
+			return statusCodeFromDistributionError(e.Cause())
168
+		}
169
+	}
170
+	return http.StatusInternalServerError
171
+}
0 172
new file mode 100644
... ...
@@ -0,0 +1,92 @@
0
+package errdefs
1
+
2
+import (
3
+	"fmt"
4
+	"net/http"
5
+	"testing"
6
+
7
+	"gotest.tools/assert"
8
+)
9
+
10
+func TestFromStatusCode(t *testing.T) {
11
+	testErr := fmt.Errorf("some error occurred")
12
+
13
+	testCases := []struct {
14
+		err    error
15
+		status int
16
+		check  func(error) bool
17
+	}{
18
+		{
19
+			err:    testErr,
20
+			status: http.StatusNotFound,
21
+			check:  IsNotFound,
22
+		},
23
+		{
24
+			err:    testErr,
25
+			status: http.StatusBadRequest,
26
+			check:  IsInvalidParameter,
27
+		},
28
+		{
29
+			err:    testErr,
30
+			status: http.StatusConflict,
31
+			check:  IsConflict,
32
+		},
33
+		{
34
+			err:    testErr,
35
+			status: http.StatusUnauthorized,
36
+			check:  IsUnauthorized,
37
+		},
38
+		{
39
+			err:    testErr,
40
+			status: http.StatusServiceUnavailable,
41
+			check:  IsUnavailable,
42
+		},
43
+		{
44
+			err:    testErr,
45
+			status: http.StatusForbidden,
46
+			check:  IsForbidden,
47
+		},
48
+		{
49
+			err:    testErr,
50
+			status: http.StatusNotModified,
51
+			check:  IsNotModified,
52
+		},
53
+		{
54
+			err:    testErr,
55
+			status: http.StatusNotImplemented,
56
+			check:  IsNotImplemented,
57
+		},
58
+		{
59
+			err:    testErr,
60
+			status: http.StatusInternalServerError,
61
+			check:  IsSystem,
62
+		},
63
+		{
64
+			err:    Unknown(testErr),
65
+			status: http.StatusInternalServerError,
66
+			check:  IsUnknown,
67
+		},
68
+		{
69
+			err:    DataLoss(testErr),
70
+			status: http.StatusInternalServerError,
71
+			check:  IsDataLoss,
72
+		},
73
+		{
74
+			err:    Deadline(testErr),
75
+			status: http.StatusInternalServerError,
76
+			check:  IsDeadline,
77
+		},
78
+		{
79
+			err:    Cancelled(testErr),
80
+			status: http.StatusInternalServerError,
81
+			check:  IsCancelled,
82
+		},
83
+	}
84
+
85
+	for _, tc := range testCases {
86
+		t.Run(http.StatusText(tc.status), func(t *testing.T) {
87
+			err := FromStatusCode(tc.err, tc.status)
88
+			assert.Check(t, tc.check(err), "unexpected error-type %T", err)
89
+		})
90
+	}
91
+}