binding/render: modular deps

This commit is contained in:
Antoine GIRARD 2019-04-17 16:10:21 +02:00
parent ffcbe77b1e
commit 54eda7fe73
No known key found for this signature in database
GPG Key ID: F3B46D1708E22963
36 changed files with 719 additions and 398 deletions

View File

@ -4,110 +4,70 @@
package binding package binding
import "net/http" import (
"fmt"
// Content-Type MIME of the most common data formats. "github.com/gin-gonic/gin/binding/common"
const (
MIMEJSON = "application/json"
MIMEHTML = "text/html"
MIMEXML = "application/xml"
MIMEXML2 = "text/xml"
MIMEPlain = "text/plain"
MIMEPOSTForm = "application/x-www-form-urlencoded"
MIMEMultipartPOSTForm = "multipart/form-data"
MIMEPROTOBUF = "application/x-protobuf"
MIMEMSGPACK = "application/x-msgpack"
MIMEMSGPACK2 = "application/msgpack"
MIMEYAML = "application/x-yaml"
) )
// Binding describes the interface which needs to be implemented for binding the
// data present in the request such as JSON request body, query parameters or
// the form POST.
type Binding interface {
Name() string
Bind(*http.Request, interface{}) error
}
// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
// but it reads the body from supplied bytes instead of req.Body.
type BindingBody interface {
Binding
BindBody([]byte, interface{}) error
}
// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
// but it read the Params.
type BindingUri interface {
Name() string
BindUri(map[string][]string, interface{}) error
}
// StructValidator is the minimal interface which needs to be implemented in
// order for it to be used as the validator engine for ensuring the correctness
// of the request. Gin provides a default implementation for this using
// https://github.com/go-playground/validator/tree/v8.18.2.
type StructValidator interface {
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
// If the received type is not a struct, any validation should be skipped and nil must be returned.
// If the received type is a struct or pointer to a struct, the validation should be performed.
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
// Otherwise nil must be returned.
ValidateStruct(interface{}) error
// Engine returns the underlying validator engine which powers the
// StructValidator implementation.
Engine() interface{}
}
// Validator is the default validator which implements the StructValidator
// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2
// under the hood.
var Validator StructValidator = &defaultValidator{}
// These implement the Binding interface and can be used to bind the data // These implement the Binding interface and can be used to bind the data
// present in the request to struct instances. // present in the request to struct instances.
var ( var (
JSON = jsonBinding{}
XML = xmlBinding{}
Form = formBinding{} Form = formBinding{}
Query = queryBinding{} Query = queryBinding{}
FormPost = formPostBinding{} FormPost = formPostBinding{}
FormMultipart = formMultipartBinding{}
ProtoBuf = protobufBinding{}
MsgPack = msgpackBinding{}
YAML = yamlBinding{}
Uri = uriBinding{} Uri = uriBinding{}
FormMultipart = formMultipartBinding{}
) )
// Default returns the appropriate Binding instance based on the HTTP method // Default returns the appropriate Binding instance based on the HTTP method
// and the content type. // and the content type.
func Default(method, contentType string) Binding { func Default(method, contentType string) common.Binding {
if method == "GET" { if method == "GET" {
return Form return Form
} }
switch contentType { switch contentType {
case MIMEJSON: case common.MIMEMultipartPOSTForm:
return JSON
case MIMEXML, MIMEXML2:
return XML
case MIMEPROTOBUF:
return ProtoBuf
case MIMEMSGPACK, MIMEMSGPACK2:
return MsgPack
case MIMEYAML:
return YAML
case MIMEMultipartPOSTForm:
return FormMultipart return FormMultipart
default: // case MIMEPOSTForm: default:
return Form b, ok := common.List[contentType]
if !ok {
return Form //Default to Form
}
return b
} }
} }
func validate(obj interface{}) error { //YAML return the binding for yaml if loaded
if Validator == nil { func YAML() common.BindingBody {
return nil return retBinding(common.MIMEYAML)
} }
return Validator.ValidateStruct(obj)
//JSON return the binding for json if loaded
func JSON() common.BindingBody {
return retBinding(common.MIMEJSON)
}
//XML return the binding for xml if loaded
func XML() common.BindingBody {
return retBinding(common.MIMEXML)
}
//ProtoBuf return the binding for ProtoBuf if loaded
func ProtoBuf() common.BindingBody {
return retBinding(common.MIMEPROTOBUF)
}
//MsgPack return the binding for MsgPack if loaded
func MsgPack() common.BindingBody {
return retBinding(common.MIMEMSGPACK)
}
//retBinding Search for a render and panic on not found
func retBinding(contentType string) common.BindingBody {
b, ok := common.List[contentType]
if !ok {
panic(fmt.Sprintf("Undefined binding %s", contentType))
}
return b
} }

View File

@ -5,6 +5,7 @@ import (
"io/ioutil" "io/ioutil"
"testing" "testing"
"github.com/gin-gonic/gin/binding/common"
"github.com/gin-gonic/gin/testdata/protoexample" "github.com/gin-gonic/gin/testdata/protoexample"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -14,18 +15,18 @@ import (
func TestBindingBody(t *testing.T) { func TestBindingBody(t *testing.T) {
for _, tt := range []struct { for _, tt := range []struct {
name string name string
binding BindingBody binding common.BindingBody
body string body string
want string want string
}{ }{
{ {
name: "JSON binding", name: "JSON binding",
binding: JSON, binding: JSON(),
body: `{"foo":"FOO"}`, body: `{"foo":"FOO"}`,
}, },
{ {
name: "XML binding", name: "XML binding",
binding: XML, binding: XML(),
body: `<?xml version="1.0" encoding="UTF-8"?> body: `<?xml version="1.0" encoding="UTF-8"?>
<root> <root>
<foo>FOO</foo> <foo>FOO</foo>
@ -33,12 +34,12 @@ func TestBindingBody(t *testing.T) {
}, },
{ {
name: "MsgPack binding", name: "MsgPack binding",
binding: MsgPack, binding: MsgPack(),
body: msgPackBody(t), body: msgPackBody(t),
}, },
{ {
name: "YAML binding", name: "YAML binding",
binding: YAML, binding: YAML(),
body: `foo: FOO`, body: `foo: FOO`,
}, },
} { } {
@ -67,6 +68,6 @@ func TestBindingBodyProto(t *testing.T) {
req := requestWithBody("POST", "/", string(data)) req := requestWithBody("POST", "/", string(data))
form := protoexample.Test{} form := protoexample.Test{}
body, _ := ioutil.ReadAll(req.Body) body, _ := ioutil.ReadAll(req.Body)
assert.NoError(t, ProtoBuf.BindBody(body, &form)) assert.NoError(t, ProtoBuf().BindBody(body, &form))
assert.Equal(t, test, form) assert.Equal(t, test, form)
} }

View File

@ -18,10 +18,17 @@ import (
"testing" "testing"
"time" "time"
"github.com/gin-gonic/gin/binding/common"
"github.com/gin-gonic/gin/testdata/protoexample" "github.com/gin-gonic/gin/testdata/protoexample"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/ugorji/go/codec" "github.com/ugorji/go/codec"
_ "github.com/gin-gonic/gin/binding/json"
_ "github.com/gin-gonic/gin/binding/msgpack"
_ "github.com/gin-gonic/gin/binding/protobuf"
_ "github.com/gin-gonic/gin/binding/xml"
_ "github.com/gin-gonic/gin/binding/yaml"
) )
type FooStruct struct { type FooStruct struct {
@ -190,54 +197,54 @@ type FooStructForMapPtrType struct {
func TestBindingDefault(t *testing.T) { func TestBindingDefault(t *testing.T) {
assert.Equal(t, Form, Default("GET", "")) assert.Equal(t, Form, Default("GET", ""))
assert.Equal(t, Form, Default("GET", MIMEJSON)) assert.Equal(t, Form, Default("GET", common.MIMEJSON))
assert.Equal(t, JSON, Default("POST", MIMEJSON)) assert.Equal(t, JSON(), Default("POST", common.MIMEJSON))
assert.Equal(t, JSON, Default("PUT", MIMEJSON)) assert.Equal(t, JSON(), Default("PUT", common.MIMEJSON))
assert.Equal(t, XML, Default("POST", MIMEXML)) assert.Equal(t, XML(), Default("POST", common.MIMEXML))
assert.Equal(t, XML, Default("PUT", MIMEXML2)) assert.Equal(t, XML(), Default("PUT", common.MIMEXML2))
assert.Equal(t, Form, Default("POST", MIMEPOSTForm)) assert.Equal(t, Form, Default("POST", common.MIMEPOSTForm))
assert.Equal(t, Form, Default("PUT", MIMEPOSTForm)) assert.Equal(t, Form, Default("PUT", common.MIMEPOSTForm))
assert.Equal(t, FormMultipart, Default("POST", MIMEMultipartPOSTForm)) assert.Equal(t, FormMultipart, Default("POST", common.MIMEMultipartPOSTForm))
assert.Equal(t, FormMultipart, Default("PUT", MIMEMultipartPOSTForm)) assert.Equal(t, FormMultipart, Default("PUT", common.MIMEMultipartPOSTForm))
assert.Equal(t, ProtoBuf, Default("POST", MIMEPROTOBUF)) assert.Equal(t, ProtoBuf(), Default("POST", common.MIMEPROTOBUF))
assert.Equal(t, ProtoBuf, Default("PUT", MIMEPROTOBUF)) assert.Equal(t, ProtoBuf(), Default("PUT", common.MIMEPROTOBUF))
assert.Equal(t, MsgPack, Default("POST", MIMEMSGPACK)) assert.Equal(t, MsgPack(), Default("POST", common.MIMEMSGPACK))
assert.Equal(t, MsgPack, Default("PUT", MIMEMSGPACK2)) assert.Equal(t, MsgPack(), Default("PUT", common.MIMEMSGPACK2))
assert.Equal(t, YAML, Default("POST", MIMEYAML)) assert.Equal(t, YAML(), Default("POST", common.MIMEYAML))
assert.Equal(t, YAML, Default("PUT", MIMEYAML)) assert.Equal(t, YAML(), Default("PUT", common.MIMEYAML))
} }
func TestBindingJSONNilBody(t *testing.T) { func TestBindingJSONNilBody(t *testing.T) {
var obj FooStruct var obj FooStruct
req, _ := http.NewRequest(http.MethodPost, "/", nil) req, _ := http.NewRequest(http.MethodPost, "/", nil)
err := JSON.Bind(req, &obj) err := JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
} }
func TestBindingJSON(t *testing.T) { func TestBindingJSON(t *testing.T) {
testBodyBinding(t, testBodyBinding(t,
JSON, "json", JSON(), "json",
"/", "/", "/", "/",
`{"foo": "bar"}`, `{"bar": "foo"}`) `{"foo": "bar"}`, `{"bar": "foo"}`)
} }
func TestBindingJSONUseNumber(t *testing.T) { func TestBindingJSONUseNumber(t *testing.T) {
testBodyBindingUseNumber(t, testBodyBindingUseNumber(t,
JSON, "json", JSON(), "json",
"/", "/", "/", "/",
`{"foo": 123}`, `{"bar": "foo"}`) `{"foo": 123}`, `{"bar": "foo"}`)
} }
func TestBindingJSONUseNumber2(t *testing.T) { func TestBindingJSONUseNumber2(t *testing.T) {
testBodyBindingUseNumber2(t, testBodyBindingUseNumber2(t,
JSON, "json", JSON(), "json",
"/", "/", "/", "/",
`{"foo": 123}`, `{"bar": "foo"}`) `{"foo": 123}`, `{"bar": "foo"}`)
} }
@ -496,28 +503,28 @@ func TestBindingQueryBoolFail(t *testing.T) {
func TestBindingXML(t *testing.T) { func TestBindingXML(t *testing.T) {
testBodyBinding(t, testBodyBinding(t,
XML, "xml", XML(), "xml",
"/", "/", "/", "/",
"<map><foo>bar</foo></map>", "<map><bar>foo</bar></map>") "<map><foo>bar</foo></map>", "<map><bar>foo</bar></map>")
} }
func TestBindingXMLFail(t *testing.T) { func TestBindingXMLFail(t *testing.T) {
testBodyBindingFail(t, testBodyBindingFail(t,
XML, "xml", XML(), "xml",
"/", "/", "/", "/",
"<map><foo>bar<foo></map>", "<map><bar>foo</bar></map>") "<map><foo>bar<foo></map>", "<map><bar>foo</bar></map>")
} }
func TestBindingYAML(t *testing.T) { func TestBindingYAML(t *testing.T) {
testBodyBinding(t, testBodyBinding(t,
YAML, "yaml", YAML(), "yaml",
"/", "/", "/", "/",
`foo: bar`, `bar: foo`) `foo: bar`, `bar: foo`)
} }
func TestBindingYAMLFail(t *testing.T) { func TestBindingYAMLFail(t *testing.T) {
testBodyBindingFail(t, testBodyBindingFail(t,
YAML, "yaml", YAML(), "yaml",
"/", "/", "/", "/",
`foo:\nbar`, `bar: foo`) `foo:\nbar`, `bar: foo`)
} }
@ -525,28 +532,28 @@ func TestBindingYAMLFail(t *testing.T) {
func createFormPostRequest(t *testing.T) *http.Request { func createFormPostRequest(t *testing.T) *http.Request {
req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo")) req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo"))
assert.NoError(t, err) assert.NoError(t, err)
req.Header.Set("Content-Type", MIMEPOSTForm) req.Header.Set("Content-Type", common.MIMEPOSTForm)
return req return req
} }
func createDefaultFormPostRequest(t *testing.T) *http.Request { func createDefaultFormPostRequest(t *testing.T) *http.Request {
req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar")) req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar"))
assert.NoError(t, err) assert.NoError(t, err)
req.Header.Set("Content-Type", MIMEPOSTForm) req.Header.Set("Content-Type", common.MIMEPOSTForm)
return req return req
} }
func createFormPostRequestForMap(t *testing.T) *http.Request { func createFormPostRequestForMap(t *testing.T) *http.Request {
req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo={\"bar\":123}")) req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo={\"bar\":123}"))
assert.NoError(t, err) assert.NoError(t, err)
req.Header.Set("Content-Type", MIMEPOSTForm) req.Header.Set("Content-Type", common.MIMEPOSTForm)
return req return req
} }
func createFormPostRequestForMapFail(t *testing.T) *http.Request { func createFormPostRequestForMapFail(t *testing.T) *http.Request {
req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=hello")) req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=hello"))
assert.NoError(t, err) assert.NoError(t, err)
req.Header.Set("Content-Type", MIMEPOSTForm) req.Header.Set("Content-Type", common.MIMEPOSTForm)
return req return req
} }
@ -569,7 +576,7 @@ func createFormFilesMultipartRequest(t *testing.T) *http.Request {
req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body)
assert.NoError(t, err2) assert.NoError(t, err2)
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) req.Header.Set("Content-Type", common.MIMEMultipartPOSTForm+"; boundary="+boundary)
return req return req
} }
@ -593,7 +600,7 @@ func createFormFilesMultipartRequestFail(t *testing.T) *http.Request {
req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body)
assert.NoError(t, err2) assert.NoError(t, err2)
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) req.Header.Set("Content-Type", common.MIMEMultipartPOSTForm+"; boundary="+boundary)
return req return req
} }
@ -609,7 +616,7 @@ func createFormMultipartRequest(t *testing.T) *http.Request {
assert.NoError(t, mw.WriteField("bar", "foo")) assert.NoError(t, mw.WriteField("bar", "foo"))
req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body)
assert.NoError(t, err) assert.NoError(t, err)
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) req.Header.Set("Content-Type", common.MIMEMultipartPOSTForm+"; boundary="+boundary)
return req return req
} }
@ -623,7 +630,7 @@ func createFormMultipartRequestForMap(t *testing.T) *http.Request {
assert.NoError(t, mw.WriteField("map_foo", "{\"bar\":123, \"name\":\"thinkerou\", \"pai\": 3.14}")) assert.NoError(t, mw.WriteField("map_foo", "{\"bar\":123, \"name\":\"thinkerou\", \"pai\": 3.14}"))
req, err := http.NewRequest("POST", "/?map_foo=getfoo", body) req, err := http.NewRequest("POST", "/?map_foo=getfoo", body)
assert.NoError(t, err) assert.NoError(t, err)
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) req.Header.Set("Content-Type", common.MIMEMultipartPOSTForm+"; boundary="+boundary)
return req return req
} }
@ -637,7 +644,7 @@ func createFormMultipartRequestForMapFail(t *testing.T) *http.Request {
assert.NoError(t, mw.WriteField("map_foo", "3.14")) assert.NoError(t, mw.WriteField("map_foo", "3.14"))
req, err := http.NewRequest("POST", "/?map_foo=getfoo", body) req, err := http.NewRequest("POST", "/?map_foo=getfoo", body)
assert.NoError(t, err) assert.NoError(t, err)
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) req.Header.Set("Content-Type", common.MIMEMultipartPOSTForm+"; boundary="+boundary)
return req return req
} }
@ -737,7 +744,7 @@ func TestBindingProtoBuf(t *testing.T) {
data, _ := proto.Marshal(test) data, _ := proto.Marshal(test)
testProtoBodyBinding(t, testProtoBodyBinding(t,
ProtoBuf, "protobuf", ProtoBuf(), "protobuf",
"/", "/", "/", "/",
string(data), string(data[1:])) string(data), string(data[1:]))
} }
@ -749,7 +756,7 @@ func TestBindingProtoBufFail(t *testing.T) {
data, _ := proto.Marshal(test) data, _ := proto.Marshal(test)
testProtoBodyBindingFail(t, testProtoBodyBindingFail(t,
ProtoBuf, "protobuf", ProtoBuf(), "protobuf",
"/", "/", "/", "/",
string(data), string(data[1:])) string(data), string(data[1:]))
} }
@ -769,7 +776,7 @@ func TestBindingMsgPack(t *testing.T) {
data := buf.Bytes() data := buf.Bytes()
testMsgPackBodyBinding(t, testMsgPackBodyBinding(t,
MsgPack, "msgpack", MsgPack(), "msgpack",
"/", "/", "/", "/",
string(data), string(data[1:])) string(data), string(data[1:]))
} }
@ -777,18 +784,18 @@ func TestBindingMsgPack(t *testing.T) {
func TestValidationFails(t *testing.T) { func TestValidationFails(t *testing.T) {
var obj FooStruct var obj FooStruct
req := requestWithBody("POST", "/", `{"bar": "foo"}`) req := requestWithBody("POST", "/", `{"bar": "foo"}`)
err := JSON.Bind(req, &obj) err := JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
} }
func TestValidationDisabled(t *testing.T) { func TestValidationDisabled(t *testing.T) {
backup := Validator backup := common.Validator
Validator = nil common.Validator = nil
defer func() { Validator = backup }() defer func() { common.Validator = backup }()
var obj FooStruct var obj FooStruct
req := requestWithBody("POST", "/", `{"bar": "foo"}`) req := requestWithBody("POST", "/", `{"bar": "foo"}`)
err := JSON.Bind(req, &obj) err := JSON().Bind(req, &obj)
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -799,7 +806,7 @@ func TestExistsSucceeds(t *testing.T) {
var obj HogeStruct var obj HogeStruct
req := requestWithBody("POST", "/", `{"hoge": 0}`) req := requestWithBody("POST", "/", `{"hoge": 0}`)
err := JSON.Bind(req, &obj) err := JSON().Bind(req, &obj)
assert.NoError(t, err) assert.NoError(t, err)
} }
@ -810,7 +817,7 @@ func TestExistsFails(t *testing.T) {
var obj HogeStruct var obj HogeStruct
req := requestWithBody("POST", "/", `{"boen": 0}`) req := requestWithBody("POST", "/", `{"boen": 0}`)
err := JSON.Bind(req, &obj) err := JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
} }
@ -864,7 +871,7 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string)
obj := FooBarStruct{} obj := FooBarStruct{}
req := requestWithBody(method, path, body) req := requestWithBody(method, path, body)
if method == "POST" { if method == "POST" {
req.Header.Add("Content-Type", MIMEPOSTForm) req.Header.Add("Content-Type", common.MIMEPOSTForm)
} }
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
assert.NoError(t, err) assert.NoError(t, err)
@ -873,7 +880,7 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string)
obj = FooBarStruct{} obj = FooBarStruct{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
} }
@ -884,7 +891,7 @@ func testFormBindingDefaultValue(t *testing.T, method, path, badPath, body, badB
obj := FooDefaultBarStruct{} obj := FooDefaultBarStruct{}
req := requestWithBody(method, path, body) req := requestWithBody(method, path, body)
if method == "POST" { if method == "POST" {
req.Header.Add("Content-Type", MIMEPOSTForm) req.Header.Add("Content-Type", common.MIMEPOSTForm)
} }
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
assert.NoError(t, err) assert.NoError(t, err)
@ -893,7 +900,7 @@ func testFormBindingDefaultValue(t *testing.T, method, path, badPath, body, badB
obj = FooDefaultBarStruct{} obj = FooDefaultBarStruct{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
} }
@ -911,7 +918,7 @@ func TestFormBindingMultipartFail(t *testing.T) {
obj := FooBarStruct{} obj := FooBarStruct{}
req, err := http.NewRequest("POST", "/", strings.NewReader("foo=bar")) req, err := http.NewRequest("POST", "/", strings.NewReader("foo=bar"))
assert.NoError(t, err) assert.NoError(t, err)
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+";boundary=testboundary") req.Header.Set("Content-Type", common.MIMEMultipartPOSTForm+";boundary=testboundary")
_, err = req.MultipartReader() _, err = req.MultipartReader()
assert.NoError(t, err) assert.NoError(t, err)
err = Form.Bind(req, &obj) err = Form.Bind(req, &obj)
@ -945,7 +952,7 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s
obj := FooBarStructForTimeType{} obj := FooBarStructForTimeType{}
req := requestWithBody(method, path, body) req := requestWithBody(method, path, body)
if method == "POST" { if method == "POST" {
req.Header.Add("Content-Type", MIMEPOSTForm) req.Header.Add("Content-Type", common.MIMEPOSTForm)
} }
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
@ -957,7 +964,7 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s
obj = FooBarStructForTimeType{} obj = FooBarStructForTimeType{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
} }
@ -968,14 +975,14 @@ func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body,
obj := FooStructForTimeTypeNotFormat{} obj := FooStructForTimeTypeNotFormat{}
req := requestWithBody(method, path, body) req := requestWithBody(method, path, body)
if method == "POST" { if method == "POST" {
req.Header.Add("Content-Type", MIMEPOSTForm) req.Header.Add("Content-Type", common.MIMEPOSTForm)
} }
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
obj = FooStructForTimeTypeNotFormat{} obj = FooStructForTimeTypeNotFormat{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
} }
@ -986,14 +993,14 @@ func testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body,
obj := FooStructForTimeTypeFailFormat{} obj := FooStructForTimeTypeFailFormat{}
req := requestWithBody(method, path, body) req := requestWithBody(method, path, body)
if method == "POST" { if method == "POST" {
req.Header.Add("Content-Type", MIMEPOSTForm) req.Header.Add("Content-Type", common.MIMEPOSTForm)
} }
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
obj = FooStructForTimeTypeFailFormat{} obj = FooStructForTimeTypeFailFormat{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
} }
@ -1004,14 +1011,14 @@ func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, bod
obj := FooStructForTimeTypeFailLocation{} obj := FooStructForTimeTypeFailLocation{}
req := requestWithBody(method, path, body) req := requestWithBody(method, path, body)
if method == "POST" { if method == "POST" {
req.Header.Add("Content-Type", MIMEPOSTForm) req.Header.Add("Content-Type", common.MIMEPOSTForm)
} }
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
obj = FooStructForTimeTypeFailLocation{} obj = FooStructForTimeTypeFailLocation{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
} }
@ -1022,7 +1029,7 @@ func testFormBindingIgnoreField(t *testing.T, method, path, badPath, body, badBo
obj := FooStructForIgnoreFormTag{} obj := FooStructForIgnoreFormTag{}
req := requestWithBody(method, path, body) req := requestWithBody(method, path, body)
if method == "POST" { if method == "POST" {
req.Header.Add("Content-Type", MIMEPOSTForm) req.Header.Add("Content-Type", common.MIMEPOSTForm)
} }
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
assert.NoError(t, err) assert.NoError(t, err)
@ -1037,7 +1044,7 @@ func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBo
obj := InvalidNameType{} obj := InvalidNameType{}
req := requestWithBody(method, path, body) req := requestWithBody(method, path, body)
if method == "POST" { if method == "POST" {
req.Header.Add("Content-Type", MIMEPOSTForm) req.Header.Add("Content-Type", common.MIMEPOSTForm)
} }
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
assert.NoError(t, err) assert.NoError(t, err)
@ -1045,7 +1052,7 @@ func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBo
obj = InvalidNameType{} obj = InvalidNameType{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
} }
@ -1056,14 +1063,14 @@ func testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badB
obj := InvalidNameMapType{} obj := InvalidNameMapType{}
req := requestWithBody(method, path, body) req := requestWithBody(method, path, body)
if method == "POST" { if method == "POST" {
req.Header.Add("Content-Type", MIMEPOSTForm) req.Header.Add("Content-Type", common.MIMEPOSTForm)
} }
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
obj = InvalidNameMapType{} obj = InvalidNameMapType{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
} }
@ -1073,7 +1080,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
req := requestWithBody(method, path, body) req := requestWithBody(method, path, body)
if method == "POST" { if method == "POST" {
req.Header.Add("Content-Type", MIMEPOSTForm) req.Header.Add("Content-Type", common.MIMEPOSTForm)
} }
switch typ { switch typ {
case "Int": case "Int":
@ -1085,7 +1092,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
obj = FooBarStructForIntType{} obj = FooBarStructForIntType{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
case "Int8": case "Int8":
obj := FooBarStructForInt8Type{} obj := FooBarStructForInt8Type{}
@ -1096,7 +1103,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
obj = FooBarStructForInt8Type{} obj = FooBarStructForInt8Type{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
case "Int16": case "Int16":
obj := FooBarStructForInt16Type{} obj := FooBarStructForInt16Type{}
@ -1107,7 +1114,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
obj = FooBarStructForInt16Type{} obj = FooBarStructForInt16Type{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
case "Int32": case "Int32":
obj := FooBarStructForInt32Type{} obj := FooBarStructForInt32Type{}
@ -1118,7 +1125,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
obj = FooBarStructForInt32Type{} obj = FooBarStructForInt32Type{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
case "Int64": case "Int64":
obj := FooBarStructForInt64Type{} obj := FooBarStructForInt64Type{}
@ -1129,7 +1136,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
obj = FooBarStructForInt64Type{} obj = FooBarStructForInt64Type{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
case "Uint": case "Uint":
obj := FooBarStructForUintType{} obj := FooBarStructForUintType{}
@ -1140,7 +1147,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
obj = FooBarStructForUintType{} obj = FooBarStructForUintType{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
case "Uint8": case "Uint8":
obj := FooBarStructForUint8Type{} obj := FooBarStructForUint8Type{}
@ -1151,7 +1158,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
obj = FooBarStructForUint8Type{} obj = FooBarStructForUint8Type{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
case "Uint16": case "Uint16":
obj := FooBarStructForUint16Type{} obj := FooBarStructForUint16Type{}
@ -1162,7 +1169,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
obj = FooBarStructForUint16Type{} obj = FooBarStructForUint16Type{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
case "Uint32": case "Uint32":
obj := FooBarStructForUint32Type{} obj := FooBarStructForUint32Type{}
@ -1173,7 +1180,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
obj = FooBarStructForUint32Type{} obj = FooBarStructForUint32Type{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
case "Uint64": case "Uint64":
obj := FooBarStructForUint64Type{} obj := FooBarStructForUint64Type{}
@ -1184,7 +1191,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
obj = FooBarStructForUint64Type{} obj = FooBarStructForUint64Type{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
case "Float32": case "Float32":
obj := FooBarStructForFloat32Type{} obj := FooBarStructForFloat32Type{}
@ -1195,7 +1202,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
obj = FooBarStructForFloat32Type{} obj = FooBarStructForFloat32Type{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
case "Float64": case "Float64":
obj := FooBarStructForFloat64Type{} obj := FooBarStructForFloat64Type{}
@ -1206,7 +1213,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
obj = FooBarStructForFloat64Type{} obj = FooBarStructForFloat64Type{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
case "Bool": case "Bool":
obj := FooBarStructForBoolType{} obj := FooBarStructForBoolType{}
@ -1217,7 +1224,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
obj = FooBarStructForBoolType{} obj = FooBarStructForBoolType{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
case "Slice": case "Slice":
obj := FooStructForSliceType{} obj := FooStructForSliceType{}
@ -1227,7 +1234,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s
obj = FooStructForSliceType{} obj = FooStructForSliceType{}
req = requestWithBody(method, badPath, badBody) req = requestWithBody(method, badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
case "Struct": case "Struct":
obj := FooStructForStructType{} obj := FooStructForStructType{}
@ -1291,7 +1298,7 @@ func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string)
obj := FooBarStruct{} obj := FooBarStruct{}
req := requestWithBody(method, path, body) req := requestWithBody(method, path, body)
if method == "POST" { if method == "POST" {
req.Header.Add("Content-Type", MIMEPOSTForm) req.Header.Add("Content-Type", common.MIMEPOSTForm)
} }
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
assert.NoError(t, err) assert.NoError(t, err)
@ -1306,7 +1313,7 @@ func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody str
obj := FooStructForMapType{} obj := FooStructForMapType{}
req := requestWithBody(method, path, body) req := requestWithBody(method, path, body)
if method == "POST" { if method == "POST" {
req.Header.Add("Content-Type", MIMEPOSTForm) req.Header.Add("Content-Type", common.MIMEPOSTForm)
} }
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
@ -1319,13 +1326,13 @@ func testQueryBindingBoolFail(t *testing.T, method, path, badPath, body, badBody
obj := FooStructForBoolType{} obj := FooStructForBoolType{}
req := requestWithBody(method, path, body) req := requestWithBody(method, path, body)
if method == "POST" { if method == "POST" {
req.Header.Add("Content-Type", MIMEPOSTForm) req.Header.Add("Content-Type", common.MIMEPOSTForm)
} }
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
} }
func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { func testBodyBinding(t *testing.T, b common.Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, name, b.Name()) assert.Equal(t, name, b.Name())
obj := FooStruct{} obj := FooStruct{}
@ -1336,16 +1343,16 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody
obj = FooStruct{} obj = FooStruct{}
req = requestWithBody("POST", badPath, badBody) req = requestWithBody("POST", badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
} }
func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body, badBody string) { func testBodyBindingUseNumber(t *testing.T, b common.Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, name, b.Name()) assert.Equal(t, name, b.Name())
obj := FooStructUseNumber{} obj := FooStructUseNumber{}
req := requestWithBody("POST", path, body) req := requestWithBody("POST", path, body)
EnableDecoderUseNumber = true common.EnableDecoderUseNumber = true
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
assert.NoError(t, err) assert.NoError(t, err)
// we hope it is int64(123) // we hope it is int64(123)
@ -1355,16 +1362,16 @@ func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body
obj = FooStructUseNumber{} obj = FooStructUseNumber{}
req = requestWithBody("POST", badPath, badBody) req = requestWithBody("POST", badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
} }
func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, body, badBody string) { func testBodyBindingUseNumber2(t *testing.T, b common.Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, name, b.Name()) assert.Equal(t, name, b.Name())
obj := FooStructUseNumber{} obj := FooStructUseNumber{}
req := requestWithBody("POST", path, body) req := requestWithBody("POST", path, body)
EnableDecoderUseNumber = false common.EnableDecoderUseNumber = false
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
assert.NoError(t, err) assert.NoError(t, err)
// it will return float64(123) if not use EnableDecoderUseNumber // it will return float64(123) if not use EnableDecoderUseNumber
@ -1373,11 +1380,11 @@ func testBodyBindingUseNumber2(t *testing.T, b Binding, name, path, badPath, bod
obj = FooStructUseNumber{} obj = FooStructUseNumber{}
req = requestWithBody("POST", badPath, badBody) req = requestWithBody("POST", badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
} }
func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { func testBodyBindingFail(t *testing.T, b common.Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, name, b.Name()) assert.Equal(t, name, b.Name())
obj := FooStruct{} obj := FooStruct{}
@ -1388,24 +1395,24 @@ func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, bad
obj = FooStruct{} obj = FooStruct{}
req = requestWithBody("POST", badPath, badBody) req = requestWithBody("POST", badPath, badBody)
err = JSON.Bind(req, &obj) err = JSON().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
} }
func testProtoBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { func testProtoBodyBinding(t *testing.T, b common.Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, name, b.Name()) assert.Equal(t, name, b.Name())
obj := protoexample.Test{} obj := protoexample.Test{}
req := requestWithBody("POST", path, body) req := requestWithBody("POST", path, body)
req.Header.Add("Content-Type", MIMEPROTOBUF) req.Header.Add("Content-Type", common.MIMEPROTOBUF)
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "yes", *obj.Label) assert.Equal(t, "yes", *obj.Label)
obj = protoexample.Test{} obj = protoexample.Test{}
req = requestWithBody("POST", badPath, badBody) req = requestWithBody("POST", badPath, badBody)
req.Header.Add("Content-Type", MIMEPROTOBUF) req.Header.Add("Content-Type", common.MIMEPROTOBUF)
err = ProtoBuf.Bind(req, &obj) err = ProtoBuf().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
} }
@ -1415,38 +1422,38 @@ func (h hook) Read([]byte) (int, error) {
return 0, errors.New("error") return 0, errors.New("error")
} }
func testProtoBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, badBody string) { func testProtoBodyBindingFail(t *testing.T, b common.Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, name, b.Name()) assert.Equal(t, name, b.Name())
obj := protoexample.Test{} obj := protoexample.Test{}
req := requestWithBody("POST", path, body) req := requestWithBody("POST", path, body)
req.Body = ioutil.NopCloser(&hook{}) req.Body = ioutil.NopCloser(&hook{})
req.Header.Add("Content-Type", MIMEPROTOBUF) req.Header.Add("Content-Type", common.MIMEPROTOBUF)
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
obj = protoexample.Test{} obj = protoexample.Test{}
req = requestWithBody("POST", badPath, badBody) req = requestWithBody("POST", badPath, badBody)
req.Header.Add("Content-Type", MIMEPROTOBUF) req.Header.Add("Content-Type", common.MIMEPROTOBUF)
err = ProtoBuf.Bind(req, &obj) err = ProtoBuf().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
} }
func testMsgPackBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody string) { func testMsgPackBodyBinding(t *testing.T, b common.Binding, name, path, badPath, body, badBody string) {
assert.Equal(t, name, b.Name()) assert.Equal(t, name, b.Name())
obj := FooStruct{} obj := FooStruct{}
req := requestWithBody("POST", path, body) req := requestWithBody("POST", path, body)
req.Header.Add("Content-Type", MIMEMSGPACK) req.Header.Add("Content-Type", common.MIMEMSGPACK)
err := b.Bind(req, &obj) err := b.Bind(req, &obj)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "bar", obj.Foo) assert.Equal(t, "bar", obj.Foo)
obj = FooStruct{} obj = FooStruct{}
req = requestWithBody("POST", badPath, badBody) req = requestWithBody("POST", badPath, badBody)
req.Header.Add("Content-Type", MIMEMSGPACK) req.Header.Add("Content-Type", common.MIMEMSGPACK)
err = MsgPack.Bind(req, &obj) err = MsgPack().Bind(req, &obj)
assert.Error(t, err) assert.Error(t, err)
} }
@ -1466,7 +1473,7 @@ func TestCanSet(t *testing.T) {
func formPostRequest(path, body string) *http.Request { func formPostRequest(path, body string) *http.Request {
req := requestWithBody("POST", path, body) req := requestWithBody("POST", path, body)
req.Header.Add("Content-Type", MIMEPOSTForm) req.Header.Add("Content-Type", common.MIMEPOSTForm)
return req return req
} }
@ -1548,3 +1555,14 @@ func TestBindingArray(t *testing.T) {
err = Form.Bind(req, &s) err = Form.Bind(req, &s)
assert.Error(t, err) assert.Error(t, err)
} }
func TestBindingPanic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("retBinding did not panic")
}
}()
//Should panic
retBinding("NotKnowBinding")
}

78
binding/common/common.go Normal file
View File

@ -0,0 +1,78 @@
package common
import "net/http"
// Content-Type MIME of the most common data formats.
const (
MIMEJSON = "application/json"
MIMEHTML = "text/html"
MIMEXML = "application/xml"
MIMEXML2 = "text/xml"
MIMEPlain = "text/plain"
MIMEPOSTForm = "application/x-www-form-urlencoded"
MIMEMultipartPOSTForm = "multipart/form-data"
MIMEPROTOBUF = "application/x-protobuf"
MIMEMSGPACK = "application/x-msgpack"
MIMEMSGPACK2 = "application/msgpack"
MIMEYAML = "application/x-yaml"
)
//List hold the defined binder
var List = map[string]BindingBody{}
// Binding describes the interface which needs to be implemented for binding the
// data present in the request such as JSON request body, query parameters or
// the form POST.
type Binding interface {
Name() string
Bind(*http.Request, interface{}) error
}
// BindingBody adds BindBody method to Binding. BindBody is similar with Bind,
// but it reads the body from supplied bytes instead of req.Body.
type BindingBody interface {
Binding
BindBody([]byte, interface{}) error
}
// BindingUri adds BindUri method to Binding. BindUri is similar with Bind,
// but it read the Params.
type BindingUri interface {
Name() string
BindUri(map[string][]string, interface{}) error
}
// EnableDecoderUseNumber is used to call the UseNumber method on the JSON
// Decoder instance. UseNumber causes the Decoder to unmarshal a number into an
// interface{} as a Number instead of as a float64.
var EnableDecoderUseNumber = false
// StructValidator is the minimal interface which needs to be implemented in
// order for it to be used as the validator engine for ensuring the correctness
// of the request. Gin provides a default implementation for this using
// https://github.com/go-playground/validator/tree/v8.18.2.
type StructValidator interface {
// ValidateStruct can receive any kind of type and it should never panic, even if the configuration is not right.
// If the received type is not a struct, any validation should be skipped and nil must be returned.
// If the received type is a struct or pointer to a struct, the validation should be performed.
// If the struct is not valid or the validation itself fails, a descriptive error should be returned.
// Otherwise nil must be returned.
ValidateStruct(interface{}) error
// Engine returns the underlying validator engine which powers the
// StructValidator implementation.
Engine() interface{}
}
// Validator is the default validator which implements the StructValidator
// interface. It uses https://github.com/go-playground/validator/tree/v8.18.2
// under the hood.
var Validator StructValidator = &defaultValidator{}
//Validate standard validate object
func Validate(obj interface{}) error {
if Validator == nil {
return nil
}
return Validator.ValidateStruct(obj)
}

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT style // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package binding package common
import ( import (
"reflect" "reflect"

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT style // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package binding package common
import ( import (
"bytes" "bytes"
@ -114,10 +114,10 @@ func TestValidateNoValidationValues(t *testing.T) {
test := createNoValidationValues() test := createNoValidationValues()
empty := structNoValidationValues{} empty := structNoValidationValues{}
assert.Nil(t, validate(test)) assert.Nil(t, Validate(test))
assert.Nil(t, validate(&test)) assert.Nil(t, Validate(&test))
assert.Nil(t, validate(empty)) assert.Nil(t, Validate(empty))
assert.Nil(t, validate(&empty)) assert.Nil(t, Validate(&empty))
assert.Equal(t, origin, test) assert.Equal(t, origin, test)
} }
@ -162,10 +162,10 @@ func TestValidateNoValidationPointers(t *testing.T) {
//test := createNoValidation_values() //test := createNoValidation_values()
empty := structNoValidationPointer{} empty := structNoValidationPointer{}
//assert.Nil(t, validate(test)) //assert.Nil(t, Validate(test))
//assert.Nil(t, validate(&test)) //assert.Nil(t, Validate(&test))
assert.Nil(t, validate(empty)) assert.Nil(t, Validate(empty))
assert.Nil(t, validate(&empty)) assert.Nil(t, Validate(&empty))
//assert.Equal(t, origin, test) //assert.Equal(t, origin, test)
} }
@ -174,22 +174,22 @@ type Object map[string]interface{}
func TestValidatePrimitives(t *testing.T) { func TestValidatePrimitives(t *testing.T) {
obj := Object{"foo": "bar", "bar": 1} obj := Object{"foo": "bar", "bar": 1}
assert.NoError(t, validate(obj)) assert.NoError(t, Validate(obj))
assert.NoError(t, validate(&obj)) assert.NoError(t, Validate(&obj))
assert.Equal(t, Object{"foo": "bar", "bar": 1}, obj) assert.Equal(t, Object{"foo": "bar", "bar": 1}, obj)
obj2 := []Object{{"foo": "bar", "bar": 1}, {"foo": "bar", "bar": 1}} obj2 := []Object{{"foo": "bar", "bar": 1}, {"foo": "bar", "bar": 1}}
assert.NoError(t, validate(obj2)) assert.NoError(t, Validate(obj2))
assert.NoError(t, validate(&obj2)) assert.NoError(t, Validate(&obj2))
nu := 10 nu := 10
assert.NoError(t, validate(nu)) assert.NoError(t, Validate(nu))
assert.NoError(t, validate(&nu)) assert.NoError(t, Validate(&nu))
assert.Equal(t, 10, nu) assert.Equal(t, 10, nu)
str := "value" str := "value"
assert.NoError(t, validate(str)) assert.NoError(t, Validate(str))
assert.NoError(t, validate(&str)) assert.NoError(t, Validate(&str))
assert.Equal(t, "value", str) assert.Equal(t, "value", str)
} }
@ -227,7 +227,7 @@ func TestValidatorEngine(t *testing.T) {
// Create an instance which will fail validation // Create an instance which will fail validation
withOne := structCustomValidation{Integer: 1} withOne := structCustomValidation{Integer: 1}
errs := validate(withOne) errs := Validate(withOne)
// Check that we got back non-nil errs // Check that we got back non-nil errs
assert.NotNil(t, errs) assert.NotNil(t, errs)

View File

@ -8,6 +8,8 @@ import (
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"reflect" "reflect"
"github.com/gin-gonic/gin/binding/common"
) )
const defaultMemory = 32 * 1024 * 1024 const defaultMemory = 32 * 1024 * 1024
@ -32,7 +34,7 @@ func (formBinding) Bind(req *http.Request, obj interface{}) error {
if err := mapForm(obj, req.Form); err != nil { if err := mapForm(obj, req.Form); err != nil {
return err return err
} }
return validate(obj) return common.Validate(obj)
} }
func (formPostBinding) Name() string { func (formPostBinding) Name() string {
@ -46,7 +48,7 @@ func (formPostBinding) Bind(req *http.Request, obj interface{}) error {
if err := mapForm(obj, req.PostForm); err != nil { if err := mapForm(obj, req.PostForm); err != nil {
return err return err
} }
return validate(obj) return common.Validate(obj)
} }
func (formMultipartBinding) Name() string { func (formMultipartBinding) Name() string {
@ -61,7 +63,7 @@ func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error {
return err return err
} }
return validate(obj) return common.Validate(obj)
} }
type multipartRequest http.Request type multipartRequest http.Request

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT style // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package binding package json
import ( import (
"bytes" "bytes"
@ -10,13 +10,13 @@ import (
"io" "io"
"net/http" "net/http"
"github.com/gin-gonic/gin/binding/common"
"github.com/gin-gonic/gin/internal/json" "github.com/gin-gonic/gin/internal/json"
) )
// EnableDecoderUseNumber is used to call the UseNumber method on the JSON func init() {
// Decoder instance. UseNumber causes the Decoder to unmarshal a number into an common.List[common.MIMEJSON] = jsonBinding{}
// interface{} as a Number instead of as a float64. }
var EnableDecoderUseNumber = false
type jsonBinding struct{} type jsonBinding struct{}
@ -37,11 +37,11 @@ func (jsonBinding) BindBody(body []byte, obj interface{}) error {
func decodeJSON(r io.Reader, obj interface{}) error { func decodeJSON(r io.Reader, obj interface{}) error {
decoder := json.NewDecoder(r) decoder := json.NewDecoder(r)
if EnableDecoderUseNumber { if common.EnableDecoderUseNumber {
decoder.UseNumber() decoder.UseNumber()
} }
if err := decoder.Decode(obj); err != nil { if err := decoder.Decode(obj); err != nil {
return err return err
} }
return validate(obj) return common.Validate(obj)
} }

View File

@ -2,16 +2,23 @@
// Use of this source code is governed by a MIT style // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package binding package msgpack
import ( import (
"bytes" "bytes"
"io" "io"
"net/http" "net/http"
"github.com/gin-gonic/gin/binding/common"
"github.com/ugorji/go/codec" "github.com/ugorji/go/codec"
) )
func init() {
msgPack := msgpackBinding{}
common.List[common.MIMEMSGPACK] = msgPack
common.List[common.MIMEMSGPACK2] = msgPack
}
type msgpackBinding struct{} type msgpackBinding struct{}
func (msgpackBinding) Name() string { func (msgpackBinding) Name() string {
@ -31,5 +38,5 @@ func decodeMsgPack(r io.Reader, obj interface{}) error {
if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil { if err := codec.NewDecoder(r, cdc).Decode(&obj); err != nil {
return err return err
} }
return validate(obj) return common.Validate(obj)
} }

View File

@ -2,15 +2,20 @@
// Use of this source code is governed by a MIT style // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package binding package protobuf
import ( import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"github.com/gin-gonic/gin/binding/common"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
) )
func init() {
common.List[common.MIMEPROTOBUF] = protobufBinding{}
}
type protobufBinding struct{} type protobufBinding struct{}
func (protobufBinding) Name() string { func (protobufBinding) Name() string {

View File

@ -4,7 +4,11 @@
package binding package binding
import "net/http" import (
"net/http"
"github.com/gin-gonic/gin/binding/common"
)
type queryBinding struct{} type queryBinding struct{}
@ -17,5 +21,5 @@ func (queryBinding) Bind(req *http.Request, obj interface{}) error {
if err := mapForm(obj, values); err != nil { if err := mapForm(obj, values); err != nil {
return err return err
} }
return validate(obj) return common.Validate(obj)
} }

View File

@ -4,6 +4,8 @@
package binding package binding
import "github.com/gin-gonic/gin/binding/common"
type uriBinding struct{} type uriBinding struct{}
func (uriBinding) Name() string { func (uriBinding) Name() string {
@ -14,5 +16,5 @@ func (uriBinding) BindUri(m map[string][]string, obj interface{}) error {
if err := mapUri(obj, m); err != nil { if err := mapUri(obj, m); err != nil {
return err return err
} }
return validate(obj) return common.Validate(obj)
} }

View File

@ -2,15 +2,23 @@
// Use of this source code is governed by a MIT style // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package binding package xml
import ( import (
"bytes" "bytes"
"encoding/xml" "encoding/xml"
"io" "io"
"net/http" "net/http"
"github.com/gin-gonic/gin/binding/common"
) )
func init() {
xml := xmlBinding{}
common.List[common.MIMEXML] = xml
common.List[common.MIMEXML2] = xml
}
type xmlBinding struct{} type xmlBinding struct{}
func (xmlBinding) Name() string { func (xmlBinding) Name() string {
@ -29,5 +37,5 @@ func decodeXML(r io.Reader, obj interface{}) error {
if err := decoder.Decode(obj); err != nil { if err := decoder.Decode(obj); err != nil {
return err return err
} }
return validate(obj) return common.Validate(obj)
} }

View File

@ -2,16 +2,21 @@
// Use of this source code is governed by a MIT style // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package binding package yaml
import ( import (
"bytes" "bytes"
"io" "io"
"net/http" "net/http"
"github.com/gin-gonic/gin/binding/common"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
func init() {
common.List[common.MIMEYAML] = yamlBinding{}
}
type yamlBinding struct{} type yamlBinding struct{}
func (yamlBinding) Name() string { func (yamlBinding) Name() string {
@ -31,5 +36,5 @@ func decodeYAML(r io.Reader, obj interface{}) error {
if err := decoder.Decode(obj); err != nil { if err := decoder.Decode(obj); err != nil {
return err return err
} }
return validate(obj) return common.Validate(obj)
} }

View File

@ -18,21 +18,15 @@ import (
"strings" "strings"
"time" "time"
commonB "github.com/gin-gonic/gin/binding/common"
"github.com/gin-gonic/gin/render/common"
"github.com/gin-contrib/sse" "github.com/gin-contrib/sse"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
"github.com/gin-gonic/gin/render" "github.com/gin-gonic/gin/render"
) )
// Content-Type MIME of the most common data formats.
const ( const (
MIMEJSON = binding.MIMEJSON
MIMEHTML = binding.MIMEHTML
MIMEXML = binding.MIMEXML
MIMEXML2 = binding.MIMEXML2
MIMEPlain = binding.MIMEPlain
MIMEPOSTForm = binding.MIMEPOSTForm
MIMEMultipartPOSTForm = binding.MIMEMultipartPOSTForm
MIMEYAML = binding.MIMEYAML
BodyBytesKey = "_gin-gonic/gin/bodybyteskey" BodyBytesKey = "_gin-gonic/gin/bodybyteskey"
) )
@ -536,12 +530,12 @@ func (c *Context) Bind(obj interface{}) error {
// BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON). // BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON).
func (c *Context) BindJSON(obj interface{}) error { func (c *Context) BindJSON(obj interface{}) error {
return c.MustBindWith(obj, binding.JSON) return c.MustBindWith(obj, binding.JSON())
} }
// BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML). // BindXML is a shortcut for c.MustBindWith(obj, binding.BindXML).
func (c *Context) BindXML(obj interface{}) error { func (c *Context) BindXML(obj interface{}) error {
return c.MustBindWith(obj, binding.XML) return c.MustBindWith(obj, binding.XML())
} }
// BindQuery is a shortcut for c.MustBindWith(obj, binding.Query). // BindQuery is a shortcut for c.MustBindWith(obj, binding.Query).
@ -551,7 +545,7 @@ func (c *Context) BindQuery(obj interface{}) error {
// BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML). // BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML).
func (c *Context) BindYAML(obj interface{}) error { func (c *Context) BindYAML(obj interface{}) error {
return c.MustBindWith(obj, binding.YAML) return c.MustBindWith(obj, binding.YAML())
} }
// BindUri binds the passed struct pointer using binding.Uri. // BindUri binds the passed struct pointer using binding.Uri.
@ -567,7 +561,7 @@ func (c *Context) BindUri(obj interface{}) error {
// MustBindWith binds the passed struct pointer using the specified binding engine. // MustBindWith binds the passed struct pointer using the specified binding engine.
// It will abort the request with HTTP 400 if any error occurs. // It will abort the request with HTTP 400 if any error occurs.
// See the binding package. // See the binding package.
func (c *Context) MustBindWith(obj interface{}, b binding.Binding) error { func (c *Context) MustBindWith(obj interface{}, b commonB.Binding) error {
if err := c.ShouldBindWith(obj, b); err != nil { if err := c.ShouldBindWith(obj, b); err != nil {
c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck
return err return err
@ -590,12 +584,12 @@ func (c *Context) ShouldBind(obj interface{}) error {
// ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON). // ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON).
func (c *Context) ShouldBindJSON(obj interface{}) error { func (c *Context) ShouldBindJSON(obj interface{}) error {
return c.ShouldBindWith(obj, binding.JSON) return c.ShouldBindWith(obj, binding.JSON())
} }
// ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML). // ShouldBindXML is a shortcut for c.ShouldBindWith(obj, binding.XML).
func (c *Context) ShouldBindXML(obj interface{}) error { func (c *Context) ShouldBindXML(obj interface{}) error {
return c.ShouldBindWith(obj, binding.XML) return c.ShouldBindWith(obj, binding.XML())
} }
// ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query). // ShouldBindQuery is a shortcut for c.ShouldBindWith(obj, binding.Query).
@ -605,7 +599,7 @@ func (c *Context) ShouldBindQuery(obj interface{}) error {
// ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML). // ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML).
func (c *Context) ShouldBindYAML(obj interface{}) error { func (c *Context) ShouldBindYAML(obj interface{}) error {
return c.ShouldBindWith(obj, binding.YAML) return c.ShouldBindWith(obj, binding.YAML())
} }
// ShouldBindUri binds the passed struct pointer using the specified binding engine. // ShouldBindUri binds the passed struct pointer using the specified binding engine.
@ -619,7 +613,7 @@ func (c *Context) ShouldBindUri(obj interface{}) error {
// ShouldBindWith binds the passed struct pointer using the specified binding engine. // ShouldBindWith binds the passed struct pointer using the specified binding engine.
// See the binding package. // See the binding package.
func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error { func (c *Context) ShouldBindWith(obj interface{}, b commonB.Binding) error {
return b.Bind(c.Request, obj) return b.Bind(c.Request, obj)
} }
@ -628,7 +622,7 @@ func (c *Context) ShouldBindWith(obj interface{}, b binding.Binding) error {
// //
// NOTE: This method reads the body before binding. So you should use // NOTE: This method reads the body before binding. So you should use
// ShouldBindWith for better performance if you need to call only once. // ShouldBindWith for better performance if you need to call only once.
func (c *Context) ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (err error) { func (c *Context) ShouldBindBodyWith(obj interface{}, bb commonB.BindingBody) (err error) {
var body []byte var body []byte
if cb, ok := c.Get(BodyBytesKey); ok { if cb, ok := c.Get(BodyBytesKey); ok {
if cbb, ok := cb.([]byte); ok { if cbb, ok := cb.([]byte); ok {
@ -767,7 +761,7 @@ func (c *Context) Cookie(name string) (string, error) {
} }
// Render writes the response headers and calls render.Render to render data. // Render writes the response headers and calls render.Render to render data.
func (c *Context) Render(code int, r render.Render) { func (c *Context) Render(code int, r common.Render) {
c.Status(code) c.Status(code)
if !bodyAllowedForStatus(code) { if !bodyAllowedForStatus(code) {
@ -794,14 +788,14 @@ func (c *Context) HTML(code int, name string, obj interface{}) {
// WARNING: we recommend to use this only for development purposes since printing pretty JSON is // WARNING: we recommend to use this only for development purposes since printing pretty JSON is
// more CPU and bandwidth consuming. Use Context.JSON() instead. // more CPU and bandwidth consuming. Use Context.JSON() instead.
func (c *Context) IndentedJSON(code int, obj interface{}) { func (c *Context) IndentedJSON(code int, obj interface{}) {
c.Render(code, render.IndentedJSON{Data: obj}) c.Render(code, render.IndentedJSON(obj))
} }
// SecureJSON serializes the given struct as Secure JSON into the response body. // SecureJSON serializes the given struct as Secure JSON into the response body.
// Default prepends "while(1)," to response body if the given struct is array values. // Default prepends "while(1)," to response body if the given struct is array values.
// It also sets the Content-Type as "application/json". // It also sets the Content-Type as "application/json".
func (c *Context) SecureJSON(code int, obj interface{}) { func (c *Context) SecureJSON(code int, obj interface{}) {
c.Render(code, render.SecureJSON{Prefix: c.engine.secureJsonPrefix, Data: obj}) c.Render(code, render.SecureJSON(c.engine.secureJsonPrefix, obj))
} }
// JSONP serializes the given struct as JSON into the response body. // JSONP serializes the given struct as JSON into the response body.
@ -810,38 +804,43 @@ func (c *Context) SecureJSON(code int, obj interface{}) {
func (c *Context) JSONP(code int, obj interface{}) { func (c *Context) JSONP(code int, obj interface{}) {
callback := c.DefaultQuery("callback", "") callback := c.DefaultQuery("callback", "")
if callback == "" { if callback == "" {
c.Render(code, render.JSON{Data: obj}) c.Render(code, render.JSON(obj))
return return
} }
c.Render(code, render.JsonpJSON{Callback: callback, Data: obj}) c.Render(code, render.JsonpJSON(callback, obj))
} }
// JSON serializes the given struct as JSON into the response body. // JSON serializes the given struct as JSON into the response body.
// It also sets the Content-Type as "application/json". // It also sets the Content-Type as "application/json".
func (c *Context) JSON(code int, obj interface{}) { func (c *Context) JSON(code int, obj interface{}) {
c.Render(code, render.JSON{Data: obj}) c.Render(code, render.JSON(obj))
} }
// AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string. // AsciiJSON serializes the given struct as JSON into the response body with unicode to ASCII string.
// It also sets the Content-Type as "application/json". // It also sets the Content-Type as "application/json".
func (c *Context) AsciiJSON(code int, obj interface{}) { func (c *Context) AsciiJSON(code int, obj interface{}) {
c.Render(code, render.AsciiJSON{Data: obj}) c.Render(code, render.AsciiJSON(obj))
} }
// XML serializes the given struct as XML into the response body. // XML serializes the given struct as XML into the response body.
// It also sets the Content-Type as "application/xml". // It also sets the Content-Type as "application/xml".
func (c *Context) XML(code int, obj interface{}) { func (c *Context) XML(code int, obj interface{}) {
c.Render(code, render.XML{Data: obj}) c.Render(code, render.XML(obj))
} }
// YAML serializes the given struct as YAML into the response body. // YAML serializes the given struct as YAML into the response body.
func (c *Context) YAML(code int, obj interface{}) { func (c *Context) YAML(code int, obj interface{}) {
c.Render(code, render.YAML{Data: obj}) c.Render(code, render.YAML(obj))
} }
// ProtoBuf serializes the given struct as ProtoBuf into the response body. // ProtoBuf serializes the given struct as ProtoBuf into the response body.
func (c *Context) ProtoBuf(code int, obj interface{}) { func (c *Context) ProtoBuf(code int, obj interface{}) {
c.Render(code, render.ProtoBuf{Data: obj}) c.Render(code, render.ProtoBuf(obj))
}
// MsgPack serializes the given struct as MsgPack into the response body.
func (c *Context) MsgPack(code int, obj interface{}) {
c.Render(code, render.MsgPack(obj))
} }
// String writes the given string into the response body. // String writes the given string into the response body.
@ -932,15 +931,15 @@ type Negotiate struct {
// Negotiate calls different Render according acceptable Accept format. // Negotiate calls different Render according acceptable Accept format.
func (c *Context) Negotiate(code int, config Negotiate) { func (c *Context) Negotiate(code int, config Negotiate) {
switch c.NegotiateFormat(config.Offered...) { switch c.NegotiateFormat(config.Offered...) {
case binding.MIMEJSON: case commonB.MIMEJSON:
data := chooseData(config.JSONData, config.Data) data := chooseData(config.JSONData, config.Data)
c.JSON(code, data) c.JSON(code, data)
case binding.MIMEHTML: case commonB.MIMEHTML:
data := chooseData(config.HTMLData, config.Data) data := chooseData(config.HTMLData, config.Data)
c.HTML(code, config.HTMLName, data) c.HTML(code, config.HTMLName, data)
case binding.MIMEXML: case commonB.MIMEXML:
data := chooseData(config.XMLData, config.Data) data := chooseData(config.XMLData, config.Data)
c.XML(code, data) c.XML(code, data)

View File

@ -13,5 +13,5 @@ import (
// PureJSON serializes the given struct as JSON into the response body. // PureJSON serializes the given struct as JSON into the response body.
// PureJSON, unlike JSON, does not replace special html characters with their unicode entities. // PureJSON, unlike JSON, does not replace special html characters with their unicode entities.
func (c *Context) PureJSON(code int, obj interface{}) { func (c *Context) PureJSON(code int, obj interface{}) {
c.Render(code, render.PureJSON{Data: obj}) c.Render(code, render.PureJSON(obj))
} }

View File

@ -20,11 +20,23 @@ import (
"github.com/gin-contrib/sse" "github.com/gin-contrib/sse"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
commonB "github.com/gin-gonic/gin/binding/common"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/ugorji/go/codec"
"golang.org/x/net/context" "golang.org/x/net/context"
testdata "github.com/gin-gonic/gin/testdata/protoexample" testdata "github.com/gin-gonic/gin/testdata/protoexample"
_ "github.com/gin-gonic/gin/binding/json"
_ "github.com/gin-gonic/gin/binding/xml"
_ "github.com/gin-gonic/gin/binding/yaml"
_ "github.com/gin-gonic/gin/render/json"
_ "github.com/gin-gonic/gin/render/msgpack"
_ "github.com/gin-gonic/gin/render/protobuf"
_ "github.com/gin-gonic/gin/render/xml"
_ "github.com/gin-gonic/gin/render/yaml"
) )
var _ context.Context = &Context{} var _ context.Context = &Context{}
@ -55,7 +67,7 @@ func createMultipartRequest() *http.Request {
must(mw.WriteField("names[b]", "tianou")) must(mw.WriteField("names[b]", "tianou"))
req, err := http.NewRequest("POST", "/", body) req, err := http.NewRequest("POST", "/", body)
must(err) must(err)
req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) req.Header.Set("Content-Type", commonB.MIMEMultipartPOSTForm+"; boundary="+boundary)
return req return req
} }
@ -413,7 +425,7 @@ func TestContextQueryAndPostForm(t *testing.T) {
body := bytes.NewBufferString("foo=bar&page=11&both=&foo=second") body := bytes.NewBufferString("foo=bar&page=11&both=&foo=second")
c.Request, _ = http.NewRequest("POST", c.Request, _ = http.NewRequest("POST",
"/?both=GET&id=main&id=omit&array[]=first&array[]=second&ids[a]=hi&ids[b]=3.14", body) "/?both=GET&id=main&id=omit&array[]=first&array[]=second&ids[a]=hi&ids[b]=3.14", body)
c.Request.Header.Add("Content-Type", MIMEPOSTForm) c.Request.Header.Add("Content-Type", commonB.MIMEPOSTForm)
assert.Equal(t, "bar", c.DefaultPostForm("foo", "none")) assert.Equal(t, "bar", c.DefaultPostForm("foo", "none"))
assert.Equal(t, "bar", c.PostForm("foo")) assert.Equal(t, "bar", c.PostForm("foo"))
@ -1005,6 +1017,32 @@ func TestContextRenderYAML(t *testing.T) {
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
} }
// TestContextRenderMsgPack tests that the response is serialized as MsgPack
// and Content-Type is set to application/msgpack; charset=utf-8
// and we just use the example MsgPack to check if the response is correct
func TestContextRenderMsgPack(t *testing.T) {
w := httptest.NewRecorder()
c, _ := CreateTestContext(w)
reps := []int64{int64(1), int64(2)}
label := "test"
data := &testdata.Test{
Label: &label,
Reps: reps,
}
c.MsgPack(http.StatusCreated, data)
var mh codec.MsgpackHandle
var msgData bytes.Buffer
err := codec.NewEncoder(&msgData, &mh).Encode(data)
assert.NoError(t, err)
assert.Equal(t, http.StatusCreated, w.Code)
assert.Equal(t, msgData.String(), w.Body.String())
assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type"))
}
// TestContextRenderProtoBuf tests that the response is serialized as ProtoBuf // TestContextRenderProtoBuf tests that the response is serialized as ProtoBuf
// and Content-Type is set to application/x-protobuf // and Content-Type is set to application/x-protobuf
// and we just use the example protobuf to check if the response is correct // and we just use the example protobuf to check if the response is correct
@ -1103,7 +1141,7 @@ func TestContextNegotiationWithJSON(t *testing.T) {
c.Request, _ = http.NewRequest("POST", "", nil) c.Request, _ = http.NewRequest("POST", "", nil)
c.Negotiate(http.StatusOK, Negotiate{ c.Negotiate(http.StatusOK, Negotiate{
Offered: []string{MIMEJSON, MIMEXML}, Offered: []string{commonB.MIMEJSON, commonB.MIMEXML},
Data: H{"foo": "bar"}, Data: H{"foo": "bar"},
}) })
@ -1118,7 +1156,7 @@ func TestContextNegotiationWithXML(t *testing.T) {
c.Request, _ = http.NewRequest("POST", "", nil) c.Request, _ = http.NewRequest("POST", "", nil)
c.Negotiate(http.StatusOK, Negotiate{ c.Negotiate(http.StatusOK, Negotiate{
Offered: []string{MIMEXML, MIMEJSON}, Offered: []string{commonB.MIMEXML, commonB.MIMEJSON},
Data: H{"foo": "bar"}, Data: H{"foo": "bar"},
}) })
@ -1135,7 +1173,7 @@ func TestContextNegotiationWithHTML(t *testing.T) {
router.SetHTMLTemplate(templ) router.SetHTMLTemplate(templ)
c.Negotiate(http.StatusOK, Negotiate{ c.Negotiate(http.StatusOK, Negotiate{
Offered: []string{MIMEHTML}, Offered: []string{commonB.MIMEHTML},
Data: H{"name": "gin"}, Data: H{"name": "gin"},
HTMLName: "t", HTMLName: "t",
}) })
@ -1151,7 +1189,7 @@ func TestContextNegotiationNotSupport(t *testing.T) {
c.Request, _ = http.NewRequest("POST", "", nil) c.Request, _ = http.NewRequest("POST", "", nil)
c.Negotiate(http.StatusOK, Negotiate{ c.Negotiate(http.StatusOK, Negotiate{
Offered: []string{MIMEPOSTForm}, Offered: []string{commonB.MIMEPOSTForm},
}) })
assert.Equal(t, http.StatusNotAcceptable, w.Code) assert.Equal(t, http.StatusNotAcceptable, w.Code)
@ -1164,8 +1202,8 @@ func TestContextNegotiationFormat(t *testing.T) {
c.Request, _ = http.NewRequest("POST", "", nil) c.Request, _ = http.NewRequest("POST", "", nil)
assert.Panics(t, func() { c.NegotiateFormat() }) assert.Panics(t, func() { c.NegotiateFormat() })
assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) assert.Equal(t, commonB.MIMEJSON, c.NegotiateFormat(commonB.MIMEJSON, commonB.MIMEXML))
assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML, MIMEJSON)) assert.Equal(t, commonB.MIMEHTML, c.NegotiateFormat(commonB.MIMEHTML, commonB.MIMEJSON))
} }
func TestContextNegotiationFormatWithAccept(t *testing.T) { func TestContextNegotiationFormatWithAccept(t *testing.T) {
@ -1173,9 +1211,9 @@ func TestContextNegotiationFormatWithAccept(t *testing.T) {
c.Request, _ = http.NewRequest("POST", "/", nil) c.Request, _ = http.NewRequest("POST", "/", nil)
c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9;q=0.8") c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9;q=0.8")
assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEJSON, MIMEXML)) assert.Equal(t, commonB.MIMEXML, c.NegotiateFormat(commonB.MIMEJSON, commonB.MIMEXML))
assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEXML, MIMEHTML)) assert.Equal(t, commonB.MIMEHTML, c.NegotiateFormat(commonB.MIMEXML, commonB.MIMEHTML))
assert.Empty(t, c.NegotiateFormat(MIMEJSON)) assert.Empty(t, c.NegotiateFormat(commonB.MIMEJSON))
} }
func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) { func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) {
@ -1186,9 +1224,9 @@ func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) {
assert.Equal(t, c.NegotiateFormat("*/*"), "*/*") assert.Equal(t, c.NegotiateFormat("*/*"), "*/*")
assert.Equal(t, c.NegotiateFormat("text/*"), "text/*") assert.Equal(t, c.NegotiateFormat("text/*"), "text/*")
assert.Equal(t, c.NegotiateFormat("application/*"), "application/*") assert.Equal(t, c.NegotiateFormat("application/*"), "application/*")
assert.Equal(t, c.NegotiateFormat(MIMEJSON), MIMEJSON) assert.Equal(t, c.NegotiateFormat(commonB.MIMEJSON), commonB.MIMEJSON)
assert.Equal(t, c.NegotiateFormat(MIMEXML), MIMEXML) assert.Equal(t, c.NegotiateFormat(commonB.MIMEXML), commonB.MIMEXML)
assert.Equal(t, c.NegotiateFormat(MIMEHTML), MIMEHTML) assert.Equal(t, c.NegotiateFormat(commonB.MIMEHTML), commonB.MIMEHTML)
c, _ = CreateTestContext(httptest.NewRecorder()) c, _ = CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("POST", "/", nil) c.Request, _ = http.NewRequest("POST", "/", nil)
@ -1197,9 +1235,9 @@ func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) {
assert.Equal(t, c.NegotiateFormat("*/*"), "*/*") assert.Equal(t, c.NegotiateFormat("*/*"), "*/*")
assert.Equal(t, c.NegotiateFormat("text/*"), "text/*") assert.Equal(t, c.NegotiateFormat("text/*"), "text/*")
assert.Equal(t, c.NegotiateFormat("application/*"), "") assert.Equal(t, c.NegotiateFormat("application/*"), "")
assert.Equal(t, c.NegotiateFormat(MIMEJSON), "") assert.Equal(t, c.NegotiateFormat(commonB.MIMEJSON), "")
assert.Equal(t, c.NegotiateFormat(MIMEXML), "") assert.Equal(t, c.NegotiateFormat(commonB.MIMEXML), "")
assert.Equal(t, c.NegotiateFormat(MIMEHTML), MIMEHTML) assert.Equal(t, c.NegotiateFormat(commonB.MIMEHTML), commonB.MIMEHTML)
} }
func TestContextNegotiationFormatCustom(t *testing.T) { func TestContextNegotiationFormatCustom(t *testing.T) {
@ -1208,11 +1246,11 @@ func TestContextNegotiationFormatCustom(t *testing.T) {
c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9;q=0.8") c.Request.Header.Add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9;q=0.8")
c.Accepted = nil c.Accepted = nil
c.SetAccepted(MIMEJSON, MIMEXML) c.SetAccepted(commonB.MIMEJSON, commonB.MIMEXML)
assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) assert.Equal(t, commonB.MIMEJSON, c.NegotiateFormat(commonB.MIMEJSON, commonB.MIMEXML))
assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEXML, MIMEHTML)) assert.Equal(t, commonB.MIMEXML, c.NegotiateFormat(commonB.MIMEXML, commonB.MIMEHTML))
assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON)) assert.Equal(t, commonB.MIMEJSON, c.NegotiateFormat(commonB.MIMEJSON))
} }
func TestContextIsAborted(t *testing.T) { func TestContextIsAborted(t *testing.T) {
@ -1376,7 +1414,7 @@ func TestContextContentType(t *testing.T) {
func TestContextAutoBindJSON(t *testing.T) { func TestContextAutoBindJSON(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}"))
c.Request.Header.Add("Content-Type", MIMEJSON) c.Request.Header.Add("Content-Type", commonB.MIMEJSON)
var obj struct { var obj struct {
Foo string `json:"foo"` Foo string `json:"foo"`
@ -1393,7 +1431,7 @@ func TestContextBindWithJSON(t *testing.T) {
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}"))
c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type c.Request.Header.Add("Content-Type", commonB.MIMEXML) // set fake content-type
var obj struct { var obj struct {
Foo string `json:"foo"` Foo string `json:"foo"`
@ -1413,7 +1451,7 @@ func TestContextBindWithXML(t *testing.T) {
<foo>FOO</foo> <foo>FOO</foo>
<bar>BAR</bar> <bar>BAR</bar>
</root>`)) </root>`))
c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type c.Request.Header.Add("Content-Type", commonB.MIMEXML) // set fake content-type
var obj struct { var obj struct {
Foo string `xml:"foo"` Foo string `xml:"foo"`
@ -1446,7 +1484,7 @@ func TestContextBindWithYAML(t *testing.T) {
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo: bar\nbar: foo")) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo: bar\nbar: foo"))
c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type c.Request.Header.Add("Content-Type", commonB.MIMEXML) // set fake content-type
var obj struct { var obj struct {
Foo string `yaml:"foo"` Foo string `yaml:"foo"`
@ -1463,7 +1501,7 @@ func TestContextBadAutoBind(t *testing.T) {
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request, _ = http.NewRequest("POST", "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}"))
c.Request.Header.Add("Content-Type", MIMEJSON) c.Request.Header.Add("Content-Type", commonB.MIMEJSON)
var obj struct { var obj struct {
Foo string `json:"foo"` Foo string `json:"foo"`
Bar string `json:"bar"` Bar string `json:"bar"`
@ -1482,7 +1520,7 @@ func TestContextBadAutoBind(t *testing.T) {
func TestContextAutoShouldBindJSON(t *testing.T) { func TestContextAutoShouldBindJSON(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}"))
c.Request.Header.Add("Content-Type", MIMEJSON) c.Request.Header.Add("Content-Type", commonB.MIMEJSON)
var obj struct { var obj struct {
Foo string `json:"foo"` Foo string `json:"foo"`
@ -1499,7 +1537,7 @@ func TestContextShouldBindWithJSON(t *testing.T) {
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("{\"foo\":\"bar\", \"bar\":\"foo\"}"))
c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type c.Request.Header.Add("Content-Type", commonB.MIMEXML) // set fake content-type
var obj struct { var obj struct {
Foo string `json:"foo"` Foo string `json:"foo"`
@ -1520,7 +1558,7 @@ func TestContextShouldBindWithXML(t *testing.T) {
<foo>FOO</foo> <foo>FOO</foo>
<bar>BAR</bar> <bar>BAR</bar>
</root>`)) </root>`))
c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type c.Request.Header.Add("Content-Type", commonB.MIMEXML) // set fake content-type
var obj struct { var obj struct {
Foo string `xml:"foo"` Foo string `xml:"foo"`
@ -1557,7 +1595,7 @@ func TestContextShouldBindWithYAML(t *testing.T) {
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo: bar\nbar: foo")) c.Request, _ = http.NewRequest("POST", "/", bytes.NewBufferString("foo: bar\nbar: foo"))
c.Request.Header.Add("Content-Type", MIMEXML) // set fake content-type c.Request.Header.Add("Content-Type", commonB.MIMEXML) // set fake content-type
var obj struct { var obj struct {
Foo string `yaml:"foo"` Foo string `yaml:"foo"`
@ -1574,7 +1612,7 @@ func TestContextBadAutoShouldBind(t *testing.T) {
c, _ := CreateTestContext(w) c, _ := CreateTestContext(w)
c.Request, _ = http.NewRequest("POST", "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}")) c.Request, _ = http.NewRequest("POST", "http://example.com", bytes.NewBufferString("\"foo\":\"bar\", \"bar\":\"foo\"}"))
c.Request.Header.Add("Content-Type", MIMEJSON) c.Request.Header.Add("Content-Type", commonB.MIMEJSON)
var obj struct { var obj struct {
Foo string `json:"foo"` Foo string `json:"foo"`
Bar string `json:"bar"` Bar string `json:"bar"`
@ -1597,20 +1635,20 @@ func TestContextShouldBindBodyWith(t *testing.T) {
} }
for _, tt := range []struct { for _, tt := range []struct {
name string name string
bindingA, bindingB binding.BindingBody bindingA, bindingB commonB.BindingBody
bodyA, bodyB string bodyA, bodyB string
}{ }{
{ {
name: "JSON & JSON", name: "JSON & JSON",
bindingA: binding.JSON, bindingA: binding.JSON(),
bindingB: binding.JSON, bindingB: binding.JSON(),
bodyA: `{"foo":"FOO"}`, bodyA: `{"foo":"FOO"}`,
bodyB: `{"bar":"BAR"}`, bodyB: `{"bar":"BAR"}`,
}, },
{ {
name: "JSON & XML", name: "JSON & XML",
bindingA: binding.JSON, bindingA: binding.JSON(),
bindingB: binding.XML, bindingB: binding.XML(),
bodyA: `{"foo":"FOO"}`, bodyA: `{"foo":"FOO"}`,
bodyB: `<?xml version="1.0" encoding="UTF-8"?> bodyB: `<?xml version="1.0" encoding="UTF-8"?>
<root> <root>
@ -1619,8 +1657,8 @@ func TestContextShouldBindBodyWith(t *testing.T) {
}, },
{ {
name: "XML & XML", name: "XML & XML",
bindingA: binding.XML, bindingA: binding.XML(),
bindingB: binding.XML, bindingB: binding.XML(),
bodyA: `<?xml version="1.0" encoding="UTF-8"?> bodyA: `<?xml version="1.0" encoding="UTF-8"?>
<root> <root>
<foo>FOO</foo> <foo>FOO</foo>
@ -1718,7 +1756,7 @@ func TestContextGetRawData(t *testing.T) {
c, _ := CreateTestContext(httptest.NewRecorder()) c, _ := CreateTestContext(httptest.NewRecorder())
body := bytes.NewBufferString("Fetch binary post data") body := bytes.NewBufferString("Fetch binary post data")
c.Request, _ = http.NewRequest("POST", "/", body) c.Request, _ = http.NewRequest("POST", "/", body)
c.Request.Header.Add("Content-Type", MIMEPOSTForm) c.Request.Header.Add("Content-Type", commonB.MIMEPOSTForm)
data, err := c.GetRawData() data, err := c.GetRawData()
assert.Nil(t, err) assert.Nil(t, err)

View File

@ -7,12 +7,12 @@ package gin
import ( import (
"log" "log"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding/common"
) )
// BindWith binds the passed struct pointer using the specified binding engine. // BindWith binds the passed struct pointer using the specified binding engine.
// See the binding package. // See the binding package.
func (c *Context) BindWith(obj interface{}, b binding.Binding) error { func (c *Context) BindWith(obj interface{}, b common.Binding) error {
log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to log.Println(`BindWith(\"interface{}, binding.Binding\") error is going to
be deprecated, please check issue #662 and either use MustBindWith() if you be deprecated, please check issue #662 and either use MustBindWith() if you
want HTTP 400 to be automatically returned if any error occur, or use want HTTP 400 to be automatically returned if any error occur, or use

4
gin.go
View File

@ -13,6 +13,8 @@ import (
"path" "path"
"sync" "sync"
"github.com/gin-gonic/gin/binding/common"
"github.com/gin-gonic/gin/render" "github.com/gin-gonic/gin/render"
) )
@ -417,7 +419,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) {
serveError(c, http.StatusNotFound, default404Body) serveError(c, http.StatusNotFound, default404Body)
} }
var mimePlain = []string{MIMEPlain} var mimePlain = []string{common.MIMEPlain}
func serveError(c *Context, code int, defaultMessage []byte) { func serveError(c *Context, code int, defaultMessage []byte) {
c.writermem.status = code c.writermem.status = code

View File

@ -8,7 +8,7 @@ import (
"io" "io"
"os" "os"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding/common"
) )
// EnvGinMode indicates environment name for gin mode. // EnvGinMode indicates environment name for gin mode.
@ -68,13 +68,13 @@ func SetMode(value string) {
// DisableBindValidation closes the default validator. // DisableBindValidation closes the default validator.
func DisableBindValidation() { func DisableBindValidation() {
binding.Validator = nil common.Validator = nil
} }
// EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumberto to // EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumberto to
// call the UseNumber method on the JSON Decoder instance. // call the UseNumber method on the JSON Decoder instance.
func EnableJsonDecoderUseNumber() { func EnableJsonDecoderUseNumber() {
binding.EnableDecoderUseNumber = true common.EnableDecoderUseNumber = true
} }
// Mode returns currently gin mode. // Mode returns currently gin mode.

View File

@ -8,7 +8,7 @@ import (
"os" "os"
"testing" "testing"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding/common"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
@ -41,7 +41,15 @@ func TestSetMode(t *testing.T) {
} }
func TestEnableJsonDecoderUseNumber(t *testing.T) { func TestEnableJsonDecoderUseNumber(t *testing.T) {
assert.False(t, binding.EnableDecoderUseNumber) assert.False(t, common.EnableDecoderUseNumber)
EnableJsonDecoderUseNumber() EnableJsonDecoderUseNumber()
assert.True(t, binding.EnableDecoderUseNumber) assert.True(t, common.EnableDecoderUseNumber)
}
func TestDisableBindValidation(t *testing.T) {
bckp := common.Validator
assert.NotNil(t, common.Validator)
DisableBindValidation()
assert.Nil(t, common.Validator)
common.Validator = bckp
} }

24
render/common/common.go Normal file
View File

@ -0,0 +1,24 @@
package common
import (
"net/http"
)
//WriteContentType set the content-type header
func WriteContentType(w http.ResponseWriter, value []string) {
header := w.Header()
if val := header["Content-Type"]; len(val) == 0 {
header["Content-Type"] = value
}
}
// Render interface is to be implemented by JSON, XML, HTML, YAML and so on.
type Render interface {
// Render writes data with custom ContentType.
Render(http.ResponseWriter) error
// WriteContentType writes custom ContentType.
WriteContentType(w http.ResponseWriter)
}
//List hold the defined render (obj, options)
var List = map[string]func(interface{}, map[string]string) Render{}

View File

@ -4,7 +4,11 @@
package render package render
import "net/http" import (
"net/http"
"github.com/gin-gonic/gin/render/common"
)
// Data contains ContentType and bytes data. // Data contains ContentType and bytes data.
type Data struct { type Data struct {
@ -21,5 +25,5 @@ func (r Data) Render(w http.ResponseWriter) (err error) {
// WriteContentType (Data) writes custom ContentType. // WriteContentType (Data) writes custom ContentType.
func (r Data) WriteContentType(w http.ResponseWriter) { func (r Data) WriteContentType(w http.ResponseWriter) {
writeContentType(w, []string{r.ContentType}) common.WriteContentType(w, []string{r.ContentType})
} }

View File

@ -7,6 +7,8 @@ package render
import ( import (
"html/template" "html/template"
"net/http" "net/http"
"github.com/gin-gonic/gin/render/common"
) )
// Delims represents a set of Left and Right delimiters for HTML template rendering. // Delims represents a set of Left and Right delimiters for HTML template rendering.
@ -20,7 +22,7 @@ type Delims struct {
// HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug. // HTMLRender interface is to be implemented by HTMLProduction and HTMLDebug.
type HTMLRender interface { type HTMLRender interface {
// Instance returns an HTML instance. // Instance returns an HTML instance.
Instance(string, interface{}) Render Instance(string, interface{}) HTML
} }
// HTMLProduction contains template reference and its delims. // HTMLProduction contains template reference and its delims.
@ -47,7 +49,7 @@ type HTML struct {
var htmlContentType = []string{"text/html; charset=utf-8"} var htmlContentType = []string{"text/html; charset=utf-8"}
// Instance (HTMLProduction) returns an HTML instance which it realizes Render interface. // Instance (HTMLProduction) returns an HTML instance which it realizes Render interface.
func (r HTMLProduction) Instance(name string, data interface{}) Render { func (r HTMLProduction) Instance(name string, data interface{}) HTML {
return HTML{ return HTML{
Template: r.Template, Template: r.Template,
Name: name, Name: name,
@ -56,7 +58,7 @@ func (r HTMLProduction) Instance(name string, data interface{}) Render {
} }
// Instance (HTMLDebug) returns an HTML instance which it realizes Render interface. // Instance (HTMLDebug) returns an HTML instance which it realizes Render interface.
func (r HTMLDebug) Instance(name string, data interface{}) Render { func (r HTMLDebug) Instance(name string, data interface{}) HTML {
return HTML{ return HTML{
Template: r.loadTemplate(), Template: r.loadTemplate(),
Name: name, Name: name,
@ -88,5 +90,5 @@ func (r HTML) Render(w http.ResponseWriter) error {
// WriteContentType (HTML) writes HTML ContentType. // WriteContentType (HTML) writes HTML ContentType.
func (r HTML) WriteContentType(w http.ResponseWriter) { func (r HTML) WriteContentType(w http.ResponseWriter) {
writeContentType(w, htmlContentType) common.WriteContentType(w, htmlContentType)
} }

View File

@ -2,7 +2,7 @@
// Use of this source code is governed by a MIT style // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package render package json
import ( import (
"bytes" "bytes"
@ -11,8 +11,42 @@ import (
"net/http" "net/http"
"github.com/gin-gonic/gin/internal/json" "github.com/gin-gonic/gin/internal/json"
"github.com/gin-gonic/gin/render/common"
) )
func init() {
common.List["JSON"] = NewJSON
common.List["IndentedJSON"] = NewIndentedJSON
common.List["SecureJSON"] = NewSecureJSON
common.List["JsonpJSON"] = NewJsonpJSON
common.List["AsciiJSON"] = NewAsciiJSON
}
//NewJSON build a new JSON render
func NewJSON(obj interface{}, _ map[string]string) common.Render {
return JSON{Data: obj}
}
//NewIndentedJSON build a new IndentedJSON render
func NewIndentedJSON(obj interface{}, _ map[string]string) common.Render {
return IndentedJSON{Data: obj}
}
//NewSecureJSON build a new SecureJSON render
func NewSecureJSON(obj interface{}, opts map[string]string) common.Render {
return SecureJSON{Prefix: opts["Prefix"], Data: obj}
}
//NewJsonpJSON build a new JsonpJSON render
func NewJsonpJSON(obj interface{}, opts map[string]string) common.Render {
return JsonpJSON{Callback: opts["Callback"], Data: obj}
}
//NewAsciiJSON build a new AsciiJSON render
func NewAsciiJSON(obj interface{}, _ map[string]string) common.Render {
return AsciiJSON{Data: obj}
}
// JSON contains the given interface object. // JSON contains the given interface object.
type JSON struct { type JSON struct {
Data interface{} Data interface{}
@ -57,12 +91,12 @@ func (r JSON) Render(w http.ResponseWriter) (err error) {
// WriteContentType (JSON) writes JSON ContentType. // WriteContentType (JSON) writes JSON ContentType.
func (r JSON) WriteContentType(w http.ResponseWriter) { func (r JSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonContentType) common.WriteContentType(w, jsonContentType)
} }
// WriteJSON marshals the given interface object and writes it with custom ContentType. // WriteJSON marshals the given interface object and writes it with custom ContentType.
func WriteJSON(w http.ResponseWriter, obj interface{}) error { func WriteJSON(w http.ResponseWriter, obj interface{}) error {
writeContentType(w, jsonContentType) common.WriteContentType(w, jsonContentType)
jsonBytes, err := json.Marshal(obj) jsonBytes, err := json.Marshal(obj)
if err != nil { if err != nil {
return err return err
@ -84,7 +118,7 @@ func (r IndentedJSON) Render(w http.ResponseWriter) error {
// WriteContentType (IndentedJSON) writes JSON ContentType. // WriteContentType (IndentedJSON) writes JSON ContentType.
func (r IndentedJSON) WriteContentType(w http.ResponseWriter) { func (r IndentedJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonContentType) common.WriteContentType(w, jsonContentType)
} }
// Render (SecureJSON) marshals the given interface object and writes it with custom ContentType. // Render (SecureJSON) marshals the given interface object and writes it with custom ContentType.
@ -107,7 +141,7 @@ func (r SecureJSON) Render(w http.ResponseWriter) error {
// WriteContentType (SecureJSON) writes JSON ContentType. // WriteContentType (SecureJSON) writes JSON ContentType.
func (r SecureJSON) WriteContentType(w http.ResponseWriter) { func (r SecureJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonContentType) common.WriteContentType(w, jsonContentType)
} }
// Render (JsonpJSON) marshals the given interface object and writes it and its callback with custom ContentType. // Render (JsonpJSON) marshals the given interface object and writes it and its callback with custom ContentType.
@ -146,7 +180,7 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) {
// WriteContentType (JsonpJSON) writes Javascript ContentType. // WriteContentType (JsonpJSON) writes Javascript ContentType.
func (r JsonpJSON) WriteContentType(w http.ResponseWriter) { func (r JsonpJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonpContentType) common.WriteContentType(w, jsonpContentType)
} }
// Render (AsciiJSON) marshals the given interface object and writes it with custom ContentType. // Render (AsciiJSON) marshals the given interface object and writes it with custom ContentType.
@ -172,5 +206,5 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) {
// WriteContentType (AsciiJSON) writes JSON ContentType. // WriteContentType (AsciiJSON) writes JSON ContentType.
func (r AsciiJSON) WriteContentType(w http.ResponseWriter) { func (r AsciiJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonAsciiContentType) common.WriteContentType(w, jsonAsciiContentType)
} }

View File

@ -4,14 +4,24 @@
// +build go1.7 // +build go1.7
package render package json
import ( import (
"net/http" "net/http"
"github.com/gin-gonic/gin/internal/json" "github.com/gin-gonic/gin/internal/json"
"github.com/gin-gonic/gin/render/common"
) )
func init() {
common.List["PureJSON"] = NewPureJSON
}
//NewPureJSON build a new PureJSON render
func NewPureJSON(obj interface{}, _ map[string]string) common.Render {
return PureJSON{Data: obj}
}
// PureJSON contains the given interface object. // PureJSON contains the given interface object.
type PureJSON struct { type PureJSON struct {
Data interface{} Data interface{}
@ -27,5 +37,5 @@ func (r PureJSON) Render(w http.ResponseWriter) error {
// WriteContentType (PureJSON) writes custom ContentType. // WriteContentType (PureJSON) writes custom ContentType.
func (r PureJSON) WriteContentType(w http.ResponseWriter) { func (r PureJSON) WriteContentType(w http.ResponseWriter) {
writeContentType(w, jsonContentType) common.WriteContentType(w, jsonContentType)
} }

View File

@ -2,14 +2,19 @@
// Use of this source code is governed by a MIT style // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package render package msgpack
import ( import (
"net/http" "net/http"
"github.com/gin-gonic/gin/render/common"
"github.com/ugorji/go/codec" "github.com/ugorji/go/codec"
) )
func init() {
common.List["MsgPack"] = NewMsgPack
}
// MsgPack contains the given interface object. // MsgPack contains the given interface object.
type MsgPack struct { type MsgPack struct {
Data interface{} Data interface{}
@ -19,7 +24,7 @@ var msgpackContentType = []string{"application/msgpack; charset=utf-8"}
// WriteContentType (MsgPack) writes MsgPack ContentType. // WriteContentType (MsgPack) writes MsgPack ContentType.
func (r MsgPack) WriteContentType(w http.ResponseWriter) { func (r MsgPack) WriteContentType(w http.ResponseWriter) {
writeContentType(w, msgpackContentType) common.WriteContentType(w, msgpackContentType)
} }
// Render (MsgPack) encodes the given interface object and writes data with custom ContentType. // Render (MsgPack) encodes the given interface object and writes data with custom ContentType.
@ -29,7 +34,12 @@ func (r MsgPack) Render(w http.ResponseWriter) error {
// WriteMsgPack writes MsgPack ContentType and encodes the given interface object. // WriteMsgPack writes MsgPack ContentType and encodes the given interface object.
func WriteMsgPack(w http.ResponseWriter, obj interface{}) error { func WriteMsgPack(w http.ResponseWriter, obj interface{}) error {
writeContentType(w, msgpackContentType) common.WriteContentType(w, msgpackContentType)
var mh codec.MsgpackHandle var mh codec.MsgpackHandle
return codec.NewEncoder(w, &mh).Encode(obj) return codec.NewEncoder(w, &mh).Encode(obj)
} }
//NewMsgPack build a new MsgPack render
func NewMsgPack(obj interface{}, _ map[string]string) common.Render {
return MsgPack{Data: obj}
}

View File

@ -2,14 +2,19 @@
// Use of this source code is governed by a MIT style // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package render package protobuf
import ( import (
"net/http" "net/http"
"github.com/gin-gonic/gin/render/common"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
) )
func init() {
common.List["ProtoBuf"] = NewProtoBuf
}
// ProtoBuf contains the given interface object. // ProtoBuf contains the given interface object.
type ProtoBuf struct { type ProtoBuf struct {
Data interface{} Data interface{}
@ -32,5 +37,10 @@ func (r ProtoBuf) Render(w http.ResponseWriter) error {
// WriteContentType (ProtoBuf) writes ProtoBuf ContentType. // WriteContentType (ProtoBuf) writes ProtoBuf ContentType.
func (r ProtoBuf) WriteContentType(w http.ResponseWriter) { func (r ProtoBuf) WriteContentType(w http.ResponseWriter) {
writeContentType(w, protobufContentType) common.WriteContentType(w, protobufContentType)
}
//NewProtoBuf build a new ProtoBuf render
func NewProtoBuf(obj interface{}, _ map[string]string) common.Render {
return ProtoBuf{Data: obj}
} }

View File

@ -8,6 +8,8 @@ import (
"io" "io"
"net/http" "net/http"
"strconv" "strconv"
"github.com/gin-gonic/gin/render/common"
) )
// Reader contains the IO reader and its length, and custom ContentType and other headers. // Reader contains the IO reader and its length, and custom ContentType and other headers.
@ -29,7 +31,7 @@ func (r Reader) Render(w http.ResponseWriter) (err error) {
// WriteContentType (Reader) writes custom ContentType. // WriteContentType (Reader) writes custom ContentType.
func (r Reader) WriteContentType(w http.ResponseWriter) { func (r Reader) WriteContentType(w http.ResponseWriter) {
writeContentType(w, []string{r.ContentType}) common.WriteContentType(w, []string{r.ContentType})
} }
// writeHeaders writes custom Header. // writeHeaders writes custom Header.

View File

@ -4,38 +4,76 @@
package render package render
import "net/http" import (
"fmt"
// Render interface is to be implemented by JSON, XML, HTML, YAML and so on. "github.com/gin-gonic/gin/render/common"
type Render interface {
// Render writes data with custom ContentType.
Render(http.ResponseWriter) error
// WriteContentType writes custom ContentType.
WriteContentType(w http.ResponseWriter)
}
var (
_ Render = JSON{}
_ Render = IndentedJSON{}
_ Render = SecureJSON{}
_ Render = JsonpJSON{}
_ Render = XML{}
_ Render = String{}
_ Render = Redirect{}
_ Render = Data{}
_ Render = HTML{}
_ HTMLRender = HTMLDebug{}
_ HTMLRender = HTMLProduction{}
_ Render = YAML{}
_ Render = MsgPack{}
_ Render = Reader{}
_ Render = AsciiJSON{}
_ Render = ProtoBuf{}
) )
func writeContentType(w http.ResponseWriter, value []string) { var (
header := w.Header() _ common.Render = String{}
if val := header["Content-Type"]; len(val) == 0 { _ common.Render = Redirect{}
header["Content-Type"] = value _ common.Render = Data{}
_ common.Render = HTML{}
_ HTMLRender = HTMLDebug{}
_ HTMLRender = HTMLProduction{}
_ common.Render = Reader{}
)
//YAML return the render for yaml if loaded
func YAML(obj interface{}) common.Render {
return retRender("YAML", obj, nil)
} }
//XML return the render for xml if loaded
func XML(obj interface{}) common.Render {
return retRender("XML", obj, nil)
}
//ProtoBuf return the render for ProtoBuf if loaded
func ProtoBuf(obj interface{}) common.Render {
return retRender("ProtoBuf", obj, nil)
}
//MsgPack return the render for MsgPack if loaded
func MsgPack(obj interface{}) common.Render {
return retRender("MsgPack", obj, nil)
}
//JSON return the render for JSON if loaded
func JSON(obj interface{}) common.Render {
return retRender("JSON", obj, nil)
}
//IndentedJSON return the render for IndentedJSON if loaded
func IndentedJSON(obj interface{}) common.Render {
return retRender("IndentedJSON", obj, nil)
}
//SecureJSON return the render for SecureJSON if loaded
func SecureJSON(prefix string, obj interface{}) common.Render {
return retRender("SecureJSON", obj, map[string]string{
"Prefix": prefix,
})
}
//JsonpJSON return the render for JsonpJSON if loaded
func JsonpJSON(callback string, obj interface{}) common.Render {
return retRender("JsonpJSON", obj, map[string]string{
"Callback": callback,
})
}
//AsciiJSON return the render for AsciiJSON if loaded
func AsciiJSON(obj interface{}) common.Render {
return retRender("AsciiJSON", obj, nil)
}
//Search for a render
func retRender(rID string, obj interface{}, opts map[string]string) common.Render {
r, ok := common.List[rID]
if !ok {
panic(fmt.Sprintf("Undefined render %s", rID))
}
return r(obj, opts)
} }

10
render/render_17.go Normal file
View File

@ -0,0 +1,10 @@
// +build go1.7
package render
import "github.com/gin-gonic/gin/render/common"
//PureJSON return the render for AsciiJSON if loaded
func PureJSON(obj interface{}) common.Render {
return retRender("PureJSON", obj, nil)
}

View File

@ -19,7 +19,7 @@ func TestRenderPureJSON(t *testing.T) {
"foo": "bar", "foo": "bar",
"html": "<b>", "html": "<b>",
} }
err := (PureJSON{data}).Render(w) err := PureJSON(data).Render(w)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String()) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"<b>\"}\n", w.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))

View File

@ -20,6 +20,12 @@ import (
"github.com/ugorji/go/codec" "github.com/ugorji/go/codec"
testdata "github.com/gin-gonic/gin/testdata/protoexample" testdata "github.com/gin-gonic/gin/testdata/protoexample"
_ "github.com/gin-gonic/gin/render/json"
_ "github.com/gin-gonic/gin/render/msgpack"
_ "github.com/gin-gonic/gin/render/protobuf"
_ "github.com/gin-gonic/gin/render/xml"
_ "github.com/gin-gonic/gin/render/yaml"
) )
// TODO unit tests // TODO unit tests
@ -31,10 +37,10 @@ func TestRenderMsgPack(t *testing.T) {
"foo": "bar", "foo": "bar",
} }
(MsgPack{data}).WriteContentType(w) MsgPack(data).WriteContentType(w)
assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "application/msgpack; charset=utf-8", w.Header().Get("Content-Type"))
err := (MsgPack{data}).Render(w) err := MsgPack(data).Render(w)
assert.NoError(t, err) assert.NoError(t, err)
@ -56,10 +62,10 @@ func TestRenderJSON(t *testing.T) {
"html": "<b>", "html": "<b>",
} }
(JSON{data}).WriteContentType(w) JSON(data).WriteContentType(w)
assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type"))
err := (JSON{data}).Render(w) err := JSON(data).Render(w)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String())
@ -71,7 +77,7 @@ func TestRenderJSONPanics(t *testing.T) {
data := make(chan int) data := make(chan int)
// json: unsupported type: chan int // json: unsupported type: chan int
assert.Panics(t, func() { assert.NoError(t, (JSON{data}).Render(w)) }) assert.Panics(t, func() { assert.NoError(t, JSON(data).Render(w)) })
} }
func TestRenderIndentedJSON(t *testing.T) { func TestRenderIndentedJSON(t *testing.T) {
@ -81,7 +87,7 @@ func TestRenderIndentedJSON(t *testing.T) {
"bar": "foo", "bar": "foo",
} }
err := (IndentedJSON{data}).Render(w) err := IndentedJSON(data).Render(w)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}", w.Body.String()) assert.Equal(t, "{\n \"bar\": \"foo\",\n \"foo\": \"bar\"\n}", w.Body.String())
@ -93,7 +99,7 @@ func TestRenderIndentedJSONPanics(t *testing.T) {
data := make(chan int) data := make(chan int)
// json: unsupported type: chan int // json: unsupported type: chan int
err := (IndentedJSON{data}).Render(w) err := IndentedJSON(data).Render(w)
assert.Error(t, err) assert.Error(t, err)
} }
@ -103,10 +109,10 @@ func TestRenderSecureJSON(t *testing.T) {
"foo": "bar", "foo": "bar",
} }
(SecureJSON{"while(1);", data}).WriteContentType(w1) SecureJSON("while(1);", data).WriteContentType(w1)
assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type")) assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type"))
err1 := (SecureJSON{"while(1);", data}).Render(w1) err1 := SecureJSON("while(1);", data).Render(w1)
assert.NoError(t, err1) assert.NoError(t, err1)
assert.Equal(t, "{\"foo\":\"bar\"}", w1.Body.String()) assert.Equal(t, "{\"foo\":\"bar\"}", w1.Body.String())
@ -119,7 +125,7 @@ func TestRenderSecureJSON(t *testing.T) {
"bar": "foo", "bar": "foo",
}} }}
err2 := (SecureJSON{"while(1);", datas}).Render(w2) err2 := SecureJSON("while(1);", datas).Render(w2)
assert.NoError(t, err2) assert.NoError(t, err2)
assert.Equal(t, "while(1);[{\"foo\":\"bar\"},{\"bar\":\"foo\"}]", w2.Body.String()) assert.Equal(t, "while(1);[{\"foo\":\"bar\"},{\"bar\":\"foo\"}]", w2.Body.String())
assert.Equal(t, "application/json; charset=utf-8", w2.Header().Get("Content-Type")) assert.Equal(t, "application/json; charset=utf-8", w2.Header().Get("Content-Type"))
@ -130,7 +136,7 @@ func TestRenderSecureJSONFail(t *testing.T) {
data := make(chan int) data := make(chan int)
// json: unsupported type: chan int // json: unsupported type: chan int
err := (SecureJSON{"while(1);", data}).Render(w) err := SecureJSON("while(1);", data).Render(w)
assert.Error(t, err) assert.Error(t, err)
} }
@ -140,10 +146,10 @@ func TestRenderJsonpJSON(t *testing.T) {
"foo": "bar", "foo": "bar",
} }
(JsonpJSON{"x", data}).WriteContentType(w1) JsonpJSON("x", data).WriteContentType(w1)
assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type")) assert.Equal(t, "application/javascript; charset=utf-8", w1.Header().Get("Content-Type"))
err1 := (JsonpJSON{"x", data}).Render(w1) err1 := JsonpJSON("x", data).Render(w1)
assert.NoError(t, err1) assert.NoError(t, err1)
assert.Equal(t, "x({\"foo\":\"bar\"})", w1.Body.String()) assert.Equal(t, "x({\"foo\":\"bar\"})", w1.Body.String())
@ -156,7 +162,7 @@ func TestRenderJsonpJSON(t *testing.T) {
"bar": "foo", "bar": "foo",
}} }}
err2 := (JsonpJSON{"x", datas}).Render(w2) err2 := JsonpJSON("x", datas).Render(w2)
assert.NoError(t, err2) assert.NoError(t, err2)
assert.Equal(t, "x([{\"foo\":\"bar\"},{\"bar\":\"foo\"}])", w2.Body.String()) assert.Equal(t, "x([{\"foo\":\"bar\"},{\"bar\":\"foo\"}])", w2.Body.String())
assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type")) assert.Equal(t, "application/javascript; charset=utf-8", w2.Header().Get("Content-Type"))
@ -167,10 +173,10 @@ func TestRenderJsonpJSONError2(t *testing.T) {
data := map[string]interface{}{ data := map[string]interface{}{
"foo": "bar", "foo": "bar",
} }
(JsonpJSON{"", data}).WriteContentType(w) JsonpJSON("", data).WriteContentType(w)
assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "application/javascript; charset=utf-8", w.Header().Get("Content-Type"))
e := (JsonpJSON{"", data}).Render(w) e := JsonpJSON("", data).Render(w)
assert.NoError(t, e) assert.NoError(t, e)
assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) assert.Equal(t, "{\"foo\":\"bar\"}", w.Body.String())
@ -182,7 +188,7 @@ func TestRenderJsonpJSONFail(t *testing.T) {
data := make(chan int) data := make(chan int)
// json: unsupported type: chan int // json: unsupported type: chan int
err := (JsonpJSON{"x", data}).Render(w) err := JsonpJSON("x", data).Render(w)
assert.Error(t, err) assert.Error(t, err)
} }
@ -193,7 +199,7 @@ func TestRenderAsciiJSON(t *testing.T) {
"tag": "<br>", "tag": "<br>",
} }
err := (AsciiJSON{data1}).Render(w1) err := AsciiJSON(data1).Render(w1)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "{\"lang\":\"GO\\u8bed\\u8a00\",\"tag\":\"\\u003cbr\\u003e\"}", w1.Body.String()) assert.Equal(t, "{\"lang\":\"GO\\u8bed\\u8a00\",\"tag\":\"\\u003cbr\\u003e\"}", w1.Body.String())
@ -202,7 +208,7 @@ func TestRenderAsciiJSON(t *testing.T) {
w2 := httptest.NewRecorder() w2 := httptest.NewRecorder()
data2 := float64(3.1415926) data2 := float64(3.1415926)
err = (AsciiJSON{data2}).Render(w2) err = AsciiJSON(data2).Render(w2)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "3.1415926", w2.Body.String()) assert.Equal(t, "3.1415926", w2.Body.String())
} }
@ -212,7 +218,7 @@ func TestRenderAsciiJSONFail(t *testing.T) {
data := make(chan int) data := make(chan int)
// json: unsupported type: chan int // json: unsupported type: chan int
assert.Error(t, (AsciiJSON{data}).Render(w)) assert.Error(t, AsciiJSON(data).Render(w))
} }
type xmlmap map[string]interface{} type xmlmap map[string]interface{}
@ -247,10 +253,10 @@ b:
c: 2 c: 2
d: [3, 4] d: [3, 4]
` `
(YAML{data}).WriteContentType(w) YAML(data).WriteContentType(w)
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
err := (YAML{data}).Render(w) err := YAML(data).Render(w)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "\"\\na : Easy!\\nb:\\n\\tc: 2\\n\\td: [3, 4]\\n\\t\"\n", w.Body.String()) assert.Equal(t, "\"\\na : Easy!\\nb:\\n\\tc: 2\\n\\td: [3, 4]\\n\\t\"\n", w.Body.String())
assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "application/x-yaml; charset=utf-8", w.Header().Get("Content-Type"))
@ -265,7 +271,7 @@ func (ft *fail) MarshalYAML() (interface{}, error) {
func TestRenderYAMLFail(t *testing.T) { func TestRenderYAMLFail(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
err := (YAML{&fail{}}).Render(w) err := YAML(&fail{}).Render(w)
assert.Error(t, err) assert.Error(t, err)
} }
@ -279,12 +285,12 @@ func TestRenderProtoBuf(t *testing.T) {
Reps: reps, Reps: reps,
} }
(ProtoBuf{data}).WriteContentType(w) ProtoBuf(data).WriteContentType(w)
protoData, err := proto.Marshal(data) protoData, err := proto.Marshal(data)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type")) assert.Equal(t, "application/x-protobuf", w.Header().Get("Content-Type"))
err = (ProtoBuf{data}).Render(w) err = ProtoBuf(data).Render(w)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, string(protoData), w.Body.String()) assert.Equal(t, string(protoData), w.Body.String())
@ -294,7 +300,7 @@ func TestRenderProtoBuf(t *testing.T) {
func TestRenderProtoBufFail(t *testing.T) { func TestRenderProtoBufFail(t *testing.T) {
w := httptest.NewRecorder() w := httptest.NewRecorder()
data := &testdata.Test{} data := &testdata.Test{}
err := (ProtoBuf{data}).Render(w) err := ProtoBuf(data).Render(w)
assert.Error(t, err) assert.Error(t, err)
} }
@ -304,10 +310,10 @@ func TestRenderXML(t *testing.T) {
"foo": "bar", "foo": "bar",
} }
(XML{data}).WriteContentType(w) XML(data).WriteContentType(w)
assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type")) assert.Equal(t, "application/xml; charset=utf-8", w.Header().Get("Content-Type"))
err := (XML{data}).Render(w) err := XML(data).Render(w)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "<map><foo>bar</foo></map>", w.Body.String()) assert.Equal(t, "<map><foo>bar</foo></map>", w.Body.String())
@ -486,3 +492,14 @@ func TestRenderReader(t *testing.T) {
assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition")) assert.Equal(t, headers["Content-Disposition"], w.Header().Get("Content-Disposition"))
assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id")) assert.Equal(t, headers["x-request-id"], w.Header().Get("x-request-id"))
} }
func TestRenderPanic(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("retRender did not panic")
}
}()
//Should panic
retRender("NotKnowRender", nil, nil)
}

View File

@ -8,6 +8,8 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"github.com/gin-gonic/gin/render/common"
) )
// String contains the given interface object slice and its format. // String contains the given interface object slice and its format.
@ -25,12 +27,12 @@ func (r String) Render(w http.ResponseWriter) error {
// WriteContentType (String) writes Plain ContentType. // WriteContentType (String) writes Plain ContentType.
func (r String) WriteContentType(w http.ResponseWriter) { func (r String) WriteContentType(w http.ResponseWriter) {
writeContentType(w, plainContentType) common.WriteContentType(w, plainContentType)
} }
// WriteString writes data according to its format and write custom ContentType. // WriteString writes data according to its format and write custom ContentType.
func WriteString(w http.ResponseWriter, format string, data []interface{}) (err error) { func WriteString(w http.ResponseWriter, format string, data []interface{}) (err error) {
writeContentType(w, plainContentType) common.WriteContentType(w, plainContentType)
if len(data) > 0 { if len(data) > 0 {
_, err = fmt.Fprintf(w, format, data...) _, err = fmt.Fprintf(w, format, data...)
return return

View File

@ -2,13 +2,19 @@
// Use of this source code is governed by a MIT style // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package render package xml
import ( import (
"encoding/xml" "encoding/xml"
"net/http" "net/http"
"github.com/gin-gonic/gin/render/common"
) )
func init() {
common.List["XML"] = NewXML
}
// XML contains the given interface object. // XML contains the given interface object.
type XML struct { type XML struct {
Data interface{} Data interface{}
@ -24,5 +30,10 @@ func (r XML) Render(w http.ResponseWriter) error {
// WriteContentType (XML) writes XML ContentType for response. // WriteContentType (XML) writes XML ContentType for response.
func (r XML) WriteContentType(w http.ResponseWriter) { func (r XML) WriteContentType(w http.ResponseWriter) {
writeContentType(w, xmlContentType) common.WriteContentType(w, xmlContentType)
}
//NewXML build a new xml render
func NewXML(obj interface{}, _ map[string]string) common.Render {
return XML{Data: obj}
} }

View File

@ -2,14 +2,19 @@
// Use of this source code is governed by a MIT style // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package render package yaml
import ( import (
"net/http" "net/http"
"github.com/gin-gonic/gin/render/common"
"gopkg.in/yaml.v2" "gopkg.in/yaml.v2"
) )
func init() {
common.List["YAML"] = NewYAML
}
// YAML contains the given interface object. // YAML contains the given interface object.
type YAML struct { type YAML struct {
Data interface{} Data interface{}
@ -32,5 +37,10 @@ func (r YAML) Render(w http.ResponseWriter) error {
// WriteContentType (YAML) writes YAML ContentType for response. // WriteContentType (YAML) writes YAML ContentType for response.
func (r YAML) WriteContentType(w http.ResponseWriter) { func (r YAML) WriteContentType(w http.ResponseWriter) {
writeContentType(w, yamlContentType) common.WriteContentType(w, yamlContentType)
}
//NewYAML build a new yaml render
func NewYAML(obj interface{}, _ map[string]string) common.Render {
return YAML{Data: obj}
} }