Files
eway/cmd/cli/components/di.go
2024-02-26 17:55:44 +03:00

222 lines
4.8 KiB
Go

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]