-
Notifications
You must be signed in to change notification settings - Fork 0
/
grpc.go
322 lines (287 loc) · 7.1 KB
/
grpc.go
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
package aerrors
import (
"errors"
"fmt"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type GRPCCoder interface {
GRPCCode() codes.Code
}
// nolint:gocyclo
func (err Code) GRPCCode() codes.Code {
switch err {
// GRPC Errors
case ErrOK:
return codes.OK
case ErrCanceled:
return codes.Canceled
case ErrUnknown:
return codes.Unknown
case ErrInvalidArgument:
return codes.InvalidArgument
case ErrDeadlineExceeded:
return codes.DeadlineExceeded
case ErrNotFound:
return codes.NotFound
case ErrAlreadyExists:
return codes.AlreadyExists
case ErrPermissionDenied:
return codes.PermissionDenied
case ErrResourceExhausted:
return codes.ResourceExhausted
case ErrFailedPrecondition:
return codes.FailedPrecondition
case ErrAborted:
return codes.Aborted
case ErrOutOfRange:
return codes.OutOfRange
case ErrUnimplemented:
return codes.Unimplemented
case ErrInternal:
return codes.Internal
case ErrUnavailable:
return codes.Unavailable
case ErrDataLoss:
return codes.DataLoss
case ErrUnauthenticated:
return codes.Unauthenticated
// HTTP Errors
case ErrBadRequest:
return codes.InvalidArgument
case ErrUnauthorized:
return codes.Unauthenticated
case ErrForbidden:
return codes.PermissionDenied
case ErrMethodNotAllowed:
return codes.Unimplemented
case ErrRequestTimeout:
return codes.DeadlineExceeded
case ErrConflict:
return codes.AlreadyExists
case ErrImATeapot:
return codes.Unknown
case ErrUnprocessableEntity:
return codes.InvalidArgument
case ErrTooManyRequests:
return codes.ResourceExhausted
case ErrUnavailableForLegalReasons:
return codes.Unavailable
case ErrInternalServerError:
return codes.Internal
case ErrNotImplemented:
return codes.Unimplemented
case ErrBadGateway:
return codes.Aborted
case ErrServiceUnavailable:
return codes.Unavailable
case ErrGatewayTimeout:
return codes.DeadlineExceeded
default:
return codes.Internal
}
}
func (err Code) GRPCStatus() *status.Status {
return errToStatus(err)
}
func (err *AError) GRPCStatus() *status.Status {
return errToStatus(err)
}
type grpcError struct {
status *status.Status
code string
reason string
message string
httpCode int
grpcCode codes.Code
}
func (err *grpcError) Error() string {
return fmt.Sprintf(
"Code=%s Reason=%s Message=(%v)",
err.code,
err.reason,
err.message,
)
}
func (err *grpcError) Status() *status.Status {
return err.status
}
func (err *grpcError) HTTPCode() int {
return err.httpCode
}
func (err *grpcError) GRPCCode() codes.Code {
return err.grpcCode
}
func (err *grpcError) TypeCode() string {
return err.code
}
// Is returns true if any of TypeCoder, HTTPCoder, GRPCCoder are a match between the error and target
func (err *grpcError) Is(target error) bool {
if t, ok := target.(GRPCCoder); ok && err.grpcCode == t.GRPCCode() {
return true
}
if t, ok := target.(HTTPCoder); ok && err.httpCode == t.HTTPCode() {
return true
}
if t, ok := target.(TypeCoder); ok && err.code == t.TypeCode() {
return true
}
return false
}
// GRPCCode returns the GRPC code for the given error or codes.OK when nil or codes.Unknown otherwise
func GRPCCode(err error) codes.Code {
if err == nil {
return ErrOK.GRPCCode()
}
var e GRPCCoder
if errors.As(err, &e) {
return e.GRPCCode()
}
return ErrUnknown.GRPCCode()
}
// SendGRPCError ensures that the error being used is sent with the correct code applied
//
// Use in the server when sending errors.
// If err is nil then SendGRPCError returns nil.
func SendGRPCError(err error) error {
if err == nil {
return nil
}
// Already setup with a grpcCode
if _, ok := status.FromError(err); ok {
return err
}
s := errToStatus(err)
return s.Err()
}
// ReceiveGRPCError recreates the error with the coded Error reapplied
//
// Non-nil results can be used as both Error and *status.Status. Methods
// errors.Is()/errors.As(), and status.Convert()/status.FromError() will
// continue to work.
//
// Use in the clients when receiving errors.
// If err is nil then ReceiveGRPCError returns nil.
func ReceiveGRPCError(err error) error {
if err == nil {
return nil
}
s, ok := status.FromError(err)
if !ok {
return &grpcError{
status: s,
grpcCode: ErrUnknown.GRPCCode(),
httpCode: ErrUnknown.HTTPCode(),
code: ErrUnknown.TypeCode(),
reason: ErrUnknown.Error(),
message: err.Error(),
}
}
grpcCode := s.Code()
httpCode := ErrUnknown.HTTPCode()
embedType := codeToError(grpcCode).TypeCode()
reason := ErrUnknown.Error()
for _, detail := range s.Details() {
switch d := detail.(type) {
case *ErrorDetail:
grpcCode = codes.Code(d.GRPCCode)
httpCode = int(d.HTTPCode)
embedType = d.TypeCode
reason = d.Reason
default:
}
}
return &grpcError{
status: s,
grpcCode: grpcCode,
httpCode: httpCode,
code: embedType,
reason: reason,
message: s.Message(),
}
}
func ExtractGRPCError(err error) (c codes.Code, msg string, ok bool) {
grpcErr, _ := ReceiveGRPCError(err).(*grpcError)
if grpcErr == nil {
return codes.OK, "", false
}
return grpcErr.GRPCCode(), grpcErr.message, true
}
// convert a code to a known Error type;
func codeToError(code codes.Code) Code {
switch code {
case codes.OK:
return ErrOK
case codes.Canceled:
return ErrCanceled
case codes.Unknown:
return ErrUnknown
case codes.InvalidArgument:
return ErrInvalidArgument
case codes.DeadlineExceeded:
return ErrDeadlineExceeded
case codes.NotFound:
return ErrNotFound
case codes.AlreadyExists:
return ErrAlreadyExists
case codes.PermissionDenied:
return ErrPermissionDenied
case codes.ResourceExhausted:
return ErrResourceExhausted
case codes.FailedPrecondition:
return ErrFailedPrecondition
case codes.Aborted:
return ErrAborted
case codes.OutOfRange:
return ErrOutOfRange
case codes.Unimplemented:
return ErrUnimplemented
case codes.Internal:
return ErrInternal
case codes.Unavailable:
return ErrUnavailable
case codes.DataLoss:
return ErrDataLoss
case codes.Unauthenticated:
return ErrUnauthenticated
default:
return ErrInternal
}
}
// convert an error into a gRPC *status.Status
func errToStatus(err error) *status.Status {
grpcCode := ErrUnknown.GRPCCode()
httpCode := ErrUnknown.HTTPCode()
typeCode := ErrUnknown.TypeCode()
// Set the grpcCode based on GRPCCoder output; otherwise leave as Unknown
var grpcCoder GRPCCoder
if errors.As(err, &grpcCoder) {
grpcCode = grpcCoder.GRPCCode()
}
// short circuit building detailed errors if the code is OK
if grpcCode == codes.OK {
return status.New(codes.OK, "")
}
// Set the httpCode based on HTTPCoder output; otherwise leave as Unknown
var httpCoder HTTPCoder
if errors.As(err, &httpCoder) {
httpCode = httpCoder.HTTPCode()
}
// Embed the specific error "type"; otherwise leave as "UNKNOWN"
var typeCoder TypeCoder
if errors.As(err, &typeCoder) {
typeCode = typeCoder.TypeCode()
}
errInfo := &ErrorDetail{
TypeCode: typeCode,
GRPCCode: int64(grpcCode),
HTTPCode: int64(httpCode),
}
var e AError
if ok := errors.As(err, &e); ok {
errInfo.Reason = e.reason
errInfo.Message = e.message
}
s, _ := status.New(grpcCode, err.Error()).WithDetails(errInfo)
return s
}