1package request
2
3import (
4	"fmt"
5	"strings"
6)
7
8// A Handlers provides a collection of request handlers for various
9// stages of handling requests.
10type Handlers struct {
11	Validate         HandlerList
12	Build            HandlerList
13	Sign             HandlerList
14	Send             HandlerList
15	ValidateResponse HandlerList
16	Unmarshal        HandlerList
17	UnmarshalStream  HandlerList
18	UnmarshalMeta    HandlerList
19	UnmarshalError   HandlerList
20	Retry            HandlerList
21	AfterRetry       HandlerList
22	CompleteAttempt  HandlerList
23	Complete         HandlerList
24}
25
26// Copy returns a copy of this handler's lists.
27func (h *Handlers) Copy() Handlers {
28	return Handlers{
29		Validate:         h.Validate.copy(),
30		Build:            h.Build.copy(),
31		Sign:             h.Sign.copy(),
32		Send:             h.Send.copy(),
33		ValidateResponse: h.ValidateResponse.copy(),
34		Unmarshal:        h.Unmarshal.copy(),
35		UnmarshalStream:  h.UnmarshalStream.copy(),
36		UnmarshalError:   h.UnmarshalError.copy(),
37		UnmarshalMeta:    h.UnmarshalMeta.copy(),
38		Retry:            h.Retry.copy(),
39		AfterRetry:       h.AfterRetry.copy(),
40		CompleteAttempt:  h.CompleteAttempt.copy(),
41		Complete:         h.Complete.copy(),
42	}
43}
44
45// Clear removes callback functions for all handlers.
46func (h *Handlers) Clear() {
47	h.Validate.Clear()
48	h.Build.Clear()
49	h.Send.Clear()
50	h.Sign.Clear()
51	h.Unmarshal.Clear()
52	h.UnmarshalStream.Clear()
53	h.UnmarshalMeta.Clear()
54	h.UnmarshalError.Clear()
55	h.ValidateResponse.Clear()
56	h.Retry.Clear()
57	h.AfterRetry.Clear()
58	h.CompleteAttempt.Clear()
59	h.Complete.Clear()
60}
61
62// IsEmpty returns if there are no handlers in any of the handlerlists.
63func (h *Handlers) IsEmpty() bool {
64	if h.Validate.Len() != 0 {
65		return false
66	}
67	if h.Build.Len() != 0 {
68		return false
69	}
70	if h.Send.Len() != 0 {
71		return false
72	}
73	if h.Sign.Len() != 0 {
74		return false
75	}
76	if h.Unmarshal.Len() != 0 {
77		return false
78	}
79	if h.UnmarshalStream.Len() != 0 {
80		return false
81	}
82	if h.UnmarshalMeta.Len() != 0 {
83		return false
84	}
85	if h.UnmarshalError.Len() != 0 {
86		return false
87	}
88	if h.ValidateResponse.Len() != 0 {
89		return false
90	}
91	if h.Retry.Len() != 0 {
92		return false
93	}
94	if h.AfterRetry.Len() != 0 {
95		return false
96	}
97	if h.CompleteAttempt.Len() != 0 {
98		return false
99	}
100	if h.Complete.Len() != 0 {
101		return false
102	}
103
104	return true
105}
106
107// A HandlerListRunItem represents an entry in the HandlerList which
108// is being run.
109type HandlerListRunItem struct {
110	Index   int
111	Handler NamedHandler
112	Request *Request
113}
114
115// A HandlerList manages zero or more handlers in a list.
116type HandlerList struct {
117	list []NamedHandler
118
119	// Called after each request handler in the list is called. If set
120	// and the func returns true the HandlerList will continue to iterate
121	// over the request handlers. If false is returned the HandlerList
122	// will stop iterating.
123	//
124	// Should be used if extra logic to be performed between each handler
125	// in the list. This can be used to terminate a list's iteration
126	// based on a condition such as error like, HandlerListStopOnError.
127	// Or for logging like HandlerListLogItem.
128	AfterEachFn func(item HandlerListRunItem) bool
129}
130
131// A NamedHandler is a struct that contains a name and function callback.
132type NamedHandler struct {
133	Name string
134	Fn   func(*Request)
135}
136
137// copy creates a copy of the handler list.
138func (l *HandlerList) copy() HandlerList {
139	n := HandlerList{
140		AfterEachFn: l.AfterEachFn,
141	}
142	if len(l.list) == 0 {
143		return n
144	}
145
146	n.list = append(make([]NamedHandler, 0, len(l.list)), l.list...)
147	return n
148}
149
150// Clear clears the handler list.
151func (l *HandlerList) Clear() {
152	l.list = l.list[0:0]
153}
154
155// Len returns the number of handlers in the list.
156func (l *HandlerList) Len() int {
157	return len(l.list)
158}
159
160// PushBack pushes handler f to the back of the handler list.
161func (l *HandlerList) PushBack(f func(*Request)) {
162	l.PushBackNamed(NamedHandler{"__anonymous", f})
163}
164
165// PushBackNamed pushes named handler f to the back of the handler list.
166func (l *HandlerList) PushBackNamed(n NamedHandler) {
167	if cap(l.list) == 0 {
168		l.list = make([]NamedHandler, 0, 5)
169	}
170	l.list = append(l.list, n)
171}
172
173// PushFront pushes handler f to the front of the handler list.
174func (l *HandlerList) PushFront(f func(*Request)) {
175	l.PushFrontNamed(NamedHandler{"__anonymous", f})
176}
177
178// PushFrontNamed pushes named handler f to the front of the handler list.
179func (l *HandlerList) PushFrontNamed(n NamedHandler) {
180	if cap(l.list) == len(l.list) {
181		// Allocating new list required
182		l.list = append([]NamedHandler{n}, l.list...)
183	} else {
184		// Enough room to prepend into list.
185		l.list = append(l.list, NamedHandler{})
186		copy(l.list[1:], l.list)
187		l.list[0] = n
188	}
189}
190
191// Remove removes a NamedHandler n
192func (l *HandlerList) Remove(n NamedHandler) {
193	l.RemoveByName(n.Name)
194}
195
196// RemoveByName removes a NamedHandler by name.
197func (l *HandlerList) RemoveByName(name string) {
198	for i := 0; i < len(l.list); i++ {
199		m := l.list[i]
200		if m.Name == name {
201			// Shift array preventing creating new arrays
202			copy(l.list[i:], l.list[i+1:])
203			l.list[len(l.list)-1] = NamedHandler{}
204			l.list = l.list[:len(l.list)-1]
205
206			// decrement list so next check to length is correct
207			i--
208		}
209	}
210}
211
212// SwapNamed will swap out any existing handlers with the same name as the
213// passed in NamedHandler returning true if handlers were swapped. False is
214// returned otherwise.
215func (l *HandlerList) SwapNamed(n NamedHandler) (swapped bool) {
216	for i := 0; i < len(l.list); i++ {
217		if l.list[i].Name == n.Name {
218			l.list[i].Fn = n.Fn
219			swapped = true
220		}
221	}
222
223	return swapped
224}
225
226// Swap will swap out all handlers matching the name passed in. The matched
227// handlers will be swapped in. True is returned if the handlers were swapped.
228func (l *HandlerList) Swap(name string, replace NamedHandler) bool {
229	var swapped bool
230
231	for i := 0; i < len(l.list); i++ {
232		if l.list[i].Name == name {
233			l.list[i] = replace
234			swapped = true
235		}
236	}
237
238	return swapped
239}
240
241// SetBackNamed will replace the named handler if it exists in the handler list.
242// If the handler does not exist the handler will be added to the end of the list.
243func (l *HandlerList) SetBackNamed(n NamedHandler) {
244	if !l.SwapNamed(n) {
245		l.PushBackNamed(n)
246	}
247}
248
249// SetFrontNamed will replace the named handler if it exists in the handler list.
250// If the handler does not exist the handler will be added to the beginning of
251// the list.
252func (l *HandlerList) SetFrontNamed(n NamedHandler) {
253	if !l.SwapNamed(n) {
254		l.PushFrontNamed(n)
255	}
256}
257
258// Run executes all handlers in the list with a given request object.
259func (l *HandlerList) Run(r *Request) {
260	for i, h := range l.list {
261		h.Fn(r)
262		item := HandlerListRunItem{
263			Index: i, Handler: h, Request: r,
264		}
265		if l.AfterEachFn != nil && !l.AfterEachFn(item) {
266			return
267		}
268	}
269}
270
271// HandlerListLogItem logs the request handler and the state of the
272// request's Error value. Always returns true to continue iterating
273// request handlers in a HandlerList.
274func HandlerListLogItem(item HandlerListRunItem) bool {
275	if item.Request.Config.Logger == nil {
276		return true
277	}
278	item.Request.Config.Logger.Log("DEBUG: RequestHandler",
279		item.Index, item.Handler.Name, item.Request.Error)
280
281	return true
282}
283
284// HandlerListStopOnError returns false to stop the HandlerList iterating
285// over request handlers if Request.Error is not nil. True otherwise
286// to continue iterating.
287func HandlerListStopOnError(item HandlerListRunItem) bool {
288	return item.Request.Error == nil
289}
290
291// WithAppendUserAgent will add a string to the user agent prefixed with a
292// single white space.
293func WithAppendUserAgent(s string) Option {
294	return func(r *Request) {
295		r.Handlers.Build.PushBack(func(r2 *Request) {
296			AddToUserAgent(r, s)
297		})
298	}
299}
300
301// MakeAddToUserAgentHandler will add the name/version pair to the User-Agent request
302// header. If the extra parameters are provided they will be added as metadata to the
303// name/version pair resulting in the following format.
304// "name/version (extra0; extra1; ...)"
305// The user agent part will be concatenated with this current request's user agent string.
306func MakeAddToUserAgentHandler(name, version string, extra ...string) func(*Request) {
307	ua := fmt.Sprintf("%s/%s", name, version)
308	if len(extra) > 0 {
309		ua += fmt.Sprintf(" (%s)", strings.Join(extra, "; "))
310	}
311	return func(r *Request) {
312		AddToUserAgent(r, ua)
313	}
314}
315
316// MakeAddToUserAgentFreeFormHandler adds the input to the User-Agent request header.
317// The input string will be concatenated with the current request's user agent string.
318func MakeAddToUserAgentFreeFormHandler(s string) func(*Request) {
319	return func(r *Request) {
320		AddToUserAgent(r, s)
321	}
322}
323