@@ -2,20 +2,12 @@ package gateway
2
2
3
3
import (
4
4
"context"
5
- "encoding/json"
6
5
"fmt"
7
6
"io"
8
- "math"
9
7
"net/http"
10
- "strconv"
11
- "strings"
12
- "sync/atomic"
13
- "time"
14
8
15
- "google.golang.org/grpc"
16
9
"google.golang.org/grpc/codes"
17
10
"google.golang.org/grpc/grpclog"
18
- "google.golang.org/grpc/metadata"
19
11
"google.golang.org/grpc/status"
20
12
21
13
"github.com/grpc-ecosystem/grpc-gateway/runtime"
@@ -28,8 +20,12 @@ import (
28
20
// Addition bool argument indicates whether method (http.ResponseWriter.WriteHeader) was called or not.
29
21
type ProtoStreamErrorHandlerFunc func (context.Context , bool , * runtime.ServeMux , runtime.Marshaler , http.ResponseWriter , * http.Request , error )
30
22
31
- type RestErrs struct {
32
- Error []map [string ]interface {} `json:"error,omitempty"`
23
+ // RestError represents an error in accordance with REST API Syntax Specification.
24
+ // See: https://github.com/infobloxopen/atlas-app-toolkit#errors
25
+ type RestError struct {
26
+ Status * RestStatus `json:"error,omitempty"`
27
+ Details []interface {} `json:"details,omitempty"`
28
+ Fields interface {} `json:"fields,omitempty"`
33
29
}
34
30
35
31
var (
@@ -110,28 +106,19 @@ func (h *ProtoErrorHandler) writeError(ctx context.Context, headerWritten bool,
110
106
}
111
107
}
112
108
113
- restErr := map [string ]interface {}{
114
- "message" : st .Message (),
115
- }
116
- if len (details ) > 0 {
117
- restErr ["details" ] = details
118
- }
119
- if fields != nil {
120
- restErr ["fields" ] = fields
109
+ restErr := & RestError {
110
+ Status : Status (ctx , st ),
111
+ Details : details ,
112
+ Fields : fields ,
121
113
}
122
114
123
- errs , _ := errorsAndSuccessFromContext (ctx )
124
- restResp := & RestErrs {
125
- Error : errs ,
126
- }
127
- restResp .Error = append (restResp .Error , restErr )
128
115
if ! headerWritten {
129
116
rw .Header ().Del ("Trailer" )
130
117
rw .Header ().Set ("Content-Type" , marshaler .ContentType ())
131
- rw .WriteHeader (HTTPStatus ( ctx , st ) )
118
+ rw .WriteHeader (restErr . Status . HTTPStatus )
132
119
}
133
120
134
- buf , merr := marshaler .Marshal (restResp )
121
+ buf , merr := marshaler .Marshal (restErr )
135
122
if merr != nil {
136
123
grpclog .Infof ("error handler: failed to marshal error message %q: %v" , restErr , merr )
137
124
rw .WriteHeader (http .StatusInternalServerError )
@@ -146,125 +133,3 @@ func (h *ProtoErrorHandler) writeError(ctx context.Context, headerWritten bool,
146
133
grpclog .Infof ("error handler: failed to write response: %v" , err )
147
134
}
148
135
}
149
-
150
- // For small performance bump, switch map[string]string to a tuple-type (string, string)
151
-
152
- type MessageWithFields interface {
153
- error
154
- GetFields () map [string ]interface {}
155
- GetMessage () string
156
- }
157
- type messageWithFields struct {
158
- message string
159
- fields map [string ]interface {}
160
- }
161
-
162
- func (m * messageWithFields ) Error () string {
163
- return m .message
164
- }
165
- func (m * messageWithFields ) GetFields () map [string ]interface {} {
166
- return m .fields
167
- }
168
- func (m * messageWithFields ) GetMessage () string {
169
- return m .message
170
- }
171
-
172
- // NewWithFields returns a new MessageWithFields that requires a message string,
173
- // and then treats the following arguments as alternating keys and values
174
- // a non-string key will immediately return the result so far, ignoring later
175
- // values. The values can be any type
176
- func NewWithFields (message string , kvpairs ... interface {}) MessageWithFields {
177
- mwf := & messageWithFields {message : message , fields : make (map [string ]interface {})}
178
- for i := 0 ; i + 1 < len (kvpairs ); i += 2 {
179
- k , ok := kvpairs [i ].(string )
180
- if ! ok {
181
- return mwf
182
- }
183
- mwf .fields [k ] = kvpairs [i + 1 ]
184
- }
185
- return mwf
186
- }
187
-
188
- // For giving each error a unique metadata key, but not leaking the exact count
189
- // of errors or something like that
190
- var counter * uint32
191
-
192
- func init () {
193
- counter = new (uint32 )
194
- * counter = uint32 (time .Now ().Nanosecond () % math .MaxUint32 )
195
- }
196
-
197
- // WithError will save an error message into the grpc trailer metadata, if it
198
- // is an error that implements MessageWithFields, it also saves the fields.
199
- // This error will then be inserted into the return JSON if the ResponseForwarder
200
- // is used
201
- func WithError (ctx context.Context , err error ) {
202
- i := atomic .AddUint32 (counter , uint32 (time .Now ().Nanosecond ()% 100 + 1 ))
203
- md := metadata .Pairs (fmt .Sprintf ("error-%d" , i ), fmt .Sprintf ("message:%s" , err .Error ()))
204
- if mwf , ok := err .(MessageWithFields ); ok {
205
- if f := mwf .GetFields (); f != nil {
206
- b , _ := json .Marshal (mwf .GetFields ())
207
- md .Append (fmt .Sprintf ("error-%d" , i ), fmt .Sprintf ("fields:%q" , b ))
208
- }
209
- }
210
- grpc .SetTrailer (ctx , md )
211
- }
212
-
213
- // WithSuccess will save a MessageWithFields into the grpc trailer metadata.
214
- // This success message will then be inserted into the return JSON if the
215
- // ResponseForwarder is used
216
- func WithSuccess (ctx context.Context , msg MessageWithFields ) {
217
- i := atomic .AddUint32 (counter , uint32 (time .Now ().Nanosecond ()% 100 + 1 ))
218
- md := metadata .Pairs (fmt .Sprintf ("success-%d" , i ), fmt .Sprintf ("message:%s" , msg .Error ()))
219
- if f := msg .GetFields (); f != nil {
220
- b , _ := json .Marshal (msg .GetFields ())
221
- md .Append (fmt .Sprintf ("success-%d" , i ), fmt .Sprintf ("fields:%q" , b ))
222
- }
223
- grpc .SetTrailer (ctx , md )
224
- }
225
-
226
- func errorsAndSuccessFromContext (ctx context.Context ) (errors []map [string ]interface {}, success map [string ]interface {}) {
227
- md , ok := runtime .ServerMetadataFromContext (ctx )
228
- if ! ok {
229
- return nil , nil
230
- }
231
- errors = make ([]map [string ]interface {}, 0 )
232
- latestSuccess := int64 (- 1 )
233
- for k , vs := range md .TrailerMD {
234
- if strings .HasPrefix (k , "error-" ) {
235
- err := make (map [string ]interface {})
236
- for _ , v := range vs {
237
- parts := strings .SplitN (v , ":" , 2 )
238
- if parts [0 ] == "fields" {
239
- uq , _ := strconv .Unquote (parts [1 ])
240
- json .Unmarshal ([]byte (uq ), & err )
241
- } else if parts [0 ] == "message" {
242
- err ["message" ] = parts [1 ]
243
- }
244
- }
245
- errors = append (errors , err )
246
- }
247
- if num := strings .TrimPrefix (k , "success-" ); num != k {
248
- // Let the later success messages override previous ones,
249
- // also account for the possiblity of wraparound with a generous check
250
- if i , err := strconv .ParseInt (num , 10 , 32 ); err == nil {
251
- if i > latestSuccess || (i < 1 << 12 && latestSuccess > 1 << 28 ) {
252
- latestSuccess = i
253
- } else {
254
- continue
255
- }
256
- }
257
- success = make (map [string ]interface {})
258
- for _ , v := range vs {
259
- parts := strings .SplitN (v , ":" , 2 )
260
- if parts [0 ] == "fields" {
261
- uq , _ := strconv .Unquote (parts [1 ])
262
- json .Unmarshal ([]byte (uq ), & success )
263
- } else if parts [0 ] == "message" {
264
- success ["message" ] = parts [1 ]
265
- }
266
- }
267
- }
268
- }
269
- return
270
- }
0 commit comments