🚌 next version preparation: new PreflightResult interface for hero handlers

Former-commit-id: ea2d7ab93889beaddfe269bd213d259d26df979f
This commit is contained in:
Gerasimos (Makis) Maropoulos 2020-03-02 10:07:44 +02:00
parent afd0f5caef
commit 5da8ff92f3
6 changed files with 90 additions and 7 deletions

View File

@ -137,7 +137,10 @@ HandleFunc(method, relativePath string, handlersFn ...interface{}) *Route
Other Improvements: Other Improvements:
- A result of <T> 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. - `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. - 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: New Context Methods:

View File

@ -8,7 +8,7 @@ It doesn't always contain the "best ways" but it does cover each important featu
## Running the examples ## Running the examples
[![Run in Postman](https://run.pstmn.io/button.svg)](https://app.getpostman.com/run-collection/bd489282b676e30de158) <!-- [![Run in Postman](https://run.pstmn.io/button.svg)](https://www.getpostman.com/collections/16e76a9528aba863e821) -->
1. Install the Go Programming Language, version 1.12+ from https://golang.org/dl. 1. Install the Go Programming Language, version 1.12+ from https://golang.org/dl.
2. [Install Iris](https://github.com/kataras/iris/wiki/installation) 2. [Install Iris](https://github.com/kataras/iris/wiki/installation)

View File

@ -23,5 +23,5 @@ func handler(id int, in testInput) testOutput {
func main() { func main() {
app := iris.New() app := iris.New()
app.HandleFunc(iris.MethodPost, "/{id:int}", handler) app.HandleFunc(iris.MethodPost, "/{id:int}", handler)
app.Listen(":5000", iris.WithOptimizations) app.Listen(":8080")
} }

View File

@ -1056,6 +1056,10 @@ func getCaller() (string, int) {
frame, more := frames.Next() frame, more := frames.Next()
file := frame.File 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 !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 { if relFile, err := filepath.Rel(wd, file); err == nil {
file = "./" + relFile file = "./" + relFile

View File

@ -15,8 +15,24 @@ import (
// //
// Example at: https://github.com/kataras/iris/tree/master/_examples/hero/overview. // Example at: https://github.com/kataras/iris/tree/master/_examples/hero/overview.
type Result interface { type Result interface {
// Dispatch should sends the response to the context's response writer. // Dispatch should send a response to the client.
Dispatch(ctx context.Context) Dispatch(context.Context)
}
// PreflightResult is an interface which implementers
// should be responsible to perform preflight checks of a <T> 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} var defaultFailureResponse = Response{Code: DefaultErrStatusCode}
@ -294,6 +310,12 @@ func dispatchCommon(ctx context.Context,
} }
if v != nil { if v != nil {
if p, ok := v.(PreflightResult); ok {
if err := p.Preflight(ctx); err != nil {
return err
}
}
if d, ok := v.(Result); ok { if d, ok := v.(Result); ok {
// write the content type now (internal check for empty value) // write the content type now (internal check for empty value)
ctx.ContentType(contentType) ctx.ContentType(contentType)

View File

@ -2,6 +2,8 @@ package hero_test
import ( import (
"errors" "errors"
"fmt"
"net/http"
"testing" "testing"
"github.com/kataras/iris/v12" "github.com/kataras/iris/v12"
@ -37,7 +39,7 @@ type testCustomResult struct {
} }
// The only one required function to make that a custom Response dispatcher. // 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) _, _ = ctx.HTML(r.HTML)
} }
@ -70,7 +72,7 @@ func GetCustomStructWithContentType() (testCustomStruct, string) {
return testCustomStruct{"Iris", 2}, "text/xml" 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} s = testCustomStruct{"Iris", 2}
if ctx.URLParamExists("err") { if ctx.URLParamExists("err") {
err = errors.New("omit return of testCustomStruct and fire error") err = errors.New("omit return of testCustomStruct and fire error")
@ -86,7 +88,7 @@ type err struct {
Message string `json:"message"` 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. // write the status code based on the err's StatusCode.
ctx.StatusCode(e.Status) ctx.StatusCode(e.Status)
// send to the client the whole object as json // send to the client the whole object as json
@ -204,3 +206,55 @@ func TestFuncResult(t *testing.T) {
e.GET("/custom/nil/struct").Expect(). e.GET("/custom/nil/struct").Expect().
Status(iris.StatusOK).ContentType(context.ContentJSONHeaderValue).Body().Empty() 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)
}