-
Notifications
You must be signed in to change notification settings - Fork 12
/
serializer.go
266 lines (227 loc) · 6.65 KB
/
serializer.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
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
package iris
import (
"bytes"
"encoding/json"
"encoding/xml"
"io"
"github.com/microcosm-cc/bluemonday"
"github.com/russross/blackfriday"
"github.com/valyala/bytebufferpool"
)
// these are the default render policies for basic REST-type render for content types:
// - application/javascript (json)
// - text/javascript (jsonp)
// - text/xml (xml)
// - custom internal text/markdown -> text/html (markdown)
// the fastest buffer pool is maden by valyala, we use that because Iris should be fast at every step.
var buffer bytebufferpool.Pool
// some options-helpers here
func tryParseStringOption(options map[string]interface{}, key string, defValue string) string {
if tryVal := options[key]; tryVal != nil {
if val, ok := tryVal.(string); ok {
return val
}
}
return defValue
}
func tryParseBoolOption(options map[string]interface{}, key string, defValue bool) bool {
if tryVal := options[key]; tryVal != nil {
if val, ok := tryVal.(bool); ok {
return val
}
}
return defValue
}
func tryParseByteSliceOption(options map[string]interface{}, key string, defValue []byte) []byte {
if tryVal := options[key]; tryVal != nil {
if val, ok := tryVal.([]byte); ok {
return val
}
}
return defValue
}
// +------------------------------------------------------------+
// | JSON |
// +------------------------------------------------------------+
var (
newLineB = []byte("\n")
// the html codes for unescaping
ltHex = []byte("\\u003c")
lt = []byte("<")
gtHex = []byte("\\u003e")
gt = []byte(">")
andHex = []byte("\\u0026")
and = []byte("&")
)
// Let's no use map here and do a func which will do simple and fast if statements.
// var serializers = map[string]func(interface{}, ...map[string]interface{}) ([]byte, error){
// contentJSON: serializeJSON,
// contentJSONP: serializeJSONP,
// contentXML: serializeXML,
// contentMarkdown: serializeMarkdown,
// }
var restRenderPolicy = RenderPolicy(func(out io.Writer, name string, val interface{}, options ...map[string]interface{}) (bool, error) {
var (
b []byte
err error
)
if name == contentJSON {
b, err = serializeJSON(val, options...)
} else if name == contentJSONP {
b, err = serializeJSONP(val, options...)
} else if name == contentXML {
b, err = serializeXML(val, options...)
} else if name == contentMarkdown {
b, err = serializeMarkdown(val, options...)
}
if err != nil {
return false, err // errors are wrapped
}
if len(b) > 0 {
_, err = out.Write(b)
return true, err
}
// continue to the next if any or notice there is no available renderer for that name
return false, nil
})
// serializeJSON accepts the 'object' value and converts it to bytes in order to be json 'renderable'
func serializeJSON(val interface{}, options ...map[string]interface{}) ([]byte, error) {
// parse the options
var (
indent bool
unEscapeHTML bool
streamingJSON bool
prefix []byte
)
if options != nil && len(options) > 0 {
opt := options[0]
indent = tryParseBoolOption(opt, "indent", false)
unEscapeHTML = tryParseBoolOption(opt, "unEscapeHTML", false)
streamingJSON = tryParseBoolOption(opt, "streamingJSON", false)
prefix = tryParseByteSliceOption(opt, "prefix", []byte(""))
}
// serialize the 'object'
if streamingJSON {
w := buffer.Get()
if len(prefix) > 0 {
w.Write(prefix)
}
err := json.NewEncoder(w).Encode(val)
result := w.Bytes()
buffer.Put(w)
return result, err
}
var result []byte
var err error
if indent {
result, err = json.MarshalIndent(val, "", " ")
result = append(result, newLineB...)
} else {
result, err = json.Marshal(val)
}
if err != nil {
return nil, err
}
if unEscapeHTML {
result = bytes.Replace(result, ltHex, lt, -1)
result = bytes.Replace(result, gtHex, gt, -1)
result = bytes.Replace(result, andHex, and, -1)
}
if len(prefix) > 0 {
result = append(prefix, result...)
}
return result, nil
}
// +------------------------------------------------------------+
// | JSONP |
// +------------------------------------------------------------+
var (
finishCallbackB = []byte(");")
)
// serializeJSONP accepts the 'object' value and converts it to bytes in order to be jsonp 'renderable'
func serializeJSONP(val interface{}, options ...map[string]interface{}) ([]byte, error) {
// parse the options
var (
indent bool
callback string
)
if options != nil && len(options) > 0 {
opt := options[0]
indent = tryParseBoolOption(opt, "indent", false)
callback = tryParseStringOption(opt, "callback", "")
}
var result []byte
var err error
if indent {
result, err = json.MarshalIndent(val, "", " ")
} else {
result, err = json.Marshal(val)
}
if err != nil {
return nil, err
}
if callback != "" {
result = append([]byte(callback+"("), result...)
result = append(result, finishCallbackB...)
}
if indent {
result = append(result, newLineB...)
}
return result, nil
}
// +------------------------------------------------------------+
// | XML |
// +------------------------------------------------------------+
// serializeXML accepts the 'object' value and converts it to bytes in order to be xml 'renderable'
func serializeXML(val interface{}, options ...map[string]interface{}) ([]byte, error) {
// parse the options
var (
indent bool
prefix []byte
)
if options != nil && len(options) > 0 {
opt := options[0]
indent = tryParseBoolOption(opt, "indent", false)
prefix = tryParseByteSliceOption(opt, "prefix", []byte(""))
}
var result []byte
var err error
if indent {
result, err = xml.MarshalIndent(val, "", " ")
result = append(result, '\n')
} else {
result, err = xml.Marshal(val)
}
if err != nil {
return nil, err
}
if len(prefix) > 0 {
result = append(prefix, result...)
}
return result, nil
}
// +------------------------------------------------------------+
// | MARKDOWN |
// +------------------------------------------------------------+
// serializeMarkdown accepts the 'object' value and converts it to bytes in order to be markdown(text/html) 'renderable'
func serializeMarkdown(val interface{}, options ...map[string]interface{}) ([]byte, error) {
// parse the options
var (
sanitize bool
)
if options != nil && len(options) > 0 {
opt := options[0]
sanitize = tryParseBoolOption(opt, "sanitize", false)
}
var b []byte
if s, isString := val.(string); isString {
b = []byte(s)
} else {
b = val.([]byte)
}
buf := blackfriday.MarkdownCommon(b)
if sanitize {
buf = bluemonday.UGCPolicy().SanitizeBytes(buf)
}
return buf, nil
}