mirror of
https://github.com/kataras/iris.git
synced 2025-03-21 23:46:27 +01:00
Update to 5.1.1 - Addons for the last feature, Transaction scopes. Read HISTORY.md
Read HISTORY.md and example here: github.com/iris-contrib/examples/tree/master/transactions
This commit is contained in:
parent
c6b6ebf757
commit
48e770dab0
|
@ -2,9 +2,16 @@
|
||||||
|
|
||||||
**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`.
|
**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.1.0 -> 5.1.1
|
||||||
|
Two hours after the previous update,
|
||||||
|
|
||||||
|
- **More on Transactions**: By-default transaction's lifetime is 'per-call/transient' meaning that each transaction has its own scope on the context, rollbacks when `scope.Complete(notNilAndNotEmptyError)` and the rest of transictions in chain are executed as expected, from now and on you have the ability to `skip the rest of the next transictions on first failure` by simply call `scope.RequestScoped(true)`.
|
||||||
|
|
||||||
|
Note: `RequestTransactionScope` renamed to ,simply, `TransactionScope`.
|
||||||
|
|
||||||
## 5.0.4 -> 5.1.0
|
## 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).
|
- **NEW (UNIQUE?) FEATURE**: Request-scoped transactions inside handler's context. Proof-of-concept example [here](https://github.com/iris-contrib/examples/tree/master/transactions).
|
||||||
|
|
||||||
## 5.0.3 -> 5.0.4
|
## 5.0.3 -> 5.0.4
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<br/>
|
<br/>
|
||||||
|
|
||||||
|
|
||||||
<a href="https://github.com/kataras/iris/releases"><img src="https://img.shields.io/badge/%20version%20-%205.1.0%20-blue.svg?style=flat-square" alt="Releases"></a>
|
<a href="https://github.com/kataras/iris/releases"><img src="https://img.shields.io/badge/%20version%20-%205.1.1%20-blue.svg?style=flat-square" alt="Releases"></a>
|
||||||
|
|
||||||
<a href="https://github.com/iris-contrib/examples"><img src="https://img.shields.io/badge/%20examples-repository-3362c2.svg?style=flat-square" alt="Examples"></a>
|
<a href="https://github.com/iris-contrib/examples"><img src="https://img.shields.io/badge/%20examples-repository-3362c2.svg?style=flat-square" alt="Examples"></a>
|
||||||
|
|
||||||
|
@ -920,7 +920,7 @@ I recommend writing your API tests using this new library, [httpexpect](https://
|
||||||
Versioning
|
Versioning
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Current: **v5.1.0**
|
Current: **v5.1.1**
|
||||||
|
|
||||||
Stable: **[v4 LTS](https://github.com/kataras/iris/tree/4.0.0#versioning)**
|
Stable: **[v4 LTS](https://github.com/kataras/iris/tree/4.0.0#versioning)**
|
||||||
|
|
||||||
|
@ -929,7 +929,7 @@ Todo
|
||||||
------------
|
------------
|
||||||
|
|
||||||
- [ ] Server-side React render, as requested [here](https://github.com/kataras/iris/issues/503)
|
- [ ] Server-side React render, as requested [here](https://github.com/kataras/iris/issues/503)
|
||||||
- [x] [v5.1.0: Request-Scoped Transactions](https://github.com/iris-contrib/examples/tree/master/request_transactions), simple and elegant.
|
- [x] [v5.1.0: (Request) Scoped Transactions](https://github.com/iris-contrib/examples/tree/master/transactions), simple and elegant.
|
||||||
|
|
||||||
|
|
||||||
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)!
|
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)!
|
||||||
|
|
138
context.go
138
context.go
|
@ -1121,20 +1121,6 @@ func (ctx *Context) MaxAge() int64 {
|
||||||
return -1
|
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
|
// ErrWithStatus custom error type which is useful
|
||||||
// to send an error containing the http status code and a reason
|
// to send an error containing the http status code and a reason
|
||||||
type ErrWithStatus struct {
|
type ErrWithStatus struct {
|
||||||
|
@ -1145,6 +1131,8 @@ type ErrWithStatus struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Status sets the http status code of this error
|
// Status sets the http status code of this error
|
||||||
|
// if only status exists but no reason then
|
||||||
|
// custom http error of this staus (if any) will be fired (context.EmitError)
|
||||||
func (err *ErrWithStatus) Status(statusCode int) *ErrWithStatus {
|
func (err *ErrWithStatus) Status(statusCode int) *ErrWithStatus {
|
||||||
err.statusCode = statusCode
|
err.statusCode = statusCode
|
||||||
return err
|
return err
|
||||||
|
@ -1173,15 +1161,77 @@ func NewErrWithStatus() *ErrWithStatus {
|
||||||
return new(ErrWithStatus)
|
return new(ErrWithStatus)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TransactionValidator used to register global transaction pre-validators
|
||||||
|
type TransactionValidator interface {
|
||||||
|
// ValidateTransaction pre-validates transactions
|
||||||
|
// transaction fails if this returns an error or it's Complete has a non empty error
|
||||||
|
ValidateTransaction(*TransactionScope) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionScope 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/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 TransactionScope struct {
|
||||||
|
Context *Context
|
||||||
|
isRequestScoped bool
|
||||||
|
isFailure bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestScoped receives a boolean which determinates if other transactions depends on this.
|
||||||
|
// If setted true then whenever this transaction is not completed succesfuly,
|
||||||
|
// the rest of the transactions will be not executed at all.
|
||||||
|
//
|
||||||
|
// Defaults to false, execute all transactions on their own independently scopes.
|
||||||
|
func (r *TransactionScope) RequestScoped(isRequestScoped bool) {
|
||||||
|
r.isRequestScoped = isRequestScoped
|
||||||
|
}
|
||||||
|
|
||||||
// Complete completes the transaction
|
// Complete completes the transaction
|
||||||
// - if the error is not nil then the response
|
// rollback and send an error when:
|
||||||
// is resetting and sends an error to the client.
|
// 1. a not nil error AND non-empty reason AND custom type error has status code
|
||||||
// - if the error is nil then the response sent as expected.
|
// 2. a not nil error AND empty reason BUT custom type error has status code
|
||||||
|
// 3. a not nil error AND non-empty reason.
|
||||||
//
|
//
|
||||||
// The error can be a type of ErrWithStatus, create using the iris.NewErrWithStatus().
|
// The error can be a type of ErrWithStatus, create using the iris.NewErrWithStatus().
|
||||||
func (r *RequestTransactionScope) Complete(err error) {
|
func (r *TransactionScope) Complete(err error) {
|
||||||
if err != nil && err.Error() != "" {
|
if err != nil {
|
||||||
|
|
||||||
ctx := r.Context
|
ctx := r.Context
|
||||||
|
statusCode := StatusInternalServerError // default http status code if not provided
|
||||||
|
reason := err.Error()
|
||||||
|
|
||||||
|
if errWstatus, ok := err.(*ErrWithStatus); ok {
|
||||||
|
if errWstatus.statusCode > 0 {
|
||||||
|
// get the status code from the custom error type
|
||||||
|
statusCode = errWstatus.statusCode
|
||||||
|
|
||||||
|
// empty error message but status code given,
|
||||||
|
if reason == "" {
|
||||||
|
r.isFailure = true
|
||||||
|
// reset everything, cookies and headers and body.
|
||||||
|
ctx.Response.Reset()
|
||||||
|
// execute from custom (if any) http error (template or plain text)
|
||||||
|
ctx.EmitError(errWstatus.statusCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if reason == "" {
|
||||||
|
// do nothing empty reason and no status code means that this is not a failure, even if the error is not nil.
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// rollback and send an error when we have:
|
||||||
|
// 1. a not nil error AND non-empty reason AND custom type error has status code
|
||||||
|
// 2. a not nil error AND empty reason BUT custom type error has status code
|
||||||
|
// 3. a not nil error AND non-empty reason.
|
||||||
|
|
||||||
// reset any previous response,
|
// reset any previous response,
|
||||||
// except the content type we may use it to fire an error or take that from custom error type (?)
|
// 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:
|
// no let's keep the custom error type as simple as possible, take that from prev attempt:
|
||||||
|
@ -1197,39 +1247,53 @@ func (r *RequestTransactionScope) Complete(err error) {
|
||||||
// and anything else we tried to sent before.
|
// and anything else we tried to sent before.
|
||||||
ctx.Response.Reset()
|
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
|
// fire from the error or the custom error type
|
||||||
ctx.SetStatusCode(statusCode)
|
ctx.SetStatusCode(statusCode)
|
||||||
ctx.SetContentType(cType)
|
ctx.SetContentType(cType)
|
||||||
ctx.SetBodyString(reason)
|
ctx.SetBodyString(reason)
|
||||||
|
r.isFailure = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// skipTransactionsContextKey set this to any value to stop executing next transactions
|
||||||
|
// it's a context-key in order to be used from anywhere, set it by calling the SkipTransactions()
|
||||||
|
const skipTransactionsContextKey = "@@IRIS_TRANSACTIONS_SKIP_@@"
|
||||||
|
|
||||||
|
// SkipTransactions if called then skip the rest of the transactions
|
||||||
|
// or all of them if called before the first transaction
|
||||||
|
func (ctx *Context) SkipTransactions() {
|
||||||
|
ctx.Set(skipTransactionsContextKey, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransactionsSkipped returns true if the transactions skipped or canceled at all.
|
||||||
|
func (ctx *Context) TransactionsSkipped() bool {
|
||||||
|
if n, err := ctx.GetInt(skipTransactionsContextKey); err == nil && n == 1 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// BeginTransaction starts a request scoped transaction.
|
// BeginTransaction starts a request scoped transaction.
|
||||||
//
|
//
|
||||||
// See more here: https://github.com/iris-contrib/examples/tree/master/request_transactions
|
// See more here: https://github.com/iris-contrib/examples/tree/master/transactions
|
||||||
func (ctx *Context) BeginTransaction(pipe func(scope *RequestTransactionScope)) {
|
func (ctx *Context) BeginTransaction(pipe func(scope *TransactionScope)) {
|
||||||
|
// do NOT begin a transaction when the previous transaction has been failed
|
||||||
|
// and it was requested scoped or SkipTransactions called manually
|
||||||
|
if ctx.TransactionsSkipped() {
|
||||||
|
return
|
||||||
|
}
|
||||||
// not the best way but this should be do the job if we want multiple transaction in the same handler and context.
|
// 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
|
tempCtx := *ctx // clone the temp context
|
||||||
scope := &RequestTransactionScope{Context: &tempCtx}
|
scope := &TransactionScope{Context: &tempCtx}
|
||||||
pipe(scope) // run the context inside its scope
|
pipe(scope) // run the context inside its scope
|
||||||
*ctx = *scope.Context // copy back the context
|
*ctx = *scope.Context // copy back the context
|
||||||
|
|
||||||
|
if scope.isRequestScoped && scope.isFailure {
|
||||||
|
ctx.SkipTransactions()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log logs to the iris defined logger
|
// Log logs to the iris defined logger
|
||||||
|
|
|
@ -781,15 +781,17 @@ func TestTemplatesDisabled(t *testing.T) {
|
||||||
e.GET("/renderErr").Expect().Status(iris.StatusServiceUnavailable).Body().Equal(expctedErrMsg)
|
e.GET("/renderErr").Expect().Status(iris.StatusServiceUnavailable).Body().Equal(expctedErrMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRequestTransactions(t *testing.T) {
|
func TestTransactions(t *testing.T) {
|
||||||
iris.ResetDefault()
|
iris.ResetDefault()
|
||||||
firstTransictionFailureMessage := "Error: Virtual failure!!!"
|
firstTransictionFailureMessage := "Error: Virtual failure!!!"
|
||||||
secondTransictionSuccessHTMLMessage := "<h1>This will sent at all cases because it lives on different transaction and it doesn't fails</h1>"
|
secondTransictionSuccessHTMLMessage := "<h1>This will sent at all cases because it lives on different transaction and it doesn't fails</h1>"
|
||||||
persistMessage := "<h1>I persist show this message to the client!</h1>"
|
persistMessage := "<h1>I persist show this message to the client!</h1>"
|
||||||
expectedTestMessage := firstTransictionFailureMessage + secondTransictionSuccessHTMLMessage + persistMessage
|
|
||||||
|
|
||||||
iris.Get("/failFirsTransactionButSuccessSecond", func(ctx *iris.Context) {
|
maybeFailureTransaction := func(shouldFail bool, isRequestScoped bool) func(scope *iris.TransactionScope) {
|
||||||
ctx.BeginTransaction(func(scope *iris.RequestTransactionScope) {
|
return func(scope *iris.TransactionScope) {
|
||||||
|
// OPTIONAl, if true then the next transictions will not be executed if this transiction fails
|
||||||
|
scope.RequestScoped(isRequestScoped)
|
||||||
|
|
||||||
// OPTIONAL STEP:
|
// OPTIONAL STEP:
|
||||||
// create a new custom type of error here to keep track of the status code and reason message
|
// create a new custom type of error here to keep track of the status code and reason message
|
||||||
err := iris.NewErrWithStatus()
|
err := iris.NewErrWithStatus()
|
||||||
|
@ -808,8 +810,7 @@ func TestRequestTransactions(t *testing.T) {
|
||||||
// }
|
// }
|
||||||
// or err.AppendReason(firstErr.Error()) // ... err.Reason(dbErr.Error()).Status(500)
|
// or err.AppendReason(firstErr.Error()) // ... err.Reason(dbErr.Error()).Status(500)
|
||||||
|
|
||||||
// virtualize a fake error for the proof of concept
|
fail := shouldFail
|
||||||
fail := true
|
|
||||||
|
|
||||||
if fail {
|
if fail {
|
||||||
err.Status(iris.StatusInternalServerError).
|
err.Status(iris.StatusInternalServerError).
|
||||||
|
@ -822,25 +823,56 @@ func TestRequestTransactions(t *testing.T) {
|
||||||
// if the reason is empty then the transaction completed succesfuly,
|
// if the reason is empty then the transaction completed succesfuly,
|
||||||
// otherwise we rollback the whole response body and cookies and everything lives inside the scope.Request.
|
// otherwise we rollback the whole response body and cookies and everything lives inside the scope.Request.
|
||||||
scope.Complete(err)
|
scope.Complete(err)
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx.BeginTransaction(func(scope *iris.RequestTransactionScope) {
|
successTransaction := func(scope *iris.TransactionScope) {
|
||||||
scope.Context.HTML(iris.StatusOK,
|
scope.Context.HTML(iris.StatusOK,
|
||||||
secondTransictionSuccessHTMLMessage)
|
secondTransictionSuccessHTMLMessage)
|
||||||
// * if we don't have any 'throw error' logic then no need of scope.Complete()
|
// * if we don't have any 'throw error' logic then no need of scope.Complete()
|
||||||
})
|
}
|
||||||
|
|
||||||
|
persistMessageHandler := func(ctx *iris.Context) {
|
||||||
// OPTIONAL, depends on the usage:
|
// OPTIONAL, depends on the usage:
|
||||||
// at any case, what ever happens inside the context's transactions send this to the client
|
// at any case, what ever happens inside the context's transactions send this to the client
|
||||||
ctx.HTML(iris.StatusOK, persistMessage)
|
ctx.HTML(iris.StatusOK, persistMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
iris.Get("/failFirsTransactionButSuccessSecondWithPersistMessage", func(ctx *iris.Context) {
|
||||||
|
ctx.BeginTransaction(maybeFailureTransaction(true, false))
|
||||||
|
ctx.BeginTransaction(successTransaction)
|
||||||
|
persistMessageHandler(ctx)
|
||||||
|
})
|
||||||
|
|
||||||
|
iris.Get("/failFirsTransactionButSuccessSecond", func(ctx *iris.Context) {
|
||||||
|
ctx.BeginTransaction(maybeFailureTransaction(true, false))
|
||||||
|
ctx.BeginTransaction(successTransaction)
|
||||||
|
})
|
||||||
|
|
||||||
|
iris.Get("/failAllBecauseOfRequestScopeAndFailure", func(ctx *iris.Context) {
|
||||||
|
ctx.BeginTransaction(maybeFailureTransaction(true, true))
|
||||||
|
ctx.BeginTransaction(successTransaction)
|
||||||
})
|
})
|
||||||
|
|
||||||
e := httptest.New(iris.Default, t)
|
e := httptest.New(iris.Default, t)
|
||||||
|
|
||||||
|
e.GET("/failFirsTransactionButSuccessSecondWithPersistMessage").
|
||||||
|
Expect().
|
||||||
|
Status(iris.StatusOK).
|
||||||
|
ContentType("text/html", iris.Config.Charset).
|
||||||
|
Body().
|
||||||
|
Equal(firstTransictionFailureMessage + secondTransictionSuccessHTMLMessage + persistMessage)
|
||||||
|
|
||||||
e.GET("/failFirsTransactionButSuccessSecond").
|
e.GET("/failFirsTransactionButSuccessSecond").
|
||||||
Expect().
|
Expect().
|
||||||
Status(iris.StatusOK).
|
Status(iris.StatusOK).
|
||||||
ContentType("text/html", iris.Config.Charset).
|
ContentType("text/html", iris.Config.Charset).
|
||||||
Body().
|
Body().
|
||||||
Equal(expectedTestMessage)
|
Equal(firstTransictionFailureMessage + secondTransictionSuccessHTMLMessage)
|
||||||
|
|
||||||
|
e.GET("/failAllBecauseOfRequestScopeAndFailure").
|
||||||
|
Expect().
|
||||||
|
Status(iris.StatusInternalServerError).
|
||||||
|
Body().
|
||||||
|
Equal(firstTransictionFailureMessage)
|
||||||
}
|
}
|
||||||
|
|
2
iris.go
2
iris.go
|
@ -80,7 +80,7 @@ const (
|
||||||
// IsLongTermSupport flag is true when the below version number is a long-term-support version
|
// IsLongTermSupport flag is true when the below version number is a long-term-support version
|
||||||
IsLongTermSupport = false
|
IsLongTermSupport = false
|
||||||
// Version is the current version number of the Iris web framework
|
// Version is the current version number of the Iris web framework
|
||||||
Version = "5.1.0"
|
Version = "5.1.1"
|
||||||
|
|
||||||
banner = ` _____ _
|
banner = ` _____ _
|
||||||
|_ _| (_)
|
|_ _| (_)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user