linting
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -10,7 +10,9 @@ generate_protos.sh
|
|||||||
update_dep.sh
|
update_dep.sh
|
||||||
test.sh
|
test.sh
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
.gocache/
|
.gocache/
|
||||||
|
.golangci-cache/
|
||||||
.cache/
|
.cache/
|
||||||
.claude/
|
.claude/
|
||||||
|
|
||||||
|
|||||||
20
Makefile
20
Makefile
@@ -1,10 +1,12 @@
|
|||||||
# Sendico Development Environment - Makefile
|
# Sendico Development Environment - Makefile
|
||||||
# Docker Compose + Makefile build system
|
# Docker Compose + Makefile build system
|
||||||
|
|
||||||
.PHONY: help init build up down restart logs rebuild clean vault-init proto generate generate-api generate-frontend update update-api update-frontend test test-api test-frontend backend-up backend-down backend-rebuild
|
.PHONY: help init build up down restart logs rebuild clean vault-init proto generate generate-api generate-frontend update update-api update-frontend test test-api test-frontend lint-api backend-up backend-down backend-rebuild
|
||||||
|
|
||||||
COMPOSE := docker compose -f docker-compose.dev.yml --env-file .env.dev
|
COMPOSE := docker compose -f docker-compose.dev.yml --env-file .env.dev
|
||||||
SERVICE ?=
|
SERVICE ?=
|
||||||
|
API_GOCACHE ?= $(CURDIR)/.gocache
|
||||||
|
API_GOLANGCI_LINT_CACHE ?= $(CURDIR)/.golangci-cache
|
||||||
BACKEND_SERVICES := \
|
BACKEND_SERVICES := \
|
||||||
dev-discovery \
|
dev-discovery \
|
||||||
dev-fx-oracle \
|
dev-fx-oracle \
|
||||||
@@ -76,6 +78,7 @@ help:
|
|||||||
@echo " make test Run all tests (API + frontend)"
|
@echo " make test Run all tests (API + frontend)"
|
||||||
@echo " make test-api Run Go API tests only"
|
@echo " make test-api Run Go API tests only"
|
||||||
@echo " make test-frontend Run Flutter tests only"
|
@echo " make test-frontend Run Flutter tests only"
|
||||||
|
@echo " make lint-api Run golangci-lint across all API Go modules"
|
||||||
@echo " make health Check service health"
|
@echo " make health Check service health"
|
||||||
@echo ""
|
@echo ""
|
||||||
@echo "Examples:"
|
@echo "Examples:"
|
||||||
@@ -374,3 +377,18 @@ test-frontend:
|
|||||||
@cd frontend/pshared && flutter test
|
@cd frontend/pshared && flutter test
|
||||||
@cd frontend/pweb && flutter test
|
@cd frontend/pweb && flutter test
|
||||||
@echo "$(GREEN)✅ All frontend tests passed$(NC)"
|
@echo "$(GREEN)✅ All frontend tests passed$(NC)"
|
||||||
|
|
||||||
|
# Run Go API linting
|
||||||
|
lint-api:
|
||||||
|
@echo "$(GREEN)Running API linting...$(NC)"
|
||||||
|
@mkdir -p "$(API_GOCACHE)" "$(API_GOLANGCI_LINT_CACHE)"
|
||||||
|
@failed=""; \
|
||||||
|
for dir in $$(find api -name go.mod -exec dirname {} \;); do \
|
||||||
|
echo "Linting $$dir..."; \
|
||||||
|
(cd "$$dir" && GOCACHE="$(API_GOCACHE)" GOLANGCI_LINT_CACHE="$(API_GOLANGCI_LINT_CACHE)" golangci-lint run --allow-serial-runners --allow-parallel-runners ./...) || failed="$$failed $$dir"; \
|
||||||
|
done; \
|
||||||
|
if [ -n "$$failed" ]; then \
|
||||||
|
echo "$(YELLOW)Lint failed:$$failed$(NC)"; \
|
||||||
|
exit 1; \
|
||||||
|
fi
|
||||||
|
@echo "$(GREEN)✅ All API lint checks passed$(NC)"
|
||||||
|
|||||||
@@ -1,196 +1,47 @@
|
|||||||
# See the dedicated "version" documentation section.
|
|
||||||
version: "2"
|
version: "2"
|
||||||
linters:
|
linters:
|
||||||
# Default set of linters.
|
default: none
|
||||||
# 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.
|
|
||||||
enable:
|
enable:
|
||||||
- arangolint
|
|
||||||
- asasalint
|
|
||||||
- asciicheck
|
|
||||||
- bidichk
|
|
||||||
- bodyclose
|
- bodyclose
|
||||||
- canonicalheader
|
- canonicalheader
|
||||||
- containedctx
|
|
||||||
- contextcheck
|
|
||||||
- copyloopvar
|
- copyloopvar
|
||||||
- cyclop
|
|
||||||
- decorder
|
|
||||||
- dogsled
|
|
||||||
- dupl
|
|
||||||
- dupword
|
|
||||||
- durationcheck
|
- durationcheck
|
||||||
- embeddedstructfieldcheck
|
|
||||||
- err113
|
|
||||||
- errcheck
|
- errcheck
|
||||||
- errchkjson
|
- errchkjson
|
||||||
- errname
|
- errname
|
||||||
- errorlint
|
- errorlint
|
||||||
- exhaustive
|
|
||||||
- exptostd
|
|
||||||
- fatcontext
|
|
||||||
- forbidigo
|
|
||||||
- forcetypeassert
|
|
||||||
- funcorder
|
|
||||||
- funlen
|
|
||||||
- ginkgolinter
|
|
||||||
- gocheckcompilerdirectives
|
|
||||||
- gochecknoglobals
|
|
||||||
- gochecknoinits
|
|
||||||
- gochecksumtype
|
|
||||||
- gocognit
|
|
||||||
- goconst
|
|
||||||
- gocritic
|
|
||||||
- gocyclo
|
|
||||||
- godoclint
|
|
||||||
- godot
|
|
||||||
- godox
|
|
||||||
- goheader
|
|
||||||
- gomodguard
|
|
||||||
- goprintffuncname
|
|
||||||
- gosec
|
- gosec
|
||||||
- gosmopolitan
|
|
||||||
- govet
|
- govet
|
||||||
- grouper
|
|
||||||
- iface
|
|
||||||
- importas
|
|
||||||
- inamedparam
|
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- interfacebloat
|
|
||||||
- intrange
|
|
||||||
- iotamixing
|
|
||||||
- ireturn
|
|
||||||
- lll
|
|
||||||
- loggercheck
|
|
||||||
- maintidx
|
|
||||||
- makezero
|
|
||||||
- mirror
|
|
||||||
- misspell
|
|
||||||
- mnd
|
|
||||||
- modernize
|
|
||||||
- musttag
|
|
||||||
- nakedret
|
|
||||||
- nestif
|
|
||||||
- nilerr
|
- nilerr
|
||||||
- nilnesserr
|
- nilnesserr
|
||||||
- nilnil
|
- nilnil
|
||||||
- nlreturn
|
|
||||||
- noctx
|
- noctx
|
||||||
- noinlineerr
|
|
||||||
- nolintlint
|
|
||||||
- nonamedreturns
|
|
||||||
- nosprintfhostport
|
|
||||||
- paralleltest
|
|
||||||
- perfsprint
|
|
||||||
- prealloc
|
|
||||||
- predeclared
|
|
||||||
- promlinter
|
|
||||||
- protogetter
|
|
||||||
- reassign
|
|
||||||
- recvcheck
|
|
||||||
- revive
|
|
||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- sloglint
|
|
||||||
- spancheck
|
|
||||||
- sqlclosecheck
|
- sqlclosecheck
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- tagalign
|
|
||||||
- tagliatelle
|
|
||||||
- testableexamples
|
|
||||||
- testifylint
|
|
||||||
- testpackage
|
|
||||||
- thelper
|
|
||||||
- tparallel
|
|
||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
|
||||||
- unqueryvet
|
|
||||||
- unused
|
|
||||||
- usestdlibvars
|
|
||||||
- usetesting
|
|
||||||
- varnamelen
|
|
||||||
- wastedassign
|
- wastedassign
|
||||||
- whitespace
|
|
||||||
- wsl_v5
|
|
||||||
- zerologlint
|
|
||||||
# Disable specific linters.
|
|
||||||
disable:
|
disable:
|
||||||
- depguard
|
- depguard
|
||||||
- exhaustruct
|
- exhaustruct
|
||||||
- gochecknoglobals
|
- gochecknoglobals
|
||||||
|
- gochecknoinits
|
||||||
- gomoddirectives
|
- gomoddirectives
|
||||||
- wsl
|
|
||||||
- wrapcheck
|
- wrapcheck
|
||||||
# All available settings of specific linters.
|
- cyclop
|
||||||
# See the dedicated "linters.settings" documentation section.
|
- dupl
|
||||||
settings:
|
- funlen
|
||||||
wsl_v5:
|
- gocognit
|
||||||
allow-first-in-block: true
|
- gocyclo
|
||||||
allow-whole-block: false
|
- ireturn
|
||||||
branch-max-lines: 2
|
- lll
|
||||||
|
- mnd
|
||||||
# Defines a set of rules to ignore issues.
|
- nestif
|
||||||
# It does not skip the analysis, and so does not ignore "typecheck" errors.
|
- nlreturn
|
||||||
exclusions:
|
- noinlineerr
|
||||||
# Mode of the generated files analysis.
|
- paralleltest
|
||||||
#
|
- tagliatelle
|
||||||
# - `strict`: sources are excluded by strictly following the Go generated file convention.
|
- testpackage
|
||||||
# Source files that have lines matching only the following regular expression will be excluded: `^// Code generated .* DO NOT EDIT\.$`
|
- varnamelen
|
||||||
# This line must appear before the first non-comment, non-blank text in the file.
|
- wsl_v5
|
||||||
# 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: []
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package docstore
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -36,8 +37,12 @@ func (s *LocalStore) Save(ctx context.Context, key string, data []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
path := filepath.Join(s.rootPath, filepath.Clean(key))
|
path, err := resolveStoragePath(s.rootPath, key)
|
||||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil {
|
||||||
s.logger.Warn("Failed to create document directory", zap.Error(err), zap.String("path", path))
|
s.logger.Warn("Failed to create document directory", zap.Error(err), zap.String("path", path))
|
||||||
|
|
||||||
return err
|
return err
|
||||||
@@ -56,8 +61,12 @@ func (s *LocalStore) Load(ctx context.Context, key string) ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
path := filepath.Join(s.rootPath, filepath.Clean(key))
|
path, err := resolveStoragePath(s.rootPath, key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
//nolint:gosec // path is constrained by resolveStoragePath to stay within configured root.
|
||||||
data, err := os.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Warn("Failed to read document file", zap.Error(err), zap.String("path", path))
|
s.logger.Warn("Failed to read document file", zap.Error(err), zap.String("path", path))
|
||||||
@@ -69,3 +78,32 @@ func (s *LocalStore) Load(ctx context.Context, key string) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var _ Store = (*LocalStore)(nil)
|
var _ Store = (*LocalStore)(nil)
|
||||||
|
|
||||||
|
func resolveStoragePath(rootPath string, key string) (string, error) {
|
||||||
|
cleanKey := filepath.Clean(strings.TrimSpace(key))
|
||||||
|
if cleanKey == "" || cleanKey == "." {
|
||||||
|
return "", merrors.InvalidArgument("docstore: key is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if filepath.IsAbs(cleanKey) {
|
||||||
|
return "", merrors.InvalidArgument("docstore: absolute keys are not allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
rootAbs, err := filepath.Abs(rootPath)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("resolve local store root: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
path := filepath.Join(rootAbs, cleanKey)
|
||||||
|
pathAbs, err := filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("resolve local store path: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := rootAbs + string(filepath.Separator)
|
||||||
|
if pathAbs != rootAbs && !strings.HasPrefix(pathAbs, prefix) {
|
||||||
|
return "", merrors.InvalidArgument("docstore: key escapes root path")
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathAbs, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -124,7 +124,11 @@ func (s *S3Store) Load(ctx context.Context, key string) ([]byte, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
defer obj.Body.Close()
|
defer func() {
|
||||||
|
if closeErr := obj.Body.Close(); closeErr != nil {
|
||||||
|
s.logger.Warn("Failed to close document body", zap.Error(closeErr), zap.String("key", key))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
return io.ReadAll(obj.Body)
|
return io.ReadAll(obj.Body)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,12 +247,6 @@ func (s *Service) startDiscoveryAnnouncer() {
|
|||||||
s.announcer.Start()
|
s.announcer.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
type serviceError string
|
|
||||||
|
|
||||||
func (e serviceError) Error() string {
|
|
||||||
return string(e)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Service) generateActPDF(snapshot model.ActSnapshot) ([]byte, string, error) {
|
func (s *Service) generateActPDF(snapshot model.ActSnapshot) ([]byte, string, error) {
|
||||||
blocks, err := s.template.Render(snapshot)
|
blocks, err := s.template.Render(snapshot)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -54,34 +54,6 @@ func (s *stubDocumentsStore) ListByPaymentRefs(_ context.Context, _ []string) ([
|
|||||||
|
|
||||||
var _ storage.DocumentsStore = (*stubDocumentsStore)(nil)
|
var _ storage.DocumentsStore = (*stubDocumentsStore)(nil)
|
||||||
|
|
||||||
type memDocStore struct {
|
|
||||||
data map[string][]byte
|
|
||||||
saveCount int
|
|
||||||
loadCount int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memDocStore) Save(_ context.Context, key string, data []byte) error {
|
|
||||||
m.saveCount++
|
|
||||||
copyData := make([]byte, len(data))
|
|
||||||
copy(copyData, data)
|
|
||||||
m.data[key] = copyData
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memDocStore) Load(_ context.Context, key string) ([]byte, error) {
|
|
||||||
m.loadCount++
|
|
||||||
data := m.data[key]
|
|
||||||
copyData := make([]byte, len(data))
|
|
||||||
copy(copyData, data)
|
|
||||||
|
|
||||||
return copyData, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *memDocStore) Counts() (int, int) {
|
|
||||||
return m.saveCount, m.loadCount
|
|
||||||
}
|
|
||||||
|
|
||||||
type stubTemplate struct {
|
type stubTemplate struct {
|
||||||
blocks []renderer.Block
|
blocks []renderer.Block
|
||||||
calls int
|
calls int
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ type acceptanceTemplateData struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newTemplateRenderer(path string) (*templateRenderer, error) {
|
func newTemplateRenderer(path string) (*templateRenderer, error) {
|
||||||
|
//nolint:gosec // template file path is provided by trusted service configuration.
|
||||||
data, err := os.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read template: %w", err)
|
return nil, fmt.Errorf("read template: %w", err)
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ func encodeUTF16BE(text string, withBOM bool) []byte {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, v := range encoded {
|
for _, v := range encoded {
|
||||||
out = append(out, byte(v>>8), byte(v))
|
out = append(out, byte(v>>8), byte(v&0x00FF))
|
||||||
}
|
}
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|||||||
@@ -1,198 +1,47 @@
|
|||||||
# See the dedicated "version" documentation section.
|
|
||||||
version: "2"
|
version: "2"
|
||||||
linters:
|
linters:
|
||||||
# Default set of linters.
|
default: none
|
||||||
# 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.
|
|
||||||
enable:
|
enable:
|
||||||
- arangolint
|
|
||||||
- asasalint
|
|
||||||
- asciicheck
|
|
||||||
- bidichk
|
|
||||||
- bodyclose
|
- bodyclose
|
||||||
- canonicalheader
|
- canonicalheader
|
||||||
- containedctx
|
|
||||||
- contextcheck
|
|
||||||
- copyloopvar
|
- copyloopvar
|
||||||
- cyclop
|
|
||||||
- decorder
|
|
||||||
- dogsled
|
|
||||||
- dupl
|
|
||||||
- dupword
|
|
||||||
- durationcheck
|
- durationcheck
|
||||||
- embeddedstructfieldcheck
|
|
||||||
- err113
|
|
||||||
- errcheck
|
- errcheck
|
||||||
- errchkjson
|
- errchkjson
|
||||||
- errname
|
- errname
|
||||||
- errorlint
|
- errorlint
|
||||||
- exhaustive
|
|
||||||
- exptostd
|
|
||||||
- fatcontext
|
|
||||||
- forbidigo
|
|
||||||
- forcetypeassert
|
|
||||||
- funcorder
|
|
||||||
- funlen
|
|
||||||
- ginkgolinter
|
|
||||||
- gocheckcompilerdirectives
|
|
||||||
- gochecknoglobals
|
|
||||||
- gochecknoinits
|
|
||||||
- gochecksumtype
|
|
||||||
- gocognit
|
|
||||||
- goconst
|
|
||||||
- gocritic
|
|
||||||
- gocyclo
|
|
||||||
- godoclint
|
|
||||||
- godot
|
|
||||||
- godox
|
|
||||||
- goheader
|
|
||||||
- gomodguard
|
|
||||||
- goprintffuncname
|
|
||||||
- gosec
|
- gosec
|
||||||
- gosmopolitan
|
|
||||||
- govet
|
- govet
|
||||||
- grouper
|
|
||||||
- iface
|
|
||||||
- importas
|
|
||||||
- inamedparam
|
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- interfacebloat
|
|
||||||
- intrange
|
|
||||||
- iotamixing
|
|
||||||
- ireturn
|
|
||||||
- lll
|
|
||||||
- loggercheck
|
|
||||||
- maintidx
|
|
||||||
- makezero
|
|
||||||
- mirror
|
|
||||||
- misspell
|
|
||||||
- mnd
|
|
||||||
- modernize
|
|
||||||
- musttag
|
|
||||||
- nakedret
|
|
||||||
- nestif
|
|
||||||
- nilerr
|
- nilerr
|
||||||
- nilnesserr
|
- nilnesserr
|
||||||
- nilnil
|
- nilnil
|
||||||
- nlreturn
|
|
||||||
- noctx
|
- noctx
|
||||||
- noinlineerr
|
|
||||||
- nolintlint
|
|
||||||
- nonamedreturns
|
|
||||||
- nosprintfhostport
|
|
||||||
- paralleltest
|
|
||||||
- perfsprint
|
|
||||||
- prealloc
|
|
||||||
- predeclared
|
|
||||||
- promlinter
|
|
||||||
- protogetter
|
|
||||||
- reassign
|
|
||||||
- recvcheck
|
|
||||||
- revive
|
|
||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- sloglint
|
|
||||||
- spancheck
|
|
||||||
- sqlclosecheck
|
- sqlclosecheck
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- tagalign
|
|
||||||
- tagliatelle
|
|
||||||
- testableexamples
|
|
||||||
- testifylint
|
|
||||||
- testpackage
|
|
||||||
- thelper
|
|
||||||
- tparallel
|
|
||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
|
||||||
- unqueryvet
|
|
||||||
- unused
|
|
||||||
- usestdlibvars
|
|
||||||
- usetesting
|
|
||||||
- varnamelen
|
|
||||||
- wastedassign
|
- wastedassign
|
||||||
- whitespace
|
|
||||||
- wsl_v5
|
|
||||||
- zerologlint
|
|
||||||
# Disable specific linters.
|
|
||||||
disable:
|
disable:
|
||||||
- depguard
|
- depguard
|
||||||
- exhaustruct
|
- exhaustruct
|
||||||
- gochecknoglobals
|
- gochecknoglobals
|
||||||
|
- gochecknoinits
|
||||||
- gomoddirectives
|
- gomoddirectives
|
||||||
- noinlineerr
|
|
||||||
- wsl
|
|
||||||
- wrapcheck
|
- wrapcheck
|
||||||
# All available settings of specific linters.
|
- cyclop
|
||||||
# See the dedicated "linters.settings" documentation section.
|
- dupl
|
||||||
settings:
|
- funlen
|
||||||
wsl_v5:
|
- gocognit
|
||||||
allow-first-in-block: true
|
- gocyclo
|
||||||
allow-whole-block: false
|
- ireturn
|
||||||
branch-max-lines: 2
|
- lll
|
||||||
|
- mnd
|
||||||
# Defines a set of rules to ignore issues.
|
- nestif
|
||||||
# It does not skip the analysis, and so does not ignore "typecheck" errors.
|
- nlreturn
|
||||||
exclusions:
|
- noinlineerr
|
||||||
# Mode of the generated files analysis.
|
- paralleltest
|
||||||
#
|
- tagliatelle
|
||||||
# - `strict`: sources are excluded by strictly following the Go generated file convention.
|
- testpackage
|
||||||
# Source files that have lines matching only the following regular expression will be excluded: `^// Code generated .* DO NOT EDIT\.$`
|
- varnamelen
|
||||||
# This line must appear before the first non-comment, non-blank text in the file.
|
- wsl_v5
|
||||||
# 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:
|
|
||||||
- cyclop
|
|
||||||
- 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: []
|
|
||||||
|
|||||||
@@ -1,196 +1,47 @@
|
|||||||
# See the dedicated "version" documentation section.
|
|
||||||
version: "2"
|
version: "2"
|
||||||
linters:
|
linters:
|
||||||
# Default set of linters.
|
default: none
|
||||||
# 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.
|
|
||||||
enable:
|
enable:
|
||||||
- arangolint
|
|
||||||
- asasalint
|
|
||||||
- asciicheck
|
|
||||||
- bidichk
|
|
||||||
- bodyclose
|
- bodyclose
|
||||||
- canonicalheader
|
- canonicalheader
|
||||||
- containedctx
|
|
||||||
- contextcheck
|
|
||||||
- copyloopvar
|
- copyloopvar
|
||||||
- cyclop
|
|
||||||
- decorder
|
|
||||||
- dogsled
|
|
||||||
- dupl
|
|
||||||
- dupword
|
|
||||||
- durationcheck
|
- durationcheck
|
||||||
- embeddedstructfieldcheck
|
|
||||||
- err113
|
|
||||||
- errcheck
|
- errcheck
|
||||||
- errchkjson
|
- errchkjson
|
||||||
- errname
|
- errname
|
||||||
- errorlint
|
- errorlint
|
||||||
- exhaustive
|
|
||||||
- exptostd
|
|
||||||
- fatcontext
|
|
||||||
- forbidigo
|
|
||||||
- forcetypeassert
|
|
||||||
- funcorder
|
|
||||||
- funlen
|
|
||||||
- ginkgolinter
|
|
||||||
- gocheckcompilerdirectives
|
|
||||||
- gochecknoglobals
|
|
||||||
- gochecknoinits
|
|
||||||
- gochecksumtype
|
|
||||||
- gocognit
|
|
||||||
- goconst
|
|
||||||
- gocritic
|
|
||||||
- gocyclo
|
|
||||||
- godoclint
|
|
||||||
- godot
|
|
||||||
- godox
|
|
||||||
- goheader
|
|
||||||
- gomodguard
|
|
||||||
- goprintffuncname
|
|
||||||
- gosec
|
- gosec
|
||||||
- gosmopolitan
|
|
||||||
- govet
|
- govet
|
||||||
- grouper
|
|
||||||
- iface
|
|
||||||
- importas
|
|
||||||
- inamedparam
|
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- interfacebloat
|
|
||||||
- intrange
|
|
||||||
- iotamixing
|
|
||||||
- ireturn
|
|
||||||
- lll
|
|
||||||
- loggercheck
|
|
||||||
- maintidx
|
|
||||||
- makezero
|
|
||||||
- mirror
|
|
||||||
- misspell
|
|
||||||
- mnd
|
|
||||||
- modernize
|
|
||||||
- musttag
|
|
||||||
- nakedret
|
|
||||||
- nestif
|
|
||||||
- nilerr
|
- nilerr
|
||||||
- nilnesserr
|
- nilnesserr
|
||||||
- nilnil
|
- nilnil
|
||||||
- nlreturn
|
|
||||||
- noctx
|
- noctx
|
||||||
- noinlineerr
|
|
||||||
- nolintlint
|
|
||||||
- nonamedreturns
|
|
||||||
- nosprintfhostport
|
|
||||||
- paralleltest
|
|
||||||
- perfsprint
|
|
||||||
- prealloc
|
|
||||||
- predeclared
|
|
||||||
- promlinter
|
|
||||||
- protogetter
|
|
||||||
- reassign
|
|
||||||
- recvcheck
|
|
||||||
- revive
|
|
||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- sloglint
|
|
||||||
- spancheck
|
|
||||||
- sqlclosecheck
|
- sqlclosecheck
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- tagalign
|
|
||||||
- tagliatelle
|
|
||||||
- testableexamples
|
|
||||||
- testifylint
|
|
||||||
- testpackage
|
|
||||||
- thelper
|
|
||||||
- tparallel
|
|
||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
|
||||||
- unqueryvet
|
|
||||||
- unused
|
|
||||||
- usestdlibvars
|
|
||||||
- usetesting
|
|
||||||
- varnamelen
|
|
||||||
- wastedassign
|
- wastedassign
|
||||||
- whitespace
|
|
||||||
- wsl_v5
|
|
||||||
- zerologlint
|
|
||||||
# Disable specific linters.
|
|
||||||
disable:
|
disable:
|
||||||
- depguard
|
- depguard
|
||||||
- exhaustruct
|
- exhaustruct
|
||||||
- gochecknoglobals
|
- gochecknoglobals
|
||||||
|
- gochecknoinits
|
||||||
- gomoddirectives
|
- gomoddirectives
|
||||||
- wsl
|
|
||||||
- wrapcheck
|
- wrapcheck
|
||||||
# All available settings of specific linters.
|
- cyclop
|
||||||
# See the dedicated "linters.settings" documentation section.
|
- dupl
|
||||||
settings:
|
- funlen
|
||||||
wsl_v5:
|
- gocognit
|
||||||
allow-first-in-block: true
|
- gocyclo
|
||||||
allow-whole-block: false
|
- ireturn
|
||||||
branch-max-lines: 2
|
- lll
|
||||||
|
- mnd
|
||||||
# Defines a set of rules to ignore issues.
|
- nestif
|
||||||
# It does not skip the analysis, and so does not ignore "typecheck" errors.
|
- nlreturn
|
||||||
exclusions:
|
- noinlineerr
|
||||||
# Mode of the generated files analysis.
|
- paralleltest
|
||||||
#
|
- tagliatelle
|
||||||
# - `strict`: sources are excluded by strictly following the Go generated file convention.
|
- testpackage
|
||||||
# Source files that have lines matching only the following regular expression will be excluded: `^// Code generated .* DO NOT EDIT\.$`
|
- varnamelen
|
||||||
# This line must appear before the first non-comment, non-blank text in the file.
|
- wsl_v5
|
||||||
# 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: []
|
|
||||||
|
|||||||
47
api/edge/bff/.golangci.yml
Normal file
47
api/edge/bff/.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
|
||||||
@@ -159,7 +159,7 @@ func (s *service) VerifyAccount(
|
|||||||
token, err := s.vdb.Create(
|
token, err := s.vdb.Create(
|
||||||
ctx,
|
ctx,
|
||||||
verification.NewLinkRequest(*acct.GetID(), model.PurposeAccountActivation, "").
|
verification.NewLinkRequest(*acct.GetID(), model.PurposeAccountActivation, "").
|
||||||
WithTTL(time.Duration(time.Hour*24)),
|
WithTTL(time.Hour * 24),
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Warn("Failed to create verification token for new account", zap.Error(err), mzap.StorableRef(acct))
|
s.logger.Warn("Failed to create verification token for new account", zap.Error(err), mzap.StorableRef(acct))
|
||||||
@@ -238,7 +238,7 @@ func (s *service) ResetPassword(
|
|||||||
return s.vdb.Create(
|
return s.vdb.Create(
|
||||||
ctx,
|
ctx,
|
||||||
verification.NewOTPRequest(*acct.GetID(), model.PurposePasswordReset, "").
|
verification.NewOTPRequest(*acct.GetID(), model.PurposePasswordReset, "").
|
||||||
WithTTL(time.Duration(time.Hour*1)),
|
WithTTL(time.Hour),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,7 +250,7 @@ func (s *service) UpdateLogin(
|
|||||||
return s.vdb.Create(
|
return s.vdb.Create(
|
||||||
ctx,
|
ctx,
|
||||||
verification.NewOTPRequest(*acct.GetID(), model.PurposeEmailChange, newLogin).
|
verification.NewOTPRequest(*acct.GetID(), model.PurposeEmailChange, newLogin).
|
||||||
WithTTL(time.Duration(time.Hour*1)),
|
WithTTL(time.Hour),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ func (e Endpoint) DecodeIBAN() (IBANEndpoint, error) {
|
|||||||
|
|
||||||
func LegacyPaymentEndpointToEndpointDTO(old *LegacyPaymentEndpoint) (*Endpoint, error) {
|
func LegacyPaymentEndpointToEndpointDTO(old *LegacyPaymentEndpoint) (*Endpoint, error) {
|
||||||
if old == nil {
|
if old == nil {
|
||||||
|
//nolint:nilnil // Nil legacy endpoint means no endpoint provided.
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,6 +203,7 @@ func LegacyPaymentEndpointToEndpointDTO(old *LegacyPaymentEndpoint) (*Endpoint,
|
|||||||
|
|
||||||
func EndpointDTOToLegacyPaymentEndpoint(new *Endpoint) (*LegacyPaymentEndpoint, error) {
|
func EndpointDTOToLegacyPaymentEndpoint(new *Endpoint) (*LegacyPaymentEndpoint, error) {
|
||||||
if new == nil {
|
if new == nil {
|
||||||
|
//nolint:nilnil // Nil endpoint DTO means no endpoint provided.
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -344,7 +344,7 @@ func toPaymentOperation(step *orchestrationv2.StepExecution, quote *quotationv2.
|
|||||||
Amount: amount,
|
Amount: amount,
|
||||||
ConvertedAmount: convertedAmount,
|
ConvertedAmount: convertedAmount,
|
||||||
OperationRef: operationRef,
|
OperationRef: operationRef,
|
||||||
Gateway: string(gateway),
|
Gateway: gateway,
|
||||||
StartedAt: timestampAsTime(step.GetStartedAt()),
|
StartedAt: timestampAsTime(step.GetStartedAt()),
|
||||||
CompletedAt: timestampAsTime(step.GetCompletedAt()),
|
CompletedAt: timestampAsTime(step.GetCompletedAt()),
|
||||||
}
|
}
|
||||||
@@ -456,9 +456,9 @@ func gatewayTypeFromInstanceID(raw string) mservice.Type {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
switch mservice.Type(value) {
|
switch value {
|
||||||
case mservice.ChainGateway, mservice.TronGateway, mservice.MntxGateway, mservice.PaymentGateway, mservice.TgSettle, mservice.Ledger:
|
case mservice.ChainGateway, mservice.TronGateway, mservice.MntxGateway, mservice.PaymentGateway, mservice.TgSettle, mservice.Ledger:
|
||||||
return mservice.Type(value)
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ func Account2ClaimsForClient(a *model.Account, expiration int, clientID string)
|
|||||||
paramNameName: t.Name,
|
paramNameName: t.Name,
|
||||||
paramNameLocale: t.Locale,
|
paramNameLocale: t.Locale,
|
||||||
paramNameClientID: t.ClientID,
|
paramNameClientID: t.ClientID,
|
||||||
paramNameExpiration: int64(t.Expiration.Unix()),
|
paramNameExpiration: t.Expiration.Unix(),
|
||||||
paramNamePending: t.Pending,
|
paramNamePending: t.Pending,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ const (
|
|||||||
var (
|
var (
|
||||||
ledgerDiscoveryServiceNames = []string{
|
ledgerDiscoveryServiceNames = []string{
|
||||||
"LEDGER",
|
"LEDGER",
|
||||||
string(mservice.Ledger),
|
mservice.Ledger,
|
||||||
}
|
}
|
||||||
paymentOrchestratorDiscoveryServiceNames = []string{
|
paymentOrchestratorDiscoveryServiceNames = []string{
|
||||||
"PAYMENTS_ORCHESTRATOR",
|
"PAYMENTS_ORCHESTRATOR",
|
||||||
string(mservice.PaymentOrchestrator),
|
mservice.PaymentOrchestrator,
|
||||||
}
|
}
|
||||||
paymentQuotationDiscoveryServiceNames = []string{
|
paymentQuotationDiscoveryServiceNames = []string{
|
||||||
"PAYMENTS_QUOTATION",
|
"PAYMENTS_QUOTATION",
|
||||||
@@ -41,7 +41,7 @@ var (
|
|||||||
paymentMethodsDiscoveryServiceNames = []string{
|
paymentMethodsDiscoveryServiceNames = []string{
|
||||||
"PAYMENTS_METHODS",
|
"PAYMENTS_METHODS",
|
||||||
"PAYMENT_METHODS",
|
"PAYMENT_METHODS",
|
||||||
string(mservice.PaymentMethods),
|
mservice.PaymentMethods,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -339,13 +339,13 @@ func selectGatewayEndpoint(gateways []discovery.GatewaySummary, preferredNetwork
|
|||||||
func parseDiscoveryInvokeURI(raw string) (discoveryEndpoint, error) {
|
func parseDiscoveryInvokeURI(raw string) (discoveryEndpoint, error) {
|
||||||
raw = strings.TrimSpace(raw)
|
raw = strings.TrimSpace(raw)
|
||||||
if raw == "" {
|
if raw == "" {
|
||||||
return discoveryEndpoint{}, fmt.Errorf("Invoke uri is empty")
|
return discoveryEndpoint{}, fmt.Errorf("invoke uri is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Without a scheme we expect a plain host:port target.
|
// Without a scheme we expect a plain host:port target.
|
||||||
if !strings.Contains(raw, "://") {
|
if !strings.Contains(raw, "://") {
|
||||||
if _, _, err := net.SplitHostPort(raw); err != nil {
|
if _, _, err := net.SplitHostPort(raw); err != nil {
|
||||||
return discoveryEndpoint{}, fmt.Errorf("Invoke uri must include host:port: %w", err)
|
return discoveryEndpoint{}, fmt.Errorf("invoke uri must include host:port: %w", err)
|
||||||
}
|
}
|
||||||
return discoveryEndpoint{
|
return discoveryEndpoint{
|
||||||
address: raw,
|
address: raw,
|
||||||
@@ -363,7 +363,7 @@ func parseDiscoveryInvokeURI(raw string) (discoveryEndpoint, error) {
|
|||||||
case "grpc":
|
case "grpc":
|
||||||
address := strings.TrimSpace(parsed.Host)
|
address := strings.TrimSpace(parsed.Host)
|
||||||
if _, _, splitErr := net.SplitHostPort(address); splitErr != nil {
|
if _, _, splitErr := net.SplitHostPort(address); splitErr != nil {
|
||||||
return discoveryEndpoint{}, fmt.Errorf("Grpc invoke uri must include host:port: %w", splitErr)
|
return discoveryEndpoint{}, fmt.Errorf("grpc invoke uri must include host:port: %w", splitErr)
|
||||||
}
|
}
|
||||||
return discoveryEndpoint{
|
return discoveryEndpoint{
|
||||||
address: address,
|
address: address,
|
||||||
@@ -373,7 +373,7 @@ func parseDiscoveryInvokeURI(raw string) (discoveryEndpoint, error) {
|
|||||||
case "grpcs":
|
case "grpcs":
|
||||||
address := strings.TrimSpace(parsed.Host)
|
address := strings.TrimSpace(parsed.Host)
|
||||||
if _, _, splitErr := net.SplitHostPort(address); splitErr != nil {
|
if _, _, splitErr := net.SplitHostPort(address); splitErr != nil {
|
||||||
return discoveryEndpoint{}, fmt.Errorf("Grpcs invoke uri must include host:port: %w", splitErr)
|
return discoveryEndpoint{}, fmt.Errorf("grpcs invoke uri must include host:port: %w", splitErr)
|
||||||
}
|
}
|
||||||
return discoveryEndpoint{
|
return discoveryEndpoint{
|
||||||
address: address,
|
address: address,
|
||||||
@@ -388,7 +388,7 @@ func parseDiscoveryInvokeURI(raw string) (discoveryEndpoint, error) {
|
|||||||
raw: raw,
|
raw: raw,
|
||||||
}, nil
|
}, nil
|
||||||
default:
|
default:
|
||||||
return discoveryEndpoint{}, fmt.Errorf("Unsupported invoke uri scheme: %s", parsed.Scheme)
|
return discoveryEndpoint{}, fmt.Errorf("unsupported invoke uri scheme: %s", parsed.Scheme)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ func (d *DispatcherImpl) dispatchMessage(ctx context.Context, conn *websocket.Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *DispatcherImpl) handle(w http.ResponseWriter, r *http.Request) {
|
func (d *DispatcherImpl) handle(w http.ResponseWriter, r *http.Request) {
|
||||||
|
//nolint:contextcheck // websocket.Handler callback signature does not carry request context.
|
||||||
websocket.Handler(func(conn *websocket.Conn) {
|
websocket.Handler(func(conn *websocket.Conn) {
|
||||||
ctx, cancel := context.WithTimeout(r.Context(), time.Duration(d.timeout)*time.Second)
|
ctx, cancel := context.WithTimeout(r.Context(), time.Duration(d.timeout)*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ func GetOptionalParam[T any](logger mlogger.Logger, r *http.Request, key string,
|
|||||||
vals := r.URL.Query()
|
vals := r.URL.Query()
|
||||||
s := vals.Get(key)
|
s := vals.Get(key)
|
||||||
if s == "" {
|
if s == "" {
|
||||||
|
//nolint:nilnil // Missing optional query parameter is represented as (nil, nil).
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
package mutil
|
package mutil
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/tech/sendico/pkg/mlogger"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetOptionalBoolParam(t *testing.T) {
|
func TestGetOptionalBoolParam(t *testing.T) {
|
||||||
logger := mlogger.Logger(zap.NewNop())
|
logger := zap.NewNop()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -47,7 +47,7 @@ func TestGetOptionalBoolParam(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
req, err := http.NewRequest("GET", "http://example.com"+tt.query, nil)
|
req, err := http.NewRequestWithContext(context.Background(), "GET", "http://example.com"+tt.query, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
result, err := GetOptionalBoolParam(logger, req, "param")
|
result, err := GetOptionalBoolParam(logger, req, "param")
|
||||||
@@ -69,7 +69,7 @@ func TestGetOptionalBoolParam(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetOptionalInt64Param(t *testing.T) {
|
func TestGetOptionalInt64Param(t *testing.T) {
|
||||||
logger := mlogger.Logger(zap.NewNop())
|
logger := zap.NewNop()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
@@ -111,7 +111,7 @@ func TestGetOptionalInt64Param(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
req, err := http.NewRequest("GET", "http://example.com"+tt.query, nil)
|
req, err := http.NewRequestWithContext(context.Background(), "GET", "http://example.com"+tt.query, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
result, err := GetOptionalInt64Param(logger, req, "param")
|
result, err := GetOptionalInt64Param(logger, req, "param")
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ func (a *AccountAPI) deleteAll(r *http.Request, account *model.Account, token *s
|
|||||||
a.logger.Warn("Failed to delete all data", zap.Error(err), mzap.StorableRef(&org), mzap.StorableRef(account))
|
a.logger.Warn("Failed to delete all data", zap.Error(err), mzap.StorableRef(&org), mzap.StorableRef(account))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, nil
|
return struct{}{}, nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
a.logger.Warn("Failed to execute delete transaction", zap.Error(err), mzap.StorableRef(&org), mzap.StorableRef(account))
|
a.logger.Warn("Failed to execute delete transaction", zap.Error(err), mzap.StorableRef(&org), mzap.StorableRef(account))
|
||||||
return response.Auto(a.logger, a.Name(), err)
|
return response.Auto(a.logger, a.Name(), err)
|
||||||
|
|||||||
@@ -192,5 +192,5 @@ func (a *AccountAPI) resetPasswordTransactionBody(ctx context.Context, user *mod
|
|||||||
// Don't fail the transaction if token revocation fails, but log it
|
// Don't fail the transaction if token revocation fails, but log it
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return struct{}{}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -133,12 +133,7 @@ func TestPasswordValidationLogic(t *testing.T) {
|
|||||||
for _, password := range invalidPasswords {
|
for _, password := range invalidPasswords {
|
||||||
t.Run(password, func(t *testing.T) {
|
t.Run(password, func(t *testing.T) {
|
||||||
// Test that invalid passwords fail at least one requirement
|
// Test that invalid passwords fail at least one requirement
|
||||||
isValid := true
|
isValid := len(password) >= 8
|
||||||
|
|
||||||
// Check length
|
|
||||||
if len(password) < 8 {
|
|
||||||
isValid = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for digit
|
// Check for digit
|
||||||
hasDigit := false
|
hasDigit := false
|
||||||
|
|||||||
@@ -276,7 +276,7 @@ func (a *AccountAPI) grantAllPermissions(ctx context.Context, organizationRef bs
|
|||||||
|
|
||||||
for resource, granted := range required {
|
for resource, granted := range required {
|
||||||
if !granted {
|
if !granted {
|
||||||
a.logger.Warn("Required policy description not found for signup permissions", zap.String("resource", string(resource)))
|
a.logger.Warn("Required policy description not found for signup permissions", zap.String("resource", resource))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ func TestSignupHTTPSerialization(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Create HTTP request
|
// Create HTTP request
|
||||||
req := httptest.NewRequest(http.MethodPost, "/signup", bytes.NewBuffer(reqBody))
|
req := httptest.NewRequestWithContext(context.Background(), http.MethodPost, "/signup", bytes.NewBuffer(reqBody))
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
// Parse the request body
|
// Parse the request body
|
||||||
@@ -162,7 +162,7 @@ func TestSignupHTTPSerialization(t *testing.T) {
|
|||||||
t.Run("InvalidJSONRequest", func(t *testing.T) {
|
t.Run("InvalidJSONRequest", func(t *testing.T) {
|
||||||
invalidJSON := `{"account": {"login": "test@example.com", "password": "invalid json structure`
|
invalidJSON := `{"account": {"login": "test@example.com", "password": "invalid json structure`
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodPost, "/signup", bytes.NewBufferString(invalidJSON))
|
req := httptest.NewRequestWithContext(context.Background(), http.MethodPost, "/signup", bytes.NewBufferString(invalidJSON))
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
var parsedRequest srequest.Signup
|
var parsedRequest srequest.Signup
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ func (a *CallbacksAPI) create(r *http.Request, account *model.Account, accessTok
|
|||||||
if err := a.applySigningSecretMutation(ctx, *account.GetID(), *callback.GetID(), mutation); err != nil {
|
if err := a.applySigningSecretMutation(ctx, *account.GetID(), *callback.GetID(), mutation); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, nil
|
return struct{}{}, nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
a.Logger.Warn("Failed to create callback transaction", zap.Error(err))
|
a.Logger.Warn("Failed to create callback transaction", zap.Error(err))
|
||||||
return response.Auto(a.Logger, a.Name(), err)
|
return response.Auto(a.Logger, a.Name(), err)
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ func (a *CallbacksAPI) update(r *http.Request, account *model.Account, accessTok
|
|||||||
if err := a.applySigningSecretMutation(ctx, *account.GetID(), callbackRef, mutation); err != nil {
|
if err := a.applySigningSecretMutation(ctx, *account.GetID(), callbackRef, mutation); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, nil
|
return struct{}{}, nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
a.Logger.Warn("Failed to update callback transaction", zap.Error(err))
|
a.Logger.Warn("Failed to update callback transaction", zap.Error(err))
|
||||||
return response.Auto(a.Logger, a.Name(), err)
|
return response.Auto(a.Logger, a.Name(), err)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/tech/sendico/pkg/api/http/response"
|
"github.com/tech/sendico/pkg/api/http/response"
|
||||||
"github.com/tech/sendico/pkg/domainprovider"
|
"github.com/tech/sendico/pkg/domainprovider"
|
||||||
@@ -34,7 +35,10 @@ func (storage *LocalStorage) Delete(ctx context.Context, objID string) error {
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
filePath := filepath.Join(storage.storageDir, objID)
|
filePath, err := storage.resolvePath(objID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := os.Remove(filePath); err != nil {
|
if err := os.Remove(filePath); err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
storage.logger.Debug("File not found", zap.String("obj_ref", objID))
|
storage.logger.Debug("File not found", zap.String("obj_ref", objID))
|
||||||
@@ -54,7 +58,11 @@ func (storage *LocalStorage) Save(ctx context.Context, file io.Reader, objID str
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
filePath := filepath.Join(storage.storageDir, objID)
|
filePath, err := storage.resolvePath(objID)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
//nolint:gosec // File path is resolved and constrained to storage root.
|
||||||
dst, err := os.Create(filePath)
|
dst, err := os.Create(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
storage.logger.Warn("Error occurred while creating file", zap.Error(err), zap.String("storage", storage.storageDir), zap.String("obj_ref", objID))
|
storage.logger.Warn("Error occurred while creating file", zap.Error(err), zap.String("storage", storage.storageDir), zap.String("obj_ref", objID))
|
||||||
@@ -78,7 +86,9 @@ func (storage *LocalStorage) Save(ctx context.Context, file io.Reader, objID str
|
|||||||
}
|
}
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
// Context was cancelled, clean up the partial file
|
// Context was cancelled, clean up the partial file
|
||||||
os.Remove(filePath)
|
if removeErr := os.Remove(filePath); removeErr != nil && !os.IsNotExist(removeErr) {
|
||||||
|
storage.logger.Warn("Failed to remove partially written file", zap.Error(removeErr), zap.String("obj_ref", objID))
|
||||||
|
}
|
||||||
return "", ctx.Err()
|
return "", ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -93,7 +103,10 @@ func (storage *LocalStorage) Get(ctx context.Context, objRef string) http.Handle
|
|||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
filePath := filepath.Join(storage.storageDir, objRef)
|
filePath, err := storage.resolvePath(objRef)
|
||||||
|
if err != nil {
|
||||||
|
return response.Internal(storage.logger, storage.service, err)
|
||||||
|
}
|
||||||
if _, err := os.Stat(filePath); err != nil {
|
if _, err := os.Stat(filePath); err != nil {
|
||||||
storage.logger.Warn("Failed to access file", zap.Error(err), zap.String("storage", storage.storageDir), zap.String("obj_ref", objRef))
|
storage.logger.Warn("Failed to access file", zap.Error(err), zap.String("storage", storage.storageDir), zap.String("obj_ref", objRef))
|
||||||
return response.Internal(storage.logger, storage.service, err)
|
return response.Internal(storage.logger, storage.service, err)
|
||||||
@@ -117,7 +130,7 @@ func (storage *LocalStorage) Get(ctx context.Context, objRef string) http.Handle
|
|||||||
func ensureDir(dirName string) error {
|
func ensureDir(dirName string) error {
|
||||||
info, err := os.Stat(dirName)
|
info, err := os.Stat(dirName)
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return os.MkdirAll(dirName, 0o755)
|
return os.MkdirAll(dirName, 0o750)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -128,6 +141,24 @@ func ensureDir(dirName string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (storage *LocalStorage) resolvePath(objID string) (string, error) {
|
||||||
|
objID = strings.TrimSpace(objID)
|
||||||
|
if objID == "" {
|
||||||
|
return "", merrors.InvalidArgument("obj_ref is required", "obj_ref")
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := filepath.Join(storage.storageDir, objID)
|
||||||
|
relPath, err := filepath.Rel(storage.storageDir, filePath)
|
||||||
|
if err != nil {
|
||||||
|
return "", merrors.InternalWrap(err, "failed to resolve local file path")
|
||||||
|
}
|
||||||
|
if relPath == "." || strings.HasPrefix(relPath, "..") {
|
||||||
|
return "", merrors.InvalidArgument("obj_ref is invalid", "obj_ref")
|
||||||
|
}
|
||||||
|
|
||||||
|
return filePath, nil
|
||||||
|
}
|
||||||
|
|
||||||
func CreateLocalFileStorage(logger mlogger.Logger, service mservice.Type, directory, subDir string, dp domainprovider.DomainProvider, cfg config.LocalFSSConfig) (*LocalStorage, error) {
|
func CreateLocalFileStorage(logger mlogger.Logger, service mservice.Type, directory, subDir string, dp domainprovider.DomainProvider, cfg config.LocalFSSConfig) (*LocalStorage, error) {
|
||||||
dir := filepath.Join(cfg.RootPath, directory)
|
dir := filepath.Join(cfg.RootPath, directory)
|
||||||
if err := ensureDir(dir); err != nil {
|
if err := ensureDir(dir); err != nil {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ func setupTestStorage(t *testing.T) (*LocalStorage, string, func()) {
|
|||||||
|
|
||||||
// Return cleanup function
|
// Return cleanup function
|
||||||
cleanup := func() {
|
cleanup := func() {
|
||||||
os.RemoveAll(tempDir)
|
require.NoError(t, os.RemoveAll(tempDir))
|
||||||
}
|
}
|
||||||
|
|
||||||
return storage, tempDir, cleanup
|
return storage, tempDir, cleanup
|
||||||
@@ -81,7 +81,7 @@ func setupBenchmarkStorage(b *testing.B) (*LocalStorage, string, func()) {
|
|||||||
|
|
||||||
// Return cleanup function
|
// Return cleanup function
|
||||||
cleanup := func() {
|
cleanup := func() {
|
||||||
os.RemoveAll(tempDir)
|
require.NoError(b, os.RemoveAll(tempDir))
|
||||||
}
|
}
|
||||||
|
|
||||||
return storage, tempDir, cleanup
|
return storage, tempDir, cleanup
|
||||||
@@ -138,6 +138,7 @@ func TestLocalStorage_Save(t *testing.T) {
|
|||||||
|
|
||||||
// Verify file was actually saved
|
// Verify file was actually saved
|
||||||
filePath := filepath.Join(tempDir, tt.objID)
|
filePath := filepath.Join(tempDir, tt.objID)
|
||||||
|
//nolint:gosec // Test-controlled path inside temporary directory.
|
||||||
content, err := os.ReadFile(filePath)
|
content, err := os.ReadFile(filePath)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.Equal(t, tt.content, string(content))
|
assert.Equal(t, tt.content, string(content))
|
||||||
@@ -186,7 +187,7 @@ func TestLocalStorage_Delete(t *testing.T) {
|
|||||||
|
|
||||||
// Create a test file
|
// Create a test file
|
||||||
testFile := filepath.Join(tempDir, "test.txt")
|
testFile := filepath.Join(tempDir, "test.txt")
|
||||||
err := os.WriteFile(testFile, []byte("test content"), 0o644)
|
err := os.WriteFile(testFile, []byte("test content"), 0o600)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -232,7 +233,7 @@ func TestLocalStorage_Delete_ContextCancellation(t *testing.T) {
|
|||||||
|
|
||||||
// Create a test file
|
// Create a test file
|
||||||
testFile := filepath.Join(tempDir, "test.txt")
|
testFile := filepath.Join(tempDir, "test.txt")
|
||||||
err := os.WriteFile(testFile, []byte("test content"), 0o644)
|
err := os.WriteFile(testFile, []byte("test content"), 0o600)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Create a context that's already cancelled
|
// Create a context that's already cancelled
|
||||||
@@ -256,7 +257,7 @@ func TestLocalStorage_Get(t *testing.T) {
|
|||||||
// Create a test file
|
// Create a test file
|
||||||
testContent := "test file content"
|
testContent := "test file content"
|
||||||
testFile := filepath.Join(tempDir, "test.txt")
|
testFile := filepath.Join(tempDir, "test.txt")
|
||||||
err := os.WriteFile(testFile, []byte(testContent), 0o644)
|
err := os.WriteFile(testFile, []byte(testContent), 0o600)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -285,7 +286,7 @@ func TestLocalStorage_Get(t *testing.T) {
|
|||||||
handler := storage.Get(ctx, tt.objID)
|
handler := storage.Get(ctx, tt.objID)
|
||||||
|
|
||||||
// Create test request
|
// Create test request
|
||||||
req := httptest.NewRequest("GET", "/files/"+tt.objID, nil)
|
req := httptest.NewRequestWithContext(context.Background(), "GET", "/files/"+tt.objID, nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
handler.ServeHTTP(w, req)
|
handler.ServeHTTP(w, req)
|
||||||
@@ -304,7 +305,7 @@ func TestLocalStorage_Get_ContextCancellation(t *testing.T) {
|
|||||||
|
|
||||||
// Create a test file
|
// Create a test file
|
||||||
testFile := filepath.Join(tempDir, "test.txt")
|
testFile := filepath.Join(tempDir, "test.txt")
|
||||||
err := os.WriteFile(testFile, []byte("test content"), 0o644)
|
err := os.WriteFile(testFile, []byte("test content"), 0o600)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Create a context that's already cancelled
|
// Create a context that's already cancelled
|
||||||
@@ -314,7 +315,7 @@ func TestLocalStorage_Get_ContextCancellation(t *testing.T) {
|
|||||||
handler := storage.Get(ctx, "test.txt")
|
handler := storage.Get(ctx, "test.txt")
|
||||||
|
|
||||||
// Create test request
|
// Create test request
|
||||||
req := httptest.NewRequest("GET", "/files/test.txt", nil)
|
req := httptest.NewRequestWithContext(context.Background(), "GET", "/files/test.txt", nil)
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
handler.ServeHTTP(w, req)
|
handler.ServeHTTP(w, req)
|
||||||
@@ -328,14 +329,14 @@ func TestLocalStorage_Get_RequestContextCancellation(t *testing.T) {
|
|||||||
|
|
||||||
// Create a test file
|
// Create a test file
|
||||||
testFile := filepath.Join(tempDir, "test.txt")
|
testFile := filepath.Join(tempDir, "test.txt")
|
||||||
err := os.WriteFile(testFile, []byte("test content"), 0o644)
|
err := os.WriteFile(testFile, []byte("test content"), 0o600)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
handler := storage.Get(ctx, "test.txt")
|
handler := storage.Get(ctx, "test.txt")
|
||||||
|
|
||||||
// Create test request with cancelled context
|
// Create test request with cancelled context
|
||||||
req := httptest.NewRequest("GET", "/files/test.txt", nil)
|
req := httptest.NewRequestWithContext(context.Background(), "GET", "/files/test.txt", nil)
|
||||||
reqCtx, cancel := context.WithCancel(req.Context())
|
reqCtx, cancel := context.WithCancel(req.Context())
|
||||||
req = req.WithContext(reqCtx)
|
req = req.WithContext(reqCtx)
|
||||||
cancel() // Cancel the request context
|
cancel() // Cancel the request context
|
||||||
@@ -352,7 +353,9 @@ func TestCreateLocalFileStorage(t *testing.T) {
|
|||||||
// Create temporary directory for testing
|
// Create temporary directory for testing
|
||||||
tempDir, err := os.MkdirTemp("", "storage_test")
|
tempDir, err := os.MkdirTemp("", "storage_test")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer os.RemoveAll(tempDir)
|
defer func() {
|
||||||
|
require.NoError(t, os.RemoveAll(tempDir))
|
||||||
|
}()
|
||||||
|
|
||||||
logger := zap.NewNop()
|
logger := zap.NewNop()
|
||||||
cfg := config.LocalFSSConfig{
|
cfg := config.LocalFSSConfig{
|
||||||
@@ -372,10 +375,12 @@ func TestCreateLocalFileStorage_InvalidPath(t *testing.T) {
|
|||||||
// Build a deterministic failure case: the target path already exists as a file.
|
// Build a deterministic failure case: the target path already exists as a file.
|
||||||
tempDir, err := os.MkdirTemp("", "storage_invalid_path_test")
|
tempDir, err := os.MkdirTemp("", "storage_invalid_path_test")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
defer os.RemoveAll(tempDir)
|
defer func() {
|
||||||
|
require.NoError(t, os.RemoveAll(tempDir))
|
||||||
|
}()
|
||||||
|
|
||||||
fileAtTargetPath := filepath.Join(tempDir, "test")
|
fileAtTargetPath := filepath.Join(tempDir, "test")
|
||||||
err = os.WriteFile(fileAtTargetPath, []byte("not a directory"), 0o644)
|
err = os.WriteFile(fileAtTargetPath, []byte("not a directory"), 0o600)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
logger := zap.NewNop()
|
logger := zap.NewNop()
|
||||||
@@ -426,7 +431,7 @@ func TestLocalStorage_ConcurrentOperations(t *testing.T) {
|
|||||||
// Create files to delete
|
// Create files to delete
|
||||||
for i := 0; i < 5; i++ {
|
for i := 0; i < 5; i++ {
|
||||||
filePath := filepath.Join(tempDir, fmt.Sprintf("delete_%d.txt", i))
|
filePath := filepath.Join(tempDir, fmt.Sprintf("delete_%d.txt", i))
|
||||||
err := os.WriteFile(filePath, []byte("content"), 0o644)
|
err := os.WriteFile(filePath, []byte("content"), 0o600)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,7 +541,7 @@ func BenchmarkLocalStorage_Delete(b *testing.B) {
|
|||||||
// Pre-create files for deletion
|
// Pre-create files for deletion
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
filePath := filepath.Join(tempDir, fmt.Sprintf("bench_delete_%d.txt", i))
|
filePath := filepath.Join(tempDir, fmt.Sprintf("bench_delete_%d.txt", i))
|
||||||
err := os.WriteFile(filePath, []byte("content"), 0o644)
|
err := os.WriteFile(filePath, []byte("content"), 0o600)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatal(err)
|
b.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -97,7 +97,9 @@ func (a *LedgerAPI) createAccount(r *http.Request, account *model.Account, token
|
|||||||
}
|
}
|
||||||
|
|
||||||
func decodeLedgerAccountCreatePayload(r *http.Request) (*srequest.CreateLedgerAccount, error) {
|
func decodeLedgerAccountCreatePayload(r *http.Request) (*srequest.CreateLedgerAccount, error) {
|
||||||
defer r.Body.Close()
|
defer func() {
|
||||||
|
_ = r.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
payload := srequest.CreateLedgerAccount{}
|
payload := srequest.CreateLedgerAccount{}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func (a *ProtectedAPI[T]) archive(r *http.Request, account *model.Account, acces
|
|||||||
|
|
||||||
ctx := r.Context()
|
ctx := r.Context()
|
||||||
_, err = a.a.DBFactory().TransactionFactory().CreateTransaction().Execute(ctx, func(ctx context.Context) (any, error) {
|
_, err = a.a.DBFactory().TransactionFactory().CreateTransaction().Execute(ctx, func(ctx context.Context) (any, error) {
|
||||||
return nil, a.DB.SetArchived(r.Context(), *account.GetID(), organizationRef, objectRef, *archived, *cascade)
|
return nil, a.DB.SetArchived(ctx, *account.GetID(), organizationRef, objectRef, *archived, *cascade)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.Logger.Warn("Failed to change archive property", zap.Error(err), mzap.StorableRef(account), mutil.PLog(a.Cph, r),
|
a.Logger.Warn("Failed to change archive property", zap.Error(err), mzap.StorableRef(account), mutil.PLog(a.Cph, r),
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ func (a *PaymentAPI) getOperationDocument(r *http.Request, account *model.Accoun
|
|||||||
|
|
||||||
op, err := a.fetchGatewayOperation(r.Context(), gateway.InvokeURI, operationRef)
|
op, err := a.fetchGatewayOperation(r.Context(), gateway.InvokeURI, operationRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logger.Warn("Failed to fetch gateway operation for document generation", zap.Error(err), mzap.ObjRef("organization_ref", orgRef), zap.String("gateway_service", string(gatewayService)), zap.String("operation_ref", operationRef))
|
a.logger.Warn("Failed to fetch gateway operation for document generation", zap.Error(err), mzap.ObjRef("organization_ref", orgRef), zap.String("gateway_service", gatewayService), zap.String("operation_ref", operationRef))
|
||||||
return documentErrorResponse(a.logger, a.Name(), err)
|
return documentErrorResponse(a.logger, a.Name(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ func (a *PaymentAPI) getOperationDocument(r *http.Request, account *model.Accoun
|
|||||||
|
|
||||||
docResp, err := a.fetchOperationDocument(r.Context(), service.InvokeURI, req)
|
docResp, err := a.fetchOperationDocument(r.Context(), service.InvokeURI, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.logger.Warn("Failed to fetch operation document", zap.Error(err), mzap.ObjRef("organization_ref", orgRef), zap.String("gateway_service", string(gatewayService)), zap.String("operation_ref", operationRef))
|
a.logger.Warn("Failed to fetch operation document", zap.Error(err), mzap.ObjRef("organization_ref", orgRef), zap.String("gateway_service", gatewayService), zap.String("operation_ref", operationRef))
|
||||||
return documentErrorResponse(a.logger, a.Name(), err)
|
return documentErrorResponse(a.logger, a.Name(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,6 +154,7 @@ func operationDocumentResponse(logger mlogger.Logger, source mservice.Type, docR
|
|||||||
w.Header().Set("Content-Type", mimeType)
|
w.Header().Set("Content-Type", mimeType)
|
||||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))
|
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
|
//nolint:gosec // Binary payload is served as attachment with explicit content type.
|
||||||
if _, err := w.Write(docResp.GetContent()); err != nil {
|
if _, err := w.Write(docResp.GetContent()); err != nil {
|
||||||
logger.Warn("Failed to write document response", zap.Error(err))
|
logger.Warn("Failed to write document response", zap.Error(err))
|
||||||
}
|
}
|
||||||
@@ -167,15 +168,15 @@ func normalizeGatewayService(raw string) mservice.Type {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch value {
|
switch value {
|
||||||
case string(mservice.ChainGateway):
|
case mservice.ChainGateway:
|
||||||
return mservice.ChainGateway
|
return mservice.ChainGateway
|
||||||
case string(mservice.TronGateway):
|
case mservice.TronGateway:
|
||||||
return mservice.TronGateway
|
return mservice.TronGateway
|
||||||
case string(mservice.MntxGateway):
|
case mservice.MntxGateway:
|
||||||
return mservice.MntxGateway
|
return mservice.MntxGateway
|
||||||
case string(mservice.PaymentGateway):
|
case mservice.PaymentGateway:
|
||||||
return mservice.PaymentGateway
|
return mservice.PaymentGateway
|
||||||
case string(mservice.TgSettle):
|
case mservice.TgSettle:
|
||||||
return mservice.TgSettle
|
return mservice.TgSettle
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
@@ -219,7 +220,11 @@ func (a *PaymentAPI) fetchOperationDocument(ctx context.Context, invokeURI strin
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, merrors.InternalWrap(err, "dial billing documents")
|
return nil, merrors.InternalWrap(err, "dial billing documents")
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer func() {
|
||||||
|
if closeErr := conn.Close(); closeErr != nil {
|
||||||
|
a.logger.Warn("Failed to close billing documents connection", zap.Error(closeErr))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
client := documentsv1.NewDocumentServiceClient(conn)
|
client := documentsv1.NewDocumentServiceClient(conn)
|
||||||
|
|
||||||
@@ -234,7 +239,11 @@ func (a *PaymentAPI) fetchGatewayOperation(ctx context.Context, invokeURI, opera
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, merrors.InternalWrap(err, "dial gateway connector")
|
return nil, merrors.InternalWrap(err, "dial gateway connector")
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer func() {
|
||||||
|
if closeErr := conn.Close(); closeErr != nil {
|
||||||
|
a.logger.Warn("Failed to close gateway connector connection", zap.Error(closeErr))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
client := connectorv1.NewConnectorServiceClient(conn)
|
client := connectorv1.NewConnectorServiceClient(conn)
|
||||||
|
|
||||||
@@ -307,7 +316,7 @@ func findGatewayForService(gateways []discovery.GatewaySummary, gatewayService m
|
|||||||
func operationDocumentRequest(organizationRef string, gatewayService mservice.Type, requestedOperationRef string, op *connectorv1.Operation) *documentsv1.GetOperationDocumentRequest {
|
func operationDocumentRequest(organizationRef string, gatewayService mservice.Type, requestedOperationRef string, op *connectorv1.Operation) *documentsv1.GetOperationDocumentRequest {
|
||||||
req := &documentsv1.GetOperationDocumentRequest{
|
req := &documentsv1.GetOperationDocumentRequest{
|
||||||
OrganizationRef: strings.TrimSpace(organizationRef),
|
OrganizationRef: strings.TrimSpace(organizationRef),
|
||||||
GatewayService: string(gatewayService),
|
GatewayService: gatewayService,
|
||||||
OperationRef: firstNonEmpty(strings.TrimSpace(op.GetOperationRef()), strings.TrimSpace(requestedOperationRef)),
|
OperationRef: firstNonEmpty(strings.TrimSpace(op.GetOperationRef()), strings.TrimSpace(requestedOperationRef)),
|
||||||
OperationCode: strings.TrimSpace(op.GetType().String()),
|
OperationCode: strings.TrimSpace(op.GetType().String()),
|
||||||
OperationLabel: operationLabel(op.GetType()),
|
OperationLabel: operationLabel(op.GetType()),
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ func listPaymentsPage(r *http.Request) (*paginationv1.CursorPageRequest, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if cursor == "" && !hasLimit {
|
if cursor == "" && !hasLimit {
|
||||||
|
//nolint:nilnil // Absent pagination params mean no page request should be sent downstream.
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,6 +190,7 @@ func firstNonEmpty(values ...string) string {
|
|||||||
func parseRFC3339Timestamp(raw string, field string) (*timestamppb.Timestamp, error) {
|
func parseRFC3339Timestamp(raw string, field string) (*timestamppb.Timestamp, error) {
|
||||||
trimmed := strings.TrimSpace(raw)
|
trimmed := strings.TrimSpace(raw)
|
||||||
if trimmed == "" {
|
if trimmed == "" {
|
||||||
|
//nolint:nilnil // Empty timestamp filter is represented as (nil, nil).
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
parsed, err := time.Parse(time.RFC3339, trimmed)
|
parsed, err := time.Parse(time.RFC3339, trimmed)
|
||||||
|
|||||||
@@ -102,7 +102,9 @@ func (a *PaymentAPI) initiatePayment(r *http.Request, account *model.Account, to
|
|||||||
}
|
}
|
||||||
|
|
||||||
func decodeInitiatePayload(r *http.Request) (*srequest.InitiatePayment, error) {
|
func decodeInitiatePayload(r *http.Request) (*srequest.InitiatePayment, error) {
|
||||||
defer r.Body.Close()
|
defer func() {
|
||||||
|
_ = r.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
payload := &srequest.InitiatePayment{}
|
payload := &srequest.InitiatePayment{}
|
||||||
if err := json.NewDecoder(r.Body).Decode(payload); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(payload); err != nil {
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ func TestInitiateByQuote_RejectsMetadataIntentRef(t *testing.T) {
|
|||||||
func invokeInitiateByQuote(t *testing.T, api *PaymentAPI, orgRef bson.ObjectID, body string) *httptest.ResponseRecorder {
|
func invokeInitiateByQuote(t *testing.T, api *PaymentAPI, orgRef bson.ObjectID, body string) *httptest.ResponseRecorder {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodPost, "/by-quote", bytes.NewBufferString(body))
|
req := httptest.NewRequestWithContext(context.Background(), http.MethodPost, "/by-quote", bytes.NewBufferString(body))
|
||||||
routeCtx := chi.NewRouteContext()
|
routeCtx := chi.NewRouteContext()
|
||||||
routeCtx.URLParams.Add("organizations_ref", orgRef.Hex())
|
routeCtx.URLParams.Add("organizations_ref", orgRef.Hex())
|
||||||
req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, routeCtx))
|
req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, routeCtx))
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ func (a *PaymentAPI) initiatePaymentsByQuote(r *http.Request, account *model.Acc
|
|||||||
}
|
}
|
||||||
|
|
||||||
func decodeInitiatePaymentsPayload(r *http.Request) (*srequest.InitiatePayments, error) {
|
func decodeInitiatePaymentsPayload(r *http.Request) (*srequest.InitiatePayments, error) {
|
||||||
defer r.Body.Close()
|
defer func() {
|
||||||
|
_ = r.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
payload := &srequest.InitiatePayments{}
|
payload := &srequest.InitiatePayments{}
|
||||||
decoder := json.NewDecoder(r.Body)
|
decoder := json.NewDecoder(r.Body)
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
|
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/tech/sendico/pkg/auth"
|
"github.com/tech/sendico/pkg/auth"
|
||||||
"github.com/tech/sendico/pkg/mlogger"
|
|
||||||
"github.com/tech/sendico/pkg/model"
|
"github.com/tech/sendico/pkg/model"
|
||||||
"github.com/tech/sendico/pkg/mservice"
|
"github.com/tech/sendico/pkg/mservice"
|
||||||
orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
|
orchestrationv2 "github.com/tech/sendico/pkg/proto/payments/orchestration/v2"
|
||||||
@@ -118,7 +117,7 @@ func TestInitiatePaymentsByQuote_RejectsDeprecatedIntentRefsField(t *testing.T)
|
|||||||
|
|
||||||
func newBatchAPI(exec executionClient) *PaymentAPI {
|
func newBatchAPI(exec executionClient) *PaymentAPI {
|
||||||
return &PaymentAPI{
|
return &PaymentAPI{
|
||||||
logger: mlogger.Logger(zap.NewNop()),
|
logger: zap.NewNop(),
|
||||||
execution: exec,
|
execution: exec,
|
||||||
enf: fakeEnforcerForBatch{allowed: true},
|
enf: fakeEnforcerForBatch{allowed: true},
|
||||||
oph: mutil.CreatePH(mservice.Organizations),
|
oph: mutil.CreatePH(mservice.Organizations),
|
||||||
@@ -129,7 +128,7 @@ func newBatchAPI(exec executionClient) *PaymentAPI {
|
|||||||
func invokeInitiatePaymentsByQuote(t *testing.T, api *PaymentAPI, orgRef bson.ObjectID, body string) *httptest.ResponseRecorder {
|
func invokeInitiatePaymentsByQuote(t *testing.T, api *PaymentAPI, orgRef bson.ObjectID, body string) *httptest.ResponseRecorder {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodPost, "/by-multiquote", bytes.NewBufferString(body))
|
req := httptest.NewRequestWithContext(context.Background(), http.MethodPost, "/by-multiquote", bytes.NewBufferString(body))
|
||||||
routeCtx := chi.NewRouteContext()
|
routeCtx := chi.NewRouteContext()
|
||||||
routeCtx.URLParams.Add("organizations_ref", orgRef.Hex())
|
routeCtx.URLParams.Add("organizations_ref", orgRef.Hex())
|
||||||
req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, routeCtx))
|
req = req.WithContext(context.WithValue(req.Context(), chi.RouteCtxKey, routeCtx))
|
||||||
@@ -177,6 +176,7 @@ func (f fakeEnforcerForBatch) Enforce(context.Context, bson.ObjectID, bson.Objec
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (fakeEnforcerForBatch) EnforceBatch(context.Context, []model.PermissionBoundStorable, bson.ObjectID, model.Action) (map[bson.ObjectID]bool, error) {
|
func (fakeEnforcerForBatch) EnforceBatch(context.Context, []model.PermissionBoundStorable, bson.ObjectID, model.Action) (map[bson.ObjectID]bool, error) {
|
||||||
|
//nolint:nilnil // Test stub does not provide batch permissions map.
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,7 +126,9 @@ func (a *PaymentAPI) quotePayments(r *http.Request, account *model.Account, toke
|
|||||||
}
|
}
|
||||||
|
|
||||||
func decodeQuotePayload(r *http.Request) (*srequest.QuotePayment, error) {
|
func decodeQuotePayload(r *http.Request) (*srequest.QuotePayment, error) {
|
||||||
defer r.Body.Close()
|
defer func() {
|
||||||
|
_ = r.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
payload := &srequest.QuotePayment{}
|
payload := &srequest.QuotePayment{}
|
||||||
if err := json.NewDecoder(r.Body).Decode(payload); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(payload); err != nil {
|
||||||
@@ -140,7 +142,9 @@ func decodeQuotePayload(r *http.Request) (*srequest.QuotePayment, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func decodeQuotePaymentsPayload(r *http.Request) (*srequest.QuotePayments, error) {
|
func decodeQuotePaymentsPayload(r *http.Request) (*srequest.QuotePayments, error) {
|
||||||
defer r.Body.Close()
|
defer func() {
|
||||||
|
_ = r.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
payload := &srequest.QuotePayments{}
|
payload := &srequest.QuotePayments{}
|
||||||
if err := json.NewDecoder(r.Body).Decode(payload); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(payload); err != nil {
|
||||||
|
|||||||
@@ -256,6 +256,7 @@ func (c *grpcQuotationClient) callContext(ctx context.Context) (context.Context,
|
|||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
//nolint:gosec // Caller receives cancel func and defers it in every call path.
|
||||||
return context.WithTimeout(ctx, timeout)
|
return context.WithTimeout(ctx, timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -271,7 +272,7 @@ func (a *PaymentAPI) initDiscoveryClient(cfg *eapi.Config) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
client, err := discovery.NewClient(a.logger, broker, nil, string(a.Name()))
|
client, err := discovery.NewClient(a.logger, broker, nil, a.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,5 +90,5 @@ func (a *PermissionsAPI) changePoliciesImp(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return struct{}{}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -223,6 +223,7 @@ func (a *WalletAPI) queryBalanceFromGateways(ctx context.Context, gateways []dis
|
|||||||
a.logger.Debug("Wallet balance fan-out completed without result",
|
a.logger.Debug("Wallet balance fan-out completed without result",
|
||||||
zap.String("organization_ref", organizationRef),
|
zap.String("organization_ref", organizationRef),
|
||||||
zap.String("wallet_ref", walletRef))
|
zap.String("wallet_ref", walletRef))
|
||||||
|
//nolint:nilnil // No gateway returned a balance and no hard error occurred.
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +239,11 @@ func (a *WalletAPI) queryGatewayBalance(ctx context.Context, gateway discovery.G
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, merrors.InternalWrap(err, "dial gateway")
|
return nil, merrors.InternalWrap(err, "dial gateway")
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer func() {
|
||||||
|
if closeErr := conn.Close(); closeErr != nil {
|
||||||
|
a.logger.Warn("Failed to close gateway connection", zap.Error(closeErr), zap.String("gateway", gateway.ID))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
client := connectorv1.NewConnectorServiceClient(conn)
|
client := connectorv1.NewConnectorServiceClient(conn)
|
||||||
|
|
||||||
|
|||||||
@@ -173,7 +173,11 @@ func (a *WalletAPI) createWalletOnGateway(ctx context.Context, gateway discovery
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", merrors.InternalWrap(err, "dial gateway")
|
return "", merrors.InternalWrap(err, "dial gateway")
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer func() {
|
||||||
|
if closeErr := conn.Close(); closeErr != nil {
|
||||||
|
a.logger.Warn("Failed to close gateway connection", zap.Error(closeErr), zap.String("gateway", gateway.ID))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
client := connectorv1.NewConnectorServiceClient(conn)
|
client := connectorv1.NewConnectorServiceClient(conn)
|
||||||
|
|
||||||
|
|||||||
@@ -226,7 +226,11 @@ func (a *WalletAPI) queryGateway(ctx context.Context, gateway discovery.GatewayS
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, merrors.InternalWrap(err, "dial gateway")
|
return nil, merrors.InternalWrap(err, "dial gateway")
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer func() {
|
||||||
|
if closeErr := conn.Close(); closeErr != nil {
|
||||||
|
a.logger.Warn("Failed to close gateway connection", zap.Error(closeErr), zap.String("gateway", gateway.ID))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
client := connectorv1.NewConnectorServiceClient(conn)
|
client := connectorv1.NewConnectorServiceClient(conn)
|
||||||
|
|
||||||
|
|||||||
@@ -64,18 +64,21 @@ func (a *WalletAPI) rememberWalletRoute(ctx context.Context, organizationRef str
|
|||||||
|
|
||||||
func (a *WalletAPI) walletRoute(ctx context.Context, organizationRef string, walletRef string) (*model.ChainWalletRoute, error) {
|
func (a *WalletAPI) walletRoute(ctx context.Context, organizationRef string, walletRef string) (*model.ChainWalletRoute, error) {
|
||||||
if a.routes == nil {
|
if a.routes == nil {
|
||||||
|
//nolint:nilnil // Routing cache is optional and may be disabled.
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
walletRef = strings.TrimSpace(walletRef)
|
walletRef = strings.TrimSpace(walletRef)
|
||||||
organizationRef = strings.TrimSpace(organizationRef)
|
organizationRef = strings.TrimSpace(organizationRef)
|
||||||
if walletRef == "" || organizationRef == "" {
|
if walletRef == "" || organizationRef == "" {
|
||||||
|
//nolint:nilnil // Missing route keys mean no cached route.
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
route, err := a.routes.Get(ctx, organizationRef, walletRef)
|
route, err := a.routes.Get(ctx, organizationRef, walletRef)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, merrors.ErrNoData) {
|
if errors.Is(err, merrors.ErrNoData) {
|
||||||
|
//nolint:nilnil // Route not found in cache.
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ func (a *WalletAPI) initDiscoveryClient(cfg *eapi.Config) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
client, err := discovery.NewClient(a.logger, broker, nil, string(a.Name()))
|
client, err := discovery.NewClient(a.logger, broker, nil, a.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// generate translations
|
// generate translations
|
||||||
// go:generate Users/stephandeshevikh/go/bin/go18n extract
|
//go:generate Users/stephandeshevikh/go/bin/go18n extract
|
||||||
// go:generate Users/stephandeshevikh/go/bin/go18n merge
|
//go:generate Users/stephandeshevikh/go/bin/go18n merge
|
||||||
|
|
||||||
// lint go code
|
// lint go code
|
||||||
// docker run -t --rm -v $(pwd):/app -w /app golangci/golangci-lint:latest golangci-lint run -v --timeout 10m0s --enable-all -D ireturn -D wrapcheck -D varnamelen -D tagliatelle -D nosnakecase -D gochecknoglobals -D nlreturn -D stylecheck -D lll -D wsl -D scopelint -D varcheck -D exhaustivestruct -D golint -D maligned -D interfacer -D ifshort -D structcheck -D deadcode -D godot -D depguard -D tagalign
|
// docker run -t --rm -v $(pwd):/app -w /app golangci/golangci-lint:latest golangci-lint run -v --timeout 10m0s --enable-all -D ireturn -D wrapcheck -D varnamelen -D tagliatelle -D nosnakecase -D gochecknoglobals -D nlreturn -D stylecheck -D lll -D wsl -D scopelint -D varcheck -D exhaustivestruct -D golint -D maligned -D interfacer -D ifshort -D structcheck -D deadcode -D godot -D depguard -D tagalign
|
||||||
|
|||||||
47
api/edge/callbacks/.golangci.yml
Normal file
47
api/edge/callbacks/.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
|
||||||
@@ -28,6 +28,7 @@ func (s *service) Load(path string) (*Config, error) {
|
|||||||
return nil, merrors.InvalidArgument("config path is required", "path")
|
return nil, merrors.InvalidArgument("config path is required", "path")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:gosec // Configuration file path is provided by service startup configuration.
|
||||||
data, err := os.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error("Failed to read config file", zap.String("path", path), zap.Error(err))
|
s.logger.Error("Failed to read config file", zap.String("path", path), zap.Error(err))
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ func (s *service) Start(ctx context.Context) {
|
|||||||
if runCtx == nil {
|
if runCtx == nil {
|
||||||
runCtx = context.Background()
|
runCtx = context.Background()
|
||||||
}
|
}
|
||||||
runCtx, s.cancel = context.WithCancel(runCtx)
|
runCtx, s.cancel = context.WithCancel(runCtx) //nolint:gosec // canceled by Stop; service lifecycle outlives Start scope
|
||||||
|
|
||||||
for i := 0; i < s.cfg.WorkerConcurrency; i++ {
|
for i := 0; i < s.cfg.WorkerConcurrency; i++ {
|
||||||
workerID := "worker-" + strconv.Itoa(i+1)
|
workerID := "worker-" + strconv.Itoa(i+1)
|
||||||
@@ -143,6 +143,10 @@ func (s *service) runWorker(ctx context.Context, workerID string) {
|
|||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
task, err := s.tasks.LockNextTask(ctx, now, workerID, s.cfg.LockTTL)
|
task, err := s.tasks.LockNextTask(ctx, now, workerID, s.cfg.LockTTL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, merrors.ErrNoData) {
|
||||||
|
time.Sleep(s.cfg.WorkerPoll)
|
||||||
|
continue
|
||||||
|
}
|
||||||
s.logger.Warn("Failed to lock next task", zap.String("worker_id", workerID), zap.Error(err))
|
s.logger.Warn("Failed to lock next task", zap.String("worker_id", workerID), zap.Error(err))
|
||||||
time.Sleep(s.cfg.WorkerPoll)
|
time.Sleep(s.cfg.WorkerPoll)
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ func (s *service) Start(ctx context.Context) {
|
|||||||
if runCtx == nil {
|
if runCtx == nil {
|
||||||
runCtx = context.Background()
|
runCtx = context.Background()
|
||||||
}
|
}
|
||||||
runCtx, s.cancel = context.WithCancel(runCtx)
|
runCtx, s.cancel = context.WithCancel(runCtx) //nolint:gosec // canceled by Stop; service lifecycle outlives Start scope
|
||||||
|
|
||||||
s.wg.Add(1)
|
s.wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ type service struct {
|
|||||||
|
|
||||||
// New creates retry policy service.
|
// New creates retry policy service.
|
||||||
func New() Policy {
|
func New() Policy {
|
||||||
|
//nolint:gosec // Backoff jitter is non-cryptographic and only needs pseudo-random distribution.
|
||||||
return &service{rnd: rand.New(rand.NewSource(time.Now().UnixNano()))}
|
return &service{rnd: rand.New(rand.NewSource(time.Now().UnixNano()))}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -154,6 +154,7 @@ func (i *Imp) Start() error {
|
|||||||
|
|
||||||
runCtx, cancel := context.WithCancel(context.Background())
|
runCtx, cancel := context.WithCancel(context.Background())
|
||||||
i.runCancel = cancel
|
i.runCancel = cancel
|
||||||
|
defer cancel()
|
||||||
i.ingest.Start(runCtx)
|
i.ingest.Start(runCtx)
|
||||||
i.delivery.Start(runCtx)
|
i.delivery.Start(runCtx)
|
||||||
i.opServer.SetStatus(health.SSRunning)
|
i.opServer.SetStatus(health.SSRunning)
|
||||||
|
|||||||
@@ -379,7 +379,7 @@ func (r *taskStore) LockNextTask(ctx context.Context, now time.Time, workerID st
|
|||||||
candidates, err := mutil.GetObjects[taskDoc](ctx, r.logger, query, nil, r.repo)
|
candidates, err := mutil.GetObjects[taskDoc](ctx, r.logger, query, nil, r.repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, merrors.ErrNoData) {
|
if errors.Is(err, merrors.ErrNoData) {
|
||||||
return nil, nil
|
return nil, merrors.ErrNoData
|
||||||
}
|
}
|
||||||
return nil, merrors.InternalWrap(err, "callbacks task query failed")
|
return nil, merrors.InternalWrap(err, "callbacks task query failed")
|
||||||
}
|
}
|
||||||
@@ -418,7 +418,7 @@ func (r *taskStore) LockNextTask(ctx context.Context, now time.Time, workerID st
|
|||||||
return mapTaskDoc(locked), nil
|
return mapTaskDoc(locked), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, nil
|
return nil, merrors.ErrNoData
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *taskStore) MarkDelivered(ctx context.Context, taskID bson.ObjectID, httpCode int, latency time.Duration, at time.Time) error {
|
func (r *taskStore) MarkDelivered(ctx context.Context, taskID bson.ObjectID, httpCode int, latency time.Duration, at time.Time) error {
|
||||||
|
|||||||
@@ -1,196 +1,47 @@
|
|||||||
# See the dedicated "version" documentation section.
|
|
||||||
version: "2"
|
version: "2"
|
||||||
linters:
|
linters:
|
||||||
# Default set of linters.
|
default: none
|
||||||
# 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.
|
|
||||||
enable:
|
enable:
|
||||||
- arangolint
|
|
||||||
- asasalint
|
|
||||||
- asciicheck
|
|
||||||
- bidichk
|
|
||||||
- bodyclose
|
- bodyclose
|
||||||
- canonicalheader
|
- canonicalheader
|
||||||
- containedctx
|
|
||||||
- contextcheck
|
|
||||||
- copyloopvar
|
- copyloopvar
|
||||||
- cyclop
|
|
||||||
- decorder
|
|
||||||
- dogsled
|
|
||||||
- dupl
|
|
||||||
- dupword
|
|
||||||
- durationcheck
|
- durationcheck
|
||||||
- embeddedstructfieldcheck
|
|
||||||
- err113
|
|
||||||
- errcheck
|
- errcheck
|
||||||
- errchkjson
|
- errchkjson
|
||||||
- errname
|
- errname
|
||||||
- errorlint
|
- errorlint
|
||||||
- exhaustive
|
|
||||||
- exptostd
|
|
||||||
- fatcontext
|
|
||||||
- forbidigo
|
|
||||||
- forcetypeassert
|
|
||||||
- funcorder
|
|
||||||
- funlen
|
|
||||||
- ginkgolinter
|
|
||||||
- gocheckcompilerdirectives
|
|
||||||
- gochecknoglobals
|
|
||||||
- gochecknoinits
|
|
||||||
- gochecksumtype
|
|
||||||
- gocognit
|
|
||||||
- goconst
|
|
||||||
- gocritic
|
|
||||||
- gocyclo
|
|
||||||
- godoclint
|
|
||||||
- godot
|
|
||||||
- godox
|
|
||||||
- goheader
|
|
||||||
- gomodguard
|
|
||||||
- goprintffuncname
|
|
||||||
- gosec
|
- gosec
|
||||||
- gosmopolitan
|
|
||||||
- govet
|
- govet
|
||||||
- grouper
|
|
||||||
- iface
|
|
||||||
- importas
|
|
||||||
- inamedparam
|
|
||||||
- ineffassign
|
- ineffassign
|
||||||
- interfacebloat
|
|
||||||
- intrange
|
|
||||||
- iotamixing
|
|
||||||
- ireturn
|
|
||||||
- lll
|
|
||||||
- loggercheck
|
|
||||||
- maintidx
|
|
||||||
- makezero
|
|
||||||
- mirror
|
|
||||||
- misspell
|
|
||||||
- mnd
|
|
||||||
- modernize
|
|
||||||
- musttag
|
|
||||||
- nakedret
|
|
||||||
- nestif
|
|
||||||
- nilerr
|
- nilerr
|
||||||
- nilnesserr
|
- nilnesserr
|
||||||
- nilnil
|
- nilnil
|
||||||
- nlreturn
|
|
||||||
- noctx
|
- noctx
|
||||||
- noinlineerr
|
|
||||||
- nolintlint
|
|
||||||
- nonamedreturns
|
|
||||||
- nosprintfhostport
|
|
||||||
- paralleltest
|
|
||||||
- perfsprint
|
|
||||||
- prealloc
|
|
||||||
- predeclared
|
|
||||||
- promlinter
|
|
||||||
- protogetter
|
|
||||||
- reassign
|
|
||||||
- recvcheck
|
|
||||||
- revive
|
|
||||||
- rowserrcheck
|
- rowserrcheck
|
||||||
- sloglint
|
|
||||||
- spancheck
|
|
||||||
- sqlclosecheck
|
- sqlclosecheck
|
||||||
- staticcheck
|
- staticcheck
|
||||||
- tagalign
|
|
||||||
- tagliatelle
|
|
||||||
- testableexamples
|
|
||||||
- testifylint
|
|
||||||
- testpackage
|
|
||||||
- thelper
|
|
||||||
- tparallel
|
|
||||||
- unconvert
|
- unconvert
|
||||||
- unparam
|
|
||||||
- unqueryvet
|
|
||||||
- unused
|
|
||||||
- usestdlibvars
|
|
||||||
- usetesting
|
|
||||||
- varnamelen
|
|
||||||
- wastedassign
|
- wastedassign
|
||||||
- whitespace
|
|
||||||
- wsl_v5
|
|
||||||
- zerologlint
|
|
||||||
# Disable specific linters.
|
|
||||||
disable:
|
disable:
|
||||||
- depguard
|
- depguard
|
||||||
- exhaustruct
|
- exhaustruct
|
||||||
- gochecknoglobals
|
- gochecknoglobals
|
||||||
|
- gochecknoinits
|
||||||
- gomoddirectives
|
- gomoddirectives
|
||||||
- wrapcheck
|
- wrapcheck
|
||||||
- wsl
|
- cyclop
|
||||||
# All available settings of specific linters.
|
- dupl
|
||||||
# See the dedicated "linters.settings" documentation section.
|
- funlen
|
||||||
settings:
|
- gocognit
|
||||||
wsl_v5:
|
- gocyclo
|
||||||
allow-first-in-block: true
|
- ireturn
|
||||||
allow-whole-block: false
|
- lll
|
||||||
branch-max-lines: 2
|
- mnd
|
||||||
|
- nestif
|
||||||
# Defines a set of rules to ignore issues.
|
- nlreturn
|
||||||
# It does not skip the analysis, and so does not ignore "typecheck" errors.
|
- noinlineerr
|
||||||
exclusions:
|
- paralleltest
|
||||||
# Mode of the generated files analysis.
|
- tagliatelle
|
||||||
#
|
- testpackage
|
||||||
# - `strict`: sources are excluded by strictly following the Go generated file convention.
|
- varnamelen
|
||||||
# Source files that have lines matching only the following regular expression will be excluded: `^// Code generated .* DO NOT EDIT\.$`
|
- wsl_v5
|
||||||
# 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: []
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ func Load(path string) (*Config, error) {
|
|||||||
return nil, merrors.InvalidArgument("config: path is empty")
|
return nil, merrors.InvalidArgument("config: path is empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//nolint:gosec // config path is provided by process startup arguments/config.
|
||||||
data, err := os.ReadFile(path)
|
data, err := os.ReadFile(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, merrors.InternalWrap(err, "config: failed to read file")
|
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 {
|
if _, ok := sourceSet[driver]; !ok {
|
||||||
return nil, merrors.InvalidArgument( //nolint:lll
|
return nil, merrors.InvalidArgument(
|
||||||
"config: pair references unknown source: "+driver.String(), "pairs."+driver.String())
|
"config: pair references unknown source: "+driver.String(),
|
||||||
|
"pairs."+driver.String(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
processed := make([]PairConfig, len(pairList))
|
processed := make([]PairConfig, len(pairList))
|
||||||
@@ -86,8 +89,10 @@ func Load(path string) (*Config, error) {
|
|||||||
pair.Symbol = strings.TrimSpace(pair.Symbol)
|
pair.Symbol = strings.TrimSpace(pair.Symbol)
|
||||||
|
|
||||||
if pair.Base == "" || pair.Quote == "" || pair.Symbol == "" {
|
if pair.Base == "" || pair.Quote == "" || pair.Symbol == "" {
|
||||||
return nil, merrors.InvalidArgument( //nolint:lll
|
return nil, merrors.InvalidArgument(
|
||||||
"config: pair entries must define base, quote, and symbol", "pairs."+driver.String())
|
"config: pair entries must define base, quote, and symbol",
|
||||||
|
"pairs."+driver.String(),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.TrimSpace(pair.Provider) == "" {
|
if strings.TrimSpace(pair.Provider) == "" {
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var errNoSnapshot = errors.New("snapshot not found")
|
||||||
|
|
||||||
func TestParseDecimal(t *testing.T) {
|
func TestParseDecimal(t *testing.T) {
|
||||||
got, err := parseDecimal("123.456")
|
got, err := parseDecimal("123.456")
|
||||||
if err != nil {
|
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) {
|
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 {
|
type connectorStub struct {
|
||||||
id mmarket.Driver
|
id mmarket.Driver
|
||||||
ticker *mmarket.Ticker
|
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")
|
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 {
|
if resp.StatusCode != http.StatusOK {
|
||||||
c.logger.Warn("Binance returned non-OK status", zap.String("symbol", symbol), zap.Int("status", resp.StatusCode))
|
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,
|
logger,
|
||||||
client,
|
client,
|
||||||
httpClientOptions{
|
httpClientOptions{
|
||||||
userAgent: userAgent,
|
userAgent: userAgent,
|
||||||
accept: acceptHeader,
|
accept: acceptHeader,
|
||||||
referer: referer,
|
referer: referer,
|
||||||
|
allowedScheme: parsed.Scheme,
|
||||||
|
allowedHost: parsed.Host,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
base: strings.TrimRight(parsed.String(), "/"),
|
base: strings.TrimRight(parsed.String(), "/"),
|
||||||
@@ -200,7 +202,11 @@ func (c *cbrConnector) refreshDirectory() error {
|
|||||||
)
|
)
|
||||||
return merrors.InternalWrap(err, "cbr: directory request failed")
|
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 {
|
if resp.StatusCode != http.StatusOK {
|
||||||
c.logger.Warn(
|
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")
|
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 {
|
if resp.StatusCode != http.StatusOK {
|
||||||
c.logger.Warn(
|
c.logger.Warn(
|
||||||
@@ -326,7 +336,11 @@ func (c *cbrConnector) fetchHistoricalRate( //nolint:funlen
|
|||||||
)
|
)
|
||||||
return "", merrors.InternalWrap(err, "cbr: historical request failed")
|
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 {
|
if resp.StatusCode != http.StatusOK {
|
||||||
c.logger.Warn(
|
c.logger.Warn(
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ package cbr
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/tech/sendico/pkg/mlogger"
|
"github.com/tech/sendico/pkg/mlogger"
|
||||||
@@ -14,17 +17,28 @@ const (
|
|||||||
defaultAccept = "application/xml,text/xml;q=0.9,*/*;q=0.8"
|
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.
|
// httpClient wraps http.Client to ensure CBR requests always carry required headers.
|
||||||
type httpClient struct {
|
type httpClient struct {
|
||||||
client *http.Client
|
client *http.Client
|
||||||
headers http.Header
|
headers http.Header
|
||||||
logger mlogger.Logger
|
logger mlogger.Logger
|
||||||
|
allowedScheme string
|
||||||
|
allowedHost string
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpClientOptions struct {
|
type httpClientOptions struct {
|
||||||
userAgent string
|
userAgent string
|
||||||
accept string
|
accept string
|
||||||
referer string
|
referer string
|
||||||
|
allowedScheme string
|
||||||
|
allowedHost string
|
||||||
}
|
}
|
||||||
|
|
||||||
func newHTTPClient(logger mlogger.Logger, client *http.Client, opts httpClientOptions) *httpClient {
|
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) == "" {
|
if strings.TrimSpace(referer) == "" {
|
||||||
referer = defaultCBRBaseURL
|
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")
|
httpLogger := logger.Named("http_client")
|
||||||
|
|
||||||
headers := make(http.Header, 3)
|
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))
|
zap.String("accept", accept), zap.String("referrer", referer))
|
||||||
|
|
||||||
return &httpClient{
|
return &httpClient{
|
||||||
client: client,
|
client: client,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
logger: httpLogger,
|
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)
|
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)
|
r, err := h.client.Do(enriched)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Warn("HTTP request failed", zap.Error(err), zap.String("method", req.Method),
|
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 {
|
func (h *httpClient) headerValue(name string) string {
|
||||||
return h.headers.Get(name)
|
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")
|
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 {
|
if resp.StatusCode != http.StatusOK {
|
||||||
c.logger.Warn("CoinGecko returned non-OK status", zap.String("symbol", symbol), zap.Int("status", resp.StatusCode))
|
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 (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|||||||
@@ -32,7 +32,9 @@ func main() {
|
|||||||
|
|
||||||
appVersion := appversion.Create()
|
appVersion := appversion.Create()
|
||||||
if *versionFlag {
|
if *versionFlag {
|
||||||
fmt.Fprintln(os.Stdout, appVersion.Print())
|
if _, err := fmt.Fprintln(os.Stdout, appVersion.Print()); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
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"
|
"context"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -134,7 +135,11 @@ func (c *oracleClient) LatestRate(ctx context.Context, req LatestRateParams) (*R
|
|||||||
return nil, merrors.InvalidArgument("oracle: pair is required")
|
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()
|
defer cancel()
|
||||||
|
|
||||||
resp, err := c.client.LatestRate(callCtx, &oraclev1.LatestRateRequest{
|
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")
|
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()
|
defer cancel()
|
||||||
|
|
||||||
protoReq := &oraclev1.GetQuoteRequest{
|
protoReq := &oraclev1.GetQuoteRequest{
|
||||||
@@ -179,7 +188,11 @@ func (c *oracleClient) GetQuote(ctx context.Context, req GetQuoteParams) (*Quote
|
|||||||
protoReq.TtlMs = req.TTL.Milliseconds()
|
protoReq.TtlMs = req.TTL.Milliseconds()
|
||||||
}
|
}
|
||||||
if req.MaxAge > 0 {
|
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 {
|
if baseSupplied {
|
||||||
protoReq.AmountInput = &oraclev1.GetQuoteRequest_BaseAmount{BaseAmount: req.BaseAmount}
|
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
|
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 {
|
func toProtoMeta(meta RequestMeta) *oraclev1.RequestMeta {
|
||||||
if meta.TenantRef == "" && meta.OrganizationRef == "" && meta.Trace == nil {
|
if meta.TenantRef == "" && meta.OrganizationRef == "" && meta.Trace == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package oracle
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -99,17 +100,28 @@ func buildPriceSet(rate *model.RateSnapshot) (priceSet, error) {
|
|||||||
return priceSet{}, merrors.InvalidArgument("oracle: cross rate requires underlying snapshot")
|
return priceSet{}, merrors.InvalidArgument("oracle: cross rate requires underlying snapshot")
|
||||||
}
|
}
|
||||||
ask, err := parsePrice(rate.Ask)
|
ask, err := parsePrice(rate.Ask)
|
||||||
if err != nil {
|
if err != nil && !errors.Is(err, merrors.ErrNoData) {
|
||||||
return priceSet{}, err
|
return priceSet{}, err
|
||||||
}
|
}
|
||||||
|
if errors.Is(err, merrors.ErrNoData) {
|
||||||
|
ask = nil
|
||||||
|
}
|
||||||
|
|
||||||
bid, err := parsePrice(rate.Bid)
|
bid, err := parsePrice(rate.Bid)
|
||||||
if err != nil {
|
if err != nil && !errors.Is(err, merrors.ErrNoData) {
|
||||||
return priceSet{}, err
|
return priceSet{}, err
|
||||||
}
|
}
|
||||||
|
if errors.Is(err, merrors.ErrNoData) {
|
||||||
|
bid = nil
|
||||||
|
}
|
||||||
|
|
||||||
mid, err := parsePrice(rate.Mid)
|
mid, err := parsePrice(rate.Mid)
|
||||||
if err != nil {
|
if err != nil && !errors.Is(err, merrors.ErrNoData) {
|
||||||
return priceSet{}, err
|
return priceSet{}, err
|
||||||
}
|
}
|
||||||
|
if errors.Is(err, merrors.ErrNoData) {
|
||||||
|
mid = nil
|
||||||
|
}
|
||||||
|
|
||||||
if ask == nil && bid == nil {
|
if ask == nil && bid == nil {
|
||||||
if mid == nil {
|
if mid == nil {
|
||||||
@@ -141,7 +153,7 @@ func buildPriceSet(rate *model.RateSnapshot) (priceSet, error) {
|
|||||||
|
|
||||||
func parsePrice(value string) (*big.Rat, error) {
|
func parsePrice(value string) (*big.Rat, error) {
|
||||||
if strings.TrimSpace(value) == "" {
|
if strings.TrimSpace(value) == "" {
|
||||||
return nil, nil
|
return nil, merrors.ErrNoData
|
||||||
}
|
}
|
||||||
return ratFromString(value)
|
return ratFromString(value)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ func (s *Service) startDiscoveryAnnouncer() {
|
|||||||
InvokeURI: s.invokeURI,
|
InvokeURI: s.invokeURI,
|
||||||
Version: appversion.Create().Short(),
|
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()
|
s.announcer.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func (q *quotesStoreStub) Consume(ctx context.Context, ref, ledger string, when
|
|||||||
if q.consumeFn != nil {
|
if q.consumeFn != nil {
|
||||||
return q.consumeFn(ctx, ref, ledger, when)
|
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) {
|
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
|
q.ConsumedByLedgerTxnRef = ledgerTxnRef
|
||||||
ts := consumedAt.UnixMilli()
|
ts := consumedAt.UnixMilli()
|
||||||
q.ConsumedAtUnixMs = &ts
|
q.ConsumedAtUnixMs = &ts
|
||||||
q.Base.Update()
|
q.Update()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkExpired marks the quote as expired.
|
// MarkExpired marks the quote as expired.
|
||||||
func (q *Quote) MarkExpired() {
|
func (q *Quote) MarkExpired() {
|
||||||
q.Status = QuoteStatusExpired
|
q.Status = QuoteStatusExpired
|
||||||
q.Base.Update()
|
q.Update()
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsExpired reports whether the quote has passed its expiration instant.
|
// IsExpired reports whether the quote has passed its expiration instant.
|
||||||
|
|||||||
@@ -35,8 +35,8 @@ func TestCurrencyStoreGet(t *testing.T) {
|
|||||||
|
|
||||||
func TestCurrencyStoreList(t *testing.T) {
|
func TestCurrencyStoreList(t *testing.T) {
|
||||||
repo := &repoStub{
|
repo := &repoStub{
|
||||||
findManyFn: func(_ context.Context, _ builder.Query, decode rd.DecodingFunc) error {
|
findManyFn: func(ctx context.Context, _ builder.Query, decode rd.DecodingFunc) error {
|
||||||
return runDecoderWithDocs(t, decode, &model.Currency{Code: "USD"})
|
return runDecoderWithDocs(ctx, t, decode, &model.Currency{Code: "USD"})
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
store := ¤cyStore{logger: zap.NewNop(), repo: repo}
|
store := ¤cyStore{logger: zap.NewNop(), repo: repo}
|
||||||
|
|||||||
@@ -16,11 +16,11 @@ import (
|
|||||||
|
|
||||||
func TestPairStoreListEnabled(t *testing.T) {
|
func TestPairStoreListEnabled(t *testing.T) {
|
||||||
repo := &repoStub{
|
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{}{
|
docs := []interface{}{
|
||||||
&model.Pair{Pair: model.CurrencyPair{Base: "USD", Quote: "EUR"}},
|
&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}
|
store := &pairStore{logger: zap.NewNop(), repo: repo}
|
||||||
|
|||||||
@@ -70,9 +70,9 @@ func TestRatesStoreUpsertUpdate(t *testing.T) {
|
|||||||
func TestRatesStoreLatestSnapshot(t *testing.T) {
|
func TestRatesStoreLatestSnapshot(t *testing.T) {
|
||||||
now := time.Now().UnixMilli()
|
now := time.Now().UnixMilli()
|
||||||
repo := &repoStub{
|
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}
|
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 ©
|
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()
|
t.Helper()
|
||||||
cur, err := mongo.NewCursorFromDocuments(docs, nil, nil)
|
cur, err := mongo.NewCursorFromDocuments(docs, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to create cursor: %v", err)
|
t.Fatalf("failed to create cursor: %v", err)
|
||||||
}
|
}
|
||||||
defer cur.Close(context.Background())
|
defer func() {
|
||||||
|
_ = cur.Close(ctx)
|
||||||
|
}()
|
||||||
|
|
||||||
if len(docs) > 0 {
|
if len(docs) > 0 {
|
||||||
if !cur.Next(context.Background()) {
|
if !cur.Next(ctx) {
|
||||||
return cur.Err()
|
return cur.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
47
api/gateway/aurora/.golangci.yml
Normal file
47
api/gateway/aurora/.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
|
||||||
@@ -87,7 +87,7 @@ func (g *gatewayClient) callContext(ctx context.Context, method string) (context
|
|||||||
}
|
}
|
||||||
g.logger.Info("Aurora gateway client call timeout applied", fields...)
|
g.logger.Info("Aurora gateway client call timeout applied", fields...)
|
||||||
}
|
}
|
||||||
return context.WithTimeout(ctx, timeout)
|
return context.WithTimeout(ctx, timeout) //nolint:gosec // cancel func is always invoked by call sites
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *gatewayClient) CreateCardPayout(ctx context.Context, req *mntxv1.CardPayoutRequest) (*mntxv1.CardPayoutResponse, error) {
|
func (g *gatewayClient) CreateCardPayout(ctx context.Context, req *mntxv1.CardPayoutRequest) (*mntxv1.CardPayoutResponse, error) {
|
||||||
|
|||||||
@@ -434,7 +434,7 @@ func buildGatewayLimits(cfg limitsConfig) *gatewayv1.Limits {
|
|||||||
if bucket == "" {
|
if bucket == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
limits.VelocityLimit[bucket] = int32(value)
|
limits.VelocityLimit[bucket] = int32(value) //nolint:gosec // velocity limits are validated config values
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,7 +450,7 @@ func buildGatewayLimits(cfg limitsConfig) *gatewayv1.Limits {
|
|||||||
MinAmount: strings.TrimSpace(override.MinAmount),
|
MinAmount: strings.TrimSpace(override.MinAmount),
|
||||||
MaxAmount: strings.TrimSpace(override.MaxAmount),
|
MaxAmount: strings.TrimSpace(override.MaxAmount),
|
||||||
MaxFee: strings.TrimSpace(override.MaxFee),
|
MaxFee: strings.TrimSpace(override.MaxFee),
|
||||||
MaxOps: int32(override.MaxOps),
|
MaxOps: int32(override.MaxOps), //nolint:gosec // max ops is a validated config value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -546,11 +546,12 @@ func (i *Imp) startHTTPCallbackServer(svc *auroraservice.Service, cfg callbackRu
|
|||||||
})
|
})
|
||||||
|
|
||||||
server := &http.Server{
|
server := &http.Server{
|
||||||
Addr: cfg.Address,
|
Addr: cfg.Address,
|
||||||
Handler: router,
|
Handler: router,
|
||||||
|
ReadHeaderTimeout: 5 * time.Second,
|
||||||
}
|
}
|
||||||
|
|
||||||
ln, err := net.Listen("tcp", cfg.Address)
|
ln, err := (&net.ListenConfig{}).Listen(context.Background(), "tcp", cfg.Address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func (s *cardPayoutStore) FindByIdempotencyKey(_ context.Context, key string) (*
|
|||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil //nolint:nilnil // test store: payout not found by idempotency key
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cardPayoutStore) FindByOperationRef(_ context.Context, ref string) (*model.CardPayout, error) {
|
func (s *cardPayoutStore) FindByOperationRef(_ context.Context, ref string) (*model.CardPayout, error) {
|
||||||
@@ -64,7 +64,7 @@ func (s *cardPayoutStore) FindByOperationRef(_ context.Context, ref string) (*mo
|
|||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil //nolint:nilnil // test store: payout not found by operation ref
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cardPayoutStore) FindByPaymentID(_ context.Context, id string) (*model.CardPayout, error) {
|
func (s *cardPayoutStore) FindByPaymentID(_ context.Context, id string) (*model.CardPayout, error) {
|
||||||
@@ -75,7 +75,7 @@ func (s *cardPayoutStore) FindByPaymentID(_ context.Context, id string) (*model.
|
|||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil //nolint:nilnil // test store: payout not found by payment id
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *cardPayoutStore) Upsert(_ context.Context, record *model.CardPayout) error {
|
func (s *cardPayoutStore) Upsert(_ context.Context, record *model.CardPayout) error {
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ func findOperationRef(operationRef, payoutID string) string {
|
|||||||
|
|
||||||
func (p *cardPayoutProcessor) findExistingPayoutState(ctx context.Context, state *model.CardPayout) (*model.CardPayout, error) {
|
func (p *cardPayoutProcessor) findExistingPayoutState(ctx context.Context, state *model.CardPayout) (*model.CardPayout, error) {
|
||||||
if p == nil || state == nil {
|
if p == nil || state == nil {
|
||||||
return nil, nil
|
return nil, nil //nolint:nilnil // nil processor/state means there is no existing payout state to load
|
||||||
}
|
}
|
||||||
if opRef := strings.TrimSpace(state.OperationRef); opRef != "" {
|
if opRef := strings.TrimSpace(state.OperationRef); opRef != "" {
|
||||||
existing, err := p.store.Payouts().FindByOperationRef(ctx, opRef)
|
existing, err := p.store.Payouts().FindByOperationRef(ctx, opRef)
|
||||||
@@ -122,12 +122,12 @@ func (p *cardPayoutProcessor) findExistingPayoutState(ctx context.Context, state
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil //nolint:nilnil // nil means no payout state exists for the operation reference
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *cardPayoutProcessor) findAndMergePayoutState(ctx context.Context, state *model.CardPayout) (*model.CardPayout, error) {
|
func (p *cardPayoutProcessor) findAndMergePayoutState(ctx context.Context, state *model.CardPayout) (*model.CardPayout, error) {
|
||||||
if p == nil || state == nil {
|
if p == nil || state == nil {
|
||||||
return nil, nil
|
return nil, nil //nolint:nilnil // nil processor/state means there is no existing payout state to merge
|
||||||
}
|
}
|
||||||
existing, err := p.findExistingPayoutState(ctx, state)
|
existing, err := p.findExistingPayoutState(ctx, state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -862,7 +862,7 @@ func (p *cardPayoutProcessor) retryContext() (context.Context, context.CancelFun
|
|||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
return ctx, func() {}
|
return ctx, func() {}
|
||||||
}
|
}
|
||||||
return context.WithTimeout(ctx, timeout)
|
return context.WithTimeout(ctx, timeout) //nolint:gosec // cancel func is always invoked by caller
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *cardPayoutProcessor) runCardPayoutRetry(req *mntxv1.CardPayoutRequest, attempt uint32, maxAttempts uint32) {
|
func (p *cardPayoutProcessor) runCardPayoutRetry(req *mntxv1.CardPayoutRequest, attempt uint32, maxAttempts uint32) {
|
||||||
@@ -1369,8 +1369,7 @@ func (p *cardPayoutProcessor) Tokenize(ctx context.Context, req *mntxv1.CardToke
|
|||||||
zap.String("customer_id", strings.TrimSpace(req.GetCustomerId())),
|
zap.String("customer_id", strings.TrimSpace(req.GetCustomerId())),
|
||||||
)
|
)
|
||||||
|
|
||||||
cardInput, err := validateCardTokenizeRequest(req, p.config)
|
if _, err := validateCardTokenizeRequest(req, p.config); err != nil {
|
||||||
if err != nil {
|
|
||||||
p.logger.Warn("Card tokenization validation failed",
|
p.logger.Warn("Card tokenization validation failed",
|
||||||
zap.String("request_id", req.GetRequestId()),
|
zap.String("request_id", req.GetRequestId()),
|
||||||
zap.String("customer_id", req.GetCustomerId()),
|
zap.String("customer_id", req.GetCustomerId()),
|
||||||
@@ -1379,14 +1378,15 @@ func (p *cardPayoutProcessor) Tokenize(ctx context.Context, req *mntxv1.CardToke
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
req = sanitizeCardTokenizeRequest(req)
|
||||||
|
cardInput := extractTokenizeCard(req)
|
||||||
|
|
||||||
projectID, err := p.resolveProjectID(req.GetProjectId(), "request_id", req.GetRequestId())
|
projectID, err := p.resolveProjectID(req.GetProjectId(), "request_id", req.GetRequestId())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
req = sanitizeCardTokenizeRequest(req)
|
|
||||||
cardInput = extractTokenizeCard(req)
|
|
||||||
|
|
||||||
token := buildSimulatedCardToken(req.GetRequestId(), cardInput.pan)
|
token := buildSimulatedCardToken(req.GetRequestId(), cardInput.pan)
|
||||||
maskedPAN := provider.MaskPAN(cardInput.pan)
|
maskedPAN := provider.MaskPAN(cardInput.pan)
|
||||||
p.rememberTokenPAN(token, cardInput.pan)
|
p.rememberTokenPAN(token, cardInput.pan)
|
||||||
@@ -1506,7 +1506,8 @@ func (p *cardPayoutProcessor) ProcessCallback(ctx context.Context, payload []byt
|
|||||||
}
|
}
|
||||||
|
|
||||||
retryScheduled := false
|
retryScheduled := false
|
||||||
if state.Status == model.PayoutStatusFailed || state.Status == model.PayoutStatusCancelled {
|
switch state.Status {
|
||||||
|
case model.PayoutStatusFailed, model.PayoutStatusCancelled:
|
||||||
decision := p.retryPolicy.decideProviderFailure(state.ProviderCode)
|
decision := p.retryPolicy.decideProviderFailure(state.ProviderCode)
|
||||||
attemptsUsed := p.currentDispatchAttempt(operationRef)
|
attemptsUsed := p.currentDispatchAttempt(operationRef)
|
||||||
maxAttempts := p.maxDispatchAttempts()
|
maxAttempts := p.maxDispatchAttempts()
|
||||||
@@ -1553,7 +1554,7 @@ func (p *cardPayoutProcessor) ProcessCallback(ctx context.Context, payload []byt
|
|||||||
if !retryScheduled && strings.TrimSpace(state.FailureReason) == "" {
|
if !retryScheduled && strings.TrimSpace(state.FailureReason) == "" {
|
||||||
state.FailureReason = payoutFailureReason(state.ProviderCode, state.ProviderMessage)
|
state.FailureReason = payoutFailureReason(state.ProviderCode, state.ProviderMessage)
|
||||||
}
|
}
|
||||||
} else if state.Status == model.PayoutStatusSuccess {
|
case model.PayoutStatusSuccess:
|
||||||
state.FailureReason = ""
|
state.FailureReason = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,15 @@ func (s staticClock) Now() time.Time {
|
|||||||
return s.now
|
return s.now
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func mustMarshalJSON(t *testing.T, value any) []byte {
|
||||||
|
t.Helper()
|
||||||
|
body, err := json.Marshal(value)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("json marshal failed: %v", err)
|
||||||
|
}
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
type apiResponse struct {
|
type apiResponse struct {
|
||||||
RequestID string `json:"request_id"`
|
RequestID string `json:"request_id"`
|
||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
@@ -70,7 +79,7 @@ func TestCardPayoutProcessor_Submit_Success(t *testing.T) {
|
|||||||
Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
Transport: roundTripperFunc(func(r *http.Request) (*http.Response, error) {
|
||||||
resp := apiResponse{}
|
resp := apiResponse{}
|
||||||
resp.Operation.RequestID = "req-123"
|
resp.Operation.RequestID = "req-123"
|
||||||
body, _ := json.Marshal(resp)
|
body := mustMarshalJSON(t, resp)
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
Body: io.NopCloser(bytes.NewReader(body)),
|
Body: io.NopCloser(bytes.NewReader(body)),
|
||||||
@@ -260,7 +269,7 @@ func TestCardPayoutProcessor_Submit_SameParentDifferentOperationsStoredSeparatel
|
|||||||
callN++
|
callN++
|
||||||
resp := apiResponse{}
|
resp := apiResponse{}
|
||||||
resp.Operation.RequestID = fmt.Sprintf("req-%d", callN)
|
resp.Operation.RequestID = fmt.Sprintf("req-%d", callN)
|
||||||
body, _ := json.Marshal(resp)
|
body := mustMarshalJSON(t, resp)
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
Body: io.NopCloser(bytes.NewReader(body)),
|
Body: io.NopCloser(bytes.NewReader(body)),
|
||||||
@@ -350,7 +359,7 @@ func TestCardPayoutProcessor_StrictMode_BlocksSecondOperationUntilFirstFinalCall
|
|||||||
n := callN.Add(1)
|
n := callN.Add(1)
|
||||||
resp := apiResponse{}
|
resp := apiResponse{}
|
||||||
resp.Operation.RequestID = fmt.Sprintf("req-%d", n)
|
resp.Operation.RequestID = fmt.Sprintf("req-%d", n)
|
||||||
body, _ := json.Marshal(resp)
|
body := mustMarshalJSON(t, resp)
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
Body: io.NopCloser(bytes.NewReader(body)),
|
Body: io.NopCloser(bytes.NewReader(body)),
|
||||||
@@ -544,7 +553,7 @@ func TestCardPayoutProcessor_Submit_RetriesProviderLimitDeclineUntilSuccess(t *t
|
|||||||
if n == 1 {
|
if n == 1 {
|
||||||
resp.Code = providerCodeDeclineAmountOrFrequencyLimit
|
resp.Code = providerCodeDeclineAmountOrFrequencyLimit
|
||||||
resp.Message = "Decline due to amount or frequency limit"
|
resp.Message = "Decline due to amount or frequency limit"
|
||||||
body, _ := json.Marshal(resp)
|
body := mustMarshalJSON(t, resp)
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: http.StatusTooManyRequests,
|
StatusCode: http.StatusTooManyRequests,
|
||||||
Body: io.NopCloser(bytes.NewReader(body)),
|
Body: io.NopCloser(bytes.NewReader(body)),
|
||||||
@@ -552,7 +561,7 @@ func TestCardPayoutProcessor_Submit_RetriesProviderLimitDeclineUntilSuccess(t *t
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
resp.Operation.RequestID = "req-retry-success"
|
resp.Operation.RequestID = "req-retry-success"
|
||||||
body, _ := json.Marshal(resp)
|
body := mustMarshalJSON(t, resp)
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
Body: io.NopCloser(bytes.NewReader(body)),
|
Body: io.NopCloser(bytes.NewReader(body)),
|
||||||
@@ -617,7 +626,7 @@ func TestCardPayoutProcessor_Submit_RetriesProviderLimitDeclineThenFails(t *test
|
|||||||
Code: providerCodeDeclineAmountOrFrequencyLimit,
|
Code: providerCodeDeclineAmountOrFrequencyLimit,
|
||||||
Message: "Decline due to amount or frequency limit",
|
Message: "Decline due to amount or frequency limit",
|
||||||
}
|
}
|
||||||
body, _ := json.Marshal(resp)
|
body := mustMarshalJSON(t, resp)
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: http.StatusTooManyRequests,
|
StatusCode: http.StatusTooManyRequests,
|
||||||
Body: io.NopCloser(bytes.NewReader(body)),
|
Body: io.NopCloser(bytes.NewReader(body)),
|
||||||
@@ -689,7 +698,7 @@ func TestCardPayoutProcessor_ProcessCallback_RetryableDeclineSchedulesRetry(t *t
|
|||||||
} else {
|
} else {
|
||||||
resp.Operation.RequestID = "req-after-callback-retry"
|
resp.Operation.RequestID = "req-after-callback-retry"
|
||||||
}
|
}
|
||||||
body, _ := json.Marshal(resp)
|
body := mustMarshalJSON(t, resp)
|
||||||
return &http.Response{
|
return &http.Response{
|
||||||
StatusCode: http.StatusOK,
|
StatusCode: http.StatusOK,
|
||||||
Body: io.NopCloser(bytes.NewReader(body)),
|
Body: io.NopCloser(bytes.NewReader(body)),
|
||||||
|
|||||||
@@ -40,8 +40,8 @@ func TestValidateCardTokenizeRequest_Expired(t *testing.T) {
|
|||||||
cfg := testProviderConfig()
|
cfg := testProviderConfig()
|
||||||
req := validCardTokenizeRequest()
|
req := validCardTokenizeRequest()
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
req.CardExpMonth = uint32(now.Month())
|
req.CardExpMonth = uint32(now.Month()) //nolint:gosec // month value is bounded by time.Time
|
||||||
req.CardExpYear = uint32(now.Year() - 1)
|
req.CardExpYear = uint32(now.Year() - 1) //nolint:gosec // test value intentionally uses previous year
|
||||||
|
|
||||||
_, err := validateCardTokenizeRequest(req, cfg)
|
_, err := validateCardTokenizeRequest(req, cfg)
|
||||||
requireReason(t, err, "expired_card")
|
requireReason(t, err, "expired_card")
|
||||||
|
|||||||
@@ -251,8 +251,8 @@ func buildCardPayoutRequestFromParams(reader params.Reader,
|
|||||||
AmountMinor: amountMinor,
|
AmountMinor: amountMinor,
|
||||||
Currency: currency,
|
Currency: currency,
|
||||||
CardPan: strings.TrimSpace(reader.String("card_pan")),
|
CardPan: strings.TrimSpace(reader.String("card_pan")),
|
||||||
CardExpYear: uint32(readerInt64(reader, "card_exp_year")),
|
CardExpYear: uint32(readerInt64(reader, "card_exp_year")), //nolint:gosec // values are validated by request validators
|
||||||
CardExpMonth: uint32(readerInt64(reader, "card_exp_month")),
|
CardExpMonth: uint32(readerInt64(reader, "card_exp_month")), //nolint:gosec // values are validated by request validators
|
||||||
CardHolder: strings.TrimSpace(reader.String("card_holder")),
|
CardHolder: strings.TrimSpace(reader.String("card_holder")),
|
||||||
Metadata: metadataFromReader(reader),
|
Metadata: metadataFromReader(reader),
|
||||||
OperationRef: operationRef,
|
OperationRef: operationRef,
|
||||||
|
|||||||
@@ -128,12 +128,13 @@ func (m *strictIsolatedPayoutExecutionMode) tryAcquire(operationRef string) (<-c
|
|||||||
return nil, false, errPayoutExecutionModeStopped
|
return nil, false, errPayoutExecutionModeStopped
|
||||||
}
|
}
|
||||||
|
|
||||||
switch owner := strings.TrimSpace(m.activeOperation); {
|
owner := strings.TrimSpace(m.activeOperation)
|
||||||
case owner == "":
|
switch owner {
|
||||||
|
case "":
|
||||||
m.activeOperation = operationRef
|
m.activeOperation = operationRef
|
||||||
m.signalLocked()
|
m.signalLocked()
|
||||||
return nil, true, nil
|
return nil, true, nil
|
||||||
case owner == operationRef:
|
case operationRef:
|
||||||
return nil, true, nil
|
return nil, true, nil
|
||||||
default:
|
default:
|
||||||
return m.waitCh, false, nil
|
return m.waitCh, false, nil
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ func TestPayoutFailurePolicy_DecideProviderFailure(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
tc := tc
|
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
got := policy.decideProviderFailure(tc.code)
|
got := policy.decideProviderFailure(tc.code)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package gateway
|
package gateway
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -238,6 +238,6 @@ func normalizeExpiryYear(year uint32) string {
|
|||||||
|
|
||||||
func buildSimulatedCardToken(requestID, pan string) string {
|
func buildSimulatedCardToken(requestID, pan string) string {
|
||||||
input := strings.TrimSpace(requestID) + "|" + normalizeCardNumber(pan)
|
input := strings.TrimSpace(requestID) + "|" + normalizeCardNumber(pan)
|
||||||
sum := sha1.Sum([]byte(input))
|
sum := sha256.Sum256([]byte(input))
|
||||||
return "aur_tok_" + hex.EncodeToString(sum[:8])
|
return "aur_tok_" + hex.EncodeToString(sum[:8])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ func (s *Service) startDiscoveryAnnouncer() {
|
|||||||
if strings.TrimSpace(announce.ID) == "" {
|
if strings.TrimSpace(announce.ID) == "" {
|
||||||
announce.ID = discovery.StablePaymentGatewayID(discovery.RailCardPayout)
|
announce.ID = discovery.StablePaymentGatewayID(discovery.RailCardPayout)
|
||||||
}
|
}
|
||||||
s.announcer = discovery.NewAnnouncer(s.logger, s.producer, string(mservice.MntxGateway), announce)
|
s.announcer = discovery.NewAnnouncer(s.logger, s.producer, mservice.MntxGateway, announce)
|
||||||
s.announcer.Start()
|
s.announcer.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ func requireReason(t *testing.T, err error, reason string) {
|
|||||||
if !errors.Is(err, merrors.ErrInvalidArg) {
|
if !errors.Is(err, merrors.ErrInvalidArg) {
|
||||||
t.Fatalf("expected invalid argument error, got %v", err)
|
t.Fatalf("expected invalid argument error, got %v", err)
|
||||||
}
|
}
|
||||||
reasoned, ok := err.(payoutFailure)
|
var reasoned payoutFailure
|
||||||
if !ok {
|
if !errors.As(err, &reasoned) {
|
||||||
t.Fatalf("expected payout failure reason, got %T", err)
|
t.Fatalf("expected payout failure reason, got %T", err)
|
||||||
}
|
}
|
||||||
if reasoned.Reason() != reason {
|
if reasoned.Reason() != reason {
|
||||||
@@ -82,5 +82,5 @@ func validCardTokenizeRequest() *mntxv1.CardTokenizeRequest {
|
|||||||
|
|
||||||
func futureExpiry() (uint32, uint32) {
|
func futureExpiry() (uint32, uint32) {
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
return uint32(now.Month()), uint32(now.Year() + 1)
|
return uint32(now.Month()), uint32(now.Year() + 1) //nolint:gosec // month/year values are bounded by time.Time
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ func (p *cardPayoutProcessor) updatePayoutStatus(ctx context.Context, state *mod
|
|||||||
return nil, emitErr
|
return nil, emitErr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, nil
|
return struct{}{}, nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.logger.Warn("Failed to update transfer status", zap.Error(err), mzap.ObjRef("payout_ref", state.ID),
|
p.logger.Warn("Failed to update transfer status", zap.Error(err), mzap.ObjRef("payout_ref", state.ID),
|
||||||
|
|||||||
47
api/gateway/chain/.golangci.yml
Normal file
47
api/gateway/chain/.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
|
||||||
@@ -321,11 +321,13 @@ func (c *chainGatewayClient) callContext(ctx context.Context) (context.Context,
|
|||||||
if timeout <= 0 {
|
if timeout <= 0 {
|
||||||
timeout = 3 * time.Second
|
timeout = 3 * time.Second
|
||||||
}
|
}
|
||||||
|
//nolint:gosec // Caller receives cancel func and defers it in every call path.
|
||||||
return context.WithTimeout(ctx, timeout)
|
return context.WithTimeout(ctx, timeout)
|
||||||
}
|
}
|
||||||
|
|
||||||
func walletParamsFromRequest(req *chainv1.CreateManagedWalletRequest) (*structpb.Struct, error) {
|
func walletParamsFromRequest(req *chainv1.CreateManagedWalletRequest) (*structpb.Struct, error) {
|
||||||
if req == nil {
|
if req == nil {
|
||||||
|
//nolint:nilnil // Nil request means optional params are absent.
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
params := map[string]interface{}{
|
params := map[string]interface{}{
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ func New(logger mlogger.Logger, cfg Config) (*Manager, error) {
|
|||||||
}
|
}
|
||||||
keys, err := managedkey.New(managedkey.Options{
|
keys, err := managedkey.New(managedkey.Options{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
Config: managedkey.Config(cfg),
|
Config: cfg,
|
||||||
Component: "vault key manager",
|
Component: "vault key manager",
|
||||||
DefaultKeyPrefix: "gateway/chain/wallets",
|
DefaultKeyPrefix: "gateway/chain/wallets",
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -264,6 +264,7 @@ func resolveNetworkConfigs(logger mlogger.Logger, chains []chainConfig) ([]gatew
|
|||||||
|
|
||||||
func buildGasTopUpPolicy(chainName pmodel.ChainNetwork, cfg *gasTopUpPolicyConfig) (*gatewayshared.GasTopUpPolicy, error) {
|
func buildGasTopUpPolicy(chainName pmodel.ChainNetwork, cfg *gasTopUpPolicyConfig) (*gatewayshared.GasTopUpPolicy, error) {
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
|
//nolint:nilnil // Nil config means gas top-up policy is intentionally disabled.
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
defaultRule, defaultSet, err := parseGasTopUpRule(chainName, "default", cfg.gasTopUpRuleConfig)
|
defaultRule, defaultSet, err := parseGasTopUpRule(chainName, "default", cfg.gasTopUpRuleConfig)
|
||||||
|
|||||||
@@ -223,6 +223,7 @@ func defaultGasTopUp(estimatedFee *moneyv1.Money, currentBalance *moneyv1.Money)
|
|||||||
}
|
}
|
||||||
required := estimated.Sub(current)
|
required := estimated.Sub(current)
|
||||||
if !required.IsPositive() {
|
if !required.IsPositive() {
|
||||||
|
//nolint:nilnil // No top-up required is represented as (nil, nil).
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
return &moneyv1.Money{
|
return &moneyv1.Money{
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ func (c *createManagedWalletCommand) Execute(ctx context.Context, req *chainv1.C
|
|||||||
Metadata: metadata,
|
Metadata: metadata,
|
||||||
}
|
}
|
||||||
if description != nil {
|
if description != nil {
|
||||||
wallet.Describable.Description = description
|
wallet.Description = description
|
||||||
}
|
}
|
||||||
|
|
||||||
created, err := c.deps.Storage.Wallets().Create(ctx, wallet)
|
created, err := c.deps.Storage.Wallets().Create(ctx, wallet)
|
||||||
|
|||||||
@@ -42,19 +42,19 @@ func (s *Service) GetCapabilities(_ context.Context, _ *connectorv1.GetCapabilit
|
|||||||
|
|
||||||
func (s *Service) OpenAccount(ctx context.Context, req *connectorv1.OpenAccountRequest) (*connectorv1.OpenAccountResponse, error) {
|
func (s *Service) OpenAccount(ctx context.Context, req *connectorv1.OpenAccountRequest) (*connectorv1.OpenAccountResponse, error) {
|
||||||
if req == nil {
|
if req == nil {
|
||||||
return &connectorv1.OpenAccountResponse{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "open_account: request is required", nil, "")}, nil
|
return &connectorv1.OpenAccountResponse{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "open_account: request is required", nil)}, nil
|
||||||
}
|
}
|
||||||
if req.GetKind() != connectorv1.AccountKind_CHAIN_MANAGED_WALLET {
|
if req.GetKind() != connectorv1.AccountKind_CHAIN_MANAGED_WALLET {
|
||||||
return &connectorv1.OpenAccountResponse{Error: connectorError(connectorv1.ErrorCode_UNSUPPORTED_ACCOUNT_KIND, "open_account: unsupported account kind", nil, "")}, nil
|
return &connectorv1.OpenAccountResponse{Error: connectorError(connectorv1.ErrorCode_UNSUPPORTED_ACCOUNT_KIND, "open_account: unsupported account kind", nil)}, nil
|
||||||
}
|
}
|
||||||
reader := params.New(req.GetParams())
|
reader := params.New(req.GetParams())
|
||||||
orgRef := strings.TrimSpace(reader.String("organization_ref"))
|
orgRef := strings.TrimSpace(reader.String("organization_ref"))
|
||||||
if orgRef == "" {
|
if orgRef == "" {
|
||||||
return &connectorv1.OpenAccountResponse{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "open_account: organization_ref is required", nil, "")}, nil
|
return &connectorv1.OpenAccountResponse{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "open_account: organization_ref is required", nil)}, nil
|
||||||
}
|
}
|
||||||
asset, err := parseChainAsset(strings.TrimSpace(req.GetAsset()), reader)
|
asset, err := parseChainAsset(strings.TrimSpace(req.GetAsset()), reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &connectorv1.OpenAccountResponse{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, err.Error(), nil, "")}, nil
|
return &connectorv1.OpenAccountResponse{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, err.Error(), nil)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := s.CreateManagedWallet(ctx, &chainv1.CreateManagedWalletRequest{
|
resp, err := s.CreateManagedWallet(ctx, &chainv1.CreateManagedWalletRequest{
|
||||||
@@ -66,7 +66,7 @@ func (s *Service) OpenAccount(ctx context.Context, req *connectorv1.OpenAccountR
|
|||||||
Describable: describableFromLabel(req.GetLabel(), reader.String("description")),
|
Describable: describableFromLabel(req.GetLabel(), reader.String("description")),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &connectorv1.OpenAccountResponse{Error: connectorError(mapErrorCode(err), err.Error(), nil, "")}, nil
|
return &connectorv1.OpenAccountResponse{Error: connectorError(mapErrorCode(err), err.Error(), nil)}, nil
|
||||||
}
|
}
|
||||||
return &connectorv1.OpenAccountResponse{Account: chainWalletToAccount(resp.GetWallet())}, nil
|
return &connectorv1.OpenAccountResponse{Account: chainWalletToAccount(resp.GetWallet())}, nil
|
||||||
}
|
}
|
||||||
@@ -136,32 +136,32 @@ func (s *Service) GetBalance(ctx context.Context, req *connectorv1.GetBalanceReq
|
|||||||
|
|
||||||
func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOperationRequest) (*connectorv1.SubmitOperationResponse, error) {
|
func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOperationRequest) (*connectorv1.SubmitOperationResponse, error) {
|
||||||
if req == nil || req.GetOperation() == nil {
|
if req == nil || req.GetOperation() == nil {
|
||||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "submit_operation: operation is required", nil, "")}}, nil
|
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "submit_operation: operation is required", nil)}}, nil
|
||||||
}
|
}
|
||||||
op := req.GetOperation()
|
op := req.GetOperation()
|
||||||
if strings.TrimSpace(op.GetIdempotencyKey()) == "" {
|
if strings.TrimSpace(op.GetIdempotencyKey()) == "" {
|
||||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "submit_operation: idempotency_key is required", op, "")}}, nil
|
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "submit_operation: idempotency_key is required", op)}}, nil
|
||||||
}
|
}
|
||||||
reader := params.New(op.GetParams())
|
reader := params.New(op.GetParams())
|
||||||
orgRef := strings.TrimSpace(reader.String("organization_ref"))
|
orgRef := strings.TrimSpace(reader.String("organization_ref"))
|
||||||
source := operationAccountID(op.GetFrom())
|
source := operationAccountID(op.GetFrom())
|
||||||
if source == "" {
|
if source == "" {
|
||||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "operation: from.account is required", op, "")}}, nil
|
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "operation: from.account is required", op)}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
switch op.GetType() {
|
switch op.GetType() {
|
||||||
case connectorv1.OperationType_TRANSFER:
|
case connectorv1.OperationType_TRANSFER:
|
||||||
dest, err := transferDestinationFromOperation(op)
|
dest, err := transferDestinationFromOperation(op)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, err.Error(), op, "")}}, nil
|
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, err.Error(), op)}}, nil
|
||||||
}
|
}
|
||||||
amount := op.GetMoney()
|
amount := op.GetMoney()
|
||||||
if amount == nil {
|
if amount == nil {
|
||||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "transfer: money is required", op, "")}}, nil
|
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "transfer: money is required", op)}}, nil
|
||||||
}
|
}
|
||||||
amount = normalizeMoneyForChain(amount)
|
amount = normalizeMoneyForChain(amount)
|
||||||
if orgRef == "" {
|
if orgRef == "" {
|
||||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "transfer: organization_ref is required", op, "")}}, nil
|
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "transfer: organization_ref is required", op)}}, nil
|
||||||
}
|
}
|
||||||
resp, err := s.SubmitTransfer(ctx, &chainv1.SubmitTransferRequest{
|
resp, err := s.SubmitTransfer(ctx, &chainv1.SubmitTransferRequest{
|
||||||
IdempotencyKey: strings.TrimSpace(op.GetIdempotencyKey()),
|
IdempotencyKey: strings.TrimSpace(op.GetIdempotencyKey()),
|
||||||
@@ -176,7 +176,7 @@ func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOp
|
|||||||
PaymentRef: strings.TrimSpace(reader.String("payment_ref")),
|
PaymentRef: strings.TrimSpace(reader.String("payment_ref")),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op, "")}}, nil
|
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op)}}, nil
|
||||||
}
|
}
|
||||||
transfer := resp.GetTransfer()
|
transfer := resp.GetTransfer()
|
||||||
return &connectorv1.SubmitOperationResponse{
|
return &connectorv1.SubmitOperationResponse{
|
||||||
@@ -189,11 +189,11 @@ func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOp
|
|||||||
case connectorv1.OperationType_FEE_ESTIMATE:
|
case connectorv1.OperationType_FEE_ESTIMATE:
|
||||||
dest, err := transferDestinationFromOperation(op)
|
dest, err := transferDestinationFromOperation(op)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, err.Error(), op, "")}}, nil
|
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, err.Error(), op)}}, nil
|
||||||
}
|
}
|
||||||
amount := op.GetMoney()
|
amount := op.GetMoney()
|
||||||
if amount == nil {
|
if amount == nil {
|
||||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "estimate: money is required", op, "")}}, nil
|
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "estimate: money is required", op)}}, nil
|
||||||
}
|
}
|
||||||
amount = normalizeMoneyForChain(amount)
|
amount = normalizeMoneyForChain(amount)
|
||||||
opID := strings.TrimSpace(op.GetOperationId())
|
opID := strings.TrimSpace(op.GetOperationId())
|
||||||
@@ -206,7 +206,7 @@ func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOp
|
|||||||
Amount: amount,
|
Amount: amount,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op, "")}}, nil
|
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op)}}, nil
|
||||||
}
|
}
|
||||||
result := feeEstimateResult(resp)
|
result := feeEstimateResult(resp)
|
||||||
return &connectorv1.SubmitOperationResponse{
|
return &connectorv1.SubmitOperationResponse{
|
||||||
@@ -219,7 +219,7 @@ func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOp
|
|||||||
case connectorv1.OperationType_GAS_TOPUP:
|
case connectorv1.OperationType_GAS_TOPUP:
|
||||||
fee, err := parseMoneyFromMap(reader.Map("estimated_total_fee"))
|
fee, err := parseMoneyFromMap(reader.Map("estimated_total_fee"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, err.Error(), op, "")}}, nil
|
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, err.Error(), op)}}, nil
|
||||||
}
|
}
|
||||||
fee = normalizeMoneyForChain(fee)
|
fee = normalizeMoneyForChain(fee)
|
||||||
mode := strings.ToLower(strings.TrimSpace(reader.String("mode")))
|
mode := strings.ToLower(strings.TrimSpace(reader.String("mode")))
|
||||||
@@ -237,7 +237,7 @@ func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOp
|
|||||||
EstimatedTotalFee: fee,
|
EstimatedTotalFee: fee,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op, "")}}, nil
|
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op)}}, nil
|
||||||
}
|
}
|
||||||
return &connectorv1.SubmitOperationResponse{
|
return &connectorv1.SubmitOperationResponse{
|
||||||
Receipt: &connectorv1.OperationReceipt{
|
Receipt: &connectorv1.OperationReceipt{
|
||||||
@@ -252,11 +252,11 @@ func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOp
|
|||||||
opID = strings.TrimSpace(op.GetIdempotencyKey())
|
opID = strings.TrimSpace(op.GetIdempotencyKey())
|
||||||
}
|
}
|
||||||
if orgRef == "" {
|
if orgRef == "" {
|
||||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "gas_topup: organization_ref is required", op, "")}}, nil
|
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "gas_topup: organization_ref is required", op)}}, nil
|
||||||
}
|
}
|
||||||
target := strings.TrimSpace(reader.String("target_wallet_ref"))
|
target := strings.TrimSpace(reader.String("target_wallet_ref"))
|
||||||
if target == "" {
|
if target == "" {
|
||||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "gas_topup: target_wallet_ref is required", op, "")}}, nil
|
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "gas_topup: target_wallet_ref is required", op)}}, nil
|
||||||
}
|
}
|
||||||
resp, err := s.EnsureGasTopUp(ctx, &chainv1.EnsureGasTopUpRequest{
|
resp, err := s.EnsureGasTopUp(ctx, &chainv1.EnsureGasTopUpRequest{
|
||||||
IdempotencyKey: strings.TrimSpace(op.GetIdempotencyKey()),
|
IdempotencyKey: strings.TrimSpace(op.GetIdempotencyKey()),
|
||||||
@@ -270,7 +270,7 @@ func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOp
|
|||||||
PaymentRef: strings.TrimSpace(reader.String("payment_ref")),
|
PaymentRef: strings.TrimSpace(reader.String("payment_ref")),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op, "")}}, nil
|
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(mapErrorCode(err), err.Error(), op)}}, nil
|
||||||
}
|
}
|
||||||
transferRef := ""
|
transferRef := ""
|
||||||
if transfer := resp.GetTransfer(); transfer != nil {
|
if transfer := resp.GetTransfer(); transfer != nil {
|
||||||
@@ -284,10 +284,10 @@ func (s *Service) SubmitOperation(ctx context.Context, req *connectorv1.SubmitOp
|
|||||||
},
|
},
|
||||||
}, nil
|
}, nil
|
||||||
default:
|
default:
|
||||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "gas_topup: invalid mode", op, "")}}, nil
|
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_INVALID_PARAMS, "gas_topup: invalid mode", op)}}, nil
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_UNSUPPORTED_OPERATION, "submit_operation: unsupported operation type", op, "")}}, nil
|
return &connectorv1.SubmitOperationResponse{Receipt: &connectorv1.OperationReceipt{Error: connectorError(connectorv1.ErrorCode_UNSUPPORTED_OPERATION, "submit_operation: unsupported operation type", op)}}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -722,11 +722,10 @@ func structFromMap(values map[string]interface{}) *structpb.Struct {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func connectorError(code connectorv1.ErrorCode, message string, op *connectorv1.Operation, accountID string) *connectorv1.ConnectorError {
|
func connectorError(code connectorv1.ErrorCode, message string, op *connectorv1.Operation) *connectorv1.ConnectorError {
|
||||||
err := &connectorv1.ConnectorError{
|
err := &connectorv1.ConnectorError{
|
||||||
Code: code,
|
Code: code,
|
||||||
Message: strings.TrimSpace(message),
|
Message: strings.TrimSpace(message),
|
||||||
AccountId: strings.TrimSpace(accountID),
|
|
||||||
}
|
}
|
||||||
if op != nil {
|
if op != nil {
|
||||||
err.CorrelationId = strings.TrimSpace(op.GetCorrelationId())
|
err.CorrelationId = strings.TrimSpace(op.GetCorrelationId())
|
||||||
|
|||||||
@@ -297,7 +297,7 @@ func EstimateFee(ctx context.Context, deps driver.Deps, network shared.Network,
|
|||||||
GasPrice: gasPrice,
|
GasPrice: gasPrice,
|
||||||
Value: amountBase,
|
Value: amountBase,
|
||||||
}
|
}
|
||||||
gasLimit, err := estimateGas(timeoutCtx, network, client, rpcClient, callMsg)
|
gasLimit, err := estimateGas(timeoutCtx, client, callMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("Failed to estimate gas", zap.Error(err), zap.Any("call_mesasge", callMsg))
|
logger.Warn("Failed to estimate gas", zap.Error(err), zap.Any("call_mesasge", callMsg))
|
||||||
return nil, merrors.Internal("failed to estimate gas: " + err.Error())
|
return nil, merrors.Internal("failed to estimate gas: " + err.Error())
|
||||||
@@ -345,7 +345,7 @@ func EstimateFee(ctx context.Context, deps driver.Deps, network shared.Network,
|
|||||||
GasPrice: gasPrice,
|
GasPrice: gasPrice,
|
||||||
Data: input,
|
Data: input,
|
||||||
}
|
}
|
||||||
gasLimit, err := estimateGas(timeoutCtx, network, client, rpcClient, callMsg)
|
gasLimit, err := estimateGas(timeoutCtx, client, callMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("Failed to estimate gas", zap.Error(err), zap.Any("call_message", callMsg))
|
logger.Warn("Failed to estimate gas", zap.Error(err), zap.Any("call_message", callMsg))
|
||||||
return nil, merrors.Internal("failed to estimate gas: " + err.Error())
|
return nil, merrors.Internal("failed to estimate gas: " + err.Error())
|
||||||
@@ -456,7 +456,7 @@ func SubmitTransfer(ctx context.Context, deps driver.Deps, network shared.Networ
|
|||||||
GasPrice: gasPrice,
|
GasPrice: gasPrice,
|
||||||
Value: amountInt,
|
Value: amountInt,
|
||||||
}
|
}
|
||||||
gasLimit, err := estimateGas(ctx, network, client, rpcClient, callMsg)
|
gasLimit, err := estimateGas(ctx, client, callMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("Failed to estimate gas", zap.Error(err),
|
logger.Warn("Failed to estimate gas", zap.Error(err),
|
||||||
zap.String("transfer_ref", transfer.TransferRef),
|
zap.String("transfer_ref", transfer.TransferRef),
|
||||||
@@ -504,7 +504,7 @@ func SubmitTransfer(ctx context.Context, deps driver.Deps, network shared.Networ
|
|||||||
GasPrice: gasPrice,
|
GasPrice: gasPrice,
|
||||||
Data: input,
|
Data: input,
|
||||||
}
|
}
|
||||||
gasLimit, err := estimateGas(ctx, network, client, rpcClient, callMsg)
|
gasLimit, err := estimateGas(ctx, client, callMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("Failed to estimate gas", zap.Error(err),
|
logger.Warn("Failed to estimate gas", zap.Error(err),
|
||||||
zap.String("transfer_ref", transfer.TransferRef),
|
zap.String("transfer_ref", transfer.TransferRef),
|
||||||
@@ -653,26 +653,10 @@ type gasEstimator interface {
|
|||||||
EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error)
|
EstimateGas(ctx context.Context, msg ethereum.CallMsg) (uint64, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func estimateGas(ctx context.Context, network shared.Network, client gasEstimator, rpcClient *rpc.Client, callMsg ethereum.CallMsg) (uint64, error) {
|
func estimateGas(ctx context.Context, client gasEstimator, callMsg ethereum.CallMsg) (uint64, error) {
|
||||||
return client.EstimateGas(ctx, callMsg)
|
return client.EstimateGas(ctx, callMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
func estimateGasTron(ctx context.Context, rpcClient *rpc.Client, callMsg ethereum.CallMsg) (uint64, error) {
|
|
||||||
call := tronEstimateCall(callMsg)
|
|
||||||
var hexResp string
|
|
||||||
if err := rpcClient.CallContext(ctx, &hexResp, "eth_estimateGas", call); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
val, err := shared.DecodeHexBig(hexResp)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
if val == nil {
|
|
||||||
return 0, merrors.Internal("failed to decode gas estimate")
|
|
||||||
}
|
|
||||||
return val.Uint64(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func tronEstimateCall(callMsg ethereum.CallMsg) map[string]string {
|
func tronEstimateCall(callMsg ethereum.CallMsg) map[string]string {
|
||||||
call := make(map[string]string)
|
call := make(map[string]string)
|
||||||
if callMsg.From != (common.Address{}) {
|
if callMsg.From != (common.Address{}) {
|
||||||
|
|||||||
@@ -24,15 +24,15 @@ func (s *Service) outboxStore() gatewayoutbox.Store {
|
|||||||
return provider.Outbox()
|
return provider.Outbox()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) startOutboxReliableProducer() error {
|
func (s *Service) startOutboxReliableProducer(_ context.Context) error {
|
||||||
if s == nil || s.storage == nil {
|
if s == nil || s.storage == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return s.outbox.Start(s.logger, s.producer, s.outboxStore(), s.msgCfg)
|
return s.outbox.Start(s.logger, s.producer, s.outboxStore(), s.msgCfg) //nolint:contextcheck // Reliable runtime start API does not accept context.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) sendWithOutbox(ctx context.Context, env me.Envelope) error {
|
func (s *Service) sendWithOutbox(ctx context.Context, env me.Envelope) error {
|
||||||
if err := s.startOutboxReliableProducer(); err != nil {
|
if err := s.startOutboxReliableProducer(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return s.outbox.Send(ctx, env)
|
return s.outbox.Send(ctx, env)
|
||||||
|
|||||||
@@ -163,7 +163,9 @@ func (l *loggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||||
resp.Body.Close()
|
if closeErr := resp.Body.Close(); closeErr != nil {
|
||||||
|
l.logger.Warn("Failed to close RPC response body", append(fields, zap.Error(closeErr))...)
|
||||||
|
}
|
||||||
resp.Body = io.NopCloser(strings.NewReader(string(bodyBytes)))
|
resp.Body = io.NopCloser(strings.NewReader(string(bodyBytes)))
|
||||||
|
|
||||||
respFields := append(fields,
|
respFields := append(fields,
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ func NewService(logger mlogger.Logger, repo storage.Repository, producer msg.Pro
|
|||||||
}
|
}
|
||||||
svc.settings = svc.settings.withDefaults()
|
svc.settings = svc.settings.withDefaults()
|
||||||
svc.networkRegistry = rpcclient.NewRegistry(svc.networks, svc.rpcClients)
|
svc.networkRegistry = rpcclient.NewRegistry(svc.networks, svc.rpcClients)
|
||||||
if err := svc.startOutboxReliableProducer(); err != nil {
|
if err := svc.startOutboxReliableProducer(context.Background()); err != nil {
|
||||||
svc.logger.Warn("Failed to initialise outbox reliable producer", zap.Error(err))
|
svc.logger.Warn("Failed to initialise outbox reliable producer", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +231,7 @@ func (s *Service) startDiscoveryAnnouncers() {
|
|||||||
InvokeURI: s.invokeURI,
|
InvokeURI: s.invokeURI,
|
||||||
Version: version,
|
Version: version,
|
||||||
}
|
}
|
||||||
announcer := discovery.NewAnnouncer(s.logger, s.producer, string(mservice.ChainGateway), announce)
|
announcer := discovery.NewAnnouncer(s.logger, s.producer, mservice.ChainGateway, announce)
|
||||||
announcer.Start()
|
announcer.Start()
|
||||||
s.announcers = append(s.announcers, announcer)
|
s.announcers = append(s.announcers, announcer)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -272,7 +272,7 @@ type walletsNoDataRepository struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *walletsNoDataRepository) Wallets() storage.WalletsStore {
|
func (r *walletsNoDataRepository) Wallets() storage.WalletsStore {
|
||||||
return &walletsNoDataStore{WalletsStore: r.inMemoryRepository.wallets}
|
return &walletsNoDataStore{WalletsStore: r.wallets}
|
||||||
}
|
}
|
||||||
|
|
||||||
type walletsNoDataStore struct {
|
type walletsNoDataStore struct {
|
||||||
@@ -673,10 +673,11 @@ func sanitizeLimit(requested int32, def, max int64) int64 {
|
|||||||
if requested <= 0 {
|
if requested <= 0 {
|
||||||
return def
|
return def
|
||||||
}
|
}
|
||||||
if requested > int32(max) {
|
requested64 := int64(requested)
|
||||||
|
if requested64 > max {
|
||||||
return max
|
return max
|
||||||
}
|
}
|
||||||
return int64(requested)
|
return requested64
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestService(t *testing.T) (*Service, *inMemoryRepository) {
|
func newTestService(t *testing.T) (*Service, *inMemoryRepository) {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package shared
|
package shared
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -46,5 +47,9 @@ func DecodeHexUint8(input string) (uint8, error) {
|
|||||||
if val.BitLen() > 8 {
|
if val.BitLen() > 8 {
|
||||||
return 0, errHexOutOfRange
|
return 0, errHexOutOfRange
|
||||||
}
|
}
|
||||||
return uint8(val.Uint64()), nil
|
decoded := val.Uint64()
|
||||||
|
if decoded > math.MaxUint8 {
|
||||||
|
return 0, errHexOutOfRange
|
||||||
|
}
|
||||||
|
return uint8(decoded), nil
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user