fixed currency pair validation

This commit is contained in:
Stephan D
2025-12-11 23:27:15 +01:00
parent 7cd9e14618
commit 39f323d050

View File

@@ -20,41 +20,17 @@ type AssetResolver interface {
// Precompile regex for efficiency.
var currencySyntax = regexp.MustCompile(`^[A-Z0-9]{2,10}$`)
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")
}
if strings.TrimSpace(m.Currency) == "" {
// 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")
}
//
// 2) Validate decimal amount
//
amount, err := decimal.NewFromString(m.Amount)
if err != nil {
return merrors.InvalidArgument("invalid decimal amount", "intent.amount")
}
// Normalize
cur = strings.ToUpper(strings.TrimSpace(cur))
if amount.IsNegative() {
return merrors.InvalidArgument("amount must be >= 0", "intent.amount")
}
//
// 3) Normalize currency
//
cur := strings.ToUpper(strings.TrimSpace(m.Currency))
//
// 4) Syntax validation first — reject malformed tickers early
//
// Syntax check
if !currencySyntax.MatchString(cur) {
return merrors.InvalidArgument(
"invalid currency format (must be AZ09, length 210)",
@@ -62,9 +38,7 @@ func ValidateMoney(m *model.Money, assetResolver AssetResolver) error {
)
}
//
// 5) Dictionary / environment validation
//
// Dictionary validation
if assetResolver == nil {
return merrors.InvalidArgument("asset resolver is not configured", "intent.currency")
}
@@ -76,37 +50,48 @@ func ValidateMoney(m *model.Money, assetResolver AssetResolver) error {
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.Base == "" {
return merrors.InvalidArgument("base currency is required", "intent.fx.pair.base")
if p == nil {
return merrors.InvalidArgument("currency pair is required", "currncy_pair")
}
if p.Quote == "" {
return merrors.InvalidArgument("quote currency is required", "intent.fx.pair.quote")
if err := ValidateCurrency(p.Base, nil); err != nil {
return merrors.InvalidArgument("invalid base currency in pair: "+err.Error(), "currency_pair.base")
}
if len(p.Base) != 3 {
return merrors.InvalidArgument("base currency must be 3 letters", "intent.fx.pair.base")
if err := ValidateCurrency(p.Quote, nil); err != nil {
return merrors.InvalidArgument("invalid quote currency in pair: "+err.Error(), "currency_pair.quote")
}
if len(p.Quote) != 3 {
return merrors.InvalidArgument("quote currency must be 3 letters", "intent.fx.pair.quote")
}
for _, c := range p.Base {
if c < 'A' || c > 'Z' {
return merrors.InvalidArgument("base currency must be uppercase A-Z", "intent.fx.pair.base")
}
}
for _, c := range p.Quote {
if c < 'A' || c > 'Z' {
return merrors.InvalidArgument("quote currency must be uppercase A-Z", "intent.fx.pair.quote")
}
}
return nil
}