132 lines
3.6 KiB
Go
132 lines
3.6 KiB
Go
package srequest
|
||
|
||
import (
|
||
"regexp"
|
||
"strings"
|
||
|
||
"github.com/shopspring/decimal"
|
||
"github.com/tech/sendico/pkg/merrors"
|
||
"github.com/tech/sendico/pkg/model"
|
||
)
|
||
|
||
// AssetResolver defines environment-specific supported assets.
|
||
// Implementations should check:
|
||
// - fiat assets (ISO-4217)
|
||
// - crypto assets supported by gateways / FX providers
|
||
type AssetResolver interface {
|
||
IsSupported(ticker string) bool
|
||
}
|
||
|
||
// Precompile regex for efficiency.
|
||
var currencySyntax = regexp.MustCompile(`^[A-Z0-9]{2,10}$`)
|
||
|
||
// ValidateCurrency validates currency syntax and checks dictionary via assetResolver.
|
||
func ValidateCurrency(cur string, assetResolver AssetResolver) error {
|
||
// Basic presence
|
||
if strings.TrimSpace(cur) == "" {
|
||
return merrors.InvalidArgument("currency is required", "intent.currency")
|
||
}
|
||
|
||
// Normalize
|
||
cur = strings.ToUpper(strings.TrimSpace(cur))
|
||
|
||
// Syntax check
|
||
if !currencySyntax.MatchString(cur) {
|
||
return merrors.InvalidArgument(
|
||
"invalid currency format (must be A–Z0–9, length 2–10)",
|
||
"intent.currency",
|
||
)
|
||
}
|
||
|
||
// Dictionary validation
|
||
if assetResolver == nil {
|
||
return merrors.InvalidArgument("asset resolver is not configured", "intent.currency")
|
||
}
|
||
|
||
if !assetResolver.IsSupported(cur) {
|
||
return merrors.InvalidArgument("unsupported currency/asset", "intent.currency")
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func ValidateMoney(m *model.Money, assetResolver AssetResolver) error {
|
||
if m == nil {
|
||
return merrors.InvalidArgument("money is required", "intent.amount")
|
||
}
|
||
|
||
// 1) Basic presence
|
||
if strings.TrimSpace(m.Amount) == "" {
|
||
return merrors.InvalidArgument("amount is required", "intent.amount")
|
||
}
|
||
|
||
// 2) Validate decimal amount
|
||
amount, err := decimal.NewFromString(m.Amount)
|
||
if err != nil {
|
||
return merrors.InvalidArgument("invalid decimal amount", "intent.amount")
|
||
}
|
||
if amount.IsNegative() {
|
||
return merrors.InvalidArgument("amount must be >= 0", "intent.amount")
|
||
}
|
||
|
||
// 3) Validate currency via helper
|
||
if err := ValidateCurrency(m.Currency, assetResolver); err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
type CurrencyPair struct {
|
||
Base string `json:"base"`
|
||
Quote string `json:"quote"`
|
||
}
|
||
|
||
func (p *CurrencyPair) Validate() error {
|
||
if p == nil {
|
||
return merrors.InvalidArgument("currency pair is required", "currncy_pair")
|
||
}
|
||
if err := ValidateCurrency(p.Base, &AssetResolverStub{}); err != nil {
|
||
return merrors.InvalidArgument("invalid base currency in pair: "+err.Error(), "currency_pair.base")
|
||
}
|
||
if err := ValidateCurrency(p.Quote, &AssetResolverStub{}); err != nil {
|
||
return merrors.InvalidArgument("invalid quote currency in pair: "+err.Error(), "currency_pair.quote")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
type FXIntent struct {
|
||
Pair *CurrencyPair `json:"pair,omitempty"`
|
||
Side FXSide `json:"side,omitempty"`
|
||
Firm bool `json:"firm,omitempty"`
|
||
TTLms int64 `json:"ttl_ms,omitempty"`
|
||
PreferredProvider string `json:"preferred_provider,omitempty"`
|
||
MaxAgeMs int32 `json:"max_age_ms,omitempty"`
|
||
}
|
||
|
||
func (fx *FXIntent) Validate() error {
|
||
if fx.Pair != nil {
|
||
if err := fx.Pair.Validate(); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
var zeroSide FXSide
|
||
if fx.Side == zeroSide {
|
||
return merrors.InvalidArgument("fx side is required", "intent.fx.side")
|
||
}
|
||
|
||
if fx.TTLms < 0 {
|
||
return merrors.InvalidArgument("fx ttl_ms cannot be negative", "intent.fx.ttl_ms")
|
||
}
|
||
if fx.TTLms == 0 && fx.Firm {
|
||
return merrors.InvalidArgument("firm quote requires positive ttl_ms", "intent.fx.ttl_ms")
|
||
}
|
||
|
||
if fx.MaxAgeMs < 0 {
|
||
return merrors.InvalidArgument("fx max_age_ms cannot be negative", "intent.fx.max_age_ms")
|
||
}
|
||
|
||
return nil
|
||
}
|