package hardware import ( "fmt" "reflect" ) // FieldIndexer indexes collection of objects for a single type against one of its fields. // FieldIndexer is not thread safe. type FieldIndexer struct { expectedType reflect.Type indexes map[string]*fieldIndex } // NewFieldIndexer creates a new FieldIndexer instance. object is the object to be indexed and will // be checked during Insert() calls. NewFieldIndexer will panic if object is nil. func NewFieldIndexer(object interface{}) *FieldIndexer { objectType := reflect.TypeOf(object) if objectType == nil { panic("object cannot be nil") } return &FieldIndexer{ expectedType: objectType, indexes: make(map[string]*fieldIndex), } } // KeyExtractorFunc returns a key from object that can be used to look up the object. type KeyExtractorFunc func(object interface{}) string // IndexField registers a new index with i. field is the index name and should represent a path // to the field such as `.Spec.ID`. fn is used to extract the lookup key on Insert() from the object // to be inserted. func (i *FieldIndexer) IndexField(field string, fn KeyExtractorFunc) { i.indexes[field] = &fieldIndex{ index: make(map[string][]interface{}), keyExtractorFunc: fn, } } // Insert inserts v into i on all indexed fields registered with IndexField. If v is not of the // expected type defined by NewFieldIndexer() ErrIncorrectType is returned. Multiple objects // with the same index value may be inserted. func (i *FieldIndexer) Insert(v interface{}) error { objectType := reflect.TypeOf(v) if objectType != i.expectedType { return ErrIncorrectType{Expected: i.expectedType, Received: objectType} } for _, idx := range i.indexes { idx.Insert(v) } return nil } // Lookup uses the index associated with field to find and return all objects associated with key. // If field has no associated index created by IndexField ErrUnknownIndex is returned. func (i *FieldIndexer) Lookup(field string, key string) ([]interface{}, error) { idx, ok := i.indexes[field] if !ok { return nil, ErrUnknownIndex{Field: field} } return idx.Lookup(key), nil } // Remove removes v from all indexes if present. If v is not present Remove is a no-op. If v is of // an incorrect type ErrUnknownType is returned. func (i *FieldIndexer) Remove(v interface{}) error { objectType := reflect.TypeOf(v) if objectType != i.expectedType { return ErrIncorrectType{Expected: i.expectedType, Received: objectType} } for _, idx := range i.indexes { idx.Remove(v) } return nil } // fieldIndex represents a single index on a particular object. When inserting into the fieldIndex // the key is extracted from the object using the KeyExtractorFunc. type fieldIndex struct { index map[string][]interface{} keyExtractorFunc KeyExtractorFunc } func (i *fieldIndex) Insert(v interface{}) { key := i.keyExtractorFunc(v) i.index[key] = append(i.index[key], v) } func (i *fieldIndex) Lookup(key string) []interface{} { return i.index[key] } func (i *fieldIndex) Remove(v interface{}) { key := i.keyExtractorFunc(v) delete(i.index, key) } // ErrIncorrectType indicates an incorrect type was used with a FieldIndexer. type ErrIncorrectType struct { Expected reflect.Type Received reflect.Type } func (e ErrIncorrectType) Error() string { return fmt.Sprintf("expected type '%s', received object of type '%v'", e.Expected, e.Received) } type ErrUnknownIndex struct { Field string } func (e ErrUnknownIndex) Error() string { return fmt.Sprintf("unknown index: %v", e.Field) }