From a537d1c0bbe7185c952ec0c7413726d2177355ad Mon Sep 17 00:00:00 2001 From: Savely Krendelhoff Date: Sat, 6 Sep 2025 12:20:05 +0700 Subject: [PATCH] Define domain layer --- domain/entities.go | 131 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 domain/entities.go diff --git a/domain/entities.go b/domain/entities.go new file mode 100644 index 0000000..5bdb23a --- /dev/null +++ b/domain/entities.go @@ -0,0 +1,131 @@ +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) +}