Skip to content

Commit 5a5b50f

Browse files
committed
Support simultanous name and group tags
As per Dig issue: #380 In order to support Fx feature requests uber-go/fx#998 uber-go/fx#1036 We need to be able to drop the restriction, both in terms of options dig.Name and dig.Group and dig.Out struct annotations on `name` and `group` being mutually exclusive. In a future PR, this can then be exploited to populate value group maps where the 'name' tag becomes the key of a map[string][T]
1 parent 7f9f0b8 commit 5a5b50f

File tree

5 files changed

+332
-74
lines changed

5 files changed

+332
-74
lines changed

decorate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ func findResultKeys(r resultList) ([]key, error) {
288288
keys = append(keys, key{t: innerResult.Type.Elem(), group: innerResult.Group})
289289
case resultObject:
290290
for _, f := range innerResult.Fields {
291-
q = append(q, f.Result)
291+
q = append(q, f.Results...)
292292
}
293293
case resultList:
294294
q = append(q, innerResult.Results...)

dig_test.go

Lines changed: 236 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,53 @@ func TestEndToEndSuccess(t *testing.T) {
749749
assert.ElementsMatch(t, actualStrs, expectedStrs, "list of strings provided must match")
750750
})
751751

752+
t.Run("multiple As with Group and Name", func(t *testing.T) {
753+
c := digtest.New(t)
754+
expectedNames := []string{"inst1", "inst2"}
755+
expectedStrs := []string{"foo", "bar"}
756+
for i, s := range expectedStrs {
757+
s := s
758+
c.RequireProvide(func() *bytes.Buffer {
759+
return bytes.NewBufferString(s)
760+
}, dig.Group("buffs"), dig.Name(expectedNames[i]),
761+
dig.As(new(io.Reader), new(io.Writer)))
762+
}
763+
764+
type in struct {
765+
dig.In
766+
767+
Reader1 io.Reader `name:"inst1"`
768+
Reader2 io.Reader `name:"inst2"`
769+
Readers []io.Reader `group:"buffs"`
770+
Writers []io.Writer `group:"buffs"`
771+
}
772+
773+
var actualStrs []string
774+
var actualStrsName []string
775+
776+
c.RequireInvoke(func(got in) {
777+
require.Len(t, got.Readers, 2)
778+
buf := make([]byte, 3)
779+
for i, r := range got.Readers {
780+
_, err := r.Read(buf)
781+
require.NoError(t, err)
782+
actualStrs = append(actualStrs, string(buf))
783+
// put the text back
784+
got.Writers[i].Write(buf)
785+
}
786+
_, err := got.Reader1.Read(buf)
787+
require.NoError(t, err)
788+
actualStrsName = append(actualStrsName, string(buf))
789+
_, err = got.Reader2.Read(buf)
790+
require.NoError(t, err)
791+
actualStrsName = append(actualStrsName, string(buf))
792+
require.Len(t, got.Writers, 2)
793+
})
794+
795+
assert.ElementsMatch(t, actualStrs, expectedStrs, "list of strings provided must match")
796+
assert.ElementsMatch(t, actualStrsName, expectedStrs, "names: list of strings provided must match")
797+
})
798+
752799
t.Run("As same interface", func(t *testing.T) {
753800
c := digtest.New(t)
754801
c.RequireProvide(func() io.Reader {
@@ -1098,6 +1145,48 @@ func TestGroups(t *testing.T) {
10981145
})
10991146
})
11001147

1148+
t.Run("values are provided; coexist with name", func(t *testing.T) {
1149+
c := digtest.New(t, dig.SetRand(rand.New(rand.NewSource(0))))
1150+
1151+
type out struct {
1152+
dig.Out
1153+
1154+
Value int `group:"val"`
1155+
}
1156+
1157+
type out2 struct {
1158+
dig.Out
1159+
1160+
Value int `name:"inst1" group:"val"`
1161+
}
1162+
1163+
provide := func(i int) {
1164+
c.RequireProvide(func() out {
1165+
return out{Value: i}
1166+
})
1167+
}
1168+
1169+
provide(1)
1170+
provide(2)
1171+
provide(3)
1172+
1173+
c.RequireProvide(func() out2 {
1174+
return out2{Value: 4}
1175+
})
1176+
1177+
type in struct {
1178+
dig.In
1179+
1180+
SingleValue int `name:"inst1"`
1181+
Values []int `group:"val"`
1182+
}
1183+
1184+
c.RequireInvoke(func(i in) {
1185+
assert.Equal(t, []int{1, 2, 3, 4}, i.Values)
1186+
assert.Equal(t, 4, i.SingleValue)
1187+
})
1188+
})
1189+
11011190
t.Run("groups are provided via option", func(t *testing.T) {
11021191
c := digtest.New(t, dig.SetRand(rand.New(rand.NewSource(0))))
11031192

@@ -1122,6 +1211,36 @@ func TestGroups(t *testing.T) {
11221211
})
11231212
})
11241213

1214+
t.Run("groups are provided via option; coexist with name", func(t *testing.T) {
1215+
c := digtest.New(t, dig.SetRand(rand.New(rand.NewSource(0))))
1216+
1217+
provide := func(i int) {
1218+
c.RequireProvide(func() int {
1219+
return i
1220+
}, dig.Group("val"))
1221+
}
1222+
1223+
provide(1)
1224+
provide(2)
1225+
provide(3)
1226+
1227+
c.RequireProvide(func() int {
1228+
return 4
1229+
}, dig.Group("val"), dig.Name("inst1"))
1230+
1231+
type in struct {
1232+
dig.In
1233+
1234+
SingleValue int `name:"inst1"`
1235+
Values []int `group:"val"`
1236+
}
1237+
1238+
c.RequireInvoke(func(i in) {
1239+
assert.Equal(t, []int{1, 2, 3, 4}, i.Values)
1240+
assert.Equal(t, 4, i.SingleValue)
1241+
})
1242+
})
1243+
11251244
t.Run("different types may be grouped", func(t *testing.T) {
11261245
c := digtest.New(t, dig.SetRand(rand.New(rand.NewSource(0))))
11271246

@@ -1429,6 +1548,44 @@ func TestGroups(t *testing.T) {
14291548
})
14301549
})
14311550

1551+
t.Run("flatten collects slices but also handles name", func(t *testing.T) {
1552+
c := digtest.New(t, dig.SetRand(rand.New(rand.NewSource(0))))
1553+
1554+
type out1 struct {
1555+
dig.Out
1556+
1557+
Value []int `name:"foo1" group:"val,flatten"`
1558+
}
1559+
1560+
type out2 struct {
1561+
dig.Out
1562+
1563+
Value []int `name:"foo2" group:"val,flatten"`
1564+
}
1565+
1566+
c.RequireProvide(func() out1 {
1567+
return out1{Value: []int{1, 2}}
1568+
})
1569+
1570+
c.RequireProvide(func() out2 {
1571+
return out2{Value: []int{3, 4}}
1572+
})
1573+
1574+
type in struct {
1575+
dig.In
1576+
1577+
NotFlattenedSlice1 []int `name:"foo1"`
1578+
NotFlattenedSlice2 []int `name:"foo2"`
1579+
Values []int `group:"val"`
1580+
}
1581+
1582+
c.RequireInvoke(func(i in) {
1583+
assert.Equal(t, []int{2, 3, 4, 1}, i.Values)
1584+
assert.Equal(t, []int{1, 2}, i.NotFlattenedSlice1)
1585+
assert.Equal(t, []int{3, 4}, i.NotFlattenedSlice2)
1586+
})
1587+
})
1588+
14321589
t.Run("flatten via option", func(t *testing.T) {
14331590
c := digtest.New(t, dig.SetRand(rand.New(rand.NewSource(0))))
14341591
c.RequireProvide(func() []int {
@@ -1446,6 +1603,31 @@ func TestGroups(t *testing.T) {
14461603
})
14471604
})
14481605

1606+
t.Run("flatten via option also handles name", func(t *testing.T) {
1607+
c := digtest.New(t, dig.SetRand(rand.New(rand.NewSource(0))))
1608+
c.RequireProvide(func() []int {
1609+
return []int{1, 2}
1610+
}, dig.Group("val,flatten"), dig.Name("foo1"))
1611+
1612+
c.RequireProvide(func() []int {
1613+
return []int{3}
1614+
}, dig.Group("val,flatten"), dig.Name("foo2"))
1615+
1616+
type in struct {
1617+
dig.In
1618+
1619+
NotFlattenedSlice1 []int `name:"foo1"`
1620+
NotFlattenedSlice2 []int `name:"foo2"`
1621+
Values []int `group:"val"`
1622+
}
1623+
1624+
c.RequireInvoke(func(i in) {
1625+
assert.Equal(t, []int{2, 3, 1}, i.Values)
1626+
assert.Equal(t, []int{1, 2}, i.NotFlattenedSlice1)
1627+
assert.Equal(t, []int{3}, i.NotFlattenedSlice2)
1628+
})
1629+
})
1630+
14491631
t.Run("flatten via option error if not a slice", func(t *testing.T) {
14501632
c := digtest.New(t, dig.SetRand(rand.New(rand.NewSource(0))))
14511633
err := c.Provide(func() int { return 1 }, dig.Group("val,flatten"))
@@ -1998,21 +2180,6 @@ func TestAsExpectingOriginalType(t *testing.T) {
19982180
})
19992181
}
20002182

2001-
func TestProvideIncompatibleOptions(t *testing.T) {
2002-
t.Parallel()
2003-
2004-
t.Run("group and name", func(t *testing.T) {
2005-
c := digtest.New(t)
2006-
err := c.Provide(func() io.Reader {
2007-
t.Fatal("this function must not be called")
2008-
return nil
2009-
}, dig.Group("foo"), dig.Name("bar"))
2010-
require.Error(t, err)
2011-
assert.Contains(t, err.Error(), "cannot use named values with value groups: "+
2012-
`name:"bar" provided with group:"foo"`)
2013-
})
2014-
}
2015-
20162183
type testStruct struct{}
20172184

20182185
func (testStruct) TestMethod(x int) float64 { return float64(x) }
@@ -2559,6 +2726,60 @@ func testProvideFailures(t *testing.T, dryRun bool) {
25592726
)
25602727
})
25612728

2729+
t.Run("provide multiple instances with the same name and same group", func(t *testing.T) {
2730+
c := digtest.New(t, dig.DryRun(dryRun))
2731+
type A struct{}
2732+
type ret1 struct {
2733+
dig.Out
2734+
*A `name:"foo" group:"foos"`
2735+
}
2736+
type ret2 struct {
2737+
dig.Out
2738+
*A `name:"foo" group:"foos"`
2739+
}
2740+
c.RequireProvide(func() ret1 {
2741+
return ret1{A: &A{}}
2742+
})
2743+
2744+
err := c.Provide(func() ret2 {
2745+
return ret2{A: &A{}}
2746+
})
2747+
require.Error(t, err, "expected error on the second provide")
2748+
dig.AssertErrorMatches(t, err,
2749+
`cannot provide function "go.uber.org/dig_test".testProvideFailures\S+`,
2750+
`dig_test.go:\d+`, // file:line
2751+
`cannot provide \*dig_test.A\[name="foo"\] from \[0\].A:`,
2752+
`already provided by "go.uber.org/dig_test".testProvideFailures\S+`,
2753+
)
2754+
})
2755+
2756+
t.Run("provide multiple instances with the same name but different group", func(t *testing.T) {
2757+
c := digtest.New(t, dig.DryRun(dryRun))
2758+
type A struct{}
2759+
type ret1 struct {
2760+
dig.Out
2761+
*A `name:"foo" group:"foos"`
2762+
}
2763+
type ret2 struct {
2764+
dig.Out
2765+
*A `name:"foo" group:"foosss"`
2766+
}
2767+
c.RequireProvide(func() ret1 {
2768+
return ret1{A: &A{}}
2769+
})
2770+
2771+
err := c.Provide(func() ret2 {
2772+
return ret2{A: &A{}}
2773+
})
2774+
require.Error(t, err, "expected error on the second provide")
2775+
dig.AssertErrorMatches(t, err,
2776+
`cannot provide function "go.uber.org/dig_test".testProvideFailures\S+`,
2777+
`dig_test.go:\d+`, // file:line
2778+
`cannot provide \*dig_test.A\[name="foo"\] from \[0\].A:`,
2779+
`already provided by "go.uber.org/dig_test".testProvideFailures\S+`,
2780+
)
2781+
})
2782+
25622783
t.Run("out with unexported field should error", func(t *testing.T) {
25632784
c := digtest.New(t, dig.DryRun(dryRun))
25642785

provide.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,6 @@ type provideOptions struct {
4646
}
4747

4848
func (o *provideOptions) Validate() error {
49-
if len(o.Group) > 0 {
50-
if len(o.Name) > 0 {
51-
return newErrInvalidInput(
52-
fmt.Sprintf("cannot use named values with value groups: name:%q provided with group:%q", o.Name, o.Group), nil)
53-
}
54-
}
5549

5650
// Names must be representable inside a backquoted string. The only
5751
// limitation for raw string literals as per

0 commit comments

Comments
 (0)