Repository: uber-go/fx
Pull request #697 (2020-03-22 17:20:59)

Add fx.Supply()
Adds `fx.Supply(values ...interface{})` to provide values for dependency injection that have been created externally (such as application configuration). `Supply` uses reflection to craft the appropriate function for `Provide`. As stated by the documentation:

Supply makes the given values available for dependency resolution as if they had been provided by a constructor. Specifically, given:

```go
type MyStruct struct{}
var myStruct = &MyStruct{}
```

The following two forms are equivalent:

```go
fx.Provide(func() *MyStruct { return myStruct })
fx.Supply(myStruct)
```

Supply operates by constructing a constructor of the first form based on the types of the supplied values. Supply accepts any number of arguments.

codecov[bot]: # [Codecov](https://codecov.io/gh/uber-go/fx/pull/697?src=pr&el=h1) Report
> Merging [#697](https://codecov.io/gh/uber-go/fx/pull/697?src=pr&el=desc) into [master](https://codecov.io/gh/uber-go/fx/commit/1488d1d0fd845270c03c080771bdf19b196827fa&el=desc) will **decrease** coverage by `0.28%`.
> The diff coverage is `84.61%`.

[![Impacted file tree graph](https://codecov.io/gh/uber-go/fx/pull/697/graphs/tree.svg?width=650&height=150&src=pr&token=PYY9PSfaC6)](https://codecov.io/gh/uber-go/fx/pull/697?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #697      +/-   ##
==========================================
- Coverage   98.06%   97.78%   -0.29%     
==========================================
  Files          14       15       +1     
  Lines         516      541      +25     
==========================================
+ Hits          506      529      +23     
- Misses          8        9       +1     
- Partials        2        3       +1     
```


| [Impacted Files](https://codecov.io/gh/uber-go/fx/pull/697?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [app.go](https://codecov.io/gh/uber-go/fx/pull/697/diff?src=pr&el=tree#diff-YXBwLmdv) | `96.63% <84.61%> (-0.70%)` | :arrow_down: |
| [internal/fxlog/spy.go](https://codecov.io/gh/uber-go/fx/pull/697/diff?src=pr&el=tree#diff-aW50ZXJuYWwvZnhsb2cvc3B5Lmdv) | `100.00% <0.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/uber-go/fx/pull/697?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/uber-go/fx/pull/697?src=pr&el=footer). Last update [1488d1d...e024f9f](https://codecov.io/gh/uber-go/fx/pull/697?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

roberth-k: @glibsm It's a fair point about the visibility. It's not completely zero fortunately, as one can still identify the type being provided. I can think of two options for improvement:

(a) Hiding `reflect.makeFuncStub()` with a dummy phrase like `fx.Supply()`.
(b) Getting the user's call site in `fx.Supply()`.

In either case, the debug messages originate from `fxlog`, and modifying that should be enough. The path of minimal refactoring would probably involve some marker function type that `fx.Supply` and `fxlog` understand -- I'll try out a few experiments in a bit. It'd definitely solve (a), but not necessarily (b). The downside of such an approach is the obvious spaghetti factor increase this would bring.
prashantv: Agree on the visibility front, I think all the existing fx functionality tracks the stack correctly, but we've seen this same issue with helpers that exist outside of `fx`. I actually ran into exactly this issue when building the same functionality outside of fx, see #604 for the task related to improving the stack trace.
roberth-k: @glibsm Supplies are now logged as such:

```
[Fx] SUPPLY	*fx_test.A
[Fx] SUPPLY	*fx_test.B
```

This could be made to log `fx.Supply`'s caller using the stack trace of the `provide`; I'm punting this decision, as one might find it confusing next to PROVIDE logs. https://github.com/uber-go/fx/issues/604 could be revisit candidate.

Patch:
diff --git a/annotated.go b/annotated.go
index b0cb29f..ba63207 100644
--- a/annotated.go
+++ b/annotated.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2019 Uber Technologies, Inc.
+// Copyright (c) 2020 Uber Technologies, Inc.
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to deal
@@ -52,6 +52,8 @@ import (
 //   })
 //
 // Annotated cannot be used with constructors which produce fx.Out objects.
+//
+// When used with fx.Supply, the target is a value rather than a constructor function.
 type Annotated struct {
 	// If specified, this will be used as the name for all non-error values returned
 	// by the constructor. For more information on named values, see the documentation
@@ -70,7 +72,7 @@ type Annotated struct {
 	// constructor should be injected into the value group individually.
 	Group string
 
-	// Target is the constructor being annotated with fx.Annotated.
+	// Target is the constructor or value being annotated with fx.Annotated.
 	Target interface{}
 }
 
diff --git a/app.go b/app.go
index 4d6dbb8..423f8f4 100644
--- a/app.go
+++ b/app.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2019 Uber Technologies, Inc.
+// Copyright (c) 2020 Uber Technologies, Inc.
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to deal
@@ -351,6 +351,9 @@ type provide struct {
 
 	// Stack trace of where this provide was made.
 	Stack fxreflect.Stack
+
+	// IsSupply is true when the Target constructor was emitted by fx.Supply.
+	IsSupply bool
 }
 
 // invoke is a single invocation request to Fx.
@@ -574,11 +577,18 @@ func (app *App) dotGraph() (DotGraph, error) {
 }
 
 func (app *App) provide(p provide) {
-	constructor := p.Target
 	if app.err != nil {
 		return
 	}
-	app.logger.PrintProvide(constructor)
+
+	constructor := p.Target
+
+	switch {
+	case p.IsSupply:
+		app.logger.PrintSupply(constructor)
+	default:
+		app.logger.PrintProvide(constructor)
+	}
 
 	if _, ok := constructor.(Option); ok {
 		app.err = fmt.Errorf("fx.Option should be passed to fx.New directly, "+
diff --git a/internal/fxlog/fxlog.go b/internal/fxlog/fxlog.go
index 18c3e36..4ea0e89 100644
--- a/internal/fxlog/fxlog.go
+++ b/internal/fxlog/fxlog.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2019 Uber Technologies, Inc.
+// Copyright (c) 2020 Uber Technologies, Inc.
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to deal
@@ -58,6 +58,14 @@ func (l *Logger) PrintProvide(t interface{}) {
 	}
 }
 
+// PrintSupply logs a type supplied directly into the dig.Container
+// by the given constructor function.
+func (l *Logger) PrintSupply(constructor interface{}) {
+	for _, rtype := range fxreflect.ReturnTypes(constructor) {
+		l.Printf("SUPPLY\t%s", rtype)
+	}
+}
+
 // PrintSignal logs an os.Signal.
 func (l *Logger) PrintSignal(signal os.Signal) {
 	l.Printf(strings.ToUpper(signal.String()))
diff --git a/supply.go b/supply.go
new file mode 100644
index 0000000..c384852
--- /dev/null
+++ b/supply.go
@@ -0,0 +1,122 @@
+// Copyright (c) 2020 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package fx
+
+import (
+	"fmt"
+	"reflect"
+	"strings"
+
+	"go.uber.org/fx/internal/fxreflect"
+)
+
+// Supply provides instantiated values for dependency injection as if
+// they had been provided using a constructor that simply returns them.
+// The most specific type of each value (as determined by reflection) is used.
+//
+// For example, given:
+//
+//  type (
+//  	TypeA struct{}
+//  	TypeB struct{}
+//  	TypeC struct{}
+//  )
+//
+//  var a, b, c = &TypeA{}, TypeB{}, &TypeC{}
+//
+// The following two forms are equivalent:
+//
+//  fx.Supply(a, b, fx.Annotate{Target: c})
+//
+//  fx.Provide(
+//  	func() *TypeA { return a },
+//  	func() TypeB { return b },
+//  	fx.Annotate{Target: func() *TypeC { return c }},
+//  )
+//
+// Supply panics if a value (or annotation target) is an untyped nil or an error.
+func Supply(values ...interface{}) Option {
+	constructors := make([]interface{}, len(values)) // one function per value
+
+	for i, value := range values {
+		switch value := value.(type) {
+		case Annotated:
+			value.Target = newSupplyConstructor(value.Target)
+			constructors[i] = value
+		default:
+			constructors[i] = newSupplyConstructor(value)
+		}
+	}
+
+	return supplyOption{
+		Targets: constructors,
+		Stack:   fxreflect.CallerStack(1, 0),
+	}
+}
+
+type supplyOption struct {
+	Targets []interface{}
+	Stack   fxreflect.Stack
+}
+
+func (o supplyOption) apply(app *App) {
+	for _, target := range o.Targets {
+		app.provides = append(app.provides, provide{
+			Target:   target,
+			Stack:    o.Stack,
+			IsSupply: true,
+		})
+	}
+}
+
+func (o supplyOption) String() string {
+	items := make([]string, 0, len(o.Targets))
+	for _, target := range o.Targets {
+		switch target := target.(type) {
+		case Annotated:
+			items = append(items, fxreflect.ReturnTypes(target.Target)...)
+		default:
+			items = append(items, fxreflect.ReturnTypes(target)...)
+		}
+	}
+
+	return fmt.Sprintf("fx.Supply(%s)", strings.Join(items, ", "))
+}
+
+// Returns a function that takes no parameters, and returns the given value.
+func newSupplyConstructor(value interface{}) interface{} {
+	switch value.(type) {
+	case nil:
+		panic("untyped nil passed to fx.Supply")
+	case error:
+		panic("error value passed to fx.Supply")
+	}
+
+	returnTypes := []reflect.Type{reflect.TypeOf(value)}
+	returnValues := []reflect.Value{reflect.ValueOf(value)}
+
+	ft := reflect.FuncOf([]reflect.Type{}, returnTypes, false)
+	fv := reflect.MakeFunc(ft, func([]reflect.Value) []reflect.Value {
+		return returnValues
+	})
+
+	return fv.Interface()
+}


Test patch:
diff --git a/app_test.go b/app_test.go
index 2092c42..be68d87 100644
--- a/app_test.go
+++ b/app_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2019 Uber Technologies, Inc.
+// Copyright (c) 2020 Uber Technologies, Inc.
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to deal
@@ -846,6 +846,16 @@ func TestOptionString(t *testing.T) {
 			give: ErrorHook(testErrorHandler{t}),
 			want: "fx.ErrorHook(TestOptionString)",
 		},
+		{
+			desc: "Supply/simple",
+			give: Supply(bytes.NewReader(nil), bytes.NewBuffer(nil)),
+			want: "fx.Supply(*bytes.Reader, *bytes.Buffer)",
+		},
+		{
+			desc: "Supply/Annotated",
+			give: Supply(Annotated{Target: bytes.NewReader(nil)}),
+			want: "fx.Supply(*bytes.Reader)",
+		},
 	}
 
 	for _, tt := range tests {
diff --git a/internal/fxlog/fxlog_test.go b/internal/fxlog/fxlog_test.go
index da3a928..0a49fb3 100644
--- a/internal/fxlog/fxlog_test.go
+++ b/internal/fxlog/fxlog_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2019 Uber Technologies, Inc.
+// Copyright (c) 2020 Uber Technologies, Inc.
 //
 // Permission is hereby granted, free of charge, to any person obtaining a copy
 // of this software and associated documentation files (the "Software"), to deal
@@ -64,6 +64,12 @@ func TestPrint(t *testing.T) {
 		assert.Equal(t, "[Fx] PROVIDE\t*bytes.Buffer <= bytes.NewBuffer()\n", sink.String())
 	})
 
+	t.Run("PrintSupply", func(t *testing.T) {
+		sink.Reset()
+		logger.PrintSupply(func() *bytes.Buffer { return bytes.NewBuffer(nil) })
+		assert.Equal(t, "[Fx] SUPPLY\t*bytes.Buffer\n", sink.String())
+	})
+
 	t.Run("printExpandsTypesInOut", func(t *testing.T) {
 		sink.Reset()
 
diff --git a/supply_test.go b/supply_test.go
new file mode 100644
index 0000000..05cf41c
--- /dev/null
+++ b/supply_test.go
@@ -0,0 +1,104 @@
+// Copyright (c) 2020 Uber Technologies, Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+
+package fx_test
+
+import (
+	"errors"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+
+	"go.uber.org/fx"
+	"go.uber.org/fx/fxtest"
+)
+
+func TestSupply(t *testing.T) {
+	type A struct{}
+	type B struct{}
+
+	t.Run("NothingIsSupplied", func(t *testing.T) {
+		app := fxtest.New(t, fx.Supply())
+		defer app.RequireStart().RequireStop()
+	})
+
+	t.Run("SomethingIsSupplied", func(t *testing.T) {
+		aIn, bIn := &A{}, &B{}
+		var aOut *A
+		var bOut *B
+
+		app := fxtest.New(
+			t,
+			fx.Supply(aIn, bIn),
+			fx.Populate(&aOut, &bOut),
+		)
+		defer app.RequireStart().RequireStop()
+
+		require.Same(t, aIn, aOut)
+		require.Same(t, bIn, bOut)
+	})
+
+	t.Run("AnnotateIsSupplied", func(t *testing.T) {
+		firstIn, secondIn, thirdIn := &A{}, &A{}, &B{}
+		var out struct {
+			fx.In
+
+			First  *A `name:"first"`
+			Second *A `name:"second"`
+			Third  *B
+		}
+
+		app := fxtest.New(
+			t,
+			fx.Supply(
+				fx.Annotated{Name: "first", Target: firstIn},
+				fx.Annotated{Name: "second", Target: secondIn},
+				thirdIn),
+			fx.Populate(&out),
+		)
+		defer app.RequireStart().RequireStop()
+
+		require.Same(t, firstIn, out.First)
+		require.Same(t, secondIn, out.Second)
+		require.Same(t, thirdIn, out.Third)
+	})
+
+	t.Run("InvalidArgumentIsSupplied", func(t *testing.T) {
+		require.PanicsWithValuef(
+			t,
+			"untyped nil passed to fx.Supply",
+			func() { fx.Supply(A{}, nil) },
+			"a naked nil should panic",
+		)
+
+		require.NotPanicsf(
+			t,
+			func() { fx.Supply(A{}, (*B)(nil)) },
+			"a wrapped nil should not panic",
+		)
+
+		require.PanicsWithValuef(
+			t,
+			"error value passed to fx.Supply",
+			func() { fx.Supply(A{}, errors.New("fail")) },
+			"an error value should panic",
+		)
+	})
+}