File: status.go

package info (click to toggle)
docker.io 27.5.1%2Bdfsg4-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 67,384 kB
  • sloc: sh: 5,847; makefile: 1,146; ansic: 664; python: 162; asm: 133
file content (143 lines) | stat: -rw-r--r-- 4,490 bytes parent folder | download | duplicates (2)
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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package httpstatus // import "github.com/docker/docker/api/server/httpstatus"

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

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

type causer interface {
	Cause() error
}

// 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
	}

	// Stop right there
	// Are you sure you should be adding a new error class here? Do one of the existing ones work?

	// Note that the below functions are already checking the error causal chain for matches.
	switch {
	case errdefs.IsNotFound(err):
		return http.StatusNotFound
	case errdefs.IsInvalidParameter(err):
		return http.StatusBadRequest
	case errdefs.IsConflict(err):
		return http.StatusConflict
	case errdefs.IsUnauthorized(err):
		return http.StatusUnauthorized
	case errdefs.IsUnavailable(err):
		return http.StatusServiceUnavailable
	case errdefs.IsForbidden(err):
		return http.StatusForbidden
	case errdefs.IsNotModified(err):
		return http.StatusNotModified
	case errdefs.IsNotImplemented(err):
		return http.StatusNotImplemented
	case errdefs.IsSystem(err) || errdefs.IsUnknown(err) || errdefs.IsDataLoss(err) || errdefs.IsDeadline(err) || errdefs.IsCancelled(err):
		return http.StatusInternalServerError
	default:
		if statusCode := statusCodeFromGRPCError(err); statusCode != http.StatusInternalServerError {
			return statusCode
		}
		if statusCode := statusCodeFromContainerdError(err); statusCode != http.StatusInternalServerError {
			return statusCode
		}
		if statusCode := statusCodeFromDistributionError(err); statusCode != http.StatusInternalServerError {
			return statusCode
		}
		if e, ok := err.(causer); ok {
			return FromError(e.Cause())
		}

		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
}

// statusCodeFromContainerdError returns status code for containerd errors when
// consumed directly (not through gRPC)
func statusCodeFromContainerdError(err error) int {
	switch {
	case cerrdefs.IsInvalidArgument(err):
		return http.StatusBadRequest
	case cerrdefs.IsNotFound(err):
		return http.StatusNotFound
	case cerrdefs.IsAlreadyExists(err):
		return http.StatusConflict
	case cerrdefs.IsFailedPrecondition(err):
		return http.StatusPreconditionFailed
	case cerrdefs.IsUnavailable(err):
		return http.StatusServiceUnavailable
	case cerrdefs.IsNotImplemented(err):
		return http.StatusNotImplemented
	default:
		return http.StatusInternalServerError
	}
}