Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
private/mud: support registering multiple implementations
Change-Id: I5df24fbc799bd11311606d8b285fd64fbad9d93c
- Loading branch information
Showing
4 changed files
with
197 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// Copyright (C) 2024 Storj Labs, Inc. | ||
// See LICENSE for copying information. | ||
|
||
package mud | ||
|
||
import ( | ||
"context" | ||
) | ||
|
||
// Implementation registers a new []T component, which will be filled with any registered instances. | ||
// Instances will be marked with "Optional{}" tag, and will be injected only, if they are initialized. | ||
// It's the responsibility of the Init code to exclude / include them during initialization. | ||
func Implementation[L ~[]T, Instance any, T any](ball *Ball) { | ||
if lookup[L](ball) == nil { | ||
RegisterManual[L](ball, func(ctx context.Context) (L, error) { | ||
var instances L | ||
component := lookup[L](ball) | ||
for _, req := range component.requirements { | ||
c, _ := lookupByType(ball, req) | ||
// only initialized instances are inject to the implementation list | ||
if c.instance != nil { | ||
instances = append(instances, c.instance.(T)) | ||
} | ||
} | ||
return instances, nil | ||
}) | ||
} | ||
lookup[L](ball).requirements = append(lookup[L](ball).requirements, typeOf[Instance]()) | ||
Tag[Instance](ball, Optional{}) | ||
} | ||
|
||
// ImplementationOf is a ForEach filter to get all the dependency of an implementation. | ||
func ImplementationOf[L ~[]T, T any](ball *Ball) ComponentSelector { | ||
component := MustLookupComponent[L](ball) | ||
return func(c *Component) bool { | ||
for _, dep := range component.requirements { | ||
if dep == c.target { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
// Copyright (C) 2024 Storj Labs, Inc. | ||
// See LICENSE for copying information. | ||
|
||
package mud | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
"storj.io/common/testcontext" | ||
) | ||
|
||
func TestImplementationAllInit(t *testing.T) { | ||
ctx := testcontext.New(t) | ||
|
||
ball := NewBall() | ||
Provide[PostgresDB](ball, func() PostgresDB { | ||
return PostgresDB{} | ||
}) | ||
Provide[CockroachDB](ball, func() CockroachDB { | ||
return CockroachDB{} | ||
}) | ||
|
||
Implementation[[]DBAdapter, PostgresDB](ball) | ||
Implementation[[]DBAdapter, CockroachDB](ball) | ||
|
||
err := ForEach(ball, Initialize(ctx), All) | ||
require.NoError(t, err) | ||
|
||
// by default all dependencies are initialized, and usable | ||
err = Execute0(ctx, ball, func(impl []DBAdapter) { | ||
require.Len(t, impl, 2) | ||
}) | ||
require.NoError(t, err) | ||
} | ||
|
||
func TestImplementationOneInit(t *testing.T) { | ||
ctx := testcontext.New(t) | ||
|
||
ball := NewBall() | ||
Provide[PostgresDB](ball, func() PostgresDB { | ||
return PostgresDB{} | ||
}) | ||
Provide[CockroachDB](ball, func() CockroachDB { | ||
return CockroachDB{} | ||
}) | ||
|
||
Implementation[[]DBAdapter, PostgresDB](ball) | ||
Implementation[[]DBAdapter, CockroachDB](ball) | ||
|
||
pg := MustLookupComponent[PostgresDB](ball) | ||
err := pg.Init(ctx) | ||
require.NoError(t, err) | ||
|
||
adapters := MustLookupComponent[[]DBAdapter](ball) | ||
|
||
// this init will use all the initialized dependencies (in our case, postgres only) | ||
err = adapters.Init(ctx) | ||
require.NoError(t, err) | ||
|
||
// Cockroach is not initialized, therefore it's an optional dependency. | ||
err = Execute0(ctx, ball, func(impl []DBAdapter) { | ||
require.Len(t, impl, 1) | ||
require.Equal(t, "postgres", impl[0].Name()) | ||
}) | ||
require.NoError(t, err) | ||
|
||
} | ||
|
||
func TestImplementationMarkedOptional(t *testing.T) { | ||
ctx := testcontext.New(t) | ||
|
||
ball := NewBall() | ||
Provide[PostgresDB](ball, func() PostgresDB { | ||
return PostgresDB{} | ||
}) | ||
Provide[CockroachDB](ball, func() CockroachDB { | ||
return CockroachDB{} | ||
}) | ||
|
||
Implementation[[]DBAdapter, PostgresDB](ball) | ||
Implementation[[]DBAdapter, CockroachDB](ball) | ||
// PostgresDB is required | ||
RemoveTag[PostgresDB, Optional](ball) | ||
|
||
// initialize the non-optional components | ||
// NOTE: optional is just a flag to find the right components | ||
// it's the responsibility of the caller to initialize the right components. | ||
for _, component := range Find(ball, And(All, func(c *Component) bool { | ||
_, found := GetTagOf[Optional](c) | ||
return !found | ||
})) { | ||
// this will ignore CockroachDB as it's not a required dependency | ||
err := component.Init(ctx) | ||
require.NoError(t, err) | ||
} | ||
|
||
// CockroachDB is not initialized, because it was optional | ||
err := Execute0(ctx, ball, func(impl []DBAdapter) { | ||
require.Len(t, impl, 1) | ||
require.Equal(t, "postgres", impl[0].Name()) | ||
}) | ||
require.NoError(t, err) | ||
|
||
} | ||
|
||
type DBAdapter interface { | ||
Name() string | ||
} | ||
type PostgresDB struct{} | ||
|
||
func (p PostgresDB) Name() string { | ||
return "postgres" | ||
} | ||
|
||
type CockroachDB struct{} | ||
|
||
func (p CockroachDB) Name() string { | ||
return "cockroach" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters