Skip to content

Commit

Permalink
#40
Browse files Browse the repository at this point in the history
Feature Request: Add a way to register custom (non-ISO) currencies
  • Loading branch information
dejurin committed Dec 23, 2024
1 parent 2411bcd commit 9a9074a
Show file tree
Hide file tree
Showing 2 changed files with 142 additions and 0 deletions.
91 changes: 91 additions & 0 deletions register.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package currency

import (
"fmt"
)

// EmptyCurrencyCodeError indicates that the currency code was empty.
type EmptyCurrencyCodeError struct{}

func (e EmptyCurrencyCodeError) Error() string {
return "register currency error: empty currency code"
}

// CurrencyAlreadyExistsError indicates that the currency code already exists in the ISO list.
type CurrencyAlreadyExistsError struct {
Code string
}

func (e CurrencyAlreadyExistsError) Error() string {
return fmt.Sprintf("register currency error: code %q already exists in ISO list", e.Code)
}

// RegisterCurrencyOptions defines parameters for registering a new or custom currency.
type RegisterCurrencyOptions struct {
// NumericCode is usually a three-digit code, for example "999".
NumericCode string

// Digits is the number of decimal fraction digits.
Digits uint8

// SymbolData is a list of possible symbols and the locales
// in which each symbol is used.
//
// Example:
// []SymbolData{
// {Symbol: "₿", Locales: []string{"en"}},
// {Symbol: "BTC", Locales: []string{"uk"}},
// }
SymbolData []SymbolData
}

// SymbolData describes one symbol and the set of locales
// for which that symbol applies.
type SymbolData struct {
Symbol string
Locales []string
}

// RegisterCurrency adds a non-ISO currency to the global structures:
// - currencies
// - currencyCodes
// - currencySymbols
//
// It returns an error if the code already exists in the ISO list, or if the code is empty.
func RegisterCurrency(code string, opts RegisterCurrencyOptions) error {
if code == "" {
return EmptyCurrencyCodeError{}
}
if _, isoExists := currencies[code]; isoExists {
return CurrencyAlreadyExistsError{Code: code}
}

// Insert into the global `currencies` map.
currencies[code] = currencyInfo{
numericCode: opts.NumericCode,
digits: opts.Digits,
}

// Also append to currencyCodes, so that GetCurrencyCodes() is aware of it.
currencyCodes = append(currencyCodes, code)

// If SymbolData is provided, insert symbols into currencySymbols.
if len(opts.SymbolData) > 0 {
// Ensure there's a slice for 'code' in currencySymbols.
if _, ok := currencySymbols[code]; !ok {
currencySymbols[code] = []symbolInfo{}
}
// Add each entry from opts.SymbolData.
for _, s := range opts.SymbolData {
currencySymbols[code] = append(
currencySymbols[code],
symbolInfo{
symbol: s.Symbol,
locales: s.Locales,
},
)
}
}

return nil
}
51 changes: 51 additions & 0 deletions register_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package currency

import (
"testing"
)

func TestRegisterCurrencyBTC(t *testing.T) {
err := RegisterCurrency("BTC", RegisterCurrencyOptions{
NumericCode: "1000",
Digits: 8,
SymbolData: []SymbolData{
{
Symbol: "₿",
Locales: []string{"en"},
},
{
Symbol: "BTC",
Locales: []string{"uk"},
},
},
})
if err != nil {
t.Errorf("RegisterCurrency returned an error for BTC: %v", err)
}

if !IsValid("BTC") {
t.Error("Expected 'BTC' to be valid after registration, but IsValid returned false.")
}

d, ok := GetDigits("BTC")
if !ok {
t.Error("Expected 'BTC' to be found, but GetDigits says not ok.")
} else if d != 8 {
t.Errorf("Expected 'BTC' digits=8, got %d", d)
}

symEN, _ := GetSymbol("BTC", NewLocale("en")) // "₿"
if symEN != "₿" {
t.Errorf("Expected '₿' for locale 'en', got '%s'", symEN)
}

symRU, _ := GetSymbol("BTC", NewLocale("uk")) // "BTC"
if symRU != "BTC" {
t.Errorf("Expected 'BTC' for locale 'uk', got '%s'", symRU)
}

err = RegisterCurrency("BTC", RegisterCurrencyOptions{})
if err == nil {
t.Error("Expected an error when re-registering code 'BTC', but got nil.")
}
}

0 comments on commit 9a9074a

Please sign in to comment.