package app import ( "fmt" "log/slog" "sort" "strings" ) func (app *Lifecycle) require(opts RequireOpts, modules ...*Module) error { if len(modules) == 0 { return fmt.Errorf("no modules to require") } if opts.Logger != nil && opts.Unique { // unique with custom logger is valid } if opts.Logger == nil && opts.Unique { // valid } setupBefore := len(app.setupOrder) for i, mod := range modules { if mod == nil { app.rollbackFrom(setupBefore) return fmt.Errorf("module %d is nil", i) } if _, ok := app.setupTracker[mod.name]; ok { if opts.Unique { app.rollbackFrom(setupBefore) return fmt.Errorf("module %s is already set up, cannot require again", mod) } app.Logger().Warn("module already set up, ignoring", "module", mod) mod.loaded = true mod.lifecycle = app mod.logger = opts.Logger continue } app.modules = append(app.modules, mod) if err := app.setupSingle(opts.Logger, mod, nil); err != nil { app.rollbackFrom(setupBefore) return fmt.Errorf("error setting up required module %s: %w", mod, err) } } app.Logger().Info("New modules initialized", "all", mapToString(app.setupTracker)) return nil } func (app *Lifecycle) setupSingle(logger *slog.Logger, mod *Module, visiting map[string]bool) error { if mod == nil { return fmt.Errorf("module is nil") } if _, ok := app.setupTracker[mod.name]; ok { return nil } if visiting == nil { visiting = make(map[string]bool) } if visiting[mod.name] { return fmt.Errorf("circular dependency detected involving %s", mod) } visiting[mod.name] = true defer delete(visiting, mod.name) mod.lifecycle = app mod.logger = logger for _, dep := range mod.depends { if _, ok := app.setupTracker[dep]; !ok { if app.opts.DisableAutoload { return fmt.Errorf("dependency %s not satisfied for '%s'", dep, mod) } depmod, err := app.getModuleByName(dep) if err != nil { return fmt.Errorf("error getting dependency '%s' for %s: %w", dep, mod, err) } if err := app.setupSingle(logger, depmod, visiting); err != nil { return fmt.Errorf("error setting up dependency %s for %s: %w", depmod, mod, err) } } } if mod.setup != nil { if err := mod.setup(mod); err != nil { return fmt.Errorf("error initializing %s: %w", mod, err) } } app.setupTracker[mod.name] = app.setupCount app.setupOrder = append(app.setupOrder, mod) app.setupCount++ mod.loaded = true return nil } func (app *Lifecycle) teardownSingle(mod *Module) error { if mod == nil { return fmt.Errorf("module is nil") } if _, ok := app.setupTracker[mod.name]; !ok { return fmt.Errorf("module %s is not set up, cannot tear down", mod) } if mod.teardown != nil { if err := mod.teardown(mod); err != nil { return fmt.Errorf("error tearing down %s: %w", mod, err) } } app.teardownTracker[mod.name] = app.teardownCount app.teardownCount++ mod.loaded = false return nil } func (app *Lifecycle) rollbackFrom(startIndex int) { for i := len(app.setupOrder) - 1; i >= startIndex; i-- { mod := app.setupOrder[i] if mod.teardown != nil { _ = mod.teardown(mod) } delete(app.setupTracker, mod.name) mod.loaded = false } app.setupOrder = app.setupOrder[:startIndex] app.setupCount = startIndex } func (app *Lifecycle) getModuleByName(name string) (*Module, error) { for _, mod := range app.modules { if mod.name == name { return mod, nil } } return nil, ErrModuleNotFound } func mapToString(m map[string]int) string { if len(m) == 0 { return "[]" } values := make([]int, 0, len(m)) reverseMap := make(map[int]string, len(m)) for k, v := range m { values = append(values, v) reverseMap[v] = k } sort.Ints(values) result := make([]string, 0, len(m)) for _, v := range values { if name, ok := reverseMap[v]; ok { result = append(result, fmt.Sprintf("'%s'", name)) } } return "[" + strings.Join(result, " ") + "]" }