Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add documentation and tests for soft group values #909

Merged
merged 5 commits into from Aug 4, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
46 changes: 44 additions & 2 deletions annotated_test.go
Expand Up @@ -444,6 +444,22 @@ func TestAnnotatedWrongUsage(t *testing.T) {
)
assert.Contains(t, app.Err().Error(), "embeds a dig.In", "expected error when result types were annotated")
})

t.Run("invalid group option", func(t *testing.T) {
t.Parallel()

app := NewForTest(t,
fx.Provide(
fx.Annotated{
Group: "foo,soft",
Target: func() string {
return "sad times"
},
},
sywhang marked this conversation as resolved.
Show resolved Hide resolved
),
)
assert.Contains(t, app.Err().Error(), "cannot use soft with result value groups", "expected error when invalid group option is provided")
})
}

func TestAnnotatedString(t *testing.T) {
Expand Down Expand Up @@ -642,6 +658,34 @@ func TestAnnotate(t *testing.T) {
assert.Contains(t, err.Error(), `missing type: []*fx_test.a[name="a"]`)
})

t.Run("Invoke function with soft group param", func(t *testing.T) {
t.Parallel()
newF := func(foos []int, bar string) {
assert.ElementsMatch(t, []int{10}, foos)
}
app := fxtest.New(t,
fx.Provide(
fx.Annotate(
func() (int, string) { return 10, "hello" },
fx.ResultTags(`group:"foos"`),
),
fx.Annotate(
func() int {
require.FailNow(t, "this function should not be called")
return 20
},
fx.ResultTags(`group:"foos"`),
),
),
fx.Invoke(
fx.Annotate(newF, fx.ParamTags(`group:"foos,soft"`)),
),
)

defer app.RequireStart().RequireStop()
require.NoError(t, app.Err())
})

t.Run("Invoke variadic function with multiple params", func(t *testing.T) {
t.Parallel()

Expand Down Expand Up @@ -984,7 +1028,6 @@ func TestAnnotate(t *testing.T) {
assert.Contains(t, err.Error(), "invalid annotation function func(fx_test.B) string")
assert.Contains(t, err.Error(), "fx.In structs cannot be annotated")
})

}

func assertApp(
Expand Down Expand Up @@ -1279,7 +1322,6 @@ func TestHookAnnotations(t *testing.T) {
require.Equal(t, "constructor", <-ch)
require.Equal(t, "decorated", <-ch)
})

}

func TestHookAnnotationFailures(t *testing.T) {
Expand Down
50 changes: 50 additions & 0 deletions decorate_test.go
Expand Up @@ -22,6 +22,7 @@ package fx_test

import (
"errors"
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -185,6 +186,55 @@ func TestDecorateSuccess(t *testing.T) {
defer app.RequireStart().RequireStop()
})

t.Run("decorator with soft value group", func(t *testing.T) {
app := fxtest.New(t,
fx.Provide(
fx.Annotate(
func() (string, int) { return "cheeseburger", 15 },
fx.ResultTags(`group:"burger"`, `group:"potato"`),
),
),
fx.Provide(
fx.Annotate(
func() (string, int) { return "mushroomburger", 35 },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

non-review comment: mushroom burger...? 🤣

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While I was writing this I couldn't remember the name of another burger hahaha

fx.ResultTags(`group:"burger"`, `group:"potato"`),
),
),
fx.Provide(
fx.Annotate(
func() string {
require.FailNow(t, "should not be called")
return "veggieburger"
},
fx.ResultTags(`group:"burger"`, `group:"potato"`),
),
),
fx.Decorate(
fx.Annotate(
func(burgers []string) []string {
retBurg := make([]string, len(burgers))
for i, burger := range burgers {
retBurg[i] = strings.ToUpper(burger)
}
return retBurg
},
fx.ParamTags(`group:"burger,soft"`),
fx.ResultTags(`group:"burger"`),
),
),
fx.Invoke(
fx.Annotate(
func(burgers []string, fries []int) {
assert.ElementsMatch(t, []string{"CHEESEBURGER", "MUSHROOMBURGER"}, burgers)
},
fx.ParamTags(`group:"burger,soft"`, `group:"potato"`),
),
),
)
defer app.RequireStart().RequireStop()
require.NoError(t, app.Err())
})

t.Run("decorator with optional parameter", func(t *testing.T) {
type Config struct {
Name string
Expand Down
71 changes: 71 additions & 0 deletions inout.go
Expand Up @@ -163,6 +163,77 @@ import "go.uber.org/dig"
// Note that values in a value group are unordered. Fx makes no guarantees
// about the order in which these values will be produced.
//
// To declare a soft relationship between a group and its constructors, use
// the `soft` option on the group tag (`group:"[groupname],soft"`), this
// option can only be used for input parameters, e.g. `fx.In` structures.
// A soft group will be populated only with values from already-executed
// constructors.
//
// type Params struct {
// fx.In
//
// Handlers []Handler `group:"server"`
// Logger *zap.Logger
// }
//
// NewHandlerAndLogger := func() (Handler, *zap.Logger) { ... }
// NewHandler := func() Handler { ... }
// Foo := func(Params) { ... }
//
// app := fx.New(
// fx.Provide(NewHandlerAndLogger),
// fx.Provide(NewHandler),
// fx.Invoke(Foo),
// )
//
// The only constructor called is `NewHandler`, because this also provides
// `*zap.Logger` needed in the `Params` struct received by `foo`
//
// In the next example, the slice `s` isn't populated as the provider would be
// called only because of `strings` soft group value
//
// app := fx.New(
// fx.Provide(
// fx.Annotate(
// func() (string,int) { return "hello" },
// fx.ResultTags(`group:"strings"`),
// ),
// ),
// fx.Invoke(
// fx.Annotate(func(s []string) {
// // s will be an empty slice
// },
// fx.ParamTags(`group:"strings,soft"`),
// ),
// ),
// )
//
// In the next example, the slice `s` will be populated because there is a
// consumer for the same type which hasn't a `soft` dependency
//
// app := fx.New(
// fx.Provide(
// fx.Annotate(
// func() string { "hello" },
// fx.ResultTags(`group:"strings"`),
// ),
// ),
// fx.Invoke(
// fx.Annotate(func(b []string) {
// // b will be ["hello"]
// },
// fx.ParamTags(`group:"strings"`),
// ),
// ),
// fx.Invoke(
// fx.Annotate(func(s []string) {
// // s will be ["hello"]
// },
// fx.ParamTags(`group:"strings,soft"`),
// ),
// ),
// )
//
// # Unexported fields
//
// By default, a type that embeds fx.In may not have any unexported fields. The
Expand Down
51 changes: 51 additions & 0 deletions module_test.go
Expand Up @@ -76,6 +76,57 @@ func TestModuleSuccess(t *testing.T) {
defer app.RequireStart().RequireStop()
})

t.Run("provide a value to a soft group value from nested modules", func(t *testing.T) {
t.Parallel()
type Param struct {
fx.In

Foos []string `group:"foo,soft"`
Bar int
}
type Result struct {
fx.Out

Foo string `group:"foo"`
sywhang marked this conversation as resolved.
Show resolved Hide resolved
Bar int
}
app := fxtest.New(t,
fx.Module("child",
fx.Module("grandchild",
fx.Provide(fx.Annotate(
func() string {
require.FailNow(t, "should not be called")
return "there"
},
fx.ResultTags(`group:"foo"`),
)),
fx.Provide(func() Result {
return Result{Foo: "hello", Bar: 10}
}),
),
),
fx.Invoke(func(p Param) {
assert.ElementsMatch(t, []string{"hello"}, p.Foos)
}),
)
defer app.RequireStart().RequireStop()
require.NoError(t, app.Err())
})
t.Run("soft provided to fx.Out struct", func(t *testing.T) {
t.Parallel()

type Result struct {
fx.Out

Bars []int `group:"bar,soft"`
}
app := NewForTest(t,
fx.Provide(func() Result { return Result{Bars: []int{1, 2, 3}} }),
)
err := app.Err()
require.Error(t, err, "failed to create app")
assert.Contains(t, err.Error(), "cannot use soft with result value groups")
})
t.Run("invoke from nested module", func(t *testing.T) {
t.Parallel()
invokeRan := false
Expand Down
41 changes: 41 additions & 0 deletions supply_test.go
Expand Up @@ -153,4 +153,45 @@ func TestSupply(t *testing.T) {
require.Error(t, supplied[1].(*fxevent.Supplied).Err)
})

t.Run("SupplyToASoftGroup", func(t *testing.T) {
t.Parallel()

type Param struct {
fx.In

Foos []string `group:"foo,soft"`
Bar []int `group:"bar"`
}
type Result struct {
fx.Out

Foo string `group:"foo"`
Bar int `group:"bar"`
}
app := fxtest.New(t,
fx.Supply(
Result{
Foo: "sad",
Bar: 20,
}),
fx.Supply(
fx.Annotated{
Target: 10,
Group: "bar",
},
fx.Annotated{
Target: "bye",
Group: "foo",
}),
fx.Supply(fx.Annotated{
Target: "hello",
Group: "foo",
}),
fx.Invoke(func(p Param) {
assert.ElementsMatch(t, []string{"sad"}, p.Foos)
}),
)

defer app.RequireStart().RequireStop()
})
}