Skip to content

Commit 5a24c26

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 5a24c26

File tree

5 files changed

+217
-31
lines changed

5 files changed

+217
-31
lines changed

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))

param.go

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,12 @@ import (
3838
// paramSingle An explicitly requested type.
3939
// paramObject dig.In struct where each field in the struct can be another
4040
// param.
41-
// paramGroupedSlice
42-
// A slice consuming a value group. This will receive all
41+
// paramGroupedCollection
42+
// A slice or map consuming a value group. This will receive all
4343
// values produced with a `group:".."` tag with the same name
44-
// as a slice.
44+
// as a slice or map. For a map, every value produced with the
45+
// same group name MUST have a name which will form the map key.
46+
4547
type param interface {
4648
fmt.Stringer
4749

@@ -59,7 +61,7 @@ var (
5961
_ param = paramSingle{}
6062
_ param = paramObject{}
6163
_ param = paramList{}
62-
_ param = paramGroupedSlice{}
64+
_ param = paramGroupedCollection{}
6365
)
6466

6567
// newParam builds a param from the given type. If the provided type is a
@@ -342,7 +344,7 @@ func getParamOrder(gh *graphHolder, param param) []int {
342344
for _, provider := range providers {
343345
orders = append(orders, provider.Order(gh.s))
344346
}
345-
case paramGroupedSlice:
347+
case paramGroupedCollection:
346348
// value group parameters have nodes of their own.
347349
// We can directly return that here.
348350
orders = append(orders, p.orders[gh.s])
@@ -401,7 +403,7 @@ func (po paramObject) Build(c containerStore) (reflect.Value, error) {
401403
var softGroupsQueue []paramObjectField
402404
var fields []paramObjectField
403405
for _, f := range po.Fields {
404-
if p, ok := f.Param.(paramGroupedSlice); ok && p.Soft {
406+
if p, ok := f.Param.(paramGroupedCollection); ok && p.Soft {
405407
softGroupsQueue = append(softGroupsQueue, f)
406408
continue
407409
}
@@ -451,7 +453,7 @@ func newParamObjectField(idx int, f reflect.StructField, c containerStore) (para
451453

452454
case f.Tag.Get(_groupTag) != "":
453455
var err error
454-
p, err = newParamGroupedSlice(f, c)
456+
p, err = newParamGroupedCollection(f, c)
455457
if err != nil {
456458
return pof, err
457459
}
@@ -488,29 +490,31 @@ func (pof paramObjectField) Build(c containerStore) (reflect.Value, error) {
488490
return v, nil
489491
}
490492

491-
// paramGroupedSlice is a param which produces a slice of values with the same
493+
// paramGroupedCollection is a param which produces a slice or map of values with the same
492494
// group name.
493-
type paramGroupedSlice struct {
495+
type paramGroupedCollection struct {
494496
// Name of the group as specified in the `group:".."` tag.
495497
Group string
496498

497-
// Type of the slice.
499+
// Type of the map or slice.
498500
Type reflect.Type
499501

500502
// Soft is used to denote a soft dependency between this param and its
501503
// constructors, if it's true its constructors are only called if they
502504
// provide another value requested in the graph
503505
Soft bool
504506

507+
isMap bool
505508
orders map[*Scope]int
506509
}
507510

508-
func (pt paramGroupedSlice) String() string {
511+
func (pt paramGroupedCollection) String() string {
509512
// io.Reader[group="foo"] refers to a group of io.Readers called 'foo'
510513
return fmt.Sprintf("%v[group=%q]", pt.Type.Elem(), pt.Group)
514+
// JQTODO, different string for map
511515
}
512516

513-
func (pt paramGroupedSlice) DotParam() []*dot.Param {
517+
func (pt paramGroupedCollection) DotParam() []*dot.Param {
514518
return []*dot.Param{
515519
{
516520
Node: &dot.Node{
@@ -521,28 +525,31 @@ func (pt paramGroupedSlice) DotParam() []*dot.Param {
521525
}
522526
}
523527

524-
// newParamGroupedSlice builds a paramGroupedSlice from the provided type with
528+
// newParamGroupedCollection builds a paramGroupedCollection from the provided type with
525529
// the given name.
526530
//
527-
// The type MUST be a slice type.
528-
func newParamGroupedSlice(f reflect.StructField, c containerStore) (paramGroupedSlice, error) {
531+
// The type MUST be a slice or map[string]T type.
532+
func newParamGroupedCollection(f reflect.StructField, c containerStore) (paramGroupedCollection, error) {
529533
g, err := parseGroupString(f.Tag.Get(_groupTag))
530534
if err != nil {
531-
return paramGroupedSlice{}, err
535+
return paramGroupedCollection{}, err
532536
}
533-
pg := paramGroupedSlice{
537+
isMap := f.Type.Kind() == reflect.Map && f.Type.Key().Kind() == reflect.String
538+
isSlice := f.Type.Kind() == reflect.Slice
539+
pg := paramGroupedCollection{
534540
Group: g.Name,
535541
Type: f.Type,
542+
isMap: isMap,
536543
orders: make(map[*Scope]int),
537544
Soft: g.Soft,
538545
}
539546

540547
name := f.Tag.Get(_nameTag)
541548
optional, _ := isFieldOptional(f)
542549
switch {
543-
case f.Type.Kind() != reflect.Slice:
550+
case !isMap && !isSlice:
544551
return pg, newErrInvalidInput(
545-
fmt.Sprintf("value groups may be consumed as slices only: field %q (%v) is not a slice", f.Name, f.Type), nil)
552+
fmt.Sprintf("value groups may be consumed as slices or string-keyed maps only: field %q (%v) is not a slice or string-keyed map", f.Name, f.Type), nil)
546553
case g.Flatten:
547554
return pg, newErrInvalidInput(
548555
fmt.Sprintf("cannot use flatten in parameter value groups: field %q (%v) specifies flatten", f.Name, f.Type), nil)
@@ -560,7 +567,7 @@ func newParamGroupedSlice(f reflect.StructField, c containerStore) (paramGrouped
560567
// any of the parent Scopes. In the case where there are multiple scopes that
561568
// are decorating the same type, the closest scope in effect will be replacing
562569
// any decorated value groups provided in further scopes.
563-
func (pt paramGroupedSlice) getDecoratedValues(c containerStore) (reflect.Value, bool) {
570+
func (pt paramGroupedCollection) getDecoratedValues(c containerStore) (reflect.Value, bool) {
564571
for _, c := range c.storesToRoot() {
565572
if items, ok := c.getDecoratedValueGroup(pt.Group, pt.Type); ok {
566573
return items, true
@@ -575,7 +582,7 @@ func (pt paramGroupedSlice) getDecoratedValues(c containerStore) (reflect.Value,
575582
// The order in which the decorators are invoked is from the top level scope to
576583
// the current scope, to account for decorators that decorate values that were
577584
// already decorated.
578-
func (pt paramGroupedSlice) callGroupDecorators(c containerStore) error {
585+
func (pt paramGroupedCollection) callGroupDecorators(c containerStore) error {
579586
stores := c.storesToRoot()
580587
for i := len(stores) - 1; i >= 0; i-- {
581588
c := stores[i]
@@ -600,7 +607,7 @@ func (pt paramGroupedSlice) callGroupDecorators(c containerStore) error {
600607
// search the given container and its parent for matching group providers and
601608
// call them to commit values. If an error is encountered, return the number
602609
// of providers called and a non-nil error from the first provided.
603-
func (pt paramGroupedSlice) callGroupProviders(c containerStore) (int, error) {
610+
func (pt paramGroupedCollection) callGroupProviders(c containerStore) (int, error) {
604611
itemCount := 0
605612
for _, c := range c.storesToRoot() {
606613
providers := c.getGroupProviders(pt.Group, pt.Type.Elem())
@@ -618,7 +625,7 @@ func (pt paramGroupedSlice) callGroupProviders(c containerStore) (int, error) {
618625
return itemCount, nil
619626
}
620627

621-
func (pt paramGroupedSlice) Build(c containerStore) (reflect.Value, error) {
628+
func (pt paramGroupedCollection) Build(c containerStore) (reflect.Value, error) {
622629
// do not call this if we are already inside a decorator since
623630
// it will result in an infinite recursion. (i.e. decorate -> params.BuildList() -> Decorate -> params.BuildList...)
624631
// this is safe since a value can be decorated at most once in a given scope.
@@ -644,6 +651,22 @@ func (pt paramGroupedSlice) Build(c containerStore) (reflect.Value, error) {
644651
}
645652

646653
stores := c.storesToRoot()
654+
if pt.isMap {
655+
result := reflect.MakeMapWithSize(pt.Type, itemCount)
656+
for _, c := range stores {
657+
kgvs := c.getValueGroup(pt.Group, pt.Type.Elem())
658+
for _, kgv := range kgvs {
659+
if kgv.key == "" {
660+
return _noValue, newErrInvalidInput(
661+
fmt.Sprintf("every entry in a map value groups must have a name, group \"%v\" is missing a name", pt.Group),
662+
nil,
663+
)
664+
}
665+
result.SetMapIndex(reflect.ValueOf(kgv.key), kgv.value)
666+
}
667+
}
668+
return result, nil
669+
}
647670
result := reflect.MakeSlice(pt.Type, 0, itemCount)
648671
for _, c := range stores {
649672
kgvs := c.getValueGroup(pt.Group, pt.Type.Elem())

0 commit comments

Comments
 (0)