From a000da35c390b9a22a72152ff0c26fe58df293b0 Mon Sep 17 00:00:00 2001 From: Gerasimos Maropoulos Date: Thu, 6 Oct 2016 23:19:55 +0300 Subject: [PATCH] Update to 4.4.8 - Custom decoders for ReadJSON/ReadXML, read HISTORY.md Read: https://github.com/kataras/iris/blob/master/HISTORY.md --- HISTORY.md | 28 +++++++++++++++++++++ README.md | 6 ++--- context.go | 62 ++++++++++++++++++++++++++++++++++------------ context_test.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++++ iris.go | 2 +- 5 files changed, 144 insertions(+), 19 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index eecf76e3..0b071f20 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,6 +2,34 @@ **How to upgrade**: remove your `$GOPATH/src/github.com/kataras` folder, open your command-line and execute this command: `go get -u github.com/kataras/iris/iris`. +## 4.4.7 -> 4.4.8 + +- **NEW**: `RequestDecoder` gives the ability to set a custom decoder **per passed object** when `context.ReadJSON` and `context.ReadXML` + +```go +// BodyDecoder is an interface which any struct can implement in order to customize the decode action +// from ReadJSON and ReadXML +// +// Trivial example of this could be: +// type User struct { Username string } +// +// func (u *User) Decode(data []byte) error { +// return json.Unmarshal(data, u) +// } +// +// the 'context.ReadJSON/ReadXML(&User{})' will call the User's +// Decode option to decode the request body +// +// Note: This is totally optionally, the default decoders +// for ReadJSON is the encoding/json and for ReadXML is the encoding/xml +type BodyDecoder interface { + Decode(data []byte) error +} + +``` + +> for a usage example go to https://github.com/kataras/iris/blob/master/context_test.go#L262 + ## 4.4.6 -> 4.4.7 - **small fix**: websocket server is nil when more than the default websocket station tries to be registered before `OnConnection` called[*](https://github.com/kataras/iris/issues/460) diff --git a/README.md b/README.md index b2b34f3c..d796f206 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@
-Releases +Releases Examples @@ -870,7 +870,7 @@ I recommend writing your API tests using this new library, [httpexpect](https:// Versioning ------------ -Current: **v4.4.7** +Current: **v4.4.8** > Iris is an active project @@ -906,7 +906,7 @@ This project is licensed under the [MIT License](LICENSE), Copyright (c) 2016 Ge [Travis]: http://travis-ci.org/kataras/iris [License Widget]: https://img.shields.io/badge/license-MIT%20%20License%20-E91E63.svg?style=flat-square [License]: https://github.com/kataras/iris/blob/master/LICENSE -[Release Widget]: https://img.shields.io/badge/release-4.4.7%20-blue.svg?style=flat-square +[Release Widget]: https://img.shields.io/badge/release-4.4.8%20-blue.svg?style=flat-square [Release]: https://github.com/kataras/iris/releases [Chat Widget]: https://img.shields.io/badge/community-chat%20-00BCD4.svg?style=flat-square [Chat]: https://kataras.rocket.chat/channel/iris diff --git a/context.go b/context.go index 1f7fdbda..e56ad261 100644 --- a/context.go +++ b/context.go @@ -371,33 +371,65 @@ func (ctx *Context) Subdomain() (subdomain string) { return } +// BodyDecoder is an interface which any struct can implement in order to customize the decode action +// from ReadJSON and ReadXML +// +// Trivial example of this could be: +// type User struct { Username string } +// +// func (u *User) Decode(data []byte) error { +// return json.Unmarshal(data, u) +// } +// +// the 'context.ReadJSON/ReadXML(&User{})' will call the User's +// Decode option to decode the request body +// +// Note: This is totally optionally, the default decoders +// for ReadJSON is the encoding/json and for ReadXML is the encoding/xml +type BodyDecoder interface { + Decode(data []byte) error +} + // ReadJSON reads JSON from request's body func (ctx *Context) ReadJSON(jsonObject interface{}) error { - data := ctx.RequestCtx.Request.Body() + rawData := ctx.Request.Body() - decoder := json.NewDecoder(strings.NewReader(string(data))) - err := decoder.Decode(jsonObject) - - //err != nil fix by @shiena - if err != nil && err != io.EOF { - return errReadBody.Format("JSON", err.Error()) + // check if the jsonObject contains its own decode + // in this case the jsonObject should be a pointer also, + // but this is up to the user's custom Decode implementation* + // + // See 'BodyDecoder' for more + if decoder, isDecoder := jsonObject.(BodyDecoder); isDecoder { + return decoder.Decode(rawData) } - return nil + // check if jsonObject is already a pointer, if yes then pass as it's + if reflect.TypeOf(jsonObject).Kind() == reflect.Ptr { + return json.Unmarshal(rawData, jsonObject) + } + // finally, if the jsonObject doesn't contains a self-body decoder and it's not a pointer + return json.Unmarshal(rawData, &jsonObject) } // ReadXML reads XML from request's body func (ctx *Context) ReadXML(xmlObject interface{}) error { - data := ctx.RequestCtx.Request.Body() + rawData := ctx.Request.Body() - decoder := xml.NewDecoder(strings.NewReader(string(data))) - err := decoder.Decode(xmlObject) - //err != nil fix by @shiena - if err != nil && err != io.EOF { - return errReadBody.Format("XML", err.Error()) + // check if the xmlObject contains its own decode + // in this case the jsonObject should be a pointer also, + // but this is up to the user's custom Decode implementation* + // + // See 'BodyDecoder' for more + if decoder, isDecoder := xmlObject.(BodyDecoder); isDecoder { + return decoder.Decode(rawData) } - return nil + // check if xmlObject is already a pointer, if yes then pass as it's + if reflect.TypeOf(xmlObject).Kind() == reflect.Ptr { + return xml.Unmarshal(rawData, xmlObject) + } + // finally, if the xmlObject doesn't contains a self-body decoder and it's not a pointer + return xml.Unmarshal(rawData, &xmlObject) } // ReadForm binds the formObject with the form data diff --git a/context_test.go b/context_test.go index 96354959..b27a7265 100644 --- a/context_test.go +++ b/context_test.go @@ -11,7 +11,9 @@ CONTRIBUTE & DISCUSSION ABOUT TESTS TO: https://github.com/iris-contrib/tests */ import ( + "encoding/json" "encoding/xml" + "fmt" "net/url" "strconv" "strings" @@ -240,13 +242,76 @@ func TestContextReadJSON(t *testing.T) { ctx.JSON(StatusOK, obj) }) + Post("/json_pointer", func(ctx *Context) { + obj := &testBinderData{} + err := ctx.ReadJSON(obj) + if err != nil { + t.Fatalf("Error when parsing the JSON body: %s", err.Error()) + } + ctx.JSON(StatusOK, obj) + }) + e := Tester(t) passed := map[string]interface{}{"Username": "myusername", "Mail": "mymail@iris-go.com", "mydata": []string{"mydata1", "mydata2"}} expectedObject := testBinderData{Username: "myusername", Mail: "mymail@iris-go.com", Data: []string{"mydata1", "mydata2"}} e.POST("/json").WithJSON(passed).Expect().Status(StatusOK).JSON().Object().Equal(expectedObject) + e.POST("/json_pointer").WithJSON(passed).Expect().Status(StatusOK).JSON().Object().Equal(expectedObject) } +type testJSONBinderDataWithDecoder struct { + Username string + Mail string + Data []string `json:"mydata"` + shouldError bool +} + +func (tj *testJSONBinderDataWithDecoder) Decode(data []byte) error { + if tj.shouldError { + return fmt.Errorf("Should error") + } + return json.Unmarshal(data, tj) +} + +func TestContextReadJSONWithDecoder(t *testing.T) { + initDefault() + Post("/json_should_error", func(ctx *Context) { + obj := testJSONBinderDataWithDecoder{shouldError: true} + err := ctx.ReadJSON(&obj) + if err == nil { + t.Fatalf("Should prompted for error 'Should error' but not error returned from the custom decoder!") + } + ctx.Write(err.Error()) + ctx.SetStatusCode(StatusOK) + }) + + Post("/json", func(ctx *Context) { + obj := testJSONBinderDataWithDecoder{} + err := ctx.ReadJSON(&obj) + if err != nil { + t.Fatalf("Error when parsing the JSON body: %s", err.Error()) + } + ctx.JSON(StatusOK, obj) + }) + + Post("/json_pointer", func(ctx *Context) { + obj := &testJSONBinderDataWithDecoder{} + err := ctx.ReadJSON(obj) + if err != nil { + t.Fatalf("Error when parsing the JSON body: %s", err.Error()) + } + ctx.JSON(StatusOK, obj) + }) + + e := Tester(t) + passed := map[string]interface{}{"Username": "kataras", "Mail": "mymail@iris-go.com", "mydata": []string{"mydata1", "mydata2"}} + expectedObject := testJSONBinderDataWithDecoder{Username: "kataras", Mail: "mymail@iris-go.com", Data: []string{"mydata1", "mydata2"}} + + e.POST("/json_should_error").WithJSON(passed).Expect().Status(StatusOK).Body().Equal("Should error") + e.POST("/json").WithJSON(passed).Expect().Status(StatusOK).JSON().Object().Equal(expectedObject) + e.POST("/json_pointer").WithJSON(passed).Expect().Status(StatusOK).JSON().Object().Equal(expectedObject) +} // no need for xml, it's exact the same. + func TestContextReadXML(t *testing.T) { initDefault() diff --git a/iris.go b/iris.go index 5cb56f38..69664982 100644 --- a/iris.go +++ b/iris.go @@ -79,7 +79,7 @@ import ( const ( // Version is the current version of the Iris web framework - Version = "4.4.7" + Version = "4.4.8" banner = ` _____ _ |_ _| (_)