service backend
This commit is contained in:
147
api/fx/ingestor/internal/config/config.go
Normal file
147
api/fx/ingestor/internal/config/config.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/tech/sendico/fx/ingestor/internal/fmerrors"
|
||||
mmodel "github.com/tech/sendico/fx/ingestor/internal/model"
|
||||
"github.com/tech/sendico/pkg/db"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const defaultPollInterval = 30 * time.Second
|
||||
|
||||
type Config struct {
|
||||
PollIntervalSeconds int `yaml:"poll_interval_seconds"`
|
||||
Market MarketConfig `yaml:"market"`
|
||||
Database *db.Config `yaml:"database"`
|
||||
Metrics *MetricsConfig `yaml:"metrics"`
|
||||
|
||||
pairs []Pair
|
||||
pairsBySource map[mmodel.Driver][]PairConfig
|
||||
}
|
||||
|
||||
func Load(path string) (*Config, error) {
|
||||
if path == "" {
|
||||
return nil, fmerrors.New("config: path is empty")
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmerrors.Wrap("config: failed to read file", err)
|
||||
}
|
||||
|
||||
cfg := &Config{}
|
||||
if err := yaml.Unmarshal(data, cfg); err != nil {
|
||||
return nil, fmerrors.Wrap("config: failed to parse yaml", err)
|
||||
}
|
||||
|
||||
if len(cfg.Market.Sources) == 0 {
|
||||
return nil, fmerrors.New("config: no market sources configured")
|
||||
}
|
||||
sourceSet := make(map[mmodel.Driver]struct{}, len(cfg.Market.Sources))
|
||||
for idx := range cfg.Market.Sources {
|
||||
src := &cfg.Market.Sources[idx]
|
||||
if src.Driver.IsEmpty() {
|
||||
return nil, fmerrors.New("config: market source driver is empty")
|
||||
}
|
||||
sourceSet[src.Driver] = struct{}{}
|
||||
}
|
||||
|
||||
if len(cfg.Market.Pairs) == 0 {
|
||||
return nil, fmerrors.New("config: no pairs configured")
|
||||
}
|
||||
|
||||
normalizedPairs := make(map[string][]PairConfig, len(cfg.Market.Pairs))
|
||||
pairsBySource := make(map[mmodel.Driver][]PairConfig, len(cfg.Market.Pairs))
|
||||
var flattened []Pair
|
||||
|
||||
for rawSource, pairList := range cfg.Market.Pairs {
|
||||
driver := mmodel.Driver(rawSource)
|
||||
if driver.IsEmpty() {
|
||||
return nil, fmerrors.New("config: pair source is empty")
|
||||
}
|
||||
if _, ok := sourceSet[driver]; !ok {
|
||||
return nil, fmerrors.New("config: pair references unknown source: " + driver.String())
|
||||
}
|
||||
|
||||
processed := make([]PairConfig, len(pairList))
|
||||
for idx := range pairList {
|
||||
pair := pairList[idx]
|
||||
pair.Base = strings.ToUpper(strings.TrimSpace(pair.Base))
|
||||
pair.Quote = strings.ToUpper(strings.TrimSpace(pair.Quote))
|
||||
pair.Symbol = strings.TrimSpace(pair.Symbol)
|
||||
if pair.Base == "" || pair.Quote == "" || pair.Symbol == "" {
|
||||
return nil, fmerrors.New("config: pair entries must define base, quote, and symbol")
|
||||
}
|
||||
if strings.TrimSpace(pair.Provider) == "" {
|
||||
pair.Provider = strings.ToLower(driver.String())
|
||||
}
|
||||
processed[idx] = pair
|
||||
flattened = append(flattened, Pair{
|
||||
PairConfig: pair,
|
||||
Source: driver,
|
||||
})
|
||||
}
|
||||
pairsBySource[driver] = processed
|
||||
normalizedPairs[driver.String()] = processed
|
||||
}
|
||||
|
||||
cfg.Market.Pairs = normalizedPairs
|
||||
cfg.pairsBySource = pairsBySource
|
||||
cfg.pairs = flattened
|
||||
if cfg.Database == nil {
|
||||
return nil, fmerrors.New("config: database configuration is required")
|
||||
}
|
||||
|
||||
if cfg.Metrics != nil && cfg.Metrics.Enabled {
|
||||
cfg.Metrics.Address = strings.TrimSpace(cfg.Metrics.Address)
|
||||
if cfg.Metrics.Address == "" {
|
||||
cfg.Metrics.Address = ":9102"
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (c *Config) PollInterval() time.Duration {
|
||||
if c == nil {
|
||||
return defaultPollInterval
|
||||
}
|
||||
if c.PollIntervalSeconds <= 0 {
|
||||
return defaultPollInterval
|
||||
}
|
||||
return time.Duration(c.PollIntervalSeconds) * time.Second
|
||||
}
|
||||
|
||||
func (c *Config) Pairs() []Pair {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
out := make([]Pair, len(c.pairs))
|
||||
copy(out, c.pairs)
|
||||
return out
|
||||
}
|
||||
|
||||
func (c *Config) PairsBySource() map[mmodel.Driver][]PairConfig {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
out := make(map[mmodel.Driver][]PairConfig, len(c.pairsBySource))
|
||||
for driver, pairs := range c.pairsBySource {
|
||||
cp := make([]PairConfig, len(pairs))
|
||||
copy(cp, pairs)
|
||||
out[driver] = cp
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (c *Config) MetricsConfig() *MetricsConfig {
|
||||
if c == nil || c.Metrics == nil {
|
||||
return nil
|
||||
}
|
||||
cp := *c.Metrics
|
||||
return &cp
|
||||
}
|
||||
24
api/fx/ingestor/internal/config/market.go
Normal file
24
api/fx/ingestor/internal/config/market.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
mmodel "github.com/tech/sendico/fx/ingestor/internal/model"
|
||||
pmodel "github.com/tech/sendico/pkg/model"
|
||||
)
|
||||
|
||||
type PairConfig struct {
|
||||
Base string `yaml:"base"`
|
||||
Quote string `yaml:"quote"`
|
||||
Symbol string `yaml:"symbol"`
|
||||
Provider string `yaml:"provider"`
|
||||
Invert bool `yaml:"invert"`
|
||||
}
|
||||
|
||||
type Pair struct {
|
||||
PairConfig `yaml:",inline"`
|
||||
Source mmodel.Driver `yaml:"-"`
|
||||
}
|
||||
|
||||
type MarketConfig struct {
|
||||
Sources []pmodel.DriverConfig[mmodel.Driver] `yaml:"sources"`
|
||||
Pairs map[string][]PairConfig `yaml:"pairs"`
|
||||
}
|
||||
6
api/fx/ingestor/internal/config/metrics.go
Normal file
6
api/fx/ingestor/internal/config/metrics.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package config
|
||||
|
||||
type MetricsConfig struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
Address string `yaml:"address"`
|
||||
}
|
||||
Reference in New Issue
Block a user