package domain import ( "errors" "fmt" "strings" "time" "github.com/shopspring/decimal" ) // Domain-specific errors var ( ErrInvalidCurrency = errors.New("invalid currency") ErrInvalidAmount = errors.New("invalid amount") ErrNegativeAmount = errors.New("amount cannot be negative") ErrInvalidRate = errors.New("invalid exchange rate") ErrCurrencyMismatch = errors.New("currency mismatch") ErrRateNotFound = errors.New("exchange rate not found") ErrUnsupportedCurrency = errors.New("unsupported currency") ) // Currency represents a currency with its metadata type Currency struct { Code string Name string Precision int } // NewCurrency creates a new Currency with validation func NewCurrency(code, name string, precision int) (Currency, error) { if strings.TrimSpace(code) == "" { return Currency{}, ErrInvalidCurrency } if precision < 0 { return Currency{}, ErrInvalidCurrency } return Currency{ Code: strings.ToUpper(strings.TrimSpace(code)), Name: strings.TrimSpace(name), Precision: precision, }, nil } // String returns the currency code func (c Currency) String() string { return c.Code } // Money represents an amount in a specific currency type Money struct { Amount decimal.Decimal Currency Currency } // NewMoney creates a new Money instance from string amount func NewMoney(amount string, currency Currency) (Money, error) { if strings.TrimSpace(amount) == "" { return Money{}, ErrInvalidAmount } decimalAmount, err := decimal.NewFromString(strings.TrimSpace(amount)) if err != nil { return Money{}, ErrInvalidAmount } if decimalAmount.IsNegative() { return Money{}, ErrNegativeAmount } return Money{ Amount: decimalAmount, Currency: currency, }, nil } // NewMoneyFromDecimal creates a new Money instance from decimal.Decimal func NewMoneyFromDecimal(amount decimal.Decimal, currency Currency) (Money, error) { if amount.IsNegative() { return Money{}, ErrNegativeAmount } return Money{ Amount: amount, Currency: currency, }, nil } // String returns formatted money representation func (m Money) String() string { return fmt.Sprintf("%s %s", m.Amount.StringFixed(int32(m.Currency.Precision)), m.Currency.Code) } // Rate represents an exchange rate between two currencies type Rate struct { From Currency To Currency Value decimal.Decimal Timestamp time.Time Source string } // NewRate creates a new Rate with validation func NewRate(from, to Currency, value decimal.Decimal, source string) (Rate, error) { if from.Code == to.Code { return Rate{}, ErrInvalidRate } if value.IsNegative() || value.IsZero() { return Rate{}, ErrInvalidRate } return Rate{ From: from, To: to, Value: value, Timestamp: time.Now(), Source: strings.TrimSpace(source), }, nil } // String returns formatted rate representation func (r Rate) String() string { return fmt.Sprintf("1 %s = %s %s", r.From.Code, r.Value.StringFixed(int32(r.To.Precision)), r.To.Code) }