1package middleware
2
3import (
4 "context"
5 "io"
6 "strings"
7)
8
9// Stack provides protocol and transport agnostic set of middleware split into
10// distinct steps. Steps have specific transitions between them, that are
11// managed by the individual step.
12//
13// Steps are composed as middleware around the underlying handler in the
14// following order:
15//
16// Initialize -> Serialize -> Build -> Finalize -> Deserialize -> Handler
17//
18// Any middleware within the chain may choose to stop and return an error or
19// response. Since the middleware decorate the handler like a call stack, each
20// middleware will receive the result of the next middleware in the chain.
21// Middleware that does not need to react to an input, or result must forward
22// along the input down the chain, or return the result back up the chain.
23//
24// Initialize <- Serialize -> Build -> Finalize <- Deserialize <- Handler
25type Stack struct {
26 // Initialize prepares the input, and sets any default parameters as
27 // needed, (e.g. idempotency token, and presigned URLs).
28 //
29 // Takes Input Parameters, and returns result or error.
30 //
31 // Receives result or error from Serialize step.
32 Initialize *InitializeStep
33
34 // Serialize serializes the prepared input into a data structure that can be consumed
35 // by the target transport's message, (e.g. REST-JSON serialization)
36 //
37 // Converts Input Parameters into a Request, and returns the result or error.
38 //
39 // Receives result or error from Build step.
40 Serialize *SerializeStep
41
42 // Build adds additional metadata to the serialized transport message
43 // (e.g. HTTP's Content-Length header, or body checksum). Decorations and
44 // modifications to the message should be copied to all message attempts.
45 //
46 // Takes Request, and returns result or error.
47 //
48 // Receives result or error from Finalize step.
49 Build *BuildStep
50
51 // Finalize performs final preparations needed before sending the message. The
52 // message should already be complete by this stage, and is only alternated
53 // to meet the expectations of the recipient (e.g. Retry and AWS SigV4
54 // request signing)
55 //
56 // Takes Request, and returns result or error.
57 //
58 // Receives result or error from Deserialize step.
59 Finalize *FinalizeStep
60
61 // Deserialize reacts to the handler's response returned by the recipient of the request
62 // message. Deserializes the response into a structured type or error above
63 // stacks can react to.
64 //
65 // Should only forward Request to underlying handler.
66 //
67 // Takes Request, and returns result or error.
68 //
69 // Receives raw response, or error from underlying handler.
70 Deserialize *DeserializeStep
71
72 id string
73}
74
75// NewStack returns an initialize empty stack.
76func NewStack(id string, newRequestFn func() interface{}) *Stack {
77 return &Stack{
78 id: id,
79 Initialize: NewInitializeStep(),
80 Serialize: NewSerializeStep(newRequestFn),
81 Build: NewBuildStep(),
82 Finalize: NewFinalizeStep(),
83 Deserialize: NewDeserializeStep(),
84 }
85}
86
87// ID returns the unique ID for the stack as a middleware.
88func (s *Stack) ID() string { return s.id }
89
90// HandleMiddleware invokes the middleware stack decorating the next handler.
91// Each step of stack will be invoked in order before calling the next step.
92// With the next handler call last.
93//
94// The input value must be the input parameters of the operation being
95// performed.
96//
97// Will return the result of the operation, or error.
98func (s *Stack) HandleMiddleware(ctx context.Context, input interface{}, next Handler) (
99 output interface{}, metadata Metadata, err error,
100) {
101 h := DecorateHandler(next,
102 s.Initialize,
103 s.Serialize,
104 s.Build,
105 s.Finalize,
106 s.Deserialize,
107 )
108
109 return h.Handle(ctx, input)
110}
111
112// List returns a list of all middleware in the stack by step.
113func (s *Stack) List() []string {
114 var l []string
115 l = append(l, s.id)
116
117 l = append(l, s.Initialize.ID())
118 l = append(l, s.Initialize.List()...)
119
120 l = append(l, s.Serialize.ID())
121 l = append(l, s.Serialize.List()...)
122
123 l = append(l, s.Build.ID())
124 l = append(l, s.Build.List()...)
125
126 l = append(l, s.Finalize.ID())
127 l = append(l, s.Finalize.List()...)
128
129 l = append(l, s.Deserialize.ID())
130 l = append(l, s.Deserialize.List()...)
131
132 return l
133}
134
135func (s *Stack) String() string {
136 var b strings.Builder
137
138 w := &indentWriter{w: &b}
139
140 w.WriteLine(s.id)
141 w.Push()
142
143 writeStepItems(w, s.Initialize)
144 writeStepItems(w, s.Serialize)
145 writeStepItems(w, s.Build)
146 writeStepItems(w, s.Finalize)
147 writeStepItems(w, s.Deserialize)
148
149 return b.String()
150}
151
152type stackStepper interface {
153 ID() string
154 List() []string
155}
156
157func writeStepItems(w *indentWriter, s stackStepper) {
158 type lister interface {
159 List() []string
160 }
161
162 w.WriteLine(s.ID())
163 w.Push()
164
165 defer w.Pop()
166
167 // ignore stack to prevent circular iterations
168 if _, ok := s.(*Stack); ok {
169 return
170 }
171
172 for _, id := range s.List() {
173 w.WriteLine(id)
174 }
175}
176
177type stringWriter interface {
178 io.Writer
179 WriteString(string) (int, error)
180 WriteRune(rune) (int, error)
181}
182
183type indentWriter struct {
184 w stringWriter
185 depth int
186}
187
188const indentDepth = "\t\t\t\t\t\t\t\t\t\t"
189
190func (w *indentWriter) Push() {
191 w.depth++
192}
193
194func (w *indentWriter) Pop() {
195 w.depth--
196 if w.depth < 0 {
197 w.depth = 0
198 }
199}
200
201func (w *indentWriter) WriteLine(v string) {
202 w.w.WriteString(indentDepth[:w.depth])
203
204 v = strings.ReplaceAll(v, "\n", "\\n")
205 v = strings.ReplaceAll(v, "\r", "\\r")
206
207 w.w.WriteString(v)
208 w.w.WriteRune('\n')
209}