-
Notifications
You must be signed in to change notification settings - Fork 1
/
options.go
247 lines (220 loc) · 7.5 KB
/
options.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
package httphandler
import (
"context"
"encoding/json"
"encoding/xml"
"fmt"
"io"
"log"
"net/http"
"strconv"
"strings"
"github.com/pkg/errors"
"github.com/google/uuid"
)
// LogFunc is the log function that will be called in case of error.
type LogFunc func(r *http.Request, handlerError, internalError, publicError error, statusCode int, requestUUID string)
// EncodeFunc is the encode function that will be called to encode the WireError in the desired format.
type EncodeFunc func(http.ResponseWriter, *http.Request, *WireError) error
// Options is a structure that should be passed into New() it defines and controls behavior of HandleFunc().
type Options struct {
// LogFunc is the log function that will be called in case of error.
// If LogFunc is nil the default logger will be used.
LogFunc LogFunc
// Encoders is a map of Content-Type and EncodeFunc, it will be used to lookup the encoder for the Content-Type.
// If Encoder is nil the default encoders will be used.
Encoders map[string]EncodeFunc
// FallbackEncoderFunc should return a fallback encoder in case the error Content-Type does not exist in the
// Encoders map.
// If FallbackEncoderFunc is nil the default fallback encoder will be used.
FallbackEncoderFunc func() (EncodeFunc, string)
// RequestUUIDFunc specifies the function that returns an request uuid. This request uuid will be send to the
// LogFunc in case of error.
// The RequestUUID is also available in the specified handler (in HandleFunc()) by using GetRequestUUID().
// If RequestUUIDFunc is nil the default request uuid func will be used.
RequestUUIDFunc func() string
// CustomPanicHandler it's called when a panic occurs in the HTTP handler. It gets the request context value.
CustomPanicHandler PanicHandler
}
// SetLogFunc sets the log function that will be called in case of error.
func (o *Options) SetLogFunc(logFunc LogFunc) error {
if logFunc == nil {
return errors.New("logFunc cannot be nil")
}
o.LogFunc = logFunc
return nil
}
// SetEncoders sets the Encoders to the specified map of content type and EncodeFunc.
// It will be used to lookup the encoder for the error content type.
func (o *Options) SetEncoders(encoders map[string]EncodeFunc) error {
if encoders == nil {
return errors.New("encoders cannot be nil")
}
for contentType, encoder := range encoders {
o.Encoders[strings.ToLower(contentType)] = encoder
}
return nil
}
// SetEncoder sets one specific encoder in the Encoders map.
func (o *Options) SetEncoder(contentType string, encoder EncodeFunc) error {
if contentType == "" {
return errors.New("content-type cannot be empty")
}
if encoder == nil {
return errors.New("encoder cannot be nil")
}
if o.Encoders == nil {
o.Encoders = make(map[string]EncodeFunc)
}
o.Encoders[strings.ToLower(contentType)] = encoder
return nil
}
// SetFallbackEncoder sets the fallback encoder in case the error Content-Type does not exist in the
// Encoders map.
func (o *Options) SetFallbackEncoder(contentType string, encoder EncodeFunc) error {
if contentType == "" {
return errors.New("content-type cannot be empty")
}
if encoder == nil {
return errors.New("encoder cannot be nil")
}
o.FallbackEncoderFunc = func() (EncodeFunc, string) {
return encoder, contentType
}
return nil
}
// SetRequestUUIDFunc specifies the function that returns an request uuid. This request uuid will be send to the
// LogFunc in case of error.
// The request uuid is also available in the specified handler (in HandleFunc()) by using GetRequestUUID().
func (o *Options) SetRequestUUIDFunc(requestUUIDFunc func() string) error {
if requestUUIDFunc == nil {
return errors.New("requestUUIDFunc cannot be nil")
}
o.RequestUUIDFunc = requestUUIDFunc
return nil
}
// SetCustomPanicHandler sets a custom function that is going to be called when a panic occurs.
func (o *Options) SetCustomPanicHandler(f PanicHandler) {
o.CustomPanicHandler = f
}
func defaultOptions() *Options {
return &Options{
LogFunc: defaultLogFunc(),
Encoders: defaultEncoders(),
FallbackEncoderFunc: defaultFallbackEncoder(),
RequestUUIDFunc: defaultRequestUUID(),
CustomPanicHandler: defaultCustomPanicHandler(),
}
}
func defaultLogFunc() LogFunc {
return func(_ *http.Request, handlerError, internalError, publicError error, statusCode int, requestUUID string) {
log.Printf("%v: internalError=%v, publicError=%v, statusCode=%d, requestUUID=%s",
handlerError,
internalError,
publicError,
statusCode,
requestUUID,
)
}
}
func defaultEncoders() map[string]EncodeFunc {
return map[string]EncodeFunc{
"application/json": DefaultJSONEncoder(),
"application/xml": DefaultXMLEncoder(),
"text/html": DefaultHTMLEncoder(),
"text/xml": DefaultXMLEncoder(),
}
}
func defaultFallbackEncoder() func() (EncodeFunc, string) {
return func() (EncodeFunc, string) {
return DefaultJSONEncoder(), "application/json"
}
}
func defaultRequestUUID() func() string {
return func() string {
return uuid.New().String()
}
}
func defaultCustomPanicHandler() PanicHandler {
return func(ctx context.Context, err *HandlerError) {}
}
// DefaultJSONEncoder implements the default JSON encoder that will be used.
func DefaultJSONEncoder() EncodeFunc {
return func(w http.ResponseWriter, r *http.Request, e *WireError) error {
errToSend := struct {
StatusCode *int
Error interface{}
RequestUUID *string
}{
StatusCode: &e.StatusCode,
RequestUUID: &e.RequestUUID,
}
// marshal the Error before everything else
buf, err := json.Marshal(e.Error)
if err != nil {
return errors.Wrap(err, "unable to encode error")
}
// if the error message is empty use the Error() function
if len(buf) == 0 || string(buf) == "{}" || string(buf) == "null" {
errToSend.Error = e.Error.Error()
} else {
errToSend.Error = json.RawMessage(buf)
}
return json.NewEncoder(w).Encode(errToSend)
}
}
// DefaultXMLEncoder implements the default XML encoder that will be used.
func DefaultXMLEncoder() EncodeFunc {
return func(w http.ResponseWriter, r *http.Request, e *WireError) error {
errToSend := struct {
StatusCode *int
Error interface{}
RequestUUID *string
}{
StatusCode: &e.StatusCode,
RequestUUID: &e.RequestUUID,
}
// marshal the Error before everything else
buf, err := xml.Marshal(e.Error)
if err != nil {
return errors.Wrap(err, "unable to encode error")
}
// if the error message is empty use the Error() function
if len(buf) == 0 || string(buf) == "<errorString></errorString>" {
errToSend.Error = json.RawMessage(e.Error.Error())
} else {
errToSend.Error = buf
}
return xml.NewEncoder(w).Encode(errToSend)
}
}
// DefaultHTMLEncoder implements the default HTML encoder that will be used.
func DefaultHTMLEncoder() EncodeFunc {
return func(w http.ResponseWriter, r *http.Request, e *WireError) error {
if _, err := io.WriteString(w, "<!DOCTYPE html><html><head><title>"); err != nil {
return err
}
if _, err := io.WriteString(w, strconv.Itoa(e.StatusCode)); err != nil {
return err
}
if _, err := io.WriteString(w, " Error</title></head><body><h1>"); err != nil {
return err
}
if _, err := fmt.Fprintf(w, "%#v", e.Error); err != nil {
return err
}
if _, err := io.WriteString(w, "<hr>"); err != nil {
return err
}
if _, err := io.WriteString(w, "<p>RequestUUID: <code>"); err != nil {
return err
}
if _, err := io.WriteString(w, e.RequestUUID); err != nil {
return err
}
if _, err := io.WriteString(w, "</code></p></body></html>"); err != nil {
return err
}
return nil
}
}