mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 15:09:42 +00:00
currency: Fix edge case in NewCode where all string characters are digits (#1593)
* add fixes and test * glorious: nits, privatise upper case field, add item field for case sensitivity for format checks, rm dead code. * fix potential panic * gk/glorious: nits * gk: nits and other things * improve commentary lol * glorious+gk: nits and improvements (with no sillyness this time) * Update currency/pairs.go Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io> * thrasher: nitssssss * linter: fix * bye bye silly boy --------- Co-authored-by: Ryan O'Hara-Reid <ryan.oharareid@thrasher.io> Co-authored-by: Adrian Gallagher <adrian.gallagher@thrasher.io>
This commit is contained in:
@@ -186,14 +186,7 @@ func (b *BaseCodes) Register(c string, newRole Role) Code {
|
||||
return EMPTYCODE
|
||||
}
|
||||
|
||||
var format bool
|
||||
// Digits fool upper and lower casing. So find first letter and check case.
|
||||
for x := range c {
|
||||
if unicode.IsLetter(rune(c[x])) {
|
||||
format = unicode.IsUpper(rune(c[x]))
|
||||
break
|
||||
}
|
||||
}
|
||||
isUpperCase := strings.ContainsFunc(c, func(r rune) bool { return unicode.IsLetter(r) && unicode.IsUpper(r) })
|
||||
|
||||
// Force upper string storage and matching
|
||||
c = strings.ToUpper(c)
|
||||
@@ -218,13 +211,13 @@ func (b *BaseCodes) Register(c string, newRole Role) Code {
|
||||
}
|
||||
stored[x].Role = newRole
|
||||
}
|
||||
return Code{Item: stored[x], UpperCase: format}
|
||||
return Code{Item: stored[x], upperCase: isUpperCase}
|
||||
}
|
||||
}
|
||||
|
||||
newItem := &Item{Symbol: c, Lower: strings.ToLower(c), Role: newRole}
|
||||
b.Items[c] = append(b.Items[c], newItem)
|
||||
return Code{Item: newItem, UpperCase: format}
|
||||
return Code{Item: newItem, upperCase: isUpperCase}
|
||||
}
|
||||
|
||||
// LoadItem sets item data
|
||||
@@ -275,21 +268,29 @@ func (c Code) String() string {
|
||||
if c.Item == nil {
|
||||
return ""
|
||||
}
|
||||
if c.UpperCase {
|
||||
if c.upperCase {
|
||||
return c.Item.Symbol
|
||||
}
|
||||
return c.Item.Lower
|
||||
}
|
||||
|
||||
// Lower converts the code to lowercase formatting
|
||||
// Lower flags the Code to use LowerCase formatting, but does not change Symbol
|
||||
// If Code cannot be lowercased then it will return Code unchanged
|
||||
func (c Code) Lower() Code {
|
||||
c.UpperCase = false
|
||||
if c.Item == nil {
|
||||
return c
|
||||
}
|
||||
c.upperCase = false
|
||||
return c
|
||||
}
|
||||
|
||||
// Upper converts the code to uppercase formatting
|
||||
// Upper flags the Code to use UpperCase formatting, but does not change Symbol
|
||||
// If Code cannot be uppercased then it will return Code unchanged
|
||||
func (c Code) Upper() Code {
|
||||
c.UpperCase = true
|
||||
if c.Item == nil {
|
||||
return c
|
||||
}
|
||||
c.upperCase = true
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -346,21 +347,3 @@ func (i *Item) Currency() Code {
|
||||
}
|
||||
return NewCode(i.Symbol)
|
||||
}
|
||||
|
||||
// UpperCurrency allows an item to revert to a code
|
||||
// taking an upper
|
||||
func (i *Item) UpperCurrency() Code {
|
||||
if i == nil {
|
||||
return EMPTYCODE.Upper()
|
||||
}
|
||||
return NewCode(i.Symbol).Upper()
|
||||
}
|
||||
|
||||
// LowerCurrency allows an item to revert to a code
|
||||
// returning in lower format
|
||||
func (i *Item) LowerCurrency() Code {
|
||||
if i == nil {
|
||||
return EMPTYCODE.Lower()
|
||||
}
|
||||
return NewCode(i.Symbol).Lower()
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestRoleString(t *testing.T) {
|
||||
@@ -456,6 +458,16 @@ func TestBaseCode(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewCodeFormatting(t *testing.T) {
|
||||
require.True(t, NewCode("BTC").upperCase)
|
||||
require.False(t, NewCode("btc").upperCase)
|
||||
require.True(t, NewCode("BTC").Equal(NewCode("btc")))
|
||||
require.False(t, NewCode("420").upperCase)
|
||||
require.False(t, NewCode("btc420").upperCase)
|
||||
require.False(t, NewCode("420").Lower().upperCase)
|
||||
require.True(t, NewCode("4BTC").upperCase)
|
||||
}
|
||||
|
||||
func TestCodeString(t *testing.T) {
|
||||
if cc, expected := NewCode("TEST"), "TEST"; cc.String() != expected {
|
||||
t.Errorf("Currency Code String() error expected %s but received %s",
|
||||
|
||||
@@ -40,7 +40,7 @@ type Code struct {
|
||||
// TODO: Below will force the use of the Equal method for comparison. Big
|
||||
// job to update all maps and instances through the code base.
|
||||
// _ []struct{}
|
||||
UpperCase bool
|
||||
upperCase bool
|
||||
}
|
||||
|
||||
// Item defines a sub type containing the main attributes of a designated
|
||||
|
||||
@@ -497,7 +497,7 @@ func (p *PairsManager) SetDelimitersFromConfig() error {
|
||||
}
|
||||
for i, p := range []Pairs{s.Enabled, s.Available} {
|
||||
for j := range p {
|
||||
if p[j].Delimiter == cf.Delimiter {
|
||||
if cf.Delimiter == "" || p[j].Delimiter == cf.Delimiter {
|
||||
continue
|
||||
}
|
||||
nP, err := NewPairDelimiter(p[j].String(), cf.Delimiter)
|
||||
|
||||
@@ -842,7 +842,7 @@ func TestPairManagerSetDelimitersFromConfig(t *testing.T) {
|
||||
err = json.Unmarshal([]byte(`{"pairs":{"spot":{"configFormat":{"delimiter":"_"},"enabled":"BTC-USDT","available":"BTC-USDT"}}}`), p)
|
||||
if assert.NoError(t, err, "UnmarshalJSON should not error") {
|
||||
err := p.SetDelimitersFromConfig()
|
||||
assert.ErrorContains(t, err, "spot.enabled.BTC-USDT: delimiter: [_] not found in currencypair string", "SetDelimitersFromConfig should error correctly")
|
||||
assert.ErrorIs(t, err, errDelimiterNotFound, "SetDelimitersFromConfig should error correctly")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,11 @@ import (
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var errCannotCreatePair = errors.New("cannot create currency pair")
|
||||
var (
|
||||
errCannotCreatePair = errors.New("cannot create currency pair")
|
||||
errDelimiterNotFound = errors.New("delimiter not found")
|
||||
errDelimiterCannotBeEmpty = errors.New("delimiter cannot be empty")
|
||||
)
|
||||
|
||||
// NewBTCUSDT is a shortcut for NewPair(BTC, USDT)
|
||||
func NewBTCUSDT() Pair {
|
||||
@@ -21,25 +25,18 @@ func NewBTCUSD() Pair {
|
||||
|
||||
// NewPairDelimiter splits the desired currency string at delimiter, then returns a Pair struct
|
||||
func NewPairDelimiter(currencyPair, delimiter string) (Pair, error) {
|
||||
if !strings.Contains(currencyPair, delimiter) {
|
||||
if currencyPair == "" {
|
||||
return EMPTYPAIR, errEmptyPairString
|
||||
}
|
||||
if delimiter == "" {
|
||||
return EMPTYPAIR, errDelimiterCannotBeEmpty
|
||||
}
|
||||
index := strings.Index(currencyPair, delimiter)
|
||||
if index == -1 {
|
||||
return EMPTYPAIR,
|
||||
fmt.Errorf("delimiter: [%s] not found in currencypair string", delimiter)
|
||||
fmt.Errorf("supplied pair: [%s] %s %w", currencyPair, delimiter, errDelimiterNotFound)
|
||||
}
|
||||
result := strings.Split(currencyPair, delimiter)
|
||||
if len(result) < 2 {
|
||||
return EMPTYPAIR,
|
||||
fmt.Errorf("supplied pair: [%s] cannot be split with %s",
|
||||
currencyPair,
|
||||
delimiter)
|
||||
}
|
||||
if len(result) > 2 {
|
||||
result[1] = strings.Join(result[1:], delimiter)
|
||||
}
|
||||
return Pair{
|
||||
Delimiter: delimiter,
|
||||
Base: NewCode(result[0]),
|
||||
Quote: NewCode(result[1]),
|
||||
}, nil
|
||||
return Pair{Delimiter: delimiter, Base: NewCode(currencyPair[:index]), Quote: NewCode(currencyPair[index+1:])}, nil
|
||||
}
|
||||
|
||||
// NewPairFromStrings returns a CurrencyPair without a delimiter
|
||||
@@ -61,19 +58,12 @@ func NewPairFromStrings(base, quote string) (Pair, error) {
|
||||
|
||||
// NewPair returns a currency pair from currency codes
|
||||
func NewPair(baseCurrency, quoteCurrency Code) Pair {
|
||||
return Pair{
|
||||
Base: baseCurrency,
|
||||
Quote: quoteCurrency,
|
||||
}
|
||||
return Pair{Base: baseCurrency, Quote: quoteCurrency}
|
||||
}
|
||||
|
||||
// NewPairWithDelimiter returns a CurrencyPair with a delimiter
|
||||
func NewPairWithDelimiter(base, quote, delimiter string) Pair {
|
||||
return Pair{
|
||||
Base: NewCode(base),
|
||||
Quote: NewCode(quote),
|
||||
Delimiter: delimiter,
|
||||
}
|
||||
return Pair{Base: NewCode(base), Quote: NewCode(quote), Delimiter: delimiter}
|
||||
}
|
||||
|
||||
// NewPairFromString converts currency string into a new CurrencyPair
|
||||
@@ -150,11 +140,21 @@ func MatchPairsWithNoDelimiter(currencyPair string, pairs Pairs, pairFmt PairFor
|
||||
|
||||
// GetFormatting returns the formatting style of a pair
|
||||
func (p Pair) GetFormatting() (PairFormat, error) {
|
||||
if p.Base.UpperCase != p.Quote.UpperCase {
|
||||
return PairFormat{}, fmt.Errorf("%w casing mismatch", errPairFormattingInconsistent)
|
||||
if p.Base.isCaseSensitive() && p.Quote.isCaseSensitive() && (p.Base.upperCase != p.Quote.upperCase) {
|
||||
return EMPTYFORMAT, fmt.Errorf("%w casing mismatch", errPairFormattingInconsistent)
|
||||
}
|
||||
return PairFormat{
|
||||
Uppercase: p.Base.UpperCase,
|
||||
Delimiter: p.Delimiter,
|
||||
}, nil
|
||||
return PairFormat{Uppercase: p.Base.upperCase || p.Quote.upperCase, Delimiter: p.Delimiter}, nil
|
||||
}
|
||||
|
||||
func (p Pair) hasFormatDifference(pairFmt PairFormat) bool {
|
||||
return p.Delimiter != pairFmt.Delimiter ||
|
||||
(p.Base.isCaseSensitive() && p.Base.upperCase != pairFmt.Uppercase) ||
|
||||
(p.Quote.isCaseSensitive() && p.Quote.upperCase != pairFmt.Uppercase)
|
||||
}
|
||||
|
||||
func (c Code) isCaseSensitive() bool {
|
||||
if c.Item == nil {
|
||||
return false
|
||||
}
|
||||
return c.Item.Symbol != c.Item.Lower
|
||||
}
|
||||
|
||||
@@ -349,66 +349,29 @@ func TestNewPairWithDelimiter(t *testing.T) {
|
||||
func TestNewPairDelimiter(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := NewPairDelimiter("", "")
|
||||
if err == nil {
|
||||
t.Fatal("error cannot be nil")
|
||||
}
|
||||
require.ErrorIs(t, err, errEmptyPairString)
|
||||
|
||||
_, err = NewPairDelimiter("BTC_USD", "")
|
||||
require.ErrorIs(t, err, errDelimiterCannotBeEmpty)
|
||||
|
||||
_, err = NewPairDelimiter("BTC_USD", "wow")
|
||||
if err == nil {
|
||||
t.Fatal("error cannot be nil")
|
||||
}
|
||||
require.ErrorIs(t, err, errDelimiterNotFound)
|
||||
|
||||
_, err = NewPairDelimiter("BTC_USD", " ")
|
||||
if err == nil {
|
||||
t.Fatal("error cannot be nil")
|
||||
}
|
||||
require.ErrorIs(t, err, errDelimiterNotFound)
|
||||
|
||||
pair, err := NewPairDelimiter(defaultPairWDelimiter, "-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual := pair.String()
|
||||
expected := defaultPairWDelimiter
|
||||
if actual != expected {
|
||||
t.Errorf(
|
||||
"Pair(): %s was not equal to expected value: %s",
|
||||
actual, expected,
|
||||
)
|
||||
}
|
||||
|
||||
actual = pair.Delimiter
|
||||
expected = "-"
|
||||
if actual != expected {
|
||||
t.Errorf(
|
||||
"Delmiter: %s was not equal to expected value: %s",
|
||||
actual, expected,
|
||||
)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, defaultPairWDelimiter, pair.String())
|
||||
assert.Equal(t, "-", pair.Delimiter)
|
||||
|
||||
pair, err = NewPairDelimiter("BTC-MOVE-0626", "-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual = pair.String()
|
||||
expected = "BTC-MOVE-0626"
|
||||
if actual != expected {
|
||||
t.Errorf(
|
||||
"Pair(): %s was not equal to expected value: %s",
|
||||
actual, expected,
|
||||
)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "BTC-MOVE-0626", pair.String())
|
||||
|
||||
pair, err = NewPairDelimiter("fBTC-USDT", "-")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
actual = pair.String()
|
||||
expected = "fbtc-USDT"
|
||||
if actual != expected {
|
||||
t.Errorf(
|
||||
"Pair(): %s was not equal to expected value: %s",
|
||||
actual, expected,
|
||||
)
|
||||
}
|
||||
pair, err = NewPairDelimiter("sETH-USDT", "-")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "SETH-USDT", pair.String(), "If any upper case is found in set this forces the pair to be uppercase")
|
||||
}
|
||||
|
||||
func TestNewPairFromString(t *testing.T) {
|
||||
@@ -564,89 +527,6 @@ func TestCopyPairFormat(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindPairDifferences(t *testing.T) {
|
||||
pairList, err := NewPairsFromStrings([]string{defaultPairWDelimiter, "ETH-USD", "LTC-USD"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
dash, err := NewPairsFromStrings([]string{"DASH-USD"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Test new pair update
|
||||
diff, err := pairList.FindDifferences(dash, PairFormat{Delimiter: DashDelimiter, Uppercase: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(diff.New) != 1 && len(diff.Remove) != 3 && diff.FormatDifference {
|
||||
t.Error("TestFindPairDifferences: Unexpected values")
|
||||
}
|
||||
|
||||
diff, err = pairList.FindDifferences(Pairs{}, EMPTYFORMAT)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(diff.New) != 0 && len(diff.Remove) != 3 && !diff.FormatDifference {
|
||||
t.Error("TestFindPairDifferences: Unexpected values")
|
||||
}
|
||||
|
||||
diff, err = Pairs{}.FindDifferences(pairList, EMPTYFORMAT)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(diff.New) != 3 && len(diff.Remove) != 0 && diff.FormatDifference {
|
||||
t.Error("TestFindPairDifferences: Unexpected values")
|
||||
}
|
||||
|
||||
// Test that the supplied pair lists are the same, so
|
||||
// no newPairs or removedPairs
|
||||
diff, err = pairList.FindDifferences(pairList, PairFormat{Delimiter: DashDelimiter, Uppercase: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(diff.New) != 0 && len(diff.Remove) != 0 && !diff.FormatDifference {
|
||||
t.Error("TestFindPairDifferences: Unexpected values")
|
||||
}
|
||||
|
||||
_, err = pairList.FindDifferences(Pairs{EMPTYPAIR}, EMPTYFORMAT)
|
||||
if !errors.Is(err, ErrCurrencyPairEmpty) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrCurrencyPairEmpty)
|
||||
}
|
||||
|
||||
_, err = Pairs{EMPTYPAIR}.FindDifferences(pairList, EMPTYFORMAT)
|
||||
if !errors.Is(err, ErrCurrencyPairEmpty) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrCurrencyPairEmpty)
|
||||
}
|
||||
|
||||
// Test duplication
|
||||
duplication, err := NewPairsFromStrings([]string{defaultPairWDelimiter, "ETH-USD", "LTC-USD", "ETH-USD"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = pairList.FindDifferences(duplication, EMPTYFORMAT)
|
||||
if !errors.Is(err, ErrPairDuplication) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, ErrPairDuplication)
|
||||
}
|
||||
|
||||
// This will allow for the removal of the duplicated item to be returned if
|
||||
// contained in the original list.
|
||||
diff, err = duplication.FindDifferences(pairList, EMPTYFORMAT)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Fatalf("received: '%v' but expected: '%v'", err, nil)
|
||||
}
|
||||
|
||||
if len(diff.Remove) != 1 {
|
||||
t.Fatal("expected removal value in pair difference struct")
|
||||
}
|
||||
|
||||
if !diff.Remove[0].Equal(pairList[1]) {
|
||||
t.Fatal("unexpected value returned", diff.Remove[0], pairList[1])
|
||||
}
|
||||
}
|
||||
|
||||
func TestPairsToStringArray(t *testing.T) {
|
||||
var pairs Pairs
|
||||
pairs = append(pairs, NewPair(BTC, USD))
|
||||
@@ -987,29 +867,38 @@ func TestIsAssociated(t *testing.T) {
|
||||
|
||||
func TestPair_GetFormatting(t *testing.T) {
|
||||
t.Parallel()
|
||||
p := NewPair(BTC, USDT)
|
||||
pFmt, err := p.GetFormatting()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !pFmt.Uppercase || pFmt.Delimiter != "" {
|
||||
t.Error("incorrect formatting")
|
||||
}
|
||||
pFmt, err := NewPair(BTC, USDT).GetFormatting()
|
||||
require.NoError(t, err)
|
||||
assert.True(t, pFmt.Uppercase)
|
||||
assert.Empty(t, pFmt.Delimiter)
|
||||
|
||||
p = NewPairWithDelimiter("eth", "usdt", "/")
|
||||
pFmt, err = p.GetFormatting()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if pFmt.Uppercase || pFmt.Delimiter != "/" {
|
||||
t.Error("incorrect formatting")
|
||||
}
|
||||
pFmt, err = NewPairWithDelimiter("eth", "usdt", "/").GetFormatting()
|
||||
require.NoError(t, err)
|
||||
assert.False(t, pFmt.Uppercase)
|
||||
assert.Equal(t, "/", pFmt.Delimiter)
|
||||
|
||||
p = NewPairWithDelimiter("eth", "USDT", "/")
|
||||
_, err = p.GetFormatting()
|
||||
if !errors.Is(err, errPairFormattingInconsistent) {
|
||||
t.Error(err)
|
||||
}
|
||||
_, err = NewPairWithDelimiter("eth", "USDT", "/").GetFormatting()
|
||||
require.ErrorIs(t, err, errPairFormattingInconsistent)
|
||||
|
||||
pFmt, err = EMPTYPAIR.GetFormatting()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, EMPTYFORMAT, pFmt)
|
||||
|
||||
pFmt, err = NewPairWithDelimiter("eth", "420", "/").GetFormatting()
|
||||
require.NoError(t, err)
|
||||
assert.False(t, pFmt.Uppercase)
|
||||
|
||||
pFmt, err = NewPairWithDelimiter("ETH", "420", "/").GetFormatting()
|
||||
require.NoError(t, err)
|
||||
assert.True(t, pFmt.Uppercase)
|
||||
|
||||
pFmt, err = NewPairWithDelimiter("420", "eth", "/").GetFormatting()
|
||||
require.NoError(t, err)
|
||||
assert.False(t, pFmt.Uppercase)
|
||||
|
||||
pFmt, err = NewPairWithDelimiter("420", "ETH", "/").GetFormatting()
|
||||
require.NoError(t, err)
|
||||
assert.True(t, pFmt.Uppercase)
|
||||
}
|
||||
|
||||
func TestNewBTCUSD(t *testing.T) {
|
||||
|
||||
@@ -60,8 +60,8 @@ func (p Pairs) Join() string {
|
||||
func (p Pairs) Format(pairFmt PairFormat) Pairs {
|
||||
pairs := slices.Clone(p)
|
||||
for x := range pairs {
|
||||
pairs[x].Base.UpperCase = pairFmt.Uppercase
|
||||
pairs[x].Quote.UpperCase = pairFmt.Uppercase
|
||||
pairs[x].Base.upperCase = pairFmt.Uppercase
|
||||
pairs[x].Quote.upperCase = pairFmt.Uppercase
|
||||
pairs[x].Delimiter = pairFmt.Delimiter
|
||||
}
|
||||
return pairs
|
||||
@@ -233,52 +233,57 @@ func (p Pairs) GetMatch(pair Pair) (Pair, error) {
|
||||
return EMPTYPAIR, ErrPairNotFound
|
||||
}
|
||||
|
||||
type pairKey struct {
|
||||
Base *Item
|
||||
Quote *Item
|
||||
}
|
||||
|
||||
// FindDifferences returns pairs which are new or have been removed
|
||||
func (p Pairs) FindDifferences(incoming Pairs, pairFmt PairFormat) (PairDifference, error) {
|
||||
newPairs := make(Pairs, 0, len(incoming))
|
||||
check := make(map[string]bool)
|
||||
check := make(map[pairKey]bool)
|
||||
formatDiff := false
|
||||
for x := range incoming {
|
||||
if incoming[x].IsEmpty() {
|
||||
return PairDifference{}, fmt.Errorf("contained in the incoming pairs a %w", ErrCurrencyPairEmpty)
|
||||
}
|
||||
format := EMPTYFORMAT.Format(incoming[x])
|
||||
if check[format] {
|
||||
|
||||
if !formatDiff {
|
||||
formatDiff = incoming[x].hasFormatDifference(pairFmt)
|
||||
}
|
||||
|
||||
k := pairKey{Base: incoming[x].Base.Item, Quote: incoming[x].Quote.Item}
|
||||
if check[k] {
|
||||
return PairDifference{}, fmt.Errorf("contained in the incoming pairs %w", ErrPairDuplication)
|
||||
}
|
||||
check[format] = true
|
||||
check[k] = true
|
||||
if !p.Contains(incoming[x], true) {
|
||||
newPairs = append(newPairs, incoming[x])
|
||||
}
|
||||
}
|
||||
removedPairs := make(Pairs, 0, len(p))
|
||||
check = make(map[string]bool)
|
||||
clear(check)
|
||||
for x := range p {
|
||||
if p[x].IsEmpty() {
|
||||
return PairDifference{}, fmt.Errorf("contained in the existing pairs a %w", ErrCurrencyPairEmpty)
|
||||
}
|
||||
format := EMPTYFORMAT.Format(p[x])
|
||||
if !incoming.Contains(p[x], true) || check[format] {
|
||||
|
||||
if !formatDiff {
|
||||
formatDiff = p[x].hasFormatDifference(pairFmt)
|
||||
}
|
||||
|
||||
k := pairKey{Base: p[x].Base.Item, Quote: p[x].Quote.Item}
|
||||
if !incoming.Contains(p[x], true) || check[k] {
|
||||
removedPairs = append(removedPairs, p[x])
|
||||
}
|
||||
check[format] = true
|
||||
check[k] = true
|
||||
}
|
||||
return PairDifference{
|
||||
New: newPairs,
|
||||
Remove: removedPairs,
|
||||
FormatDifference: p.HasFormatDifference(pairFmt),
|
||||
}, nil
|
||||
return PairDifference{New: newPairs, Remove: removedPairs, FormatDifference: formatDiff}, nil
|
||||
}
|
||||
|
||||
// HasFormatDifference checks and validates full formatting across a pairs list
|
||||
func (p Pairs) HasFormatDifference(pairFmt PairFormat) bool {
|
||||
for x := range p {
|
||||
if p[x].Delimiter != pairFmt.Delimiter ||
|
||||
(!p[x].Base.IsEmpty() && p[x].Base.UpperCase != pairFmt.Uppercase) ||
|
||||
(!p[x].Quote.IsEmpty() && p[x].Quote.UpperCase != pairFmt.Uppercase) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return slices.ContainsFunc(p, func(pair Pair) bool { return pair.hasFormatDifference(pairFmt) })
|
||||
}
|
||||
|
||||
// GetRandomPair returns a random pair from a list of pairs
|
||||
@@ -328,10 +333,10 @@ func (p Pairs) GetCrypto() Currencies {
|
||||
m := make(map[*Item]bool)
|
||||
for x := range p {
|
||||
if p[x].Base.IsCryptocurrency() {
|
||||
m[p[x].Base.Item] = p[x].Base.UpperCase
|
||||
m[p[x].Base.Item] = p[x].Base.upperCase
|
||||
}
|
||||
if p[x].Quote.IsCryptocurrency() {
|
||||
m[p[x].Quote.Item] = p[x].Quote.UpperCase
|
||||
m[p[x].Quote.Item] = p[x].Quote.upperCase
|
||||
}
|
||||
}
|
||||
return currencyConstructor(m)
|
||||
@@ -342,10 +347,10 @@ func (p Pairs) GetFiat() Currencies {
|
||||
m := make(map[*Item]bool)
|
||||
for x := range p {
|
||||
if p[x].Base.IsFiatCurrency() {
|
||||
m[p[x].Base.Item] = p[x].Base.UpperCase
|
||||
m[p[x].Base.Item] = p[x].Base.upperCase
|
||||
}
|
||||
if p[x].Quote.IsFiatCurrency() {
|
||||
m[p[x].Quote.Item] = p[x].Quote.UpperCase
|
||||
m[p[x].Quote.Item] = p[x].Quote.upperCase
|
||||
}
|
||||
}
|
||||
return currencyConstructor(m)
|
||||
@@ -356,8 +361,8 @@ func (p Pairs) GetFiat() Currencies {
|
||||
func (p Pairs) GetCurrencies() Currencies {
|
||||
m := make(map[*Item]bool)
|
||||
for x := range p {
|
||||
m[p[x].Base.Item] = p[x].Base.UpperCase
|
||||
m[p[x].Quote.Item] = p[x].Quote.UpperCase
|
||||
m[p[x].Base.Item] = p[x].Base.upperCase
|
||||
m[p[x].Quote.Item] = p[x].Quote.upperCase
|
||||
}
|
||||
return currencyConstructor(m)
|
||||
}
|
||||
@@ -367,10 +372,10 @@ func (p Pairs) GetStables() Currencies {
|
||||
m := make(map[*Item]bool)
|
||||
for x := range p {
|
||||
if p[x].Base.IsStableCurrency() {
|
||||
m[p[x].Base.Item] = p[x].Base.UpperCase
|
||||
m[p[x].Base.Item] = p[x].Base.upperCase
|
||||
}
|
||||
if p[x].Quote.IsStableCurrency() {
|
||||
m[p[x].Quote.Item] = p[x].Quote.UpperCase
|
||||
m[p[x].Quote.Item] = p[x].Quote.upperCase
|
||||
}
|
||||
}
|
||||
return currencyConstructor(m)
|
||||
@@ -383,7 +388,7 @@ func currencyConstructor(m map[*Item]bool) Currencies {
|
||||
var target int
|
||||
for code, upper := range m {
|
||||
cryptos[target].Item = code
|
||||
cryptos[target].UpperCase = upper
|
||||
cryptos[target].upperCase = upper
|
||||
target++
|
||||
}
|
||||
return cryptos
|
||||
|
||||
@@ -715,35 +715,40 @@ func TestValidateAndConform(t *testing.T) {
|
||||
|
||||
func TestPairs_GetFormatting(t *testing.T) {
|
||||
t.Parallel()
|
||||
p := Pairs{NewPair(BTC, USDT)}
|
||||
pFmt, err := p.GetFormatting()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !pFmt.Uppercase || pFmt.Delimiter != "" {
|
||||
t.Error("incorrect formatting")
|
||||
}
|
||||
pFmt, err := Pairs{NewPair(BTC, USDT)}.GetFormatting()
|
||||
require.NoError(t, err)
|
||||
assert.True(t, pFmt.Uppercase)
|
||||
assert.Empty(t, pFmt.Delimiter)
|
||||
|
||||
p = Pairs{NewPairWithDelimiter("eth", "usdt", "/")}
|
||||
pFmt, err = p.GetFormatting()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if pFmt.Uppercase || pFmt.Delimiter != "/" {
|
||||
t.Error("incorrect formatting")
|
||||
}
|
||||
pFmt, err = Pairs{NewPairWithDelimiter("eth", "usdt", "/")}.GetFormatting()
|
||||
require.NoError(t, err)
|
||||
assert.False(t, pFmt.Uppercase)
|
||||
assert.Equal(t, "/", pFmt.Delimiter)
|
||||
|
||||
p = Pairs{NewPair(BTC, USDT), NewPairWithDelimiter("eth", "usdt", "/")}
|
||||
_, err = p.GetFormatting()
|
||||
if !errors.Is(err, errPairFormattingInconsistent) {
|
||||
t.Error(err)
|
||||
}
|
||||
_, err = Pairs{NewPair(BTC, USDT), NewPairWithDelimiter("eth", "usdt", "/")}.GetFormatting()
|
||||
require.ErrorIs(t, err, errPairFormattingInconsistent)
|
||||
|
||||
p = Pairs{NewPairWithDelimiter("eth", "USDT", "/")}
|
||||
_, err = p.GetFormatting()
|
||||
if !errors.Is(err, errPairFormattingInconsistent) {
|
||||
t.Error(err)
|
||||
}
|
||||
_, err = Pairs{NewPairWithDelimiter("eth", "USDT", "/")}.GetFormatting()
|
||||
require.ErrorIs(t, err, errPairFormattingInconsistent)
|
||||
|
||||
_, err = Pairs{NewPairWithDelimiter("eth", "usdt", "/"), NewPairWithDelimiter("eth", "usdt", "/")}.GetFormatting()
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = Pairs{NewPairWithDelimiter("eth", "usdt", "/"), NewPairWithDelimiter("eth", "usdt", "|")}.GetFormatting()
|
||||
require.ErrorIs(t, err, errPairFormattingInconsistent)
|
||||
|
||||
_, err = Pairs{NewPairWithDelimiter("eth", "420", "/"), NewPairWithDelimiter("eth", "420", "/")}.GetFormatting()
|
||||
require.NoError(t, err)
|
||||
_, err = Pairs{NewPairWithDelimiter("ETH", "420", "/"), NewPairWithDelimiter("ETH", "420", "/")}.GetFormatting()
|
||||
require.NoError(t, err)
|
||||
_, err = Pairs{NewPairWithDelimiter("420", "ETH", "/"), NewPairWithDelimiter("420", "ETH", "/")}.GetFormatting()
|
||||
require.NoError(t, err)
|
||||
_, err = Pairs{NewPairWithDelimiter("420", "eth", "/"), NewPairWithDelimiter("420", "eth", "/")}.GetFormatting()
|
||||
require.NoError(t, err)
|
||||
_, err = Pairs{NewPairWithDelimiter("420", "eth", "/"), NewPairWithDelimiter("eth", "420", "/")}.GetFormatting()
|
||||
require.NoError(t, err)
|
||||
_, err = Pairs{NewPairWithDelimiter("420", "ETH", "/"), NewPairWithDelimiter("ETH", "420", "/")}.GetFormatting()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestGetPairsByQuote(t *testing.T) {
|
||||
@@ -836,3 +841,83 @@ func TestPairsEqual(t *testing.T) {
|
||||
assert.Equal(t, "USDT-BTC", orig[0].String(), "Equal Pairs should not effect original order or format")
|
||||
assert.False(t, orig.Equal(Pairs{NewPair(DAI, XRP), NewPair(DAI, BTC), NewPair(USD, LTC)}), "UnEqual Pairs should return false")
|
||||
}
|
||||
|
||||
func TestFindPairDifferences(t *testing.T) {
|
||||
pairList, err := NewPairsFromStrings([]string{defaultPairWDelimiter, "ETH-USD", "LTC-USD"})
|
||||
require.NoError(t, err)
|
||||
|
||||
dash, err := NewPairsFromStrings([]string{"DASH-USD"})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test new pair update
|
||||
diff, err := pairList.FindDifferences(dash, PairFormat{Delimiter: DashDelimiter, Uppercase: true})
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, diff.New, 1)
|
||||
assert.Len(t, diff.Remove, 3)
|
||||
|
||||
diff, err = pairList.FindDifferences(Pairs{}, EMPTYFORMAT)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, diff.New)
|
||||
assert.Len(t, diff.Remove, 3)
|
||||
assert.True(t, diff.FormatDifference)
|
||||
|
||||
diff, err = Pairs{}.FindDifferences(pairList, EMPTYFORMAT)
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, diff.New, 3)
|
||||
assert.Empty(t, diff.Remove)
|
||||
assert.True(t, diff.FormatDifference)
|
||||
|
||||
// Test that the supplied pair lists are the same, so
|
||||
// no newPairs or removedPairs
|
||||
diff, err = pairList.FindDifferences(pairList, PairFormat{Delimiter: DashDelimiter, Uppercase: true})
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, diff.New)
|
||||
assert.Empty(t, diff.Remove)
|
||||
assert.False(t, diff.FormatDifference)
|
||||
|
||||
_, err = pairList.FindDifferences(Pairs{EMPTYPAIR}, EMPTYFORMAT)
|
||||
require.ErrorIs(t, err, ErrCurrencyPairEmpty)
|
||||
|
||||
_, err = Pairs{EMPTYPAIR}.FindDifferences(pairList, EMPTYFORMAT)
|
||||
require.ErrorIs(t, err, ErrCurrencyPairEmpty)
|
||||
|
||||
// Test duplication
|
||||
duplication, err := NewPairsFromStrings([]string{defaultPairWDelimiter, "ETH-USD", "LTC-USD", "ETH-USD"})
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = pairList.FindDifferences(duplication, EMPTYFORMAT)
|
||||
require.ErrorIs(t, err, ErrPairDuplication)
|
||||
|
||||
// This will allow for the removal of the duplicated item to be returned if
|
||||
// contained in the original list.
|
||||
diff, err = duplication.FindDifferences(pairList, EMPTYFORMAT)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, diff.Remove, 1)
|
||||
require.True(t, diff.Remove[0].Equal(pairList[1]))
|
||||
|
||||
original, err := NewPairsFromStrings([]string{"ETH-USD", "LTC-USD", "ETH-USD"})
|
||||
require.NoError(t, err)
|
||||
|
||||
compare, err := NewPairsFromStrings([]string{"ETH-123", "LTC-123", "MEOW-123"})
|
||||
require.NoError(t, err)
|
||||
|
||||
diff, err = original.FindDifferences(compare, PairFormat{Delimiter: DashDelimiter, Uppercase: true})
|
||||
require.NoError(t, err)
|
||||
require.False(t, diff.FormatDifference)
|
||||
}
|
||||
|
||||
// 2208139 509.3 ns/op 288 B/op 2 allocs/op (current)
|
||||
//
|
||||
// 1614865 712.5 ns/op 336 B/op 8 allocs/op (prev)
|
||||
func BenchmarkFindDifferences(b *testing.B) {
|
||||
original, err := NewPairsFromStrings([]string{"ETH-USD", "LTC-USD", "ETH-USD"})
|
||||
require.NoError(b, err)
|
||||
|
||||
compare, err := NewPairsFromStrings([]string{"ETH-123", "LTC-123", "MEOW-123"})
|
||||
require.NoError(b, err)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err = original.FindDifferences(compare, EMPTYFORMAT)
|
||||
require.NoError(b, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@ func (s *Storage) SetDefaults() {
|
||||
if item == USDT.Item {
|
||||
continue
|
||||
}
|
||||
fiatCurrencies = append(fiatCurrencies, Code{Item: item, UpperCase: true})
|
||||
fiatCurrencies = append(fiatCurrencies, Code{Item: item, upperCase: true})
|
||||
}
|
||||
|
||||
err := s.SetDefaultFiatCurrencies(fiatCurrencies)
|
||||
|
||||
@@ -112,7 +112,7 @@ func GetHoldings(exch string, creds *Credentials, assetType asset.Item) (Holding
|
||||
}
|
||||
assetHoldings.m.Lock()
|
||||
currencyBalances = append(currencyBalances, Balance{
|
||||
Currency: currency.Code{Item: mapKey.Currency, UpperCase: true},
|
||||
Currency: mapKey.Currency.Currency().Upper(),
|
||||
Total: assetHoldings.total,
|
||||
Hold: assetHoldings.hold,
|
||||
Free: assetHoldings.free,
|
||||
|
||||
@@ -1177,9 +1177,14 @@ func (b *Bitfinex) GetHistoricCandlesExtended(ctx context.Context, pair currency
|
||||
}
|
||||
|
||||
func (b *Bitfinex) fixCasing(in currency.Pair, a asset.Item) (string, error) {
|
||||
if in.IsEmpty() || in.Base.IsEmpty() {
|
||||
if in.Base.IsEmpty() {
|
||||
return "", currency.ErrCurrencyPairEmpty
|
||||
}
|
||||
|
||||
// Convert input to lowercase to ensure consistent formatting.
|
||||
// Required for currencies that start with T or F eg tTNBUSD
|
||||
in = in.Lower()
|
||||
|
||||
var checkString [2]byte
|
||||
if a == asset.Spot || a == asset.Margin {
|
||||
checkString[0] = 't'
|
||||
|
||||
@@ -96,10 +96,10 @@ var (
|
||||
Value: "error",
|
||||
}
|
||||
currencyPair = &objects.String{
|
||||
Value: "BTCUSD",
|
||||
Value: "BTC-USD",
|
||||
}
|
||||
delimiter = &objects.String{
|
||||
Value: "",
|
||||
Value: "-",
|
||||
}
|
||||
assetType = &objects.String{
|
||||
Value: "spot",
|
||||
|
||||
Reference in New Issue
Block a user