package components import ( "context" "errors" "fmt" "io" "os" "time" "git.loyso.art/frx/eway/internal/config" "git.loyso.art/frx/eway/internal/dimension" "git.loyso.art/frx/eway/internal/interconnect/eway" "git.loyso.art/frx/eway/internal/storage" xbadger "git.loyso.art/frx/eway/internal/storage/badger" "github.com/BurntSushi/toml" "github.com/dgraph-io/badger/v4" "github.com/rs/zerolog" "github.com/samber/do" ) // Yeah, singleton is not good UNLESS you're really lazy var diInjector *do.Injector func GetEwayClient() (eway.Client, error) { return do.Invoke[eway.Client](diInjector) } func GetRepository() (storage.Repository, error) { adapter, err := do.Invoke[*storageRepositoryAdapter](diInjector) if err != nil { return nil, err } return adapter.entity, nil } func GetLogger() (zerolog.Logger, error) { log, err := do.Invoke[*loggerAdapter](diInjector) if err != nil { return zerolog.Nop(), err } return log.entity.log, nil } func GetDimensionMatcher() (*dimension.Matcher, error) { return do.Invoke[*dimension.Matcher](diInjector) } func SetupDI(ctx context.Context, cfgpath string, verbose bool, logAsJSON bool) error { cfg, err := parseSettings(cfgpath) if err != nil { // if no settings provided allow cli to run without them. if errors.Is(err, os.ErrNotExist) { return nil } return err } diInjector = do.New() do.Provide(diInjector, func(i *do.Injector) (*loggerAdapter, error) { tsSet := func(wr *zerolog.ConsoleWriter) { wr.TimeFormat = time.RFC3339 } var outfile *os.File var output io.Writer switch cfg.Log.Output { case "", "stdout": output = os.Stdout case "stderr": output = os.Stderr default: outfile, err = os.OpenFile(cfg.Log.Output, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { return nil, fmt.Errorf("creating file for logging: %w", err) } output = zerolog.SyncWriter(outfile) } var writer io.Writer if logAsJSON { writer = output } else { writer = zerolog.NewConsoleWriter(tsSet, func(w *zerolog.ConsoleWriter) { w.Out = output }) } log := zerolog. New(writer). With(). Timestamp(). Str("app", "converter"). Logger() if verbose { log = log.Level(zerolog.DebugLevel) } else { log = log.Level(zerolog.InfoLevel) } out := &logger{ log: log, underlyingFile: outfile, } return &loggerAdapter{ entity: out, }, nil }) do.Provide[eway.Client](diInjector, func(i *do.Injector) (eway.Client, error) { log, err := GetLogger() if err != nil { return nil, fmt.Errorf("getting logger: %w", err) } client, err := eway.New(ctx, eway.Config(cfg.Eway), log) if err != nil { return nil, fmt.Errorf("making new eway client: %w", err) } return client, nil }) do.Provide[*badgerDBAdapter](diInjector, func(i *do.Injector) (*badgerDBAdapter, error) { db, err := xbadger.Open(ctx, cfg.Badger.Dir, cfg.Badger.Debug, zerolog.Nop()) if err != nil { return nil, fmt.Errorf("getting db: %w", err) } out := &badgerDBAdapter{entity: db} return out, nil }) do.Provide[*storageRepositoryAdapter](diInjector, func(i *do.Injector) (*storageRepositoryAdapter, error) { db, err := getDB() if err != nil { return nil, err } client, err := xbadger.NewClient(db) if err != nil { return nil, fmt.Errorf("getting badger client: %w", err) } out := &storageRepositoryAdapter{entity: client} return out, nil }) do.Provide[*dimension.Matcher](diInjector, func(i *do.Injector) (*dimension.Matcher, error) { matcher := dimension.New(cfg.DimensionMatcher) return matcher, nil }) return nil } func Shutdown() error { if diInjector == nil { return nil } return diInjector.Shutdown() } func getDB() (*badger.DB, error) { adapter, err := do.Invoke[*badgerDBAdapter](diInjector) if err != nil { return nil, err } return adapter.entity, nil } type settings struct { Badger config.Badger `toml:"badger"` Log config.Log `toml:"log"` Eway config.Eway `toml:"eway"` DimensionMatcher config.DimensionMatcher `toml:"dimension_matcher"` } func parseSettings(cfgpath string) (cfg settings, err error) { _, err = toml.DecodeFile(cfgpath, &cfg) if err != nil { return cfg, fmt.Errorf("parsing file: %w", err) } return cfg, nil } type logger struct { log zerolog.Logger underlyingFile *os.File } func (l *logger) Close() error { if l.underlyingFile == nil { return nil } return l.underlyingFile.Close() } type entityCloserAdapter[T io.Closer] struct { entity T } func (a entityCloserAdapter[T]) Shutdown() error { return a.entity.Close() } type storageRepositoryAdapter entityCloserAdapter[storage.Repository] type badgerDBAdapter entityCloserAdapter[*badger.DB] type loggerAdapter entityCloserAdapter[*logger]