diff --git a/README.md b/README.md index 18d5585..7d261a9 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,10 @@ to use *declared functions*. ### Declared functions -> Declared functions replace hooks since version 0.3.0. Hooks were very badly designed and totally removed for now. - -Declared functions will be called only when the global container starts an *invocation* at the first time. +Declared functions will be called only when the global container starts runs *functors* at the first time. It's useful for libraries which may not perform container filling up operations if their entities just used manually. -To declare a function use `kinit.Declare` or `kinit.DeclareErrorProne` method: +To declare a function use `kinit.Declare` and `kinit.DeclareErrorProne` methods: ```go kinit.Declare(func() { /* fill up the global container here */ }) @@ -66,7 +64,7 @@ func init() { ### Constructors -Constructors are entities which creates objects (dependencies for injection in context of the DI). This library +Constructors are entities which create objects (dependencies for injection in context of the DI). This library considers constructors to have the following interface: ```go @@ -105,14 +103,14 @@ The **[KInitX](https://github.com/go-kata/kinitx)** library provides following c * **Constructor** is the function-based constructor. * **Opener** is the function-based constructor which creates an object that implements the `io.Closer` interface. - The `Close` method is treated as object destructor. -* **Initializer** is the memberwise initializer for structs that doesn't provide a destructor. + The object's `Close` method is treated as destructor. +* **Initializer** is the memberwise initializer of a struct. It doesn't provide a destructor. You can find more details in the documentation for the library. ### Processors -Processors are entities which processes already created objects. This library applies processors immediately after +Processors are entities which process already created objects. This library applies processors immediately after object creation and before it will be injected as a dependency at the first time. It considers processors to have the following interface: @@ -131,68 +129,49 @@ type Processor interface { Here `Type` returns a type of object to process, `Parameters` returns types of other objects required to this object processing (dependencies) and finally `Process` processes an object. -To register a new processor in container use `kinit.Apply` method (or `ctr.Apply` for local containers). -There are also `kinit.MustApply` method (or `ctr.MustApply` for local containers) that panics on error. +To register a new processor in container use `kinit.Attach` method (or `ctr.Attach` for local containers). +There are also `kinit.MustAttach` method (or `ctr.MustAttach` for local containers) that panics on error. -Container allows to have an unlimited number of processors for each type but doesn't guarantee the order of their -calling. +Container allows to have an unlimited number of processors for each type but doesn't guarantee the order of +their calling. The **[KInitX](https://github.com/go-kata/kinitx)** library provides the function-based processor implementation. -### Invocation - -Invocation is the process of some activity execution resolving a dependency tree as roots of which are treated -this activity dependencies. - -To perform an invocation use `kinit.Invoke` method (or `ctr.Invoke` for local containers). There are also -`kinit.MustInvoke` method (or `ctr.MustInvoke` for local containers) that panics on error. Both methods -require an *executor* and allow to pass one or more *bootstrappers*. - -At the start of invocation container creates so-called *arena* that contains all created objects (only one object -for each type). If some object that is required as a dependency is already on the arena it will be used, otherwise -it will be previously created and processed. All objects that are on the arena at the end of invocation will be -automatically destroyed using their destructors. - -When all dependencies of an activity are resolved, container executes it. Executed activity may provide other -activity to continue invocation - its dependencies will be also resolved using the same arena. This process is -called the *cascade initialization*. When a currently executed activity doesn't provide a next activity to execute, -invocation ends. - -### Executors +### Functors -Executors are representations of activities. This library considers executors to have the following interface: +Functors are run in the container. This library considers functors to have the following interface: ```go -type Executor interface { +type Functor interface { Parameters() []reflect.Type - Execute(a ...reflect.Value) (Executor, error) + Call(a ...reflect.Value) ([]Functor, error) } ``` -Here `Parameters` returns type of objects required to activity execution (root dependencies) and `Execute` executes -activity and may return a next executor to continue invocation. +Here `Parameters` returns types of objects required to function running (dependencies) and `Call` calls a function +and may return *further functors*. -The **[KInitX](https://github.com/go-kata/kinitx)** library provides the function-based executor implementation. +To run functors in container use `kinit.Run` method (or `ctr.Run` for local containers). There are also +`kinit.MustRun` method (or `ctr.MustRun` for local containers) that panics on error. -### Bootstrappers +At the start of run container creates so-called *arena* that contains all created objects (only one object +for each type). If some object that is required as a dependency is already on the arena it will be used, otherwise +it will be created and processed at first. All objects that are on the arena at the end of run will be automatically +destroyed using their destructors. -Bootstrappers are special entities which allows the arena bootstrapping at the start of invocation. This library -considers bootstrappers to have the following interface: +Container runs given functors sequentially resolving their dependencies recursively using registered constructors +and processors. If functor (let's call it *branched*) returns further functors, container runs all of them before +continue running functors that follows the branched one. This is called the *Depth-First Run*. -```go -type Bootstrapper interface { - - Bootstrap(arena *kinit.Arena) error - -} -``` +The **[KInitX](https://github.com/go-kata/kinitx)** library provides following functor implementations: -The **[KInitX](https://github.com/go-kata/kinitx)** library provides the bootstrapper implementation called *literal*. -Literals are objects that are registered on the arena for direct use instead of being created during dependency tree -resolution. Those objects have no destructors to call at the end of invocation - they must be destroyed manually. +* **Functor** is the function-based functor. +* **Injector** is the provider of object that is registered on the arena for direct use instead of being created + during the dependency tree resolution. The provided object has no destructor to call at the end of execution + and must be destroyed manually. ### Putting all together diff --git a/arena.go b/arena.go index 1aa3cec..e70c879 100644 --- a/arena.go +++ b/arena.go @@ -7,27 +7,38 @@ import ( "github.com/go-kata/kerror" ) -// Arena represents an invocation context. +// Arena represents an objects holder. type Arena struct { - // objects specifies the list of registered objects. + // parents specifies parent arenas. + parents []*Arena + // objects specifies registered objects. objects map[reflect.Type]reflect.Value // reaper specifies the reaper for registered objects. reaper *kdone.Reaper + // finalized specifies whether were registered objects destroyed. + finalized bool } -// NewArena returns a new arena. -func NewArena() *Arena { - return &Arena{ +// NewArena returns a new arena with given parent arenas. +func NewArena(parents ...*Arena) *Arena { + a := &Arena{ objects: make(map[reflect.Type]reflect.Value), reaper: kdone.NewReaper(), } + if len(parents) > 0 { + a.parents = make([]*Arena, len(parents)) + copy(a.parents, parents) + } + return a } // Register registers the given object on this arena. func (a *Arena) Register(t reflect.Type, obj reflect.Value, dtor kdone.Destructor) error { if a == nil { - kerror.NPE() - return nil + return kerror.New(kerror.ENil, "nil arena cannot register object") + } + if a.finalized { + return kerror.New(kerror.EIllegal, "arena has already destroyed objects") } if t == nil { return kerror.New(kerror.EInvalid, "arena cannot register object of nil type") @@ -49,12 +60,24 @@ func (a *Arena) MustRegister(t reflect.Type, obj reflect.Value, dtor kdone.Destr } } -// Get returns an object of the given type if registered on this arena. +// Get returns an object of the given type if registered on this arena +// or on the one of non-finalized parent arenas which will be bypassed +// in the order that they were passed to the NewArena. func (a *Arena) Get(t reflect.Type) (obj reflect.Value, ok bool) { if a == nil || t == nil { return reflect.Value{}, false } - obj, ok = a.objects[t] + if obj, ok = a.objects[t]; ok { + return + } + for _, parent := range a.parents { + if parent.Finalized() { + continue + } + if obj, ok = parent.Get(t); ok { + return + } + } return } @@ -63,6 +86,12 @@ func (a *Arena) Finalize() error { if a == nil { return nil } + if a.finalized { + return kerror.New(kerror.EIllegal, "arena has already destroyed objects") + } + defer func() { + a.finalized = true + }() return a.reaper.Finalize() } @@ -72,3 +101,11 @@ func (a *Arena) MustFinalize() { panic(err) } } + +// Finalized returns boolean specifies were objects registered on this arena destroyed. +func (a *Arena) Finalized() bool { + if a == nil { + return false + } + return a.finalized +} diff --git a/arena_test.go b/arena_test.go index a2b8540..989adfe 100644 --- a/arena_test.go +++ b/arena_test.go @@ -9,27 +9,110 @@ import ( ) func TestArena(t *testing.T) { - arena := NewArena() + parent1 := NewArena() + defer parent1.MustFinalize() + x := int16(1) + xt := reflect.TypeOf(x) + parent1.MustRegister(xt, reflect.ValueOf(x), kdone.Noop) + + parent2 := NewArena() + defer parent2.MustFinalize() + y := int32(1) + yt := reflect.TypeOf(y) + parent2.MustRegister(yt, reflect.ValueOf(y), kdone.Noop) + + arena := NewArena(parent1, parent2) defer arena.MustFinalize() + z := int64(1) + zt := reflect.TypeOf(z) + arena.MustRegister(zt, reflect.ValueOf(z), kdone.Noop) + + if obj, ok := arena.Get(xt); !ok || obj.Interface() != x { + t.Fail() + return + } + if obj, ok := arena.Get(yt); !ok || obj.Interface() != y { + t.Fail() + return + } + if obj, ok := arena.Get(zt); !ok || obj.Interface() != z { + t.Fail() + return + } +} + +func TestArena__SameType(t *testing.T) { + parent := NewArena() + defer parent.MustFinalize() x := 1 xt := reflect.TypeOf(x) - arena.MustRegister(xt, reflect.ValueOf(x), kdone.Noop) + parent.MustRegister(xt, reflect.ValueOf(x), kdone.Noop) + + arena := NewArena(parent) + defer arena.MustFinalize() + y := 2 + yt := reflect.TypeOf(y) + arena.MustRegister(yt, reflect.ValueOf(y), kdone.Noop) + + if obj, ok := arena.Get(yt); !ok || obj.Interface() != y { + t.Fail() + return + } +} + +func TestArena__NilParent(t *testing.T) { + parent := NewArena() + defer parent.MustFinalize() + x := 1 + xt := reflect.TypeOf(x) + parent.MustRegister(xt, reflect.ValueOf(x), kdone.Noop) + + arena := NewArena(nil, parent) + defer arena.MustFinalize() + if obj, ok := arena.Get(xt); !ok || obj.Interface() != x { t.Fail() + return + } +} + +func TestArena__FinalizedParent(t *testing.T) { + parent := NewArena() + x := int16(1) + xt := reflect.TypeOf(x) + parent.MustRegister(xt, reflect.ValueOf(x), kdone.Noop) + + arena := NewArena(parent) + defer arena.MustFinalize() + y := int64(1) + yt := reflect.TypeOf(y) + arena.MustRegister(yt, reflect.ValueOf(y), kdone.Noop) + + parent.MustFinalize() + + if obj, ok := arena.Get(xt); ok { + t.Logf("%+v", obj) + t.Fail() + return + } + if obj, ok := arena.Get(yt); !ok || obj.Interface() != y { + t.Fail() + return } } -func TestArena_RegisterWithNilObject(t *testing.T) { +func TestArena_Register__NilObject(t *testing.T) { arena := NewArena() defer arena.MustFinalize() err := arena.Register(nil, reflect.Value{}, kdone.Noop) t.Logf("%+v", err) if kerror.ClassOf(err) != kerror.EInvalid { t.Fail() + return } } -func TestArena_RegisterWithNilDestructor(t *testing.T) { +func TestArena_Register__NilDestructor(t *testing.T) { arena := NewArena() defer arena.MustFinalize() x := 1 @@ -37,10 +120,11 @@ func TestArena_RegisterWithNilDestructor(t *testing.T) { t.Logf("%+v", err) if kerror.ClassOf(err) != kerror.EInvalid { t.Fail() + return } } -func TestArena_RegisterWithAmbiguousObject(t *testing.T) { +func TestArena_Register__AmbiguousObject(t *testing.T) { arena := NewArena() defer arena.MustFinalize() x := 1 @@ -51,15 +135,29 @@ func TestArena_RegisterWithAmbiguousObject(t *testing.T) { t.Logf("%+v", err) if kerror.ClassOf(err) != kerror.EAmbiguous { t.Fail() + return + } +} + +func TestArena_Register__Finalized(t *testing.T) { + arena := NewArena() + arena.MustFinalize() + x := 1 + err := arena.Register(reflect.TypeOf(x), reflect.ValueOf(x), nil) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EIllegal { + t.Fail() + return } } -func TestArena_GetWithNilType(t *testing.T) { +func TestArena_Get__NilType(t *testing.T) { arena := NewArena() defer arena.MustFinalize() x := 1 arena.MustRegister(reflect.TypeOf(x), reflect.ValueOf(x), kdone.Noop) - if _, ok := arena.Get(nil); ok { + if obj, ok := arena.Get(nil); ok { + t.Logf("%+v", obj) t.Fail() return } @@ -82,21 +180,55 @@ func TestArena_Finalize(t *testing.T) { })) } +func TestArena_Finalize__Finalized(t *testing.T) { + arena := NewArena() + arena.MustFinalize() + err := arena.Finalize() + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EIllegal { + t.Fail() + return + } +} + +func TestArena_Finalized(t *testing.T) { + arena := NewArena() + arena.MustFinalize() + if !arena.Finalized() { + t.Fail() + return + } +} + func TestNilArena_Register(t *testing.T) { - defer func() { - v := recover() - t.Logf("%+v", v) - if v == nil { - t.Fail() - return - } - }() - _ = (*Arena)(nil).Register(nil, reflect.Value{}, nil) + err := (*Arena)(nil).Register(nil, reflect.Value{}, nil) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.ENil { + t.Fail() + return + } +} + +func TestNilArena_Get(t *testing.T) { + x := 1 + if obj, ok := (*Arena)(nil).Get(reflect.TypeOf(x)); ok { + t.Logf("%+v", obj) + t.Fail() + return + } } func TestNilArena_Finalize(t *testing.T) { if err := (*Arena)(nil).Finalize(); err != nil { t.Logf("%+v", err) + t.Fail() + return + } +} + +func TestNilArena_Finalized(t *testing.T) { + if (*Arena)(nil).Finalized() { + t.Fail() return } } diff --git a/constructor_test.go b/constructor_test.go index 341143b..9e975c4 100644 --- a/constructor_test.go +++ b/constructor_test.go @@ -50,16 +50,44 @@ func (c *testConstructor) Create(a ...reflect.Value) (reflect.Value, kdone.Destr return obj, dtor, err } -type testBrokenConstructor struct{} +type testConstructorWithBrokenType struct{} -func (testBrokenConstructor) Type() reflect.Type { +func (testConstructorWithBrokenType) Type() reflect.Type { return nil } -func (testBrokenConstructor) Parameters() []reflect.Type { +func (testConstructorWithBrokenType) Parameters() []reflect.Type { return nil } -func (testBrokenConstructor) Create(a ...reflect.Value) (reflect.Value, kdone.Destructor, error) { +func (testConstructorWithBrokenType) Create(a ...reflect.Value) (reflect.Value, kdone.Destructor, error) { + return reflect.Value{}, nil, nil +} + +type testConstructorWithBrokenParameters struct{} + +func (testConstructorWithBrokenParameters) Type() reflect.Type { + return reflect.TypeOf(1) +} + +func (testConstructorWithBrokenParameters) Parameters() []reflect.Type { + return []reflect.Type{nil} +} + +func (testConstructorWithBrokenParameters) Create(a ...reflect.Value) (reflect.Value, kdone.Destructor, error) { + return reflect.Value{}, nil, nil +} + +type testConstructorWithBrokenDestructor struct{} + +func (testConstructorWithBrokenDestructor) Type() reflect.Type { + return reflect.TypeOf(1) +} + +func (testConstructorWithBrokenDestructor) Parameters() []reflect.Type { + return nil +} + +func (testConstructorWithBrokenDestructor) Create(a ...reflect.Value) (reflect.Value, kdone.Destructor, error) { return reflect.Value{}, nil, nil } diff --git a/container.go b/container.go index 8e2da9b..862c5af 100644 --- a/container.go +++ b/container.go @@ -30,8 +30,7 @@ func NewContainer() *Container { // Only one constructor for a type may be registered. func (c *Container) Provide(ctor Constructor) error { if c == nil { - kerror.NPE() - return nil + return kerror.New(kerror.ENil, "nil container cannot register constructor") } if ctor == nil { return kerror.New(kerror.EInvalid, "container cannot register nil constructor") @@ -54,13 +53,12 @@ func (c *Container) MustProvide(ctor Constructor) { } } -// Apply registers the given processor in this container. +// Attach registers the given processor in this container. // // Multiple processors may be registered for one type, but there are no guaranty of order of their call. -func (c *Container) Apply(proc Processor) error { +func (c *Container) Attach(proc Processor) error { if c == nil { - kerror.NPE() - return nil + return kerror.New(kerror.ENil, "nil container cannot register processor") } if proc == nil { return kerror.New(kerror.EInvalid, "container cannot register nil processor") @@ -73,118 +71,100 @@ func (c *Container) Apply(proc Processor) error { return nil } -// MustApply is a variant of the Apply that panics on error. -func (c *Container) MustApply(proc Processor) { - if err := c.Apply(proc); err != nil { +// MustAttach is a variant of the Attach that panics on error. +func (c *Container) MustAttach(proc Processor) { + if err := c.Attach(proc); err != nil { panic(err) } } -// Invoke applies given bootstrappers, resolves the dependency graph based on parameters -// of the given executor using this container and then executes an activity. Dependencies -// of each subsequent executor will be resolved dynamically before it's activity execution. -func (c *Container) Invoke(exec Executor, bootstrappers ...Bootstrapper) (err error) { +// Run runs given functors sequentially resolving their dependencies recursively using this container. +// If some functor returns further functors all of them will be run before the running of functors that follows it. +// +// All objects created during run will be automatically destroyed when it ends. +func (c *Container) Run(functors ...Functor) (err error) { if c == nil { - kerror.NPE() - return nil - } - if exec == nil { - return kerror.New(kerror.EInvalid, "container cannot invoke nil executor") + return kerror.New(kerror.ENil, "nil container cannot run functors") } arena := NewArena() defer func() { - err = kerror.Collect(err, arena.Finalize()) + err = kerror.Join(err, arena.Finalize()) }() - for _, boot := range bootstrappers { - if boot == nil { - return kerror.New(kerror.EInvalid, "container cannot apply nil bootstrapper") - } - if err := boot.Bootstrap(arena); err != nil { - return err - } + runtime, err := NewRuntime(c, arena) + if err != nil { + return err } - return c.execute(arena, exec) + if err := arena.Register(reflect.TypeOf(runtime), reflect.ValueOf(runtime), kdone.Noop); err != nil { + return err + } + return c.run(arena, functors...) } -// MustInvoke is a variant of Invoke that panics on error. -func (c *Container) MustInvoke(exec Executor, bootstrappers ...Bootstrapper) { - if err := c.Invoke(exec, bootstrappers...); err != nil { +// MustRun is a variant of the Run that panics on error. +func (c *Container) MustRun(functors ...Functor) { + if err := c.Run(functors...); err != nil { panic(err) } } -// get creates if needed and returns an object of the given type. -func (c *Container) get(arena *Arena, t reflect.Type) (reflect.Value, error) { - if t == nil { - return reflect.Value{}, kerror.New(kerror.EInvalid, "container cannot resolve dependency of nil type") - } - if obj, ok := arena.Get(t); ok { - return obj, nil - } - ctor, ok := c.constructors[t] - if !ok { - return reflect.Value{}, kerror.Newf(kerror.ENotFound, "%s constructor is not registered", t) - } - obj, dtor, err := c.create(arena, ctor) - if err != nil { - return reflect.Value{}, err - } - for _, proc := range c.processors[t] { - if err := c.process(arena, obj, proc); err != nil { - return reflect.Value{}, err +// run runs given functors using the given arena. +func (c *Container) run(arena *Arena, functors ...Functor) error { + for _, fun := range functors { + if fun == nil { + return kerror.New(kerror.EInvalid, "container cannot run nil functor") } - } - if err := arena.Register(t, obj, dtor); err != nil { - return reflect.Value{}, err - } - return obj, nil -} - -// create creates and returns a new object. -func (c *Container) create(arena *Arena, ctor Constructor) (reflect.Value, kdone.Destructor, error) { - p := ctor.Parameters() - a := make([]reflect.Value, len(p)) - for i := range p { - aobj, err := c.get(arena, p[i]) + a, err := c.resolve(arena, fun.Parameters()) if err != nil { - return reflect.Value{}, nil, err + return err } - a[i] = aobj - } - return ctor.Create(a...) -} - -// process processes the given object. -func (c *Container) process(arena *Arena, obj reflect.Value, proc Processor) error { - p := proc.Parameters() - a := make([]reflect.Value, len(p)) - for i := range p { - aobj, err := c.get(arena, p[i]) + further, err := fun.Call(a...) if err != nil { return err } - a[i] = aobj + if err := c.run(arena, further...); err != nil { + return err + } } - return proc.Process(obj, a...) + return nil } -// execute executes an activity. -func (c *Container) execute(arena *Arena, exec Executor) error { - p := exec.Parameters() - a := make([]reflect.Value, len(p)) - for i := range p { - aobj, err := c.get(arena, p[i]) +// resolve returns objects of given types. If object is already on the given arena, it will be used. +// Otherwise it will be firstly created and processed using this container and registered on the arena. +func (c *Container) resolve(arena *Arena, types []reflect.Type) ([]reflect.Value, error) { + objects := make([]reflect.Value, len(types)) + for i, t := range types { + if t == nil { + return nil, kerror.New(kerror.EInvalid, "container cannot resolve dependency of nil type") + } + if obj, ok := arena.Get(t); ok { + objects[i] = obj + continue + } + ctor, ok := c.constructors[t] + if !ok { + return nil, kerror.Newf(kerror.ENotFound, "%s constructor is not registered", t) + } + a, err := c.resolve(arena, ctor.Parameters()) if err != nil { - return err + return nil, err } - a[i] = aobj - } - next, err := exec.Execute(a...) - if err != nil { - return err - } - if next != nil { - return c.execute(arena, next) + obj, dtor, err := ctor.Create(a...) + if err != nil { + return nil, err + } + for _, proc := range c.processors[t] { + a, err := c.resolve(arena, proc.Parameters()) + if err != nil { + return nil, err + } + if err := proc.Process(obj, a...); err != nil { + return nil, err + } + } + if err := arena.Register(t, obj, dtor); err != nil { + return nil, err + } + objects[i] = obj } - return nil + return objects, nil } diff --git a/container_test.go b/container_test.go index b0ecaf7..ab14f0b 100644 --- a/container_test.go +++ b/container_test.go @@ -1,6 +1,7 @@ package kinit import ( + "reflect" "testing" "github.com/go-kata/kdone" @@ -50,30 +51,32 @@ func TestContainer(t *testing.T) { return } }() - c := NewContainer() - c.MustProvide(newTestConstructor(func() (*int, kdone.Destructor, error) { return &counter, kdone.Noop, nil })) - c.MustApply(newTestProcessor(processTestCounter)) - c.MustProvide(newTestConstructor(newTestObject1)) - c.MustProvide(newTestConstructor(newTestObject2)) - c.MustInvoke( - newTestExecutor(func(*testObject1) (Executor, error) { + ctr := NewContainer() + ctr.MustProvide(newTestConstructor(func() (*int, kdone.Destructor, error) { return &counter, kdone.Noop, nil })) + ctr.MustAttach(newTestProcessor(processTestCounter)) + ctr.MustProvide(newTestConstructor(newTestObject1)) + ctr.MustProvide(newTestConstructor(newTestObject2)) + ctr.MustRun( + newTestFunctor(func(runtime *Runtime) ([]Functor, error) { + return nil, runtime.Register(reflect.TypeOf(t), reflect.ValueOf(t), kdone.Noop) + }), + newTestFunctor(func(*testObject1) ([]Functor, error) { if counter != 2 { return nil, kerror.Newf(nil, "counter must be 2, %d found", counter) } - return newTestExecutor(func(*testObject2) (Executor, error) { + return []Functor{newTestFunctor(func(*testObject2) ([]Functor, error) { if counter != 0 { return nil, kerror.Newf(nil, "counter must be 0, %d found", counter) } return nil, nil - }), nil + })}, nil }), - newTestBootstrapper(t), ) } -func TestContainer_ProvideWithNilConstructor(t *testing.T) { - c := NewContainer() - err := c.Provide(nil) +func TestContainer_Provide__NilConstructor(t *testing.T) { + ctr := NewContainer() + err := ctr.Provide(nil) t.Logf("%+v", err) if kerror.ClassOf(err) != kerror.EInvalid { t.Fail() @@ -81,9 +84,9 @@ func TestContainer_ProvideWithNilConstructor(t *testing.T) { } } -func TestContainer_ProvideWithBrokenConstructor(t *testing.T) { - c := NewContainer() - err := c.Provide(testBrokenConstructor{}) +func TestContainer_Provide__ConstructorWithBrokenType(t *testing.T) { + ctr := NewContainer() + err := ctr.Provide(testConstructorWithBrokenType{}) t.Logf("%+v", err) if kerror.ClassOf(err) != kerror.EInvalid { t.Fail() @@ -91,10 +94,10 @@ func TestContainer_ProvideWithBrokenConstructor(t *testing.T) { } } -func TestContainer_ProvideWithAmbiguousConstructor(t *testing.T) { - c := NewContainer() - c.MustProvide(newTestConstructor(newTestObject1)) - err := c.Provide(newTestConstructor(newTestObject1)) +func TestContainer_Provide__AmbiguousConstructor(t *testing.T) { + ctr := NewContainer() + ctr.MustProvide(newTestConstructor(newTestObject1)) + err := ctr.Provide(newTestConstructor(newTestObject1)) t.Logf("%+v", err) if kerror.ClassOf(err) != kerror.EAmbiguous { t.Fail() @@ -102,9 +105,9 @@ func TestContainer_ProvideWithAmbiguousConstructor(t *testing.T) { } } -func TestContainer_ApplyWithNilProcessor(t *testing.T) { - c := NewContainer() - err := c.Apply(nil) +func TestContainer_Attach__NilProcessor(t *testing.T) { + ctr := NewContainer() + err := ctr.Attach(nil) t.Logf("%+v", err) if kerror.ClassOf(err) != kerror.EInvalid { t.Fail() @@ -112,9 +115,9 @@ func TestContainer_ApplyWithNilProcessor(t *testing.T) { } } -func TestContainer_ApplyWithBrokenProcessor(t *testing.T) { - c := NewContainer() - err := c.Apply(testBrokenProcessor{}) +func TestContainer_Attach__ProcessorWithBrokenType(t *testing.T) { + ctr := NewContainer() + err := ctr.Attach(testProcessorWithBrokenType{}) t.Logf("%+v", err) if kerror.ClassOf(err) != kerror.EInvalid { t.Fail() @@ -122,9 +125,9 @@ func TestContainer_ApplyWithBrokenProcessor(t *testing.T) { } } -func TestContainer_InvokeWithNilExecutor(t *testing.T) { - c := NewContainer() - err := c.Invoke(nil) +func TestContainer_Run__NilFunctor(t *testing.T) { + ctr := NewContainer() + err := ctr.Run(nil) t.Logf("%+v", err) if kerror.ClassOf(err) != kerror.EInvalid { t.Fail() @@ -132,9 +135,27 @@ func TestContainer_InvokeWithNilExecutor(t *testing.T) { } } -func TestContainer_InvokeWithNilBootstrapper(t *testing.T) { - c := NewContainer() - err := c.Invoke(newTestExecutor(func() (Executor, error) { return nil, nil }), nil) +func TestContainer_Run__ErrorProneFunctor(t *testing.T) { + ctr := NewContainer() + err := ctr.Run( + newTestFunctor(func() ([]Functor, error) { + return nil, kerror.New(kerror.ECustom, "test error") + }), + ) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.ECustom { + t.Fail() + return + } +} + +func TestContainer_Run__NilSubsequentFunctor(t *testing.T) { + ctr := NewContainer() + err := ctr.Run( + newTestFunctor(func() ([]Functor, error) { + return []Functor{nil}, nil + }), + ) t.Logf("%+v", err) if kerror.ClassOf(err) != kerror.EInvalid { t.Fail() @@ -142,14 +163,65 @@ func TestContainer_InvokeWithNilBootstrapper(t *testing.T) { } } -func TestContainer_InvokeWithBrokenGraph(t *testing.T) { - c := NewContainer() - c.MustProvide(newTestConstructor(newTestObject1)) - err := c.Invoke( - newTestExecutor(func(*testObject1) (Executor, error) { +func TestContainer_Run__ErrorProneSubsequentFunctor(t *testing.T) { + ctr := NewContainer() + err := ctr.Run( + newTestFunctor(func() ([]Functor, error) { + return []Functor{newTestFunctor(func() ([]Functor, error) { + return nil, kerror.New(kerror.ECustom, "test error") + })}, nil + }), + ) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.ECustom { + t.Fail() + return + } +} + +func TestContainer_Run__ErrorProneConstructor(t *testing.T) { + ctr := NewContainer() + ctr.MustProvide(newTestConstructor(func() (int, kdone.Destructor, error) { + return 0, kdone.Noop, kerror.New(kerror.ECustom, "test error") + })) + err := ctr.Run( + newTestFunctor(func(int) ([]Functor, error) { + return nil, nil + }), + ) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.ECustom { + t.Fail() + return + } +} + +func TestContainer_Run__ErrorProneProcessor(t *testing.T) { + ctr := NewContainer() + ctr.MustProvide(newTestConstructor(func() (int, kdone.Destructor, error) { + return 1, kdone.Noop, nil + })) + ctr.MustAttach(newTestProcessor(func(int) error { + return kerror.New(kerror.ECustom, "test error") + })) + err := ctr.Run( + newTestFunctor(func(int) ([]Functor, error) { + return nil, nil + }), + ) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.ECustom { + t.Fail() + return + } +} + +func TestContainer_Run__BrokenGraph(t *testing.T) { + ctr := NewContainer() + err := ctr.Run( + newTestFunctor(func(int) ([]Functor, error) { return nil, nil }), - newTestBootstrapper(t), ) t.Logf("%+v", err) if kerror.ClassOf(err) != kerror.ENotFound { @@ -158,38 +230,87 @@ func TestContainer_InvokeWithBrokenGraph(t *testing.T) { } } +func TestContainer_Run__ConstructorWithBrokenParameters(t *testing.T) { + ctr := NewContainer() + ctr.MustProvide(testConstructorWithBrokenParameters{}) + err := ctr.Run( + newTestFunctor(func(int) ([]Functor, error) { + return nil, nil + }), + ) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EInvalid { + t.Fail() + return + } +} + +func TestContainer_Run__ConstructorWithBrokenDestructor(t *testing.T) { + ctr := NewContainer() + ctr.MustProvide(testConstructorWithBrokenDestructor{}) + err := ctr.Run( + newTestFunctor(func(int) ([]Functor, error) { + return nil, nil + }), + ) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EInvalid { + t.Fail() + return + } +} + +func TestContainer_Run__ProcessorWithBrokenParameters(t *testing.T) { + ctr := NewContainer() + ctr.MustProvide(newTestConstructor(func() (int, kdone.Destructor, error) { + return 1, kdone.Noop, nil + })) + ctr.MustAttach(testProcessorWithBrokenParameters{}) + err := ctr.Run( + newTestFunctor(func(int) ([]Functor, error) { + return nil, nil + }), + ) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EInvalid { + t.Fail() + return + } +} + +func TestContainer_Run__FunctorWithBrokenParameters(t *testing.T) { + ctr := NewContainer() + err := ctr.Run(testFunctorWithBrokenParameters{}) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EInvalid { + t.Fail() + return + } +} + func TestNilContainer_Provide(t *testing.T) { - defer func() { - v := recover() - t.Logf("%+v", v) - if v == nil { - t.Fail() - return - } - }() - _ = (*Container)(nil).Provide(newTestConstructor(newTestObject1)) + err := (*Container)(nil).Provide(newTestConstructor(newTestObject1)) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.ENil { + t.Fail() + return + } } -func TestNilContainer_Apply(t *testing.T) { - defer func() { - v := recover() - t.Logf("%+v", v) - if v == nil { - t.Fail() - return - } - }() - _ = (*Container)(nil).Apply(newTestProcessor(processTestCounter)) +func TestNilContainer_Attach(t *testing.T) { + err := (*Container)(nil).Attach(newTestProcessor(processTestCounter)) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.ENil { + t.Fail() + return + } } -func TestNilContainer_Invoke(t *testing.T) { - defer func() { - v := recover() - t.Logf("%+v", v) - if v == nil { - t.Fail() - return - } - }() - _ = (*Container)(nil).Invoke(newTestExecutor(func() (Executor, error) { return nil, nil })) +func TestNilContainer_Run(t *testing.T) { + err := (*Container)(nil).Run() + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.ENil { + t.Fail() + return + } } diff --git a/declaration.go b/declaration.go index 5c548fb..a42fc42 100644 --- a/declaration.go +++ b/declaration.go @@ -6,8 +6,8 @@ import "github.com/go-kata/kerror" type Declaration struct { // functions specifies declared functions. functions []func() error - // performed specifies whether were the declared functions called. - performed bool + // fulfilled specifies whether were the declared functions called. + fulfilled bool } // NewDeclaration returns a new declaration. @@ -37,14 +37,13 @@ func (d *Declaration) MustDeclare(f func()) { // DeclareErrorProne appends the given function to this declaration. func (d *Declaration) DeclareErrorProne(f func() error) error { if d == nil { - kerror.NPE() - return nil + return kerror.New(kerror.ENil, "nil declaration cannot declare function") } - if d.performed { - return kerror.New(kerror.EIllegal, "declared functions already called") + if d.fulfilled { + return kerror.New(kerror.EIllegal, "declaration has already called declared functions") } if f == nil { - return kerror.New(kerror.EInvalid, "nil function cannot be declared") + return kerror.New(kerror.EInvalid, "declaration cannot declare nil function") } d.functions = append(d.functions, f) return nil @@ -57,15 +56,17 @@ func (d *Declaration) MustDeclareErrorProne(f func() error) { } } -// Perform calls declared functions. -func (d *Declaration) Perform() error { +// Fulfill calls declared functions. +func (d *Declaration) Fulfill() error { if d == nil { return nil } - if d.performed { - return kerror.New(kerror.EIllegal, "declared functions already called") + if d.fulfilled { + return kerror.New(kerror.EIllegal, "declaration has already called declared functions") } - d.performed = true + defer func() { + d.fulfilled = true + }() for _, f := range d.functions { if err := f(); err != nil { return err @@ -74,9 +75,17 @@ func (d *Declaration) Perform() error { return nil } -// MustPerform is a variant of the Perform that panics on error. -func (d *Declaration) MustPerform() { - if err := d.Perform(); err != nil { +// MustFulfill is a variant of the Fulfill that panics on error. +func (d *Declaration) MustFulfill() { + if err := d.Fulfill(); err != nil { panic(err) } } + +// Fulfilled returns boolean whether were the declared functions called. +func (d *Declaration) Fulfilled() bool { + if d == nil { + return false + } + return d.fulfilled +} diff --git a/declaration_test.go b/declaration_test.go index 397a654..069f630 100644 --- a/declaration_test.go +++ b/declaration_test.go @@ -14,7 +14,7 @@ func TestDeclaration(t *testing.T) { t.Fail() return } - if err := d.Perform(); err != nil { + if err := d.Fulfill(); err != nil { t.Logf("%+v", err) t.Fail() return @@ -25,7 +25,7 @@ func TestDeclaration(t *testing.T) { } } -func TestDeclarationWithError(t *testing.T) { +func TestDeclaration__Error(t *testing.T) { d := NewDeclaration() var c int d.MustDeclareErrorProne(func() error { @@ -33,7 +33,7 @@ func TestDeclarationWithError(t *testing.T) { return kerror.New(nil, "test error") }) d.MustDeclare(func() { c++ }) - err := d.Perform() + err := d.Fulfill() t.Logf("%+v", err) if err == nil { t.Fail() @@ -45,7 +45,7 @@ func TestDeclarationWithError(t *testing.T) { } } -func TestDeclaration_DeclareWithNilFunction(t *testing.T) { +func TestDeclaration_Declare__NilFunction(t *testing.T) { d := NewDeclaration() err := d.Declare(nil) t.Logf("%+v", err) @@ -55,7 +55,18 @@ func TestDeclaration_DeclareWithNilFunction(t *testing.T) { } } -func TestDeclaration_DeclareErrorProneWithNilFunction(t *testing.T) { +func TestDeclaration_Declare__Fulfilled(t *testing.T) { + d := NewDeclaration() + d.MustFulfill() + err := d.Declare(func() {}) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EIllegal { + t.Fail() + return + } +} + +func TestDeclaration_DeclareErrorProne__NilFunction(t *testing.T) { d := NewDeclaration() err := d.DeclareErrorProne(nil) t.Logf("%+v", err) @@ -65,10 +76,10 @@ func TestDeclaration_DeclareErrorProneWithNilFunction(t *testing.T) { } } -func TestDeclaration_DeclareWhenPerformed(t *testing.T) { +func TestDeclaration_DeclareErrorProne__Fulfilled(t *testing.T) { d := NewDeclaration() - d.MustPerform() - err := d.Declare(func() {}) + d.MustFulfill() + err := d.DeclareErrorProne(func() error { return nil }) t.Logf("%+v", err) if kerror.ClassOf(err) != kerror.EIllegal { t.Fail() @@ -76,10 +87,10 @@ func TestDeclaration_DeclareWhenPerformed(t *testing.T) { } } -func TestDeclaration_PerformWhenPerformed(t *testing.T) { +func TestDeclaration_Fulfill__Fulfilled(t *testing.T) { d := NewDeclaration() - d.MustPerform() - err := d.Perform() + d.MustFulfill() + err := d.Fulfill() t.Logf("%+v", err) if kerror.ClassOf(err) != kerror.EIllegal { t.Fail() @@ -87,34 +98,44 @@ func TestDeclaration_PerformWhenPerformed(t *testing.T) { } } +func TestDeclaration_Fulfilled(t *testing.T) { + d := NewDeclaration() + d.MustFulfill() + if !d.Fulfilled() { + t.Fail() + return + } +} + func TestNilDeclaration_Declare(t *testing.T) { - defer func() { - v := recover() - t.Logf("%+v", v) - if v == nil { - t.Fail() - return - } - }() - _ = (*Declaration)(nil).Declare(func() {}) + err := (*Declaration)(nil).Declare(func() {}) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.ENil { + t.Fail() + return + } } func TestNilDeclaration_DeclareErrorProne(t *testing.T) { - defer func() { - v := recover() - t.Logf("%+v", v) - if v == nil { - t.Fail() - return - } - }() - _ = (*Declaration)(nil).DeclareErrorProne(func() error { return nil }) + err := (*Declaration)(nil).DeclareErrorProne(func() error { return nil }) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.ENil { + t.Fail() + return + } } -func TestNilDeclaration_Perform(t *testing.T) { - if err := (*Declaration)(nil).Perform(); err != nil { +func TestNilDeclaration_Fulfill(t *testing.T) { + if err := (*Declaration)(nil).Fulfill(); err != nil { t.Logf("%+v", err) t.Fail() return } } + +func TestNilDeclaration_Fulfilled(t *testing.T) { + if (*Declaration)(nil).Fulfilled() { + t.Fail() + return + } +} diff --git a/functor.go b/functor.go new file mode 100644 index 0000000..cf002f8 --- /dev/null +++ b/functor.go @@ -0,0 +1,13 @@ +package kinit + +import "reflect" + +// Functor represents a function. +// +// The usual identifier for variables of this type is fun. +type Functor interface { + // Parameters returns types of objects this functor depends on. + Parameters() []reflect.Type + // Call calls a function and may return further functors. + Call(a ...reflect.Value) ([]Functor, error) +} diff --git a/functor_test.go b/functor_test.go new file mode 100644 index 0000000..5505870 --- /dev/null +++ b/functor_test.go @@ -0,0 +1,50 @@ +package kinit + +import "reflect" + +// func(...) ([]Functor, error) + +type testFunctor struct { + f reflect.Value + in []reflect.Type +} + +func newTestFunctor(x interface{}) *testFunctor { + ft := reflect.TypeOf(x) + r := &testFunctor{ + f: reflect.ValueOf(x), + } + numIn := ft.NumIn() + r.in = make([]reflect.Type, numIn) + for i := 0; i < numIn; i++ { + r.in[i] = ft.In(i) + } + return r +} + +func (f *testFunctor) Parameters() []reflect.Type { + return f.in +} + +func (f *testFunctor) Call(a ...reflect.Value) ([]Functor, error) { + out := f.f.Call(a) + var further []Functor + if v := out[0].Interface(); v != nil { + further = v.([]Functor) + } + var err error + if v := out[1].Interface(); v != nil { + err = v.(error) + } + return further, err +} + +type testFunctorWithBrokenParameters struct{} + +func (testFunctorWithBrokenParameters) Parameters() []reflect.Type { + return []reflect.Type{nil} +} + +func (testFunctorWithBrokenParameters) Call(a ...reflect.Value) ([]Functor, error) { + return nil, nil +} diff --git a/go.mod b/go.mod index b3ab2ce..a9bb802 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,6 @@ module github.com/go-kata/kinit go 1.14 require ( - github.com/go-kata/kdone v0.2.4 - github.com/go-kata/kerror v0.1.4 + github.com/go-kata/kdone v0.2.8 + github.com/go-kata/kerror v0.3.0 ) diff --git a/go.sum b/go.sum index 0161346..64c2a57 100644 --- a/go.sum +++ b/go.sum @@ -12,6 +12,14 @@ github.com/go-kata/kdone v0.2.3 h1:V2o/5C6YFponu0IIng3GlwWp5WkoP0w6NlIOeK0NpV4= github.com/go-kata/kdone v0.2.3/go.mod h1:0dzf9X5WunfvCVP+wAf+Ol3SUDi4+6nu9OCKGd9nt3A= github.com/go-kata/kdone v0.2.4 h1:2GvvuwoMzzz33naFzMkwvt0zh13XSCJfTWzIAQZmOmY= github.com/go-kata/kdone v0.2.4/go.mod h1:zofqcVtBiDwMdxH7m3Cs+Vrc65aMSnAPF3EJQTr5coc= +github.com/go-kata/kdone v0.2.5 h1:B+7P6mUxtTsyE5jx4a0nLXEREehndm+GgH0Q16E/1TI= +github.com/go-kata/kdone v0.2.5/go.mod h1:+OuFTRN7AXO8+xJ99z6UQEiGZ4NdwjC8wl147kYjrYU= +github.com/go-kata/kdone v0.2.6 h1:VyQZCHDZITVFFLSV1ne1HUGhPOrq1o8vLwSE1MQXqkM= +github.com/go-kata/kdone v0.2.6/go.mod h1:+OuFTRN7AXO8+xJ99z6UQEiGZ4NdwjC8wl147kYjrYU= +github.com/go-kata/kdone v0.2.7 h1:ecCxYSxGs1R1ufCT0dhBzBCKFbNOt3/BB5H51BvP1ks= +github.com/go-kata/kdone v0.2.7/go.mod h1:SukeO0s1xGgnGIvfrzJMiUHLGC1KYpR9X4pkrRKCWtM= +github.com/go-kata/kdone v0.2.8 h1:vupfF3Nwwsy0X5z8YWlcagHvG6or3bxO4pMkhSHnIXw= +github.com/go-kata/kdone v0.2.8/go.mod h1:waAaVCVOlZRiL4Xf/valgVJe8KvWcnKTkRogPeU0QlQ= github.com/go-kata/kerror v0.1.0 h1:Y7y0XGOrhWMU1YxvMAYw5VAeDfo4wUZrhLdSQ+KM7LE= github.com/go-kata/kerror v0.1.0/go.mod h1:TtwtjetJ75COpTfrJBL9y2q4MEbDWt1r/FeF5M+5xlw= github.com/go-kata/kerror v0.1.1 h1:9RMFNpTzN0HxOZHI2Fm0FBRgx2i+0UkQzIMVvyO33Vs= @@ -22,3 +30,9 @@ github.com/go-kata/kerror v0.1.3 h1:7FXsN5i4tsg9+ZxKV5baflj32r6iRr9uwpsj//3zrGQ= github.com/go-kata/kerror v0.1.3/go.mod h1:TtwtjetJ75COpTfrJBL9y2q4MEbDWt1r/FeF5M+5xlw= github.com/go-kata/kerror v0.1.4 h1:IQ18UqnrSOjG+dfbNLYkeiKsQHdsVP0BQLEkv/QoyQU= github.com/go-kata/kerror v0.1.4/go.mod h1:TtwtjetJ75COpTfrJBL9y2q4MEbDWt1r/FeF5M+5xlw= +github.com/go-kata/kerror v0.2.0 h1:UlwoVTG5m8C+1k3znFd93G45joyJGdxTTGNYK7cdriU= +github.com/go-kata/kerror v0.2.0/go.mod h1:TtwtjetJ75COpTfrJBL9y2q4MEbDWt1r/FeF5M+5xlw= +github.com/go-kata/kerror v0.2.1 h1:fE5KsH5Dntrhmv1hWdc7/BActYUHkCPl7CjQI5kBfjA= +github.com/go-kata/kerror v0.2.1/go.mod h1:TtwtjetJ75COpTfrJBL9y2q4MEbDWt1r/FeF5M+5xlw= +github.com/go-kata/kerror v0.3.0 h1:UwpL0r2Y8s1r8HOcPm0wy4tVHkKDItW64MwZYVmJLKc= +github.com/go-kata/kerror v0.3.0/go.mod h1:TtwtjetJ75COpTfrJBL9y2q4MEbDWt1r/FeF5M+5xlw= diff --git a/kinit.go b/kinit.go index b33feaf..1439668 100644 --- a/kinit.go +++ b/kinit.go @@ -1,36 +1,4 @@ // Package kinit provides tools for creating objects. -// -// In contrast to the Wire (github.com/google/wire) the dependency injection mechanism provided by this package -// doesn't require any codegeneration and relies on the reflection. The dependency injection is a process that -// takes place only once on a program startup in the vast majority of cases and the low performance of reflection -// in this case is not crucial. On other hand this solution gives more control over a program on debug (because -// it includes injection process itself) and doesn't divide program to the real code and configuration. Also it -// makes the dependency injection process more customizable thanks to interfaces. -// -// However, taking in account that the reflection is slow, it is better to use declared function provided by -// this package instead of raw init functions to fill up the global container in libraries. It avoids slow -// reflection calls when library entities are used manually. -// -// You may divide resolving of the dependency graph into steps using the executors chaining. It makes possible -// to chose the next step depending on some conditions, e.g. depends on what command of console program is -// executed for now or what module is mounted to the extension point of framework. -// -// Objects are uniquely identified by container using reflection of their types. Default implementations of -// interfaces from this package based on the reflect.Type interface directly are provided by the -// github.com/go-kata/kinitx package. To extend the objects identification (for example, with custom names -// like in the github.com/uber-go/dig) you may write your own implementation of the type reflection like a -// -// type NamedType struct { -// reflect.Type -// Name string -// } -// -// and then use it as a base of your own implementations of package interfaces. -// -// Objects are created when container is invoked and destroyed (finalized) at the end of invocation. Object is -// destroyed using it's destructor returning by it's constructor on creation. You may be sure that all destructors -// of correctly created objects will be guaranteed called in a correct order even in case of panic at any step of -// the container invocation. package kinit // globalDeclaration specifies the global declaration. @@ -77,30 +45,32 @@ func MustProvide(ctor Constructor) { } } -// Apply calls the Apply method of the global container. -func Apply(proc Processor) error { - return globalContainer.Apply(proc) +// Attach calls the Attach method of the global container. +func Attach(proc Processor) error { + return globalContainer.Attach(proc) } -// MustApply is a variant of the Apply that panics on error. -func MustApply(proc Processor) { - if err := Apply(proc); err != nil { +// MustAttach is a variant of the Attach that panics on error. +func MustAttach(proc Processor) { + if err := Attach(proc); err != nil { panic(err) } } -// Invoke calls declared functions if not called yet and then -// calls the Invoke method of the global container. -func Invoke(exec Executor, bootstrappers ...Bootstrapper) error { - if err := globalDeclaration.Perform(); err != nil { - return err +// Run calls declared functions if not called yet and then +// calls the Run method of the global container. +func Run(functors ...Functor) error { + if !globalDeclaration.Fulfilled() { + if err := globalDeclaration.Fulfill(); err != nil { + return err + } } - return globalContainer.Invoke(exec, bootstrappers...) + return globalContainer.Run(functors...) } -// MustInvoke is a variant of the Invoke that panics on error. -func MustInvoke(exec Executor, bootstrappers ...Bootstrapper) { - if err := Invoke(exec, bootstrappers...); err != nil { +// MustRun is a variant of the Run that panics on error. +func MustRun(functors ...Functor) { + if err := Run(functors...); err != nil { panic(err) } } diff --git a/kinit_test.go b/kinit_test.go index 7f6e5bf..91046a2 100644 --- a/kinit_test.go +++ b/kinit_test.go @@ -1,129 +1,91 @@ package kinit -import "testing" +import ( + "testing" -func TestDeclareWithNilFunction(t *testing.T) { + "github.com/go-kata/kerror" +) + +func TestDeclare__NilFunction(t *testing.T) { err := Declare(nil) t.Logf("%+v", err) - if err == nil { + if kerror.ClassOf(err) != kerror.EInvalid { t.Fail() return } } -func TestMustDeclareWithNilFunction(t *testing.T) { - defer func() { - v := recover() - t.Logf("%+v", v) - if v == nil { - t.Fail() - return - } - }() - MustDeclare(nil) +func TestMustDeclare__NilFunction(t *testing.T) { + err := kerror.Try(func() error { + MustDeclare(nil) + return nil + }) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EInvalid { + t.Fail() + return + } } -func TestDeclareErrorProneWithNilFunction(t *testing.T) { +func TestDeclareErrorProne__NilFunction(t *testing.T) { err := DeclareErrorProne(nil) t.Logf("%+v", err) - if err == nil { + if kerror.ClassOf(err) != kerror.EInvalid { t.Fail() return } } -func TestMustDeclareErrorProneWithNilFunction(t *testing.T) { - defer func() { - v := recover() - t.Logf("%+v", v) - if v == nil { - t.Fail() - return - } - }() - MustDeclareErrorProne(nil) +func TestMustDeclareErrorProne__NilFunction(t *testing.T) { + err := kerror.Try(func() error { + MustDeclareErrorProne(nil) + return nil + }) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EInvalid { + t.Fail() + return + } } -func TestProvideWithNilConstructor(t *testing.T) { +func TestProvide__NilConstructor(t *testing.T) { err := Provide(nil) t.Logf("%+v", err) - if err == nil { + if kerror.ClassOf(err) != kerror.EInvalid { t.Fail() return } } -func TestMustProvideWithNilConstructor(t *testing.T) { - defer func() { - v := recover() - t.Logf("%+v", v) - if v == nil { - t.Fail() - return - } - }() - MustProvide(nil) -} - -func TestApplyWithNilConstructor(t *testing.T) { - err := Apply(nil) +func TestMustProvide__NilConstructor(t *testing.T) { + err := kerror.Try(func() error { + MustProvide(nil) + return nil + }) t.Logf("%+v", err) - if err == nil { + if kerror.ClassOf(err) != kerror.EInvalid { t.Fail() return } } -func TestMustApplyWithNilProcessor(t *testing.T) { - defer func() { - v := recover() - t.Logf("%+v", v) - if v == nil { - t.Fail() - return - } - }() - MustApply(nil) -} - -func TestInvokeWithNilExecutor(t *testing.T) { - err := Invoke(nil) +func TestAttach__NilProcessor(t *testing.T) { + err := Attach(nil) t.Logf("%+v", err) - if err == nil { + if kerror.ClassOf(err) != kerror.EInvalid { t.Fail() return } } -func TestInvokeWithNilBootstrapper(t *testing.T) { - err := Invoke(newTestExecutor(func() (Executor, error) { return nil, nil }), nil) +func TestMustAttach__NilProcessor(t *testing.T) { + err := kerror.Try(func() error { + MustAttach(nil) + return nil + }) t.Logf("%+v", err) - if err == nil { + if kerror.ClassOf(err) != kerror.EInvalid { t.Fail() return } } - -func TestMustInvokeWithNilExecutor(t *testing.T) { - defer func() { - v := recover() - t.Logf("%+v", v) - if v == nil { - t.Fail() - return - } - }() - MustInvoke(nil) -} - -func TestMustInvokeWithNilBootstrapper(t *testing.T) { - defer func() { - v := recover() - t.Logf("%+v", v) - if v == nil { - t.Fail() - return - } - }() - MustInvoke(newTestExecutor(func() (Executor, error) { return nil, nil }), nil) -} diff --git a/processor_test.go b/processor_test.go index 2b6795d..e515718 100644 --- a/processor_test.go +++ b/processor_test.go @@ -1,8 +1,6 @@ package kinit -import ( - "reflect" -) +import "reflect" // func(T, ...) error @@ -43,16 +41,30 @@ func (p *testProcessor) Process(obj reflect.Value, a ...reflect.Value) error { return err } -type testBrokenProcessor struct{} +type testProcessorWithBrokenType struct{} -func (testBrokenProcessor) Type() reflect.Type { +func (testProcessorWithBrokenType) Type() reflect.Type { return nil } -func (testBrokenProcessor) Parameters() []reflect.Type { +func (testProcessorWithBrokenType) Parameters() []reflect.Type { return nil } -func (testBrokenProcessor) Process(obj reflect.Value, a ...reflect.Value) error { +func (testProcessorWithBrokenType) Process(obj reflect.Value, a ...reflect.Value) error { + return nil +} + +type testProcessorWithBrokenParameters struct{} + +func (testProcessorWithBrokenParameters) Type() reflect.Type { + return reflect.TypeOf(1) +} + +func (testProcessorWithBrokenParameters) Parameters() []reflect.Type { + return []reflect.Type{nil} +} + +func (testProcessorWithBrokenParameters) Process(obj reflect.Value, a ...reflect.Value) error { return nil } diff --git a/runtime.go b/runtime.go new file mode 100644 index 0000000..62623e2 --- /dev/null +++ b/runtime.go @@ -0,0 +1,81 @@ +package kinit + +import ( + "reflect" + + "github.com/go-kata/kdone" + "github.com/go-kata/kerror" +) + +// Runtime represents a runtime. +type Runtime struct { + // container specifies the container associated with this runtime. + container *Container + // arena specifies the arena associated with this runtime. + arena *Arena +} + +// NewRuntime returns a new runtime associated with given container and arena. +func NewRuntime(ctr *Container, arena *Arena) (*Runtime, error) { + if ctr == nil { + return nil, kerror.New(kerror.EInvalid, "runtime cannot be associated with nil container") + } + if arena == nil { + return nil, kerror.New(kerror.EInvalid, "runtime cannot be associated with nil arena") + } + return &Runtime{ + container: ctr, + arena: arena, + }, nil +} + +// MustNewRuntime is a variant of the NewRuntime that panics on error. +func MustNewRuntime(ctr *Container, arena *Arena) *Runtime { + r, err := NewRuntime(ctr, arena) + if err != nil { + panic(err) + } + return r +} + +// Register registers the given object on the associated arena. +func (r *Runtime) Register(t reflect.Type, obj reflect.Value, dtor kdone.Destructor) error { + if r == nil { + return kerror.New(kerror.ENil, "nil runtime cannot register object") + } + return r.arena.Register(t, obj, dtor) +} + +// MustRegister is a variant of the Register that panics on error. +func (r *Runtime) MustRegister(t reflect.Type, obj reflect.Value, dtor kdone.Destructor) { + if err := r.Register(t, obj, dtor); err != nil { + panic(err) + } +} + +// Run runs given functors using the associated container. +// The created separate arena will use the associated arena as a parent. +func (r *Runtime) Run(functors ...Functor) (err error) { + if r == nil { + return kerror.New(kerror.ENil, "nil runtime cannot run functors") + } + arena := NewArena(r.arena) + defer func() { + err = kerror.Join(err, arena.Finalize()) + }() + runtime, err := NewRuntime(r.container, arena) + if err != nil { + return err + } + if err := arena.Register(reflect.TypeOf(runtime), reflect.ValueOf(runtime), kdone.Noop); err != nil { + return err + } + return r.container.run(arena, functors...) +} + +// MustRun is a variant of Run that panics on error. +func (r *Runtime) MustRun(functors ...Functor) { + if err := r.Run(functors...); err != nil { + panic(err) + } +} diff --git a/runtime_test.go b/runtime_test.go new file mode 100644 index 0000000..19529b0 --- /dev/null +++ b/runtime_test.go @@ -0,0 +1,80 @@ +package kinit + +import ( + "reflect" + "testing" + + "github.com/go-kata/kdone" + "github.com/go-kata/kerror" +) + +func TestRuntime(t *testing.T) { + var c int + ctr := NewContainer() + ctr.MustProvide(newTestConstructor(func() (int32, kdone.Destructor, error) { + c++ + return int32(c), kdone.Noop, nil + })) + ctr.MustProvide(newTestConstructor(func() (int64, kdone.Destructor, error) { + c++ + return int64(c), kdone.Noop, nil + })) + arena := NewArena() + defer arena.MustFinalize() + runtime := MustNewRuntime(ctr, arena) + runtime.MustRegister(reflect.TypeOf(t), reflect.ValueOf(t), kdone.Noop) + runtime.MustRun(newTestFunctor(func(innerRuntime1 *Runtime, i32 int32) ([]Functor, error) { + if i32 != 1 { + return nil, kerror.Newf(kerror.EInvalid, "int32: %d expected, %d given", 1, i32) + } + innerRuntime1.MustRegister(reflect.TypeOf(t), reflect.ValueOf(t), kdone.Noop) + innerRuntime1.MustRun(newTestFunctor(func(innerRuntime2 *Runtime, i32 int32, i64 int64) ([]Functor, error) { + if i32 != 1 { + return nil, kerror.Newf(kerror.EInvalid, "int32: %d expected, %d given", 1, i32) + } + if i64 != 2 { + return nil, kerror.Newf(kerror.EInvalid, "int64: %d expected, %d given", 2, i64) + } + innerRuntime2.MustRegister(reflect.TypeOf(t), reflect.ValueOf(t), kdone.Noop) + return nil, nil + })) + return nil, nil + })) +} + +func TestNewRuntime__NilContainer(t *testing.T) { + _, err := NewRuntime(nil, NewArena()) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EInvalid { + t.Fail() + return + } +} + +func TestNewRuntime__NilArena(t *testing.T) { + _, err := NewRuntime(NewContainer(), nil) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EInvalid { + t.Fail() + return + } +} + +func TestNilRuntime_Register(t *testing.T) { + x := 1 + err := (*Runtime)(nil).Register(reflect.TypeOf(x), reflect.ValueOf(x), kdone.Noop) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.ENil { + t.Fail() + return + } +} + +func TestNilRuntime_Run(t *testing.T) { + err := (*Runtime)(nil).Run() + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.ENil { + t.Fail() + return + } +} diff --git a/tests/TestMustRun__ErrorProneDeclaredFunction/pkg.go b/tests/TestMustRun__ErrorProneDeclaredFunction/pkg.go new file mode 100644 index 0000000..870f097 --- /dev/null +++ b/tests/TestMustRun__ErrorProneDeclaredFunction/pkg.go @@ -0,0 +1,3 @@ +package pkg + +// This package is for testing purposes only. diff --git a/tests/TestMustRun__ErrorProneDeclaredFunction/pkg_test.go b/tests/TestMustRun__ErrorProneDeclaredFunction/pkg_test.go new file mode 100644 index 0000000..0d87d28 --- /dev/null +++ b/tests/TestMustRun__ErrorProneDeclaredFunction/pkg_test.go @@ -0,0 +1,23 @@ +package pkg + +import ( + "testing" + + "github.com/go-kata/kerror" + "github.com/go-kata/kinit" +) + +func TestRun__ErrorProneDeclaredFunction(t *testing.T) { + kinit.MustDeclareErrorProne(func() error { + return kerror.New(kerror.ECustom, "test error") + }) + err := kerror.Try(func() error { + kinit.MustRun() + return nil + }) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.ECustom { + t.Fail() + return + } +} diff --git a/tests/TestMustRun__NilFunctor/pkg.go b/tests/TestMustRun__NilFunctor/pkg.go new file mode 100644 index 0000000..870f097 --- /dev/null +++ b/tests/TestMustRun__NilFunctor/pkg.go @@ -0,0 +1,3 @@ +package pkg + +// This package is for testing purposes only. diff --git a/tests/TestMustRun__NilFunctor/pkg_test.go b/tests/TestMustRun__NilFunctor/pkg_test.go new file mode 100644 index 0000000..eca670c --- /dev/null +++ b/tests/TestMustRun__NilFunctor/pkg_test.go @@ -0,0 +1,20 @@ +package pkg + +import ( + "testing" + + "github.com/go-kata/kerror" + "github.com/go-kata/kinit" +) + +func TestMustRun__NilFunctor(t *testing.T) { + err := kerror.Try(func() error { + kinit.MustRun(nil) + return nil + }) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EInvalid { + t.Fail() + return + } +} diff --git a/tests/TestRun__ErrorProneDeclaredFunction/pkg.go b/tests/TestRun__ErrorProneDeclaredFunction/pkg.go new file mode 100644 index 0000000..870f097 --- /dev/null +++ b/tests/TestRun__ErrorProneDeclaredFunction/pkg.go @@ -0,0 +1,3 @@ +package pkg + +// This package is for testing purposes only. diff --git a/tests/TestRun__ErrorProneDeclaredFunction/pkg_test.go b/tests/TestRun__ErrorProneDeclaredFunction/pkg_test.go new file mode 100644 index 0000000..a953d56 --- /dev/null +++ b/tests/TestRun__ErrorProneDeclaredFunction/pkg_test.go @@ -0,0 +1,20 @@ +package pkg + +import ( + "testing" + + "github.com/go-kata/kerror" + "github.com/go-kata/kinit" +) + +func TestRun__ErrorProneDeclaredFunction(t *testing.T) { + kinit.MustDeclareErrorProne(func() error { + return kerror.New(kerror.ECustom, "test error") + }) + err := kinit.Run() + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.ECustom { + t.Fail() + return + } +} diff --git a/tests/TestRun__NilFunctor/pkg.go b/tests/TestRun__NilFunctor/pkg.go new file mode 100644 index 0000000..870f097 --- /dev/null +++ b/tests/TestRun__NilFunctor/pkg.go @@ -0,0 +1,3 @@ +package pkg + +// This package is for testing purposes only. diff --git a/tests/TestRun__NilFunctor/pkg_test.go b/tests/TestRun__NilFunctor/pkg_test.go new file mode 100644 index 0000000..2844555 --- /dev/null +++ b/tests/TestRun__NilFunctor/pkg_test.go @@ -0,0 +1,17 @@ +package pkg + +import ( + "testing" + + "github.com/go-kata/kerror" + "github.com/go-kata/kinit" +) + +func TestRun__NilFunctor(t *testing.T) { + err := kinit.Run(nil) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EInvalid { + t.Fail() + return + } +} diff --git a/tests/zzz_TestInvoke__NilBootstrapper/pkg.go b/tests/zzz_TestInvoke__NilBootstrapper/pkg.go new file mode 100644 index 0000000..870f097 --- /dev/null +++ b/tests/zzz_TestInvoke__NilBootstrapper/pkg.go @@ -0,0 +1,3 @@ +package pkg + +// This package is for testing purposes only. diff --git a/tests/zzz_TestInvoke__NilBootstrapper/pkg_test.go b/tests/zzz_TestInvoke__NilBootstrapper/pkg_test.go new file mode 100644 index 0000000..da8d6c5 --- /dev/null +++ b/tests/zzz_TestInvoke__NilBootstrapper/pkg_test.go @@ -0,0 +1,28 @@ +package pkg + +import ( + "reflect" + "testing" + + "github.com/go-kata/kerror" + "github.com/go-kata/kinit" +) + +type testExecutor struct{} + +func (testExecutor) Parameters() []reflect.Type { + return nil +} + +func (testExecutor) Execute(a ...reflect.Value) (kinit.Executor, error) { + return nil, nil +} + +func TestInvoke__NilBootstrapper(t *testing.T) { + err := kinit.Invoke(testExecutor{}, nil) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EInvalid { + t.Fail() + return + } +} diff --git a/tests/zzz_TestInvoke__NilExecutor/pkg.go b/tests/zzz_TestInvoke__NilExecutor/pkg.go new file mode 100644 index 0000000..870f097 --- /dev/null +++ b/tests/zzz_TestInvoke__NilExecutor/pkg.go @@ -0,0 +1,3 @@ +package pkg + +// This package is for testing purposes only. diff --git a/tests/zzz_TestInvoke__NilExecutor/pkg_test.go b/tests/zzz_TestInvoke__NilExecutor/pkg_test.go new file mode 100644 index 0000000..39e9746 --- /dev/null +++ b/tests/zzz_TestInvoke__NilExecutor/pkg_test.go @@ -0,0 +1,17 @@ +package pkg + +import ( + "testing" + + "github.com/go-kata/kerror" + "github.com/go-kata/kinit" +) + +func TestInvoke__NilExecutor(t *testing.T) { + err := kinit.Invoke(nil) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EInvalid { + t.Fail() + return + } +} diff --git a/tests/zzz_TestMustInvoke__NilBootstrapper/pkg.go b/tests/zzz_TestMustInvoke__NilBootstrapper/pkg.go new file mode 100644 index 0000000..870f097 --- /dev/null +++ b/tests/zzz_TestMustInvoke__NilBootstrapper/pkg.go @@ -0,0 +1,3 @@ +package pkg + +// This package is for testing purposes only. diff --git a/tests/zzz_TestMustInvoke__NilBootstrapper/pkg_test.go b/tests/zzz_TestMustInvoke__NilBootstrapper/pkg_test.go new file mode 100644 index 0000000..988e1da --- /dev/null +++ b/tests/zzz_TestMustInvoke__NilBootstrapper/pkg_test.go @@ -0,0 +1,31 @@ +package pkg + +import ( + "reflect" + "testing" + + "github.com/go-kata/kerror" + "github.com/go-kata/kinit" +) + +type testExecutor struct{} + +func (testExecutor) Parameters() []reflect.Type { + return nil +} + +func (testExecutor) Execute(a ...reflect.Value) (kinit.Executor, error) { + return nil, nil +} + +func TestMustInvoke__NilBootstrapper(t *testing.T) { + err := kerror.Try(func() error { + kinit.MustInvoke(testExecutor{}, nil) + return nil + }) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EInvalid { + t.Fail() + return + } +} diff --git a/tests/zzz_TestMustInvoke__NilExecutor/pkg.go b/tests/zzz_TestMustInvoke__NilExecutor/pkg.go new file mode 100644 index 0000000..870f097 --- /dev/null +++ b/tests/zzz_TestMustInvoke__NilExecutor/pkg.go @@ -0,0 +1,3 @@ +package pkg + +// This package is for testing purposes only. diff --git a/tests/zzz_TestMustInvoke__NilExecutor/pkg_test.go b/tests/zzz_TestMustInvoke__NilExecutor/pkg_test.go new file mode 100644 index 0000000..89d4665 --- /dev/null +++ b/tests/zzz_TestMustInvoke__NilExecutor/pkg_test.go @@ -0,0 +1,20 @@ +package pkg + +import ( + "testing" + + "github.com/go-kata/kerror" + "github.com/go-kata/kinit" +) + +func TestMustInvoke__NilExecutor(t *testing.T) { + err := kerror.Try(func() error { + kinit.MustInvoke(nil) + return nil + }) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EInvalid { + t.Fail() + return + } +} diff --git a/zzz_apply.go b/zzz_apply.go new file mode 100644 index 0000000..a01ec97 --- /dev/null +++ b/zzz_apply.go @@ -0,0 +1,48 @@ +package kinit + +import "github.com/go-kata/kerror" + +// Apply calls the Apply method of the global container. +// +// Deprecated: since 0.4.0, use Attach instead. +func Apply(proc Processor) error { + return globalContainer.Apply(proc) +} + +// MustApply is a variant of the Apply that panics on error. +// +// Deprecated: since 0.4.0, use MustAttach instead. +func MustApply(proc Processor) { + if err := Apply(proc); err != nil { + panic(err) + } +} + +// Apply registers the given processor in this container. +// +// Multiple processors may be registered for one type, but there are no guaranty of order of their call. +// +// Deprecated: since 0.4.0, use Attach instead. +func (c *Container) Apply(proc Processor) error { + if c == nil { + return kerror.New(kerror.ENil, "nil container cannot register processor") + } + if proc == nil { + return kerror.New(kerror.EInvalid, "container cannot register nil processor") + } + t := proc.Type() + if t == nil { + return kerror.New(kerror.EInvalid, "container cannot register processor for nil type") + } + c.processors[t] = append(c.processors[t], proc) + return nil +} + +// MustApply is a variant of the Apply that panics on error. +// +// Deprecated: since 0.4.0, use MustAttach instead. +func (c *Container) MustApply(proc Processor) { + if err := c.Apply(proc); err != nil { + panic(err) + } +} diff --git a/zzz_apply_test.go b/zzz_apply_test.go new file mode 100644 index 0000000..a911eff --- /dev/null +++ b/zzz_apply_test.go @@ -0,0 +1,57 @@ +package kinit + +import ( + "testing" + + "github.com/go-kata/kerror" +) + +func TestApply__NilProcessor(t *testing.T) { + err := Apply(nil) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EInvalid { + t.Fail() + return + } +} + +func TestMustApply__NilProcessor(t *testing.T) { + err := kerror.Try(func() error { + MustApply(nil) + return nil + }) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EInvalid { + t.Fail() + return + } +} + +func TestContainer_Apply__NilProcessor(t *testing.T) { + ctr := NewContainer() + err := ctr.Apply(nil) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EInvalid { + t.Fail() + return + } +} + +func TestContainer_Apply__ProcessorWithBrokenType(t *testing.T) { + ctr := NewContainer() + err := ctr.Apply(testProcessorWithBrokenType{}) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EInvalid { + t.Fail() + return + } +} + +func TestNilContainer_Apply(t *testing.T) { + err := (*Container)(nil).Apply(newTestProcessor(processTestCounter)) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.ENil { + t.Fail() + return + } +} diff --git a/bootstrapper.go b/zzz_bootstrapper.go similarity index 78% rename from bootstrapper.go rename to zzz_bootstrapper.go index ddfab33..e5ecc3c 100644 --- a/bootstrapper.go +++ b/zzz_bootstrapper.go @@ -3,6 +3,8 @@ package kinit // Bootstrapper represents an invocation bootstrapper. // // The usual identifier for variables of this type is boot. +// +// Deprecated: since 0.4.0, use Functor and Runtime instead. type Bootstrapper interface { // Bootstrap bootstraps an invocation. Bootstrap(arena *Arena) error diff --git a/bootstrapper_test.go b/zzz_bootstrapper_test.go similarity index 66% rename from bootstrapper_test.go rename to zzz_bootstrapper_test.go index 4b9221e..59b70b8 100644 --- a/bootstrapper_test.go +++ b/zzz_bootstrapper_test.go @@ -4,6 +4,7 @@ import ( "reflect" "github.com/go-kata/kdone" + "github.com/go-kata/kerror" ) type testBootstrapper struct { @@ -21,3 +22,9 @@ func newTestBootstrapper(x interface{}) *testBootstrapper { func (b *testBootstrapper) Bootstrap(arena *Arena) error { return arena.Register(b.t, b.v, kdone.Noop) } + +type testErrorProneBootstrapper struct{} + +func (testErrorProneBootstrapper) Bootstrap(arena *Arena) error { + return kerror.New(kerror.ECustom, "test error") +} diff --git a/executor.go b/zzz_executor.go similarity index 88% rename from executor.go rename to zzz_executor.go index d12fee9..aff665f 100644 --- a/executor.go +++ b/zzz_executor.go @@ -5,6 +5,8 @@ import "reflect" // Executor represents an activity executor. // // The usual identifier for variables of this type is exec. +// +// Deprecated: since 0.4.0, use Functor instead. type Executor interface { // Parameters returns types of objects this executor depends on. Parameters() []reflect.Type diff --git a/executor_test.go b/zzz_executor_test.go similarity index 100% rename from executor_test.go rename to zzz_executor_test.go diff --git a/zzz_invoke.go b/zzz_invoke.go new file mode 100644 index 0000000..fbd1590 --- /dev/null +++ b/zzz_invoke.go @@ -0,0 +1,77 @@ +package kinit + +import "github.com/go-kata/kerror" + +// Invoke calls declared functions if not called yet and then +// calls the Invoke method of the global container. +// +// Deprecated: since 0.4.0, use Run instead. +func Invoke(exec Executor, bootstrappers ...Bootstrapper) error { + if !globalDeclaration.Fulfilled() { + if err := globalDeclaration.Fulfill(); err != nil { + return err + } + } + return globalContainer.Invoke(exec, bootstrappers...) +} + +// MustInvoke is a variant of the Invoke that panics on error. +// +// Deprecated: since 0.4.0, use MustRun instead. +func MustInvoke(exec Executor, bootstrappers ...Bootstrapper) { + if err := Invoke(exec, bootstrappers...); err != nil { + panic(err) + } +} + +// Invoke applies given bootstrappers, resolves the dependency graph based on parameters +// of the given executor using this container and then executes an activity. Dependencies +// of each subsequent executor will be resolved dynamically before it's activity execution. +// +// Deprecated: since 0.4.0, use Run instead. +func (c *Container) Invoke(exec Executor, bootstrappers ...Bootstrapper) (err error) { + if c == nil { + return kerror.New(kerror.ENil, "nil container cannot invoke executor") + } + if exec == nil { + return kerror.New(kerror.EInvalid, "container cannot invoke nil executor") + } + arena := NewArena() + defer func() { + err = kerror.Join(err, arena.Finalize()) + }() + for _, boot := range bootstrappers { + if boot == nil { + return kerror.New(kerror.EInvalid, "container cannot apply nil bootstrapper") + } + if err := boot.Bootstrap(arena); err != nil { + return err + } + } + return c.invoke(arena, exec) +} + +// MustInvoke is a variant of Invoke that panics on error. +// +// Deprecated: since 0.4.0, use MustRun instead. +func (c *Container) MustInvoke(exec Executor, bootstrappers ...Bootstrapper) { + if err := c.Invoke(exec, bootstrappers...); err != nil { + panic(err) + } +} + +// invoke executes an activity using the given arena. +func (c *Container) invoke(arena *Arena, exec Executor) error { + a, err := c.resolve(arena, exec.Parameters()) + if err != nil { + return err + } + next, err := exec.Execute(a...) + if err != nil { + return err + } + if next != nil { + return c.invoke(arena, next) + } + return nil +} diff --git a/zzz_invoke_test.go b/zzz_invoke_test.go new file mode 100644 index 0000000..058850e --- /dev/null +++ b/zzz_invoke_test.go @@ -0,0 +1,110 @@ +package kinit + +import ( + "testing" + + "github.com/go-kata/kdone" + "github.com/go-kata/kerror" +) + +func TestContainer_Invoke(t *testing.T) { + c := 0 + defer func() { + if c != 1 { + t.Fail() + return + } + }() + ctr := NewContainer() + ctr.MustProvide(newTestConstructor(func() (*int, kdone.Destructor, error) { return &c, kdone.Noop, nil })) + ctr.MustApply(newTestProcessor(processTestCounter)) + ctr.MustProvide(newTestConstructor(newTestObject1)) + ctr.MustProvide(newTestConstructor(newTestObject2)) + ctr.MustInvoke( + newTestExecutor(func(*testObject1) (Executor, error) { + if c != 2 { + return nil, kerror.Newf(nil, "counter must be 2, %d found", c) + } + return newTestExecutor(func(*testObject2) (Executor, error) { + if c != 0 { + return nil, kerror.Newf(nil, "counter must be 0, %d found", c) + } + return nil, nil + }), nil + }), + newTestBootstrapper(t), + ) +} + +func TestContainer_Invoke__NilExecutor(t *testing.T) { + ctr := NewContainer() + err := ctr.Invoke(nil) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EInvalid { + t.Fail() + return + } +} + +func TestContainer_Invoke__NilBootstrapper(t *testing.T) { + ctr := NewContainer() + err := ctr.Invoke(newTestExecutor(func() (Executor, error) { return nil, nil }), nil) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.EInvalid { + t.Fail() + return + } +} + +func TestContainer_Invoke__BrokenGraph(t *testing.T) { + ctr := NewContainer() + err := ctr.Invoke( + newTestExecutor(func(*testObject1) (Executor, error) { + return nil, nil + }), + newTestBootstrapper(t), + ) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.ENotFound { + t.Fail() + return + } +} + +func TestContainer_Invoke__ErrorProneExecutor(t *testing.T) { + ctr := NewContainer() + err := ctr.Invoke( + newTestExecutor(func() (Executor, error) { + return nil, kerror.New(kerror.ECustom, "test error") + }), + ) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.ECustom { + t.Fail() + return + } +} + +func TestContainer_Invoke__ErrorProneBootstrapper(t *testing.T) { + ctr := NewContainer() + err := ctr.Invoke( + newTestExecutor(func() ([]Functor, error) { + return nil, nil + }), + testErrorProneBootstrapper{}, + ) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.ECustom { + t.Fail() + return + } +} + +func TestNilContainer_Invoke(t *testing.T) { + err := (*Container)(nil).Invoke(newTestExecutor(func() (Executor, error) { return nil, nil })) + t.Logf("%+v", err) + if kerror.ClassOf(err) != kerror.ENil { + t.Fail() + return + } +}