forked from infobloxopen/atlas-app-toolkit
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patherrors.go
135 lines (110 loc) · 4.67 KB
/
errors.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
package gateway
import (
"context"
"fmt"
"io"
"net/http"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/status"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/infobloxopen/atlas-app-toolkit/rpc/errdetails"
"github.com/infobloxopen/atlas-app-toolkit/rpc/errfields"
)
// ProtoStreamErrorHandlerFunc handles the error as a gRPC error generated via status package and replies to the testRequest.
// Addition bool argument indicates whether method (http.ResponseWriter.WriteHeader) was called or not.
type ProtoStreamErrorHandlerFunc func(context.Context, bool, *runtime.ServeMux, runtime.Marshaler, http.ResponseWriter, *http.Request, error)
// RestError represents an error in accordance with REST API Syntax Specification.
// See: https://github.com/infobloxopen/atlas-app-toolkit#errors
type RestError struct {
Status *RestStatus `json:"error,omitempty"`
Details []interface{} `json:"details,omitempty"`
Fields interface{} `json:"fields,omitempty"`
}
var (
// ProtoMessageErrorHandler uses PrefixOutgoingHeaderMatcher.
// To use ProtoErrorHandler with custom outgoing header matcher call NewProtoMessageErrorHandler.
ProtoMessageErrorHandler = NewProtoMessageErrorHandler(PrefixOutgoingHeaderMatcher)
// ProtoStreamErrorHandler uses PrefixOutgoingHeaderMatcher.
// To use ProtoErrorHandler with custom outgoing header matcher call NewProtoStreamErrorHandler.
ProtoStreamErrorHandler = NewProtoStreamErrorHandler(PrefixOutgoingHeaderMatcher)
)
// NewProtoMessageErrorHandler returns runtime.ProtoErrorHandlerFunc
func NewProtoMessageErrorHandler(out runtime.HeaderMatcherFunc) runtime.ProtoErrorHandlerFunc {
h := &ProtoErrorHandler{out}
return h.MessageHandler
}
// NewProtoStreamErrorHandler returns ProtoStreamErrorHandlerFunc
func NewProtoStreamErrorHandler(out runtime.HeaderMatcherFunc) ProtoStreamErrorHandlerFunc {
h := &ProtoErrorHandler{out}
return h.StreamHandler
}
// ProtoErrorHandler implements runtime.ProtoErrorHandlerFunc in method MessageHandler
// and ProtoStreamErrorHandlerFunc in method StreamHandler
// in accordance with REST API Syntax Specification.
// See RestError for the JSON format of an error
type ProtoErrorHandler struct {
OutgoingHeaderMatcher runtime.HeaderMatcherFunc
}
// MessageHandler implements runtime.ProtoErrorHandlerFunc
// in accordance with REST API Syntax Specification.
// See RestError for the JSON format of an error
func (h *ProtoErrorHandler) MessageHandler(ctx context.Context, mux *runtime.ServeMux, marshaler runtime.Marshaler, rw http.ResponseWriter, req *http.Request, err error) {
md, ok := runtime.ServerMetadataFromContext(ctx)
if !ok {
grpclog.Infof("error handler: failed to extract ServerMetadata from context")
}
handleForwardResponseServerMetadata(h.OutgoingHeaderMatcher, rw, md)
handleForwardResponseTrailerHeader(rw, md)
h.writeError(ctx, false, marshaler, rw, err)
handleForwardResponseTrailer(rw, md)
}
// StreamHandler implements ProtoStreamErrorHandlerFunc
// in accordance with REST API Syntax Specification.
// See RestError for the JSON format of an error
func (h *ProtoErrorHandler) StreamHandler(ctx context.Context, headerWritten bool, mux *runtime.ServeMux, marshaler runtime.Marshaler, rw http.ResponseWriter, req *http.Request, err error) {
h.writeError(ctx, headerWritten, marshaler, rw, err)
}
func (h *ProtoErrorHandler) writeError(ctx context.Context, headerWritten bool, marshaler runtime.Marshaler, rw http.ResponseWriter, err error) {
const fallback = `{"code":"INTERNAL","status":500,"message":"%s"}`
st, ok := status.FromError(err)
if !ok {
st = status.New(codes.Unknown, err.Error())
}
details := []interface{}{}
var fields interface{}
for _, d := range st.Details() {
switch d.(type) {
case *errdetails.TargetInfo:
details = append(details, d)
case *errfields.FieldInfo:
fields = d
default:
grpclog.Infof("error handler: failed to recognize error message")
rw.WriteHeader(http.StatusInternalServerError)
return
}
}
restErr := &RestError{
Status: Status(ctx, st),
Details: details,
Fields: fields,
}
if !headerWritten {
rw.Header().Del("Trailer")
rw.Header().Set("Content-Type", marshaler.ContentType())
rw.WriteHeader(restErr.Status.HTTPStatus)
}
buf, merr := marshaler.Marshal(restErr)
if merr != nil {
grpclog.Infof("error handler: failed to marshal error message %q: %v", restErr, merr)
rw.WriteHeader(http.StatusInternalServerError)
if _, err := io.WriteString(rw, fmt.Sprintf(fallback, merr)); err != nil {
grpclog.Infof("error handler: failed to write response: %v", err)
}
return
}
if _, err := rw.Write(buf); err != nil {
grpclog.Infof("error handler: failed to write response: %v", err)
}
}