-
Notifications
You must be signed in to change notification settings - Fork 7
/
jsend.go
210 lines (164 loc) · 4.15 KB
/
jsend.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
/*
Package jsend implements JSend* specification.
You can wrap your http.ResponseWriter:
jsend.Wrap(w)
Returning object also implements http.ResponseWriter. So you can pass it to your
http middlewares.
Success example:
jsend.Wrap(w).
Data(yourData).
Send()
// body:
{
"status": "success",
"data": {
"foo": "bar"
}
}
Status field in response body is derived from http status code. Status is "fail"
if code is 4XX, "error" if code is 5XX and "success" otherwise.
Fail:
jsend.Wrap(w).
Status(400).
Data(yourData).
Send()
// body:
{
"status": "fail",
"data": {
"foo": "invalid"
}
}
Error:
jsend.Wrap(w).
Status(500).
Message("we are closed").
Send()
// body:
{
"status": "error",
"message": "we are closed"
}
* See http://labs.omniti.com/labs/jsend for jsend spec.
*/
package jsend
import (
"encoding/json"
"errors"
"net/http"
"sync"
)
// JSend status codes
const (
StatusSuccess = "success"
StatusError = "error"
StatusFail = "fail"
)
const (
fieldMsg = "message"
fieldData = "data"
fieldStatus = "status"
)
// Wrap wraps given http.ResponseWriter and returns a response object which
// implements JResponseWriter interface.
//
// If given parameter already implements JResponseWriter "Wrap" returns it
// instead of wrapping it again.
func Wrap(w http.ResponseWriter) JResponseWriter {
if w, ok := w.(JResponseWriter); ok {
return w
}
if w.Header().Get("Content-Type") == "" {
w.Header().Set("Content-Type", "application/json")
}
return &Response{rw: w, fields: make(map[string]interface{})}
}
// A JResponseWriter interface extends http.ResponseWriter of go standard library
// to add utility methods for JSend format.
type JResponseWriter interface {
http.ResponseWriter
Data(interface{}) JResponseWriter
Message(string) JResponseWriter
Status(int) JResponseWriter
Field(string, interface{}) JResponseWriter
Send() (int, error)
}
// Response wraps a http.ResponseWriter type and adds jsend methods. Returning
// type implements JResponseWriter which extends http.ResponseWriter.
//
// Response buffers given data and writes nothing until "Send" is called.
type Response struct {
rw http.ResponseWriter
code int
sent bool
fields map[string]interface{}
mu sync.Mutex
}
// Field method allows you to set custom response fields.
func (r *Response) Field(key string, value interface{}) JResponseWriter {
r.fields[key] = value
return r
}
// Data sets response's "data" field with given value.
func (r *Response) Data(data interface{}) JResponseWriter {
return r.Field(fieldData, data)
}
// Message sets response's "message" field with given value.
func (r *Response) Message(msg string) JResponseWriter {
return r.Field(fieldMsg, msg)
}
// Status sets http statusCode. It is a shorthand for "WriteHeader" method
// in order to keep method chaining.
func (r *Response) Status(code int) JResponseWriter {
r.code = code
return r
}
// Header calls Header method of wrapped http.ResponseWriter.
func (r *Response) Header() http.Header {
return r.rw.Header()
}
// WriteHeader calls WriteHeader method of wrapped http.ResponseWriter.
func (r *Response) WriteHeader(code int) {
r.code = code
r.rw.WriteHeader(code)
}
// Write calls Write method of wrapped http.ResponseWriter.
func (r *Response) Write(data []byte) (int, error) {
return r.rw.Write(data)
}
var errSentAlready = errors.New("jsend: sent already")
// Send encodes and writes buffered data to underlying http response object.
func (r *Response) Send() (int, error) {
r.mu.Lock()
defer r.mu.Unlock()
if r.sent {
return 0, errSentAlready
}
r.sent = true
if r.code == 0 {
r.code = 200
}
status := getStatus(r.code)
r.WriteHeader(r.code)
r.Field(fieldStatus, status)
if _, hasMsg := r.fields[fieldMsg]; !hasMsg && status == StatusError {
r.Message(http.StatusText(r.code))
}
if _, hasData := r.fields[fieldData]; !hasData && status != StatusError {
r.Data([]byte(nil))
}
j, err := json.Marshal(r.fields)
if err != nil {
return 0, err
}
return r.Write(j)
}
func getStatus(code int) string {
switch {
case code >= 500:
return StatusError
case code >= 400 && code < 500:
return StatusFail
}
return StatusSuccess
}