goldi

package module
v0.0.0-...-fcbfc17 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 27, 2025 License: MIT Imports: 9 Imported by: 0

README ¶

Build Status Coverage Status GoDoc license

Goldi: lazy dependency injection framework for go.

This library enables you to build your applications based on a dependency injection container. It helps to make your code modular, flexible and ensures that you can reuse components easily.

If you are familiar with the Symfony dependency injection framework you should feel at home here.

🚀 Go 1.24 Enhanced Features

Goldi has been optimized for Go 1.24 with significant performance improvements and modern language features:

✨ New Features

  • Type-Safe Generics: Use goldi.Get[T]() for compile-time type safety
  • Iterator Support: Efficient iteration over types and instances using range over func
  • Memory Pool Optimization: Reduced allocations with intelligent object pooling
  • Reflection Caching: Dramatically improved reflection performance
  • Concurrent Safety: Enhanced thread-safe operations with minimal overhead

📈 Performance Improvements

  • 86% faster warm cache access (26.37 ns/op vs 200+ ns/op)
  • Zero allocations for cached instance retrieval
  • Memory efficient iterators vs traditional collection methods
  • Optimized reflection operations with intelligent caching

🔧 Modern Go Features

// Type-safe retrieval with generics
logger, err := goldi.Get[Logger](container, "logger")
if err != nil {
    return err
}

// Efficient iteration over registered types
for typeID := range registry.TypeIDs() {
    fmt.Printf("Registered: %s\n", typeID)
}

// Memory-efficient bulk operations
allInstances := container.CollectAllInstances()

The goldi API

Use go get to get the goldi API:

$ go get github.com/tarokamikaze/goldi

No additional dependencies are required to use the library. The full documentation is available at godoc.org. It is almost complete and includes a lot of examples on how to use goldi.

Usage

First you need to define the types you are going to use later

import (
    "github.com/tarokamikaze/goldi"
    "github.com/tarokamikaze/goldi/validation"
)

// create a new container when your application loads
registry := goldi.NewTypeRegistry()
config := map[string]interface{}{
    "some_parameter": "Hello World",
    "timeout":        42.7,
}
container := goldi.NewContainer(registry, config)

// now define the types you want to build using the di container
// you can use simple structs
container.RegisterType("logger", &SimpleLogger{})
container.RegisterType("api.geo.client", new(GeoClient), "http://example.com/geo:1234")

// you can also use factory functions and parameters
container.RegisterType("acme_corp.mailer", NewAwesomeMailer, "first argument", "%some_parameter%")

// dynamic or static parameters and references to other services can be used as arguments
container.RegisterType("renderer", NewRenderer, "@logger")

// closures and functions are also possible
container.Register("http_handler", goldi.NewFuncType(func(w http.ResponseWriter, r *http.Request) {
    // do amazing stuff
}))

// once you are done registering all your types you should probably validate the container
validator := validation.NewContainerValidator()
validator.MustValidate(container) // will panic, use validator.Validate to get the error

// whoever has access to the container can request these types now
logger := container.MustGet("logger").(LoggerInterface)
logger.DoStuff("...")

// in the tests you might want to exchange the registered types with mocks or other implementations
container.RegisterType("logger", NewNullLogger)

// if you already have an instance you want to be used you can inject it directly
myLogger := NewNullLogger()
container.InjectInstance("logger", myLogger)

The types are build lazily. This means that the logger will only be created when you ask the container for it the first time. Also all built types are singletons. This means that if you call container.Get("typeID")two times you will always get the same instance of whatever typeID stands for.

More detailed usage examples and a list of features will be available eventually.

The goldigen binary

If you are used to frameworks like Symfony you might want to define your types in an easy to maintain yaml file. You can do this using goldigen.

Use go get to install the goldigen binary:

$ go get github.com/tarokamikaze/goldi/goldigen

Goldigen depends on gopkg.in/yaml.v2 (LGPLv3) for the parsing of the yaml files and Kingpin (MIT licensed) for the command line flag parsing.

You then need to define your types like this:

types:
    logger:
        package: github.com/tarokamikaze/goldi-example/lib
        type: SimpleLogger

    my_fancy.client:
        package: github.com/tarokamikaze/goldi-example/lib
        type: Client
        factory: NewDefaultClient
        arguments:
            - "%client_base_url%"   # As in the API you can use parameters here
            - "@logger"             # You can also reference other types 

    time.clock:
        package: github.com/tarokamikaze/goldi-example/lib/mytime
        type: Clock
        factory: NewSystemClock
        
    http_handler:
        package: github.com/tarokamikaze/servo/example
        func:    HandleHTTP         # You can register functions as types using the "func" keyword

Now you have your type configuration file you can use goldigen like this:

$ goldigen --in config/types.yml --out lib/dependency_injection.go

This will generate the following output and write it to lib/dependency_injection.go:

//go:generate goldigen --in "../config/types.yml" --out "dependency_injection.go" --package github.com/tarokamikaze/goldi-example/lib --function RegisterTypes --overwrite --nointeraction
package lib

import (
	"github.com/tarokamikaze/goldi"
	"github.com/tarokamikaze/goldi-example/lib/mytime"
	"github.com/tarokamikaze/servo/example"
)

// RegisterTypes registers all types that have been defined in the file "../config/types.yml"
//
// DO NOT EDIT THIS FILE: it has been generated by goldigen v0.9.9.
// It is however good practice to put this file under version control.
// See https://github.com/tarokamikaze/goldi for what is going on here.
func RegisterTypes(types goldi.TypeRegistry) {
	types.RegisterAll(map[string]goldi.TypeFactory{
        "http_handler":    goldi.NewFuncType(example.HandleHTTP),
        "logger":          goldi.NewStructType(new(SimpleLogger)),
        "my_fancy.client": goldi.NewType(NewDefaultClient, "%client_base_url%", "@logger"),
        "time.clock":      goldi.NewType(mytime.NewSystemClock),
	})
}

As you might have noticed goldigen has created a go generate comment for you. Next time you want to update dependency_injection.go you can simply run go generate.

Goldigen tries its best to determine the output files package by looking into your GOPATH. In certain situations this might not be enough so you can set a package explicitly using the --package parameter.

For a full list of goldigens flags and parameters try:

$ goldigen --help

Now all you need to to is to create the di container as you would just using the goldi API and then somewhere in the bootstrapping of your application call.

RegisterTypes(registry)

If you have a serious error in your type registration (like returning more than one result from your type factory method) goldi defers error handling by return an invalid type. You can check for invalid types with the ContainerValidator or by using goldi.IsValid(TypeFactory) directly. Using the ContainerValidator is always the preferred option since it will check for a wide variety of bad configurations like undefined parameters or circular type dependencies.

Note that using goldigen is completely optional. If you do not like the idea of having an extra build step for your application just use goldis API directly.

License

Goldi is licensed under the the MIT license. Please see the LICENSE file for details.

Contributing

Any contributions are always welcome (use pull requests). For each pull request make sure that you covered your changes and additions with ginkgo tests. If you are unsure how to write those just drop me a message.

Please keep in mind that I might not always be able to respond immediately but I usually try to react within the week ☺.

Documentation ¶

Overview ¶

Package goldi implements a lazy dependency injection framework for go. Goldi is MIT-Licensed

Example ¶
package main

import (
	"fmt"
	"net/http"
	"time"

	"github.com/tarokamikaze/goldi"
)

type SimpleLogger struct {
	Name string
}

func (l *SimpleLogger) Log(msg string) {
	fmt.Printf("LOG: %s\n", msg)
}

func main() {
	// create a new container when your application loads
	registry := goldi.NewTypeRegistry()
	config := map[string]interface{}{
		"some_parameter": "Hello World",
		"timeout":        42.7,
	}

	// register a simple type with parameter
	registry.RegisterType("logger", NewSimpleLoggerWithParam, "%some_parameter%")

	// register a struct type
	registry.RegisterType("httpClient", &http.Client{}, time.Second*5)

	// you can also register already instantiated types
	registry.InjectInstance("myInstance", &SimpleLogger{Name: "Foo"})

	// create a new container with the registry and the config
	container := goldi.NewContainer(registry, config)

	// retrieve types from the container
	logger := container.MustGet("logger").(*SimpleLogger)
	fmt.Printf("logger.Name = %q\n", logger.Name)

}

func NewSimpleLoggerWithParam(name string) *SimpleLogger {
	return &SimpleLogger{Name: name}
}
Output:

logger.Name = "Hello World"
Example (Go124Features) ¶

Example_go124Features demonstrates the new Go 1.24 features in Goldi

package main

import (
	"fmt"

	"github.com/tarokamikaze/goldi"
)

func main() {
	registry := goldi.NewTypeRegistry()

	// Register some types without dependencies for example
	registry.RegisterType("logger", func() Logger { return &SimpleLogger{} })
	registry.RegisterType("database", func() Database { return &SimpleDatabase{Logger: &SimpleLogger{}} })
	registry.RegisterType("service", func() Service {
		return &SimpleService{DB: &SimpleDatabase{Logger: &SimpleLogger{}}, Logger: &SimpleLogger{}}
	})

	container := goldi.NewContainer(registry, map[string]interface{}{})

	// 1. Range over func - iterate through registered types
	fmt.Println("Registered types:")
	for typeID := range registry.TypeIDs() {
		fmt.Printf("- %s\n", typeID)
	}

	// 2. Improved Type Inference - no type assertions needed
	logger, err := goldi.Get[Logger](container, "logger")
	if err != nil {
		panic(err)
	}
	logger.Log("Using improved type inference!")

	// 3. slices.Collect - efficiently collect type IDs
	allTypeIDs := registry.CollectTypeIDs()
	fmt.Printf("Total registered types: %d\n", len(allTypeIDs))

	// 4. Iterator-based warmup
	err = container.WarmupCache()
	if err != nil {
		panic(err)
	}

	// 5. Check cached instances count
	cachedCount := 0
	for range container.CachedTypeIDs() {
		cachedCount++
	}
	fmt.Printf("Cached instances count: %d\n", cachedCount)

}

// Test types for examples
type Logger interface {
	Log(string)
}

type SimpleLogger struct {
	Name string
}

func (l *SimpleLogger) Log(msg string) {
	fmt.Printf("LOG: %s\n", msg)
}

type Database interface {
	Query(string) string
}

type SimpleDatabase struct {
	Logger Logger
}

func (d *SimpleDatabase) Query(query string) string {
	d.Logger.Log("Executing query: " + query)
	return "result"
}

type Service interface {
	Process(string) string
}

type SimpleService struct {
	DB     Database
	Logger Logger
}

func (s *SimpleService) Process(data string) string {
	s.Logger.Log("Processing: " + data)
	return s.DB.Query("SELECT * FROM data WHERE value = '" + data + "'")
}
Output:

Registered types:
- logger
- database
- service
LOG: Using improved type inference!
Total registered types: 3
Cached instances count: 3

Index ¶

Examples ¶

Constants ¶

This section is empty.

Variables ¶

This section is empty.

Functions ¶

func Get ¶

func Get[T any](c *Container, typeID string) (T, error)

Get retrieves a type with improved type inference using Go 1.24 generics This method provides compile-time type safety and eliminates the need for type assertions

func IsParameter ¶

func IsParameter(p string) bool

IsParameter returns whether the given type ID represents a parameter. A goldi parameter is recognized by the leading and trailing percent sign Example: %foobar%

func IsParameterOrTypeReference ¶

func IsParameterOrTypeReference(p string) bool

IsParameterOrTypeReference is a utility function that returns whether the given string represents a parameter or a reference to a type. See IsParameter and IsTypeReference for further details

func IsTypeReference ¶

func IsTypeReference(p string) bool

IsTypeReference returns whether the given string represents a reference to a type. A goldi type reference is recognized by the leading @ sign. Example: @foobar

func IsValid ¶

func IsValid(t TypeFactory) bool

IsValid checks if a given type factory is valid. This function can be used to check the result of functions like NewType

func MustGet ¶

func MustGet[T any](c *Container, typeID string) T

MustGet with improved type inference - panics on error but provides type safety

Types ¶

type Container ¶

type Container struct {
	TypeRegistry
	Config   map[string]interface{}
	Resolver *ParameterResolver
	// contains filtered or unexported fields
}

Container is the dependency injection container that can be used by your application to define and get types.

Basically this is just a TypeRegistry with access to the application configuration and the knowledge of how to build individual services. Additionally this implements the laziness of the DI using a simple in memory type cache

You must use goldi.NewContainer to get a initialized instance of a Container!

Example ¶
registry := goldi.NewTypeRegistry()
config := map[string]interface{}{}
container := goldi.NewContainer(registry, config)

container.Register("logger", goldi.NewType(NewNullLogger))

l := container.MustGet("logger")
fmt.Printf("%T", l)
Output:

*goldi_test.NullLogger

func NewContainer ¶

func NewContainer(registry TypeRegistry, config map[string]interface{}) *Container

NewContainer creates a new container instance using the provided arguments

func (*Container) AllInstances ¶

func (c *Container) AllInstances() iter.Seq2[string, interface{}]

AllInstances returns an iterator over all cached instances This uses Go 1.24's range over func feature for memory-efficient iteration

func (*Container) CachedTypeIDs ¶

func (c *Container) CachedTypeIDs() iter.Seq[string]

CachedTypeIDs returns an iterator over all cached type IDs

func (*Container) CollectCachedInstances ¶

func (c *Container) CollectCachedInstances() map[string]interface{}

CollectCachedInstances efficiently collects all cached instances using slices.Collect

func (*Container) CollectCachedTypeIDs ¶

func (c *Container) CollectCachedTypeIDs() []string

CollectCachedTypeIDs efficiently collects all cached type IDs using slices.Collect

func (*Container) Get ¶

func (c *Container) Get(typeID string) (interface{}, error)

Get retrieves a previously defined type or an error. If the requested typeID has not been registered before or can not be generated Get will return an error.

For your dependency injection to work properly it is important that you do only try to assert interface types when you use Get(..). Otherwise it might be impossible to assert the correct type when you change the underlying type implementations. Also make sure your application is properly tested and defers some panic handling in case you forgot to define a service.

See also Container.MustGet

Example ¶
registry := goldi.NewTypeRegistry()
config := map[string]interface{}{}
container := goldi.NewContainer(registry, config)

container.Register("logger", goldi.NewType(NewNullLogger))

l, err := container.Get("logger")
if err != nil {
	fmt.Println(err.Error())
	return
}

// do stuff with the logger. usually you need a type assertion
fmt.Printf("%T", l.(*NullLogger))
Output:

*goldi_test.NullLogger

func (*Container) GetAllInstances ¶

func (c *Container) GetAllInstances() (map[string]interface{}, error)

GetAllInstances retrieves all registered types and returns them as a map

func (*Container) GetMultiple ¶

func (c *Container) GetMultiple(typeIDs iter.Seq[string]) iter.Seq2[string, interface{}]

GetMultiple efficiently retrieves multiple types using iterator pattern

func (*Container) MustGet ¶

func (c *Container) MustGet(typeID string) interface{}

MustGet behaves exactly like Get but will panic instead of returning an error Since MustGet can only return interface{} you need to add a type assertion after the call:

container.MustGet("logger").(LoggerInterface)
Example ¶
package main

import (
	"fmt"

	"github.com/tarokamikaze/goldi"
)

type SimpleLogger struct {
	Name string
}

func (l *SimpleLogger) Log(msg string) {
	fmt.Printf("LOG: %s\n", msg)
}

func main() {
	registry := goldi.NewTypeRegistry()
	config := map[string]interface{}{
		"some_parameter": "Hello World",
	}

	registry.RegisterType("logger", NewSimpleLoggerWithParam, "%some_parameter%")
	container := goldi.NewContainer(registry, config)

	logger := container.MustGet("logger").(*SimpleLogger)
	fmt.Printf("logger.Name = %q\n", logger.Name)

}

func NewSimpleLoggerWithParam(name string) *SimpleLogger {
	return &SimpleLogger{Name: name}
}
Output:

logger.Name = "Hello World"

func (*Container) WarmupCache ¶

func (c *Container) WarmupCache() error

WarmupCache pre-generates instances for all registered types

type MemoryPool ¶

type MemoryPool struct {
	// contains filtered or unexported fields
}

MemoryPool provides object pooling to reduce memory allocations

func GetGlobalMemoryPool ¶

func GetGlobalMemoryPool() *MemoryPool

GetGlobalMemoryPool returns the global memory pool instance

func NewMemoryPool ¶

func NewMemoryPool() *MemoryPool

NewMemoryPool creates a new memory pool instance

func (*MemoryPool) GetInterfaceSlice ¶

func (mp *MemoryPool) GetInterfaceSlice() []interface{}

GetInterfaceSlice returns a pooled interface{} slice

func (*MemoryPool) GetReflectValueSlice ¶

func (mp *MemoryPool) GetReflectValueSlice() []reflect.Value

GetReflectValueSlice returns a pooled reflect.Value slice

func (*MemoryPool) GetStringSlice ¶

func (mp *MemoryPool) GetStringSlice() []string

GetStringSlice returns a pooled string slice

func (*MemoryPool) PutInterfaceSlice ¶

func (mp *MemoryPool) PutInterfaceSlice(slice []interface{})

PutInterfaceSlice returns an interface{} slice to the pool

func (*MemoryPool) PutReflectValueSlice ¶

func (mp *MemoryPool) PutReflectValueSlice(slice []reflect.Value)

PutReflectValueSlice returns a reflect.Value slice to the pool

func (*MemoryPool) PutStringSlice ¶

func (mp *MemoryPool) PutStringSlice(slice []string)

PutStringSlice returns a string slice to the pool

type ParameterResolver ¶

type ParameterResolver struct {
	Container *Container
}

The ParameterResolver is used by type factories to resolve the values of the dynamic factory arguments (parameters and other type references).

func NewParameterResolver ¶

func NewParameterResolver(container *Container) *ParameterResolver

NewParameterResolver creates a new ParameterResolver and initializes it with the given Container. The container is used when resolving parameters and the type references.

func (*ParameterResolver) Resolve ¶

func (r *ParameterResolver) Resolve(parameter reflect.Value, expectedType reflect.Type) (reflect.Value, error)

Resolve takes a parameter and resolves any references to configuration parameter values or type references. If the type of `parameter` is not a parameter or type reference it is returned as is. Parameters must always have the form `%my.beautiful.param%. Type references must have the form `@my_type.bla`. It is also legal to request an optional type using the syntax `@?my_optional_type`. If this type is not registered Resolve will not return an error but instead give you the null value of the expected type.

type ReflectionCache ¶

type ReflectionCache struct {
	// contains filtered or unexported fields
}

ReflectionCache provides cached reflection operations to reduce runtime overhead

func GetGlobalReflectionCache ¶

func GetGlobalReflectionCache() *ReflectionCache

GetGlobalReflectionCache returns the global reflection cache instance

func NewReflectionCache ¶

func NewReflectionCache() *ReflectionCache

NewReflectionCache creates a new reflection cache instance

func (*ReflectionCache) GetFactoryType ¶

func (rc *ReflectionCache) GetFactoryType(factory TypeFactory) reflect.Type

GetFactoryType returns the type that a TypeFactory produces

func (*ReflectionCache) GetMethodByName ¶

func (rc *ReflectionCache) GetMethodByName(obj interface{}, methodName string) (reflect.Method, bool)

GetMethodByName returns cached method or computes and caches it

func (*ReflectionCache) GetType ¶

func (rc *ReflectionCache) GetType(obj interface{}) reflect.Type

GetType returns cached reflect.Type or computes and caches it

func (*ReflectionCache) GetValue ¶

func (rc *ReflectionCache) GetValue(obj interface{}) reflect.Value

GetValue returns cached reflect.Value or computes and caches it Note: Values are not cached as they represent specific instances

type StringSet ¶

type StringSet map[string]struct{}

A StringSet represents a set of strings. Uses empty struct{} as value to minimize memory usage.

func NewStringSet ¶

func NewStringSet(capacity ...int) StringSet

NewStringSet creates a new StringSet with optional initial capacity

func (StringSet) Clear ¶

func (s StringSet) Clear()

Clear removes all elements from the set.

func (StringSet) Contains ¶

func (s StringSet) Contains(value string) bool

Contains returns true if the given value is contained in this string set.

func (StringSet) Len ¶

func (s StringSet) Len() int

Len returns the number of elements in the set.

func (StringSet) Remove ¶

func (s StringSet) Remove(value string)

Remove removes a value from the set.

func (StringSet) Set ¶

func (s StringSet) Set(value string)

Set adds a value to the set.

func (StringSet) ToSlice ¶

func (s StringSet) ToSlice() []string

ToSlice returns all values as a slice.

type TypeConfigurator ¶

type TypeConfigurator struct {
	ConfiguratorTypeID string
	MethodName         string
}

The TypeConfigurator is used to configure a type after its instantiation. You can specify a function in another type that is known to the container. The type instance is passed to the configurator type, allowing the configurator to do whatever it needs to configure the type after its creation.

A TypeConfigurator can be used, for example, when you have a type that requires complex setup based on configuration settings coming from different sources. Using an external configurator, you can decouple the setup logic from the business logic of the corresponding type to keep it DRY and easy to maintain. Also this way its easy to exchange setup logic at run time for example on different environments.

Another interesting use case is when you have multiple objects that share a common configuration or that should be configured in a similar way at runtime.

func NewTypeConfigurator ¶

func NewTypeConfigurator(configuratorTypeID, methodName string) *TypeConfigurator

NewTypeConfigurator creates a new TypeConfigurator

func (*TypeConfigurator) Configure ¶

func (c *TypeConfigurator) Configure(thing interface{}, container *Container) error

Configure will get the configurator type and ass `thing` its configuration function. The method returns an error if thing is nil, the configurator type is not defined or the configurators function does not exist.

type TypeFactory ¶

type TypeFactory interface {

	// Arguments returns all arguments that are used to generate the type.
	// This enables the container validator to check if all required parameters exist
	// and if there are circular type dependencies.
	Arguments() []interface{}

	// Generate will instantiate a new instance of the according type or return an error.
	Generate(parameterResolver *ParameterResolver) (interface{}, error)
}

A TypeFactory is used to instantiate a certain type.

func NewAliasType ¶

func NewAliasType(typeID string) TypeFactory

NewAliasType create a new TypeFactory which just serves as alias to the given type ID.

A call to an alias type will retrieve the aliased type as if it was requested via container.Get(typeID) This method will always return a valid type and works bot for regular type references (without leading @) and references to type functions.

Goldigen yaml syntax example:

type_that_is_aliased:
    alias: "@some_type"  // container.Get("type_that_is_aliased") will now return "some_type" instead

Goldigen yaml syntax example with function reference:

func_type_that_is_aliased:
    alias: "@some_type::DoStuff"
Example ¶
package main

import (
	"fmt"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"github.com/tarokamikaze/goldi"
)

func main() {
	container := goldi.NewContainer(goldi.NewTypeRegistry(), map[string]interface{}{})

	container.Register("logger", goldi.NewStructType(SimpleLogger{}))
	container.Register("mock", goldi.NewStructType(MockType{}))
	container.Register("default_logger", goldi.NewAliasType("logger"))
	container.Register("logging_func", goldi.NewAliasType("mock::DoStuff"))

	fmt.Printf("logger:         %T\n", container.MustGet("logger"))
	fmt.Printf("default_logger: %T\n", container.MustGet("default_logger"))
	fmt.Printf("logging_func:   %T\n", container.MustGet("logging_func"))
}

var _ = Describe("aliasType", func() {
	It("should implement the TypeFactory interface", func() {
		var factory goldi.TypeFactory
		factory = goldi.NewAliasType("foo")
		// if this compiles the test passes (next expectation only to make compiler happy)
		Expect(factory).NotTo(BeNil())
	})

	Describe("Arguments()", func() {
		It("should return the aliased service ID", func() {
			typeDef := goldi.NewAliasType("foo")
			Expect(typeDef.Arguments()).To(Equal([]interface{}{"@foo"}))
		})
	})

	Describe("Generate()", func() {
		var (
			container *goldi.Container
			resolver  *goldi.ParameterResolver
		)

		BeforeEach(func() {
			config := map[string]interface{}{}
			container = goldi.NewContainer(goldi.NewTypeRegistry(), config)
			resolver = goldi.NewParameterResolver(container)
		})

		It("should act as alias for the actual type", func() {
			container.Register("foo", goldi.NewStructType(Foo{}, "I was created by @foo"))
			alias := goldi.NewAliasType("foo")

			generated, err := alias.Generate(resolver)
			Expect(err).NotTo(HaveOccurred())
			Expect(generated).To(BeAssignableToTypeOf(&Foo{}))
			Expect(generated.(*Foo).Value).To(Equal("I was created by @foo"))
		})

		It("should work with func reference types", func() {
			container.Register("foo", goldi.NewStructType(Foo{}, "I was created by @foo"))
			alias := goldi.NewAliasType("foo::ReturnString")

			generated, err := alias.Generate(resolver)
			Expect(err).NotTo(HaveOccurred())
			Expect(generated).To(BeAssignableToTypeOf(func(string) string { return "" }))
			Expect(generated.(func(string) string)("TEST")).To(Equal("I was created by @foo TEST"))
		})
	})
})
Output:

logger:         *goldi_test.SimpleLogger
default_logger: *goldi_test.SimpleLogger
logging_func:   func() string

func NewConfiguredType ¶

func NewConfiguredType(embeddedType TypeFactory, configuratorTypeID, configuratorMethod string) TypeFactory

NewConfiguredType creates a new TypeFactory that decorates a given TypeFactory. The returned configurator will use the decorated type factory first to create a type and then use the resolve the configurator by the given type ID and call the configured method with the instance.

Internally the goldi.TypeConfigurator is used.

The method removes any leading or trailing whitespace from configurator type ID and method. NewConfiguredType will return an invalid type when embeddedType is nil or the trimmed configurator typeID or method is empty.

Goldigen yaml syntax example:

my_type:
    package: github.com/fgrosse/foobar
    type:    MyType
    configurator: [ "@my_configurator", Configure ]
Example ¶
package main

import (
	"fmt"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"github.com/tarokamikaze/goldi"
)

func main() {
	container := goldi.NewContainer(goldi.NewTypeRegistry(), map[string]interface{}{})

	// this example configurator accepts a Foo type and will set its Value field to the given value
	configurator := &MyConfigurator{ConfiguredValue: "success!"}

	// register the configurator under a type ID
	container.Register("configurator_type", goldi.NewInstanceType(configurator))

	// create the type that should be configured
	embeddedType := goldi.NewStructType(Foo{})
	container.Register("foo", goldi.NewConfiguredType(embeddedType, "configurator_type", "Configure"))

	fmt.Println(container.MustGet("foo").(*Foo).Value)
}

var _ = Describe("configuredType", func() {
	var embeddedType goldi.TypeFactory
	BeforeEach(func() {
		embeddedType = goldi.NewStructType(Foo{})
	})

	It("should implement the TypeFactory interface", func() {
		var factory goldi.TypeFactory
		factory = goldi.NewConfiguredType(embeddedType, "configurator_type", "Configure")
		// if this compiles the test passes (next expectation only to make compiler happy)
		Expect(factory).NotTo(BeNil())
	})

	Describe("NewConfiguredType()", func() {
		Context("with invalid argument", func() {
			It("should return an invalid type if the embedded type is nil", func() {
				typeDef := goldi.NewConfiguredType(nil, "configurator_type", "Configure")
				Expect(goldi.IsValid(typeDef)).To(BeFalse())
			})

			It("should return an invalid type if either the configurator ID or method is empty", func() {
				Expect(goldi.IsValid(goldi.NewConfiguredType(embeddedType, "", ""))).To(BeFalse())
				Expect(goldi.IsValid(goldi.NewConfiguredType(embeddedType, "configurator_type", ""))).To(BeFalse())
				Expect(goldi.IsValid(goldi.NewConfiguredType(embeddedType, "", "configure"))).To(BeFalse())
			})

			It("should return an invalid type if the configurator method is not exported", func() {
				Expect(goldi.IsValid(goldi.NewConfiguredType(embeddedType, "configurator_type", "configure"))).To(BeFalse())
			})
		})

		Context("with valid arguments", func() {
			It("should create the type", func() {
				typeDef := goldi.NewConfiguredType(embeddedType, "configurator_type", "Configure")
				Expect(typeDef).NotTo(BeNil())
			})
		})
	})

	Describe("Arguments()", func() {
		It("should return the arguments of the embedded type and the configurator as type ID", func() {
			embeddedType = goldi.NewStructType(Foo{}, "%param_of_embedded%", "another param")
			typeDef := goldi.NewConfiguredType(embeddedType, "configurator_type", "Configure")
			Expect(typeDef.Arguments()).NotTo(BeNil())
			Expect(typeDef.Arguments()).To(HaveLen(3))
			Expect(typeDef.Arguments()).To(ContainElement("%param_of_embedded%"))
			Expect(typeDef.Arguments()).To(ContainElement("another param"))
			Expect(typeDef.Arguments()).To(ContainElement("@configurator_type"))
		})
	})

	Describe("Generate()", func() {
		var (
			config    = map[string]interface{}{}
			container *goldi.Container
			resolver  *goldi.ParameterResolver
		)

		BeforeEach(func() {
			container = goldi.NewContainer(goldi.NewTypeRegistry(), config)
			resolver = goldi.NewParameterResolver(container)
		})

		It("should get the embedded type and configurator and configure it", func() {
			typeDef := goldi.NewConfiguredType(embeddedType, "configurator_type", "Configure")
			configurator := &MyConfigurator{ConfiguredValue: "success!"}
			container.Register("configurator_type", goldi.NewInstanceType(configurator))

			generatedType, err := typeDef.Generate(resolver)
			Expect(err).NotTo(HaveOccurred())
			Expect(generatedType).NotTo(BeNil())
			Expect(generatedType).To(BeAssignableToTypeOf(&Foo{}))
			Expect(generatedType.(*Foo).Value).To(Equal("success!"))
		})

		It("should return an error if the embedded type can not be generated", func() {
			invalidType := goldi.NewStructType(nil)
			typeDef := goldi.NewConfiguredType(invalidType, "configurator_type", "Configure")
			configurator := &MyConfigurator{ConfiguredValue: "should not happen"}
			container.Register("configurator_type", goldi.NewInstanceType(configurator))

			generatedType, err := typeDef.Generate(resolver)
			Expect(err).To(MatchError("can not generate configured type: the given struct is nil"))
			Expect(generatedType).To(BeNil())
		})

		It("should return an error if the configurator returns an error", func() {
			typeDef := goldi.NewConfiguredType(embeddedType, "configurator_type", "Configure")
			configurator := &MyConfigurator{ReturnError: true}
			container.Register("configurator_type", goldi.NewInstanceType(configurator))

			generatedType, err := typeDef.Generate(resolver)
			Expect(err).To(MatchError("can not configure type: this is the error message from the tests.MockTypeConfigurator"))
			Expect(generatedType).To(BeNil())
		})
	})
})
Output:

success!

func NewFuncReferenceType ¶

func NewFuncReferenceType(typeID, functionName string) TypeFactory

NewFuncReferenceType returns a TypeFactory that returns a method of another type as method value (function).

Goldigen yaml syntax example:

my_func_type:
    func: "@some_type::FancyAction"
Example ¶
package main

import (
	"fmt"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"github.com/tarokamikaze/goldi"
)

func main() {
	container := goldi.NewContainer(goldi.NewTypeRegistry(), map[string]interface{}{})

	mockType := &MockType{StringParameter: "Hello World"}
	container.Register("mock", goldi.NewInstanceType(mockType))
	container.Register("log_func", goldi.NewFuncReferenceType("mock", "DoStuff"))

	f := container.MustGet("log_func").(func() string)
	fmt.Println(f()) // executes mockType.DoStuff
}

var _ = Describe("funcReferenceType", func() {
	It("should implement the TypeFactory interface", func() {
		var factory goldi.TypeFactory
		factory = goldi.NewFuncReferenceType("my_controller", "FancyAction")
		// if this compiles the test passes (next expectation only to make compiler happy)
		Expect(factory).NotTo(BeNil())
	})

	Describe("NewFuncReferenceType()", func() {
		It("should return an invalid type if the method name is not exported", func() {
			t := goldi.NewFuncReferenceType("foo", "doStuff")
			Expect(goldi.IsValid(t)).To(BeFalse())
			Expect(t).To(MatchError(`can not use unexported method "doStuff" as second argument to NewFuncReferenceType`))
		})
	})

	Describe("Arguments()", func() {
		It("should return the referenced service ID", func() {
			typeDef := goldi.NewFuncReferenceType("my_controller", "FancyAction")
			Expect(typeDef.Arguments()).To(Equal([]interface{}{"@my_controller"}))
		})
	})

	Describe("Generate()", func() {
		var (
			container *goldi.Container
			resolver  *goldi.ParameterResolver
		)

		BeforeEach(func() {
			config := map[string]interface{}{}
			container = goldi.NewContainer(goldi.NewTypeRegistry(), config)
			resolver = goldi.NewParameterResolver(container)
		})

		It("should get the correct method of the referenced type", func() {
			container.Register("foo", goldi.NewStructType(Foo{}, "I was created by @foo"))
			typeDef := goldi.NewFuncReferenceType("foo", "ReturnString")

			generated, err := typeDef.Generate(resolver)
			Expect(err).NotTo(HaveOccurred())
			Expect(generated).To(BeAssignableToTypeOf(func(string) string { return "" }))
			Expect(generated.(func(string) string)("TEST")).To(Equal("I was created by @foo TEST"))
		})

		It("should return an error if the referenced type can not be generated", func() {
			container.Register("foo", goldi.NewStructType(nil))
			typeDef := goldi.NewFuncReferenceType("foo", "DoStuff")

			_, err := typeDef.Generate(resolver)
			Expect(err).To(MatchError(`could not generate func reference type @foo::DoStuff : goldi: error while generating type "foo": the given struct is nil`))
		})

		It("should return an error if the referenced type has no such method", func() {
			container.Register("foo", goldi.NewStructType(Foo{}))
			typeDef := goldi.NewFuncReferenceType("foo", "ThisMethodDoesNotExist")

			_, err := typeDef.Generate(resolver)
			Expect(err).To(MatchError("could not generate func reference type @foo::ThisMethodDoesNotExist : method does not exist"))
		})
	})
})
Output:

I did stuff

func NewFuncType ¶

func NewFuncType(function interface{}) TypeFactory

NewFuncType creates a new TypeFactory that will return a method value

Goldigen yaml syntax example:

my_func_type:
    package: github.com/fgrosse/foobar
    func:    DoStuff
Example ¶
package main

import (
	"net/http"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"github.com/tarokamikaze/goldi"
)

func main() {
	container := goldi.NewContainer(goldi.NewTypeRegistry(), map[string]interface{}{})

	// define the type
	container.Register("my_func", goldi.NewFuncType(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "test" {
			w.WriteHeader(http.StatusAccepted)
		}
	}))

	// generate it
	result, err := container.Get("my_func")
	if err != nil {
		return
	}

	// call it
	f := result.(func(name string, age int) (bool, error))
	ok, err := f("foo", 42)
	if ok != true || err != nil {
		panic("!!!")
	}
}

var _ = Describe("funcType", func() {
	It("should implement the TypeFactory interface", func() {
		var factory goldi.TypeFactory
		factory = goldi.NewFuncType(SomeFunctionForFuncTypeTest)

		// if this compiles the test passes (next expectation only to make compiler happy)
		Expect(factory).NotTo(BeNil())
	})

	Describe("goldi.NewFuncType()", func() {
		Context("with invalid argument", func() {
			It("should return an invalid type if the argument is no function", func() {
				Expect(goldi.IsValid(goldi.NewFuncType(42))).To(BeFalse())
			})
		})

		Context("with argument beeing a function", func() {
			It("should create the type", func() {
				typeDef := goldi.NewFuncType(SomeFunctionForFuncTypeTest)
				Expect(typeDef).NotTo(BeNil())
			})
		})
	})

	Describe("Arguments()", func() {
		It("should return an empty list", func() {
			typeDef := goldi.NewFuncType(SomeFunctionForFuncTypeTest)
			Expect(typeDef.Arguments()).NotTo(BeNil())
			Expect(typeDef.Arguments()).To(BeEmpty())
		})
	})

	Describe("Generate()", func() {
		var (
			config    = map[string]interface{}{}
			container *goldi.Container
			resolver  *goldi.ParameterResolver
		)

		BeforeEach(func() {
			container = goldi.NewContainer(goldi.NewTypeRegistry(), config)
			resolver = goldi.NewParameterResolver(container)
		})

		It("should just return the function", func() {
			typeDef := goldi.NewFuncType(SomeFunctionForFuncTypeTest)
			Expect(typeDef.Generate(resolver)).To(BeAssignableToTypeOf(SomeFunctionForFuncTypeTest))
		})
	})
})

func SomeFunctionForFuncTypeTest(name string, age int) (bool, error) {
	return true, nil
}

func NewInstanceType ¶

func NewInstanceType(instance interface{}) TypeFactory

NewInstanceType creates a new TypeFactory which will return the given instance on each call to Generate. It will return an invalid type factory if the given instance is nil

You can not generate this type using goldigen

Example ¶
package main

import (
	"fmt"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"github.com/tarokamikaze/goldi"
)

func main() {
	container := goldi.NewContainer(goldi.NewTypeRegistry(), map[string]interface{}{})

	myInstance := new(SimpleLogger)
	myInstance.Name = "Foobar" // you can configure the instance in your code

	// now register this instance as a type
	container.Register("logger", goldi.NewInstanceType(myInstance))

	// each reference to the "logger" type will now be resolved to that instance
	fmt.Println(container.MustGet("logger").(*SimpleLogger).Name)
}

var _ = Describe("instanceType", func() {
	var resolver *goldi.ParameterResolver

	BeforeEach(func() {
		container := goldi.NewContainer(goldi.NewTypeRegistry(), map[string]interface{}{})
		resolver = goldi.NewParameterResolver(container)
	})

	It("should return an invalid type if NewInstanceType is called with nil", func() {
		Expect(goldi.IsValid(goldi.NewInstanceType(nil))).To(BeFalse())
	})

	Describe("Arguments()", func() {
		It("should return an empty list", func() {
			typeDef := goldi.NewInstanceType(NewFoo())
			Expect(typeDef.Arguments()).To(BeEmpty())
		})
	})

	Describe("Generate", func() {
		It("should always return the given instance", func() {
			instance := NewFoo()
			factory := goldi.NewInstanceType(instance)

			for i := 0; i < 3; i++ {
				generateResult, err := factory.Generate(resolver)
				Expect(err).NotTo(HaveOccurred())
				Expect(generateResult == instance).To(BeTrue(),
					fmt.Sprintf("generateResult (%p) should point to the same instance as instance (%p)", generateResult, instance),
				)
			}
		})
	})

	It("should implement the TypeFactory interface", func() {
		var factory goldi.TypeFactory
		factory = goldi.NewInstanceType("foo")
		// if this compiles the test passes (next expectation only to make compiler happy)
		Expect(factory).NotTo(BeNil())
	})
})
Output:

Foobar

func NewProxyType ¶

func NewProxyType(typeID, functionName string, args ...interface{}) TypeFactory

NewProxyType returns a TypeFactory that uses a function of another type to generate a result.

Goldigen yaml syntax example:

logger:
    factory: "@logger_provider::GetLogger"
    args:    [ "My Logger" ]
Example ¶

Let's assume that we have a LoggerProvider type that produces configured instances of a Logger each time we call LoggerProvider.GetLogger(loggerName string).

The example shows how to register a `logger` as proxy for a specific call to this LoggerProvider.

package main

import (
	"fmt"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"github.com/tarokamikaze/goldi"
)

// LoggerProvider provides logger instances
type LoggerProvider struct{}

func (lp *LoggerProvider) GetLogger(name string) *SimpleLogger {
	return &SimpleLogger{Name: name}
}

// Let's assume that we have a LoggerProvider type that produces configured instances
// of a Logger each time we call LoggerProvider.GetLogger(loggerName string).
//
// The example shows how to register a `logger` as proxy for a specific call to this LoggerProvider.
func main() {
	container := goldi.NewContainer(goldi.NewTypeRegistry(), map[string]interface{}{})

	// register some type as always
	container.Register("logger_provider", goldi.NewStructType(LoggerProvider{}))

	// register a proxy type that references the method of previously defined type and append call arguments if any
	container.Register("logger", goldi.NewProxyType("logger_provider", "GetLogger", "My logger"))

	l := container.MustGet("logger").(*SimpleLogger)
	fmt.Printf("%s: %T", l.Name, l)
}

var _ = Describe("proxyType", func() {
	It("should implement the TypeFactory interface", func() {
		var factory goldi.TypeFactory
		factory = goldi.NewProxyType("logger_provider", "GetLogger", "My logger")
		// if this compiles the test passes (next expectation only to make compiler happy)
		Expect(factory).NotTo(BeNil())
	})

	Describe("NewProxyType()", func() {
		It("should return an invalid type if the method name is not exported", func() {
			t := goldi.NewProxyType("logger_provider", "getLogger", "My logger")
			Expect(goldi.IsValid(t)).To(BeFalse())
			Expect(t).To(MatchError(`can not use unexported method "getLogger" as second argument to NewProxyType`))
		})
	})

	Describe("Arguments()", func() {
		It("should return the referenced service ID", func() {
			typeDef := goldi.NewProxyType("logger_provider", "GetLogger", "My logger")
			Expect(typeDef.Arguments()).To(Equal([]interface{}{"@logger_provider", "My logger"}))
		})
	})

	Describe("Generate()", func() {
		var (
			container *goldi.Container
			resolver  *goldi.ParameterResolver
		)

		BeforeEach(func() {
			config := map[string]interface{}{}
			container = goldi.NewContainer(goldi.NewTypeRegistry(), config)
			resolver = goldi.NewParameterResolver(container)
		})

		It("should get the correct method of the referenced type", func() {
			container.Register("logger_provider", goldi.NewStructType(LoggerProvider{}))
			typeDef := goldi.NewProxyType("logger_provider", "GetLogger", "My logger")

			generated, err := typeDef.Generate(resolver)
			Expect(err).NotTo(HaveOccurred())
			Expect(generated).To(BeAssignableToTypeOf(&SimpleLogger{}))
			Expect(generated.(*SimpleLogger).Name).To(Equal("My logger"))
		})

		It("should return an error if the referenced type has no such method", func() {
			typeDef := goldi.NewProxyType("foobar", "DoStuff")

			_, err := typeDef.Generate(resolver)
			Expect(err).To(MatchError("could not generate proxy type @foobar::DoStuff : type foobar does not exist"))
		})

		It("should return an error if the referenced type has no such method", func() {
			container.Register("logger_provider", goldi.NewStructType(LoggerProvider{}))
			typeDef := goldi.NewProxyType("logger_provider", "ThisMethodDoesNotExist", "foobar")

			_, err := typeDef.Generate(resolver)
			Expect(err).To(MatchError("could not generate proxy type @logger_provider::ThisMethodDoesNotExist : method does not exist"))
		})
	})
})
Output:

My logger: *goldi_test.SimpleLogger

func NewStructType ¶

func NewStructType(structT interface{}, structParameters ...interface{}) TypeFactory

NewStructType creates a TypeFactory that can be used to create a new instance of some struct type.

This function will return an invalid type if:

  • structT is no struct or pointer to a struct,
  • the number of given structParameters exceed the number of field of structT
  • the structParameters types do not match the fields of structT

Goldigen yaml syntax example:

logger:
    package: github.com/fgrosse/foobar
    type:    MyType
Example ¶
package main

import (
	"fmt"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"github.com/tarokamikaze/goldi"
)

func main() {
	container := goldi.NewContainer(goldi.NewTypeRegistry(), map[string]interface{}{})

	// all of the following types are semantically identical
	container.Register("foo_1", goldi.NewStructType(Foo{}))
	container.Register("foo_2", goldi.NewStructType(&Foo{}))
	container.Register("foo_3", goldi.NewStructType(new(Foo)))

	// each reference to the "logger" type will now be resolved to that instance
	fmt.Printf("foo_1: %T\n", container.MustGet("foo_1"))
	fmt.Printf("foo_2: %T\n", container.MustGet("foo_2"))
	fmt.Printf("foo_3: %T\n", container.MustGet("foo_3"))
}

var _ = Describe("structType", func() {
	It("should implement the TypeFactory interface", func() {
		var factory goldi.TypeFactory
		factory = goldi.NewStructType(MockType{})
		// if this compiles the test passes (next expectation only to make compiler happy)
		Expect(factory).NotTo(BeNil())
	})

	Describe("goldi.NewStructType()", func() {
		Context("with invalid arguments", func() {
			It("should return an invalid type if the generator is no struct or pointer to a struct", func() {
				Expect(goldi.IsValid(goldi.NewStructType(42))).To(BeFalse())
			})

			It("should return an invalid type if the generator is a pointer to something other than a struct", func() {
				something := "Hello Pointer World!"
				Expect(goldi.IsValid(goldi.NewStructType(&something))).To(BeFalse())
			})
		})

		Context("with first argument beeing a struct", func() {
			It("should create the type", func() {
				typeDef := goldi.NewStructType(MockType{})
				Expect(typeDef).NotTo(BeNil())
			})
		})

		Context("with first argument beeing a pointer to struct", func() {
			It("should create the type", func() {
				typeDef := goldi.NewStructType(&MockType{})
				Expect(typeDef).NotTo(BeNil())
			})
		})

		It("should return an invalid type if more factory arguments were provided than the struct has fields", func() {
			t := goldi.NewStructType(&MockType{}, "foo", true, "bar")
			Expect(goldi.IsValid(t)).To(BeFalse())
			Expect(t).To(MatchError("the struct MockType has only 2 fields but 3 arguments where provided"))
		})
	})

	Describe("Arguments()", func() {
		It("should return all factory arguments", func() {
			args := []interface{}{"foo", true}
			typeDef := goldi.NewStructType(MockType{}, args...)
			Expect(typeDef.Arguments()).To(Equal(args))
		})
	})

	Describe("Generate()", func() {
		var (
			config    = map[string]interface{}{}
			container *goldi.Container
			resolver  *goldi.ParameterResolver
		)

		BeforeEach(func() {
			container = goldi.NewContainer(goldi.NewTypeRegistry(), config)
			resolver = goldi.NewParameterResolver(container)
		})

		Context("without struct arguments", func() {
			Context("when the factory is a struct (no pointer)", func() {
				It("should generate the type", func() {
					typeDef := goldi.NewStructType(MockType{})
					Expect(typeDef.Generate(resolver)).To(BeAssignableToTypeOf(&MockType{}))
				})
			})

			It("should generate the type", func() {
				typeDef := goldi.NewStructType(&MockType{})
				Expect(typeDef.Generate(resolver)).To(BeAssignableToTypeOf(&MockType{}))
			})

			It("should generate a new type each time", func() {
				typeDef := goldi.NewStructType(&MockType{})
				t1, err1 := typeDef.Generate(resolver)
				t2, err2 := typeDef.Generate(resolver)

				Expect(err1).NotTo(HaveOccurred())
				Expect(err2).NotTo(HaveOccurred())
				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.(*MockType)
				t2Mock := t2.(*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 one or more arguments", func() {
			It("should generate the type", func() {
				typeDef := goldi.NewStructType(&MockType{}, "foo", true)

				generatedType, err := typeDef.Generate(resolver)
				Expect(err).NotTo(HaveOccurred())
				Expect(generatedType).To(BeAssignableToTypeOf(&MockType{}))

				generatedMock := generatedType.(*MockType)
				Expect(generatedMock.StringParameter).To(Equal("foo"))
				Expect(generatedMock.BoolParameter).To(Equal(true))
			})

			It("should use the given parameters", func() {
				typeDef := goldi.NewStructType(&MockType{}, "%param1%", "%param2%")
				config["param1"] = "TEST"
				config["param2"] = true
				generatedType, err := typeDef.Generate(resolver)
				Expect(err).NotTo(HaveOccurred())
				Expect(generatedType).To(BeAssignableToTypeOf(&MockType{}))

				generatedMock := generatedType.(*MockType)
				Expect(generatedMock.StringParameter).To(Equal("TEST"))
				Expect(generatedMock.BoolParameter).To(Equal(true))
			})

			Context("when a type reference is given", func() {
				Context("and its type matches the struct field type", func() {
					It("should generate the type", func() {
						container.RegisterType("foo", NewMockType)
						typeDef := goldi.NewStructType(TypeForServiceInjection{}, "@foo")

						generatedType, err := typeDef.Generate(resolver)
						Expect(err).NotTo(HaveOccurred())
						Expect(generatedType).To(BeAssignableToTypeOf(&TypeForServiceInjection{}))

						generatedMock := generatedType.(*TypeForServiceInjection)
						Expect(generatedMock.InjectedType).To(BeAssignableToTypeOf(&MockType{}))
					})
				})

				Context("and its type does not match the function signature", func() {
					It("should return an error", func() {
						container.RegisterType("foo", NewFoo)
						typeDef := goldi.NewStructType(TypeForServiceInjection{}, "@foo")

						_, err := typeDef.Generate(resolver)
						Expect(err).To(MatchError(`the referenced type "@foo" (type *goldi_test.Foo) can not be used as field 1 for struct type goldi_test.TypeForServiceInjection`))
					})
				})
			})
		})
	})
})
Output:

foo_1: *goldi_test.Foo
foo_2: *goldi_test.Foo
foo_3: *goldi_test.Foo

func NewType ¶

func NewType(factoryFunction interface{}, factoryParameters ...interface{}) TypeFactory

NewType creates a new TypeFactory.

This function will return an invalid type if:

  • the factoryFunction is nil or no function,
  • the factoryFunction returns zero or more than one parameter
  • the factoryFunctions return parameter is no pointer, interface or function type.
  • the number of given factoryParameters does not match the number of arguments of the factoryFunction

Goldigen yaml syntax example:

my_type:
    package: github.com/fgrosse/foobar
    factory: NewType
    args:
        - "Hello World"
        - true
Example ¶
package main

import (
	"fmt"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	"github.com/tarokamikaze/goldi"
)

func main() {
	container := goldi.NewContainer(goldi.NewTypeRegistry(), map[string]interface{}{})

	// register the type using the factory function NewMockTypeWithArgs and pass two arguments
	container.Register("my_type", goldi.NewType(NewMockTypeWithArgs, "Hello World", true))

	t := container.MustGet("my_type").(*MockType)
	fmt.Printf("%#v", t)
}

var _ = Describe("type", func() {
	It("should implement the TypeFactory interface", func() {
		var factory goldi.TypeFactory
		factory = goldi.NewType(NewFoo)
		// if this compiles the test passes (next expectation only to make compiler happy)
		Expect(factory).NotTo(BeNil())
	})

	Describe("goldi.NewType()", func() {
		Context("with invalid factory function", func() {
			It("should return an invalid type if the generator is no function", func() {
				Expect(goldi.IsValid(goldi.NewType(42))).To(BeFalse())
			})

			It("should return an invalid type if the generator has no output parameters", func() {
				Expect(goldi.IsValid(goldi.NewType(func() {}))).To(BeFalse())
			})

			It("should return an invalid type if the generator has more than one output parameter", func() {
				Expect(goldi.IsValid(goldi.NewType(func() (*MockType, *MockType) { return nil, nil }))).To(BeFalse())
			})

			It("should allow struct return types for flexibility", func() {
				Expect(goldi.IsValid(goldi.NewType(func() MockType { return MockType{} }))).To(BeTrue())
			})

			It("should not return an invalid type if the return parameter is an interface", func() {
				Expect(goldi.IsValid(goldi.NewType(func() interface{} { return MockType{} }))).To(BeTrue())
			})

			It("should not return an invalid type if the return parameter is a function", func() {
				Expect(goldi.IsValid(goldi.NewType(func() func() { return func() {} }))).To(BeTrue())
			})
		})

		Context("without factory function arguments", func() {
			Context("when no factory argument is given", func() {
				It("should create the type", func() {
					typeDef := goldi.NewType(NewMockType)
					Expect(typeDef).NotTo(BeNil())
				})
			})

			Context("when any argument is given", func() {
				It("should return an invalid type", func() {
					Expect(goldi.IsValid(goldi.NewType(NewMockType, "foo"))).To(BeFalse())
				})
			})
		})

		Context("with one or more factory function arguments", func() {
			Context("when an invalid number of arguments is given", func() {
				It("should return an invalid type", func() {
					Expect(goldi.IsValid(goldi.NewType(NewMockTypeWithArgs))).To(BeFalse())
					Expect(goldi.IsValid(goldi.NewType(NewMockTypeWithArgs, "foo"))).To(BeFalse())
					Expect(goldi.IsValid(goldi.NewType(NewMockTypeWithArgs, "foo", false, 42))).To(BeFalse())
				})
			})

			Context("when the wrong argument types are given", func() {
				It("should return an invalid type", func() {
					Expect(goldi.IsValid(goldi.NewType(NewMockTypeWithArgs, "foo", "bar"))).To(BeFalse())
					Expect(goldi.IsValid(goldi.NewType(NewMockTypeWithArgs, true, "bar"))).To(BeFalse())
				})
			})

			Context("when the correct argument number and types are given", func() {
				It("should create the type", func() {
					typeDef := goldi.NewType(NewMockTypeWithArgs, "foo", true)
					Expect(typeDef).NotTo(BeNil())
				})
			})

			Context("when the arguments are variadic", func() {
				It("should create the type", func() {
					typeDef := goldi.NewType(NewVariadicMockType, true, "ignored", "1", "two", "drei")
					Expect(typeDef).NotTo(BeNil())
				})

				It("should return an invalid type if not enough arguments where given", func() {
					t := goldi.NewType(NewVariadicMockType, true)
					Expect(goldi.IsValid(t)).To(BeFalse())
					Expect(t).To(MatchError("invalid number of input parameters for variadic function: got 1 but expected at least 3"))
				})
			})
		})
	})

	Describe("Arguments()", func() {
		It("should return all factory arguments", func() {
			args := []interface{}{"foo", true}
			typeDef := goldi.NewType(NewMockTypeWithArgs, args...)
			Expect(typeDef.Arguments()).To(Equal(args))
		})
	})

	Describe("Generate()", func() {
		var (
			config    = map[string]interface{}{}
			container *goldi.Container
			resolver  *goldi.ParameterResolver
		)

		BeforeEach(func() {
			container = goldi.NewContainer(goldi.NewTypeRegistry(), config)
			resolver = goldi.NewParameterResolver(container)
		})

		Context("without factory function arguments", func() {
			It("should generate the type", func() {
				typeDef := goldi.NewType(NewMockType)
				Expect(typeDef.Generate(resolver)).To(BeAssignableToTypeOf(&MockType{}))
			})
		})

		Context("with one or more factory function arguments", func() {
			It("should generate the type", func() {
				typeDef := goldi.NewType(NewMockTypeWithArgs, "foo", true)

				generatedType, err := typeDef.Generate(resolver)
				Expect(err).NotTo(HaveOccurred())
				Expect(generatedType).To(BeAssignableToTypeOf(&MockType{}))

				generatedMock := generatedType.(*MockType)
				Expect(generatedMock.StringParameter).To(Equal("foo"))
				Expect(generatedMock.BoolParameter).To(Equal(true))
			})

			Context("when a type reference is given", func() {
				Context("and its type matches the function signature", func() {
					It("should generate the type", func() {
						container.RegisterType("foo", NewMockType)
						typeDef := goldi.NewType(NewTypeForServiceInjection, "@foo")

						generatedType, err := typeDef.Generate(resolver)
						Expect(err).NotTo(HaveOccurred())
						Expect(generatedType).To(BeAssignableToTypeOf(&TypeForServiceInjection{}))

						generatedMock := generatedType.(*TypeForServiceInjection)
						Expect(generatedMock.InjectedType).To(BeAssignableToTypeOf(&MockType{}))
					})
				})

				Context("and its type does not match the function signature", func() {
					It("should return an error", func() {
						container.RegisterType("foo", NewFoo)
						typeDef := goldi.NewType(NewTypeForServiceInjectionWithArgs, "@foo", "arg1", "arg2", true)

						_, err := typeDef.Generate(resolver)
						Expect(err).To(MatchError(`the referenced type "@foo" (type *goldi_test.Foo) can not be passed as argument 1 to the function signature goldi_test.NewTypeForServiceInjectionWithArgs(*goldi_test.MockType, string, string, bool)`))
					})
				})
			})

			Context("when the arguments are variadic", func() {
				It("should generate the type", func() {
					typeDef := goldi.NewType(NewVariadicMockType, true, "ignored", "1", "two", "drei")
					Expect(typeDef).NotTo(BeNil())

					generatedType, err := typeDef.Generate(resolver)
					Expect(err).NotTo(HaveOccurred())
					Expect(generatedType).To(BeAssignableToTypeOf(&MockType{}))

					generatedMock := generatedType.(*MockType)
					Expect(generatedMock.BoolParameter).To(BeTrue())
					Expect(generatedMock.StringParameter).To(Equal("1, two, drei"))
				})
			})

			Context("when a func reference type is given", func() {
				It("should generate the type", func() {
					foo := &MockType{StringParameter: "Success!"}
					container.InjectInstance("foo", foo)
					typeDef := goldi.NewType(NewMockTypeFromStringFunc, "YEAH", "@foo::ReturnString")

					generatedType, err := typeDef.Generate(resolver)
					Expect(err).NotTo(HaveOccurred())
					Expect(generatedType).To(BeAssignableToTypeOf(NewMockType()))
					Expect(generatedType.(*MockType).StringParameter).To(Equal("Success! YEAH"))
				})
			})

			Context("when a func reference type is given as variadic argument", func() {
				It("should generate the type", func() {
					foo := &MockType{StringParameter: "Success!"}
					container.InjectInstance("foo", foo)
					typeDef := goldi.NewType(NewVariadicMockTypeFuncs, "@foo::ReturnString", "@foo::ReturnString")

					generatedType, err := typeDef.Generate(resolver)
					Expect(err).NotTo(HaveOccurred())
					Expect(generatedType).To(BeAssignableToTypeOf(NewMockType()))
					Expect(generatedType.(*MockType).StringParameter).To(Equal("Success! Success! "))
				})
			})
		})
	})
})
Output:

&goldi_test.MockType{StringParameter:"Hello World", BoolParameter:true}

type TypeID ¶

type TypeID struct {
	ID, Raw             string
	FuncReferenceMethod string
	IsOptional          bool
	IsFuncReference     bool
}

TypeID represents a parsed type identifier and associated meta data.

func NewTypeID ¶

func NewTypeID(s string) *TypeID

NewTypeID creates a new TypeId. Trying to create a type ID from an empty string will panic

func (*TypeID) String ¶

func (t *TypeID) String() string

String implements the fmt.Stringer interface by returning the raw representation of this type ID.

type TypeReferenceError ¶

type TypeReferenceError struct {
	TypeID       string
	TypeInstance interface{}
	// contains filtered or unexported fields
}

A TypeReferenceError occurs if you tried to inject a type that does not match the function declaration of the corresponding method.

type TypeRegistry ¶

type TypeRegistry map[string]TypeFactory

The TypeRegistry is effectively a map of typeID strings to TypeFactory

func NewTypeRegistry ¶

func NewTypeRegistry() TypeRegistry

NewTypeRegistry creates a new empty TypeRegistry

func (TypeRegistry) All ¶

All returns an iterator over all registered type IDs and their factories This uses Go 1.24's range over func feature for memory-efficient iteration

func (TypeRegistry) Clone ¶

func (r TypeRegistry) Clone() TypeRegistry

Clone creates a deep copy of the registry using maps.Collect

func (TypeRegistry) CollectAll ¶

func (r TypeRegistry) CollectAll() map[string]TypeFactory

CollectAll efficiently collects all type registrations into a map using maps.Collect

func (TypeRegistry) CollectByType ¶

func (r TypeRegistry) CollectByType(targetType reflect.Type) []string

CollectByType efficiently collects type IDs matching a specific type

func (TypeRegistry) CollectFactories ¶

func (r TypeRegistry) CollectFactories() []TypeFactory

CollectFactories efficiently collects all factories into a slice using slices.Collect

func (TypeRegistry) CollectTypeIDs ¶

func (r TypeRegistry) CollectTypeIDs() []string

CollectTypeIDs efficiently collects all type IDs into a slice using slices.Collect

func (TypeRegistry) Factories ¶

func (r TypeRegistry) Factories() iter.Seq[TypeFactory]

Factories returns an iterator over all registered factories

func (TypeRegistry) FilterByType ¶

func (r TypeRegistry) FilterByType(targetType reflect.Type) iter.Seq[string]

FilterByType returns an iterator over type IDs that match the given type

func (TypeRegistry) InjectInstance ¶

func (r TypeRegistry) InjectInstance(typeID string, instance interface{})

InjectInstance enables you to inject type instances. If instance is nil an error is returned

func (TypeRegistry) Register ¶

func (r TypeRegistry) Register(typeID string, typeDef TypeFactory)

Register saves a type under the given symbolic typeID so it can be retrieved later. It is perfectly legal to call Register multiple times with the same typeID. In this case you overwrite existing type definitions with new once

func (TypeRegistry) RegisterAll ¶

func (r TypeRegistry) RegisterAll(factories map[string]TypeFactory)

RegisterAll will register all given type factories under the mapped type ID It uses maps.Copy for efficient bulk registration

func (TypeRegistry) RegisterType ¶

func (r TypeRegistry) RegisterType(typeID string, factory interface{}, arguments ...interface{})

RegisterType is convenience method for TypeRegistry.Register It tries to create the correct TypeFactory and passes this to TypeRegistry.Register This function panics if the given generator function and arguments can not be used to create a new type factory.

func (TypeRegistry) TypeIDs ¶

func (r TypeRegistry) TypeIDs() iter.Seq[string]

TypeIDs returns an iterator over all registered type IDs

type UnknownTypeReferenceError ¶

type UnknownTypeReferenceError struct {
	TypeID string
	// contains filtered or unexported fields
}

The UnknownTypeReferenceError occurs if you try to get a type by an unknown type id (type has not been registered).

Directories ¶

Path Synopsis
The goldigen binary See https://github.com/tarokamikaze/goldi#the-goldigen-binary
The goldigen binary See https://github.com/tarokamikaze/goldi#the-goldigen-binary
Package validation provides simple validation of goldi containers
Package validation provides simple validation of goldi containers

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL