From da766ab6f8c1b8add42d9aecfdf260f11ee9e00b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Friedrich=20Gro=C3=9Fe?= Date: Sat, 16 May 2015 09:13:59 +0200 Subject: [PATCH 1/5] Fix error message when checking referenced types Fixes #12 --- tests/testAPI/mocks.go | 4 ++++ tests/type_test.go | 4 ++-- type.go | 26 +++++++++++++++++++++----- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/tests/testAPI/mocks.go b/tests/testAPI/mocks.go index 5bc0a19..68aeea3 100644 --- a/tests/testAPI/mocks.go +++ b/tests/testAPI/mocks.go @@ -29,3 +29,7 @@ type TypeForServiceInjection struct { func NewTypeForServiceInjection(injectedType *MockType) *TypeForServiceInjection { return &TypeForServiceInjection{injectedType} } + +func NewTypeForServiceInjectionWithArgs(injectedType *MockType, name, location string, flag bool) *TypeForServiceInjection { + return &TypeForServiceInjection{injectedType} +} diff --git a/tests/type_test.go b/tests/type_test.go index 07756aa..1da03ef 100644 --- a/tests/type_test.go +++ b/tests/type_test.go @@ -124,14 +124,14 @@ var _ = Describe("Type", func() { err := typeRegistry.RegisterType("foo", testAPI.NewFoo) Expect(err).NotTo(HaveOccurred()) - typeDef = goldi.NewType(testAPI.NewTypeForServiceInjection, "@foo") + typeDef = goldi.NewType(testAPI.NewTypeForServiceInjectionWithArgs, "@foo", "arg1", "arg2", true) defer func() { r := recover() Expect(r).NotTo(BeNil(), "Expected Generate to panic") Expect(r).To(BeAssignableToTypeOf(errors.New(""))) err := r.(error) - Expect(err.Error()).To(Equal("could not generate type: the referenced type \"@foo\" (type *testAPI.Foo) can not be passed as argument 1 to the function signature github.com/fgrosse/goldi/tests/testAPI.NewTypeForServiceInjection(*testAPI.MockType)")) + Expect(err.Error()).To(Equal("could not generate type: the referenced type \"@foo\" (type *testAPI.Foo) can not be passed as argument 1 to the function signature testAPI.NewTypeForServiceInjectionWithArgs(*testAPI.MockType, string, string, bool)")) }() typeDef.Generate(config, typeRegistry) diff --git a/type.go b/type.go index 6fd52cf..e24ab11 100644 --- a/type.go +++ b/type.go @@ -4,6 +4,7 @@ import ( "fmt" "reflect" "runtime" + "strings" ) // A Type holds all information that is necessary to create a new instance of a type ID @@ -122,11 +123,7 @@ func (t *Type) resolveTypeReference(i int, typeID string, config map[string]inte typeInstance := referencedType.Generate(config, registry) if reflect.TypeOf(typeInstance).AssignableTo(expectedArgument) == false { - factoryName := runtime.FuncForPC(t.generator.Pointer()).Name() - err := fmt.Errorf("the referenced type \"@%s\" (type %T) can not be passed as argument %d to the function signature %s(%s)", - typeID, typeInstance, i+1, factoryName, expectedArgument.String(), - ) - panic(err) + panic(t.invalidReferencedTypeErr(typeID, typeInstance, i)) } // TODO check if this type is assignable and generate helpful error message if not @@ -135,6 +132,25 @@ func (t *Type) resolveTypeReference(i int, typeID string, config map[string]inte return argument } +func (t *Type) invalidReferencedTypeErr(typeID string, typeInstance interface{}, i int) error { + factoryName := runtime.FuncForPC(t.generator.Pointer()).Name() + factoryNameParts := strings.Split(factoryName, "/") + factoryName = factoryNameParts[len(factoryNameParts)-1] + + n := t.generator.Type().NumIn() + factoryArguments := make([]string, n) + for i := 0; i < n; i++ { + arg := t.generator.Type().In(i) + factoryArguments[i] = arg.String() + } + + err := fmt.Errorf("the referenced type \"@%s\" (type %T) can not be passed as argument %d to the function signature %s(%s)", + typeID, typeInstance, i+1, factoryName, strings.Join(factoryArguments, ", "), + ) + + return err +} + // typeReferenceArguments is an internal function that returns all generator arguments that are type references func (t *Type) typeReferenceArguments() []string { var typeRefParameters []string From fd628b082a5b473214a399f229a32913952ffcf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Friedrich=20Gro=C3=9Fe?= Date: Sat, 16 May 2015 09:17:29 +0200 Subject: [PATCH 2/5] Consequently use factory instead of generator --- type.go | 57 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/type.go b/type.go index e24ab11..372d953 100644 --- a/type.go +++ b/type.go @@ -9,9 +9,9 @@ import ( // A Type holds all information that is necessary to create a new instance of a type ID type Type struct { - generator reflect.Value - generatorType reflect.Type - generatorArguments []reflect.Value + factory reflect.Value + factoryType reflect.Type + factoryArguments []reflect.Value } // NewType creates a new Type and checks if the given factory method can be used get a go type @@ -27,35 +27,35 @@ func NewType(factoryFunction interface{}, factoryParameters ...interface{}) *Typ } }() - generatorType := reflect.TypeOf(factoryFunction) - if generatorType.Kind() != reflect.Func { - panic(fmt.Errorf("kind was %v, not Func", generatorType.Kind())) + factoryType := reflect.TypeOf(factoryFunction) + if factoryType.Kind() != reflect.Func { + panic(fmt.Errorf("kind was %v, not Func", factoryType.Kind())) } - if generatorType.NumOut() != 1 { - panic(fmt.Errorf("invalid number of return parameters: %d", generatorType.NumOut())) + if factoryType.NumOut() != 1 { + panic(fmt.Errorf("invalid number of return parameters: %d", factoryType.NumOut())) } - kindOfGeneratedType := generatorType.Out(0).Kind() + kindOfGeneratedType := factoryType.Out(0).Kind() if kindOfGeneratedType != reflect.Interface && kindOfGeneratedType != reflect.Ptr { panic(fmt.Errorf("return parameter is no interface or pointer but a %v", kindOfGeneratedType)) } - if generatorType.NumIn() != len(factoryParameters) { - panic(fmt.Errorf("invalid number of input parameters: got %d but expected %d", generatorType.NumIn(), len(factoryParameters))) + if factoryType.NumIn() != len(factoryParameters) { + panic(fmt.Errorf("invalid number of input parameters: got %d but expected %d", factoryType.NumIn(), len(factoryParameters))) } return &Type{ - generator: reflect.ValueOf(factoryFunction), - generatorType: generatorType, - generatorArguments: buildGeneratorCallArguments(generatorType, factoryParameters), + factory: reflect.ValueOf(factoryFunction), + factoryType: factoryType, + factoryArguments: buildFactoryCallArguments(factoryType, factoryParameters), } } -func buildGeneratorCallArguments(generatorType reflect.Type, factoryParameters []interface{}) []reflect.Value { +func buildFactoryCallArguments(factoryType reflect.Type, factoryParameters []interface{}) []reflect.Value { args := make([]reflect.Value, len(factoryParameters)) for i, argument := range factoryParameters { - expectedArgumentType := generatorType.In(i) + expectedArgumentType := factoryType.In(i) args[i] = reflect.ValueOf(argument) if args[i].Kind() != expectedArgumentType.Kind() { if stringArg, isString := argument.(string); isString && isParameterOrTypeReference(stringArg) == false { @@ -77,14 +77,14 @@ func (t *Type) Generate(config map[string]interface{}, registry TypeRegistry) in } }() - args := make([]reflect.Value, len(t.generatorArguments)) - for i, argument := range t.generatorArguments { - args[i] = t.resolveParameter(i, argument, t.generatorType.In(i), config, registry) + args := make([]reflect.Value, len(t.factoryArguments)) + for i, argument := range t.factoryArguments { + args[i] = t.resolveParameter(i, argument, t.factoryType.In(i), config, registry) } - result := t.generator.Call(args) + result := t.factory.Call(args) if len(result) == 0 { - panic(fmt.Errorf("no return parameter found. Seems like you did not use goldi.NewTypeGenerator to create this TypeGenerator")) + panic(fmt.Errorf("no return parameter found. Seems like you did not use goldi.NewType to create this Type")) } return result[0].Interface() @@ -126,21 +126,20 @@ func (t *Type) resolveTypeReference(i int, typeID string, config map[string]inte panic(t.invalidReferencedTypeErr(typeID, typeInstance, i)) } - // TODO check if this type is assignable and generate helpful error message if not argument := reflect.New(expectedArgument).Elem() argument.Set(reflect.ValueOf(typeInstance)) return argument } func (t *Type) invalidReferencedTypeErr(typeID string, typeInstance interface{}, i int) error { - factoryName := runtime.FuncForPC(t.generator.Pointer()).Name() + factoryName := runtime.FuncForPC(t.factory.Pointer()).Name() factoryNameParts := strings.Split(factoryName, "/") factoryName = factoryNameParts[len(factoryNameParts)-1] - n := t.generator.Type().NumIn() + n := t.factory.Type().NumIn() factoryArguments := make([]string, n) for i := 0; i < n; i++ { - arg := t.generator.Type().In(i) + arg := t.factory.Type().In(i) factoryArguments[i] = arg.String() } @@ -151,10 +150,10 @@ func (t *Type) invalidReferencedTypeErr(typeID string, typeInstance interface{}, return err } -// typeReferenceArguments is an internal function that returns all generator arguments that are type references +// typeReferenceArguments is an internal function that returns all factory arguments that are type references func (t *Type) typeReferenceArguments() []string { var typeRefParameters []string - for _, argument := range t.generatorArguments { + for _, argument := range t.factoryArguments { stringArgument := argument.Interface().(string) if isTypeReference(stringArgument) { typeRefParameters = append(typeRefParameters, stringArgument[1:]) @@ -163,10 +162,10 @@ func (t *Type) typeReferenceArguments() []string { return typeRefParameters } -// parameterArguments is an internal function that returns all generator arguments that are parameters +// parameterArguments is an internal function that returns all factory arguments that are parameters func (t *Type) parameterArguments() []string { var parameterArguments []string - for _, argument := range t.generatorArguments { + for _, argument := range t.factoryArguments { stringArgument := argument.Interface().(string) if isParameter(stringArgument) { parameterArguments = append(parameterArguments, stringArgument[1:len(stringArgument)-1]) From b01b5bc39dcc61bf08df2d9b6f7d49968561921f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Friedrich=20Gro=C3=9Fe?= Date: Sat, 16 May 2015 10:27:26 +0200 Subject: [PATCH 3/5] Allow generating struct types without factory method Fixes #13 --- tests/type_test.go | 205 +++++++++++++++++++++++++++++++-------------- type.go | 69 +++++++++++++-- 2 files changed, 203 insertions(+), 71 deletions(-) diff --git a/tests/type_test.go b/tests/type_test.go index 1da03ef..79ead9f 100644 --- a/tests/type_test.go +++ b/tests/type_test.go @@ -5,6 +5,7 @@ import ( . "github.com/onsi/gomega" "errors" + "fmt" "github.com/fgrosse/goldi" "github.com/fgrosse/goldi/tests/testAPI" ) @@ -14,7 +15,7 @@ var _ = Describe("Type", func() { Describe("NewType()", func() { Context("with invalid factory function", func() { - It("should panic if the generator is no function", func() { + It("should panic if the generator is no function or pointer to a struct", func() { Expect(func() { goldi.NewType(42) }).To(Panic()) }) @@ -35,44 +36,53 @@ var _ = Describe("Type", func() { }) }) - Context("with factory functions without arguments", func() { - Context("when no factory argument is given", func() { - It("should create the type", func() { - typeDef = goldi.NewType(testAPI.NewMockType) - Expect(typeDef).NotTo(BeNil()) + Context("with factory functions", func() { + Context("without arguments", func() { + Context("when no factory argument is given", func() { + It("should create the type", func() { + typeDef = goldi.NewType(testAPI.NewMockType) + Expect(typeDef).NotTo(BeNil()) + }) }) - }) - Context("when any argument is given", func() { - It("should panic", func() { - Expect(func() { goldi.NewType(testAPI.NewMockType, "foo") }).To(Panic()) + Context("when any argument is given", func() { + It("should panic", func() { + Expect(func() { goldi.NewType(testAPI.NewMockType, "foo") }).To(Panic()) + }) }) }) - }) - Context("with factory functions with one or more arguments", func() { - Context("when an invalid number of arguments is given", func() { - It("should panic", func() { - Expect(func() { goldi.NewType(testAPI.NewMockTypeWithArgs) }).To(Panic()) - Expect(func() { goldi.NewType(testAPI.NewMockTypeWithArgs, "foo") }).To(Panic()) - Expect(func() { goldi.NewType(testAPI.NewMockTypeWithArgs, "foo", false, 42) }).To(Panic()) + Context("with one or more arguments", func() { + Context("when an invalid number of arguments is given", func() { + It("should panic", func() { + Expect(func() { goldi.NewType(testAPI.NewMockTypeWithArgs) }).To(Panic()) + Expect(func() { goldi.NewType(testAPI.NewMockTypeWithArgs, "foo") }).To(Panic()) + Expect(func() { goldi.NewType(testAPI.NewMockTypeWithArgs, "foo", false, 42) }).To(Panic()) + }) }) - }) - Context("when the wrong argument types are given", func() { - It("should panic", func() { - Expect(func() { goldi.NewType(testAPI.NewMockTypeWithArgs, "foo", "bar") }).To(Panic()) - Expect(func() { goldi.NewType(testAPI.NewMockTypeWithArgs, true, "bar") }).To(Panic()) + Context("when the wrong argument types are given", func() { + It("should panic", func() { + Expect(func() { goldi.NewType(testAPI.NewMockTypeWithArgs, "foo", "bar") }).To(Panic()) + Expect(func() { goldi.NewType(testAPI.NewMockTypeWithArgs, true, "bar") }).To(Panic()) + }) }) - }) - Context("when the correct argument number and types are given", func() { - It("should create the type", func() { - typeDef = goldi.NewType(testAPI.NewMockTypeWithArgs, "foo", true) - Expect(typeDef).NotTo(BeNil()) + Context("when the correct argument number and types are given", func() { + It("should create the type", func() { + typeDef = goldi.NewType(testAPI.NewMockTypeWithArgs, "foo", true) + Expect(typeDef).NotTo(BeNil()) + }) }) }) }) + + Context("with struct factory", func() { + It("should create the type", func() { + typeDef = goldi.NewType(&testAPI.MockType{}) + Expect(typeDef).NotTo(BeNil()) + }) + }) }) Describe("Generate()", func() { @@ -85,56 +95,123 @@ var _ = Describe("Type", func() { typeRegistry = goldi.NewTypeRegistry() }) - Context("with factory functions without arguments", func() { - It("should generate the type", func() { - typeDef = goldi.NewType(testAPI.NewMockType) - Expect(typeDef.Generate(config, typeRegistry)).To(BeAssignableToTypeOf(&testAPI.MockType{})) + Context("with a type factory", func() { + Context("without arguments", func() { + It("should generate the type", func() { + typeDef = goldi.NewType(&testAPI.MockType{}) + Expect(typeDef.Generate(config, typeRegistry)).To(BeAssignableToTypeOf(&testAPI.MockType{})) + }) + + It("should generate a new type each time", func() { + typeDef = goldi.NewType(&testAPI.MockType{}) + t1 := typeDef.Generate(config, typeRegistry) + t2 := typeDef.Generate(config, typeRegistry) + + Expect(t1).NotTo(BeNil()) + Expect(t2).NotTo(BeNil()) + Expect(t1 == t2).To(BeFalse(), fmt.Sprintf("t1 (%p) should not point to the same instance as t2 (%p)", t1, t2)) + + // Just to make the whole issue more explicit: + t1Mock := t1.(*testAPI.MockType) + t2Mock := t2.(*testAPI.MockType) + t1Mock.StringParameter = "CHANGED" + Expect(t2Mock.StringParameter).NotTo(Equal(t1Mock.StringParameter), + "Changing two indipendently generated types should not affect both at the same time", + ) + }) }) - }) - Context("with factory functions with one or more arguments", func() { - It("should generate the type", func() { - typeDef = goldi.NewType(testAPI.NewMockTypeWithArgs, "foo", true) + Context("with one or more arguments", func() { + It("should generate the type", func() { + typeDef = goldi.NewType(&testAPI.MockType{}, "foo", true) - generatedType := typeDef.Generate(config, typeRegistry) - Expect(generatedType).To(BeAssignableToTypeOf(&testAPI.MockType{})) + generatedType := typeDef.Generate(config, typeRegistry) + Expect(generatedType).To(BeAssignableToTypeOf(&testAPI.MockType{})) - generatedMock := generatedType.(*testAPI.MockType) - Expect(generatedMock.StringParameter).To(Equal("foo")) - Expect(generatedMock.BoolParameter).To(Equal(true)) + generatedMock := generatedType.(*testAPI.MockType) + Expect(generatedMock.StringParameter).To(Equal("foo")) + Expect(generatedMock.BoolParameter).To(Equal(true)) + }) + + It("should use the given parameters", func() { + typeDef = goldi.NewType(&testAPI.MockType{}, "%param1%", "%param2%") + config["param1"] = "TEST" + config["param2"] = true + generatedType := typeDef.Generate(config, typeRegistry) + Expect(generatedType).To(BeAssignableToTypeOf(&testAPI.MockType{})) + + generatedMock := generatedType.(*testAPI.MockType) + Expect(generatedMock.StringParameter).To(Equal("TEST")) + Expect(generatedMock.BoolParameter).To(Equal(true)) + }) + + It("should panic if more factory arguments where provided than the struct has fields", func() { + typeDef = goldi.NewType(&testAPI.MockType{}, "foo", true, "bar") + defer func() { + r := recover() + Expect(r).NotTo(BeNil(), "Expected Generate to panic") + Expect(r).To(BeAssignableToTypeOf(errors.New(""))) + err := r.(error) + Expect(err.Error()).To(Equal("could not generate type: the struct testAPI.MockType has only 2 fields but 3 arguments where provided on type registration")) + }() + + typeDef.Generate(config, typeRegistry) + }) }) + }) - Context("when a type reference is given", func() { - Context("and its type matches the function signature", func() { - It("should generate the type", func() { - err := typeRegistry.RegisterType("foo", testAPI.NewMockType) - Expect(err).NotTo(HaveOccurred()) + Context("with factory functions", func() { + Context("without arguments", func() { + It("should generate the type", func() { + typeDef = goldi.NewType(testAPI.NewMockType) + Expect(typeDef.Generate(config, typeRegistry)).To(BeAssignableToTypeOf(&testAPI.MockType{})) + }) + }) + + Context("with one or more arguments", func() { + It("should generate the type", func() { + typeDef = goldi.NewType(testAPI.NewMockTypeWithArgs, "foo", true) - typeDef = goldi.NewType(testAPI.NewTypeForServiceInjection, "@foo") - generatedType := typeDef.Generate(config, typeRegistry) - Expect(generatedType).To(BeAssignableToTypeOf(&testAPI.TypeForServiceInjection{})) + generatedType := typeDef.Generate(config, typeRegistry) + Expect(generatedType).To(BeAssignableToTypeOf(&testAPI.MockType{})) - generatedMock := generatedType.(*testAPI.TypeForServiceInjection) - Expect(generatedMock.InjectedType).To(BeAssignableToTypeOf(&testAPI.MockType{})) - }) + generatedMock := generatedType.(*testAPI.MockType) + Expect(generatedMock.StringParameter).To(Equal("foo")) + Expect(generatedMock.BoolParameter).To(Equal(true)) }) - Context("and its type does not match the function signature", func() { - It("should panic with a helpful error message", func() { - err := typeRegistry.RegisterType("foo", testAPI.NewFoo) - Expect(err).NotTo(HaveOccurred()) + Context("when a type reference is given", func() { + Context("and its type matches the function signature", func() { + It("should generate the type", func() { + err := typeRegistry.RegisterType("foo", testAPI.NewMockType) + Expect(err).NotTo(HaveOccurred()) + + typeDef = goldi.NewType(testAPI.NewTypeForServiceInjection, "@foo") + generatedType := typeDef.Generate(config, typeRegistry) + Expect(generatedType).To(BeAssignableToTypeOf(&testAPI.TypeForServiceInjection{})) + + generatedMock := generatedType.(*testAPI.TypeForServiceInjection) + Expect(generatedMock.InjectedType).To(BeAssignableToTypeOf(&testAPI.MockType{})) + }) + }) + + Context("and its type does not match the function signature", func() { + It("should panic with a helpful error message", func() { + err := typeRegistry.RegisterType("foo", testAPI.NewFoo) + Expect(err).NotTo(HaveOccurred()) - typeDef = goldi.NewType(testAPI.NewTypeForServiceInjectionWithArgs, "@foo", "arg1", "arg2", true) + typeDef = goldi.NewType(testAPI.NewTypeForServiceInjectionWithArgs, "@foo", "arg1", "arg2", true) - defer func() { - r := recover() - Expect(r).NotTo(BeNil(), "Expected Generate to panic") - Expect(r).To(BeAssignableToTypeOf(errors.New(""))) - err := r.(error) - Expect(err.Error()).To(Equal("could not generate type: the referenced type \"@foo\" (type *testAPI.Foo) can not be passed as argument 1 to the function signature testAPI.NewTypeForServiceInjectionWithArgs(*testAPI.MockType, string, string, bool)")) - }() + defer func() { + r := recover() + Expect(r).NotTo(BeNil(), "Expected Generate to panic") + Expect(r).To(BeAssignableToTypeOf(errors.New(""))) + err := r.(error) + Expect(err.Error()).To(Equal("could not generate type: the referenced type \"@foo\" (type *testAPI.Foo) can not be passed as argument 1 to the function signature testAPI.NewTypeForServiceInjectionWithArgs(*testAPI.MockType, string, string, bool)")) + }() - typeDef.Generate(config, typeRegistry) + typeDef.Generate(config, typeRegistry) + }) }) }) }) diff --git a/type.go b/type.go index 372d953..63d23f5 100644 --- a/type.go +++ b/type.go @@ -2,6 +2,7 @@ package goldi import ( "fmt" + "math" "reflect" "runtime" "strings" @@ -12,6 +13,7 @@ type Type struct { factory reflect.Value factoryType reflect.Type factoryArguments []reflect.Value + isFactoryMethod bool } // NewType creates a new Type and checks if the given factory method can be used get a go type @@ -28,10 +30,32 @@ func NewType(factoryFunction interface{}, factoryParameters ...interface{}) *Typ }() factoryType := reflect.TypeOf(factoryFunction) - if factoryType.Kind() != reflect.Func { - panic(fmt.Errorf("kind was %v, not Func", factoryType.Kind())) + + switch factoryType.Kind() { + case reflect.Ptr: + return newTypeFromStruct(factoryFunction, factoryType, factoryParameters) + case reflect.Func: + return newTypeFromFactoryFunction(factoryFunction, factoryType, factoryParameters) + default: + panic(fmt.Errorf("the given factory function must either be a function or a struct (given %q)", factoryType.Kind())) + } +} + +func newTypeFromStruct(structFactory interface{}, generatedType reflect.Type, parameters []interface{}) *Type { + args := make([]reflect.Value, len(parameters)) + for i, argument := range parameters { + args[i] = reflect.ValueOf(argument) + } + + return &Type{ + factory: reflect.ValueOf(structFactory), + isFactoryMethod: false, + factoryType: generatedType, + factoryArguments: args, } +} +func newTypeFromFactoryFunction(function interface{}, factoryType reflect.Type, parameters []interface{}) *Type { if factoryType.NumOut() != 1 { panic(fmt.Errorf("invalid number of return parameters: %d", factoryType.NumOut())) } @@ -41,14 +65,15 @@ func NewType(factoryFunction interface{}, factoryParameters ...interface{}) *Typ panic(fmt.Errorf("return parameter is no interface or pointer but a %v", kindOfGeneratedType)) } - if factoryType.NumIn() != len(factoryParameters) { - panic(fmt.Errorf("invalid number of input parameters: got %d but expected %d", factoryType.NumIn(), len(factoryParameters))) + if factoryType.NumIn() != len(parameters) { + panic(fmt.Errorf("invalid number of input parameters: got %d but expected %d", factoryType.NumIn(), len(parameters))) } return &Type{ - factory: reflect.ValueOf(factoryFunction), + factory: reflect.ValueOf(function), + isFactoryMethod: true, factoryType: factoryType, - factoryArguments: buildFactoryCallArguments(factoryType, factoryParameters), + factoryArguments: buildFactoryCallArguments(factoryType, parameters), } } @@ -77,6 +102,14 @@ func (t *Type) Generate(config map[string]interface{}, registry TypeRegistry) in } }() + if t.isFactoryMethod { + return t.generateFromFactory(config, registry) + } else { + return t.generateFromStruct(config, registry) + } +} + +func (t *Type) generateFromFactory(config map[string]interface{}, registry TypeRegistry) interface{} { args := make([]reflect.Value, len(t.factoryArguments)) for i, argument := range t.factoryArguments { args[i] = t.resolveParameter(i, argument, t.factoryType.In(i), config, registry) @@ -84,7 +117,7 @@ func (t *Type) Generate(config map[string]interface{}, registry TypeRegistry) in result := t.factory.Call(args) if len(result) == 0 { - panic(fmt.Errorf("no return parameter found. Seems like you did not use goldi.NewType to create this Type")) + panic(fmt.Errorf("no return parameter found. this should never ever happen ò.Ó")) } return result[0].Interface() @@ -150,6 +183,28 @@ func (t *Type) invalidReferencedTypeErr(typeID string, typeInstance interface{}, return err } +func (t *Type) generateFromStruct(config map[string]interface{}, registry TypeRegistry) interface{} { + if t.factory.Elem().NumField() < len(t.factoryArguments) { + panic(fmt.Errorf("the struct %T has only %d fields but %d arguments where provided on type registration", + t.factory.Elem().Interface(), t.factory.Elem().NumField(), len(t.factoryArguments), + )) + } + + args := make([]reflect.Value, len(t.factoryArguments)) + for i, argument := range t.factoryArguments { + expectedArgument := t.factory.Elem().Field(i).Type() + args[i] = t.resolveParameter(i, argument, expectedArgument, config, registry) + } + + factory := reflect.New(t.factory.Elem().Type()) + n := int(math.Min(float64(factory.Elem().NumField()), float64(len(args)))) + for i := 0; i < n; i++ { + factory.Elem().Field(i).Set(args[i]) + } + + return factory.Interface() +} + // typeReferenceArguments is an internal function that returns all factory arguments that are type references func (t *Type) typeReferenceArguments() []string { var typeRefParameters []string From 610e35af253c002147ecf512bc210e1b542b6bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Friedrich=20Gro=C3=9Fe?= Date: Sat, 16 May 2015 10:34:10 +0200 Subject: [PATCH 4/5] Add more checks and error handling --- tests/type_test.go | 18 ++++++++++++++++++ type.go | 14 +++++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/tests/type_test.go b/tests/type_test.go index 79ead9f..c6ff39a 100644 --- a/tests/type_test.go +++ b/tests/type_test.go @@ -19,6 +19,11 @@ var _ = Describe("Type", func() { Expect(func() { goldi.NewType(42) }).To(Panic()) }) + It("should panic if the generator is a pointer to something other than a struct", func() { + something := "Hello Pointer World!" + Expect(func() { goldi.NewType(&something) }).To(Panic()) + }) + It("should panic if the generator has no output parameters", func() { Expect(func() { goldi.NewType(func() {}) }).To(Panic()) }) @@ -95,6 +100,19 @@ var _ = Describe("Type", func() { typeRegistry = goldi.NewTypeRegistry() }) + It("should panic if Generate is called on an uninitialized type", func() { + typeDef = &goldi.Type{} + defer func() { + r := recover() + Expect(r).NotTo(BeNil(), "Expected Generate to panic") + Expect(r).To(BeAssignableToTypeOf(errors.New(""))) + err := r.(error) + Expect(err.Error()).To(Equal("could not generate type: this type is not initialized. Did you use NewType to create it?")) + }() + + typeDef.Generate(config, typeRegistry) + }) + Context("with a type factory", func() { Context("without arguments", func() { It("should generate the type", func() { diff --git a/type.go b/type.go index 63d23f5..fb61984 100644 --- a/type.go +++ b/type.go @@ -30,14 +30,14 @@ func NewType(factoryFunction interface{}, factoryParameters ...interface{}) *Typ }() factoryType := reflect.TypeOf(factoryFunction) - - switch factoryType.Kind() { - case reflect.Ptr: + kind := factoryType.Kind() + switch { + case kind == reflect.Ptr && factoryType.Elem().Kind() == reflect.Struct: return newTypeFromStruct(factoryFunction, factoryType, factoryParameters) - case reflect.Func: + case kind == reflect.Func: return newTypeFromFactoryFunction(factoryFunction, factoryType, factoryParameters) default: - panic(fmt.Errorf("the given factory function must either be a function or a struct (given %q)", factoryType.Kind())) + panic(fmt.Errorf("the given factory function must either be a function or a pointer to a struct (given %q)", factoryType.Kind())) } } @@ -102,6 +102,10 @@ func (t *Type) Generate(config map[string]interface{}, registry TypeRegistry) in } }() + if t.factory.IsValid() == false { + panic("this type is not initialized. Did you use NewType to create it?") + } + if t.isFactoryMethod { return t.generateFromFactory(config, registry) } else { From ddddfbd49eecf7b6c65e6e599461ff080d883a20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Friedrich=20Gro=C3=9Fe?= Date: Sat, 16 May 2015 10:38:05 +0200 Subject: [PATCH 5/5] Bump version --- README.md | 2 +- generator/generator.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 950cf9b..479d0e4 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ import ( // RegisterTypes registers all types that have been defined in the file "types.yml" // -// DO NOT EDIT THIS FILE: it has been generated by goldigen v0.5.0. +// DO NOT EDIT THIS FILE: it has been generated by goldigen v0.6.0. // It is however good practice to put this file under version control. // See https://github.com/fgrosse/goldi for what is going on here. func RegisterTypes(types goldi.TypeRegistry) { diff --git a/generator/generator.go b/generator/generator.go index cd3635c..3f24fe4 100644 --- a/generator/generator.go +++ b/generator/generator.go @@ -10,7 +10,7 @@ import ( "strings" ) -const Version = "0.5.0" +const Version = "0.6.0" // The Generator is used to generate compilable go code from a yaml configuration type Generator struct {