mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01: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:
parent
a4ff95f1d9
commit
57aea4aa75
|
@ -522,14 +522,17 @@ func New() iris.Policies {
|
||||||
RouterReversionPolicy: iris.RouterReversionPolicy{
|
RouterReversionPolicy: iris.RouterReversionPolicy{
|
||||||
// path normalization done on iris' side
|
// path normalization done on iris' side
|
||||||
StaticPath: func(path string) string {
|
StaticPath: func(path string) string {
|
||||||
|
|
||||||
i := strings.IndexByte(path, parameterStartByte)
|
i := strings.IndexByte(path, parameterStartByte)
|
||||||
x := strings.IndexByte(path, matchEverythingByte)
|
v := strings.IndexByte(path, matchEverythingByte)
|
||||||
if i > -1 {
|
if i > -1 || v > -1 {
|
||||||
|
if i < v {
|
||||||
return path[0:i]
|
return path[0:i]
|
||||||
}
|
}
|
||||||
if x > -1 {
|
// we can't return path[0:0]
|
||||||
return path[0:x]
|
if v > 0 {
|
||||||
|
return path[0:v]
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return path
|
return path
|
||||||
|
|
|
@ -125,13 +125,13 @@ func (h *Adaptor) Adapt(frame *iris.Policies) {
|
||||||
// adapt the build event to the main policies
|
// adapt the build event to the main policies
|
||||||
evt.Adapt(frame)
|
evt.Adapt(frame)
|
||||||
|
|
||||||
r := iris.RenderPolicy(func(out io.Writer, file string, tmplContext interface{}, options ...map[string]interface{}) (error, bool) {
|
r := iris.RenderPolicy(func(out io.Writer, file string, tmplContext interface{}, options ...map[string]interface{}) (bool, error) {
|
||||||
// template mux covers that but maybe we have more than one RenderPolicy
|
// template mux covers that but maybe we have more than one RenderPolicy
|
||||||
// and each of them carries a different mux on the new design.
|
// and each of them carries a different mux on the new design.
|
||||||
if strings.Contains(file, h.extension) {
|
if strings.Contains(file, h.extension) {
|
||||||
return mux.ExecuteWriter(out, file, tmplContext, options...), true
|
return true, mux.ExecuteWriter(out, file, tmplContext, options...)
|
||||||
}
|
}
|
||||||
return nil, false
|
return false, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
r.Adapt(frame)
|
r.Adapt(frame)
|
||||||
|
|
|
@ -1034,7 +1034,7 @@ func (ctx *Context) XML(status int, v interface{}) error {
|
||||||
// MarkdownString parses the (dynamic) markdown string and returns the converted html string
|
// MarkdownString parses the (dynamic) markdown string and returns the converted html string
|
||||||
func (ctx *Context) MarkdownString(markdownText string) string {
|
func (ctx *Context) MarkdownString(markdownText string) string {
|
||||||
out := &bytes.Buffer{}
|
out := &bytes.Buffer{}
|
||||||
_, ok := ctx.framework.policies.RenderPolicy(out, contentMarkdown, markdownText)
|
ok, _ := ctx.framework.policies.RenderPolicy(out, contentMarkdown, markdownText)
|
||||||
if ok {
|
if ok {
|
||||||
return out.String()
|
return out.String()
|
||||||
}
|
}
|
||||||
|
|
40
iris.go
40
iris.go
|
@ -28,7 +28,6 @@ import (
|
||||||
"github.com/geekypanda/httpcache"
|
"github.com/geekypanda/httpcache"
|
||||||
"github.com/kataras/go-errors"
|
"github.com/kataras/go-errors"
|
||||||
"github.com/kataras/go-fs"
|
"github.com/kataras/go-fs"
|
||||||
"github.com/kataras/go-serializer"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -260,42 +259,7 @@ func New(setters ...OptionSetter) *Framework {
|
||||||
// | Adapt one RenderPolicy which is responsible |
|
// | Adapt one RenderPolicy which is responsible |
|
||||||
// | for json,jsonp,xml and markdown rendering |
|
// | for json,jsonp,xml and markdown rendering |
|
||||||
// +------------------------------------------------------------+
|
// +------------------------------------------------------------+
|
||||||
|
s.Adapt(restRenderPolicy)
|
||||||
// prepare the serializers,
|
|
||||||
// serializer content-types(json,jsonp,xml,markdown) the defaults are setted:
|
|
||||||
serializers := serializer.Serializers{}
|
|
||||||
serializer.RegisterDefaults(serializers)
|
|
||||||
|
|
||||||
//
|
|
||||||
// notes for me: Why not at the build state? in order to be overridable and not only them,
|
|
||||||
// these are easy to be overridden by external adaptors too, no matter the order,
|
|
||||||
// this is why the RenderPolicy last registration executing first and the first last.
|
|
||||||
//
|
|
||||||
|
|
||||||
// Adapt the RenderPolicy on the Build in order to be the last
|
|
||||||
// render policy, so the users can adapt their own before the default(= to override json,xml,jsonp renderer).
|
|
||||||
//
|
|
||||||
// Notes: the Renderer of the view system is managed by the
|
|
||||||
// adaptors because they are optional.
|
|
||||||
// If templates are binded to the RenderPolicy then
|
|
||||||
// If a key contains a dot('.') then is a template file
|
|
||||||
// otherwise try to find a serializer, if contains error then we return false and the error
|
|
||||||
// in order the renderer to continue to search for any other custom registerer RenderPolicy
|
|
||||||
// if no error then check if it has written anything, if yes write the content
|
|
||||||
// to the writer(which is the context.ResponseWriter or the gzip version of it)
|
|
||||||
// if no error but nothing written then we return false and the error
|
|
||||||
s.Adapt(RenderPolicy(func(out io.Writer, name string, bind interface{}, options ...map[string]interface{}) (error, bool) {
|
|
||||||
b, err := serializers.Serialize(name, bind, options...)
|
|
||||||
if err != nil {
|
|
||||||
return err, false // errors should be wrapped
|
|
||||||
}
|
|
||||||
if len(b) > 0 {
|
|
||||||
_, err = out.Write(b)
|
|
||||||
return err, true
|
|
||||||
}
|
|
||||||
// continue to the next if any or notice there is no available renderer for that name
|
|
||||||
return nil, false
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
// +------------------------------------------------------------+
|
// +------------------------------------------------------------+
|
||||||
|
@ -1061,7 +1025,7 @@ type RenderOptions map[string]interface{}
|
||||||
//
|
//
|
||||||
// It can also render json,xml,jsonp and markdown by-default before or after .Build too.
|
// It can also render json,xml,jsonp and markdown by-default before or after .Build too.
|
||||||
func (s *Framework) Render(w io.Writer, name string, bind interface{}, options ...map[string]interface{}) error {
|
func (s *Framework) Render(w io.Writer, name string, bind interface{}, options ...map[string]interface{}) error {
|
||||||
err, ok := s.policies.RenderPolicy(w, name, bind, options...)
|
ok, err := s.policies.RenderPolicy(w, name, bind, options...)
|
||||||
if !ok {
|
if !ok {
|
||||||
// ok is false ONLY WHEN there is no registered render policy
|
// ok is false ONLY WHEN there is no registered render policy
|
||||||
// that is responsible for that 'name` (if contains dot '.' it's for templates).
|
// that is responsible for that 'name` (if contains dot '.' it's for templates).
|
||||||
|
|
10
policy.go
10
policy.go
|
@ -379,7 +379,7 @@ func (r RouterWrapperPolicy) Adapt(frame *Policies) {
|
||||||
// - the first registered is executing last.
|
// - the first registered is executing last.
|
||||||
// So a custom adaptor that the community can create and share with each other
|
// So a custom adaptor that the community can create and share with each other
|
||||||
// can override the existing one with just a simple registration.
|
// can override the existing one with just a simple registration.
|
||||||
type RenderPolicy func(out io.Writer, name string, bind interface{}, options ...map[string]interface{}) (error, bool)
|
type RenderPolicy func(out io.Writer, name string, bind interface{}, options ...map[string]interface{}) (bool, error)
|
||||||
|
|
||||||
// Adapt adaps a RenderPolicy object to the main *Policies.
|
// Adapt adaps a RenderPolicy object to the main *Policies.
|
||||||
func (r RenderPolicy) Adapt(frame *Policies) {
|
func (r RenderPolicy) Adapt(frame *Policies) {
|
||||||
|
@ -388,14 +388,14 @@ func (r RenderPolicy) Adapt(frame *Policies) {
|
||||||
prevRenderer := frame.RenderPolicy
|
prevRenderer := frame.RenderPolicy
|
||||||
if prevRenderer != nil {
|
if prevRenderer != nil {
|
||||||
nextRenderer := r
|
nextRenderer := r
|
||||||
renderer = func(out io.Writer, name string, binding interface{}, options ...map[string]interface{}) (error, bool) {
|
renderer = func(out io.Writer, name string, binding interface{}, options ...map[string]interface{}) (bool, error) {
|
||||||
// Remember: RenderPolicy works in the opossite order of declaration,
|
// Remember: RenderPolicy works in the opossite order of declaration,
|
||||||
// the last registered is trying to be executed first,
|
// the last registered is trying to be executed first,
|
||||||
// the first registered is executing last.
|
// the first registered is executing last.
|
||||||
err, ok := nextRenderer(out, name, binding, options...)
|
ok, err := nextRenderer(out, name, binding, options...)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
||||||
prevErr, prevOk := prevRenderer(out, name, binding, options...)
|
prevOk, prevErr := prevRenderer(out, name, binding, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if prevErr != nil {
|
if prevErr != nil {
|
||||||
err = errors.New(prevErr.Error()).Append(err.Error())
|
err = errors.New(prevErr.Error()).Append(err.Error())
|
||||||
|
@ -407,7 +407,7 @@ func (r RenderPolicy) Adapt(frame *Policies) {
|
||||||
}
|
}
|
||||||
// this renderer is responsible for this name
|
// this renderer is responsible for this name
|
||||||
// but it has an error, so don't continue to the next
|
// but it has an error, so don't continue to the next
|
||||||
return err, ok
|
return ok, err
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user