aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/aws/smithy-go/middleware/stack.go
blob: 45ccb5b93c9f47dbc968be785b19cc58a1af2632 (plain) (blame)
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
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package middleware

import (
	"context"
	"io"
	"strings"
)

// Stack provides protocol and transport agnostic set of middleware split into
// distinct steps. Steps have specific transitions between them, that are
// managed by the individual step.
//
// Steps are composed as middleware around the underlying handler in the
// following order:
//
//   Initialize -> Serialize -> Build -> Finalize -> Deserialize -> Handler
//
// Any middleware within the chain may choose to stop and return an error or
// response. Since the middleware decorate the handler like a call stack, each
// middleware will receive the result of the next middleware in the chain.
// Middleware that does not need to react to an input, or result must forward
// along the input down the chain, or return the result back up the chain.
//
//   Initialize <- Serialize -> Build -> Finalize <- Deserialize <- Handler
type Stack struct {
	// Initialize prepares the input, and sets any default parameters as
	// needed, (e.g. idempotency token, and presigned URLs).
	//
	// Takes Input Parameters, and returns result or error.
	//
	// Receives result or error from Serialize step.
	Initialize *InitializeStep

	// Serialize serializes the prepared input into a data structure that can be consumed
	// by the target transport's message, (e.g. REST-JSON serialization)
	//
	// Converts Input Parameters into a Request, and returns the result or error.
	//
	// Receives result or error from Build step.
	Serialize *SerializeStep

	// Build adds additional metadata to the serialized transport message
	// (e.g. HTTP's Content-Length header, or body checksum). Decorations and
	// modifications to the message should be copied to all message attempts.
	//
	// Takes Request, and returns result or error.
	//
	// Receives result or error from Finalize step.
	Build *BuildStep

	// Finalize performs final preparations needed before sending the message. The
	// message should already be complete by this stage, and is only alternated
	// to meet the expectations of the recipient (e.g. Retry and AWS SigV4
	// request signing)
	//
	// Takes Request, and returns result or error.
	//
	// Receives result or error from Deserialize step.
	Finalize *FinalizeStep

	// Deserialize reacts to the handler's response returned by the recipient of the request
	// message. Deserializes the response into a structured type or error above
	// stacks can react to.
	//
	// Should only forward Request to underlying handler.
	//
	// Takes Request, and returns result or error.
	//
	// Receives raw response, or error from underlying handler.
	Deserialize *DeserializeStep

	id string
}

// NewStack returns an initialize empty stack.
func NewStack(id string, newRequestFn func() interface{}) *Stack {
	return &Stack{
		id:          id,
		Initialize:  NewInitializeStep(),
		Serialize:   NewSerializeStep(newRequestFn),
		Build:       NewBuildStep(),
		Finalize:    NewFinalizeStep(),
		Deserialize: NewDeserializeStep(),
	}
}

// ID returns the unique ID for the stack as a middleware.
func (s *Stack) ID() string { return s.id }

// HandleMiddleware invokes the middleware stack decorating the next handler.
// Each step of stack will be invoked in order before calling the next step.
// With the next handler call last.
//
// The input value must be the input parameters of the operation being
// performed.
//
// Will return the result of the operation, or error.
func (s *Stack) HandleMiddleware(ctx context.Context, input interface{}, next Handler) (
	output interface{}, metadata Metadata, err error,
) {
	h := DecorateHandler(next,
		s.Initialize,
		s.Serialize,
		s.Build,
		s.Finalize,
		s.Deserialize,
	)

	return h.Handle(ctx, input)
}

// List returns a list of all middleware in the stack by step.
func (s *Stack) List() []string {
	var l []string
	l = append(l, s.id)

	l = append(l, s.Initialize.ID())
	l = append(l, s.Initialize.List()...)

	l = append(l, s.Serialize.ID())
	l = append(l, s.Serialize.List()...)

	l = append(l, s.Build.ID())
	l = append(l, s.Build.List()...)

	l = append(l, s.Finalize.ID())
	l = append(l, s.Finalize.List()...)

	l = append(l, s.Deserialize.ID())
	l = append(l, s.Deserialize.List()...)

	return l
}

func (s *Stack) String() string {
	var b strings.Builder

	w := &indentWriter{w: &b}

	w.WriteLine(s.id)
	w.Push()

	writeStepItems(w, s.Initialize)
	writeStepItems(w, s.Serialize)
	writeStepItems(w, s.Build)
	writeStepItems(w, s.Finalize)
	writeStepItems(w, s.Deserialize)

	return b.String()
}

type stackStepper interface {
	ID() string
	List() []string
}

func writeStepItems(w *indentWriter, s stackStepper) {
	type lister interface {
		List() []string
	}

	w.WriteLine(s.ID())
	w.Push()

	defer w.Pop()

	// ignore stack to prevent circular iterations
	if _, ok := s.(*Stack); ok {
		return
	}

	for _, id := range s.List() {
		w.WriteLine(id)
	}
}

type stringWriter interface {
	io.Writer
	WriteString(string) (int, error)
	WriteRune(rune) (int, error)
}

type indentWriter struct {
	w     stringWriter
	depth int
}

const indentDepth = "\t\t\t\t\t\t\t\t\t\t"

func (w *indentWriter) Push() {
	w.depth++
}

func (w *indentWriter) Pop() {
	w.depth--
	if w.depth < 0 {
		w.depth = 0
	}
}

func (w *indentWriter) WriteLine(v string) {
	w.w.WriteString(indentDepth[:w.depth])

	v = strings.ReplaceAll(v, "\n", "\\n")
	v = strings.ReplaceAll(v, "\r", "\\r")

	w.w.WriteString(v)
	w.w.WriteRune('\n')
}