This repository has been archived by the owner on Jun 18, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
chain.go
183 lines (155 loc) · 4.65 KB
/
chain.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
// Goro
//
// Created by Yakka
// http://theyakka.com
//
// Copyright (c) 2019 Yakka LLC.
// All rights reserved.
// See the LICENSE file for licensing details and requirements.
package goro
import (
"net/http"
)
// ChainStatus - the status of the chain
type ChainStatus int
const handlerIndexStateKey = "_goro.chainHandlerIndex"
const (
// ChainCompleted - the chain completed normally
ChainCompleted ChainStatus = 1 << iota
// ChainError - the chain was stopped because of an error
ChainError
// ChainHalted - the chain was halted before it could finish executing
ChainHalted
)
// ChainResult - the chain execution result
type ChainResult struct {
Status ChainStatus
Error error
StatusCode int
}
type ChainHandler func(*Chain, *HandlerContext)
// ChainCompletedFunc - callback function executed when chain execution has
// completed
type ChainCompletedFunc func(result ChainResult)
// Chain allows for chaining of Handlers
type Chain struct {
router *Router
// RouterCatchesErrors - if true and the chain is attached to a router then
// errors will bubble up to the router error handler
RouterCatchesErrors bool
// EmitHTTPError - if true, the router will emit an http.Error when the chain
// result is an error
EmitHTTPError bool
// Handlers - the handlers in the Chain
handlers []ChainHandler
completedCallback ChainCompletedFunc
// ChainCompletedFunc - called when chain completes
ChainCompletedFunc ChainCompletedFunc
}
// NewChain - creates a new Chain instance
func NewChain(router *Router, handlers ...ChainHandler) Chain {
return Chain{
RouterCatchesErrors: true,
EmitHTTPError: true,
handlers: handlers,
router: router,
}
}
func HC(router *Router, handlers ...ChainHandler) Chain {
return NewChain(router, handlers...)
}
// Append - returns a new chain with the ChainHandler appended to
// the list of handlers
func (ch *Chain) Append(handlers ...ChainHandler) Chain {
allHandlers := make([]ChainHandler, 0, len(ch.handlers)+len(handlers))
allHandlers = append(allHandlers, ch.handlers...)
allHandlers = append(allHandlers, handlers...)
newChain := copyChain(*ch)
newChain.handlers = allHandlers
return newChain
}
// Then - calls the chain and then the designated Handler
func (ch Chain) Then(handler ContextHandlerFunc) ContextHandlerFunc {
return func(ctx *HandlerContext) {
cChain := ch.Copy()
cChain.completedCallback = func(result ChainResult) {
if result.Status != ChainError {
handler(ctx)
}
}
cChain.startChain(ctx)
}
}
// Call - calls the chain
func (ch Chain) Call() ContextHandlerFunc {
return func(ctx *HandlerContext) {
cChain := ch.Copy()
cChain.startChain(ctx)
}
}
func (ch *Chain) startChain(ctx *HandlerContext) {
ch.resetState(ctx)
ch.handlers[0](ch, ctx)
}
func (ch *Chain) doNext(ctx *HandlerContext) {
hIdx := ctx.state[handlerIndexStateKey].(int)
hIdx++
ctx.SetState(handlerIndexStateKey, hIdx)
handlersCount := len(ch.handlers)
if hIdx >= handlersCount {
// nothing to execute. notify that the chain has finished
finish(ch, ctx, ChainCompleted, nil, 0)
return
}
// execute the current chain handler
ch.handlers[hIdx](ch, ctx)
}
// Next - execute the next handler in the chain
func (ch *Chain) Next(ctx *HandlerContext) {
ch.doNext(ctx)
}
// Halt - halt chain execution
func (ch *Chain) Halt(ctx *HandlerContext) {
finish(ch, ctx, ChainHalted, nil, 0)
}
// Error - halt the chain and report an error
func (ch *Chain) Error(ctx *HandlerContext, chainError error, statusCode int) {
finish(ch, ctx, ChainError, chainError, statusCode)
if ch.router != nil && ch.RouterCatchesErrors {
ch.router.emitError(ctx, statusCode, chainError.Error(), ChainGenericErrorCode, chainError)
} else if ch.EmitHTTPError {
http.Error(ctx.ResponseWriter, chainError.Error(), statusCode)
}
}
func (ch Chain) Copy() Chain {
return copyChain(ch)
}
// reset - resets the chain
func (ch *Chain) resetState(ctx *HandlerContext) {
ctx.SetState(handlerIndexStateKey, 0)
}
func finish(chain *Chain, ctx *HandlerContext, status ChainStatus, chainError error, statusCode int) ChainResult {
result := ChainResult{
Status: status,
Error: chainError,
StatusCode: statusCode,
}
if chain.completedCallback != nil {
chain.completedCallback(result)
}
if chain.ChainCompletedFunc != nil {
chain.ChainCompletedFunc(result)
}
chain.resetState(ctx)
return result
}
func copyChain(chain Chain) Chain {
return Chain{
RouterCatchesErrors: chain.RouterCatchesErrors,
EmitHTTPError: chain.EmitHTTPError,
router: chain.router,
handlers: chain.handlers,
ChainCompletedFunc: chain.ChainCompletedFunc,
completedCallback: chain.completedCallback,
}
}