add Lifecycle.RequireUnique methods for load-once deps

This commit is contained in:
Elijah Duffy
2025-06-03 16:58:59 -07:00
parent b290bc0f7e
commit 6f9e3731ac
2 changed files with 80 additions and 0 deletions

View File

@@ -123,6 +123,12 @@ func (app *Lifecycle) Require(modules ...*Module) error {
return app.RequireL(app.logger, modules...) return app.RequireL(app.logger, modules...)
} }
// RequireUnique is the same as Require, but it returns an error if any requested
// module is already set up--rather than ignoring it.
func (app *Lifecycle) RequireUnique(modules ...*Module) error {
return app.RequireUniqueL(app.logger, modules...)
}
// RequireL adds module(s) to the lifecycle with a specific logger and // RequireL adds module(s) to the lifecycle with a specific logger and
// immediately runs any setup functions. See Require for more details. // immediately runs any setup functions. See Require for more details.
// This variation is useful when you need to set up modules with a non- // This variation is useful when you need to set up modules with a non-
@@ -163,6 +169,23 @@ func (app *Lifecycle) RequireL(logger *slog.Logger, modules ...*Module) error {
return nil return nil
} }
// RequireUniqueL is the same as RequireL, but it returns an error if any requested
// module is already set up--rather than ignoring it.
func (app *Lifecycle) RequireUniqueL(logger *slog.Logger, modules ...*Module) error {
// Check if any module is already set up
for _, mod := range modules {
if mod == nil {
return fmt.Errorf("module is nil")
}
if _, ok := app.setupTracker[mod.name]; ok {
return fmt.Errorf("module '%s' is already set up, cannot require it again", mod.name)
}
}
return app.RequireL(logger, modules...)
}
// setupSingle is a helper function to set up a single module. Returns an error // setupSingle is a helper function to set up a single module. Returns an error
// if the module cannot be set up or if dependencies are not satisfied. // if the module cannot be set up or if dependencies are not satisfied.
func (app *Lifecycle) setupSingle(mod *Module, logger *slog.Logger) error { func (app *Lifecycle) setupSingle(mod *Module, logger *slog.Logger) error {

View File

@@ -222,6 +222,14 @@ func TestLifecycle_Require(t *testing.T) {
assert.Equal(t, lc.logger, mod.logger, "expected module logger to match lifecycle logger") assert.Equal(t, lc.logger, mod.logger, "expected module logger to match lifecycle logger")
} }
func TestLifecycle_RequireUnique(t *testing.T) {
lc := NewLifecycle()
mod := NewModule("module1", ModuleOpts{})
err := lc.RequireUnique(mod)
assert.NoError(t, err, "expected RequireL to succeed")
assert.Equal(t, lc.logger, mod.logger, "expected module logger to match lifecycle logger")
}
func TestLifecycle_RequireL(t *testing.T) { func TestLifecycle_RequireL(t *testing.T) {
cases := []struct { cases := []struct {
name string name string
@@ -324,7 +332,56 @@ func TestLifecycle_RequireL(t *testing.T) {
} }
}) })
} }
}
func TestLifecycle_RequireUniqueL(t *testing.T) {
cases := []struct {
name string
modules []*Module
existingModules []string
expectedErr string
}{
{
name: "no existing modules",
modules: []*Module{
NewModule("module1", ModuleOpts{}),
},
},
{
name: "module already set up",
modules: []*Module{
NewModule("module1", ModuleOpts{}),
NewModule("module2", ModuleOpts{}),
},
existingModules: []string{"module1"},
expectedErr: "module 'module1' is already set up, cannot require it again",
},
{
name: "nil module",
modules: []*Module{nil},
expectedErr: "module is nil",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
assert := assert.New(t)
lc := NewLifecycle()
// Fake existing modules
for _, modName := range tc.existingModules {
lc.setupTracker[modName] = 0 // Mark as set up
}
err := lc.RequireUniqueL(lc.logger, tc.modules...)
if tc.expectedErr == "" {
assert.NoError(err, "expected RequireUniqueL to succeed")
} else {
assert.Error(err, "expected RequireUniqueL to fail")
assert.Contains(err.Error(), tc.expectedErr, "expected error message to match")
}
})
}
} }
func TestLifecycle_setupSingle(t *testing.T) { func TestLifecycle_setupSingle(t *testing.T) {