internal/wire: support specifying struct fields to inject (#147)
Added wire.Struct function and deprecate old form. Updates #36
This commit is contained in:
@@ -21,6 +21,7 @@ import (
|
|||||||
"go/ast"
|
"go/ast"
|
||||||
"go/token"
|
"go/token"
|
||||||
"go/types"
|
"go/types"
|
||||||
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -550,6 +551,12 @@ func (oc *objectCache) processExpr(info *types.Info, pkgPath string, expr ast.Ex
|
|||||||
return nil, []error{notePosition(exprPos, err)}
|
return nil, []error{notePosition(exprPos, err)}
|
||||||
}
|
}
|
||||||
return v, nil
|
return v, nil
|
||||||
|
case "Struct":
|
||||||
|
s, err := processStructProvider(oc.fset, info, call)
|
||||||
|
if err != nil {
|
||||||
|
return nil, []error{notePosition(exprPos, err)}
|
||||||
|
}
|
||||||
|
return s, nil
|
||||||
case "FieldsOf":
|
case "FieldsOf":
|
||||||
v, err := processFieldsOf(oc.fset, info, call)
|
v, err := processFieldsOf(oc.fset, info, call)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -561,7 +568,7 @@ func (oc *objectCache) processExpr(info *types.Info, pkgPath string, expr ast.Ex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if tn := structArgType(info, expr); tn != nil {
|
if tn := structArgType(info, expr); tn != nil {
|
||||||
p, errs := processStructProvider(oc.fset, tn)
|
p, errs := processStructLiteralProvider(oc.fset, tn)
|
||||||
if len(errs) > 0 {
|
if len(errs) > 0 {
|
||||||
return nil, notePositionAll(exprPos, errs)
|
return nil, notePositionAll(exprPos, errs)
|
||||||
}
|
}
|
||||||
@@ -733,9 +740,11 @@ func funcOutput(sig *types.Signature) (outputSignature, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// processStructProvider creates a provider for a named struct type.
|
// processStructLiteralProvider creates a provider for a named struct type.
|
||||||
// It produces pointer and non-pointer variants via two values in Out.
|
// It produces pointer and non-pointer variants via two values in Out.
|
||||||
func processStructProvider(fset *token.FileSet, typeName *types.TypeName) (*Provider, []error) {
|
//
|
||||||
|
// This is a copy of the old processStructProvider, which is deprecated now.
|
||||||
|
func processStructLiteralProvider(fset *token.FileSet, typeName *types.TypeName) (*Provider, []error) {
|
||||||
out := typeName.Type()
|
out := typeName.Type()
|
||||||
st, ok := out.Underlying().(*types.Struct)
|
st, ok := out.Underlying().(*types.Struct)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -743,6 +752,10 @@ func processStructProvider(fset *token.FileSet, typeName *types.TypeName) (*Prov
|
|||||||
}
|
}
|
||||||
|
|
||||||
pos := typeName.Pos()
|
pos := typeName.Pos()
|
||||||
|
fmt.Fprintf(os.Stderr,
|
||||||
|
"Deprecated: %v, see https://godoc.org/github.com/google/wire#Struct for more information.",
|
||||||
|
notePosition(fset.Position(pos),
|
||||||
|
fmt.Errorf("using struct literal to inject %s, use wire.Struct instead", typeName.Type())))
|
||||||
provider := &Provider{
|
provider := &Provider{
|
||||||
Pkg: typeName.Pkg(),
|
Pkg: typeName.Pkg(),
|
||||||
Name: typeName.Name(),
|
Name: typeName.Name(),
|
||||||
@@ -766,6 +779,82 @@ func processStructProvider(fset *token.FileSet, typeName *types.TypeName) (*Prov
|
|||||||
return provider, nil
|
return provider, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// processStructProvider creates a provider for a named struct type.
|
||||||
|
// It produces pointer and non-pointer variants via two values in Out.
|
||||||
|
func processStructProvider(fset *token.FileSet, info *types.Info, call *ast.CallExpr) (*Provider, error) {
|
||||||
|
// Assumes that call.Fun is wire.Struct.
|
||||||
|
|
||||||
|
if len(call.Args) < 1 {
|
||||||
|
return nil, notePosition(fset.Position(call.Pos()),
|
||||||
|
errors.New("call to Struct must specify the struct to be injected"))
|
||||||
|
}
|
||||||
|
const firstArgReqFormat = "first argument to Struct must be a pointer to a named struct; found %s"
|
||||||
|
structType := info.TypeOf(call.Args[0])
|
||||||
|
structPtr, ok := structType.(*types.Pointer)
|
||||||
|
if !ok {
|
||||||
|
return nil, notePosition(fset.Position(call.Pos()),
|
||||||
|
fmt.Errorf(firstArgReqFormat, types.TypeString(structType, nil)))
|
||||||
|
}
|
||||||
|
|
||||||
|
st, ok := structPtr.Elem().Underlying().(*types.Struct)
|
||||||
|
if !ok {
|
||||||
|
return nil, notePosition(fset.Position(call.Pos()),
|
||||||
|
fmt.Errorf(firstArgReqFormat, types.TypeString(st, nil)))
|
||||||
|
}
|
||||||
|
|
||||||
|
stExpr := call.Args[0].(*ast.CallExpr)
|
||||||
|
typeName := qualifiedIdentObject(info, stExpr.Args[0]) // should be either an identifier or selector
|
||||||
|
provider := &Provider{
|
||||||
|
Pkg: typeName.Pkg(),
|
||||||
|
Name: typeName.Name(),
|
||||||
|
Pos: typeName.Pos(),
|
||||||
|
IsStruct: true,
|
||||||
|
Out: []types.Type{structPtr.Elem(), structPtr},
|
||||||
|
}
|
||||||
|
if allFields(call) {
|
||||||
|
provider.Args = make([]ProviderInput, st.NumFields())
|
||||||
|
for i := 0; i < st.NumFields(); i++ {
|
||||||
|
f := st.Field(i)
|
||||||
|
provider.Args[i] = ProviderInput{
|
||||||
|
Type: f.Type(),
|
||||||
|
FieldName: f.Name(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
provider.Args = make([]ProviderInput, len(call.Args)-1)
|
||||||
|
for i := 1; i < len(call.Args); i++ {
|
||||||
|
v, err := checkField(call.Args[i], st)
|
||||||
|
if err != nil {
|
||||||
|
return nil, notePosition(fset.Position(call.Pos()), err)
|
||||||
|
}
|
||||||
|
provider.Args[i-1] = ProviderInput{
|
||||||
|
Type: v.Type(),
|
||||||
|
FieldName: v.Name(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := 0; i < len(provider.Args); i++ {
|
||||||
|
for j := 0; j < i; j++ {
|
||||||
|
if types.Identical(provider.Args[i].Type, provider.Args[j].Type) {
|
||||||
|
f := st.Field(j)
|
||||||
|
return nil, notePosition(fset.Position(f.Pos()), fmt.Errorf("provider struct has multiple fields of type %s", types.TypeString(provider.Args[j].Type, nil)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return provider, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func allFields(call *ast.CallExpr) bool {
|
||||||
|
if len(call.Args) != 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
b, ok := call.Args[1].(*ast.BasicLit)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.EqualFold(strconv.Quote("*"), b.Value)
|
||||||
|
}
|
||||||
|
|
||||||
// processBind creates an interface binding from a wire.Bind call.
|
// processBind creates an interface binding from a wire.Bind call.
|
||||||
func processBind(fset *token.FileSet, info *types.Info, call *ast.CallExpr) (*IfaceBinding, error) {
|
func processBind(fset *token.FileSet, info *types.Info, call *ast.CallExpr) (*IfaceBinding, error) {
|
||||||
// Assumes that call.Fun is wire.Bind.
|
// Assumes that call.Fun is wire.Bind.
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ func main() {
|
|||||||
|
|
||||||
// appSet is a provider set for creating a real app.
|
// appSet is a provider set for creating a real app.
|
||||||
var appSet = wire.NewSet(
|
var appSet = wire.NewSet(
|
||||||
app{},
|
wire.Struct(new(app), "*"),
|
||||||
greeter{},
|
wire.Struct(new(greeter), "*"),
|
||||||
wire.InterfaceValue(new(timer), realTime{}),
|
wire.InterfaceValue(new(timer), realTime{}),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -61,17 +61,17 @@ var appSet = wire.NewSet(
|
|||||||
// arguments to the injector.
|
// arguments to the injector.
|
||||||
// It is used for Approach A.
|
// It is used for Approach A.
|
||||||
var appSetWithoutMocks = wire.NewSet(
|
var appSetWithoutMocks = wire.NewSet(
|
||||||
app{},
|
wire.Struct(new(app), "*"),
|
||||||
greeter{},
|
wire.Struct(new(greeter), "*"),
|
||||||
)
|
)
|
||||||
|
|
||||||
// mockAppSet is a provider set for creating a mocked app, including the mocked
|
// mockAppSet is a provider set for creating a mocked app, including the mocked
|
||||||
// dependencies.
|
// dependencies.
|
||||||
// It is used for Approach B.
|
// It is used for Approach B.
|
||||||
var mockAppSet = wire.NewSet(
|
var mockAppSet = wire.NewSet(
|
||||||
app{},
|
wire.Struct(new(app), "*"),
|
||||||
greeter{},
|
wire.Struct(new(greeter), "*"),
|
||||||
appWithMocks{},
|
wire.Struct(new(appWithMocks), "*"),
|
||||||
// For each mocked dependency, add a provider and use wire.Bind to bind
|
// For each mocked dependency, add a provider and use wire.Bind to bind
|
||||||
// the concrete type to the relevant interface.
|
// the concrete type to the relevant interface.
|
||||||
newMockTimer,
|
newMockTimer,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
|
|
||||||
func newBazService(*baz.Config) *baz.Service {
|
func newBazService(*baz.Config) *baz.Service {
|
||||||
wire.Build(
|
wire.Build(
|
||||||
baz.Service{},
|
wire.Struct(new(baz.Service), "*"),
|
||||||
wire.FieldsOf(
|
wire.FieldsOf(
|
||||||
new(*baz.Config),
|
new(*baz.Config),
|
||||||
"Foo",
|
"Foo",
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
|
|
||||||
func newBazService() *baz.Service {
|
func newBazService() *baz.Service {
|
||||||
wire.Build(
|
wire.Build(
|
||||||
baz.Service{},
|
wire.Struct(new(baz.Service), "*"),
|
||||||
wire.Value(&baz.Config{
|
wire.Value(&baz.Config{
|
||||||
Foo: &foo.Config{1},
|
Foo: &foo.Config{1},
|
||||||
Bar: &bar.Config{2},
|
Bar: &bar.Config{2},
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ func (m *MainService) String() string {
|
|||||||
|
|
||||||
func newMainService(MainConfig) *MainService {
|
func newMainService(MainConfig) *MainService {
|
||||||
wire.Build(
|
wire.Build(
|
||||||
MainService{},
|
wire.Struct(new(MainService), "Foo", "Bar", "baz"),
|
||||||
wire.FieldsOf(
|
wire.FieldsOf(
|
||||||
new(MainConfig),
|
new(MainConfig),
|
||||||
"Foo",
|
"Foo",
|
||||||
|
|||||||
9
internal/wire/testdata/Struct/foo/foo.go
vendored
9
internal/wire/testdata/Struct/foo/foo.go
vendored
@@ -22,7 +22,9 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fb := injectFooBar()
|
fb := injectFooBar()
|
||||||
|
pfb := injectPartFooBar()
|
||||||
fmt.Println(fb.Foo, fb.Bar)
|
fmt.Println(fb.Foo, fb.Bar)
|
||||||
|
fmt.Println(pfb.Foo, pfb.Bar)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Foo int
|
type Foo int
|
||||||
@@ -42,6 +44,11 @@ func provideBar() Bar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var Set = wire.NewSet(
|
var Set = wire.NewSet(
|
||||||
FooBar{},
|
wire.Struct(new(FooBar), "*"),
|
||||||
provideFoo,
|
provideFoo,
|
||||||
provideBar)
|
provideBar)
|
||||||
|
|
||||||
|
var PartSet = wire.NewSet(
|
||||||
|
wire.Struct(new(FooBar), "Foo"),
|
||||||
|
provideFoo,
|
||||||
|
)
|
||||||
|
|||||||
5
internal/wire/testdata/Struct/foo/wire.go
vendored
5
internal/wire/testdata/Struct/foo/wire.go
vendored
@@ -24,3 +24,8 @@ func injectFooBar() FooBar {
|
|||||||
wire.Build(Set)
|
wire.Build(Set)
|
||||||
return FooBar{}
|
return FooBar{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func injectPartFooBar() FooBar {
|
||||||
|
wire.Build(PartSet)
|
||||||
|
return FooBar{}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1 +1,2 @@
|
|||||||
41 1
|
41 1
|
||||||
|
41 0
|
||||||
|
|||||||
@@ -16,3 +16,11 @@ func injectFooBar() FooBar {
|
|||||||
}
|
}
|
||||||
return fooBar
|
return fooBar
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func injectPartFooBar() FooBar {
|
||||||
|
foo := provideFoo()
|
||||||
|
fooBar := FooBar{
|
||||||
|
Foo: foo,
|
||||||
|
}
|
||||||
|
return fooBar
|
||||||
|
}
|
||||||
|
|||||||
@@ -45,6 +45,6 @@ func provideBar() Bar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var Set = wire.NewSet(
|
var Set = wire.NewSet(
|
||||||
FooBar{},
|
wire.Struct(new(FooBar), "*"),
|
||||||
provideFoo,
|
provideFoo,
|
||||||
provideBar)
|
provideBar)
|
||||||
|
|||||||
@@ -26,6 +26,6 @@ func injectFooBar() *FooBar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func injectEmptyStruct() *Empty {
|
func injectEmptyStruct() *Empty {
|
||||||
wire.Build(Empty{})
|
wire.Build(wire.Struct(new(Empty)))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
36
wire.go
36
wire.go
@@ -29,9 +29,9 @@ package wire
|
|||||||
type ProviderSet struct{}
|
type ProviderSet struct{}
|
||||||
|
|
||||||
// NewSet creates a new provider set that includes the providers in its
|
// NewSet creates a new provider set that includes the providers in its
|
||||||
// arguments. Each argument is a function value, a struct (zero) value, a
|
// arguments. Each argument is a function value, a provider set, a call to
|
||||||
// provider set, a call to Bind, a call to Value, a call to InterfaceValue or a
|
// Struct, a call to Bind, a call to Value, a call to InterfaceValue or a call
|
||||||
// call to FieldsOf.
|
// to FieldsOf.
|
||||||
//
|
//
|
||||||
// Passing a function value to NewSet declares that the function's first
|
// Passing a function value to NewSet declares that the function's first
|
||||||
// return value type will be provided by calling the function. The arguments
|
// return value type will be provided by calling the function. The arguments
|
||||||
@@ -44,15 +44,17 @@ type ProviderSet struct{}
|
|||||||
// will call all the appropriate cleanup functions and return the error from
|
// will call all the appropriate cleanup functions and return the error from
|
||||||
// the injector function.
|
// the injector function.
|
||||||
//
|
//
|
||||||
// Passing a struct value of type S to NewSet declares that both S and *S will
|
|
||||||
// be provided by creating a new value of the appropriate type by filling in
|
|
||||||
// each field of S using the provider of the field's type.
|
|
||||||
//
|
|
||||||
// Passing a ProviderSet to NewSet is the same as if the set's contents
|
// Passing a ProviderSet to NewSet is the same as if the set's contents
|
||||||
// were passed as arguments to NewSet directly.
|
// were passed as arguments to NewSet directly.
|
||||||
//
|
//
|
||||||
// The behavior of passing the result of a call to other functions in this
|
// The behavior of passing the result of a call to other functions in this
|
||||||
// package are described in their respective doc comments.
|
// package are described in their respective doc comments.
|
||||||
|
//
|
||||||
|
// For compatibility with older versions of Wire, passing a struct value of type
|
||||||
|
// S to NewSet declares that both S and *S will be provided by creating a new
|
||||||
|
// value of the appropriate type by filling in each field of S using the
|
||||||
|
// provider of the field's type. This form is deprecated and will be removed in
|
||||||
|
// a future version of Wire: new providers sets should use wire.Struct.
|
||||||
func NewSet(...interface{}) ProviderSet {
|
func NewSet(...interface{}) ProviderSet {
|
||||||
return ProviderSet{}
|
return ProviderSet{}
|
||||||
}
|
}
|
||||||
@@ -137,6 +139,26 @@ func InterfaceValue(typ interface{}, x interface{}) ProvidedValue {
|
|||||||
return ProvidedValue{}
|
return ProvidedValue{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A StructProvider represents a named struct.
|
||||||
|
type StructProvider struct{}
|
||||||
|
|
||||||
|
// Struct specifies that the given struct type will be provided by filling in the fields
|
||||||
|
// in the struct that have the names given. Each of the arguments must be a name
|
||||||
|
// to the field they wish to reference. As a special case, if a single name "*"
|
||||||
|
// is given, then all of the fields in the struct will be filled in.
|
||||||
|
//
|
||||||
|
// For example:
|
||||||
|
//
|
||||||
|
// type S struct {
|
||||||
|
// MyFoo *Foo
|
||||||
|
// MyBar *Bar
|
||||||
|
// }
|
||||||
|
// var Set = wire.NewSet(wire.Struct(new(S), "MyFoo")) -> inject only S.MyFoo
|
||||||
|
// var Set = wire.NewSet(wire.Struct(new(S), "*")) -> inject all fields
|
||||||
|
func Struct(structType interface{}, fieldNames ...string) StructProvider {
|
||||||
|
return StructProvider{}
|
||||||
|
}
|
||||||
|
|
||||||
// StructFields is a collection of the fields from a struct.
|
// StructFields is a collection of the fields from a struct.
|
||||||
type StructFields struct{}
|
type StructFields struct{}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user