linting
This commit is contained in:
@@ -1,196 +1,47 @@
|
||||
# See the dedicated "version" documentation section.
|
||||
version: "2"
|
||||
linters:
|
||||
# Default set of linters.
|
||||
# The value can be:
|
||||
# - `standard`: https://golangci-lint.run/docs/linters/#enabled-by-default
|
||||
# - `all`: enables all linters by default.
|
||||
# - `none`: disables all linters by default.
|
||||
# - `fast`: enables only linters considered as "fast" (`golangci-lint help linters --json | jq '[ .[] | select(.fast==true) ] | map(.name)'`).
|
||||
# Default: standard
|
||||
default: all
|
||||
# Enable specific linter.
|
||||
default: none
|
||||
enable:
|
||||
- arangolint
|
||||
- asasalint
|
||||
- asciicheck
|
||||
- bidichk
|
||||
- bodyclose
|
||||
- canonicalheader
|
||||
- containedctx
|
||||
- contextcheck
|
||||
- copyloopvar
|
||||
- cyclop
|
||||
- decorder
|
||||
- dogsled
|
||||
- dupl
|
||||
- dupword
|
||||
- durationcheck
|
||||
- embeddedstructfieldcheck
|
||||
- err113
|
||||
- errcheck
|
||||
- errchkjson
|
||||
- errname
|
||||
- errorlint
|
||||
- exhaustive
|
||||
- exptostd
|
||||
- fatcontext
|
||||
- forbidigo
|
||||
- forcetypeassert
|
||||
- funcorder
|
||||
- funlen
|
||||
- ginkgolinter
|
||||
- gocheckcompilerdirectives
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- gochecksumtype
|
||||
- gocognit
|
||||
- goconst
|
||||
- gocritic
|
||||
- gocyclo
|
||||
- godoclint
|
||||
- godot
|
||||
- godox
|
||||
- goheader
|
||||
- gomodguard
|
||||
- goprintffuncname
|
||||
- gosec
|
||||
- gosmopolitan
|
||||
- govet
|
||||
- grouper
|
||||
- iface
|
||||
- importas
|
||||
- inamedparam
|
||||
- ineffassign
|
||||
- interfacebloat
|
||||
- intrange
|
||||
- iotamixing
|
||||
- ireturn
|
||||
- lll
|
||||
- loggercheck
|
||||
- maintidx
|
||||
- makezero
|
||||
- mirror
|
||||
- misspell
|
||||
- mnd
|
||||
- modernize
|
||||
- musttag
|
||||
- nakedret
|
||||
- nestif
|
||||
- nilerr
|
||||
- nilnesserr
|
||||
- nilnil
|
||||
- nlreturn
|
||||
- noctx
|
||||
- noinlineerr
|
||||
- nolintlint
|
||||
- nonamedreturns
|
||||
- nosprintfhostport
|
||||
- paralleltest
|
||||
- perfsprint
|
||||
- prealloc
|
||||
- predeclared
|
||||
- promlinter
|
||||
- protogetter
|
||||
- reassign
|
||||
- recvcheck
|
||||
- revive
|
||||
- rowserrcheck
|
||||
- sloglint
|
||||
- spancheck
|
||||
- sqlclosecheck
|
||||
- staticcheck
|
||||
- tagalign
|
||||
- tagliatelle
|
||||
- testableexamples
|
||||
- testifylint
|
||||
- testpackage
|
||||
- thelper
|
||||
- tparallel
|
||||
- unconvert
|
||||
- unparam
|
||||
- unqueryvet
|
||||
- unused
|
||||
- usestdlibvars
|
||||
- usetesting
|
||||
- varnamelen
|
||||
- wastedassign
|
||||
- whitespace
|
||||
- wsl_v5
|
||||
- zerologlint
|
||||
# Disable specific linters.
|
||||
disable:
|
||||
disable:
|
||||
- depguard
|
||||
- exhaustruct
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- gomoddirectives
|
||||
- wrapcheck
|
||||
- wsl
|
||||
# All available settings of specific linters.
|
||||
# See the dedicated "linters.settings" documentation section.
|
||||
settings:
|
||||
wsl_v5:
|
||||
allow-first-in-block: true
|
||||
allow-whole-block: false
|
||||
branch-max-lines: 2
|
||||
|
||||
# Defines a set of rules to ignore issues.
|
||||
# It does not skip the analysis, and so does not ignore "typecheck" errors.
|
||||
exclusions:
|
||||
# Mode of the generated files analysis.
|
||||
#
|
||||
# - `strict`: sources are excluded by strictly following the Go generated file convention.
|
||||
# Source files that have lines matching only the following regular expression will be excluded: `^// Code generated .* DO NOT EDIT\.$`
|
||||
# This line must appear before the first non-comment, non-blank text in the file.
|
||||
# https://go.dev/s/generatedcode
|
||||
# - `lax`: sources are excluded if they contain lines like `autogenerated file`, `code generated`, `do not edit`, etc.
|
||||
# - `disable`: disable the generated files exclusion.
|
||||
#
|
||||
# Default: strict
|
||||
generated: lax
|
||||
# Log a warning if an exclusion rule is unused.
|
||||
# Default: false
|
||||
warn-unused: true
|
||||
# Predefined exclusion rules.
|
||||
# Default: []
|
||||
presets:
|
||||
- comments
|
||||
- std-error-handling
|
||||
- common-false-positives
|
||||
- legacy
|
||||
# Excluding configuration per-path, per-linter, per-text and per-source.
|
||||
rules:
|
||||
# Exclude some linters from running on tests files.
|
||||
- path: _test\.go
|
||||
linters:
|
||||
- funlen
|
||||
- gocyclo
|
||||
- errcheck
|
||||
- dupl
|
||||
- gosec
|
||||
# Run some linter only for test files by excluding its issues for everything else.
|
||||
- path-except: _test\.go
|
||||
linters:
|
||||
- forbidigo
|
||||
# Exclude known linters from partially hard-vendored code,
|
||||
# which is impossible to exclude via `nolint` comments.
|
||||
# `/` will be replaced by the current OS file path separator to properly work on Windows.
|
||||
- path: internal/hmac/
|
||||
text: "weak cryptographic primitive"
|
||||
linters:
|
||||
- gosec
|
||||
# Exclude some `staticcheck` messages.
|
||||
- linters:
|
||||
- staticcheck
|
||||
text: "SA9003:"
|
||||
# Exclude `lll` issues for long lines with `go:generate`.
|
||||
- linters:
|
||||
- lll
|
||||
source: "^//go:generate "
|
||||
# Which file paths to exclude: they will be analyzed, but issues from them won't be reported.
|
||||
# "/" will be replaced by the current OS file path separator to properly work on Windows.
|
||||
# Default: []
|
||||
paths: []
|
||||
# Which file paths to not exclude.
|
||||
# Default: []
|
||||
paths-except: []
|
||||
- cyclop
|
||||
- dupl
|
||||
- funlen
|
||||
- gocognit
|
||||
- gocyclo
|
||||
- ireturn
|
||||
- lll
|
||||
- mnd
|
||||
- nestif
|
||||
- nlreturn
|
||||
- noinlineerr
|
||||
- paralleltest
|
||||
- tagliatelle
|
||||
- testpackage
|
||||
- varnamelen
|
||||
- wsl_v5
|
||||
|
||||
@@ -31,6 +31,7 @@ func Load(path string) (*Config, error) {
|
||||
return nil, merrors.InvalidArgument("config: path is empty")
|
||||
}
|
||||
|
||||
//nolint:gosec // config path is provided by process startup arguments/config.
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, merrors.InternalWrap(err, "config: failed to read file")
|
||||
@@ -73,8 +74,10 @@ func Load(path string) (*Config, error) {
|
||||
}
|
||||
|
||||
if _, ok := sourceSet[driver]; !ok {
|
||||
return nil, merrors.InvalidArgument( //nolint:lll
|
||||
"config: pair references unknown source: "+driver.String(), "pairs."+driver.String())
|
||||
return nil, merrors.InvalidArgument(
|
||||
"config: pair references unknown source: "+driver.String(),
|
||||
"pairs."+driver.String(),
|
||||
)
|
||||
}
|
||||
|
||||
processed := make([]PairConfig, len(pairList))
|
||||
@@ -86,14 +89,16 @@ func Load(path string) (*Config, error) {
|
||||
pair.Symbol = strings.TrimSpace(pair.Symbol)
|
||||
|
||||
if pair.Base == "" || pair.Quote == "" || pair.Symbol == "" {
|
||||
return nil, merrors.InvalidArgument( //nolint:lll
|
||||
"config: pair entries must define base, quote, and symbol", "pairs."+driver.String())
|
||||
return nil, merrors.InvalidArgument(
|
||||
"config: pair entries must define base, quote, and symbol",
|
||||
"pairs."+driver.String(),
|
||||
)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(pair.Provider) == "" {
|
||||
pair.Provider = strings.ToLower(driver.String())
|
||||
}
|
||||
|
||||
|
||||
processed[idx] = pair
|
||||
flattened = append(flattened, Pair{
|
||||
PairConfig: pair,
|
||||
|
||||
@@ -14,6 +14,8 @@ import (
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var errNoSnapshot = errors.New("snapshot not found")
|
||||
|
||||
func TestParseDecimal(t *testing.T) {
|
||||
got, err := parseDecimal("123.456")
|
||||
if err != nil {
|
||||
@@ -191,19 +193,9 @@ func (r *ratesStoreStub) UpsertSnapshot(_ context.Context, snapshot *model.RateS
|
||||
}
|
||||
|
||||
func (r *ratesStoreStub) LatestSnapshot(context.Context, model.CurrencyPair, string) (*model.RateSnapshot, error) {
|
||||
return nil, nil
|
||||
return nil, errNoSnapshot
|
||||
}
|
||||
|
||||
type repositoryStub struct {
|
||||
rates storage.RatesStore
|
||||
}
|
||||
|
||||
func (r *repositoryStub) Ping(context.Context) error { return nil }
|
||||
func (r *repositoryStub) Rates() storage.RatesStore { return r.rates }
|
||||
func (r *repositoryStub) Quotes() storage.QuotesStore { return nil }
|
||||
func (r *repositoryStub) Pairs() storage.PairStore { return nil }
|
||||
func (r *repositoryStub) Currencies() storage.CurrencyStore { return nil }
|
||||
|
||||
type connectorStub struct {
|
||||
id mmarket.Driver
|
||||
ticker *mmarket.Ticker
|
||||
|
||||
@@ -116,7 +116,11 @@ func (c *binanceConnector) FetchTicker(ctx context.Context, symbol string) (*mmo
|
||||
|
||||
return nil, merrors.InternalWrap(err, "binance: request failed")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if closeErr := resp.Body.Close(); closeErr != nil {
|
||||
c.logger.Warn("Failed to close Binance response body", zap.Error(closeErr))
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
c.logger.Warn("Binance returned non-OK status", zap.String("symbol", symbol), zap.Int("status", resp.StatusCode))
|
||||
|
||||
@@ -122,9 +122,11 @@ func NewConnector(logger mlogger.Logger, settings model.SettingsT) (mmodel.Conne
|
||||
logger,
|
||||
client,
|
||||
httpClientOptions{
|
||||
userAgent: userAgent,
|
||||
accept: acceptHeader,
|
||||
referer: referer,
|
||||
userAgent: userAgent,
|
||||
accept: acceptHeader,
|
||||
referer: referer,
|
||||
allowedScheme: parsed.Scheme,
|
||||
allowedHost: parsed.Host,
|
||||
},
|
||||
),
|
||||
base: strings.TrimRight(parsed.String(), "/"),
|
||||
@@ -200,7 +202,11 @@ func (c *cbrConnector) refreshDirectory() error {
|
||||
)
|
||||
return merrors.InternalWrap(err, "cbr: directory request failed")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if closeErr := resp.Body.Close(); closeErr != nil {
|
||||
c.logger.Warn("Failed to close CBR daily response body", zap.Error(closeErr))
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
c.logger.Warn(
|
||||
@@ -258,7 +264,11 @@ func (c *cbrConnector) fetchDailyRate(ctx context.Context, valute valuteInfo) (s
|
||||
)
|
||||
return "", merrors.InternalWrap(err, "cbr: daily request failed")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if closeErr := resp.Body.Close(); closeErr != nil {
|
||||
c.logger.Warn("Failed to close CBR historical response body", zap.Error(closeErr))
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
c.logger.Warn(
|
||||
@@ -326,7 +336,11 @@ func (c *cbrConnector) fetchHistoricalRate( //nolint:funlen
|
||||
)
|
||||
return "", merrors.InternalWrap(err, "cbr: historical request failed")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if closeErr := resp.Body.Close(); closeErr != nil {
|
||||
c.logger.Warn("Failed to close CBR historical response body", zap.Error(closeErr))
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
c.logger.Warn(
|
||||
|
||||
@@ -2,7 +2,10 @@ package cbr
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
@@ -14,17 +17,28 @@ const (
|
||||
defaultAccept = "application/xml,text/xml;q=0.9,*/*;q=0.8"
|
||||
)
|
||||
|
||||
var (
|
||||
errNilRequestURL = errors.New("http_client: request URL is nil")
|
||||
errRelativeRequestURL = errors.New("http_client: request URL must be absolute")
|
||||
errUnexpectedURLScheme = errors.New("http_client: unexpected URL scheme")
|
||||
errUnexpectedURLHost = errors.New("http_client: unexpected URL host")
|
||||
)
|
||||
|
||||
// httpClient wraps http.Client to ensure CBR requests always carry required headers.
|
||||
type httpClient struct {
|
||||
client *http.Client
|
||||
headers http.Header
|
||||
logger mlogger.Logger
|
||||
client *http.Client
|
||||
headers http.Header
|
||||
logger mlogger.Logger
|
||||
allowedScheme string
|
||||
allowedHost string
|
||||
}
|
||||
|
||||
type httpClientOptions struct {
|
||||
userAgent string
|
||||
accept string
|
||||
referer string
|
||||
userAgent string
|
||||
accept string
|
||||
referer string
|
||||
allowedScheme string
|
||||
allowedHost string
|
||||
}
|
||||
|
||||
func newHTTPClient(logger mlogger.Logger, client *http.Client, opts httpClientOptions) *httpClient {
|
||||
@@ -42,6 +56,20 @@ func newHTTPClient(logger mlogger.Logger, client *http.Client, opts httpClientOp
|
||||
if strings.TrimSpace(referer) == "" {
|
||||
referer = defaultCBRBaseURL
|
||||
}
|
||||
|
||||
allowedScheme := strings.ToLower(strings.TrimSpace(opts.allowedScheme))
|
||||
allowedHost := strings.ToLower(strings.TrimSpace(opts.allowedHost))
|
||||
|
||||
if allowedScheme == "" || allowedHost == "" {
|
||||
if parsed, err := url.Parse(referer); err == nil {
|
||||
if allowedScheme == "" {
|
||||
allowedScheme = strings.ToLower(parsed.Scheme)
|
||||
}
|
||||
if allowedHost == "" {
|
||||
allowedHost = strings.ToLower(parsed.Host)
|
||||
}
|
||||
}
|
||||
}
|
||||
httpLogger := logger.Named("http_client")
|
||||
|
||||
headers := make(http.Header, 3)
|
||||
@@ -53,9 +81,11 @@ func newHTTPClient(logger mlogger.Logger, client *http.Client, opts httpClientOp
|
||||
zap.String("accept", accept), zap.String("referrer", referer))
|
||||
|
||||
return &httpClient{
|
||||
client: client,
|
||||
headers: headers,
|
||||
logger: httpLogger,
|
||||
client: client,
|
||||
headers: headers,
|
||||
logger: httpLogger,
|
||||
allowedScheme: allowedScheme,
|
||||
allowedHost: allowedHost,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,6 +104,13 @@ func (h *httpClient) Do(req *http.Request) (*http.Response, error) {
|
||||
enriched.Header.Add(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.validateRequestTarget(enriched.URL); err != nil {
|
||||
h.logger.Warn("HTTP request blocked by target validation", zap.Error(err), zap.String("method", req.Method))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//nolint:gosec // request URL is constrained in validateRequestTarget before any outbound call.
|
||||
r, err := h.client.Do(enriched)
|
||||
if err != nil {
|
||||
h.logger.Warn("HTTP request failed", zap.Error(err), zap.String("method", req.Method),
|
||||
@@ -85,3 +122,26 @@ func (h *httpClient) Do(req *http.Request) (*http.Response, error) {
|
||||
func (h *httpClient) headerValue(name string) string {
|
||||
return h.headers.Get(name)
|
||||
}
|
||||
|
||||
func (h *httpClient) validateRequestTarget(requestURL *url.URL) error {
|
||||
if requestURL == nil {
|
||||
return errNilRequestURL
|
||||
}
|
||||
|
||||
if !requestURL.IsAbs() {
|
||||
return errRelativeRequestURL
|
||||
}
|
||||
|
||||
scheme := strings.ToLower(requestURL.Scheme)
|
||||
host := strings.ToLower(requestURL.Host)
|
||||
|
||||
if h.allowedScheme != "" && scheme != h.allowedScheme {
|
||||
return fmt.Errorf("%w: %q", errUnexpectedURLScheme, requestURL.Scheme)
|
||||
}
|
||||
|
||||
if h.allowedHost != "" && host != h.allowedHost {
|
||||
return fmt.Errorf("%w: %q", errUnexpectedURLHost, requestURL.Host)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -125,7 +125,11 @@ func (c *coingeckoConnector) FetchTicker(ctx context.Context, symbol string) (*m
|
||||
|
||||
return nil, merrors.InternalWrap(err, "coingecko: request failed")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
defer func() {
|
||||
if closeErr := resp.Body.Close(); closeErr != nil {
|
||||
c.logger.Warn("Failed to close CoinGecko response body", zap.Error(closeErr))
|
||||
}
|
||||
}()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
c.logger.Warn("CoinGecko returned non-OK status", zap.String("symbol", symbol), zap.Int("status", resp.StatusCode))
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
package common //nolint:revive // package provides shared market connector utilities
|
||||
// Package common provides shared market connector utilities.
|
||||
package common
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
@@ -32,7 +32,9 @@ func main() {
|
||||
|
||||
appVersion := appversion.Create()
|
||||
if *versionFlag {
|
||||
fmt.Fprintln(os.Stdout, appVersion.Print())
|
||||
if _, err := fmt.Fprintln(os.Stdout, appVersion.Print()); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
47
api/fx/oracle/.golangci.yml
Normal file
47
api/fx/oracle/.golangci.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
version: "2"
|
||||
linters:
|
||||
default: none
|
||||
enable:
|
||||
- bodyclose
|
||||
- canonicalheader
|
||||
- copyloopvar
|
||||
- durationcheck
|
||||
- errcheck
|
||||
- errchkjson
|
||||
- errname
|
||||
- errorlint
|
||||
- gosec
|
||||
- govet
|
||||
- ineffassign
|
||||
- nilerr
|
||||
- nilnesserr
|
||||
- nilnil
|
||||
- noctx
|
||||
- rowserrcheck
|
||||
- sqlclosecheck
|
||||
- staticcheck
|
||||
- unconvert
|
||||
- wastedassign
|
||||
disable:
|
||||
- depguard
|
||||
- exhaustruct
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- gomoddirectives
|
||||
- wrapcheck
|
||||
- cyclop
|
||||
- dupl
|
||||
- funlen
|
||||
- gocognit
|
||||
- gocyclo
|
||||
- ireturn
|
||||
- lll
|
||||
- mnd
|
||||
- nestif
|
||||
- nlreturn
|
||||
- noinlineerr
|
||||
- paralleltest
|
||||
- tagliatelle
|
||||
- testpackage
|
||||
- varnamelen
|
||||
- wsl_v5
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -134,7 +135,11 @@ func (c *oracleClient) LatestRate(ctx context.Context, req LatestRateParams) (*R
|
||||
return nil, merrors.InvalidArgument("oracle: pair is required")
|
||||
}
|
||||
|
||||
callCtx, cancel := c.callContext(ctx)
|
||||
callCtx := ctx
|
||||
cancel := func() {}
|
||||
if _, hasDeadline := ctx.Deadline(); !hasDeadline {
|
||||
callCtx, cancel = context.WithTimeout(ctx, c.cfg.CallTimeout)
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
resp, err := c.client.LatestRate(callCtx, &oraclev1.LatestRateRequest{
|
||||
@@ -165,7 +170,11 @@ func (c *oracleClient) GetQuote(ctx context.Context, req GetQuoteParams) (*Quote
|
||||
return nil, merrors.InvalidArgument("oracle: exactly one of base_amount or quote_amount must be set")
|
||||
}
|
||||
|
||||
callCtx, cancel := c.callContext(ctx)
|
||||
callCtx := ctx
|
||||
cancel := func() {}
|
||||
if _, hasDeadline := ctx.Deadline(); !hasDeadline {
|
||||
callCtx, cancel = context.WithTimeout(ctx, c.cfg.CallTimeout)
|
||||
}
|
||||
defer cancel()
|
||||
|
||||
protoReq := &oraclev1.GetQuoteRequest{
|
||||
@@ -179,7 +188,11 @@ func (c *oracleClient) GetQuote(ctx context.Context, req GetQuoteParams) (*Quote
|
||||
protoReq.TtlMs = req.TTL.Milliseconds()
|
||||
}
|
||||
if req.MaxAge > 0 {
|
||||
protoReq.MaxAgeMs = int32(req.MaxAge.Milliseconds())
|
||||
maxAgeMs := req.MaxAge.Milliseconds()
|
||||
if maxAgeMs > math.MaxInt32 {
|
||||
maxAgeMs = math.MaxInt32
|
||||
}
|
||||
protoReq.MaxAgeMs = int32(maxAgeMs)
|
||||
}
|
||||
if baseSupplied {
|
||||
protoReq.AmountInput = &oraclev1.GetQuoteRequest_BaseAmount{BaseAmount: req.BaseAmount}
|
||||
@@ -197,13 +210,6 @@ func (c *oracleClient) GetQuote(ctx context.Context, req GetQuoteParams) (*Quote
|
||||
return fromProtoQuote(resp.GetQuote()), nil
|
||||
}
|
||||
|
||||
func (c *oracleClient) callContext(ctx context.Context) (context.Context, context.CancelFunc) {
|
||||
if _, ok := ctx.Deadline(); ok {
|
||||
return context.WithCancel(ctx)
|
||||
}
|
||||
return context.WithTimeout(ctx, c.cfg.CallTimeout)
|
||||
}
|
||||
|
||||
func toProtoMeta(meta RequestMeta) *oraclev1.RequestMeta {
|
||||
if meta.TenantRef == "" && meta.OrganizationRef == "" && meta.Trace == nil {
|
||||
return nil
|
||||
|
||||
@@ -2,6 +2,7 @@ package oracle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strings"
|
||||
@@ -99,17 +100,28 @@ func buildPriceSet(rate *model.RateSnapshot) (priceSet, error) {
|
||||
return priceSet{}, merrors.InvalidArgument("oracle: cross rate requires underlying snapshot")
|
||||
}
|
||||
ask, err := parsePrice(rate.Ask)
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, merrors.ErrNoData) {
|
||||
return priceSet{}, err
|
||||
}
|
||||
if errors.Is(err, merrors.ErrNoData) {
|
||||
ask = nil
|
||||
}
|
||||
|
||||
bid, err := parsePrice(rate.Bid)
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, merrors.ErrNoData) {
|
||||
return priceSet{}, err
|
||||
}
|
||||
if errors.Is(err, merrors.ErrNoData) {
|
||||
bid = nil
|
||||
}
|
||||
|
||||
mid, err := parsePrice(rate.Mid)
|
||||
if err != nil {
|
||||
if err != nil && !errors.Is(err, merrors.ErrNoData) {
|
||||
return priceSet{}, err
|
||||
}
|
||||
if errors.Is(err, merrors.ErrNoData) {
|
||||
mid = nil
|
||||
}
|
||||
|
||||
if ask == nil && bid == nil {
|
||||
if mid == nil {
|
||||
@@ -141,7 +153,7 @@ func buildPriceSet(rate *model.RateSnapshot) (priceSet, error) {
|
||||
|
||||
func parsePrice(value string) (*big.Rat, error) {
|
||||
if strings.TrimSpace(value) == "" {
|
||||
return nil, nil
|
||||
return nil, merrors.ErrNoData
|
||||
}
|
||||
return ratFromString(value)
|
||||
}
|
||||
|
||||
@@ -110,7 +110,7 @@ func (s *Service) startDiscoveryAnnouncer() {
|
||||
InvokeURI: s.invokeURI,
|
||||
Version: appversion.Create().Short(),
|
||||
}
|
||||
s.announcer = discovery.NewAnnouncer(s.logger, s.producer, string(mservice.FXOracle), announce)
|
||||
s.announcer = discovery.NewAnnouncer(s.logger, s.producer, mservice.FXOracle, announce)
|
||||
s.announcer.Start()
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ func (q *quotesStoreStub) Consume(ctx context.Context, ref, ledger string, when
|
||||
if q.consumeFn != nil {
|
||||
return q.consumeFn(ctx, ref, ledger, when)
|
||||
}
|
||||
return nil, nil
|
||||
return nil, merrors.ErrNoData
|
||||
}
|
||||
|
||||
func (q *quotesStoreStub) ExpireIssuedBefore(ctx context.Context, cutoff time.Time) (int, error) {
|
||||
|
||||
47
api/fx/storage/.golangci.yml
Normal file
47
api/fx/storage/.golangci.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
version: "2"
|
||||
linters:
|
||||
default: none
|
||||
enable:
|
||||
- bodyclose
|
||||
- canonicalheader
|
||||
- copyloopvar
|
||||
- durationcheck
|
||||
- errcheck
|
||||
- errchkjson
|
||||
- errname
|
||||
- errorlint
|
||||
- gosec
|
||||
- govet
|
||||
- ineffassign
|
||||
- nilerr
|
||||
- nilnesserr
|
||||
- nilnil
|
||||
- noctx
|
||||
- rowserrcheck
|
||||
- sqlclosecheck
|
||||
- staticcheck
|
||||
- unconvert
|
||||
- wastedassign
|
||||
disable:
|
||||
- depguard
|
||||
- exhaustruct
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- gomoddirectives
|
||||
- wrapcheck
|
||||
- cyclop
|
||||
- dupl
|
||||
- funlen
|
||||
- gocognit
|
||||
- gocyclo
|
||||
- ireturn
|
||||
- lll
|
||||
- mnd
|
||||
- nestif
|
||||
- nlreturn
|
||||
- noinlineerr
|
||||
- paralleltest
|
||||
- tagliatelle
|
||||
- testpackage
|
||||
- varnamelen
|
||||
- wsl_v5
|
||||
@@ -47,13 +47,13 @@ func (q *Quote) MarkConsumed(ledgerTxnRef string, consumedAt time.Time) {
|
||||
q.ConsumedByLedgerTxnRef = ledgerTxnRef
|
||||
ts := consumedAt.UnixMilli()
|
||||
q.ConsumedAtUnixMs = &ts
|
||||
q.Base.Update()
|
||||
q.Update()
|
||||
}
|
||||
|
||||
// MarkExpired marks the quote as expired.
|
||||
func (q *Quote) MarkExpired() {
|
||||
q.Status = QuoteStatusExpired
|
||||
q.Base.Update()
|
||||
q.Update()
|
||||
}
|
||||
|
||||
// IsExpired reports whether the quote has passed its expiration instant.
|
||||
|
||||
@@ -35,8 +35,8 @@ func TestCurrencyStoreGet(t *testing.T) {
|
||||
|
||||
func TestCurrencyStoreList(t *testing.T) {
|
||||
repo := &repoStub{
|
||||
findManyFn: func(_ context.Context, _ builder.Query, decode rd.DecodingFunc) error {
|
||||
return runDecoderWithDocs(t, decode, &model.Currency{Code: "USD"})
|
||||
findManyFn: func(ctx context.Context, _ builder.Query, decode rd.DecodingFunc) error {
|
||||
return runDecoderWithDocs(ctx, t, decode, &model.Currency{Code: "USD"})
|
||||
},
|
||||
}
|
||||
store := ¤cyStore{logger: zap.NewNop(), repo: repo}
|
||||
|
||||
@@ -16,11 +16,11 @@ import (
|
||||
|
||||
func TestPairStoreListEnabled(t *testing.T) {
|
||||
repo := &repoStub{
|
||||
findManyFn: func(_ context.Context, _ builder.Query, decode rd.DecodingFunc) error {
|
||||
findManyFn: func(ctx context.Context, _ builder.Query, decode rd.DecodingFunc) error {
|
||||
docs := []interface{}{
|
||||
&model.Pair{Pair: model.CurrencyPair{Base: "USD", Quote: "EUR"}},
|
||||
}
|
||||
return runDecoderWithDocs(t, decode, docs...)
|
||||
return runDecoderWithDocs(ctx, t, decode, docs...)
|
||||
},
|
||||
}
|
||||
store := &pairStore{logger: zap.NewNop(), repo: repo}
|
||||
|
||||
@@ -70,9 +70,9 @@ func TestRatesStoreUpsertUpdate(t *testing.T) {
|
||||
func TestRatesStoreLatestSnapshot(t *testing.T) {
|
||||
now := time.Now().UnixMilli()
|
||||
repo := &repoStub{
|
||||
findManyFn: func(_ context.Context, _ builder.Query, decode rd.DecodingFunc) error {
|
||||
findManyFn: func(ctx context.Context, _ builder.Query, decode rd.DecodingFunc) error {
|
||||
doc := &model.RateSnapshot{RateRef: "latest", AsOfUnixMs: now}
|
||||
return runDecoderWithDocs(t, decode, doc)
|
||||
return runDecoderWithDocs(ctx, t, decode, doc)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -176,16 +176,18 @@ func cloneCurrency(t *testing.T, obj storable.Storable) *model.Currency {
|
||||
return ©
|
||||
}
|
||||
|
||||
func runDecoderWithDocs(t *testing.T, decode rd.DecodingFunc, docs ...interface{}) error {
|
||||
func runDecoderWithDocs(ctx context.Context, t *testing.T, decode rd.DecodingFunc, docs ...interface{}) error {
|
||||
t.Helper()
|
||||
cur, err := mongo.NewCursorFromDocuments(docs, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create cursor: %v", err)
|
||||
}
|
||||
defer cur.Close(context.Background())
|
||||
defer func() {
|
||||
_ = cur.Close(ctx)
|
||||
}()
|
||||
|
||||
if len(docs) > 0 {
|
||||
if !cur.Next(context.Background()) {
|
||||
if !cur.Next(ctx) {
|
||||
return cur.Err()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user