File: status.go

package info (click to toggle)
docker.io 28.5.2%2Bdfsg1-1
  • links: PTS, VCS
  • area: main
  • in suites: experimental
  • size: 69,048 kB
  • sloc: sh: 5,867; makefile: 863; ansic: 184; python: 162; asm: 159
file content (124 lines) | stat: -rw-r--r-- 3,920 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
package httpstatus

import (
	"context"
	"fmt"
	"net/http"

	cerrdefs "github.com/containerd/errdefs"
	"github.com/containerd/log"
	"github.com/docker/distribution/registry/api/errcode"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

// FromError retrieves status code from error message.
func FromError(err error) int {
	if err == nil {
		log.G(context.TODO()).WithError(err).Error("unexpected HTTP error handling")
		return http.StatusInternalServerError
	}

	// Resolve the error to ensure status is chosen from the first outermost error
	rerr := cerrdefs.Resolve(err)

	// Note that the below functions are already checking the error causal chain for matches.
	// Only check errors from the errdefs package, no new error type checking may be added
	switch {
	case cerrdefs.IsNotFound(rerr):
		return http.StatusNotFound
	case cerrdefs.IsInvalidArgument(rerr):
		return http.StatusBadRequest
	case cerrdefs.IsConflict(rerr):
		return http.StatusConflict
	case cerrdefs.IsUnauthorized(rerr):
		return http.StatusUnauthorized
	case cerrdefs.IsUnavailable(rerr):
		return http.StatusServiceUnavailable
	case cerrdefs.IsPermissionDenied(rerr):
		return http.StatusForbidden
	case cerrdefs.IsNotModified(rerr):
		return http.StatusNotModified
	case cerrdefs.IsNotImplemented(rerr):
		return http.StatusNotImplemented
	case cerrdefs.IsInternal(rerr) || cerrdefs.IsDataLoss(rerr) || cerrdefs.IsDeadlineExceeded(rerr) || cerrdefs.IsCanceled(rerr):
		return http.StatusInternalServerError
	default:
		if statusCode := statusCodeFromGRPCError(err); statusCode != http.StatusInternalServerError {
			return statusCode
		}
		if statusCode := statusCodeFromDistributionError(err); statusCode != http.StatusInternalServerError {
			return statusCode
		}
		switch e := err.(type) {
		case interface{ Unwrap() error }:
			return FromError(e.Unwrap())
		case interface{ Unwrap() []error }:
			for _, ue := range e.Unwrap() {
				if statusCode := FromError(ue); statusCode != http.StatusInternalServerError {
					return statusCode
				}
			}
		}

		if !cerrdefs.IsUnknown(err) {
			log.G(context.TODO()).WithFields(log.Fields{
				"module":     "api",
				"error":      err,
				"error_type": fmt.Sprintf("%T", err),
			}).Debug("FIXME: Got an API for which error does not match any expected type!!!")
		}

		return http.StatusInternalServerError
	}
}

// statusCodeFromGRPCError returns status code according to gRPC error
func statusCodeFromGRPCError(err error) int {
	switch status.Code(err) {
	case codes.InvalidArgument: // code 3
		return http.StatusBadRequest
	case codes.NotFound: // code 5
		return http.StatusNotFound
	case codes.AlreadyExists: // code 6
		return http.StatusConflict
	case codes.PermissionDenied: // code 7
		return http.StatusForbidden
	case codes.FailedPrecondition: // code 9
		return http.StatusBadRequest
	case codes.Unauthenticated: // code 16
		return http.StatusUnauthorized
	case codes.OutOfRange: // code 11
		return http.StatusBadRequest
	case codes.Unimplemented: // code 12
		return http.StatusNotImplemented
	case codes.Unavailable: // code 14
		return http.StatusServiceUnavailable
	default:
		// codes.Canceled(1)
		// codes.Unknown(2)
		// codes.DeadlineExceeded(4)
		// codes.ResourceExhausted(8)
		// codes.Aborted(10)
		// codes.Internal(13)
		// codes.DataLoss(15)
		return http.StatusInternalServerError
	}
}

// statusCodeFromDistributionError returns status code according to registry errcode
// code is loosely based on errcode.ServeJSON() in docker/distribution
func statusCodeFromDistributionError(err error) int {
	switch errs := err.(type) {
	case errcode.Errors:
		if len(errs) < 1 {
			return http.StatusInternalServerError
		}
		if _, ok := errs[0].(errcode.ErrorCoder); ok {
			return statusCodeFromDistributionError(errs[0])
		}
	case errcode.ErrorCoder:
		return errs.ErrorCode().Descriptor().HTTPStatusCode
	}
	return http.StatusInternalServerError
}