transaction.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package context
  2. // TransactionErrResult could be named also something like 'MaybeError',
  3. // it is useful to send it on transaction.Complete in order to execute a custom error mesasge to the user.
  4. //
  5. // in simple words it's just a 'traveler message' between the transaction and its scope.
  6. // it is totally optional
  7. type TransactionErrResult struct {
  8. StatusCode int
  9. // if reason is empty then the already relative registered (custom or not)
  10. // error will be executed if the scope allows that.
  11. Reason string
  12. ContentType string
  13. }
  14. // Error returns the reason given by the user or an empty string
  15. func (err TransactionErrResult) Error() string {
  16. return err.Reason
  17. }
  18. // IsFailure returns true if this is an actual error
  19. func (err TransactionErrResult) IsFailure() bool {
  20. return StatusCodeNotSuccessful(err.StatusCode)
  21. }
  22. // NewTransactionErrResult returns a new transaction result with the given error message,
  23. // it can be empty too, but if not then the transaction's scope is decided what to do with that
  24. func NewTransactionErrResult() TransactionErrResult {
  25. return TransactionErrResult{}
  26. }
  27. // TransactionScope is the manager of the transaction's response, can be resseted and skipped
  28. // from its parent context or execute an error or skip other transactions
  29. type TransactionScope interface {
  30. // EndTransaction returns if can continue to the next transactions or not (false)
  31. // called after Complete, empty or not empty error
  32. EndTransaction(maybeErr TransactionErrResult, ctx Context) bool
  33. }
  34. // TransactionScopeFunc the transaction's scope signature
  35. type TransactionScopeFunc func(maybeErr TransactionErrResult, ctx Context) bool
  36. // EndTransaction ends the transaction with a callback to itself, implements the TransactionScope interface
  37. func (tsf TransactionScopeFunc) EndTransaction(maybeErr TransactionErrResult, ctx Context) bool {
  38. return tsf(maybeErr, ctx)
  39. }
  40. // +------------------------------------------------------------+
  41. // | Transaction Implementation |
  42. // +------------------------------------------------------------+
  43. // Transaction gives the users the opportunity to code their route handlers cleaner and safier
  44. // it receives a scope which is decided when to send an error to the user, recover from panics
  45. // stop the execution of the next transactions and so on...
  46. //
  47. // it's default scope is the TransientTransactionScope which is silently
  48. // skips the current transaction's response if transaction.Complete accepts a non-empty error.
  49. //
  50. // Create and set custom transactions scopes with transaction.SetScope.
  51. //
  52. // For more information please visit the tests.
  53. type Transaction struct {
  54. context Context
  55. parent Context
  56. hasError bool
  57. scope TransactionScope
  58. }
  59. func newTransaction(from *context) *Transaction {
  60. tempCtx := *from
  61. writer := tempCtx.ResponseWriter().Clone()
  62. tempCtx.ResetResponseWriter(writer)
  63. t := &Transaction{
  64. parent: from,
  65. context: &tempCtx,
  66. scope: TransientTransactionScope,
  67. }
  68. return t
  69. }
  70. // Context returns the current context of the transaction.
  71. func (t *Transaction) Context() Context {
  72. return t.context
  73. }
  74. // SetScope sets the current transaction's scope
  75. // iris.RequestTransactionScope || iris.TransientTransactionScope (default).
  76. func (t *Transaction) SetScope(scope TransactionScope) {
  77. t.scope = scope
  78. }
  79. // Complete completes the transaction
  80. // rollback and send an error when the error is not empty.
  81. // The next steps depends on its Scope.
  82. //
  83. // The error can be a type of context.NewTransactionErrResult().
  84. func (t *Transaction) Complete(err error) {
  85. maybeErr := TransactionErrResult{}
  86. if err != nil {
  87. t.hasError = true
  88. statusCode := 400 // bad request
  89. reason := err.Error()
  90. cType := "text/plain; charset=" + t.context.Application().ConfigurationReadOnly().GetCharset()
  91. if errWstatus, ok := err.(TransactionErrResult); ok {
  92. if errWstatus.StatusCode > 0 {
  93. statusCode = errWstatus.StatusCode
  94. }
  95. if errWstatus.Reason != "" {
  96. reason = errWstatus.Reason
  97. }
  98. // get the content type used on this transaction
  99. if cTypeH := t.context.ResponseWriter().Header().Get(ContentTypeHeaderKey); cTypeH != "" {
  100. cType = cTypeH
  101. }
  102. }
  103. maybeErr.StatusCode = statusCode
  104. maybeErr.Reason = reason
  105. maybeErr.ContentType = cType
  106. }
  107. // the transaction ends with error or not error, it decides what to do next with its Response
  108. // the Response is appended to the parent context an all cases but it checks for empty body,headers and all that,
  109. // if they are empty (silent error or not error at all)
  110. // then all transaction's actions are skipped as expected
  111. canContinue := t.scope.EndTransaction(maybeErr, t.context)
  112. if !canContinue {
  113. t.parent.SkipTransactions()
  114. }
  115. }
  116. // TransientTransactionScope explanation:
  117. //
  118. // independent 'silent' scope, if transaction fails (if transaction.IsFailure() == true)
  119. // then its response is not written to the real context no error is provided to the user.
  120. // useful for the most cases.
  121. var TransientTransactionScope = TransactionScopeFunc(func(maybeErr TransactionErrResult, ctx Context) bool {
  122. if maybeErr.IsFailure() {
  123. ctx.Recorder().Reset() // this response is skipped because it's empty.
  124. }
  125. return true
  126. })
  127. // RequestTransactionScope explanation:
  128. //
  129. // if scope fails (if transaction.IsFailure() == true)
  130. // then the rest of the context's response (transaction or normal flow)
  131. // is not written to the client, and an error status code is written instead.
  132. var RequestTransactionScope = TransactionScopeFunc(func(maybeErr TransactionErrResult, ctx Context) bool {
  133. if maybeErr.IsFailure() {
  134. // we need to register a beforeResponseFlush event here in order
  135. // to execute last the FireStatusCode
  136. // (which will reset the whole response's body, status code and headers setted from normal flow or other transactions too)
  137. ctx.ResponseWriter().SetBeforeFlush(func() {
  138. // we need to re-take the context's response writer
  139. // because inside here the response writer is changed to the original's
  140. // look ~context:1306
  141. w := ctx.ResponseWriter().(*ResponseRecorder)
  142. if maybeErr.Reason != "" {
  143. // send the error with the info user provided
  144. w.SetBodyString(maybeErr.Reason)
  145. w.WriteHeader(maybeErr.StatusCode)
  146. ctx.ContentType(maybeErr.ContentType)
  147. } else {
  148. // else execute the registered user error and skip the next transactions and all normal flow,
  149. ctx.StatusCode(maybeErr.StatusCode)
  150. ctx.StopExecution()
  151. }
  152. })
  153. return false
  154. }
  155. return true
  156. })