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 }