-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathclassification.go
More file actions
140 lines (110 loc) · 2.73 KB
/
Copy pathclassification.go
File metadata and controls
140 lines (110 loc) · 2.73 KB
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
package errors
import (
"context"
stderrors "errors"
"net/http"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type (
classified interface {
classification() errorClassification
}
singleUnwrapper interface {
Unwrap() error
}
multiUnwrapper interface {
Unwrap() []error
}
)
func resolveClassification(err error) (errorClassification, bool) {
if err == nil {
return errorClassification{}, false
}
// explicitly classified outer error returned as-is
if e, ok := err.(classified); ok {
return e.classification(), true
}
if c, ok := externalClassification(err); ok {
return c, true
}
// a gRPC status error, such as one from status.Error or a client call
if e, ok := err.(interface{ GRPCStatus() *status.Status }); ok {
if s := e.GRPCStatus(); s != nil {
return statusClassification(s, nil), true
}
}
// check for single unwrap
if e, ok := err.(singleUnwrapper); ok {
return resolveClassification(e.Unwrap())
}
// check for multi unwrap
if multi, ok := err.(multiUnwrapper); ok {
var selected errorClassification
found := false
for _, child := range multi.Unwrap() {
curr, ok := resolveClassification(child)
if !ok && child != nil {
return unknownClassification(), true
}
if !ok {
continue
}
if !found {
selected = curr
found = true
continue
}
if !sameClassification(selected, curr) {
return unknownClassification(), true
}
}
return selected, found
}
// special case for context errors, which are not classified by default
switch {
case stderrors.Is(err, context.Canceled):
return categoryClassification(ErrCanceled), true
case stderrors.Is(err, context.DeadlineExceeded):
return categoryClassification(ErrDeadlineExceeded), true
}
return errorClassification{}, false
}
func externalClassification(err error) (errorClassification, bool) {
c := unknownClassification()
found := false
if e, ok := err.(TypeCoder); ok {
c.typeCode = e.TypeCode()
found = true
}
if e, ok := err.(HTTPCoder); ok {
c.httpCode = e.HTTPCode()
found = true
}
if e, ok := err.(GRPCCoder); ok {
c.grpcCode = e.GRPCCode()
found = true
}
if !found {
return errorClassification{}, false
}
// A non-nil error never reports success.
if c.typeCode == "" || c.typeCode == string(ErrOK) {
c.typeCode = string(ErrUnknown)
}
if c.httpCode < 400 || c.httpCode > 599 {
c.httpCode = http.StatusInternalServerError
}
if c.grpcCode == codes.OK {
c.grpcCode = codes.Unknown
}
return c, true
}
func sameClassification(a, b errorClassification) bool {
return a.kind == b.kind &&
a.category == b.category &&
a.typeCode == b.typeCode &&
a.httpCode == b.httpCode &&
a.grpcCode == b.grpcCode &&
a.publicMessage == b.publicMessage
}