github.com/xmidt-org/webpa-common@v1.11.9/event/multimap.go (about) 1 package event 2 3 import "fmt" 4 5 // MultiMap describes a set of events together with how those events should be processed. Most often, 6 // event types are mapped to URLs, but that is not required. Event values can be any string that is 7 // meaningful to an application. 8 type MultiMap map[string][]string 9 10 // Add appends one or more values to an event type. If mappedTo is empty, this method does nothing. 11 // If the eventType doesn't exist, it is created. 12 func (m MultiMap) Add(eventType string, mappedTo ...string) { 13 if len(mappedTo) == 0 { 14 return 15 } 16 17 m[eventType] = append(m[eventType], mappedTo...) 18 } 19 20 // Set changes the given eventType so that it maps only to the values supplied in mappedTo. If 21 // mappedTo is empty, this method deletes the event type. 22 func (m MultiMap) Set(eventType string, mappedTo ...string) { 23 if len(mappedTo) == 0 { 24 delete(m, eventType) 25 return 26 } 27 28 copyOf := make([]string, len(mappedTo)) 29 copy(copyOf, mappedTo) 30 m[eventType] = copyOf 31 } 32 33 // Get returns the values associated with the given event type. The fallback event types, if supplied, are used 34 // if no values are present for the given eventType. The fallback is useful for defaults, e.g. m.Get("IOT", "default"). 35 func (m MultiMap) Get(eventType string, fallback ...string) ([]string, bool) { 36 values, ok := m[eventType] 37 if !ok { 38 for i := 0; i < len(fallback) && !ok; i++ { 39 values, ok = m[fallback[i]] 40 } 41 } 42 43 return values, ok 44 } 45 46 // NestedToMultiMap translates a map with potentially nested string keys into a MultiMap. This function is useful 47 // when unmarshalling from libraries that impose some meaning on a separator, like viper does with periods. Essentially, 48 // this function returns a MultiMap that is the result of "flattening" the given raw map. 49 // 50 // The separator string must be nonempty. It is used as the separator for nested map keys, e.g. "foo.bar". 51 func NestedToMultiMap(separator string, raw map[string]interface{}) (MultiMap, error) { 52 if len(separator) == 0 { 53 return nil, fmt.Errorf("The separator cannot be empty") 54 } 55 56 output := make(MultiMap, len(raw)) 57 if err := nestedToMultiMap("", separator, raw, output); err != nil { 58 return nil, err 59 } 60 61 return output, nil 62 } 63 64 // nestedToMultiMap is a recursive function that builds a MultiMap by travsersing any nested maps with the raw map. 65 func nestedToMultiMap(base, separator string, raw map[string]interface{}, output MultiMap) error { 66 var eventType string 67 for k, v := range raw { 68 if len(base) > 0 { 69 eventType = base + separator + k 70 } else { 71 eventType = k 72 } 73 74 switch value := v.(type) { 75 case string: 76 output.Set(eventType, value) 77 78 case []string: 79 output.Set(eventType, value...) 80 81 case []interface{}: 82 for _, rawElement := range value { 83 if stringElement, ok := rawElement.(string); ok { 84 output.Add(eventType, stringElement) 85 } else { 86 return fmt.Errorf("Invalid element value of type %T: %v", v, v) 87 } 88 } 89 90 case map[string]interface{}: 91 if err := nestedToMultiMap(eventType, separator, value, output); err != nil { 92 return err 93 } 94 95 case map[string][]string: 96 for nestedKey, nestedValues := range value { 97 output.Set(eventType+separator+nestedKey, nestedValues...) 98 } 99 100 default: 101 return fmt.Errorf("Invalid raw event value of type %T: %v", v, v) 102 } 103 } 104 105 return nil 106 }