-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgrpc_status.go
More file actions
181 lines (141 loc) · 4.11 KB
/
grpc_status.go
File metadata and controls
181 lines (141 loc) · 4.11 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
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
package servers
import (
"context"
"errors"
"github.com/bool64/ctxd"
"github.com/google/uuid"
"google.golang.org/genproto/googleapis/rpc/errdetails"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"
)
// Status references the status.Status from google.golang.org/grpc/status.
type Status struct {
*status.Status
err error
}
// Err returns an immutable error representing s; returns nil if s.Code() is OK.
func (s *Status) Err() error {
if s.Code() == codes.OK {
return nil
}
return &errSt{s: s}
}
// newStatus returns a Status representing c and msg.
//
// details is a string or a map[string]string otherwise panic.
//
// When details is a string, it is used as the error message.
// When details is a map[string]string, it is used as the details of validator failure and
// message is set as http.StatusText(runtime.HTTPStatusFromCode(c)).
func newStatus(c codes.Code, err error, details map[string]string) *Status {
msg := err.Error()
var lerr ctxd.SentinelError
if errors.As(err, &lerr) {
msg = lerr.Error()
}
id := uuid.New().String()
errInfo := &errdetails.ErrorInfo{
Reason: err.Error(),
Metadata: map[string]string{
"error_id": id,
},
}
// Creating kvs for structured logging with all possible details.
kvs := make([]any, 0, len(details)*2+2)
// Adding the error id to the metadata of the error.
kvs = append(kvs, "error_id", id)
// Adding the error id to the metadata of the error.
werr := errors.Unwrap(err)
if werr == nil {
werr = err
}
if len(details) == 0 {
grpcst, err := status.New(c, msg).WithDetails(errInfo)
if err != nil {
panic(err)
}
return &Status{
Status: grpcst,
err: ctxd.WrapError(context.Background(), werr, msg, kvs...),
}
}
kvs = append(kvs, "details", details)
for f, m := range details {
errInfo.Metadata[f] = m
}
grpcst, err := status.New(c, msg).WithDetails(errInfo)
if err != nil {
panic(err)
}
return &Status{
Status: grpcst,
err: ctxd.WrapError(context.Background(), werr, msg, kvs...),
}
}
// Error creates a new error with the given code, message and details if this is provided.
// If more than one details are provided, they are merged into a single map.
func Error(c codes.Code, msg string, details ...map[string]string) error {
if len(details) == 0 {
return newError(c, ctxd.SentinelError(msg), details...)
}
return newError(c, ctxd.SentinelError(msg), details...)
}
func newError(c codes.Code, err error, details ...map[string]string) error {
merge := make(map[string]string)
for _, d := range details {
for k, v := range d {
merge[k] = v
}
}
return newStatus(c, err, merge).Err()
}
// WrapError wraps an error with the given code, message and details if this is provided.
// If more than one details are provided, they are merged into a single map.
func WrapError(c codes.Code, err error, msg string, details ...map[string]string) error {
err = ctxd.LabeledError(err, ctxd.SentinelError(msg))
if len(details) == 0 {
return newError(c, err, details...)
}
return newError(c, err, details...)
}
type errSt struct {
s *Status
}
// Error returns the error message.
func (e *errSt) Error() string {
return e.s.Message()
}
// GRPCStatus returns the Status represented by se.
func (e *errSt) GRPCStatus() *status.Status {
return e.s.Status
}
// Is implements future error.Is functionality.
// A Error is equivalent if the code and message are identical.
func (e *errSt) Is(target error) bool {
tse, ok := target.(*errSt)
if !ok {
return false
}
return proto.Equal(e.s.Status.Proto(), tse.s.Status.Proto())
}
// Unwrap implements errors wrapper.
func (e *errSt) Unwrap() error {
return e.s.err
}
// Tuples returns structured data of error in form of loosely-typed key-value pairs.
func (e *errSt) Tuples() []any {
var errStuctured ctxd.StructuredError
if !errors.As(e.s.err, &errStuctured) {
return nil
}
return errStuctured.Tuples()
}
// Fields returns structured data of error as a map.
func (e *errSt) Fields() map[string]any {
var errStuctured ctxd.StructuredError
if !errors.As(e.s.err, &errStuctured) {
return nil
}
return errStuctured.Fields()
}