@@ -1,30 +1,76 @@
package srequest
import (
"regexp"
"strings"
"github.com/shopspring/decimal"
"github.com/tech/sendico/pkg/merrors"
"github.com/tech/sendico/pkg/model"
)
func ValidateMoney ( m * model . Money ) error {
if m . Amount == "" {
// 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}$ ` )
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 m . Currency == "" {
if strings . TrimSpace ( m. Currency ) == "" {
return merrors . InvalidArgument ( "currency is required" , "intent.currency" )
}
if _ , err := decimal . NewFromString ( m . Amount ) ; err != nil {
return merrors . InvalidArgument ( "invalid amount decimal" , "intent. amount" )
//
// 2) Validate decimal amount
//
amount, err := decimal.NewFromString(m.Amount)
if err != nil {
return merrors . InvalidArgument ( "invalid decimal amount" , "intent.amount" )
}
if len ( m . Currency ) != 3 {
return merrors . InvalidArgument ( "currency must be 3 letters " , "intent.currency " )
if amount . IsNegative ( ) {
return merrors . InvalidArgument ( "amount must be >= 0 " , "intent.amount " )
}
for _ , c := range m . Currency {
if c < 'A' || c > 'Z' {
return merrors . InvalidArgument ( "currency must be uppercase A-Z" , "intent. currency" )
}
//
// 3) Normalize currency
//
cur := strings.ToUpper(strings.TrimSpace(m.Currency))
//
// 4) Syntax validation first — reject malformed tickers early
//
if !currencySyntax.MatchString(cur) {
return merrors . InvalidArgument (
"invalid currency format (must be A– Z0– 9, length 2– 10)" ,
"intent.currency" ,
)
}
//
// 5) Dictionary / environment 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