MapCollectionWithLookups does a 'map' operation over the collection, transforming each input row into an output row according to `mapper`, however it also provides the ability to collect ID's of related items as it iterates the collection, and call out to lambdas to look those values up. See the uni
( cacheContainer *MapCollectionCacheContainer, // cache for keys (typically this will store a mapping of ID->[Name, Name]). collection []T, // input (e.g. list of Releases) keySelector func(T) []string, // fetches the keys (e.g given a Release, returns the [ChannelID, ProjectID] mapper func(T, []string) TResult, // fetches the value to lookup (e.g given a Release and the [ChannelName,ProjectName], does the mapping to return the output struct) runLookups ...func([]string) ([]string, error), // callbacks to go fetch values for the keys (given a list of Channel IDs, it should return the list of associated Channel Names) )
| 129 | // items as it iterates the collection, and call out to lambdas to look those values up. |
| 130 | // See the unit tests for examples which should clarify the use-cases for this. |
| 131 | func MapCollectionWithLookups[T any, TResult any]( |
| 132 | cacheContainer *MapCollectionCacheContainer, // cache for keys (typically this will store a mapping of ID->[Name, Name]). |
| 133 | collection []T, // input (e.g. list of Releases) |
| 134 | keySelector func(T) []string, // fetches the keys (e.g given a Release, returns the [ChannelID, ProjectID] |
| 135 | mapper func(T, []string) TResult, // fetches the value to lookup (e.g given a Release and the [ChannelName,ProjectName], does the mapping to return the output struct) |
| 136 | runLookups ...func([]string) ([]string, error), // callbacks to go fetch values for the keys (given a list of Channel IDs, it should return the list of associated Channel Names) |
| 137 | ) ([]TResult, error) { |
| 138 | // if the caller didn't specify an external cache, create an internal one. |
| 139 | // it'll work, but we lose the ability to cache across multiple lookups |
| 140 | // (e.g. when fetching more than one page of results from the server) |
| 141 | if cacheContainer == nil { |
| 142 | cacheContainer = &MapCollectionCacheContainer{} |
| 143 | } |
| 144 | |
| 145 | if len(cacheContainer.Caches) < len(runLookups) { |
| 146 | // caches aren't allocated, we need to do that |
| 147 | cacheContainer.Caches = nil |
| 148 | for i := 0; i < len(runLookups); i++ { |
| 149 | cacheContainer.Caches = append(cacheContainer.Caches, map[string]string{}) |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | caches := cacheContainer.Caches |
| 154 | |
| 155 | // first pass: walk the collection and see if there's anything we need to look up. |
| 156 | // if we detect a situation where all the lookups are already satisfied by the cache, |
| 157 | // then we may not need to do any lookups at all. |
| 158 | var allKeysToLookup = make([][]string, len(caches)) // preallocate the right number of nils |
| 159 | for _, item := range collection { |
| 160 | keys := keySelector(item) |
| 161 | for i, key := range keys { |
| 162 | _, ok := caches[i][key] |
| 163 | if !ok { // we haven't seen this value in the cache, we need to go and look something up |
| 164 | if allKeysToLookup[i] == nil { |
| 165 | allKeysToLookup[i] = []string{key} |
| 166 | } else { |
| 167 | if !SliceContains(allKeysToLookup[i], key) { |
| 168 | allKeysToLookup[i] = append(allKeysToLookup[i], key) |
| 169 | } // else we've already seen this key |
| 170 | } |
| 171 | } |
| 172 | } |
| 173 | } |
| 174 | |
| 175 | // if we have things we need to look up, go and look them up and insert them into the cache. |
| 176 | for lookupIdx, keysToLookup := range allKeysToLookup { |
| 177 | if keysToLookup != nil { |
| 178 | lookedUpValues, err := runLookups[lookupIdx](keysToLookup) |
| 179 | if err != nil { |
| 180 | return nil, err |
| 181 | } |
| 182 | for valueIdx, value := range lookedUpValues { |
| 183 | caches[lookupIdx][keysToLookup[valueIdx]] = value |
| 184 | } |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | // Now we do a second pass over the array in order to produce the output (incorporating the looked-up values from cache) |