package app import ( "errors" "fmt" "log/slog" "strings" ) // ErrModuleNotFound is returned when a module is not found in the lifecycle. var ErrModuleNotFound = errors.New("module not found") // moduleFn is a function type for module setup and teardown functions. type moduleFn func(*Module) error // GenericModule is an interface that allows modules to be extended with custom // functionality, as the module can return a pointer to the underlying Module. type GenericModule interface { // Module returns the underlying Module instance. Module() *Module } // Module represents a sub-system of the application, with its setup and // teardown functions and dependencies. type Module struct { lifecycle *Lifecycle // lifecycle is the parent lifecycle this module belongs to logger *slog.Logger name string setup moduleFn teardown moduleFn depends []string loaded bool // loaded indicates if the module has been set up } // ModuleOpts contains user-exposed options when defining a module. type ModuleOpts struct { // Setup is the setup function for the module. Setup moduleFn // Teardown is the teardown function for the module. Teardown moduleFn // Depends is a list of modules that this module depends on. Each entry must // be the exact name of a module registered in the lifecycle. Depends []string } // NewModule creates a new Module instance with the given name and options. func NewModule(name string, opts ModuleOpts) *Module { return &Module{ name: name, setup: opts.Setup, teardown: opts.Teardown, depends: opts.Depends, } } // Lifecycle returns the lifecycle this module belongs to. If the module is not // part of a lifecycle, it will return nil. func (s *Module) Lifecycle() *Lifecycle { return s.lifecycle } // Logger returns the logger for the module. Uses the lifecycle's logger unless // a specific logger has been set during module load. func (s *Module) Logger() *slog.Logger { if s.logger != nil { return s.logger } if s.lifecycle == nil { panic("module has no lifecycle, cannot get logger") } return s.lifecycle.Logger() } // Name returns the name of the module. func (s *Module) Name() string { return s.name } // String returns the name of the module in square brackets, e.g. "[module-name]". func (s *Module) String() string { return fmt.Sprintf("[%s]", s.name) } // Loaded returns whether the module has been set up. func (s *Module) Loaded() bool { return s.loaded } // RequireLoaded panics if the module is not loaded. Any arguments are included // in the panic message for additional context. func (s *Module) RequireLoaded(msg ...string) { ctx := "" if len(msg) > 0 { ctx = ": " + strings.Join(msg, ", ") } if s == nil { panic("module is nil" + ctx) } if !s.loaded { panic(fmt.Sprintf("module %s not loaded", s.name) + ctx) } }