Skip to content

Commit 67fffd0

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 67fffd0

File tree

5 files changed

+307
-74
lines changed

5 files changed

+307
-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: 211 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 {
@@ -1998,21 +2155,6 @@ func TestAsExpectingOriginalType(t *testing.T) {
19982155
})
19992156
}
20002157

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-
20162158
type testStruct struct{}
20172159

20182160
func (testStruct) TestMethod(x int) float64 { return float64(x) }
@@ -2559,6 +2701,60 @@ func testProvideFailures(t *testing.T, dryRun bool) {
25592701
)
25602702
})
25612703

2704+
t.Run("provide multiple instances with the same name and same group", func(t *testing.T) {
2705+
c := digtest.New(t, dig.DryRun(dryRun))
2706+
type A struct{}
2707+
type ret1 struct {
2708+
dig.Out
2709+
*A `name:"foo" group:"foos"`
2710+
}
2711+
type ret2 struct {
2712+
dig.Out
2713+
*A `name:"foo" group:"foos"`
2714+
}
2715+
c.RequireProvide(func() ret1 {
2716+
return ret1{A: &A{}}
2717+
})
2718+
2719+
err := c.Provide(func() ret2 {
2720+
return ret2{A: &A{}}
2721+
})
2722+
require.Error(t, err, "expected error on the second provide")
2723+
dig.AssertErrorMatches(t, err,
2724+
`cannot provide function "go.uber.org/dig_test".testProvideFailures\S+`,
2725+
`dig_test.go:\d+`, // file:line
2726+
`cannot provide \*dig_test.A\[name="foo"\] from \[0\].A:`,
2727+
`already provided by "go.uber.org/dig_test".testProvideFailures\S+`,
2728+
)
2729+
})
2730+
2731+
t.Run("provide multiple instances with the same name but different group", func(t *testing.T) {
2732+
c := digtest.New(t, dig.DryRun(dryRun))
2733+
type A struct{}
2734+
type ret1 struct {
2735+
dig.Out
2736+
*A `name:"foo" group:"foos"`
2737+
}
2738+
type ret2 struct {
2739+
dig.Out
2740+
*A `name:"foo" group:"foosss"`
2741+
}
2742+
c.RequireProvide(func() ret1 {
2743+
return ret1{A: &A{}}
2744+
})
2745+
2746+
err := c.Provide(func() ret2 {
2747+
return ret2{A: &A{}}
2748+
})
2749+
require.Error(t, err, "expected error on the second provide")
2750+
dig.AssertErrorMatches(t, err,
2751+
`cannot provide function "go.uber.org/dig_test".testProvideFailures\S+`,
2752+
`dig_test.go:\d+`, // file:line
2753+
`cannot provide \*dig_test.A\[name="foo"\] from \[0\].A:`,
2754+
`already provided by "go.uber.org/dig_test".testProvideFailures\S+`,
2755+
)
2756+
})
2757+
25622758
t.Run("out with unexported field should error", func(t *testing.T) {
25632759
c := digtest.New(t, dig.DryRun(dryRun))
25642760

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)