fixed linting config
This commit is contained in:
@@ -27,30 +27,33 @@ type binanceConnector struct {
|
||||
}
|
||||
|
||||
const defaultBinanceBaseURL = "https://api.binance.com"
|
||||
|
||||
const (
|
||||
defaultDialTimeoutSeconds = 5 * time.Second
|
||||
defaultDialKeepAliveSeconds = 30 * time.Second
|
||||
defaultTLSHandshakeTimeoutSeconds = 5 * time.Second
|
||||
defaultResponseHeaderTimeoutSeconds = 10 * time.Second
|
||||
defaultRequestTimeoutSeconds = 10 * time.Second
|
||||
defaultDialTimeout = 5 * time.Second
|
||||
defaultDialKeepAlive = 30 * time.Second
|
||||
defaultTLSHandshakeTimeout = 5 * time.Second
|
||||
defaultResponseHeaderTimeout = 10 * time.Second
|
||||
defaultRequestTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
func NewConnector(logger mlogger.Logger, settings model.SettingsT) (mmodel.Connector, error) {
|
||||
func NewConnector(logger mlogger.Logger, settings model.SettingsT) (mmodel.Connector, error) { //nolint:ireturn
|
||||
baseURL := defaultBinanceBaseURL
|
||||
provider := strings.ToLower(mmodel.DriverBinance.String())
|
||||
dialTimeout := defaultDialTimeoutSeconds
|
||||
dialKeepAlive := defaultDialKeepAliveSeconds
|
||||
tlsHandshakeTimeout := defaultTLSHandshakeTimeoutSeconds
|
||||
responseHeaderTimeout := defaultResponseHeaderTimeoutSeconds
|
||||
requestTimeout := defaultRequestTimeoutSeconds
|
||||
dialTimeout := defaultDialTimeout
|
||||
dialKeepAlive := defaultDialKeepAlive
|
||||
tlsHandshakeTimeout := defaultTLSHandshakeTimeout
|
||||
responseHeaderTimeout := defaultResponseHeaderTimeout
|
||||
requestTimeout := defaultRequestTimeout
|
||||
|
||||
if settings != nil {
|
||||
if value, ok := settings["base_url"].(string); ok && strings.TrimSpace(value) != "" {
|
||||
baseURL = strings.TrimSpace(value)
|
||||
}
|
||||
|
||||
if value, ok := settings["provider"].(string); ok && strings.TrimSpace(value) != "" {
|
||||
provider = strings.TrimSpace(value)
|
||||
}
|
||||
|
||||
dialTimeout = common.DurationSetting(settings, "dial_timeout_seconds", dialTimeout)
|
||||
dialKeepAlive = common.DurationSetting(settings, "dial_keep_alive_seconds", dialKeepAlive)
|
||||
tlsHandshakeTimeout = common.DurationSetting(settings, "tls_handshake_timeout_seconds", tlsHandshakeTimeout)
|
||||
@@ -96,6 +99,7 @@ func (c *binanceConnector) FetchTicker(ctx context.Context, symbol string) (*mmo
|
||||
if err != nil {
|
||||
return nil, merrors.InternalWrap(err, "binance: parse base url")
|
||||
}
|
||||
|
||||
endpoint.Path = "/api/v3/ticker/bookTicker"
|
||||
query := endpoint.Query()
|
||||
query.Set("symbol", strings.ToUpper(strings.TrimSpace(symbol)))
|
||||
@@ -109,12 +113,14 @@ func (c *binanceConnector) FetchTicker(ctx context.Context, symbol string) (*mmo
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
c.logger.Warn("Binance request failed", zap.String("symbol", symbol), zap.Error(err))
|
||||
|
||||
return nil, merrors.InternalWrap(err, "binance: request failed")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
c.logger.Warn("Binance returned non-OK status", zap.String("symbol", symbol), zap.Int("status", resp.StatusCode))
|
||||
|
||||
return nil, merrors.Internal("binance: unexpected status " + strconv.Itoa(resp.StatusCode))
|
||||
}
|
||||
|
||||
@@ -124,9 +130,11 @@ func (c *binanceConnector) FetchTicker(ctx context.Context, symbol string) (*mmo
|
||||
AskPrice string `json:"askPrice"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
|
||||
c.logger.Warn("Binance decode failed", zap.String("symbol", symbol), zap.Error(err))
|
||||
return nil, merrors.InternalWrap(err, "binance: decode response")
|
||||
decodeErr := json.NewDecoder(resp.Body).Decode(&payload)
|
||||
if decodeErr != nil {
|
||||
c.logger.Warn("Binance decode failed", zap.String("symbol", symbol), zap.Error(decodeErr))
|
||||
|
||||
return nil, merrors.InternalWrap(decodeErr, "binance: decode response")
|
||||
}
|
||||
|
||||
return &mmodel.Ticker{
|
||||
|
||||
@@ -49,7 +49,7 @@ const (
|
||||
defaultRequestTimeoutSeconds = 10 * time.Second
|
||||
)
|
||||
|
||||
func NewConnector(logger mlogger.Logger, settings model.SettingsT) (mmodel.Connector, error) {
|
||||
func NewConnector(logger mlogger.Logger, settings model.SettingsT) (mmodel.Connector, error) { //nolint:cyclop,ireturn
|
||||
baseURL := defaultCBRBaseURL
|
||||
provider := strings.ToLower(mmodel.DriverCBR.String())
|
||||
dialTimeout := defaultDialTimeoutSeconds
|
||||
@@ -284,7 +284,7 @@ func (c *cbrConnector) fetchDailyRate(ctx context.Context, valute valuteInfo) (s
|
||||
return computePrice(entry.Value, entry.Nominal)
|
||||
}
|
||||
|
||||
func (c *cbrConnector) fetchHistoricalRate(ctx context.Context, valute valuteInfo, date time.Time) (string, error) {
|
||||
func (c *cbrConnector) fetchHistoricalRate(ctx context.Context, valute valuteInfo, date time.Time) (string, error) { //nolint:funlen
|
||||
query := map[string]string{
|
||||
"date_req1": date.Format("02/01/2006"),
|
||||
"date_req2": date.Format("02/01/2006"),
|
||||
@@ -366,6 +366,7 @@ func (c *cbrConnector) buildURL(path string, query map[string]string) (string, e
|
||||
return "", merrors.InternalWrap(err, "cbr: parse base url")
|
||||
}
|
||||
base.Path = strings.TrimRight(base.Path, "/") + path
|
||||
|
||||
q := base.Query()
|
||||
for key, value := range query {
|
||||
q.Set(key, value)
|
||||
@@ -401,7 +402,7 @@ type valuteMapping struct {
|
||||
byID map[string]valuteInfo
|
||||
}
|
||||
|
||||
func buildValuteMapping(logger *zap.Logger, items []valuteItem) (*valuteMapping, error) {
|
||||
func buildValuteMapping(logger *zap.Logger, items []valuteItem) (*valuteMapping, error) { //nolint:gocognit,nestif
|
||||
byISO := make(map[string]valuteInfo, len(items))
|
||||
byID := make(map[string]valuteInfo, len(items))
|
||||
byNum := make(map[string]string, len(items))
|
||||
@@ -453,11 +454,12 @@ func buildValuteMapping(logger *zap.Logger, items []valuteItem) (*valuteMapping,
|
||||
// 2) Otherwise prefer smaller nominal
|
||||
keepExisting := true
|
||||
|
||||
if existing.Nominal != 1 && info.Nominal == 1 {
|
||||
switch {
|
||||
case existing.Nominal != 1 && info.Nominal == 1:
|
||||
keepExisting = false
|
||||
} else if existing.Nominal == 1 && info.Nominal != 1 {
|
||||
case existing.Nominal == 1 && info.Nominal != 1:
|
||||
keepExisting = true
|
||||
} else if info.Nominal < existing.Nominal {
|
||||
case info.Nominal < existing.Nominal:
|
||||
keepExisting = false
|
||||
}
|
||||
|
||||
@@ -513,7 +515,9 @@ func buildValuteMapping(logger *zap.Logger, items []valuteItem) (*valuteMapping,
|
||||
byNum[isoNum] = id
|
||||
}
|
||||
|
||||
logger.Info("Installing currency code", zap.String("iso_code", isoChar), zap.String("id", id), zap.Int64("nominal", nominal))
|
||||
logger.Info("Installing currency code",
|
||||
zap.String("iso_code", isoChar), zap.String("id", id), zap.Int64("nominal", nominal),
|
||||
)
|
||||
|
||||
byISO[isoChar] = info
|
||||
byID[id] = info
|
||||
@@ -546,6 +550,7 @@ func (d *dailyRates) find(id string) *dailyValute {
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for idx := range d.Valutes {
|
||||
if strings.EqualFold(strings.TrimSpace(d.Valutes[idx].ID), id) {
|
||||
return &d.Valutes[idx]
|
||||
@@ -569,7 +574,9 @@ func (d *dynamicRates) find(id string, date time.Time) *dynamicRecord {
|
||||
if d == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
target := date.Format("02.01.2006")
|
||||
|
||||
for idx := range d.Records {
|
||||
rec := &d.Records[idx]
|
||||
if !strings.EqualFold(strings.TrimSpace(rec.ID), id) {
|
||||
@@ -663,7 +670,7 @@ func computePrice(value string, nominalStr string) (string, error) {
|
||||
|
||||
den := big.NewRat(nominal, 1)
|
||||
price := new(big.Rat).Quo(r, den)
|
||||
return price.FloatString(8), nil
|
||||
return price.FloatString(8), nil //nolint:mnd
|
||||
}
|
||||
|
||||
func formatSymbol(iso string, asOf *time.Time) string {
|
||||
|
||||
@@ -29,29 +29,36 @@ type coingeckoConnector struct {
|
||||
const defaultCoinGeckoBaseURL = "https://api.coingecko.com/api/v3"
|
||||
|
||||
const (
|
||||
defaultDialTimeoutSeconds = 5 * time.Second
|
||||
defaultDialKeepAliveSeconds = 30 * time.Second
|
||||
defaultTLSHandshakeTimeoutSeconds = 5 * time.Second
|
||||
defaultResponseHeaderTimeoutSeconds = 10 * time.Second
|
||||
defaultRequestTimeoutSeconds = 10 * time.Second
|
||||
defaultDialTimeout = 5 * time.Second
|
||||
defaultDialKeepAlive = 30 * time.Second
|
||||
defaultTLSHandshakeTimeout = 5 * time.Second
|
||||
defaultResponseHeaderTimeout = 10 * time.Second
|
||||
defaultRequestTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
func NewConnector(logger mlogger.Logger, settings model.SettingsT) (mmodel.Connector, error) {
|
||||
const (
|
||||
expectedSymbolParts = 2
|
||||
tsToMillis = 1000
|
||||
)
|
||||
|
||||
func NewConnector(logger mlogger.Logger, settings model.SettingsT) (mmodel.Connector, error) { //nolint:ireturn
|
||||
baseURL := defaultCoinGeckoBaseURL
|
||||
provider := strings.ToLower(mmodel.DriverCoinGecko.String())
|
||||
dialTimeout := defaultDialTimeoutSeconds
|
||||
dialKeepAlive := defaultDialKeepAliveSeconds
|
||||
tlsHandshakeTimeout := defaultTLSHandshakeTimeoutSeconds
|
||||
responseHeaderTimeout := defaultResponseHeaderTimeoutSeconds
|
||||
requestTimeout := defaultRequestTimeoutSeconds
|
||||
dialTimeout := defaultDialTimeout
|
||||
dialKeepAlive := defaultDialKeepAlive
|
||||
tlsHandshakeTimeout := defaultTLSHandshakeTimeout
|
||||
responseHeaderTimeout := defaultResponseHeaderTimeout
|
||||
requestTimeout := defaultRequestTimeout
|
||||
|
||||
if settings != nil {
|
||||
if value, ok := settings["base_url"].(string); ok && strings.TrimSpace(value) != "" {
|
||||
baseURL = strings.TrimSpace(value)
|
||||
}
|
||||
|
||||
if value, ok := settings["provider"].(string); ok && strings.TrimSpace(value) != "" {
|
||||
provider = strings.TrimSpace(value)
|
||||
}
|
||||
|
||||
dialTimeout = common.DurationSetting(settings, "dial_timeout_seconds", dialTimeout)
|
||||
dialKeepAlive = common.DurationSetting(settings, "dial_keep_alive_seconds", dialKeepAlive)
|
||||
tlsHandshakeTimeout = common.DurationSetting(settings, "tls_handshake_timeout_seconds", tlsHandshakeTimeout)
|
||||
@@ -88,6 +95,7 @@ func (c *coingeckoConnector) ID() mmodel.Driver {
|
||||
return c.id
|
||||
}
|
||||
|
||||
//nolint:cyclop,funlen
|
||||
func (c *coingeckoConnector) FetchTicker(ctx context.Context, symbol string) (*mmodel.Ticker, error) {
|
||||
coinID, vsCurrency, err := parseSymbol(symbol)
|
||||
if err != nil {
|
||||
@@ -98,6 +106,7 @@ func (c *coingeckoConnector) FetchTicker(ctx context.Context, symbol string) (*m
|
||||
if err != nil {
|
||||
return nil, merrors.InternalWrap(err, "coingecko: parse base url")
|
||||
}
|
||||
|
||||
endpoint.Path = strings.TrimRight(endpoint.Path, "/") + "/simple/price"
|
||||
query := endpoint.Query()
|
||||
query.Set("ids", coinID)
|
||||
@@ -113,44 +122,51 @@ func (c *coingeckoConnector) FetchTicker(ctx context.Context, symbol string) (*m
|
||||
resp, err := c.client.Do(req)
|
||||
if err != nil {
|
||||
c.logger.Warn("CoinGecko request failed", zap.String("symbol", symbol), zap.Error(err))
|
||||
|
||||
return nil, merrors.InternalWrap(err, "coingecko: request failed")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
c.logger.Warn("CoinGecko returned non-OK status", zap.String("symbol", symbol), zap.Int("status", resp.StatusCode))
|
||||
|
||||
return nil, merrors.Internal("coingecko: unexpected status " + strconv.Itoa(resp.StatusCode))
|
||||
}
|
||||
|
||||
decoder := json.NewDecoder(resp.Body)
|
||||
decoder.UseNumber()
|
||||
|
||||
var payload map[string]map[string]interface{}
|
||||
if err := decoder.Decode(&payload); err != nil {
|
||||
c.logger.Warn("CoinGecko decode failed", zap.String("symbol", symbol), zap.Error(err))
|
||||
return nil, merrors.InternalWrap(err, "coingecko: decode response")
|
||||
var payload map[string]map[string]any
|
||||
|
||||
decodeErr := decoder.Decode(&payload)
|
||||
if decodeErr != nil {
|
||||
c.logger.Warn("CoinGecko decode failed", zap.String("symbol", symbol), zap.Error(decodeErr))
|
||||
|
||||
return nil, merrors.InternalWrap(decodeErr, "coingecko: decode response")
|
||||
}
|
||||
|
||||
coinData, ok := payload[coinID]
|
||||
if !ok {
|
||||
coinData, coinFound := payload[coinID]
|
||||
if !coinFound {
|
||||
return nil, merrors.Internal("coingecko: coin id not found in response")
|
||||
}
|
||||
priceValue, ok := coinData[vsCurrency]
|
||||
if !ok {
|
||||
|
||||
priceValue, priceFound := coinData[vsCurrency]
|
||||
if !priceFound {
|
||||
return nil, merrors.Internal("coingecko: vs currency not found in response")
|
||||
}
|
||||
|
||||
price, ok := toFloat(priceValue)
|
||||
if !ok || price <= 0 {
|
||||
price, priceOk := toFloat(priceValue)
|
||||
if !priceOk || price <= 0 {
|
||||
return nil, merrors.Internal("coingecko: invalid price value in response")
|
||||
}
|
||||
|
||||
priceStr := strconv.FormatFloat(price, 'f', -1, 64)
|
||||
|
||||
timestamp := time.Now().UnixMilli()
|
||||
if tsValue, ok := coinData["last_updated_at"]; ok {
|
||||
if tsFloat, ok := toFloat(tsValue); ok && tsFloat > 0 {
|
||||
tsMillis := int64(tsFloat * 1000)
|
||||
|
||||
if tsValue, tsFound := coinData["last_updated_at"]; tsFound {
|
||||
if tsFloat, tsOk := toFloat(tsValue); tsOk && tsFloat > 0 {
|
||||
tsMillis := int64(tsFloat * tsToMillis)
|
||||
if tsMillis > 0 {
|
||||
timestamp = tsMillis
|
||||
}
|
||||
@@ -179,14 +195,16 @@ func parseSymbol(symbol string) (string, string, error) {
|
||||
case ':', '/', '-', '_':
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
if len(parts) != 2 {
|
||||
if len(parts) != expectedSymbolParts {
|
||||
return "", "", merrors.InvalidArgument("coingecko: symbol must be <coin_id>/<vs_currency>", "symbol")
|
||||
}
|
||||
|
||||
coinID := strings.TrimSpace(parts[0])
|
||||
|
||||
vsCurrency := strings.TrimSpace(parts[1])
|
||||
if coinID == "" || vsCurrency == "" {
|
||||
return "", "", merrors.InvalidArgument("coingecko: symbol contains empty segments", "symbol")
|
||||
@@ -195,28 +213,31 @@ func parseSymbol(symbol string) (string, string, error) {
|
||||
return coinID, vsCurrency, nil
|
||||
}
|
||||
|
||||
func toFloat(value interface{}) (float64, bool) {
|
||||
switch v := value.(type) {
|
||||
func toFloat(value any) (float64, bool) {
|
||||
switch val := value.(type) {
|
||||
case json.Number:
|
||||
f, err := v.Float64()
|
||||
f, err := val.Float64()
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return f, true
|
||||
case float64:
|
||||
return v, true
|
||||
return val, true
|
||||
case float32:
|
||||
return float64(v), true
|
||||
return float64(val), true
|
||||
case int:
|
||||
return float64(v), true
|
||||
return float64(val), true
|
||||
case int64:
|
||||
return float64(v), true
|
||||
return float64(val), true
|
||||
case uint64:
|
||||
return float64(v), true
|
||||
return float64(val), true
|
||||
case string:
|
||||
if parsed, err := strconv.ParseFloat(v, 64); err == nil {
|
||||
parsed, parseErr := strconv.ParseFloat(val, 64)
|
||||
if parseErr == nil {
|
||||
return parsed, true
|
||||
}
|
||||
}
|
||||
|
||||
return 0, false
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package common
|
||||
package common //nolint:revive // package provides shared market connector utilities
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
@@ -8,39 +8,46 @@ import (
|
||||
)
|
||||
|
||||
// DurationSetting reads a positive duration override from settings or returns def when the value is missing or invalid.
|
||||
//
|
||||
//nolint:cyclop
|
||||
func DurationSetting(settings model.SettingsT, key string, def time.Duration) time.Duration {
|
||||
if settings == nil {
|
||||
return def
|
||||
}
|
||||
|
||||
value, ok := settings[key]
|
||||
if !ok {
|
||||
return def
|
||||
}
|
||||
|
||||
switch v := value.(type) {
|
||||
switch val := value.(type) {
|
||||
case time.Duration:
|
||||
if v > 0 {
|
||||
return v
|
||||
if val > 0 {
|
||||
return val
|
||||
}
|
||||
case int:
|
||||
if v > 0 {
|
||||
return time.Duration(v) * time.Second
|
||||
if val > 0 {
|
||||
return time.Duration(val) * time.Second
|
||||
}
|
||||
case int64:
|
||||
if v > 0 {
|
||||
return time.Duration(v) * time.Second
|
||||
if val > 0 {
|
||||
return time.Duration(val) * time.Second
|
||||
}
|
||||
case float64:
|
||||
if v > 0 {
|
||||
return time.Duration(v * float64(time.Second))
|
||||
if val > 0 {
|
||||
return time.Duration(val * float64(time.Second))
|
||||
}
|
||||
case string:
|
||||
if parsed, err := time.ParseDuration(v); err == nil && parsed > 0 {
|
||||
parsed, parseErr := time.ParseDuration(val)
|
||||
if parseErr == nil && parsed > 0 {
|
||||
return parsed
|
||||
}
|
||||
if seconds, err := strconv.ParseFloat(v, 64); err == nil && seconds > 0 {
|
||||
|
||||
seconds, floatErr := strconv.ParseFloat(val, 64)
|
||||
if floatErr == nil && seconds > 0 {
|
||||
return time.Duration(seconds * float64(time.Second))
|
||||
}
|
||||
}
|
||||
|
||||
return def
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user