package app import ( "go.uber.org/fx" "go.uber.org/fx/fxevent" "go.uber.org/zap" "go.uber.org/zap/zapcore" "sync/atomic" "universe.mrlokop.dev/core/config" "universe.mrlokop.dev/core/errors" "universe.mrlokop.dev/core/modules" ) var ( ErrAlreadyStarted = errors.Type("ErrAlreadyStarted", "already started") ErrUnknownModule = errors.Type("ErrUnknownModule", "specified module does not exist") ) type Application interface { Config() *config.UniverseConfig Run() error } type UniverseApplication struct { fx *fx.App config *config.UniverseConfig options []fx.Option started atomic.Bool } func (app *UniverseApplication) Config() *config.UniverseConfig { return app.config } func (app *UniverseApplication) Run() error { if !app.started.CompareAndSwap(false, true) { return ErrAlreadyStarted.CreateEmpty() } options := make([]fx.Option, 0) options = append(options, app.options...) options = append(options, fx.Provide(func() Application { return app }), fx.WithLogger(func(log *zap.Logger) fxevent.Logger { logger := &fxevent.ZapLogger{Logger: log.WithOptions(zap.AddCallerSkip(2))} logger.UseLogLevel(zapcore.DebugLevel) return logger }), ) if app.config.Modules != nil && len(app.config.Modules) > 0 { for id, modConf := range app.config.Modules { kind := modConf["kind"].(string) module := modules.GetModule(kind) if module == nil { return ErrUnknownModule.Builder(). Messagef("module '%s' is not registered", kind). WithField("module", kind). Build() } option, err := module.Create(id, modConf) if err != nil { return err } options = append(options, option) } } app.fx = fx.New(options...) app.fx.Run() return nil } func (app *UniverseApplication) WithOption(options ...fx.Option) *UniverseApplication { if app.started.Load() { panic(ErrAlreadyStarted) } app.options = append(app.options, options...) return app } func NewUniverseApplication(config *config.UniverseConfig) *UniverseApplication { return &UniverseApplication{config: config} }