Skip to content

Commit 70add85

Browse files
authored
Adding a before-call-begins callback, to help with troubleshooting (#431)
* implementing a before-call-begins callback, to help with troubleshooting * review feedback
1 parent f02120a commit 70add85

File tree

7 files changed

+180
-49
lines changed

7 files changed

+180
-49
lines changed

before_callback.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright (c) 2025 Uber Technologies, Inc.
2+
//
3+
// Permission is hereby granted, free of charge, to any person obtaining a copy
4+
// of this software and associated documentation files (the "Software"), to deal
5+
// in the Software without restriction, including without limitation the rights
6+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
// copies of the Software, and to permit persons to whom the Software is
8+
// furnished to do so, subject to the following conditions:
9+
//
10+
// The above copyright notice and this permission notice shall be included in
11+
// all copies or substantial portions of the Software.
12+
//
13+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
// THE SOFTWARE.
20+
21+
package dig
22+
23+
// BeforeCallbackInfo contains information about a provided function or decorator
24+
// called by Dig, and is passed to a [BeforeCallback] registered with
25+
// [WithProviderBeforeCallback] or [WithDecoratorBeforeCallback].
26+
type BeforeCallbackInfo struct {
27+
// Name is the name of the function in the format:
28+
// <package_name>.<function_name>
29+
Name string
30+
}
31+
32+
// BeforeCallback is a function that can be registered with a provided function
33+
// using [WithProviderBeforeCallback] or decorator using [WithDecoratorBeforeCallback]
34+
// to cause it to be called before the provided function or decorator is run.
35+
type BeforeCallback func(bci BeforeCallbackInfo)
36+
37+
// WithProviderBeforeCallback returns a [ProvideOption] which has Dig call
38+
// the passed in [BeforeCallback] before the corresponding constructor begins running.
39+
//
40+
// For example, the following prints a message
41+
// before "myConstructor" is called:
42+
//
43+
// c := dig.New()
44+
// myCallback := func(bci BeforeCallbackInfo) {
45+
// fmt.Printf("%q started", bci.Name)
46+
// }
47+
// c.Provide(myConstructor, WithProviderBeforeCallback(myCallback)),
48+
//
49+
// BeforeCallbacks can also be specified for Decorators with [WithDecoratorBeforeCallback].
50+
//
51+
// See [BeforeCallbackInfo] for more info on the information passed to the [BeforeCallback].
52+
func WithProviderBeforeCallback(callback BeforeCallback) ProvideOption {
53+
return withBeforeCallbackOption{
54+
callback: callback,
55+
}
56+
}
57+
58+
// WithDecoratorBeforeCallback returns a [DecorateOption] which has Dig call
59+
// the passed in [BeforeCallback] before the corresponding decorator begins running.
60+
//
61+
// For example, the following prints a message
62+
// before "myDecorator" is called:
63+
//
64+
// c := dig.New()
65+
// myCallback := func(bci BeforeCallbackInfo) {
66+
// fmt.Printf("%q started", bci.Name)
67+
// }
68+
// c.Decorate(myDecorator, WithDecoratorBeforeCallback(myCallback)),
69+
//
70+
// BeforeCallbacks can also be specified for Constructors with [WithProviderBeforeCallback].
71+
//
72+
// See [BeforeCallbackInfo] for more info on the information passed to the [BeforeCallback].
73+
func WithDecoratorBeforeCallback(callback BeforeCallback) DecorateOption {
74+
return withBeforeCallbackOption{
75+
callback: callback,
76+
}
77+
}
78+
79+
type withBeforeCallbackOption struct {
80+
callback BeforeCallback
81+
}
82+
83+
var (
84+
_ ProvideOption = withBeforeCallbackOption{}
85+
_ DecorateOption = withBeforeCallbackOption{}
86+
)
87+
88+
func (o withBeforeCallbackOption) applyProvideOption(po *provideOptions) {
89+
po.BeforeCallback = o.callback
90+
}
91+
92+
func (o withBeforeCallbackOption) apply(do *decorateOptions) {
93+
do.BeforeCallback = o.callback
94+
}

callback.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ type CallbackInfo struct {
4141
}
4242

4343
// Callback is a function that can be registered with a provided function
44-
// or decorator with [WithCallback] to cause it to be called after the
45-
// provided function or decorator is run.
44+
// or decorator with [WithProviderCallback] or decorator with [WithDecoratorCallback]
45+
// to cause it to be called after the provided function or decorator is run.
4646
type Callback func(CallbackInfo)
4747

4848
// WithProviderCallback returns a [ProvideOption] which has Dig call

constructor.go

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -66,16 +66,20 @@ type constructorNode struct {
6666

6767
// Callback for this provided function, if there is one.
6868
callback Callback
69+
70+
// BeforeCallback for this provided function, if there is one.
71+
beforeCallback BeforeCallback
6972
}
7073

7174
type constructorOptions struct {
7275
// If specified, all values produced by this constructor have the provided name
7376
// belong to the specified value group or implement any of the interfaces.
74-
ResultName string
75-
ResultGroup string
76-
ResultAs []interface{}
77-
Location *digreflect.Func
78-
Callback Callback
77+
ResultName string
78+
ResultGroup string
79+
ResultAs []interface{}
80+
Location *digreflect.Func
81+
Callback Callback
82+
BeforeCallback BeforeCallback
7983
}
8084

8185
func newConstructorNode(ctor interface{}, s *Scope, origS *Scope, opts constructorOptions) (*constructorNode, error) {
@@ -106,16 +110,17 @@ func newConstructorNode(ctor interface{}, s *Scope, origS *Scope, opts construct
106110
}
107111

108112
n := &constructorNode{
109-
ctor: ctor,
110-
ctype: ctype,
111-
location: location,
112-
id: dot.CtorID(cptr),
113-
paramList: params,
114-
resultList: results,
115-
orders: make(map[*Scope]int),
116-
s: s,
117-
origS: origS,
118-
callback: opts.Callback,
113+
ctor: ctor,
114+
ctype: ctype,
115+
location: location,
116+
id: dot.CtorID(cptr),
117+
paramList: params,
118+
resultList: results,
119+
orders: make(map[*Scope]int),
120+
s: s,
121+
origS: origS,
122+
callback: opts.Callback,
123+
beforeCallback: opts.BeforeCallback,
119124
}
120125
s.newGraphNode(n, n.orders)
121126
return n, nil
@@ -160,6 +165,12 @@ func (n *constructorNode) Call(c containerStore) (err error) {
160165
}
161166
}
162167

168+
if n.beforeCallback != nil {
169+
n.beforeCallback(BeforeCallbackInfo{
170+
Name: fmt.Sprintf("%v.%v", n.location.Package, n.location.Name),
171+
})
172+
}
173+
163174
if n.callback != nil {
164175
start := c.clock().Now()
165176
// Wrap in separate func to include PanicErrors

decorate.go

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ type decoratorNode struct {
6868

6969
// Callback for this decorator, if there is one.
7070
callback Callback
71+
72+
// BeforeCallback for this decorator, if there is one
73+
beforeCallback BeforeCallback
7174
}
7275

7376
func newDecoratorNode(dcor interface{}, s *Scope, opts decorateOptions) (*decoratorNode, error) {
@@ -86,15 +89,16 @@ func newDecoratorNode(dcor interface{}, s *Scope, opts decorateOptions) (*decora
8689
}
8790

8891
n := &decoratorNode{
89-
dcor: dcor,
90-
dtype: dtype,
91-
id: dot.CtorID(dptr),
92-
location: digreflect.InspectFunc(dcor),
93-
orders: make(map[*Scope]int),
94-
params: pl,
95-
results: rl,
96-
s: s,
97-
callback: opts.Callback,
92+
dcor: dcor,
93+
dtype: dtype,
94+
id: dot.CtorID(dptr),
95+
location: digreflect.InspectFunc(dcor),
96+
orders: make(map[*Scope]int),
97+
params: pl,
98+
results: rl,
99+
s: s,
100+
callback: opts.Callback,
101+
beforeCallback: opts.BeforeCallback,
98102
}
99103
return n, nil
100104
}
@@ -120,6 +124,11 @@ func (n *decoratorNode) Call(s containerStore) (err error) {
120124
Reason: err,
121125
}
122126
}
127+
if n.beforeCallback != nil {
128+
n.beforeCallback(BeforeCallbackInfo{
129+
Name: fmt.Sprintf("%v.%v", n.location.Package, n.location.Name),
130+
})
131+
}
123132

124133
if n.callback != nil {
125134
start := s.clock().Now()
@@ -162,8 +171,9 @@ type DecorateOption interface {
162171
}
163172

164173
type decorateOptions struct {
165-
Info *DecorateInfo
166-
Callback Callback
174+
Info *DecorateInfo
175+
Callback Callback
176+
BeforeCallback BeforeCallback
167177
}
168178

169179
// FillDecorateInfo is a DecorateOption that writes info on what Dig was

decorate_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828

2929
"github.com/stretchr/testify/assert"
3030
"github.com/stretchr/testify/require"
31+
3132
"go.uber.org/dig"
3233
"go.uber.org/dig/internal/digtest"
3334
)
@@ -164,7 +165,7 @@ func TestDecorateSuccess(t *testing.T) {
164165
i := i
165166
c.RequireProvide(func() *someInt { return newSomeInt(i) }, dig.Group("values"), dig.As(new(myInt)))
166167
}
167-
c.Decorate(func(in A) B {
168+
c.RequireDecorate(func(in A) B {
168169
for _, v := range in.Values {
169170
v.Increment()
170171
}

dig_test.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333

3434
"github.com/stretchr/testify/assert"
3535
"github.com/stretchr/testify/require"
36+
3637
"go.uber.org/dig"
3738
"go.uber.org/dig/internal/digclock"
3839
"go.uber.org/dig/internal/digtest"
@@ -1689,13 +1690,19 @@ func giveInt() int { return 5 }
16891690
func TestCallback(t *testing.T) {
16901691
t.Run("no errors", func(t *testing.T) {
16911692
var (
1692-
provideCallbackCalled bool
1693-
decorateCallbackCalled bool
1693+
provideBeforeCallbackCalled bool
1694+
decorateBeforeCallbackCalled bool
1695+
provideCallbackCalled bool
1696+
decorateCallbackCalled bool
16941697
)
16951698

16961699
c := digtest.New(t)
16971700
c.RequireProvide(
16981701
giveInt,
1702+
dig.WithProviderBeforeCallback(func(bci dig.BeforeCallbackInfo) {
1703+
assert.Equal(t, "go.uber.org/dig_test.giveInt", bci.Name)
1704+
provideBeforeCallbackCalled = true
1705+
}),
16991706
dig.WithProviderCallback(func(ci dig.CallbackInfo) {
17001707
assert.Equal(t, "go.uber.org/dig_test.giveInt", ci.Name)
17011708
assert.NoError(t, ci.Error)
@@ -1704,17 +1711,23 @@ func TestCallback(t *testing.T) {
17041711
)
17051712
c.RequireDecorate(
17061713
func(a int) int { return a + 5 },
1714+
dig.WithDecoratorBeforeCallback(func(bci dig.BeforeCallbackInfo) {
1715+
assert.Equal(t, "go.uber.org/dig_test.TestCallback.func1.3", bci.Name)
1716+
decorateBeforeCallbackCalled = true
1717+
}),
17071718
dig.WithDecoratorCallback(func(ci dig.CallbackInfo) {
1708-
assert.Equal(t, "go.uber.org/dig_test.TestCallback.func1.2", ci.Name)
1719+
assert.Equal(t, "go.uber.org/dig_test.TestCallback.func1.3", ci.Name)
17091720
assert.NoError(t, ci.Error)
17101721
decorateCallbackCalled = true
17111722
}),
17121723
)
17131724

17141725
c.RequireInvoke(func(a int) {})
17151726

1716-
assert.True(t, provideCallbackCalled)
1717-
assert.True(t, decorateCallbackCalled)
1727+
assert.True(t, provideCallbackCalled, "missing provide callback")
1728+
assert.True(t, decorateCallbackCalled, "missing decorate callback")
1729+
assert.True(t, provideBeforeCallbackCalled, "missing provide before-callback")
1730+
assert.True(t, decorateBeforeCallbackCalled, "missing decorate before-callback")
17181731
})
17191732

17201733
t.Run("provide error", func(t *testing.T) {
@@ -1732,7 +1745,7 @@ func TestCallback(t *testing.T) {
17321745
}),
17331746
)
17341747

1735-
c.Invoke(func(a int) {})
1748+
assert.Error(t, c.Invoke(func(a int) {}))
17361749
assert.True(t, called)
17371750
})
17381751

@@ -1752,7 +1765,7 @@ func TestCallback(t *testing.T) {
17521765
}),
17531766
)
17541767

1755-
c.Invoke(func(a int) {})
1768+
assert.Error(t, c.Invoke(func(a int) {}))
17561769
assert.True(t, called)
17571770
})
17581771

@@ -1771,7 +1784,7 @@ func TestCallback(t *testing.T) {
17711784
}),
17721785
)
17731786

1774-
c.Invoke(func(int) {})
1787+
assert.Error(t, c.Invoke(func(int) {}))
17751788
assert.True(t, called)
17761789
})
17771790

provide.go

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,14 @@ type ProvideOption interface {
3737
}
3838

3939
type provideOptions struct {
40-
Name string
41-
Group string
42-
Info *ProvideInfo
43-
As []interface{}
44-
Location *digreflect.Func
45-
Exported bool
46-
Callback Callback
40+
Name string
41+
Group string
42+
Info *ProvideInfo
43+
As []interface{}
44+
Location *digreflect.Func
45+
Exported bool
46+
Callback Callback
47+
BeforeCallback BeforeCallback
4748
}
4849

4950
func (o *provideOptions) Validate() error {
@@ -467,11 +468,12 @@ func (s *Scope) provide(ctor interface{}, opts provideOptions) (err error) {
467468
s,
468469
origScope,
469470
constructorOptions{
470-
ResultName: opts.Name,
471-
ResultGroup: opts.Group,
472-
ResultAs: opts.As,
473-
Location: opts.Location,
474-
Callback: opts.Callback,
471+
ResultName: opts.Name,
472+
ResultGroup: opts.Group,
473+
ResultAs: opts.As,
474+
Location: opts.Location,
475+
Callback: opts.Callback,
476+
BeforeCallback: opts.BeforeCallback,
475477
},
476478
)
477479
if err != nil {

0 commit comments

Comments
 (0)