diff --git a/HISTORY.md b/HISTORY.md
index a5c6e885..a52c4e15 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -2,6 +2,10 @@
**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`.
+## 5.0.4 -> 5.1.0
+
+- **NEW (UNIQUE?) FEATURE**: Request-scoped transactions inside handler's context. Proof-of-concept example [here](https://github.com/iris-contrib/examples/tree/master/request_transactions).
+
## 5.0.3 -> 5.0.4
diff --git a/README.md b/README.md
index cef98940..0e157fee 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@
-
+
@@ -52,6 +52,7 @@ Feature Overview
- Automatically install and serve certificates from https://letsencrypt.org
- Robust routing and middleware ecosystem
- Build RESTful APIs
+- Request-Scoped Transactions
- Group API's and subdomains with wildcard support
- Body binding for JSON, XML, Forms, can be extended to use your own custom binders
- More than 50 handy functions to send HTTP responses
@@ -919,7 +920,7 @@ I recommend writing your API tests using this new library, [httpexpect](https://
Versioning
------------
-Current: **v5.0.4**
+Current: **v5.1.0**
Stable: **[v4 LTS](https://github.com/kataras/iris/tree/4.0.0#versioning)**
@@ -928,7 +929,7 @@ Todo
------------
- [ ] Server-side React render, as requested [here](https://github.com/kataras/iris/issues/503)
-
+- [+] [NEW: Request-Scoped Transactions](https://github.com/iris-contrib/examples/tree/master/request_transactions)
Iris is a **Community-Driven** Project, waiting for your suggestions and [feature requests](https://github.com/kataras/iris/issues?utf8=%E2%9C%93&q=label%3A%22feature%20request%22)!
People
diff --git a/context.go b/context.go
index a00c4984..afd9b12f 100644
--- a/context.go
+++ b/context.go
@@ -1121,6 +1121,117 @@ func (ctx *Context) MaxAge() int64 {
return -1
}
+// RequestTransactionScope is the request transaction scope of a handler's context
+// Can't say a lot here because I it will take more than 200 lines to write about.
+// You can search third-party articles or books on how Business Transaction works (it's quite simple, especialy here).
+// But I can provide you a simple example here: https://github.com/iris-contrib/examples/tree/master/request_transactions
+//
+// Note that this is unique and new
+// (=I haver never seen any other examples or code in Golang on this subject, so far, as with the most of iris features...)
+// it's not covers all paths,
+// such as databases, this should be managed by the libraries you use to make your database connection,
+// this transaction scope is only for iris' request/response(Context).
+type RequestTransactionScope struct {
+ Context *Context
+}
+
+// ErrWithStatus custom error type which is useful
+// to send an error containing the http status code and a reason
+type ErrWithStatus struct {
+ // failure status code, required
+ statusCode int
+ // plain text message, optional
+ message string // if it's empty then the already registered custom(or default) http error will be fired.
+}
+
+// Status sets the http status code of this error
+func (err *ErrWithStatus) Status(statusCode int) *ErrWithStatus {
+ err.statusCode = statusCode
+ return err
+}
+
+// Reason sets the reason message of this error
+func (err *ErrWithStatus) Reason(msg string) *ErrWithStatus {
+ err.message = msg
+ return err
+}
+
+// AppendReason just appends a reason message
+func (err *ErrWithStatus) AppendReason(msg string) *ErrWithStatus {
+ err.message += "\n" + msg
+ return err
+}
+
+// Error implements the error standard
+func (err ErrWithStatus) Error() string {
+ return err.message
+}
+
+// NewErrWithStatus returns an new custom error type which should be used
+// side by side with Transaction(s)
+func NewErrWithStatus() *ErrWithStatus {
+ return new(ErrWithStatus)
+}
+
+// Complete completes the transaction
+// - if the error is not nil then the response
+// is resetting and sends an error to the client.
+// - if the error is nil then the response sent as expected.
+//
+// The error can be a type of ErrWithStatus, create using the iris.NewErrWithStatus().
+func (r *RequestTransactionScope) Complete(err error) {
+ if err != nil && err.Error() != "" {
+ ctx := r.Context
+ // reset any previous response,
+ // except the content type we may use it to fire an error or take that from custom error type (?)
+ // no let's keep the custom error type as simple as possible, take that from prev attempt:
+ cType := string(ctx.Response.Header.ContentType())
+ if cType == "" {
+ cType = "text/plain; charset=" + ctx.framework.Config.Charset
+ }
+
+ // clears:
+ // - body
+ // - cookies
+ // - any headers
+ // and anything else we tried to sent before.
+ ctx.Response.Reset()
+
+ statusCode := StatusInternalServerError // default http status code if not provided
+ reason := err.Error()
+ shouldFireCustom := false
+ if errWstatus, ok := err.(*ErrWithStatus); ok {
+ statusCode = errWstatus.statusCode
+ if reason == "" { // if we have custom error type with a given status but empty reason then fire from custom http error (or default)
+ shouldFireCustom = true
+ }
+ }
+ if shouldFireCustom {
+ // if it's not our custom type of error nor an error with a non empty reason then we fire a default
+ // or custom (EmitError) using the 500 internal server error
+ ctx.EmitError(StatusInternalServerError)
+ return
+ }
+ // fire from the error or the custom error type
+ ctx.SetStatusCode(statusCode)
+ ctx.SetContentType(cType)
+ ctx.SetBodyString(reason)
+ return
+ }
+
+}
+
+// BeginTransaction starts a request scoped transaction.
+//
+// See more here: https://github.com/iris-contrib/examples/tree/master/request_transactions
+func (ctx *Context) BeginTransaction(pipe func(scope *RequestTransactionScope)) {
+ // not the best way but this should be do the job if we want multiple transaction in the same handler and context.
+ tempCtx := *ctx // clone the temp context
+ scope := &RequestTransactionScope{Context: &tempCtx}
+ pipe(scope) // run the context inside its scope
+ *ctx = *scope.Context // copy back the context
+}
+
// Log logs to the iris defined logger
func (ctx *Context) Log(format string, a ...interface{}) {
ctx.framework.Logger.Printf(format, a...)
diff --git a/context_test.go b/context_test.go
index be67e6df..002dfed3 100644
--- a/context_test.go
+++ b/context_test.go
@@ -780,3 +780,67 @@ func TestTemplatesDisabled(t *testing.T) {
e := httptest.New(iris.Default, t)
e.GET("/renderErr").Expect().Status(iris.StatusServiceUnavailable).Body().Equal(expctedErrMsg)
}
+
+func TestRequestTransactions(t *testing.T) {
+ iris.ResetDefault()
+ firstTransictionFailureMessage := "Error: Virtual failure!!!"
+ secondTransictionSuccessHTMLMessage := "