Skip to content

Commit 5158d37

Browse files
committed
Support map value groups
This revision allows dig to specify value groups of map type. For example: ``` type Params struct { dig.In Things []int `group:"foogroup"` MapOfThings map[string]int `group:"foogroup"` } type Result struct { dig.Out Int1 int `name:"foo1" group:"foogroup"` Int2 int `name:"foo2" group:"foogroup"` Int3 int `name:"foo3" group:"foogroup"` } c.Provide(func() Result { return Result{Int1: 1, Int2: 2, Int3: 3} }) c.Invoke(func(p Params) { }) ``` p.Things will be a value group slice as per usual, containing the elements {1,2,3} in an arbitrary order. p.MapOfThings will be a key-value pairing of {"foo1":1, "foo2":2, "foo3":3}.
1 parent deaa0ab commit 5158d37

File tree

7 files changed

+319
-32
lines changed

7 files changed

+319
-32
lines changed

decorate.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,9 @@ func findResultKeys(r resultList) ([]key, error) {
309309
case resultSingle:
310310
keys = append(keys, key{t: innerResult.Type, name: innerResult.Name})
311311
case resultGrouped:
312-
if innerResult.Type.Kind() != reflect.Slice {
312+
isMap := innerResult.Type.Kind() == reflect.Map && innerResult.Type.Key().Kind() == reflect.String
313+
isSlice := innerResult.Type.Kind() == reflect.Slice
314+
if !isMap && !isSlice {
313315
return nil, newErrInvalidInput("decorating a value group requires decorating the entire value group, not a single value", nil)
314316
}
315317
keys = append(keys, key{t: innerResult.Type.Elem(), group: innerResult.Group})

decorate_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,64 @@ func TestDecorateSuccess(t *testing.T) {
216216
}))
217217
})
218218

219+
t.Run("map is treated as an ordinary dependency without group tag, named or unnamed, and passes through multiple scopes", func(t *testing.T) {
220+
type params struct {
221+
dig.In
222+
223+
Strings1 map[string]string
224+
Strings2 map[string]string `name:"strings2"`
225+
}
226+
227+
type childResult struct {
228+
dig.Out
229+
230+
Strings1 map[string]string
231+
Strings2 map[string]string `name:"strings2"`
232+
}
233+
234+
type A map[string]string
235+
type B map[string]string
236+
237+
parent := digtest.New(t)
238+
parent.RequireProvide(func() map[string]string { return map[string]string{"key1": "val1", "key2": "val2"} })
239+
parent.RequireProvide(func() map[string]string { return map[string]string{"key1": "val21", "key2": "val22"} }, dig.Name("strings2"))
240+
241+
parent.RequireProvide(func(p params) A { return A(p.Strings1) })
242+
parent.RequireProvide(func(p params) B { return B(p.Strings2) })
243+
244+
child := parent.Scope("child")
245+
246+
parent.RequireDecorate(func(p params) childResult {
247+
res := childResult{Strings1: make(map[string]string, len(p.Strings1))}
248+
for k, s := range p.Strings1 {
249+
res.Strings1[k] = strings.ToUpper(s)
250+
}
251+
res.Strings2 = p.Strings2
252+
return res
253+
})
254+
255+
child.RequireDecorate(func(p params) childResult {
256+
res := childResult{Strings2: make(map[string]string, len(p.Strings2))}
257+
for k, s := range p.Strings2 {
258+
res.Strings2[k] = strings.ToUpper(s)
259+
}
260+
res.Strings1 = p.Strings1
261+
res.Strings1["key3"] = "newval"
262+
return res
263+
})
264+
265+
require.NoError(t, child.Invoke(func(p params) {
266+
require.Len(t, p.Strings1, 3)
267+
assert.Equal(t, "VAL1", p.Strings1["key1"])
268+
assert.Equal(t, "VAL2", p.Strings1["key2"])
269+
assert.Equal(t, "newval", p.Strings1["key3"])
270+
require.Len(t, p.Strings2, 2)
271+
assert.Equal(t, "VAL21", p.Strings2["key1"])
272+
assert.Equal(t, "VAL22", p.Strings2["key2"])
273+
274+
}))
275+
276+
})
219277
t.Run("decorate values in soft group", func(t *testing.T) {
220278
type params struct {
221279
dig.In
@@ -394,6 +452,46 @@ func TestDecorateSuccess(t *testing.T) {
394452
assert.Equal(t, `[]string[group = "animals"]`, info.Inputs[0].String())
395453
})
396454

455+
t.Run("decorate with map value groups", func(t *testing.T) {
456+
type Params struct {
457+
dig.In
458+
459+
Animals map[string]string `group:"animals"`
460+
}
461+
462+
type Result struct {
463+
dig.Out
464+
465+
Animals map[string]string `group:"animals"`
466+
}
467+
468+
c := digtest.New(t)
469+
c.RequireProvide(func() string { return "dog" }, dig.Name("animal1"), dig.Group("animals"))
470+
c.RequireProvide(func() string { return "cat" }, dig.Name("animal2"), dig.Group("animals"))
471+
c.RequireProvide(func() string { return "gopher" }, dig.Name("animal3"), dig.Group("animals"))
472+
473+
var info dig.DecorateInfo
474+
c.RequireDecorate(func(p Params) Result {
475+
animals := p.Animals
476+
for k, v := range animals {
477+
animals[k] = "good " + v
478+
}
479+
return Result{
480+
Animals: animals,
481+
}
482+
}, dig.FillDecorateInfo(&info))
483+
484+
c.RequireInvoke(func(p Params) {
485+
assert.Len(t, p.Animals, 3)
486+
assert.Equal(t, "good dog", p.Animals["animal1"])
487+
assert.Equal(t, "good cat", p.Animals["animal2"])
488+
assert.Equal(t, "good gopher", p.Animals["animal3"])
489+
})
490+
491+
require.Equal(t, 1, len(info.Inputs))
492+
assert.Equal(t, `map[string]string[group = "animals"]`, info.Inputs[0].String())
493+
})
494+
397495
t.Run("decorate with optional parameter", func(t *testing.T) {
398496
c := digtest.New(t)
399497

@@ -919,6 +1017,7 @@ func TestMultipleDecorates(t *testing.T) {
9191017
assert.ElementsMatch(t, []int{2, 3, 4}, a.Values)
9201018
})
9211019
})
1020+
9221021
}
9231022

9241023
func TestFillDecorateInfoString(t *testing.T) {

dig_test.go

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1288,6 +1288,27 @@ func TestGroups(t *testing.T) {
12881288
})
12891289
})
12901290

1291+
t.Run("provide multiple with the same name and group but different type", func(t *testing.T) {
1292+
c := digtest.New(t)
1293+
type A struct{}
1294+
type B struct{}
1295+
type ret1 struct {
1296+
dig.Out
1297+
*A `name:"foo" group:"foos"`
1298+
}
1299+
type ret2 struct {
1300+
dig.Out
1301+
*B `name:"foo" group:"foos"`
1302+
}
1303+
c.RequireProvide(func() ret1 {
1304+
return ret1{A: &A{}}
1305+
})
1306+
1307+
c.RequireProvide(func() ret2 {
1308+
return ret2{B: &B{}}
1309+
})
1310+
})
1311+
12911312
t.Run("different types may be grouped", func(t *testing.T) {
12921313
c := digtest.New(t, dig.SetRand(rand.New(rand.NewSource(0))))
12931314

@@ -1792,6 +1813,118 @@ func TestGroups(t *testing.T) {
17921813
assert.ElementsMatch(t, []string{"a"}, param.Value)
17931814
})
17941815
})
1816+
/* map tests */
1817+
t.Run("empty map received without provides", func(t *testing.T) {
1818+
c := digtest.New(t)
1819+
1820+
type in struct {
1821+
dig.In
1822+
1823+
Values map[string]int `group:"foo"`
1824+
}
1825+
1826+
c.RequireInvoke(func(i in) {
1827+
require.Empty(t, i.Values)
1828+
})
1829+
})
1830+
1831+
t.Run("map value group using dig.Name and dig.Group", func(t *testing.T) {
1832+
c := digtest.New(t, dig.SetRand(rand.New(rand.NewSource(0))))
1833+
1834+
c.RequireProvide(func() int {
1835+
return 1
1836+
}, dig.Name("value1"), dig.Group("val"))
1837+
c.RequireProvide(func() int {
1838+
return 2
1839+
}, dig.Name("value2"), dig.Group("val"))
1840+
c.RequireProvide(func() int {
1841+
return 3
1842+
}, dig.Name("value3"), dig.Group("val"))
1843+
1844+
type in struct {
1845+
dig.In
1846+
1847+
Value1 int `name:"value1"`
1848+
Value2 int `name:"value2"`
1849+
Value3 int `name:"value3"`
1850+
Values []int `group:"val"`
1851+
ValueMap map[string]int `group:"val"`
1852+
}
1853+
1854+
c.RequireInvoke(func(i in) {
1855+
assert.Equal(t, []int{2, 3, 1}, i.Values)
1856+
assert.Equal(t, i.ValueMap["value1"], 1)
1857+
assert.Equal(t, i.ValueMap["value2"], 2)
1858+
assert.Equal(t, i.ValueMap["value3"], 3)
1859+
assert.Equal(t, i.Value1, 1)
1860+
assert.Equal(t, i.Value2, 2)
1861+
assert.Equal(t, i.Value3, 3)
1862+
})
1863+
})
1864+
t.Run("values are provided, map and name and slice", func(t *testing.T) {
1865+
c := digtest.New(t, dig.SetRand(rand.New(rand.NewSource(0))))
1866+
type out struct {
1867+
dig.Out
1868+
1869+
Value1 int `name:"value1" group:"val"`
1870+
Value2 int `name:"value2" group:"val"`
1871+
Value3 int `name:"value3" group:"val"`
1872+
}
1873+
1874+
c.RequireProvide(func() out {
1875+
return out{Value1: 1, Value2: 2, Value3: 3}
1876+
})
1877+
1878+
type in struct {
1879+
dig.In
1880+
1881+
Value1 int `name:"value1"`
1882+
Value2 int `name:"value2"`
1883+
Value3 int `name:"value3"`
1884+
Values []int `group:"val"`
1885+
ValueMap map[string]int `group:"val"`
1886+
}
1887+
1888+
c.RequireInvoke(func(i in) {
1889+
assert.Equal(t, []int{2, 3, 1}, i.Values)
1890+
assert.Equal(t, i.ValueMap["value1"], 1)
1891+
assert.Equal(t, i.ValueMap["value2"], 2)
1892+
assert.Equal(t, i.ValueMap["value3"], 3)
1893+
assert.Equal(t, i.Value1, 1)
1894+
assert.Equal(t, i.Value2, 2)
1895+
assert.Equal(t, i.Value3, 3)
1896+
})
1897+
})
1898+
1899+
t.Run("Every item used in a map must have a named key", func(t *testing.T) {
1900+
c := digtest.New(t, dig.SetRand(rand.New(rand.NewSource(0))))
1901+
1902+
type out struct {
1903+
dig.Out
1904+
1905+
Value1 int `name:"value1" group:"val"`
1906+
Value2 int `name:"value2" group:"val"`
1907+
Value3 int `group:"val"`
1908+
}
1909+
1910+
c.RequireProvide(func() out {
1911+
return out{Value1: 1, Value2: 2, Value3: 3}
1912+
})
1913+
1914+
type in struct {
1915+
dig.In
1916+
1917+
ValueMap map[string]int `group:"val"`
1918+
}
1919+
var called = false
1920+
err := c.Invoke(func(i in) { called = true })
1921+
dig.AssertErrorMatches(t, err,
1922+
`could not build arguments for function "go.uber.org/dig_test".TestGroups\S+`,
1923+
`dig_test.go:\d+`, // file:line
1924+
`every entry in a map value groups must have a name, group "val" is missing a name`)
1925+
assert.False(t, called, "shouldn't call invoked function when deps aren't available")
1926+
})
1927+
17951928
}
17961929

17971930
// --- END OF END TO END TESTS
@@ -2974,7 +3107,27 @@ func testProvideFailures(t *testing.T, dryRun bool) {
29743107
)
29753108
})
29763109

2977-
t.Run("provide multiple instances with the same name but different group", func(t *testing.T) {
3110+
t.Run("provide multiple instances with the same name and same group using options", func(t *testing.T) {
3111+
c := digtest.New(t, dig.DryRun(dryRun))
3112+
type A struct{}
3113+
3114+
c.RequireProvide(func() *A {
3115+
return &A{}
3116+
}, dig.Group("foos"), dig.Name("foo"))
3117+
3118+
err := c.Provide(func() *A {
3119+
return &A{}
3120+
}, dig.Group("foos"), dig.Name("foo"))
3121+
require.Error(t, err, "expected error on the second provide")
3122+
dig.AssertErrorMatches(t, err,
3123+
`cannot provide function "go.uber.org/dig_test".testProvideFailures\S+`,
3124+
`dig_test.go:\d+`, // file:line
3125+
`cannot provide \*dig_test.A\[name="foo"\] from \[1\]:`,
3126+
`already provided by "go.uber.org/dig_test".testProvideFailures\S+`,
3127+
)
3128+
})
3129+
3130+
t.Run("provide multiple instances with the same name and type but different group", func(t *testing.T) {
29783131
c := digtest.New(t, dig.DryRun(dryRun))
29793132
type A struct{}
29803133
type ret1 struct {

graph.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ type graphNode struct {
2828
}
2929

3030
// graphHolder is the dependency graph of the container.
31-
// It saves constructorNodes and paramGroupedSlice (value groups)
31+
// It saves constructorNodes and paramGroupedCollection (value groups)
3232
// as nodes in the graph.
3333
// It implements the graph interface defined by internal/graph.
3434
// It has 1-1 correspondence with the Scope whose graph it represents.
@@ -68,7 +68,7 @@ func (gh *graphHolder) EdgesFrom(u int) []int {
6868
for _, param := range w.paramList.Params {
6969
orders = append(orders, getParamOrder(gh, param)...)
7070
}
71-
case *paramGroupedSlice:
71+
case *paramGroupedCollection:
7272
providers := gh.s.getAllGroupProviders(w.Group, w.Type.Elem())
7373
for _, provider := range providers {
7474
orders = append(orders, provider.Order(gh.s))

0 commit comments

Comments
 (0)