From 5da8ff92f373a5576bcaf25c56c72beb377fe37d Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Mon, 2 Mar 2020 10:07:44 +0200 Subject: [PATCH] :bus: next version preparation: new PreflightResult interface for hero handlers Former-commit-id: ea2d7ab93889beaddfe269bd213d259d26df979f --- HISTORY.md | 3 + _examples/README.md | 2 +- _examples/dependency-injection/basic/main.go | 2 +- core/router/api_builder.go | 4 ++ hero/func_result.go | 26 ++++++++- hero/func_result_test.go | 60 +++++++++++++++++++- 6 files changed, 90 insertions(+), 7 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 6e98d367..fec6e346 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -137,7 +137,10 @@ HandleFunc(method, relativePath string, handlersFn ...interface{}) *Route Other Improvements: +- A result of can implement the new `hero.PreflightResult` interface which contains a single method of `Preflight(iris.Context) error`. If this method exists on a custom struct value which is returned from a handler then it will fire that `Preflight` first and if not errored then it will cotninue by sending the struct value as JSON(by-default) response body. + - `ctx.JSON, JSONP, XML`: if `iris.WithOptimizations` is NOT passed on `app.Run/Listen` then the indentation defaults to `" "` (two spaces) otherwise it is empty or the provided value. + - Hero Handlers (and `app.HandleFunc`) do not have to require `iris.Context` just to call `ctx.Next()` anymore, this is done automatically now. New Context Methods: diff --git a/_examples/README.md b/_examples/README.md index d7efeebf..7c5aff1b 100644 --- a/_examples/README.md +++ b/_examples/README.md @@ -8,7 +8,7 @@ It doesn't always contain the "best ways" but it does cover each important featu ## Running the examples -[![Run in Postman](https://run.pstmn.io/button.svg)](https://app.getpostman.com/run-collection/bd489282b676e30de158) + 1. Install the Go Programming Language, version 1.12+ from https://golang.org/dl. 2. [Install Iris](https://github.com/kataras/iris/wiki/installation) diff --git a/_examples/dependency-injection/basic/main.go b/_examples/dependency-injection/basic/main.go index 451fb6be..257e8b7e 100644 --- a/_examples/dependency-injection/basic/main.go +++ b/_examples/dependency-injection/basic/main.go @@ -23,5 +23,5 @@ func handler(id int, in testInput) testOutput { func main() { app := iris.New() app.HandleFunc(iris.MethodPost, "/{id:int}", handler) - app.Listen(":5000", iris.WithOptimizations) + app.Listen(":8080") } diff --git a/core/router/api_builder.go b/core/router/api_builder.go index 0076f8a9..4f2c5445 100644 --- a/core/router/api_builder.go +++ b/core/router/api_builder.go @@ -1056,6 +1056,10 @@ func getCaller() (string, int) { frame, more := frames.Next() file := frame.File + if strings.Contains(file, "src/testing/testing.go") { + continue + } + if !strings.Contains(file, "/kataras/iris") || strings.Contains(file, "/kataras/iris/_examples") || strings.Contains(file, "iris-contrib/examples") { if relFile, err := filepath.Rel(wd, file); err == nil { file = "./" + relFile diff --git a/hero/func_result.go b/hero/func_result.go index fee73bca..98f43943 100644 --- a/hero/func_result.go +++ b/hero/func_result.go @@ -15,8 +15,24 @@ import ( // // Example at: https://github.com/kataras/iris/tree/master/_examples/hero/overview. type Result interface { - // Dispatch should sends the response to the context's response writer. - Dispatch(ctx context.Context) + // Dispatch should send a response to the client. + Dispatch(context.Context) +} + +// PreflightResult is an interface which implementers +// should be responsible to perform preflight checks of a resource (or Result) before sent to the client. +// +// If a non-nil error returned from the `Preflight` method then the JSON result +// will be not sent to the client and an ErrorHandler will be responsible to render the error. +// +// Usage: a custom struct value will be a JSON body response (by-default) but it contains +// "Code int" and `ID string` fields, the "Code" should be the status code of the response +// and the "ID" should be sent as a Header of "X-Request-ID: $ID". +// +// The caller can manage it at the handler itself. However, +// to reduce thoese type of duplications it's preferable to use such a standard interface instead. +type PreflightResult interface { + Preflight(context.Context) error } var defaultFailureResponse = Response{Code: DefaultErrStatusCode} @@ -294,6 +310,12 @@ func dispatchCommon(ctx context.Context, } if v != nil { + if p, ok := v.(PreflightResult); ok { + if err := p.Preflight(ctx); err != nil { + return err + } + } + if d, ok := v.(Result); ok { // write the content type now (internal check for empty value) ctx.ContentType(contentType) diff --git a/hero/func_result_test.go b/hero/func_result_test.go index 132f9b90..4352d617 100644 --- a/hero/func_result_test.go +++ b/hero/func_result_test.go @@ -2,6 +2,8 @@ package hero_test import ( "errors" + "fmt" + "net/http" "testing" "github.com/kataras/iris/v12" @@ -37,7 +39,7 @@ type testCustomResult struct { } // The only one required function to make that a custom Response dispatcher. -func (r testCustomResult) Dispatch(ctx context.Context) { +func (r testCustomResult) Dispatch(ctx iris.Context) { _, _ = ctx.HTML(r.HTML) } @@ -70,7 +72,7 @@ func GetCustomStructWithContentType() (testCustomStruct, string) { return testCustomStruct{"Iris", 2}, "text/xml" } -func GetCustomStructWithError(ctx context.Context) (s testCustomStruct, err error) { +func GetCustomStructWithError(ctx iris.Context) (s testCustomStruct, err error) { s = testCustomStruct{"Iris", 2} if ctx.URLParamExists("err") { err = errors.New("omit return of testCustomStruct and fire error") @@ -86,7 +88,7 @@ type err struct { Message string `json:"message"` } -func (e err) Dispatch(ctx context.Context) { +func (e err) Dispatch(ctx iris.Context) { // write the status code based on the err's StatusCode. ctx.StatusCode(e.Status) // send to the client the whole object as json @@ -204,3 +206,55 @@ func TestFuncResult(t *testing.T) { e.GET("/custom/nil/struct").Expect(). Status(iris.StatusOK).ContentType(context.ContentJSONHeaderValue).Body().Empty() } + +type ( + testPreflightRequest struct { + FailCode int `json:"fail_code"` // for the shake of the test. + } + + testPreflightResponse struct { + Code int + Message string + } +) + +func (r testPreflightResponse) Preflight(ctx iris.Context) error { + if r.Code == httptest.StatusInternalServerError { + return fmt.Errorf("custom error") + } + + ctx.StatusCode(r.Code) + return nil +} + +func TestPreflightResult(t *testing.T) { + app := iris.New() + c := New() + handler := c.Handler(func(in testPreflightRequest) testPreflightResponse { + return testPreflightResponse{Code: in.FailCode, Message: http.StatusText(in.FailCode)} + }) + app.Post("/", handler) + handler2 := c.Handler(func(in testInput) (int, testOutput) { + return httptest.StatusAccepted, testOutput{Name: in.Name} + }) + app.Post("/alternative", handler2) + + e := httptest.New(t, app) + + expected1 := testPreflightResponse{Code: httptest.StatusOK, Message: "OK"} + e.POST("/").WithJSON(testPreflightRequest{FailCode: expected1.Code}). + Expect().Status(httptest.StatusOK).JSON().Equal(expected1) + + expected2 := testPreflightResponse{Code: httptest.StatusBadRequest, Message: "Bad Request"} + e.POST("/").WithJSON(testPreflightRequest{FailCode: expected2.Code}). + Expect().Status(httptest.StatusBadRequest).JSON().Equal(expected2) + + // Test error returned from Preflight. + e.POST("/").WithJSON(testPreflightRequest{FailCode: httptest.StatusInternalServerError}). + Expect().Status(httptest.StatusBadRequest).Body().Equal("custom error") + + // Can be done without Preflight as the second output argument can be a status code. + expected4 := testOutput{Name: "my_name"} + e.POST("/alternative").WithJSON(testInput{expected4.Name}). + Expect().Status(httptest.StatusAccepted).JSON().Equal(expected4) +}