Skip to content

Commit

Permalink
Implement Apply() method
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxim Bovtunov committed Feb 15, 2021
1 parent ccbb0be commit 4aee580
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 39 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ The format is based on
project adheres to
[Semantic Versioning](https://semver.org/spec/v2.0.0.html): TBD, use modules or another vendor system.

## v1.8.0

### Added

- `container.Apply()` function.

## v1.7.1

### Fixed
Expand Down
73 changes: 44 additions & 29 deletions container.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,17 @@ import (
type Container struct {
// Dependency injection schema.
schema *defaultSchema
// Initial options will be processed on di.New().
initial struct {
// Array of di.Provide() options.
provides []provideOptions
// Array of di.Invoke() options.
invokes []invokeOptions
// Array of di.Resolve() options.
resolves []resolveOptions
}
// Array of provider cleanups.
cleanups []func()
// Flag indicates acyclic verification state
//verified map[key]bool
}

type diopts struct {
// Array of di.Provide() options.
provides []provideOptions
// Array of di.Invoke() options.
invokes []invokeOptions
// Array of di.Resolve() options.
resolves []resolveOptions
}

// New constructs container with provided options. Example usage (simplified):
Expand Down Expand Up @@ -58,36 +56,53 @@ func New(options ...Option) (_ *Container, err error) {
schema: newDefaultSchema(),
cleanups: []func(){},
}
// apply container options
var di diopts
// apply container diopts
for _, opt := range options {
opt.apply(c)
opt.apply(&di)
}
// provide container to advanced usage e.g. condition providing
_ = c.provide(func() *Container { return c })
if err := c.apply(di); err != nil {
return nil, err
}
// process di.Resolve() options
for _, provide := range c.initial.provides {
return c, nil
}

// Apply
func (c *Container) Apply(options ...Option) error {
var di diopts
for _, opt := range options {
opt.apply(&di)
}
return c.apply(di)
}

func (c *Container) apply(di diopts) error {
// process di.Resolve() diopts
for _, provide := range di.provides {
if err := c.provide(provide.constructor, provide.options...); err != nil {
return nil, fmt.Errorf("%s: %w", provide.frame, err)
return fmt.Errorf("%s: %w", provide.frame, err)
}
}
// provide container to advanced usage e.g. condition providing
_ = c.provide(func() *Container { return c })
// error omitted because if logger could not be resolved it will be default
// process di.Invoke() options
for _, invoke := range c.initial.invokes {
// process di.Invoke() diopts
for _, invoke := range di.invokes {
err := c.invoke(invoke.fn, invoke.options...)
if err != nil && knownError(err) {
return nil, fmt.Errorf("%s: %w", invoke.frame, err)
return fmt.Errorf("%s: %w", invoke.frame, err)
}
if err != nil {
return nil, err
return err
}
}
// process di.Resolve() options
for _, resolve := range c.initial.resolves {
// process di.Resolve() diopts
for _, resolve := range di.resolves {
if err := c.resolve(resolve.target, resolve.options...); err != nil {
return nil, fmt.Errorf("%s: %w", resolve.frame, err)
return fmt.Errorf("%s: %w", resolve.frame, err)
}
}
return c, nil
return nil
}

// Provide provides to container reliable way to build type. The constructor will be invoked lazily on-demand.
Expand All @@ -105,7 +120,7 @@ func (c *Container) provide(constructor Constructor, options ...ProvideOption) e
return fmt.Errorf("invalid constructor signature, got nil")
}
params := ProvideParams{}
// apply provide options
// apply provide diopts
for _, opt := range options {
opt.applyProvide(&params)
}
Expand Down Expand Up @@ -180,7 +195,7 @@ func (c *Container) Invoke(invocation Invocation, options ...InvokeOption) error

func (c *Container) invoke(invocation Invocation, _ ...InvokeOption) error {
// params := InvokeParams{}
// for _, opt := range options {
// for _, opt := range diopts {
// opt.apply(&params)
// }
if invocation == nil {
Expand Down Expand Up @@ -282,7 +297,7 @@ func (c *Container) find(ptr Pointer, options ...ResolveOption) (*node, error) {
return nil, fmt.Errorf("target must be a pointer, got %s", reflect.TypeOf(ptr))
}
params := ResolveParams{}
// apply extract options
// apply extract diopts
for _, opt := range options {
opt.applyResolve(&params)
}
Expand Down
18 changes: 18 additions & 0 deletions container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,24 @@ func TestContainer_Resolve(t *testing.T) {
})
}

func TestContainer_Apply(t *testing.T) {
t.Run("apply applies container options with error", func(t *testing.T) {
c, err := di.New()
require.NoError(t, err)
require.NotNil(t, c)
err = c.Apply(
di.Provide(func() *http.Server { return &http.Server{} }, di.As(new(io.Closer))),
di.Provide(func() *os.File { return &os.File{} }, di.As(new(io.Closer))),
)
require.NoError(t, err)
var closer io.Closer
err = c.Resolve(&closer)
require.Error(t, err)
require.Contains(t, err.Error(), "container_test.go:")
require.Contains(t, err.Error(), ": multiple definitions of io.Closer, maybe you need to use group type: []io.Closer")
})
}

func TestContainer_Interfaces(t *testing.T) {
t.Run("resolve interface with several implementations cause error", func(t *testing.T) {
c, err := di.New(
Expand Down
20 changes: 10 additions & 10 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ package di
// - di.Invoke - add invocations
// - di.Resolve - resolves type
type Option interface {
apply(c *Container)
apply(c *diopts)
}

// Provide returns container option that provides to container reliable way to build type. The constructor will
// be invoked lazily on-demand. For more information about constructors see Constructor interface. ProvideOption can
// add additional behavior to the process of type resolving.
func Provide(constructor Constructor, options ...ProvideOption) Option {
frame := stacktrace(0)
return option(func(c *Container) {
c.initial.provides = append(c.initial.provides, provideOptions{
return option(func(c *diopts) {
c.provides = append(c.provides, provideOptions{
frame,
constructor,
options,
Expand Down Expand Up @@ -121,8 +121,8 @@ func WithName(name string) ProvideOption {
// after call invokes.
func Resolve(target interface{}, options ...ResolveOption) Option {
frame := stacktrace(0)
return option(func(c *Container) {
c.initial.resolves = append(c.initial.resolves, resolveOptions{
return option(func(c *diopts) {
c.resolves = append(c.resolves, resolveOptions{
frame,
target,
options,
Expand All @@ -135,8 +135,8 @@ func Resolve(target interface{}, options ...ResolveOption) Option {
// See Container.Invoke() for details.
func Invoke(fn Invocation, options ...InvokeOption) Option {
frame := stacktrace(0)
return option(func(c *Container) {
c.initial.invokes = append(c.initial.invokes, invokeOptions{
return option(func(c *diopts) {
c.invokes = append(c.invokes, invokeOptions{
frame,
fn,
options,
Expand All @@ -162,7 +162,7 @@ func Invoke(fn Invocation, options ...InvokeOption) Option {
// // handle error
// }
func Options(options ...Option) Option {
return option(func(container *Container) {
return option(func(container *diopts) {
for _, opt := range options {
opt.apply(container)
}
Expand Down Expand Up @@ -221,9 +221,9 @@ func (p ResolveParams) applyResolve(params *ResolveParams) {
*params = p
}

type option func(c *Container)
type option func(c *diopts)

func (o option) apply(c *Container) { o(c) }
func (o option) apply(c *diopts) { o(c) }

type provideOption func(params *ProvideParams)

Expand Down

0 comments on commit 4aee580

Please sign in to comment.