Repository: uber-go/dig
Pull request #320 (2022-02-16 18:53:01)

Add DecorateInfo and FillDecorateInfo
This adds `dig.DecorateInfo` and `dig.FillDecorateInfo`, which behave like `dig.ProvideInfo` and `dig.FillProvideInfo`, except it's for `scope.Decorate`. It records the input and output parameters of the decorator function that Dig was able to get out of. 

Example usage:
```
var info DecorateInfo
c.Decorate(dcor, FillDecorateInfo(&info)) {
  ...
}

// info.Input/info.Output are now lists of stringers (dig.Input) that
// record the type/name/group info for each parameter/result types of dcor.

```

As a side note, I considered naming this `FillDecoratorInfo` but since the other option is named `FillProvideInfo`, not `FillProviderInfo`, I'm naming this to `FillDecorateInfo` to keep some level of consistency.

Ref GO-1220, #318 

codecov[bot]: # [Codecov](https://codecov.io/gh/uber-go/dig/pull/320?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go) Report
> Merging [#320](https://codecov.io/gh/uber-go/dig/pull/320?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go) (425bab2) into [master](https://codecov.io/gh/uber-go/dig/commit/8193c7fedade47266fc4d2bed5f985a09ab5a1cc?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go) (8193c7f) will **decrease** coverage by `0.04%`.
> The diff coverage is `95.00%`.

[![Impacted file tree graph](https://codecov.io/gh/uber-go/dig/pull/320/graphs/tree.svg?width=650&height=150&src=pr&token=ARuH7Frwgw&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go)](https://codecov.io/gh/uber-go/dig/pull/320?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go)

```diff
@@            Coverage Diff             @@
##           master     #320      +/-   ##
==========================================
- Coverage   98.42%   98.37%   -0.05%     
==========================================
  Files          21       21              
  Lines        1398     1417      +19     
==========================================
+ Hits         1376     1394      +18     
- Misses         14       15       +1     
  Partials        8        8              
```


| [Impacted Files](https://codecov.io/gh/uber-go/dig/pull/320?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go) | Coverage Δ | |
|---|---|---|
| [provide.go](https://codecov.io/gh/uber-go/dig/pull/320/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go#diff-cHJvdmlkZS5nbw==) | `100.00% <ø> (ø)` | |
| [decorate.go](https://codecov.io/gh/uber-go/dig/pull/320/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go#diff-ZGVjb3JhdGUuZ28=) | `98.80% <95.00%> (-1.20%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/uber-go/dig/pull/320?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/uber-go/dig/pull/320?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go). Last update [8193c7f...425bab2](https://codecov.io/gh/uber-go/dig/pull/320?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=uber-go).


Patch:
diff --git a/decorate.go b/decorate.go
index 6a81d84..2d96b75 100644
--- a/decorate.go
+++ b/decorate.go
@@ -116,10 +116,37 @@ func (n *decoratorNode) Call(s containerStore) error {
 
 func (n *decoratorNode) ID() dot.CtorID { return n.id }
 
-// DecorateOption modifies the default behavior of Provide.
-// Currently, there is no implementation of it yet.
+// DecorateOption modifies the default behavior of Decorate.
 type DecorateOption interface {
-	noOptionsYet()
+	apply(*decorateOptions)
+}
+
+type decorateOptions struct {
+	Info *DecorateInfo
+}
+
+// FillDecorateInfo is a DecorateOption that writes info on what Dig was
+// able to get out of the provided decorator into the provided DecorateInfo.
+func FillDecorateInfo(info *DecorateInfo) DecorateOption {
+	return fillDecorateInfoOption{info: info}
+}
+
+type fillDecorateInfoOption struct{ info *DecorateInfo }
+
+func (o fillDecorateInfoOption) String() string {
+	return fmt.Sprintf("FillDecorateInfo(%p)", o.info)
+}
+
+func (o fillDecorateInfoOption) apply(opts *decorateOptions) {
+	opts.Info = o.info
+}
+
+// DecorateInfo provides information about the decorator's inputs and outputs
+// types as strings, as well as the ID of the decorator supplied to the Container.
+type DecorateInfo struct {
+	ID      ID
+	Inputs  []*Input
+	Outputs []*Output
 }
 
 // Decorate provides a decorator for a type that has already been provided in the Container.
@@ -164,7 +191,10 @@ func (c *Container) Decorate(decorator interface{}, opts ...DecorateOption) erro
 //
 // Similar to a provider, the decorator function gets called *at most once*.
 func (s *Scope) Decorate(decorator interface{}, opts ...DecorateOption) error {
-	_ = opts // there are no options at this time
+	var options decorateOptions
+	for _, opt := range opts {
+		opt.apply(&options)
+	}
 
 	dn, err := newDecoratorNode(decorator, s)
 	if err != nil {
@@ -181,6 +211,30 @@ func (s *Scope) Decorate(decorator interface{}, opts ...DecorateOption) error {
 		}
 		s.decorators[k] = append(s.decorators[k], dn)
 	}
+
+	if info := options.Info; info != nil {
+		params := dn.params.DotParam()
+		results := dn.results.DotResult()
+		info.ID = (ID)(dn.id)
+		info.Inputs = make([]*Input, len(params))
+		info.Outputs = make([]*Output, len(results))
+
+		for i, param := range params {
+			info.Inputs[i] = &Input{
+				t:        param.Type,
+				optional: param.Optional,
+				name:     param.Name,
+				group:    param.Group,
+			}
+		}
+		for i, res := range results {
+			info.Outputs[i] = &Output{
+				t:     res.Type,
+				name:  res.Name,
+				group: res.Group,
+			}
+		}
+	}
 	return nil
 }
 
diff --git a/provide.go b/provide.go
index 0a04e84..012919c 100644
--- a/provide.go
+++ b/provide.go
@@ -152,7 +152,7 @@ type ProvideInfo struct {
 	Outputs []*Output
 }
 
-// Input contains information on an input parameter of the constructor.
+// Input contains information on an input parameter of a function.
 type Input struct {
 	t           reflect.Type
 	optional    bool
@@ -178,7 +178,7 @@ func (i *Input) String() string {
 	return fmt.Sprintf("%v[%v]", t, strings.Join(toks, ", "))
 }
 
-// Output contains information on an output produced by the constructor.
+// Output contains information on an output produced by a function.
 type Output struct {
 	t           reflect.Type
 	name, group string


Test patch:
diff --git a/decorate_test.go b/decorate_test.go
index 07b5890..8c0d5d9 100644
--- a/decorate_test.go
+++ b/decorate_test.go
@@ -22,6 +22,7 @@ package dig_test
 
 import (
 	"errors"
+	"fmt"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -44,11 +45,17 @@ func TestDecorateSuccess(t *testing.T) {
 			assert.Equal(t, "A", a.name, "expected name to not be decorated yet.")
 		})
 
-		c.RequireDecorate(func(a *A) *A { return &A{name: a.name + "'"} })
+		var info dig.DecorateInfo
 
+		c.RequireDecorate(func(a *A) *A { return &A{name: a.name + "'"} }, dig.FillDecorateInfo(&info))
 		c.RequireInvoke(func(a *A) {
 			assert.Equal(t, "A'", a.name, "expected name to equal decorated name.")
 		})
+
+		require.Equal(t, len(info.Inputs), 1)
+		require.Equal(t, len(info.Outputs), 1)
+		assert.Equal(t, "*dig_test.A", info.Inputs[0].String())
+		assert.Equal(t, "*dig_test.A", info.Outputs[0].String())
 	})
 
 	t.Run("simple decorate a provider from child scope", func(t *testing.T) {
@@ -61,7 +68,8 @@ func TestDecorateSuccess(t *testing.T) {
 		child := c.Scope("child")
 		child.RequireProvide(func() *A { return &A{name: "A"} }, dig.Export(true))
 
-		child.RequireDecorate(func(a *A) *A { return &A{name: a.name + "'"} })
+		var info dig.DecorateInfo
+		child.RequireDecorate(func(a *A) *A { return &A{name: a.name + "'"} }, dig.FillDecorateInfo(&info))
 		c.RequireInvoke(func(a *A) {
 			assert.Equal(t, "A", a.name, "expected name to equal original name in parent scope")
 		})
@@ -69,6 +77,11 @@ func TestDecorateSuccess(t *testing.T) {
 		child.RequireInvoke(func(a *A) {
 			assert.Equal(t, "A'", a.name, "expected name to equal decorated name in child scope")
 		})
+
+		require.Equal(t, len(info.Inputs), 1)
+		require.Equal(t, len(info.Outputs), 1)
+		assert.Equal(t, "*dig_test.A", info.Inputs[0].String())
+		assert.Equal(t, "*dig_test.A", info.Outputs[0].String())
 	})
 
 	t.Run("check parent-provided decorator doesn't need parent to invoke", func(t *testing.T) {
@@ -183,6 +196,7 @@ func TestDecorateSuccess(t *testing.T) {
 		c.RequireProvide(func() *A { return &A{Name: "A"} })
 		c.RequireProvide(func() string { return "b" }, dig.Name("b"))
 
+		var info dig.DecorateInfo
 		c.RequireDecorate(func(b B) C {
 			return C{
 				A: &A{
@@ -190,12 +204,17 @@ func TestDecorateSuccess(t *testing.T) {
 				},
 				B: b.B + "'",
 			}
-		})
+		}, dig.FillDecorateInfo(&info))
 
 		c.RequireInvoke(func(b B) {
 			assert.Equal(t, "A'", b.A.Name)
 			assert.Equal(t, "b'", b.B)
 		})
+
+		require.Equal(t, 2, len(info.Inputs))
+		require.Equal(t, 2, len(info.Outputs))
+		assert.Equal(t, "*dig_test.A", info.Inputs[0].String())
+		assert.Equal(t, `string[name = "b"]`, info.Inputs[1].String())
 	})
 
 	t.Run("decorate with value groups", func(t *testing.T) {
@@ -216,6 +235,7 @@ func TestDecorateSuccess(t *testing.T) {
 		c.RequireProvide(func() string { return "cat" }, dig.Group("animals"))
 		c.RequireProvide(func() string { return "gopher" }, dig.Group("animals"))
 
+		var info dig.DecorateInfo
 		c.RequireDecorate(func(p Params) Result {
 			animals := p.Animals
 			for i := 0; i < len(animals); i++ {
@@ -224,11 +244,14 @@ func TestDecorateSuccess(t *testing.T) {
 			return Result{
 				Animals: animals,
 			}
-		})
+		}, dig.FillDecorateInfo(&info))
 
 		c.RequireInvoke(func(p Params) {
 			assert.ElementsMatch(t, []string{"good dog", "good cat", "good gopher"}, p.Animals)
 		})
+
+		require.Equal(t, 1, len(info.Inputs))
+		assert.Equal(t, `[]string[group = "animals"]`, info.Inputs[0].String())
 	})
 
 	t.Run("decorate with optional parameter", func(t *testing.T) {
@@ -562,3 +585,22 @@ func TestDecorateFailure(t *testing.T) {
 		assert.Contains(t, err.Error(), "*dig_test.A already decorated")
 	})
 }
+
+func TestFillDecorateInfoString(t *testing.T) {
+	t.Parallel()
+
+	t.Run("nil", func(t *testing.T) {
+		t.Parallel()
+
+		assert.Equal(t, "FillDecorateInfo(0x0)", fmt.Sprint(dig.FillDecorateInfo(nil)))
+	})
+
+	t.Run("not nil", func(t *testing.T) {
+		t.Parallel()
+
+		opt := dig.FillDecorateInfo(new(dig.DecorateInfo))
+		assert.NotEqual(t, fmt.Sprint(opt), "FillDecorateInfo(0x0)")
+		assert.Contains(t, fmt.Sprint(opt), "FillDecorateInfo(0x")
+	})
+
+}