diff --git a/binding/binding.go b/binding/binding.go index 520c5109..a14bce1b 100644 --- a/binding/binding.go +++ b/binding/binding.go @@ -4,110 +4,70 @@ package binding -import "net/http" +import ( + "fmt" -// 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" + "github.com/gin-gonic/gin/binding/common" ) -// 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 // present in the request to struct instances. var ( - JSON = jsonBinding{} - XML = xmlBinding{} Form = formBinding{} Query = queryBinding{} FormPost = formPostBinding{} - FormMultipart = formMultipartBinding{} - ProtoBuf = protobufBinding{} - MsgPack = msgpackBinding{} - YAML = yamlBinding{} Uri = uriBinding{} + FormMultipart = formMultipartBinding{} ) // Default returns the appropriate Binding instance based on the HTTP method // and the content type. -func Default(method, contentType string) Binding { +func Default(method, contentType string) common.Binding { if method == "GET" { return Form } - switch contentType { - case MIMEJSON: - return JSON - case MIMEXML, MIMEXML2: - return XML - case MIMEPROTOBUF: - return ProtoBuf - case MIMEMSGPACK, MIMEMSGPACK2: - return MsgPack - case MIMEYAML: - return YAML - case MIMEMultipartPOSTForm: + case common.MIMEMultipartPOSTForm: return FormMultipart - default: // case MIMEPOSTForm: - return Form + default: + b, ok := common.List[contentType] + if !ok { + return Form //Default to Form + } + return b } } -func validate(obj interface{}) error { - if Validator == nil { - return nil - } - return Validator.ValidateStruct(obj) +//YAML return the binding for yaml if loaded +func YAML() common.BindingBody { + return retBinding(common.MIMEYAML) +} + +//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 } diff --git a/binding/binding_body_test.go b/binding/binding_body_test.go index 901d429c..4e560bfc 100644 --- a/binding/binding_body_test.go +++ b/binding/binding_body_test.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "testing" + "github.com/gin-gonic/gin/binding/common" "github.com/gin-gonic/gin/testdata/protoexample" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" @@ -14,18 +15,18 @@ import ( func TestBindingBody(t *testing.T) { for _, tt := range []struct { name string - binding BindingBody + binding common.BindingBody body string want string }{ { name: "JSON binding", - binding: JSON, + binding: JSON(), body: `{"foo":"FOO"}`, }, { name: "XML binding", - binding: XML, + binding: XML(), body: ` FOO @@ -33,12 +34,12 @@ func TestBindingBody(t *testing.T) { }, { name: "MsgPack binding", - binding: MsgPack, + binding: MsgPack(), body: msgPackBody(t), }, { name: "YAML binding", - binding: YAML, + binding: YAML(), body: `foo: FOO`, }, } { @@ -67,6 +68,6 @@ func TestBindingBodyProto(t *testing.T) { req := requestWithBody("POST", "/", string(data)) form := protoexample.Test{} body, _ := ioutil.ReadAll(req.Body) - assert.NoError(t, ProtoBuf.BindBody(body, &form)) + assert.NoError(t, ProtoBuf().BindBody(body, &form)) assert.Equal(t, test, form) } diff --git a/binding/binding_test.go b/binding/binding_test.go index ee788225..bc502499 100644 --- a/binding/binding_test.go +++ b/binding/binding_test.go @@ -18,10 +18,17 @@ import ( "testing" "time" + "github.com/gin-gonic/gin/binding/common" "github.com/gin-gonic/gin/testdata/protoexample" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" "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 { @@ -190,54 +197,54 @@ type FooStructForMapPtrType struct { func TestBindingDefault(t *testing.T) { 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("PUT", MIMEJSON)) + assert.Equal(t, JSON(), Default("POST", common.MIMEJSON)) + assert.Equal(t, JSON(), Default("PUT", common.MIMEJSON)) - assert.Equal(t, XML, Default("POST", MIMEXML)) - assert.Equal(t, XML, Default("PUT", MIMEXML2)) + assert.Equal(t, XML(), Default("POST", common.MIMEXML)) + assert.Equal(t, XML(), Default("PUT", common.MIMEXML2)) - assert.Equal(t, Form, Default("POST", MIMEPOSTForm)) - assert.Equal(t, Form, Default("PUT", MIMEPOSTForm)) + assert.Equal(t, Form, Default("POST", common.MIMEPOSTForm)) + assert.Equal(t, Form, Default("PUT", common.MIMEPOSTForm)) - assert.Equal(t, FormMultipart, Default("POST", MIMEMultipartPOSTForm)) - assert.Equal(t, FormMultipart, Default("PUT", MIMEMultipartPOSTForm)) + assert.Equal(t, FormMultipart, Default("POST", common.MIMEMultipartPOSTForm)) + assert.Equal(t, FormMultipart, Default("PUT", common.MIMEMultipartPOSTForm)) - assert.Equal(t, ProtoBuf, Default("POST", MIMEPROTOBUF)) - assert.Equal(t, ProtoBuf, Default("PUT", MIMEPROTOBUF)) + assert.Equal(t, ProtoBuf(), Default("POST", common.MIMEPROTOBUF)) + assert.Equal(t, ProtoBuf(), Default("PUT", common.MIMEPROTOBUF)) - assert.Equal(t, MsgPack, Default("POST", MIMEMSGPACK)) - assert.Equal(t, MsgPack, Default("PUT", MIMEMSGPACK2)) + assert.Equal(t, MsgPack(), Default("POST", common.MIMEMSGPACK)) + assert.Equal(t, MsgPack(), Default("PUT", common.MIMEMSGPACK2)) - assert.Equal(t, YAML, Default("POST", MIMEYAML)) - assert.Equal(t, YAML, Default("PUT", MIMEYAML)) + assert.Equal(t, YAML(), Default("POST", common.MIMEYAML)) + assert.Equal(t, YAML(), Default("PUT", common.MIMEYAML)) } func TestBindingJSONNilBody(t *testing.T) { var obj FooStruct req, _ := http.NewRequest(http.MethodPost, "/", nil) - err := JSON.Bind(req, &obj) + err := JSON().Bind(req, &obj) assert.Error(t, err) } func TestBindingJSON(t *testing.T) { testBodyBinding(t, - JSON, "json", + JSON(), "json", "/", "/", `{"foo": "bar"}`, `{"bar": "foo"}`) } func TestBindingJSONUseNumber(t *testing.T) { testBodyBindingUseNumber(t, - JSON, "json", + JSON(), "json", "/", "/", `{"foo": 123}`, `{"bar": "foo"}`) } func TestBindingJSONUseNumber2(t *testing.T) { testBodyBindingUseNumber2(t, - JSON, "json", + JSON(), "json", "/", "/", `{"foo": 123}`, `{"bar": "foo"}`) } @@ -496,28 +503,28 @@ func TestBindingQueryBoolFail(t *testing.T) { func TestBindingXML(t *testing.T) { testBodyBinding(t, - XML, "xml", + XML(), "xml", "/", "/", "bar", "foo") } func TestBindingXMLFail(t *testing.T) { testBodyBindingFail(t, - XML, "xml", + XML(), "xml", "/", "/", "bar", "foo") } func TestBindingYAML(t *testing.T) { testBodyBinding(t, - YAML, "yaml", + YAML(), "yaml", "/", "/", `foo: bar`, `bar: foo`) } func TestBindingYAMLFail(t *testing.T) { testBodyBindingFail(t, - YAML, "yaml", + YAML(), "yaml", "/", "/", `foo:\nbar`, `bar: foo`) } @@ -525,28 +532,28 @@ func TestBindingYAMLFail(t *testing.T) { func createFormPostRequest(t *testing.T) *http.Request { req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar&bar=foo")) assert.NoError(t, err) - req.Header.Set("Content-Type", MIMEPOSTForm) + req.Header.Set("Content-Type", common.MIMEPOSTForm) return req } func createDefaultFormPostRequest(t *testing.T) *http.Request { req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", bytes.NewBufferString("foo=bar")) assert.NoError(t, err) - req.Header.Set("Content-Type", MIMEPOSTForm) + req.Header.Set("Content-Type", common.MIMEPOSTForm) return req } func createFormPostRequestForMap(t *testing.T) *http.Request { req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo={\"bar\":123}")) assert.NoError(t, err) - req.Header.Set("Content-Type", MIMEPOSTForm) + req.Header.Set("Content-Type", common.MIMEPOSTForm) return req } func createFormPostRequestForMapFail(t *testing.T) *http.Request { req, err := http.NewRequest("POST", "/?map_foo=getfoo", bytes.NewBufferString("map_foo=hello")) assert.NoError(t, err) - req.Header.Set("Content-Type", MIMEPOSTForm) + req.Header.Set("Content-Type", common.MIMEPOSTForm) return req } @@ -569,7 +576,7 @@ func createFormFilesMultipartRequest(t *testing.T) *http.Request { req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) assert.NoError(t, err2) - req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) + req.Header.Set("Content-Type", common.MIMEMultipartPOSTForm+"; boundary="+boundary) return req } @@ -593,7 +600,7 @@ func createFormFilesMultipartRequestFail(t *testing.T) *http.Request { req, err2 := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) assert.NoError(t, err2) - req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) + req.Header.Set("Content-Type", common.MIMEMultipartPOSTForm+"; boundary="+boundary) return req } @@ -609,7 +616,7 @@ func createFormMultipartRequest(t *testing.T) *http.Request { assert.NoError(t, mw.WriteField("bar", "foo")) req, err := http.NewRequest("POST", "/?foo=getfoo&bar=getbar", body) assert.NoError(t, err) - req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) + req.Header.Set("Content-Type", common.MIMEMultipartPOSTForm+"; boundary="+boundary) 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}")) req, err := http.NewRequest("POST", "/?map_foo=getfoo", body) assert.NoError(t, err) - req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) + req.Header.Set("Content-Type", common.MIMEMultipartPOSTForm+"; boundary="+boundary) return req } @@ -637,7 +644,7 @@ func createFormMultipartRequestForMapFail(t *testing.T) *http.Request { assert.NoError(t, mw.WriteField("map_foo", "3.14")) req, err := http.NewRequest("POST", "/?map_foo=getfoo", body) assert.NoError(t, err) - req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) + req.Header.Set("Content-Type", common.MIMEMultipartPOSTForm+"; boundary="+boundary) return req } @@ -737,7 +744,7 @@ func TestBindingProtoBuf(t *testing.T) { data, _ := proto.Marshal(test) testProtoBodyBinding(t, - ProtoBuf, "protobuf", + ProtoBuf(), "protobuf", "/", "/", string(data), string(data[1:])) } @@ -749,7 +756,7 @@ func TestBindingProtoBufFail(t *testing.T) { data, _ := proto.Marshal(test) testProtoBodyBindingFail(t, - ProtoBuf, "protobuf", + ProtoBuf(), "protobuf", "/", "/", string(data), string(data[1:])) } @@ -769,7 +776,7 @@ func TestBindingMsgPack(t *testing.T) { data := buf.Bytes() testMsgPackBodyBinding(t, - MsgPack, "msgpack", + MsgPack(), "msgpack", "/", "/", string(data), string(data[1:])) } @@ -777,18 +784,18 @@ func TestBindingMsgPack(t *testing.T) { func TestValidationFails(t *testing.T) { var obj FooStruct req := requestWithBody("POST", "/", `{"bar": "foo"}`) - err := JSON.Bind(req, &obj) + err := JSON().Bind(req, &obj) assert.Error(t, err) } func TestValidationDisabled(t *testing.T) { - backup := Validator - Validator = nil - defer func() { Validator = backup }() + backup := common.Validator + common.Validator = nil + defer func() { common.Validator = backup }() var obj FooStruct req := requestWithBody("POST", "/", `{"bar": "foo"}`) - err := JSON.Bind(req, &obj) + err := JSON().Bind(req, &obj) assert.NoError(t, err) } @@ -799,7 +806,7 @@ func TestExistsSucceeds(t *testing.T) { var obj HogeStruct req := requestWithBody("POST", "/", `{"hoge": 0}`) - err := JSON.Bind(req, &obj) + err := JSON().Bind(req, &obj) assert.NoError(t, err) } @@ -810,7 +817,7 @@ func TestExistsFails(t *testing.T) { var obj HogeStruct req := requestWithBody("POST", "/", `{"boen": 0}`) - err := JSON.Bind(req, &obj) + err := JSON().Bind(req, &obj) assert.Error(t, err) } @@ -864,7 +871,7 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) obj := FooBarStruct{} req := requestWithBody(method, path, body) if method == "POST" { - req.Header.Add("Content-Type", MIMEPOSTForm) + req.Header.Add("Content-Type", common.MIMEPOSTForm) } err := b.Bind(req, &obj) assert.NoError(t, err) @@ -873,7 +880,7 @@ func testFormBinding(t *testing.T, method, path, badPath, body, badBody string) obj = FooBarStruct{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) } @@ -884,7 +891,7 @@ func testFormBindingDefaultValue(t *testing.T, method, path, badPath, body, badB obj := FooDefaultBarStruct{} req := requestWithBody(method, path, body) if method == "POST" { - req.Header.Add("Content-Type", MIMEPOSTForm) + req.Header.Add("Content-Type", common.MIMEPOSTForm) } err := b.Bind(req, &obj) assert.NoError(t, err) @@ -893,7 +900,7 @@ func testFormBindingDefaultValue(t *testing.T, method, path, badPath, body, badB obj = FooDefaultBarStruct{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) } @@ -911,7 +918,7 @@ func TestFormBindingMultipartFail(t *testing.T) { obj := FooBarStruct{} req, err := http.NewRequest("POST", "/", strings.NewReader("foo=bar")) assert.NoError(t, err) - req.Header.Set("Content-Type", MIMEMultipartPOSTForm+";boundary=testboundary") + req.Header.Set("Content-Type", common.MIMEMultipartPOSTForm+";boundary=testboundary") _, err = req.MultipartReader() assert.NoError(t, err) err = Form.Bind(req, &obj) @@ -945,7 +952,7 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s obj := FooBarStructForTimeType{} req := requestWithBody(method, path, body) if method == "POST" { - req.Header.Add("Content-Type", MIMEPOSTForm) + req.Header.Add("Content-Type", common.MIMEPOSTForm) } err := b.Bind(req, &obj) @@ -957,7 +964,7 @@ func testFormBindingForTime(t *testing.T, method, path, badPath, body, badBody s obj = FooBarStructForTimeType{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) } @@ -968,14 +975,14 @@ func testFormBindingForTimeNotFormat(t *testing.T, method, path, badPath, body, obj := FooStructForTimeTypeNotFormat{} req := requestWithBody(method, path, body) if method == "POST" { - req.Header.Add("Content-Type", MIMEPOSTForm) + req.Header.Add("Content-Type", common.MIMEPOSTForm) } err := b.Bind(req, &obj) assert.Error(t, err) obj = FooStructForTimeTypeNotFormat{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) } @@ -986,14 +993,14 @@ func testFormBindingForTimeFailFormat(t *testing.T, method, path, badPath, body, obj := FooStructForTimeTypeFailFormat{} req := requestWithBody(method, path, body) if method == "POST" { - req.Header.Add("Content-Type", MIMEPOSTForm) + req.Header.Add("Content-Type", common.MIMEPOSTForm) } err := b.Bind(req, &obj) assert.Error(t, err) obj = FooStructForTimeTypeFailFormat{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) } @@ -1004,14 +1011,14 @@ func testFormBindingForTimeFailLocation(t *testing.T, method, path, badPath, bod obj := FooStructForTimeTypeFailLocation{} req := requestWithBody(method, path, body) if method == "POST" { - req.Header.Add("Content-Type", MIMEPOSTForm) + req.Header.Add("Content-Type", common.MIMEPOSTForm) } err := b.Bind(req, &obj) assert.Error(t, err) obj = FooStructForTimeTypeFailLocation{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) } @@ -1022,7 +1029,7 @@ func testFormBindingIgnoreField(t *testing.T, method, path, badPath, body, badBo obj := FooStructForIgnoreFormTag{} req := requestWithBody(method, path, body) if method == "POST" { - req.Header.Add("Content-Type", MIMEPOSTForm) + req.Header.Add("Content-Type", common.MIMEPOSTForm) } err := b.Bind(req, &obj) assert.NoError(t, err) @@ -1037,7 +1044,7 @@ func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBo obj := InvalidNameType{} req := requestWithBody(method, path, body) if method == "POST" { - req.Header.Add("Content-Type", MIMEPOSTForm) + req.Header.Add("Content-Type", common.MIMEPOSTForm) } err := b.Bind(req, &obj) assert.NoError(t, err) @@ -1045,7 +1052,7 @@ func testFormBindingInvalidName(t *testing.T, method, path, badPath, body, badBo obj = InvalidNameType{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) } @@ -1056,14 +1063,14 @@ func testFormBindingInvalidName2(t *testing.T, method, path, badPath, body, badB obj := InvalidNameMapType{} req := requestWithBody(method, path, body) if method == "POST" { - req.Header.Add("Content-Type", MIMEPOSTForm) + req.Header.Add("Content-Type", common.MIMEPOSTForm) } err := b.Bind(req, &obj) assert.Error(t, err) obj = InvalidNameMapType{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) } @@ -1073,7 +1080,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s req := requestWithBody(method, path, body) if method == "POST" { - req.Header.Add("Content-Type", MIMEPOSTForm) + req.Header.Add("Content-Type", common.MIMEPOSTForm) } switch typ { case "Int": @@ -1085,7 +1092,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj = FooBarStructForIntType{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) case "Int8": obj := FooBarStructForInt8Type{} @@ -1096,7 +1103,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj = FooBarStructForInt8Type{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) case "Int16": obj := FooBarStructForInt16Type{} @@ -1107,7 +1114,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj = FooBarStructForInt16Type{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) case "Int32": obj := FooBarStructForInt32Type{} @@ -1118,7 +1125,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj = FooBarStructForInt32Type{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) case "Int64": obj := FooBarStructForInt64Type{} @@ -1129,7 +1136,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj = FooBarStructForInt64Type{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) case "Uint": obj := FooBarStructForUintType{} @@ -1140,7 +1147,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj = FooBarStructForUintType{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) case "Uint8": obj := FooBarStructForUint8Type{} @@ -1151,7 +1158,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj = FooBarStructForUint8Type{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) case "Uint16": obj := FooBarStructForUint16Type{} @@ -1162,7 +1169,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj = FooBarStructForUint16Type{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) case "Uint32": obj := FooBarStructForUint32Type{} @@ -1173,7 +1180,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj = FooBarStructForUint32Type{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) case "Uint64": obj := FooBarStructForUint64Type{} @@ -1184,7 +1191,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj = FooBarStructForUint64Type{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) case "Float32": obj := FooBarStructForFloat32Type{} @@ -1195,7 +1202,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj = FooBarStructForFloat32Type{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) case "Float64": obj := FooBarStructForFloat64Type{} @@ -1206,7 +1213,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj = FooBarStructForFloat64Type{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) case "Bool": obj := FooBarStructForBoolType{} @@ -1217,7 +1224,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj = FooBarStructForBoolType{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) case "Slice": obj := FooStructForSliceType{} @@ -1227,7 +1234,7 @@ func testFormBindingForType(t *testing.T, method, path, badPath, body, badBody s obj = FooStructForSliceType{} req = requestWithBody(method, badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) assert.Error(t, err) case "Struct": obj := FooStructForStructType{} @@ -1291,7 +1298,7 @@ func testQueryBinding(t *testing.T, method, path, badPath, body, badBody string) obj := FooBarStruct{} req := requestWithBody(method, path, body) if method == "POST" { - req.Header.Add("Content-Type", MIMEPOSTForm) + req.Header.Add("Content-Type", common.MIMEPOSTForm) } err := b.Bind(req, &obj) assert.NoError(t, err) @@ -1306,7 +1313,7 @@ func testQueryBindingFail(t *testing.T, method, path, badPath, body, badBody str obj := FooStructForMapType{} req := requestWithBody(method, path, body) if method == "POST" { - req.Header.Add("Content-Type", MIMEPOSTForm) + req.Header.Add("Content-Type", common.MIMEPOSTForm) } err := b.Bind(req, &obj) assert.Error(t, err) @@ -1319,13 +1326,13 @@ func testQueryBindingBoolFail(t *testing.T, method, path, badPath, body, badBody obj := FooStructForBoolType{} req := requestWithBody(method, path, body) if method == "POST" { - req.Header.Add("Content-Type", MIMEPOSTForm) + req.Header.Add("Content-Type", common.MIMEPOSTForm) } err := b.Bind(req, &obj) 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()) obj := FooStruct{} @@ -1336,16 +1343,16 @@ func testBodyBinding(t *testing.T, b Binding, name, path, badPath, body, badBody obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) 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()) obj := FooStructUseNumber{} req := requestWithBody("POST", path, body) - EnableDecoderUseNumber = true + common.EnableDecoderUseNumber = true err := b.Bind(req, &obj) assert.NoError(t, err) // we hope it is int64(123) @@ -1355,16 +1362,16 @@ func testBodyBindingUseNumber(t *testing.T, b Binding, name, path, badPath, body obj = FooStructUseNumber{} req = requestWithBody("POST", badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) 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()) obj := FooStructUseNumber{} req := requestWithBody("POST", path, body) - EnableDecoderUseNumber = false + common.EnableDecoderUseNumber = false err := b.Bind(req, &obj) assert.NoError(t, err) // 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{} req = requestWithBody("POST", badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) 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()) obj := FooStruct{} @@ -1388,24 +1395,24 @@ func testBodyBindingFail(t *testing.T, b Binding, name, path, badPath, body, bad obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) - err = JSON.Bind(req, &obj) + err = JSON().Bind(req, &obj) 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()) obj := protoexample.Test{} req := requestWithBody("POST", path, body) - req.Header.Add("Content-Type", MIMEPROTOBUF) + req.Header.Add("Content-Type", common.MIMEPROTOBUF) err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, "yes", *obj.Label) obj = protoexample.Test{} req = requestWithBody("POST", badPath, badBody) - req.Header.Add("Content-Type", MIMEPROTOBUF) - err = ProtoBuf.Bind(req, &obj) + req.Header.Add("Content-Type", common.MIMEPROTOBUF) + err = ProtoBuf().Bind(req, &obj) assert.Error(t, err) } @@ -1415,38 +1422,38 @@ func (h hook) Read([]byte) (int, 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()) obj := protoexample.Test{} req := requestWithBody("POST", path, body) req.Body = ioutil.NopCloser(&hook{}) - req.Header.Add("Content-Type", MIMEPROTOBUF) + req.Header.Add("Content-Type", common.MIMEPROTOBUF) err := b.Bind(req, &obj) assert.Error(t, err) obj = protoexample.Test{} req = requestWithBody("POST", badPath, badBody) - req.Header.Add("Content-Type", MIMEPROTOBUF) - err = ProtoBuf.Bind(req, &obj) + req.Header.Add("Content-Type", common.MIMEPROTOBUF) + err = ProtoBuf().Bind(req, &obj) 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()) obj := FooStruct{} req := requestWithBody("POST", path, body) - req.Header.Add("Content-Type", MIMEMSGPACK) + req.Header.Add("Content-Type", common.MIMEMSGPACK) err := b.Bind(req, &obj) assert.NoError(t, err) assert.Equal(t, "bar", obj.Foo) obj = FooStruct{} req = requestWithBody("POST", badPath, badBody) - req.Header.Add("Content-Type", MIMEMSGPACK) - err = MsgPack.Bind(req, &obj) + req.Header.Add("Content-Type", common.MIMEMSGPACK) + err = MsgPack().Bind(req, &obj) assert.Error(t, err) } @@ -1466,7 +1473,7 @@ func TestCanSet(t *testing.T) { func formPostRequest(path, body string) *http.Request { req := requestWithBody("POST", path, body) - req.Header.Add("Content-Type", MIMEPOSTForm) + req.Header.Add("Content-Type", common.MIMEPOSTForm) return req } @@ -1548,3 +1555,14 @@ func TestBindingArray(t *testing.T) { err = Form.Bind(req, &s) 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") +} diff --git a/binding/common/common.go b/binding/common/common.go new file mode 100644 index 00000000..c7d984a6 --- /dev/null +++ b/binding/common/common.go @@ -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) +} diff --git a/binding/default_validator.go b/binding/common/default_validator.go similarity index 98% rename from binding/default_validator.go rename to binding/common/default_validator.go index e7a302de..7d3b0a2e 100644 --- a/binding/default_validator.go +++ b/binding/common/default_validator.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -package binding +package common import ( "reflect" diff --git a/binding/validate_test.go b/binding/common/validate_test.go similarity index 90% rename from binding/validate_test.go rename to binding/common/validate_test.go index 2c76b6d6..7d608ee1 100644 --- a/binding/validate_test.go +++ b/binding/common/validate_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -package binding +package common import ( "bytes" @@ -114,10 +114,10 @@ func TestValidateNoValidationValues(t *testing.T) { test := createNoValidationValues() empty := structNoValidationValues{} - assert.Nil(t, validate(test)) - assert.Nil(t, validate(&test)) - assert.Nil(t, validate(empty)) - assert.Nil(t, validate(&empty)) + assert.Nil(t, Validate(test)) + assert.Nil(t, Validate(&test)) + assert.Nil(t, Validate(empty)) + assert.Nil(t, Validate(&empty)) assert.Equal(t, origin, test) } @@ -162,10 +162,10 @@ func TestValidateNoValidationPointers(t *testing.T) { //test := createNoValidation_values() empty := structNoValidationPointer{} - //assert.Nil(t, validate(test)) - //assert.Nil(t, validate(&test)) - assert.Nil(t, validate(empty)) - assert.Nil(t, validate(&empty)) + //assert.Nil(t, Validate(test)) + //assert.Nil(t, Validate(&test)) + assert.Nil(t, Validate(empty)) + assert.Nil(t, Validate(&empty)) //assert.Equal(t, origin, test) } @@ -174,22 +174,22 @@ type Object map[string]interface{} func TestValidatePrimitives(t *testing.T) { 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) 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 - 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) 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) } @@ -227,7 +227,7 @@ func TestValidatorEngine(t *testing.T) { // Create an instance which will fail validation withOne := structCustomValidation{Integer: 1} - errs := validate(withOne) + errs := Validate(withOne) // Check that we got back non-nil errs assert.NotNil(t, errs) diff --git a/binding/form.go b/binding/form.go index 0b28aa8a..c9f675ee 100644 --- a/binding/form.go +++ b/binding/form.go @@ -8,6 +8,8 @@ import ( "mime/multipart" "net/http" "reflect" + + "github.com/gin-gonic/gin/binding/common" ) 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 { return err } - return validate(obj) + return common.Validate(obj) } 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 { return err } - return validate(obj) + return common.Validate(obj) } func (formMultipartBinding) Name() string { @@ -61,7 +63,7 @@ func (formMultipartBinding) Bind(req *http.Request, obj interface{}) error { return err } - return validate(obj) + return common.Validate(obj) } type multipartRequest http.Request diff --git a/binding/json.go b/binding/json/json.go similarity index 72% rename from binding/json.go rename to binding/json/json.go index f968161b..6e7441cc 100644 --- a/binding/json.go +++ b/binding/json/json.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -package binding +package json import ( "bytes" @@ -10,13 +10,13 @@ import ( "io" "net/http" + "github.com/gin-gonic/gin/binding/common" "github.com/gin-gonic/gin/internal/json" ) -// 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 +func init() { + common.List[common.MIMEJSON] = jsonBinding{} +} type jsonBinding struct{} @@ -37,11 +37,11 @@ func (jsonBinding) BindBody(body []byte, obj interface{}) error { func decodeJSON(r io.Reader, obj interface{}) error { decoder := json.NewDecoder(r) - if EnableDecoderUseNumber { + if common.EnableDecoderUseNumber { decoder.UseNumber() } if err := decoder.Decode(obj); err != nil { return err } - return validate(obj) + return common.Validate(obj) } diff --git a/binding/msgpack.go b/binding/msgpack/msgpack.go similarity index 76% rename from binding/msgpack.go rename to binding/msgpack/msgpack.go index b7f73197..c8bea55c 100644 --- a/binding/msgpack.go +++ b/binding/msgpack/msgpack.go @@ -2,16 +2,23 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -package binding +package msgpack import ( "bytes" "io" "net/http" + "github.com/gin-gonic/gin/binding/common" "github.com/ugorji/go/codec" ) +func init() { + msgPack := msgpackBinding{} + common.List[common.MIMEMSGPACK] = msgPack + common.List[common.MIMEMSGPACK2] = msgPack +} + type msgpackBinding struct{} 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 { return err } - return validate(obj) + return common.Validate(obj) } diff --git a/binding/protobuf.go b/binding/protobuf/protobuf.go similarity index 86% rename from binding/protobuf.go rename to binding/protobuf/protobuf.go index f9ece928..5162aeeb 100644 --- a/binding/protobuf.go +++ b/binding/protobuf/protobuf.go @@ -2,15 +2,20 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -package binding +package protobuf import ( "io/ioutil" "net/http" + "github.com/gin-gonic/gin/binding/common" "github.com/golang/protobuf/proto" ) +func init() { + common.List[common.MIMEPROTOBUF] = protobufBinding{} +} + type protobufBinding struct{} func (protobufBinding) Name() string { diff --git a/binding/query.go b/binding/query.go index 219743f2..23da5550 100644 --- a/binding/query.go +++ b/binding/query.go @@ -4,7 +4,11 @@ package binding -import "net/http" +import ( + "net/http" + + "github.com/gin-gonic/gin/binding/common" +) type queryBinding struct{} @@ -17,5 +21,5 @@ func (queryBinding) Bind(req *http.Request, obj interface{}) error { if err := mapForm(obj, values); err != nil { return err } - return validate(obj) + return common.Validate(obj) } diff --git a/binding/uri.go b/binding/uri.go index f91ec381..cd6e4448 100644 --- a/binding/uri.go +++ b/binding/uri.go @@ -4,6 +4,8 @@ package binding +import "github.com/gin-gonic/gin/binding/common" + type uriBinding struct{} 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 { return err } - return validate(obj) + return common.Validate(obj) } diff --git a/binding/xml.go b/binding/xml/xml.go similarity index 77% rename from binding/xml.go rename to binding/xml/xml.go index 4e901149..16a25636 100644 --- a/binding/xml.go +++ b/binding/xml/xml.go @@ -2,15 +2,23 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -package binding +package xml import ( "bytes" "encoding/xml" "io" "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{} func (xmlBinding) Name() string { @@ -29,5 +37,5 @@ func decodeXML(r io.Reader, obj interface{}) error { if err := decoder.Decode(obj); err != nil { return err } - return validate(obj) + return common.Validate(obj) } diff --git a/binding/yaml.go b/binding/yaml/yaml.go similarity index 81% rename from binding/yaml.go rename to binding/yaml/yaml.go index a2d36d6a..03b67545 100644 --- a/binding/yaml.go +++ b/binding/yaml/yaml.go @@ -2,16 +2,21 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -package binding +package yaml import ( "bytes" "io" "net/http" + "github.com/gin-gonic/gin/binding/common" "gopkg.in/yaml.v2" ) +func init() { + common.List[common.MIMEYAML] = yamlBinding{} +} + type yamlBinding struct{} func (yamlBinding) Name() string { @@ -31,5 +36,5 @@ func decodeYAML(r io.Reader, obj interface{}) error { if err := decoder.Decode(obj); err != nil { return err } - return validate(obj) + return common.Validate(obj) } diff --git a/context.go b/context.go index 5dc7f8a0..6c301d81 100644 --- a/context.go +++ b/context.go @@ -18,22 +18,16 @@ import ( "strings" "time" + commonB "github.com/gin-gonic/gin/binding/common" + "github.com/gin-gonic/gin/render/common" + "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/render" ) -// Content-Type MIME of the most common data formats. 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" ) const abortIndex int8 = math.MaxInt8 / 2 @@ -536,12 +530,12 @@ func (c *Context) Bind(obj interface{}) error { // BindJSON is a shortcut for c.MustBindWith(obj, binding.JSON). 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). 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). @@ -551,7 +545,7 @@ func (c *Context) BindQuery(obj interface{}) error { // BindYAML is a shortcut for c.MustBindWith(obj, binding.YAML). 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. @@ -567,7 +561,7 @@ func (c *Context) BindUri(obj interface{}) error { // MustBindWith binds the passed struct pointer using the specified binding engine. // It will abort the request with HTTP 400 if any error occurs. // 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 { c.AbortWithError(http.StatusBadRequest, err).SetType(ErrorTypeBind) // nolint: errcheck return err @@ -590,12 +584,12 @@ func (c *Context) ShouldBind(obj interface{}) error { // ShouldBindJSON is a shortcut for c.ShouldBindWith(obj, binding.JSON). 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). 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). @@ -605,7 +599,7 @@ func (c *Context) ShouldBindQuery(obj interface{}) error { // ShouldBindYAML is a shortcut for c.ShouldBindWith(obj, binding.YAML). 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. @@ -619,7 +613,7 @@ func (c *Context) ShouldBindUri(obj interface{}) error { // ShouldBindWith binds the passed struct pointer using the specified binding engine. // 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) } @@ -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 // 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 if cb, ok := c.Get(BodyBytesKey); 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. -func (c *Context) Render(code int, r render.Render) { +func (c *Context) Render(code int, r common.Render) { c.Status(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 // more CPU and bandwidth consuming. Use Context.JSON() instead. 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. // Default prepends "while(1)," to response body if the given struct is array values. // It also sets the Content-Type as "application/json". 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. @@ -810,38 +804,43 @@ func (c *Context) SecureJSON(code int, obj interface{}) { func (c *Context) JSONP(code int, obj interface{}) { callback := c.DefaultQuery("callback", "") if callback == "" { - c.Render(code, render.JSON{Data: obj}) + c.Render(code, render.JSON(obj)) 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. // It also sets the Content-Type as "application/json". 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. // It also sets the Content-Type as "application/json". 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. // It also sets the Content-Type as "application/xml". 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. 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. 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. @@ -932,15 +931,15 @@ type Negotiate struct { // Negotiate calls different Render according acceptable Accept format. func (c *Context) Negotiate(code int, config Negotiate) { switch c.NegotiateFormat(config.Offered...) { - case binding.MIMEJSON: + case commonB.MIMEJSON: data := chooseData(config.JSONData, config.Data) c.JSON(code, data) - case binding.MIMEHTML: + case commonB.MIMEHTML: data := chooseData(config.HTMLData, config.Data) c.HTML(code, config.HTMLName, data) - case binding.MIMEXML: + case commonB.MIMEXML: data := chooseData(config.XMLData, config.Data) c.XML(code, data) diff --git a/context_17.go b/context_17.go index 8e9f75ad..6eb1fb0a 100644 --- a/context_17.go +++ b/context_17.go @@ -13,5 +13,5 @@ import ( // PureJSON serializes the given struct as JSON into the response body. // PureJSON, unlike JSON, does not replace special html characters with their unicode entities. func (c *Context) PureJSON(code int, obj interface{}) { - c.Render(code, render.PureJSON{Data: obj}) + c.Render(code, render.PureJSON(obj)) } diff --git a/context_test.go b/context_test.go index 0da5fbe6..a1b10615 100644 --- a/context_test.go +++ b/context_test.go @@ -20,11 +20,23 @@ import ( "github.com/gin-contrib/sse" "github.com/gin-gonic/gin/binding" + commonB "github.com/gin-gonic/gin/binding/common" "github.com/golang/protobuf/proto" "github.com/stretchr/testify/assert" + "github.com/ugorji/go/codec" "golang.org/x/net/context" 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{} @@ -55,7 +67,7 @@ func createMultipartRequest() *http.Request { must(mw.WriteField("names[b]", "tianou")) req, err := http.NewRequest("POST", "/", body) must(err) - req.Header.Set("Content-Type", MIMEMultipartPOSTForm+"; boundary="+boundary) + req.Header.Set("Content-Type", commonB.MIMEMultipartPOSTForm+"; boundary="+boundary) return req } @@ -413,7 +425,7 @@ func TestContextQueryAndPostForm(t *testing.T) { body := bytes.NewBufferString("foo=bar&page=11&both=&foo=second") c.Request, _ = http.NewRequest("POST", "/?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.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")) } +// 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 // and Content-Type is set to application/x-protobuf // 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.Negotiate(http.StatusOK, Negotiate{ - Offered: []string{MIMEJSON, MIMEXML}, + Offered: []string{commonB.MIMEJSON, commonB.MIMEXML}, Data: H{"foo": "bar"}, }) @@ -1118,7 +1156,7 @@ func TestContextNegotiationWithXML(t *testing.T) { c.Request, _ = http.NewRequest("POST", "", nil) c.Negotiate(http.StatusOK, Negotiate{ - Offered: []string{MIMEXML, MIMEJSON}, + Offered: []string{commonB.MIMEXML, commonB.MIMEJSON}, Data: H{"foo": "bar"}, }) @@ -1135,7 +1173,7 @@ func TestContextNegotiationWithHTML(t *testing.T) { router.SetHTMLTemplate(templ) c.Negotiate(http.StatusOK, Negotiate{ - Offered: []string{MIMEHTML}, + Offered: []string{commonB.MIMEHTML}, Data: H{"name": "gin"}, HTMLName: "t", }) @@ -1151,7 +1189,7 @@ func TestContextNegotiationNotSupport(t *testing.T) { c.Request, _ = http.NewRequest("POST", "", nil) c.Negotiate(http.StatusOK, Negotiate{ - Offered: []string{MIMEPOSTForm}, + Offered: []string{commonB.MIMEPOSTForm}, }) assert.Equal(t, http.StatusNotAcceptable, w.Code) @@ -1164,8 +1202,8 @@ func TestContextNegotiationFormat(t *testing.T) { c.Request, _ = http.NewRequest("POST", "", nil) assert.Panics(t, func() { c.NegotiateFormat() }) - assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) - assert.Equal(t, MIMEHTML, c.NegotiateFormat(MIMEHTML, MIMEJSON)) + assert.Equal(t, commonB.MIMEJSON, c.NegotiateFormat(commonB.MIMEJSON, commonB.MIMEXML)) + assert.Equal(t, commonB.MIMEHTML, c.NegotiateFormat(commonB.MIMEHTML, commonB.MIMEJSON)) } func TestContextNegotiationFormatWithAccept(t *testing.T) { @@ -1173,9 +1211,9 @@ func TestContextNegotiationFormatWithAccept(t *testing.T) { c.Request, _ = http.NewRequest("POST", "/", nil) 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, MIMEHTML, c.NegotiateFormat(MIMEXML, MIMEHTML)) - assert.Empty(t, c.NegotiateFormat(MIMEJSON)) + assert.Equal(t, commonB.MIMEXML, c.NegotiateFormat(commonB.MIMEJSON, commonB.MIMEXML)) + assert.Equal(t, commonB.MIMEHTML, c.NegotiateFormat(commonB.MIMEXML, commonB.MIMEHTML)) + assert.Empty(t, c.NegotiateFormat(commonB.MIMEJSON)) } func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) { @@ -1186,9 +1224,9 @@ func TestContextNegotiationFormatWithWildcardAccept(t *testing.T) { assert.Equal(t, c.NegotiateFormat("*/*"), "*/*") assert.Equal(t, c.NegotiateFormat("text/*"), "text/*") assert.Equal(t, c.NegotiateFormat("application/*"), "application/*") - assert.Equal(t, c.NegotiateFormat(MIMEJSON), MIMEJSON) - assert.Equal(t, c.NegotiateFormat(MIMEXML), MIMEXML) - assert.Equal(t, c.NegotiateFormat(MIMEHTML), MIMEHTML) + assert.Equal(t, c.NegotiateFormat(commonB.MIMEJSON), commonB.MIMEJSON) + assert.Equal(t, c.NegotiateFormat(commonB.MIMEXML), commonB.MIMEXML) + assert.Equal(t, c.NegotiateFormat(commonB.MIMEHTML), commonB.MIMEHTML) c, _ = CreateTestContext(httptest.NewRecorder()) 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("text/*"), "text/*") assert.Equal(t, c.NegotiateFormat("application/*"), "") - assert.Equal(t, c.NegotiateFormat(MIMEJSON), "") - assert.Equal(t, c.NegotiateFormat(MIMEXML), "") - assert.Equal(t, c.NegotiateFormat(MIMEHTML), MIMEHTML) + assert.Equal(t, c.NegotiateFormat(commonB.MIMEJSON), "") + assert.Equal(t, c.NegotiateFormat(commonB.MIMEXML), "") + assert.Equal(t, c.NegotiateFormat(commonB.MIMEHTML), commonB.MIMEHTML) } 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.Accepted = nil - c.SetAccepted(MIMEJSON, MIMEXML) + c.SetAccepted(commonB.MIMEJSON, commonB.MIMEXML) - assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON, MIMEXML)) - assert.Equal(t, MIMEXML, c.NegotiateFormat(MIMEXML, MIMEHTML)) - assert.Equal(t, MIMEJSON, c.NegotiateFormat(MIMEJSON)) + assert.Equal(t, commonB.MIMEJSON, c.NegotiateFormat(commonB.MIMEJSON, commonB.MIMEXML)) + assert.Equal(t, commonB.MIMEXML, c.NegotiateFormat(commonB.MIMEXML, commonB.MIMEHTML)) + assert.Equal(t, commonB.MIMEJSON, c.NegotiateFormat(commonB.MIMEJSON)) } func TestContextIsAborted(t *testing.T) { @@ -1376,7 +1414,7 @@ func TestContextContentType(t *testing.T) { func TestContextAutoBindJSON(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) 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 { Foo string `json:"foo"` @@ -1393,7 +1431,7 @@ func TestContextBindWithJSON(t *testing.T) { c, _ := CreateTestContext(w) 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 { Foo string `json:"foo"` @@ -1413,7 +1451,7 @@ func TestContextBindWithXML(t *testing.T) { FOO BAR `)) - 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 { Foo string `xml:"foo"` @@ -1446,7 +1484,7 @@ func TestContextBindWithYAML(t *testing.T) { c, _ := CreateTestContext(w) 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 { Foo string `yaml:"foo"` @@ -1463,7 +1501,7 @@ func TestContextBadAutoBind(t *testing.T) { c, _ := CreateTestContext(w) 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 { Foo string `json:"foo"` Bar string `json:"bar"` @@ -1482,7 +1520,7 @@ func TestContextBadAutoBind(t *testing.T) { func TestContextAutoShouldBindJSON(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) 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 { Foo string `json:"foo"` @@ -1499,7 +1537,7 @@ func TestContextShouldBindWithJSON(t *testing.T) { c, _ := CreateTestContext(w) 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 { Foo string `json:"foo"` @@ -1520,7 +1558,7 @@ func TestContextShouldBindWithXML(t *testing.T) { FOO BAR `)) - 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 { Foo string `xml:"foo"` @@ -1557,7 +1595,7 @@ func TestContextShouldBindWithYAML(t *testing.T) { c, _ := CreateTestContext(w) 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 { Foo string `yaml:"foo"` @@ -1574,7 +1612,7 @@ func TestContextBadAutoShouldBind(t *testing.T) { c, _ := CreateTestContext(w) 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 { Foo string `json:"foo"` Bar string `json:"bar"` @@ -1597,20 +1635,20 @@ func TestContextShouldBindBodyWith(t *testing.T) { } for _, tt := range []struct { name string - bindingA, bindingB binding.BindingBody + bindingA, bindingB commonB.BindingBody bodyA, bodyB string }{ { name: "JSON & JSON", - bindingA: binding.JSON, - bindingB: binding.JSON, + bindingA: binding.JSON(), + bindingB: binding.JSON(), bodyA: `{"foo":"FOO"}`, bodyB: `{"bar":"BAR"}`, }, { name: "JSON & XML", - bindingA: binding.JSON, - bindingB: binding.XML, + bindingA: binding.JSON(), + bindingB: binding.XML(), bodyA: `{"foo":"FOO"}`, bodyB: ` @@ -1619,8 +1657,8 @@ func TestContextShouldBindBodyWith(t *testing.T) { }, { name: "XML & XML", - bindingA: binding.XML, - bindingB: binding.XML, + bindingA: binding.XML(), + bindingB: binding.XML(), bodyA: ` FOO @@ -1718,7 +1756,7 @@ func TestContextGetRawData(t *testing.T) { c, _ := CreateTestContext(httptest.NewRecorder()) body := bytes.NewBufferString("Fetch binary post data") 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() assert.Nil(t, err) diff --git a/deprecated.go b/deprecated.go index ab447429..e2978d24 100644 --- a/deprecated.go +++ b/deprecated.go @@ -7,12 +7,12 @@ package gin import ( "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. // 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 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 diff --git a/gin.go b/gin.go index 2d24092f..2a8fa964 100644 --- a/gin.go +++ b/gin.go @@ -13,6 +13,8 @@ import ( "path" "sync" + "github.com/gin-gonic/gin/binding/common" + "github.com/gin-gonic/gin/render" ) @@ -417,7 +419,7 @@ func (engine *Engine) handleHTTPRequest(c *Context) { serveError(c, http.StatusNotFound, default404Body) } -var mimePlain = []string{MIMEPlain} +var mimePlain = []string{common.MIMEPlain} func serveError(c *Context, code int, defaultMessage []byte) { c.writermem.status = code diff --git a/mode.go b/mode.go index 8aa84aa8..af87284a 100644 --- a/mode.go +++ b/mode.go @@ -8,7 +8,7 @@ import ( "io" "os" - "github.com/gin-gonic/gin/binding" + "github.com/gin-gonic/gin/binding/common" ) // EnvGinMode indicates environment name for gin mode. @@ -68,13 +68,13 @@ func SetMode(value string) { // DisableBindValidation closes the default validator. func DisableBindValidation() { - binding.Validator = nil + common.Validator = nil } // EnableJsonDecoderUseNumber sets true for binding.EnableDecoderUseNumberto to // call the UseNumber method on the JSON Decoder instance. func EnableJsonDecoderUseNumber() { - binding.EnableDecoderUseNumber = true + common.EnableDecoderUseNumber = true } // Mode returns currently gin mode. diff --git a/mode_test.go b/mode_test.go index 3dba5150..05ab2a3e 100644 --- a/mode_test.go +++ b/mode_test.go @@ -8,7 +8,7 @@ import ( "os" "testing" - "github.com/gin-gonic/gin/binding" + "github.com/gin-gonic/gin/binding/common" "github.com/stretchr/testify/assert" ) @@ -41,7 +41,15 @@ func TestSetMode(t *testing.T) { } func TestEnableJsonDecoderUseNumber(t *testing.T) { - assert.False(t, binding.EnableDecoderUseNumber) + assert.False(t, common.EnableDecoderUseNumber) 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 } diff --git a/render/common/common.go b/render/common/common.go new file mode 100644 index 00000000..cfcc2079 --- /dev/null +++ b/render/common/common.go @@ -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{} diff --git a/render/data.go b/render/data.go index 6ba657ba..8b5d9cf9 100644 --- a/render/data.go +++ b/render/data.go @@ -4,7 +4,11 @@ package render -import "net/http" +import ( + "net/http" + + "github.com/gin-gonic/gin/render/common" +) // Data contains ContentType and bytes data. type Data struct { @@ -21,5 +25,5 @@ func (r Data) Render(w http.ResponseWriter) (err error) { // WriteContentType (Data) writes custom ContentType. func (r Data) WriteContentType(w http.ResponseWriter) { - writeContentType(w, []string{r.ContentType}) + common.WriteContentType(w, []string{r.ContentType}) } diff --git a/render/html.go b/render/html.go index 6696ece9..163eff0a 100644 --- a/render/html.go +++ b/render/html.go @@ -7,6 +7,8 @@ package render import ( "html/template" "net/http" + + "github.com/gin-gonic/gin/render/common" ) // 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. type HTMLRender interface { // Instance returns an HTML instance. - Instance(string, interface{}) Render + Instance(string, interface{}) HTML } // HTMLProduction contains template reference and its delims. @@ -47,7 +49,7 @@ type HTML struct { var htmlContentType = []string{"text/html; charset=utf-8"} // 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{ Template: r.Template, 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. -func (r HTMLDebug) Instance(name string, data interface{}) Render { +func (r HTMLDebug) Instance(name string, data interface{}) HTML { return HTML{ Template: r.loadTemplate(), Name: name, @@ -88,5 +90,5 @@ func (r HTML) Render(w http.ResponseWriter) error { // WriteContentType (HTML) writes HTML ContentType. func (r HTML) WriteContentType(w http.ResponseWriter) { - writeContentType(w, htmlContentType) + common.WriteContentType(w, htmlContentType) } diff --git a/render/json.go b/render/json/json.go similarity index 75% rename from render/json.go rename to render/json/json.go index c7cf330e..11e72e64 100644 --- a/render/json.go +++ b/render/json/json.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -package render +package json import ( "bytes" @@ -11,8 +11,42 @@ import ( "net/http" "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. type JSON struct { Data interface{} @@ -57,12 +91,12 @@ func (r JSON) Render(w http.ResponseWriter) (err error) { // WriteContentType (JSON) writes JSON ContentType. 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. func WriteJSON(w http.ResponseWriter, obj interface{}) error { - writeContentType(w, jsonContentType) + common.WriteContentType(w, jsonContentType) jsonBytes, err := json.Marshal(obj) if err != nil { return err @@ -84,7 +118,7 @@ func (r IndentedJSON) Render(w http.ResponseWriter) error { // WriteContentType (IndentedJSON) writes JSON ContentType. 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. @@ -107,7 +141,7 @@ func (r SecureJSON) Render(w http.ResponseWriter) error { // WriteContentType (SecureJSON) writes JSON ContentType. 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. @@ -146,7 +180,7 @@ func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { // WriteContentType (JsonpJSON) writes Javascript ContentType. 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. @@ -172,5 +206,5 @@ func (r AsciiJSON) Render(w http.ResponseWriter) (err error) { // WriteContentType (AsciiJSON) writes JSON ContentType. func (r AsciiJSON) WriteContentType(w http.ResponseWriter) { - writeContentType(w, jsonAsciiContentType) + common.WriteContentType(w, jsonAsciiContentType) } diff --git a/render/json_17.go b/render/json/json_17.go similarity index 70% rename from render/json_17.go rename to render/json/json_17.go index 208193c7..6f387e7a 100644 --- a/render/json_17.go +++ b/render/json/json_17.go @@ -4,14 +4,24 @@ // +build go1.7 -package render +package json import ( "net/http" "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. type PureJSON struct { Data interface{} @@ -27,5 +37,5 @@ func (r PureJSON) Render(w http.ResponseWriter) error { // WriteContentType (PureJSON) writes custom ContentType. func (r PureJSON) WriteContentType(w http.ResponseWriter) { - writeContentType(w, jsonContentType) + common.WriteContentType(w, jsonContentType) } diff --git a/render/msgpack.go b/render/msgpack/msgpack.go similarity index 72% rename from render/msgpack.go rename to render/msgpack/msgpack.go index dc681fcf..5b390b85 100644 --- a/render/msgpack.go +++ b/render/msgpack/msgpack.go @@ -2,14 +2,19 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -package render +package msgpack import ( "net/http" + "github.com/gin-gonic/gin/render/common" "github.com/ugorji/go/codec" ) +func init() { + common.List["MsgPack"] = NewMsgPack +} + // MsgPack contains the given interface object. type MsgPack struct { Data interface{} @@ -19,7 +24,7 @@ var msgpackContentType = []string{"application/msgpack; charset=utf-8"} // WriteContentType (MsgPack) writes MsgPack ContentType. 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. @@ -29,7 +34,12 @@ func (r MsgPack) Render(w http.ResponseWriter) error { // WriteMsgPack writes MsgPack ContentType and encodes the given interface object. func WriteMsgPack(w http.ResponseWriter, obj interface{}) error { - writeContentType(w, msgpackContentType) + common.WriteContentType(w, msgpackContentType) var mh codec.MsgpackHandle 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} +} diff --git a/render/protobuf.go b/render/protobuf/protobuf.go similarity index 72% rename from render/protobuf.go rename to render/protobuf/protobuf.go index 15aca995..9a2b9f1a 100644 --- a/render/protobuf.go +++ b/render/protobuf/protobuf.go @@ -2,14 +2,19 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -package render +package protobuf import ( "net/http" + "github.com/gin-gonic/gin/render/common" "github.com/golang/protobuf/proto" ) +func init() { + common.List["ProtoBuf"] = NewProtoBuf +} + // ProtoBuf contains the given interface object. type ProtoBuf struct { Data interface{} @@ -32,5 +37,10 @@ func (r ProtoBuf) Render(w http.ResponseWriter) error { // WriteContentType (ProtoBuf) writes ProtoBuf ContentType. 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} } diff --git a/render/reader.go b/render/reader.go index 312af741..80b2485c 100644 --- a/render/reader.go +++ b/render/reader.go @@ -8,6 +8,8 @@ import ( "io" "net/http" "strconv" + + "github.com/gin-gonic/gin/render/common" ) // 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. func (r Reader) WriteContentType(w http.ResponseWriter) { - writeContentType(w, []string{r.ContentType}) + common.WriteContentType(w, []string{r.ContentType}) } // writeHeaders writes custom Header. diff --git a/render/render.go b/render/render.go index abfc79fc..27d2a6b9 100644 --- a/render/render.go +++ b/render/render.go @@ -4,38 +4,76 @@ package render -import "net/http" +import ( + "fmt" -// 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) -} - -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{} + "github.com/gin-gonic/gin/render/common" ) -func writeContentType(w http.ResponseWriter, value []string) { - header := w.Header() - if val := header["Content-Type"]; len(val) == 0 { - header["Content-Type"] = value - } +var ( + _ common.Render = String{} + _ common.Render = Redirect{} + _ 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) } diff --git a/render/render_17.go b/render/render_17.go new file mode 100644 index 00000000..544c16b3 --- /dev/null +++ b/render/render_17.go @@ -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) +} diff --git a/render/render_17_test.go b/render/render_17_test.go index 68330090..2b026ee6 100644 --- a/render/render_17_test.go +++ b/render/render_17_test.go @@ -19,7 +19,7 @@ func TestRenderPureJSON(t *testing.T) { "foo": "bar", "html": "", } - err := (PureJSON{data}).Render(w) + err := PureJSON(data).Render(w) assert.NoError(t, err) assert.Equal(t, "{\"foo\":\"bar\",\"html\":\"\"}\n", w.Body.String()) assert.Equal(t, "application/json; charset=utf-8", w.Header().Get("Content-Type")) diff --git a/render/render_test.go b/render/render_test.go index 76e29eeb..be65682d 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -20,6 +20,12 @@ import ( "github.com/ugorji/go/codec" 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 @@ -31,10 +37,10 @@ func TestRenderMsgPack(t *testing.T) { "foo": "bar", } - (MsgPack{data}).WriteContentType(w) + MsgPack(data).WriteContentType(w) 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) @@ -56,10 +62,10 @@ func TestRenderJSON(t *testing.T) { "html": "", } - (JSON{data}).WriteContentType(w) + JSON(data).WriteContentType(w) 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.Equal(t, "{\"foo\":\"bar\",\"html\":\"\\u003cb\\u003e\"}", w.Body.String()) @@ -71,7 +77,7 @@ func TestRenderJSONPanics(t *testing.T) { data := make(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) { @@ -81,7 +87,7 @@ func TestRenderIndentedJSON(t *testing.T) { "bar": "foo", } - err := (IndentedJSON{data}).Render(w) + err := IndentedJSON(data).Render(w) assert.NoError(t, err) 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) // json: unsupported type: chan int - err := (IndentedJSON{data}).Render(w) + err := IndentedJSON(data).Render(w) assert.Error(t, err) } @@ -103,10 +109,10 @@ func TestRenderSecureJSON(t *testing.T) { "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")) - err1 := (SecureJSON{"while(1);", data}).Render(w1) + err1 := SecureJSON("while(1);", data).Render(w1) assert.NoError(t, err1) assert.Equal(t, "{\"foo\":\"bar\"}", w1.Body.String()) @@ -119,7 +125,7 @@ func TestRenderSecureJSON(t *testing.T) { "bar": "foo", }} - err2 := (SecureJSON{"while(1);", datas}).Render(w2) + err2 := SecureJSON("while(1);", datas).Render(w2) assert.NoError(t, err2) 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")) @@ -130,7 +136,7 @@ func TestRenderSecureJSONFail(t *testing.T) { data := make(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) } @@ -140,10 +146,10 @@ func TestRenderJsonpJSON(t *testing.T) { "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")) - err1 := (JsonpJSON{"x", data}).Render(w1) + err1 := JsonpJSON("x", data).Render(w1) assert.NoError(t, err1) assert.Equal(t, "x({\"foo\":\"bar\"})", w1.Body.String()) @@ -156,7 +162,7 @@ func TestRenderJsonpJSON(t *testing.T) { "bar": "foo", }} - err2 := (JsonpJSON{"x", datas}).Render(w2) + err2 := JsonpJSON("x", datas).Render(w2) assert.NoError(t, err2) assert.Equal(t, "x([{\"foo\":\"bar\"},{\"bar\":\"foo\"}])", w2.Body.String()) 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{}{ "foo": "bar", } - (JsonpJSON{"", data}).WriteContentType(w) + JsonpJSON("", data).WriteContentType(w) 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.Equal(t, "{\"foo\":\"bar\"}", w.Body.String()) @@ -182,7 +188,7 @@ func TestRenderJsonpJSONFail(t *testing.T) { data := make(chan int) // json: unsupported type: chan int - err := (JsonpJSON{"x", data}).Render(w) + err := JsonpJSON("x", data).Render(w) assert.Error(t, err) } @@ -193,7 +199,7 @@ func TestRenderAsciiJSON(t *testing.T) { "tag": "
", } - err := (AsciiJSON{data1}).Render(w1) + err := AsciiJSON(data1).Render(w1) assert.NoError(t, err) 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() data2 := float64(3.1415926) - err = (AsciiJSON{data2}).Render(w2) + err = AsciiJSON(data2).Render(w2) assert.NoError(t, err) assert.Equal(t, "3.1415926", w2.Body.String()) } @@ -212,7 +218,7 @@ func TestRenderAsciiJSONFail(t *testing.T) { data := make(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{} @@ -247,10 +253,10 @@ b: c: 2 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")) - err := (YAML{data}).Render(w) + err := YAML(data).Render(w) 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, "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) { w := httptest.NewRecorder() - err := (YAML{&fail{}}).Render(w) + err := YAML(&fail{}).Render(w) assert.Error(t, err) } @@ -279,12 +285,12 @@ func TestRenderProtoBuf(t *testing.T) { Reps: reps, } - (ProtoBuf{data}).WriteContentType(w) + ProtoBuf(data).WriteContentType(w) protoData, err := proto.Marshal(data) assert.NoError(t, err) 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.Equal(t, string(protoData), w.Body.String()) @@ -294,7 +300,7 @@ func TestRenderProtoBuf(t *testing.T) { func TestRenderProtoBufFail(t *testing.T) { w := httptest.NewRecorder() data := &testdata.Test{} - err := (ProtoBuf{data}).Render(w) + err := ProtoBuf(data).Render(w) assert.Error(t, err) } @@ -304,10 +310,10 @@ func TestRenderXML(t *testing.T) { "foo": "bar", } - (XML{data}).WriteContentType(w) + XML(data).WriteContentType(w) 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.Equal(t, "bar", 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["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) +} diff --git a/render/text.go b/render/text.go index 4e52d4c5..cd42e388 100644 --- a/render/text.go +++ b/render/text.go @@ -8,6 +8,8 @@ import ( "fmt" "io" "net/http" + + "github.com/gin-gonic/gin/render/common" ) // 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. 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. func WriteString(w http.ResponseWriter, format string, data []interface{}) (err error) { - writeContentType(w, plainContentType) + common.WriteContentType(w, plainContentType) if len(data) > 0 { _, err = fmt.Fprintf(w, format, data...) return diff --git a/render/xml.go b/render/xml/xml.go similarity index 71% rename from render/xml.go rename to render/xml/xml.go index cc5390a2..637604b6 100644 --- a/render/xml.go +++ b/render/xml/xml.go @@ -2,13 +2,19 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -package render +package xml import ( "encoding/xml" "net/http" + + "github.com/gin-gonic/gin/render/common" ) +func init() { + common.List["XML"] = NewXML +} + // XML contains the given interface object. type XML struct { Data interface{} @@ -24,5 +30,10 @@ func (r XML) Render(w http.ResponseWriter) error { // WriteContentType (XML) writes XML ContentType for response. 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} } diff --git a/render/yaml.go b/render/yaml/yaml.go similarity index 73% rename from render/yaml.go rename to render/yaml/yaml.go index 0df78360..58a6bbe4 100644 --- a/render/yaml.go +++ b/render/yaml/yaml.go @@ -2,14 +2,19 @@ // Use of this source code is governed by a MIT style // license that can be found in the LICENSE file. -package render +package yaml import ( "net/http" + "github.com/gin-gonic/gin/render/common" "gopkg.in/yaml.v2" ) +func init() { + common.List["YAML"] = NewYAML +} + // YAML contains the given interface object. type YAML struct { Data interface{} @@ -32,5 +37,10 @@ func (r YAML) Render(w http.ResponseWriter) error { // WriteContentType (YAML) writes YAML ContentType for response. 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} }