Skip to content

Commit ce2eae9

Browse files
committed
☕ add a dependency-injection examples folder for the next release and some improvements
Former-commit-id: 040168afb7caf808618f7da5e68ae8eb01cb7170
1 parent 5fc2481 commit ce2eae9

File tree

19 files changed

+210
-72
lines changed

19 files changed

+210
-72
lines changed

HISTORY.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ func main() {
7272

7373
Your eyes don't lie you. You read well, no `ctx.ReadJSON(&v)` and `ctx.JSON(send)` neither `error` handling are presented. It is a huge relief but don't worry you can still control everything if you ever need, even errors from dependencies. Any error may occur from request-scoped dependencies or your own handler is dispatched through `Party.GetContainer().GetErrorHandler` which defaults to the `hero.DefaultErrorHandler` which sends a `400 Bad Request` response with the error's text as its body contents. If you want to handle `testInput` otherwise then just add a `Party.RegisterDependency(func(ctx iris.Context) testInput {...})` and you are ready to go.
7474

75+
Other Improvements:
76+
77+
- `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.
78+
- Hero Handlers (and `app.HandleFunc`) do not have to require `iris.Context` just to call `ctx.Next()` anymore, this is done automatically now.
79+
7580
New Context Methods:
7681

7782
- `context.Defer(Handler)` works like `Party.Done` but for the request life-cycle.

_benchmarks/_internal/README.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,17 @@ Internal selected benchmarks between modified features across different versions
1414

1515
Measures handler factory time.
1616

17+
```sh
18+
$ cd v12.1.x
19+
$ go test -run=NONE --bench=. -count=5 --benchmem > di_test.txt
20+
$ cd ../vNext
21+
$ go test -run=NONE --bench=. -count=5 --benchmem > di_test.txt
22+
```
23+
1724
| Name | Ops | Ns/op | B/op | Allocs/op |
1825
|---------|:------|:--------|:--------|----|
19-
| vNext | 181726 | 6631 | 1544 | 17 |
20-
| v12.1.x | 96001 | 12604 | 976 | 26 |
26+
| vNext | 184512 | 6607 | 1544 | 17 |
27+
| v12.1.x | 95974 | 12653 | 976 | 26 |
2128

2229
It accepts a dynamic path parameter and a JSON request. It returns a JSON response. Fires 500000 requests with 125 concurrent connections.
2330

-490 Bytes
Binary file not shown.

_benchmarks/_internal/vNext/di.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package main
22

3-
import (
4-
"github.com/kataras/iris/v12"
5-
)
3+
import "github.com/kataras/iris/v12"
64

75
type (
86
testInput struct {
-484 Bytes
Binary file not shown.

_benchmarks/_internal/vNext/go.mod

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,7 @@ go 1.14
44

55
replace github.com/kataras/iris/v12 => C:/mygopath/src/github.com/kataras/iris
66

7-
require github.com/kataras/iris/v12 v12.1.8
7+
require (
8+
github.com/kataras/iris/v12 v12.1.8
9+
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
10+
)

_examples/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ It doesn't always contain the "best ways" but it does cover each important featu
88

99
## Running the examples
1010

11+
[![Run in Postman](https://run.pstmn.io/button.svg)](https://app.getpostman.com/run-collection/bd489282b676e30de158)
12+
1113
1. Install the Go Programming Language, version 1.12+ from https://golang.org/dl.
1214
2. [Install Iris](https://github.com/kataras/iris/wiki/installation)
1315
3. Install any external packages that required by the examples
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package main
2+
3+
import "github.com/kataras/iris/v12"
4+
5+
type (
6+
testInput struct {
7+
Email string `json:"email"`
8+
}
9+
10+
testOutput struct {
11+
ID int `json:"id"`
12+
Name string `json:"name"`
13+
}
14+
)
15+
16+
func handler(id int, in testInput) testOutput {
17+
return testOutput{
18+
ID: id,
19+
Name: in.Email,
20+
}
21+
}
22+
23+
func main() {
24+
app := iris.New()
25+
app.HandleFunc(iris.MethodPost, "/{id:int}", handler)
26+
app.Listen(":5000", iris.WithOptimizations)
27+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package main
2+
3+
import (
4+
"errors"
5+
6+
"github.com/kataras/iris/v12"
7+
)
8+
9+
type (
10+
testInput struct {
11+
Email string `json:"email"`
12+
}
13+
14+
testOutput struct {
15+
ID int `json:"id"`
16+
Name string `json:"name"`
17+
}
18+
)
19+
20+
func handler(id int, in testInput) testOutput {
21+
return testOutput{
22+
ID: id,
23+
Name: in.Email,
24+
}
25+
}
26+
27+
var errCustom = errors.New("my_error")
28+
29+
func middleware(in testInput) (int, error) {
30+
if in.Email == "invalid" {
31+
// stop the execution and don't continue to "handler"
32+
// without firing an error.
33+
return iris.StatusAccepted, iris.ErrStopExecution
34+
} else if in.Email == "error" {
35+
// stop the execution and fire a custom error.
36+
return iris.StatusConflict, errCustom
37+
}
38+
39+
return iris.StatusOK, nil
40+
}
41+
42+
func newApp() *iris.Application {
43+
app := iris.New()
44+
45+
// handle the route, respond with
46+
// a JSON and 200 status code
47+
// or 202 status code and empty body
48+
// or a 409 status code and "my_error" body.
49+
app.HandleFunc(iris.MethodPost, "/{id:int}", middleware, handler)
50+
51+
app.Configure(
52+
iris.WithOptimizations, /* optional */
53+
iris.WithoutBodyConsumptionOnUnmarshal /* required when more than one handler is consuming request payload(testInput) */)
54+
55+
return app
56+
}
57+
58+
func main() {
59+
app := newApp()
60+
app.Listen(":8080")
61+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
6+
"github.com/kataras/iris/v12/httptest"
7+
)
8+
9+
func TestDependencyInjectionBasic_Middleware(t *testing.T) {
10+
app := newApp()
11+
12+
e := httptest.New(t, app)
13+
e.POST("/42").WithJSON(testInput{Email: "my_email"}).Expect().
14+
Status(httptest.StatusOK).
15+
JSON().Equal(testOutput{ID: 42, Name: "my_email"})
16+
17+
// it should stop the execution at the middleware and return the middleware's status code,
18+
// because the error is `ErrStopExecution`.
19+
e.POST("/42").WithJSON(testInput{Email: "invalid"}).Expect().
20+
Status(httptest.StatusAccepted).Body().Empty()
21+
22+
// it should stop the execution at the middleware and return the error's text.
23+
e.POST("/42").WithJSON(testInput{Email: "error"}).Expect().
24+
Status(httptest.StatusConflict).Body().Equal("my_error")
25+
}

context/context.go

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3200,13 +3200,16 @@ var (
32003200

32013201
// WriteJSON marshals the given interface object and writes the JSON response to the 'writer'.
32023202
// Ignores StatusCode, Gzip, StreamingJSON options.
3203-
func WriteJSON(writer io.Writer, v interface{}, options JSON, enableOptimization ...bool) (int, error) {
3203+
func WriteJSON(writer io.Writer, v interface{}, options JSON, optimize bool) (int, error) {
32043204
var (
3205-
result []byte
3206-
err error
3207-
optimize = len(enableOptimization) > 0 && enableOptimization[0]
3205+
result []byte
3206+
err error
32083207
)
32093208

3209+
if !optimize && options.Indent == "" {
3210+
options.Indent = " "
3211+
}
3212+
32103213
if indent := options.Indent; indent != "" {
32113214
marshalIndent := json.MarshalIndent
32123215
if optimize {
@@ -3291,7 +3294,7 @@ func (ctx *context) JSON(v interface{}, opts ...JSON) (n int, err error) {
32913294
var finishCallbackB = []byte(");")
32923295

32933296
// WriteJSONP marshals the given interface object and writes the JSON response to the writer.
3294-
func WriteJSONP(writer io.Writer, v interface{}, options JSONP, enableOptimization ...bool) (int, error) {
3297+
func WriteJSONP(writer io.Writer, v interface{}, options JSONP, optimize bool) (int, error) {
32953298
if callback := options.Callback; callback != "" {
32963299
n, err := writer.Write([]byte(callback + "("))
32973300
if err != nil {
@@ -3300,7 +3303,9 @@ func WriteJSONP(writer io.Writer, v interface{}, options JSONP, enableOptimizati
33003303
defer writer.Write(finishCallbackB)
33013304
}
33023305

3303-
optimize := len(enableOptimization) > 0 && enableOptimization[0]
3306+
if !optimize && options.Indent == "" {
3307+
options.Indent = " "
3308+
}
33043309

33053310
if indent := options.Indent; indent != "" {
33063311
marshalIndent := json.MarshalIndent
@@ -3396,14 +3401,18 @@ func (m xmlMap) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
33963401
}
33973402

33983403
// WriteXML marshals the given interface object and writes the XML response to the writer.
3399-
func WriteXML(writer io.Writer, v interface{}, options XML) (int, error) {
3404+
func WriteXML(writer io.Writer, v interface{}, options XML, optimize bool) (int, error) {
34003405
if prefix := options.Prefix; prefix != "" {
34013406
n, err := writer.Write([]byte(prefix))
34023407
if err != nil {
34033408
return n, err
34043409
}
34053410
}
34063411

3412+
if !optimize && options.Indent == "" {
3413+
options.Indent = " "
3414+
}
3415+
34073416
if indent := options.Indent; indent != "" {
34083417
result, err := xml.MarshalIndent(v, "", indent)
34093418
if err != nil {
@@ -3435,7 +3444,7 @@ func (ctx *context) XML(v interface{}, opts ...XML) (int, error) {
34353444

34363445
ctx.ContentType(ContentXMLHeaderValue)
34373446

3438-
n, err := WriteXML(ctx.writer, v, options)
3447+
n, err := WriteXML(ctx.writer, v, options, ctx.shouldOptimize())
34393448
if err != nil {
34403449
ctx.Application().Logger().Debugf("XML: %v", err)
34413450
ctx.StatusCode(http.StatusInternalServerError)

core/router/api_builder.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,12 +282,22 @@ func (api *APIBuilder) RegisterDependency(dependency interface{}) *hero.Dependen
282282
// can accept any input arguments that match with the Party's registered Container's `Dependencies` and
283283
// any output result; like custom structs <T>, string, []byte, int, error,
284284
// a combination of the above, hero.Result(hero.View | hero.Response) and more.
285+
//
286+
// It's common from a hero handler to not even need to accept a `Context`, for that reason,
287+
// the "handlersFn" will call `ctx.Next()` automatically when not called manually.
288+
// To stop the execution and not continue to the next "handlersFn"
289+
// the end-developer should output an error and return `iris.ErrStopExecution`.
285290
func (api *APIBuilder) HandleFunc(method, relativePath string, handlersFn ...interface{}) *Route {
286291
handlers := make(context.Handlers, 0, len(handlersFn))
287292
for _, h := range handlersFn {
288293
handlers = append(handlers, api.container.Handler(h))
289294
}
290295

296+
// On that type of handlers the end-developer does not have to include the Context in the handler,
297+
// so the ctx.Next is automatically called unless an `ErrStopExecution` returned (implementation inside hero pkg).
298+
o := ExecutionOptions{Force: true}
299+
o.apply(&handlers)
300+
291301
return api.Handle(method, relativePath, handlers...)
292302
}
293303

core/router/party.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,11 @@ type Party interface {
130130
// can accept any input arguments that match with the Party's registered Container's `Dependencies` and
131131
// any output result; like custom structs <T>, string, []byte, int, error,
132132
// a combination of the above, hero.Result(hero.View | hero.Response) and more.
133+
//
134+
// It's common from a hero handler to not even need to accept a `Context`, for that reason,
135+
// the "handlersFn" will call `ctx.Next()` automatically when not called manually.
136+
// To stop the execution and not continue to the next "handlersFn"
137+
// the end-developer should output an error and return `iris.ErrStopExecution`.
133138
HandleFunc(method, relativePath string, handlersFn ...interface{}) *Route
134139

135140
// Handle registers a route to the server's router.

hero/func_result.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ func dispatchFuncResult(ctx context.Context, values []reflect.Value) error {
225225
continue
226226
}
227227

228-
if statusCode < 400 {
228+
if statusCode < 400 && value != ErrStopExecution {
229229
statusCode = DefaultErrStatusCode
230230
}
231231

@@ -286,7 +286,11 @@ func dispatchCommon(ctx context.Context,
286286
if contentType == "" {
287287
// to respect any ctx.ContentType(...) call
288288
// especially if v is not nil.
289-
contentType = ctx.GetContentType()
289+
if contentType = ctx.GetContentType(); contentType == "" {
290+
// if it's still empty set to JSON. (useful for dynamic middlewares that returns an int status code and the next handler dispatches the JSON,
291+
// see dependency-injection/basic/middleware example)
292+
contentType = context.ContentJSONHeaderValue
293+
}
290294
}
291295

292296
if v != nil {
@@ -302,10 +306,13 @@ func dispatchCommon(ctx context.Context,
302306
if strings.HasPrefix(contentType, context.ContentJavascriptHeaderValue) {
303307
_, err = ctx.JSONP(v)
304308
} else if strings.HasPrefix(contentType, context.ContentXMLHeaderValue) {
305-
_, err = ctx.XML(v, context.XML{Indent: " "})
309+
_, err = ctx.XML(v)
310+
// no need: context.XML{Indent: " "}), after v12.2,
311+
// if not iris.WithOptimizations passed and indent is empty then it sets it to two spaces for JSON, JSONP and XML,
312+
// otherwise given indentation.
306313
} else {
307314
// defaults to json if content type is missing or its application/json.
308-
_, err = ctx.JSON(v, context.JSON{Indent: " "})
315+
_, err = ctx.JSON(v)
309316
}
310317

311318
return err

hero/handler.go

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,16 @@ func (fn ErrorHandlerFunc) HandleError(ctx context.Context, err error) {
1818
fn(ctx, err)
1919
}
2020

21+
var (
22+
// ErrSeeOther may be returned from a dependency handler to skip a specific dependency
23+
// based on custom logic.
24+
ErrSeeOther = fmt.Errorf("see other")
25+
// ErrStopExecution may be returned from a dependency handler to stop
26+
// and return the execution of the function without error (it calls ctx.StopExecution() too).
27+
// It may be occurred from request-scoped dependencies as well.
28+
ErrStopExecution = fmt.Errorf("stop execution")
29+
)
30+
2131
var (
2232
// DefaultErrStatusCode is the default error status code (400)
2333
// when the response contains a non-nil error or a request-scoped binding error occur.
@@ -26,25 +36,18 @@ var (
2636
// DefaultErrorHandler is the default error handler which is fired
2737
// when a function returns a non-nil error or a request-scoped dependency failed to binded.
2838
DefaultErrorHandler = ErrorHandlerFunc(func(ctx context.Context, err error) {
29-
if status := ctx.GetStatusCode(); status == 0 || !context.StatusCodeNotSuccessful(status) {
30-
ctx.StatusCode(DefaultErrStatusCode)
39+
if err != ErrStopExecution {
40+
if status := ctx.GetStatusCode(); status == 0 || !context.StatusCodeNotSuccessful(status) {
41+
ctx.StatusCode(DefaultErrStatusCode)
42+
}
43+
44+
ctx.WriteString(err.Error())
3145
}
3246

33-
ctx.WriteString(err.Error())
3447
ctx.StopExecution()
3548
})
3649
)
3750

38-
var (
39-
// ErrSeeOther may be returned from a dependency handler to skip a specific dependency
40-
// based on custom logic.
41-
ErrSeeOther = fmt.Errorf("see other")
42-
// ErrStopExecution may be returned from a dependency handler to stop
43-
// and return the execution of the function without error (it calls ctx.StopExecution() too).
44-
// It may be occurred from request-scoped dependencies as well.
45-
ErrStopExecution = fmt.Errorf("stop execution")
46-
)
47-
4851
func makeHandler(fn interface{}, c *Container) context.Handler {
4952
if fn == nil {
5053
panic("makeHandler: function is nil")
@@ -77,10 +80,12 @@ func makeHandler(fn interface{}, c *Container) context.Handler {
7780
if err != nil {
7881
if err == ErrSeeOther {
7982
continue
80-
} else if err == ErrStopExecution {
81-
ctx.StopExecution()
82-
return // return without error.
8383
}
84+
// handled inside ErrorHandler.
85+
// else if err == ErrStopExecution {
86+
// ctx.StopExecution()
87+
// return // return without error.
88+
// }
8489

8590
c.GetErrorHandler(ctx).HandleError(ctx, err)
8691
return

0 commit comments

Comments
 (0)