1package middleware
2
3import "fmt"
4
5// RelativePosition provides specifying the relative position of a middleware
6// in an ordered group.
7type RelativePosition int
8
9// Relative position for middleware in steps.
10const (
11 After RelativePosition = iota
12 Before
13)
14
15type ider interface {
16 ID() string
17}
18
19// orderedIDs provides an ordered collection of items with relative ordering
20// by name.
21type orderedIDs struct {
22 order *relativeOrder
23 items map[string]ider
24}
25
26const baseOrderedItems = 5
27
28func newOrderedIDs() *orderedIDs {
29 return &orderedIDs{
30 order: newRelativeOrder(),
31 items: make(map[string]ider, baseOrderedItems),
32 }
33}
34
35// Add injects the item to the relative position of the item group. Returns an
36// error if the item already exists.
37func (g *orderedIDs) Add(m ider, pos RelativePosition) error {
38 id := m.ID()
39 if len(id) == 0 {
40 return fmt.Errorf("empty ID, ID must not be empty")
41 }
42
43 if err := g.order.Add(pos, id); err != nil {
44 return err
45 }
46
47 g.items[id] = m
48 return nil
49}
50
51// Insert injects the item relative to an existing item id. Returns an error if
52// the original item does not exist, or the item being added already exists.
53func (g *orderedIDs) Insert(m ider, relativeTo string, pos RelativePosition) error {
54 if len(m.ID()) == 0 {
55 return fmt.Errorf("insert ID must not be empty")
56 }
57 if len(relativeTo) == 0 {
58 return fmt.Errorf("relative to ID must not be empty")
59 }
60
61 if err := g.order.Insert(relativeTo, pos, m.ID()); err != nil {
62 return err
63 }
64
65 g.items[m.ID()] = m
66 return nil
67}
68
69// Get returns the ider identified by id. If ider is not present, returns false.
70func (g *orderedIDs) Get(id string) (ider, bool) {
71 v, ok := g.items[id]
72 return v, ok
73}
74
75// Swap removes the item by id, replacing it with the new item. Returns an error
76// if the original item doesn't exist.
77func (g *orderedIDs) Swap(id string, m ider) (ider, error) {
78 if len(id) == 0 {
79 return nil, fmt.Errorf("swap from ID must not be empty")
80 }
81
82 iderID := m.ID()
83 if len(iderID) == 0 {
84 return nil, fmt.Errorf("swap to ID must not be empty")
85 }
86
87 if err := g.order.Swap(id, iderID); err != nil {
88 return nil, err
89 }
90
91 removed := g.items[id]
92
93 delete(g.items, id)
94 g.items[iderID] = m
95
96 return removed, nil
97}
98
99// Remove removes the item by id. Returns an error if the item
100// doesn't exist.
101func (g *orderedIDs) Remove(id string) (ider, error) {
102 if len(id) == 0 {
103 return nil, fmt.Errorf("remove ID must not be empty")
104 }
105
106 if err := g.order.Remove(id); err != nil {
107 return nil, err
108 }
109
110 removed := g.items[id]
111 delete(g.items, id)
112 return removed, nil
113}
114
115func (g *orderedIDs) List() []string {
116 items := g.order.List()
117 order := make([]string, len(items))
118 copy(order, items)
119 return order
120}
121
122// Clear removes all entries and slots.
123func (g *orderedIDs) Clear() {
124 g.order.Clear()
125 g.items = map[string]ider{}
126}
127
128// GetOrder returns the item in the order it should be invoked in.
129func (g *orderedIDs) GetOrder() []interface{} {
130 order := g.order.List()
131 ordered := make([]interface{}, len(order))
132 for i := 0; i < len(order); i++ {
133 ordered[i] = g.items[order[i]]
134 }
135
136 return ordered
137}
138
139// relativeOrder provides ordering of item
140type relativeOrder struct {
141 order []string
142}
143
144func newRelativeOrder() *relativeOrder {
145 return &relativeOrder{
146 order: make([]string, 0, baseOrderedItems),
147 }
148}
149
150// Add inserts an item into the order relative to the position provided.
151func (s *relativeOrder) Add(pos RelativePosition, ids ...string) error {
152 if len(ids) == 0 {
153 return nil
154 }
155
156 for _, id := range ids {
157 if _, ok := s.has(id); ok {
158 return fmt.Errorf("already exists, %v", id)
159 }
160 }
161
162 switch pos {
163 case Before:
164 return s.insert(0, Before, ids...)
165
166 case After:
167 s.order = append(s.order, ids...)
168
169 default:
170 return fmt.Errorf("invalid position, %v", int(pos))
171 }
172
173 return nil
174}
175
176// Insert injects an item before or after the relative item. Returns
177// an error if the relative item does not exist.
178func (s *relativeOrder) Insert(relativeTo string, pos RelativePosition, ids ...string) error {
179 if len(ids) == 0 {
180 return nil
181 }
182
183 for _, id := range ids {
184 if _, ok := s.has(id); ok {
185 return fmt.Errorf("already exists, %v", id)
186 }
187 }
188
189 i, ok := s.has(relativeTo)
190 if !ok {
191 return fmt.Errorf("not found, %v", relativeTo)
192 }
193
194 return s.insert(i, pos, ids...)
195}
196
197// Swap will replace the item id with the to item. Returns an
198// error if the original item id does not exist. Allows swapping out an
199// item for another item with the same id.
200func (s *relativeOrder) Swap(id, to string) error {
201 i, ok := s.has(id)
202 if !ok {
203 return fmt.Errorf("not found, %v", id)
204 }
205
206 if _, ok = s.has(to); ok && id != to {
207 return fmt.Errorf("already exists, %v", to)
208 }
209
210 s.order[i] = to
211 return nil
212}
213
214func (s *relativeOrder) Remove(id string) error {
215 i, ok := s.has(id)
216 if !ok {
217 return fmt.Errorf("not found, %v", id)
218 }
219
220 s.order = append(s.order[:i], s.order[i+1:]...)
221 return nil
222}
223
224func (s *relativeOrder) List() []string {
225 return s.order
226}
227
228func (s *relativeOrder) Clear() {
229 s.order = s.order[0:0]
230}
231
232func (s *relativeOrder) insert(i int, pos RelativePosition, ids ...string) error {
233 switch pos {
234 case Before:
235 n := len(ids)
236 var src []string
237 if n <= cap(s.order)-len(s.order) {
238 s.order = s.order[:len(s.order)+n]
239 src = s.order
240 } else {
241 src = s.order
242 s.order = make([]string, len(s.order)+n)
243 copy(s.order[:i], src[:i]) // only when allocating a new slice do we need to copy the front half
244 }
245 copy(s.order[i+n:], src[i:])
246 copy(s.order[i:], ids)
247 case After:
248 if i == len(s.order)-1 || len(s.order) == 0 {
249 s.order = append(s.order, ids...)
250 } else {
251 s.order = append(s.order[:i+1], append(ids, s.order[i+1:]...)...)
252 }
253
254 default:
255 return fmt.Errorf("invalid position, %v", int(pos))
256 }
257
258 return nil
259}
260
261func (s *relativeOrder) has(id string) (i int, found bool) {
262 for i := 0; i < len(s.order); i++ {
263 if s.order[i] == id {
264 return i, true
265 }
266 }
267 return 0, false
268}