Skip to content

Commit 23338b1

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 e781757 commit 23338b1

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
@@ -282,7 +282,9 @@ func findResultKeys(r resultList) ([]key, error) {
282282
case resultSingle:
283283
keys = append(keys, key{t: innerResult.Type, name: innerResult.Name})
284284
case resultGrouped:
285-
if innerResult.Type.Kind() != reflect.Slice {
285+
isMap := innerResult.Type.Kind() == reflect.Map && innerResult.Type.Key().Kind() == reflect.String
286+
isSlice := innerResult.Type.Kind() == reflect.Slice
287+
if !isMap && !isSlice {
286288
return nil, newErrInvalidInput("decorating a value group requires decorating the entire value group, not a single value", nil)
287289
}
288290
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
@@ -215,6 +215,64 @@ func TestDecorateSuccess(t *testing.T) {
215215
}))
216216
})
217217

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

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

@@ -918,6 +1016,7 @@ func TestMultipleDecorates(t *testing.T) {
9181016
assert.ElementsMatch(t, []int{2, 3, 4}, a.Values)
9191017
})
9201018
})
1019+
9211020
}
9221021

9231022
func TestFillDecorateInfoString(t *testing.T) {

dig_test.go

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1241,6 +1241,27 @@ func TestGroups(t *testing.T) {
12411241
})
12421242
})
12431243

1244+
t.Run("provide multiple with the same name and group but different type", func(t *testing.T) {
1245+
c := digtest.New(t)
1246+
type A struct{}
1247+
type B struct{}
1248+
type ret1 struct {
1249+
dig.Out
1250+
*A `name:"foo" group:"foos"`
1251+
}
1252+
type ret2 struct {
1253+
dig.Out
1254+
*B `name:"foo" group:"foos"`
1255+
}
1256+
c.RequireProvide(func() ret1 {
1257+
return ret1{A: &A{}}
1258+
})
1259+
1260+
c.RequireProvide(func() ret2 {
1261+
return ret2{B: &B{}}
1262+
})
1263+
})
1264+
12441265
t.Run("different types may be grouped", func(t *testing.T) {
12451266
c := digtest.New(t, dig.SetRand(rand.New(rand.NewSource(0))))
12461267

@@ -1745,6 +1766,118 @@ func TestGroups(t *testing.T) {
17451766
assert.ElementsMatch(t, []string{"a"}, param.Value)
17461767
})
17471768
})
1769+
/* map tests */
1770+
t.Run("empty map received without provides", func(t *testing.T) {
1771+
c := digtest.New(t)
1772+
1773+
type in struct {
1774+
dig.In
1775+
1776+
Values map[string]int `group:"foo"`
1777+
}
1778+
1779+
c.RequireInvoke(func(i in) {
1780+
require.Empty(t, i.Values)
1781+
})
1782+
})
1783+
1784+
t.Run("map value group using dig.Name and dig.Group", func(t *testing.T) {
1785+
c := digtest.New(t, dig.SetRand(rand.New(rand.NewSource(0))))
1786+
1787+
c.RequireProvide(func() int {
1788+
return 1
1789+
}, dig.Name("value1"), dig.Group("val"))
1790+
c.RequireProvide(func() int {
1791+
return 2
1792+
}, dig.Name("value2"), dig.Group("val"))
1793+
c.RequireProvide(func() int {
1794+
return 3
1795+
}, dig.Name("value3"), dig.Group("val"))
1796+
1797+
type in struct {
1798+
dig.In
1799+
1800+
Value1 int `name:"value1"`
1801+
Value2 int `name:"value2"`
1802+
Value3 int `name:"value3"`
1803+
Values []int `group:"val"`
1804+
ValueMap map[string]int `group:"val"`
1805+
}
1806+
1807+
c.RequireInvoke(func(i in) {
1808+
assert.Equal(t, []int{2, 3, 1}, i.Values)
1809+
assert.Equal(t, i.ValueMap["value1"], 1)
1810+
assert.Equal(t, i.ValueMap["value2"], 2)
1811+
assert.Equal(t, i.ValueMap["value3"], 3)
1812+
assert.Equal(t, i.Value1, 1)
1813+
assert.Equal(t, i.Value2, 2)
1814+
assert.Equal(t, i.Value3, 3)
1815+
})
1816+
})
1817+
t.Run("values are provided, map and name and slice", func(t *testing.T) {
1818+
c := digtest.New(t, dig.SetRand(rand.New(rand.NewSource(0))))
1819+
type out struct {
1820+
dig.Out
1821+
1822+
Value1 int `name:"value1" group:"val"`
1823+
Value2 int `name:"value2" group:"val"`
1824+
Value3 int `name:"value3" group:"val"`
1825+
}
1826+
1827+
c.RequireProvide(func() out {
1828+
return out{Value1: 1, Value2: 2, Value3: 3}
1829+
})
1830+
1831+
type in struct {
1832+
dig.In
1833+
1834+
Value1 int `name:"value1"`
1835+
Value2 int `name:"value2"`
1836+
Value3 int `name:"value3"`
1837+
Values []int `group:"val"`
1838+
ValueMap map[string]int `group:"val"`
1839+
}
1840+
1841+
c.RequireInvoke(func(i in) {
1842+
assert.Equal(t, []int{2, 3, 1}, i.Values)
1843+
assert.Equal(t, i.ValueMap["value1"], 1)
1844+
assert.Equal(t, i.ValueMap["value2"], 2)
1845+
assert.Equal(t, i.ValueMap["value3"], 3)
1846+
assert.Equal(t, i.Value1, 1)
1847+
assert.Equal(t, i.Value2, 2)
1848+
assert.Equal(t, i.Value3, 3)
1849+
})
1850+
})
1851+
1852+
t.Run("Every item used in a map must have a named key", func(t *testing.T) {
1853+
c := digtest.New(t, dig.SetRand(rand.New(rand.NewSource(0))))
1854+
1855+
type out struct {
1856+
dig.Out
1857+
1858+
Value1 int `name:"value1" group:"val"`
1859+
Value2 int `name:"value2" group:"val"`
1860+
Value3 int `group:"val"`
1861+
}
1862+
1863+
c.RequireProvide(func() out {
1864+
return out{Value1: 1, Value2: 2, Value3: 3}
1865+
})
1866+
1867+
type in struct {
1868+
dig.In
1869+
1870+
ValueMap map[string]int `group:"val"`
1871+
}
1872+
var called = false
1873+
err := c.Invoke(func(i in) { called = true })
1874+
dig.AssertErrorMatches(t, err,
1875+
`could not build arguments for function "go.uber.org/dig_test".TestGroups\S+`,
1876+
`dig_test.go:\d+`, // file:line
1877+
`every entry in a map value groups must have a name, group "val" is missing a name`)
1878+
assert.False(t, called, "shouldn't call invoked function when deps aren't available")
1879+
})
1880+
17481881
}
17491882

17501883
// --- END OF END TO END TESTS
@@ -2753,7 +2886,27 @@ func testProvideFailures(t *testing.T, dryRun bool) {
27532886
)
27542887
})
27552888

2756-
t.Run("provide multiple instances with the same name but different group", func(t *testing.T) {
2889+
t.Run("provide multiple instances with the same name and same group using options", func(t *testing.T) {
2890+
c := digtest.New(t, dig.DryRun(dryRun))
2891+
type A struct{}
2892+
2893+
c.RequireProvide(func() *A {
2894+
return &A{}
2895+
}, dig.Group("foos"), dig.Name("foo"))
2896+
2897+
err := c.Provide(func() *A {
2898+
return &A{}
2899+
}, dig.Group("foos"), dig.Name("foo"))
2900+
require.Error(t, err, "expected error on the second provide")
2901+
dig.AssertErrorMatches(t, err,
2902+
`cannot provide function "go.uber.org/dig_test".testProvideFailures\S+`,
2903+
`dig_test.go:\d+`, // file:line
2904+
`cannot provide \*dig_test.A\[name="foo"\] from \[1\]:`,
2905+
`already provided by "go.uber.org/dig_test".testProvideFailures\S+`,
2906+
)
2907+
})
2908+
2909+
t.Run("provide multiple instances with the same name and type but different group", func(t *testing.T) {
27572910
c := digtest.New(t, dig.DryRun(dryRun))
27582911
type A struct{}
27592912
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)