mirror of
https://github.com/kataras/iris.git
synced 2025-12-27 14:57:05 +00:00
Move the kataras/go-serializer into one iris' file.
Start organising the kataras/go-*package which are relative to the Iris project. The kataras/go-*package will be exists and their features will be adapted to Iris every 2 months, as we say at readme, iris-relative packages should be tested for a long time before adapted to Iris. With this way we have stability, code readability(the developer can easly navigate to iris' code without need to move across different kataras/ projects). Former-commit-id: db291faaf59d4f53f14ce5800fde805f56c8b802
This commit is contained in:
266
serializer.go
Normal file
266
serializer.go
Normal file
@@ -0,0 +1,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
|
||||
}
|
||||
Reference in New Issue
Block a user