-
Notifications
You must be signed in to change notification settings - Fork 1
/
respond.go
197 lines (180 loc) · 5.48 KB
/
respond.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
package respond
import (
"encoding/json"
"io"
"net/http"
"strings"
)
// With describes the kind of response that will be made.
// respond.With{ [options] }.To(w, r)
type With struct {
// Data is the data object to respond with.
Data interface{}
// Status is the HTTP status to respond with. Use http.Status*
Status int
// Headers represents explicit HTTP headers that will be send
// along with the response.
Headers http.Header
// Options are the Options to use when responding.
// DefaultOptions are used by default.
Options *Options
}
// To writes the response from R to the specified http.ResponseWriter,
// referring to details in the http.Request where appropriate.
func (with With) To(w http.ResponseWriter, r *http.Request) error {
pWith := &with
opts := options(pWith)
ctx := &Ctx{W: w, R: r, With: pWith}
opts.WriteHeader(ctx, status(pWith))
if pWith.Data != nil {
if err := opts.WriteData(ctx, pWith.Data); err != nil {
return err
}
}
return nil
}
// status gets the status code that should be written
// for the specified With data.
func status(with *With) int {
const NoStatus = 0
if with.Status != NoStatus {
return with.Status
}
return options(with).DefaultStatus
}
func options(with *With) *Options {
if with.Options != nil {
return with.Options
}
return DefaultOptions
}
// Options represents the options with which responses are
// handled.
type Options struct {
// Encoders represents a map of content types to Encoder objects.
Encoders map[string]Encoder
// SetHeaders will set the headers on the ResponseWriter using the
// DefaultHeaders first, followed by any explicit headers.
SetHeaders func(c *Ctx)
// WriteHeader writes the header to the http.ResponseWriter.
WriteHeader func(c *Ctx, code int)
// WriteData writes the data to the http.ResponseWriter.
WriteData func(c *Ctx, data interface{}) error
// Encoder gets the Encoder to write the response data with.
Encoder func(c *Ctx) (Encoder, error)
// DefaultStatus is the default http.Status to use when none
// is specified.
DefaultStatus int
// DefaultEncoder is the default Encoder to use when none
// is specified.
DefaultEncoder Encoder
// DefaultHeaders represents the default HTTP headers to send
// with each request. DefaultHeaders will be merged with the
// explicit Headers when responding.
// DefaultHeaders will be overridden by any explicit headers
// if the keys match by default, do SetHeaders = SetHeadersAggregate
// to add the values instead.
DefaultHeaders http.Header
}
// Copy makes a copy of the options allowing the returning object
// to be modified without affecting the original.
// MyOptions := DefaultOptions.Copy()
func (o *Options) Copy() *Options {
return &Options{
Encoders: o.Encoders,
SetHeaders: o.SetHeaders,
WriteHeader: o.WriteHeader,
WriteData: o.WriteData,
Encoder: o.Encoder,
DefaultStatus: o.DefaultStatus,
DefaultEncoder: o.DefaultEncoder,
DefaultHeaders: o.DefaultHeaders,
}
}
// DefaultOptions represents the default options that will be
// used when responding.
// Properties of DefaultOptions may be changed directly, or else
// you can set specific options in each With object.
var DefaultOptions *Options
func init() {
JSONEncoder = (*jsonEncoder)(nil)
DefaultOptions = &Options{
DefaultStatus: http.StatusOK,
SetHeaders: SetHeadersOverride,
WriteHeader: func(c *Ctx, code int) {
options(c.With).SetHeaders(c)
c.W.WriteHeader(code)
},
WriteData: func(c *Ctx, data interface{}) error {
enc, err := options(c.With).Encoder(c)
if err != nil {
return err
}
return enc.Encode(c.W, data)
},
Encoders: map[string]Encoder{"application/json": JSONEncoder},
DefaultEncoder: JSONEncoder,
}
DefaultOptions.Encoder = func(c *Ctx) (Encoder, error) {
opts := options(c.With)
accept := strings.ToLower(c.R.Header.Get("Accept"))
for contentType, enc := range opts.Encoders {
if strings.Contains(accept, strings.ToLower(contentType)) {
return enc, nil
}
}
return DefaultOptions.DefaultEncoder, nil
}
}
// SetHeadersOverride is an Options.SetHeaders func that overrides
// DefaultHeaders with explicit ones.
func SetHeadersOverride(c *Ctx) {
setHeaders(c, true)
}
// SetHeadersAggregate is an Options.SetHeaders func that adds
// explicit headers to DefaultHeaders.
func SetHeadersAggregate(c *Ctx) {
setHeaders(c, false)
}
// setHeaders sets the headers, optionally overriding the
// defaults or not.
func setHeaders(c *Ctx, override bool) {
for key, vals := range options(c.With).DefaultHeaders {
for _, val := range vals {
c.W.Header().Add(key, val)
}
}
for key, vals := range c.With.Headers {
if override {
c.W.Header().Del(key)
}
for _, val := range vals {
c.W.Header().Add(key, val)
}
}
}
// Ctx wraps the http.ResponseWriter, http.Request and
// respond.With objects.
// Used when overriding DefaultOptions.
type Ctx struct {
// W is the http.ResponseWriter.
W http.ResponseWriter
// R is the http.Request.
R *http.Request
// With is the With object.
With *With
}
// Encoder represents an object capable of encoding data
// to an io.Writer.
type Encoder interface {
// Encode writes the data to the writer, returns an error
// if something went wrong, otherwise nil.
Encode(w io.Writer, data interface{}) error
}
type jsonEncoder struct{}
func (_ *jsonEncoder) Encode(w io.Writer, data interface{}) error {
return json.NewEncoder(w).Encode(data)
}
// JSONEncoder represents an Encoder capable of writing
// JSON.
var JSONEncoder Encoder // assigned to in init