aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/aws/smithy-go/middleware/ordered_group.go
blob: 4b195308c599b7889cc513c6918b4375dd29a200 (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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
package middleware

import "fmt"

// RelativePosition provides specifying the relative position of a middleware
// in an ordered group.
type RelativePosition int

// Relative position for middleware in steps.
const (
	After RelativePosition = iota
	Before
)

type ider interface {
	ID() string
}

// orderedIDs provides an ordered collection of items with relative ordering
// by name.
type orderedIDs struct {
	order *relativeOrder
	items map[string]ider
}

const baseOrderedItems = 5

func newOrderedIDs() *orderedIDs {
	return &orderedIDs{
		order: newRelativeOrder(),
		items: make(map[string]ider, baseOrderedItems),
	}
}

// Add injects the item to the relative position of the item group. Returns an
// error if the item already exists.
func (g *orderedIDs) Add(m ider, pos RelativePosition) error {
	id := m.ID()
	if len(id) == 0 {
		return fmt.Errorf("empty ID, ID must not be empty")
	}

	if err := g.order.Add(pos, id); err != nil {
		return err
	}

	g.items[id] = m
	return nil
}

// Insert injects the item relative to an existing item id. Returns an error if
// the original item does not exist, or the item being added already exists.
func (g *orderedIDs) Insert(m ider, relativeTo string, pos RelativePosition) error {
	if len(m.ID()) == 0 {
		return fmt.Errorf("insert ID must not be empty")
	}
	if len(relativeTo) == 0 {
		return fmt.Errorf("relative to ID must not be empty")
	}

	if err := g.order.Insert(relativeTo, pos, m.ID()); err != nil {
		return err
	}

	g.items[m.ID()] = m
	return nil
}

// Get returns the ider identified by id. If ider is not present, returns false.
func (g *orderedIDs) Get(id string) (ider, bool) {
	v, ok := g.items[id]
	return v, ok
}

// Swap removes the item by id, replacing it with the new item. Returns an error
// if the original item doesn't exist.
func (g *orderedIDs) Swap(id string, m ider) (ider, error) {
	if len(id) == 0 {
		return nil, fmt.Errorf("swap from ID must not be empty")
	}

	iderID := m.ID()
	if len(iderID) == 0 {
		return nil, fmt.Errorf("swap to ID must not be empty")
	}

	if err := g.order.Swap(id, iderID); err != nil {
		return nil, err
	}

	removed := g.items[id]

	delete(g.items, id)
	g.items[iderID] = m

	return removed, nil
}

// Remove removes the item by id. Returns an error if the item
// doesn't exist.
func (g *orderedIDs) Remove(id string) (ider, error) {
	if len(id) == 0 {
		return nil, fmt.Errorf("remove ID must not be empty")
	}

	if err := g.order.Remove(id); err != nil {
		return nil, err
	}

	removed := g.items[id]
	delete(g.items, id)
	return removed, nil
}

func (g *orderedIDs) List() []string {
	items := g.order.List()
	order := make([]string, len(items))
	copy(order, items)
	return order
}

// Clear removes all entries and slots.
func (g *orderedIDs) Clear() {
	g.order.Clear()
	g.items = map[string]ider{}
}

// GetOrder returns the item in the order it should be invoked in.
func (g *orderedIDs) GetOrder() []interface{} {
	order := g.order.List()
	ordered := make([]interface{}, len(order))
	for i := 0; i < len(order); i++ {
		ordered[i] = g.items[order[i]]
	}

	return ordered
}

// relativeOrder provides ordering of item
type relativeOrder struct {
	order []string
}

func newRelativeOrder() *relativeOrder {
	return &relativeOrder{
		order: make([]string, 0, baseOrderedItems),
	}
}

// Add inserts an item into the order relative to the position provided.
func (s *relativeOrder) Add(pos RelativePosition, ids ...string) error {
	if len(ids) == 0 {
		return nil
	}

	for _, id := range ids {
		if _, ok := s.has(id); ok {
			return fmt.Errorf("already exists, %v", id)
		}
	}

	switch pos {
	case Before:
		return s.insert(0, Before, ids...)

	case After:
		s.order = append(s.order, ids...)

	default:
		return fmt.Errorf("invalid position, %v", int(pos))
	}

	return nil
}

// Insert injects an item before or after the relative item. Returns
// an error if the relative item does not exist.
func (s *relativeOrder) Insert(relativeTo string, pos RelativePosition, ids ...string) error {
	if len(ids) == 0 {
		return nil
	}

	for _, id := range ids {
		if _, ok := s.has(id); ok {
			return fmt.Errorf("already exists, %v", id)
		}
	}

	i, ok := s.has(relativeTo)
	if !ok {
		return fmt.Errorf("not found, %v", relativeTo)
	}

	return s.insert(i, pos, ids...)
}

// Swap will replace the item id with the to item. Returns an
// error if the original item id does not exist. Allows swapping out an
// item for another item with the same id.
func (s *relativeOrder) Swap(id, to string) error {
	i, ok := s.has(id)
	if !ok {
		return fmt.Errorf("not found, %v", id)
	}

	if _, ok = s.has(to); ok && id != to {
		return fmt.Errorf("already exists, %v", to)
	}

	s.order[i] = to
	return nil
}

func (s *relativeOrder) Remove(id string) error {
	i, ok := s.has(id)
	if !ok {
		return fmt.Errorf("not found, %v", id)
	}

	s.order = append(s.order[:i], s.order[i+1:]...)
	return nil
}

func (s *relativeOrder) List() []string {
	return s.order
}

func (s *relativeOrder) Clear() {
	s.order = s.order[0:0]
}

func (s *relativeOrder) insert(i int, pos RelativePosition, ids ...string) error {
	switch pos {
	case Before:
		n := len(ids)
		var src []string
		if n <= cap(s.order)-len(s.order) {
			s.order = s.order[:len(s.order)+n]
			src = s.order
		} else {
			src = s.order
			s.order = make([]string, len(s.order)+n)
			copy(s.order[:i], src[:i]) // only when allocating a new slice do we need to copy the front half
		}
		copy(s.order[i+n:], src[i:])
		copy(s.order[i:], ids)
	case After:
		if i == len(s.order)-1 || len(s.order) == 0 {
			s.order = append(s.order, ids...)
		} else {
			s.order = append(s.order[:i+1], append(ids, s.order[i+1:]...)...)
		}

	default:
		return fmt.Errorf("invalid position, %v", int(pos))
	}

	return nil
}

func (s *relativeOrder) has(id string) (i int, found bool) {
	for i := 0; i < len(s.order); i++ {
		if s.order[i] == id {
			return i, true
		}
	}
	return 0, false
}