Config: Refactor version packages (#1887)

* Config: Move config versions to separate pacakges

* Config: Move version tests to blackbox texts

* Config: Protect registerVersion from overflow

* Config: Protect against version already registered
This commit is contained in:
Gareth Kirwan
2025-04-22 04:13:01 +02:00
committed by GitHub
parent 545fa9d01a
commit 1bf3433d61
19 changed files with 254 additions and 153 deletions

View File

@@ -0,0 +1,21 @@
package versions
import (
v0 "github.com/thrasher-corp/gocryptotrader/config/versions/v0"
v1 "github.com/thrasher-corp/gocryptotrader/config/versions/v1"
v2 "github.com/thrasher-corp/gocryptotrader/config/versions/v2"
v3 "github.com/thrasher-corp/gocryptotrader/config/versions/v3"
v4 "github.com/thrasher-corp/gocryptotrader/config/versions/v4"
v5 "github.com/thrasher-corp/gocryptotrader/config/versions/v5"
v6 "github.com/thrasher-corp/gocryptotrader/config/versions/v6"
)
func init() {
Manager.registerVersion(0, &v0.Version{})
Manager.registerVersion(1, &v1.Version{})
Manager.registerVersion(2, &v2.Version{})
Manager.registerVersion(3, &v3.Version{})
Manager.registerVersion(4, &v4.Version{})
Manager.registerVersion(5, &v5.Version{})
Manager.registerVersion(6, &v6.Version{})
}

View File

@@ -0,0 +1,49 @@
package versions_test
import (
"io/fs"
"os"
"path/filepath"
"reflect"
"regexp"
"strconv"
"testing"
"github.com/stretchr/testify/require"
"github.com/thrasher-corp/gocryptotrader/config/versions"
testutils "github.com/thrasher-corp/gocryptotrader/internal/testing/utils"
)
func TestVersionsRegistered(t *testing.T) {
t.Parallel()
r, err := testutils.RootPathFromCWD()
require.NoError(t, err)
versionsDir := filepath.Join(r, "config", "versions")
_, err = os.Stat(versionsDir)
require.NoError(t, err, "config/versions must exist")
err = filepath.WalkDir(versionsDir, func(p string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if !d.IsDir() || p == versionsDir {
return nil
}
verStr := filepath.Base(p)
verMatch := regexp.MustCompile(`v(\d+)`).FindStringSubmatch(verStr)
if len(verMatch) != 2 {
return filepath.SkipDir
}
t.Run(verStr, func(t *testing.T) {
version, err := strconv.ParseUint(verMatch[1], 10, 16)
require.NoError(t, err, "verMatch must ParseUint without error")
v := versions.Manager.Version(uint16(version))
require.NotNil(t, v, "version.Manager init must register this version")
require.Contains(t, reflect.TypeOf(v).String(), "*"+verStr+".Version", "version registered must be the correct type")
})
return filepath.SkipDir
})
require.NoError(t, err, "WalkDir must not error")
}

View File

@@ -1,23 +0,0 @@
package versions
import (
"context"
)
// Version0 is a baseline version with no changes, so we can downgrade back to nothing
// It does not implement any upgrade interfaces
type Version0 struct{}
func init() {
Manager.registerVersion(0, &Version0{})
}
// UpgradeConfig is an empty stub
func (v *Version0) UpgradeConfig(_ context.Context, j []byte) ([]byte, error) {
return j, nil
}
// DowngradeConfig is an empty stub
func (v *Version0) DowngradeConfig(_ context.Context, j []byte) ([]byte, error) {
return j, nil
}

19
config/versions/v0/v0.go Normal file
View File

@@ -0,0 +1,19 @@
package v0
import (
"context"
)
// Version is a baseline version with no changes, so we can downgrade back to nothing
// It does not implement any upgrade interfaces
type Version struct{}
// UpgradeConfig is an empty stub
func (*Version) UpgradeConfig(_ context.Context, j []byte) ([]byte, error) {
return j, nil
}
// DowngradeConfig is an empty stub
func (*Version) DowngradeConfig(_ context.Context, j []byte) ([]byte, error) {
return j, nil
}

View File

@@ -0,0 +1,26 @@
package v0_test
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
v0 "github.com/thrasher-corp/gocryptotrader/config/versions/v0"
)
func TestUpgradeConfig(t *testing.T) {
t.Parallel()
in := []byte(`{"untouched":true}`)
out, err := new(v0.Version).UpgradeConfig(context.Background(), in)
require.NoError(t, err)
assert.Equal(t, in, out)
}
func TestDowngradeConfig(t *testing.T) {
t.Parallel()
in := []byte(`{"untouched":true}`)
out, err := new(v0.Version).DowngradeConfig(context.Background(), in)
require.NoError(t, err)
assert.Equal(t, in, out)
}

View File

@@ -1,26 +1,21 @@
package versions
package v1
import (
"context"
"github.com/buger/jsonparser"
v0 "github.com/thrasher-corp/gocryptotrader/config/versions/v0"
v1 "github.com/thrasher-corp/gocryptotrader/config/versions/v1"
"github.com/thrasher-corp/gocryptotrader/encoding/json"
)
// Version1 is an ExchangeVersion to upgrade currency pair format for exchanges
type Version1 struct{}
func init() {
Manager.registerVersion(1, &Version1{})
}
// Version is an ExchangeVersion to upgrade currency pair format for exchanges
type Version struct{}
// Exchanges returns all exchanges: "*"
func (v *Version1) Exchanges() []string { return []string{"*"} }
func (*Version) Exchanges() []string { return []string{"*"} }
// UpgradeExchange will upgrade currency pair format
func (v *Version1) UpgradeExchange(_ context.Context, e []byte) ([]byte, error) {
func (*Version) UpgradeExchange(_ context.Context, e []byte) ([]byte, error) {
if _, d, _, err := jsonparser.Get(e, "currencyPairs"); err == nil && d == jsonparser.Object {
return e, nil
}
@@ -30,12 +25,12 @@ func (v *Version1) UpgradeExchange(_ context.Context, e []byte) ([]byte, error)
return e, err
}
p := &v1.PairsManager{
p := &PairsManager{
UseGlobalFormat: true,
LastUpdated: d.PairsLastUpdated,
ConfigFormat: d.ConfigCurrencyPairFormat,
RequestFormat: d.RequestCurrencyPairFormat,
Pairs: v1.FullStore{
Pairs: FullStore{
"spot": {
Available: d.AvailablePairs,
Enabled: d.EnabledPairs,
@@ -53,6 +48,6 @@ func (v *Version1) UpgradeExchange(_ context.Context, e []byte) ([]byte, error)
}
// DowngradeExchange doesn't do anything for v1; There's no downgrade path since the original state is lossy and v1 was before versioning
func (v *Version1) DowngradeExchange(_ context.Context, e []byte) ([]byte, error) {
func (*Version) DowngradeExchange(_ context.Context, e []byte) ([]byte, error) {
return e, nil
}

View File

@@ -1,4 +1,4 @@
package versions
package v1_test
import (
"bytes"
@@ -7,12 +7,18 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
v1 "github.com/thrasher-corp/gocryptotrader/config/versions/v1"
)
func TestVersion1Upgrade(t *testing.T) {
func TestExchanges(t *testing.T) {
t.Parallel()
assert.Equal(t, []string{"*"}, new(v1.Version).Exchanges())
}
func TestUpgradeExchange(t *testing.T) {
t.Parallel()
v := &Version1{}
v := &v1.Version{}
in := []byte(`{"name":"Wibble","pairsLastUpdated":1566798411,"assetTypes":"spot","configCurrencyPairFormat":{"uppercase":true,"delimiter":"_"},"requestCurrencyPairFormat":{"uppercase":false,"delimiter":"_","separator":"-"},"enabledPairs":"LTC_BTC","availablePairs":"LTC_BTC,ETH_BTC,BTC_USD"}`)
exp := []byte(`{"name":"Wibble","currencyPairs":{"bypassConfigFormatUpgrades":false,"requestFormat":{"uppercase":false,"delimiter":"_","separator":"-"},"configFormat":{"uppercase":true,"delimiter":"_"},"useGlobalFormat":true,"lastUpdated":1566798411,"pairs":{"spot":{"enabled":"LTC_BTC","available":"LTC_BTC,ETH_BTC,BTC_USD"}}}}`)
@@ -22,10 +28,10 @@ func TestVersion1Upgrade(t *testing.T) {
assert.Equal(t, string(exp), string(out))
}
func TestVersion1Downgrade(t *testing.T) {
func TestDowngradeExchange(t *testing.T) {
t.Parallel()
in := []byte("just leave me alone, mkay?")
out, err := new(Version1).DowngradeExchange(context.Background(), bytes.Clone(in))
out, err := new(v1.Version).DowngradeExchange(context.Background(), bytes.Clone(in))
require.NoError(t, err)
assert.Equal(t, out, in)
}

View File

@@ -1,4 +1,4 @@
package versions
package v2
import (
"context"
@@ -6,18 +6,14 @@ import (
"github.com/buger/jsonparser"
)
// Version2 is an ExchangeVersion to change the name of GDAX to CoinbasePro
type Version2 struct{}
func init() {
Manager.registerVersion(2, &Version2{})
}
// Version is an ExchangeVersion to change the name of GDAX to CoinbasePro
type Version struct{}
// Exchanges returns just GDAX and CoinbasePro
func (v *Version2) Exchanges() []string { return []string{"GDAX", "CoinbasePro"} }
func (*Version) Exchanges() []string { return []string{"GDAX", "CoinbasePro"} }
// UpgradeExchange will change the exchange name from GDAX to CoinbasePro
func (v *Version2) UpgradeExchange(_ context.Context, e []byte) ([]byte, error) {
func (*Version) UpgradeExchange(_ context.Context, e []byte) ([]byte, error) {
if n, err := jsonparser.GetString(e, "name"); err == nil && n == "GDAX" {
return jsonparser.Set(e, []byte(`"CoinbasePro"`), "name")
}
@@ -25,7 +21,7 @@ func (v *Version2) UpgradeExchange(_ context.Context, e []byte) ([]byte, error)
}
// DowngradeExchange will change the exchange name from CoinbasePro to GDAX
func (v *Version2) DowngradeExchange(_ context.Context, e []byte) ([]byte, error) {
func (*Version) DowngradeExchange(_ context.Context, e []byte) ([]byte, error) {
if n, err := jsonparser.GetString(e, "name"); err == nil && n == "CoinbasePro" {
return jsonparser.Set(e, []byte(`"GDAX"`), "name")
}

View File

@@ -1,4 +1,4 @@
package versions
package v2_test
import (
"context"
@@ -6,30 +6,31 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
v2 "github.com/thrasher-corp/gocryptotrader/config/versions/v2"
)
func TestVersion2Upgrade(t *testing.T) {
func TestUpgradeExchange(t *testing.T) {
t.Parallel()
for _, tt := range [][]string{
{"GDAX", "CoinbasePro"},
{"Kraken", "Kraken"},
{"CoinbasePro", "CoinbasePro"},
} {
out, err := new(Version2).UpgradeExchange(context.Background(), []byte(`{"name":"`+tt[0]+`"}`))
out, err := new(v2.Version).UpgradeExchange(context.Background(), []byte(`{"name":"`+tt[0]+`"}`))
require.NoError(t, err)
require.NotEmpty(t, out)
assert.Equalf(t, `{"name":"`+tt[1]+`"}`, string(out), "Test exchange name %s", tt[0])
}
}
func TestVersion2Downgrade(t *testing.T) {
func TestDowngradeExchange(t *testing.T) {
t.Parallel()
for _, tt := range [][]string{
{"GDAX", "GDAX"},
{"Kraken", "Kraken"},
{"CoinbasePro", "GDAX"},
} {
out, err := new(Version2).DowngradeExchange(context.Background(), []byte(`{"name":"`+tt[0]+`"}`))
out, err := new(v2.Version).DowngradeExchange(context.Background(), []byte(`{"name":"`+tt[0]+`"}`))
require.NoError(t, err)
require.NotEmpty(t, out)
assert.Equalf(t, `{"name":"`+tt[1]+`"}`, string(out), "Test exchange name %s", tt[0])

View File

@@ -1,4 +1,4 @@
package versions
package v3
import (
"context"
@@ -8,18 +8,14 @@ import (
"github.com/thrasher-corp/gocryptotrader/encoding/json"
)
// Version3 is an ExchangeVersion to remove the publishPeriod from the exchange's orderbook config
type Version3 struct{}
func init() {
Manager.registerVersion(3, &Version3{})
}
// Version is an ExchangeVersion to remove the publishPeriod from the exchange's orderbook config
type Version struct{}
// Exchanges returns all exchanges: "*"
func (v *Version3) Exchanges() []string { return []string{"*"} }
func (*Version) Exchanges() []string { return []string{"*"} }
// UpgradeExchange will remove the publishPeriod from the exchange's orderbook config
func (v *Version3) UpgradeExchange(_ context.Context, e []byte) ([]byte, error) {
func (*Version) UpgradeExchange(_ context.Context, e []byte) ([]byte, error) {
e = jsonparser.Delete(e, "orderbook", "publishPeriod")
return e, nil
}
@@ -27,7 +23,7 @@ func (v *Version3) UpgradeExchange(_ context.Context, e []byte) ([]byte, error)
const defaultOrderbookPublishPeriod = time.Second * 10
// DowngradeExchange will downgrade the exchange's config by setting the default orderbook publish period
func (v *Version3) DowngradeExchange(_ context.Context, e []byte) ([]byte, error) {
func (*Version) DowngradeExchange(_ context.Context, e []byte) ([]byte, error) {
if _, _, _, err := jsonparser.Get(e, "orderbook"); err != nil {
return e, nil //nolint:nilerr // No error, just return the original config
}

View File

@@ -1,42 +1,43 @@
package versions
package v3_test
import (
"context"
"testing"
"github.com/stretchr/testify/require"
v3 "github.com/thrasher-corp/gocryptotrader/config/versions/v3"
)
func TestVersion3UpgradeExchange(t *testing.T) {
func TestUpgradeExchange(t *testing.T) {
t.Parallel()
got, err := (&Version3{}).UpgradeExchange(context.Background(), nil)
got, err := (&v3.Version{}).UpgradeExchange(context.Background(), nil)
require.NoError(t, err)
require.Nil(t, got)
payload := []byte(`{"orderbook": {"verificationBypass": false,"websocketBufferLimit": 5,"websocketBufferEnabled": false,"publishPeriod": 10000000000}}`)
expected := []byte(`{"orderbook": {"verificationBypass": false,"websocketBufferLimit": 5,"websocketBufferEnabled": false}}`)
got, err = (&Version3{}).UpgradeExchange(context.Background(), payload)
got, err = (&v3.Version{}).UpgradeExchange(context.Background(), payload)
require.NoError(t, err)
require.Equal(t, expected, got)
}
func TestVersion3DowngradeExchange(t *testing.T) {
func TestDowngradeExchange(t *testing.T) {
t.Parallel()
got, err := (&Version3{}).DowngradeExchange(context.Background(), nil)
got, err := (&v3.Version{}).DowngradeExchange(context.Background(), nil)
require.NoError(t, err)
require.Nil(t, got)
payload := []byte(`{"orderbook": {"verificationBypass": false,"websocketBufferLimit": 5,"websocketBufferEnabled": false}}`)
expected := []byte(`{"orderbook": {"verificationBypass": false,"websocketBufferLimit": 5,"websocketBufferEnabled": false,"publishPeriod":10000000000}}`)
got, err = (&Version3{}).DowngradeExchange(context.Background(), payload)
got, err = (&v3.Version{}).DowngradeExchange(context.Background(), payload)
require.NoError(t, err)
require.Equal(t, expected, got)
}
func TestVersion3Exchanges(t *testing.T) {
func TestExchanges(t *testing.T) {
t.Parallel()
assert := require.New(t)
assert.Equal([]string{"*"}, (&Version3{}).Exchanges())
assert.Equal([]string{"*"}, (&v3.Version{}).Exchanges())
}

View File

@@ -1,4 +1,4 @@
package versions
package v4
import (
"bytes"
@@ -10,18 +10,14 @@ import (
"github.com/buger/jsonparser"
)
// Version4 is an Exchange upgrade to move currencyPairs.assetTypes to currencyPairs.pairs.*.assetEnabled
type Version4 struct{}
func init() {
Manager.registerVersion(4, &Version4{})
}
// Version is an Exchange upgrade to move currencyPairs.assetTypes to currencyPairs.pairs.*.assetEnabled
type Version struct{}
// Exchanges returns all exchanges: "*"
func (v *Version4) Exchanges() []string { return []string{"*"} }
func (*Version) Exchanges() []string { return []string{"*"} }
// UpgradeExchange sets AssetEnabled: true for all assets listed in assetTypes, and false for any with no field
func (v *Version4) UpgradeExchange(_ context.Context, e []byte) ([]byte, error) {
func (*Version) UpgradeExchange(_ context.Context, e []byte) ([]byte, error) {
toEnable := map[string]bool{}
assetTypesFn := func(asset []byte, valueType jsonparser.ValueType, _ int, _ error) {
@@ -31,7 +27,7 @@ func (v *Version4) UpgradeExchange(_ context.Context, e []byte) ([]byte, error)
}
_, err := jsonparser.ArrayEach(e, assetTypesFn, "currencyPairs", "assetTypes")
if err != nil && !errors.Is(err, jsonparser.KeyPathNotFoundError) {
return e, fmt.Errorf("%w assetTypes: %w", errUpgrading, err)
return e, fmt.Errorf("error upgrading assetTypes: %w", err)
}
assetEnabledFn := func(assetBytes, v []byte, _ jsonparser.ValueType, _ int) (err error) {
@@ -54,14 +50,14 @@ func (v *Version4) UpgradeExchange(_ context.Context, e []byte) ([]byte, error)
return err
}
if err = jsonparser.ObjectEach(bytes.Clone(e), assetEnabledFn, "currencyPairs", "pairs"); err != nil {
return e, fmt.Errorf("%w currencyPairs.pairs: %w", errUpgrading, err)
return e, fmt.Errorf("error upgrading currencyPairs.pairs: %w", err)
}
e = jsonparser.Delete(e, "currencyPairs", "assetTypes")
return e, err
}
// DowngradeExchange moves AssetEnabled assets into AssetType field
func (v *Version4) DowngradeExchange(_ context.Context, e []byte) ([]byte, error) {
func (*Version) DowngradeExchange(_ context.Context, e []byte) ([]byte, error) {
assetTypes := []string{}
assetEnabledFn := func(asset, v []byte, _ jsonparser.ValueType, _ int) error {

View File

@@ -1,4 +1,4 @@
package versions
package v4_test
import (
"bytes"
@@ -8,31 +8,25 @@ import (
"github.com/buger/jsonparser"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
v4 "github.com/thrasher-corp/gocryptotrader/config/versions/v4"
)
func TestVersion4ExchangeType(t *testing.T) {
func TestExchanges(t *testing.T) {
t.Parallel()
assert.Implements(t, (*ExchangeVersion)(nil), new(Version4))
assert.Equal(t, []string{"*"}, new(v4.Version).Exchanges())
}
func TestVersion4Exchanges(t *testing.T) {
t.Parallel()
assert.Equal(t, []string{"*"}, new(Version4).Exchanges())
}
func TestVersion4Upgrade(t *testing.T) {
func TestUpgradeExchange(t *testing.T) {
t.Parallel()
_, err := new(Version4).UpgradeExchange(context.Background(), []byte{})
require.ErrorIs(t, err, errUpgrading)
require.ErrorContains(t, err, `assetTypes`)
_, err := new(v4.Version).UpgradeExchange(context.Background(), []byte{})
require.ErrorContains(t, err, `error upgrading assetTypes`)
_, err = new(Version4).UpgradeExchange(context.Background(), []byte(`{}`))
require.ErrorIs(t, err, errUpgrading)
require.ErrorContains(t, err, `currencyPairs.pairs`)
_, err = new(v4.Version).UpgradeExchange(context.Background(), []byte(`{}`))
require.ErrorContains(t, err, `error upgrading currencyPairs.pairs`)
in := []byte(`{"name":"Cracken","currencyPairs":{"assetTypes":["spot"],"pairs":{"spot":{"enabled":"BTC-AUD","available":"BTC-AUD"},"futures":{"assetEnabled":true},"options":{},"margin":{"assetEnabled":null}}}}`)
out, err := new(Version4).UpgradeExchange(context.Background(), in)
out, err := new(v4.Version).UpgradeExchange(context.Background(), in)
require.NoError(t, err)
require.NotEmpty(t, out)
@@ -55,26 +49,26 @@ func TestVersion4Upgrade(t *testing.T) {
require.NoError(t, err, "Must find assetEnabled for margin")
assert.False(t, e, "assetEnabled should be set to false")
out2, err := new(Version4).UpgradeExchange(context.Background(), out)
out2, err := new(v4.Version).UpgradeExchange(context.Background(), out)
require.NoError(t, err, "Must not error on re-upgrading")
assert.Equal(t, out, out2, "Should not affect an already upgraded config")
in = []byte(`{"name":"Cracken","currencyPairs":{"assetTypes":["spot"],"pairs":{"spot":{"assetEnabled":{}}}}}`)
_, err = new(Version4).UpgradeExchange(context.Background(), in)
_, err = new(v4.Version).UpgradeExchange(context.Background(), in)
require.NoError(t, err)
in = []byte(`{"name":"Cracken","currencyPairs":{"assetTypes":["spot"],"pairs":{"margin":{"assetEnabled":{}}}}}`)
_, err = new(Version4).UpgradeExchange(context.Background(), in)
_, err = new(v4.Version).UpgradeExchange(context.Background(), in)
require.ErrorIs(t, err, jsonparser.UnknownValueTypeError)
require.ErrorContains(t, err, "`margin`")
require.ErrorContains(t, err, "`object`")
}
func TestVersion4Downgrade(t *testing.T) {
func TestDowngradeExchange(t *testing.T) {
t.Parallel()
in := []byte(`{"name":"Cracken","currencyPairs":{"pairs":{"spot":{"enabled":"BTC-AUD","available":"BTC-AUD","assetEnabled":true},"futures":{"assetEnabled":false},"options":{},"options_combo":{"assetEnabled":true}}}}`)
out, err := new(Version4).DowngradeExchange(context.Background(), in)
out, err := new(v4.Version).DowngradeExchange(context.Background(), in)
require.NoError(t, err)
require.NotEmpty(t, out)

View File

@@ -1,4 +1,4 @@
package versions
package v5
import (
"context"
@@ -6,25 +6,20 @@ import (
"strconv"
"github.com/buger/jsonparser"
v5 "github.com/thrasher-corp/gocryptotrader/config/versions/v5"
)
// Version5 implements ConfigVersion
type Version5 struct{}
func init() {
Manager.registerVersion(5, &Version5{})
}
// Version implements ConfigVersion
type Version struct{}
// UpgradeConfig handles upgrading config for OrderManager:
// * Sets OrderManager config to defaults if it doesn't exist or enabled is null
// * Sets respectOrderHistoryLimits to true if it doesn't exist or is null
// * Sets futuresTrackingSeekDuration to positive if it's negative
func (v *Version5) UpgradeConfig(_ context.Context, e []byte) ([]byte, error) {
func (*Version) UpgradeConfig(_ context.Context, e []byte) ([]byte, error) {
_, valueType, _, err := jsonparser.Get(e, "orderManager", "enabled")
switch {
case errors.Is(err, jsonparser.KeyPathNotFoundError), valueType == jsonparser.Null:
return jsonparser.Set(e, v5.DefaultOrderbookConfig, "orderManager")
return jsonparser.Set(e, DefaultOrderbookConfig, "orderManager")
case err != nil:
return e, err
}
@@ -37,7 +32,7 @@ func (v *Version5) UpgradeConfig(_ context.Context, e []byte) ([]byte, error) {
}
if i, err := jsonparser.GetInt(e, "orderManager", "futuresTrackingSeekDuration"); err != nil {
if e, err = jsonparser.Set(e, []byte(v5.DefaultFuturesTrackingSeekDuration), "orderManager", "futuresTrackingSeekDuration"); err != nil {
if e, err = jsonparser.Set(e, []byte(DefaultFuturesTrackingSeekDuration), "orderManager", "futuresTrackingSeekDuration"); err != nil {
return e, err
}
} else if i < 0 {
@@ -49,7 +44,7 @@ func (v *Version5) UpgradeConfig(_ context.Context, e []byte) ([]byte, error) {
}
// DowngradeConfig just reverses the futuresTrackingSeekDuration to negative, and leaves everything else alone
func (v *Version5) DowngradeConfig(_ context.Context, e []byte) ([]byte, error) {
func (*Version) DowngradeConfig(_ context.Context, e []byte) ([]byte, error) {
if i, err := jsonparser.GetInt(e, "orderManager", "futuresTrackingSeekDuration"); err == nil && i > 0 {
if e, err = jsonparser.Set(e, []byte(strconv.FormatInt(-i, 10)), "orderManager", "futuresTrackingSeekDuration"); err != nil {
return e, err

View File

@@ -1,4 +1,4 @@
package versions
package v5_test
import (
"bytes"
@@ -10,9 +10,10 @@ import (
"github.com/buger/jsonparser"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
v5 "github.com/thrasher-corp/gocryptotrader/config/versions/v5"
)
func TestVersion5Upgrade(t *testing.T) {
func TestUpgradeConfig(t *testing.T) {
t.Parallel()
expDef := `{"orderManager":{"enabled":true,"verbose":false,"activelyTrackFuturesPositions":true,"futuresTrackingSeekDuration":31536000000000000,"cancelOrdersOnShutdown":false,"respectOrderHistoryLimits":true}}`
@@ -37,7 +38,7 @@ func TestVersion5Upgrade(t *testing.T) {
for _, tt := range tests {
_ = t.Run(tt.name, func(t *testing.T) {
t.Parallel()
out, err := new(Version5).UpgradeConfig(context.Background(), []byte(tt.in))
out, err := new(v5.Version).UpgradeConfig(context.Background(), []byte(tt.in))
if tt.err != nil {
require.ErrorIs(t, err, tt.err)
return
@@ -50,16 +51,16 @@ func TestVersion5Upgrade(t *testing.T) {
}
}
func TestVersion5Downgrade(t *testing.T) {
func TestDowngradeConfig(t *testing.T) {
t.Parallel()
in := `{"orderManager":{"enabled":false,"verbose":true,"activelyTrackFuturesPositions":false,"futuresTrackingSeekDuration":-47000,"cancelOrdersOnShutdown":true,"respectOrderHistoryLimits":true}}`
exp := `{"orderManager":{"enabled":false,"verbose":true,"activelyTrackFuturesPositions":false,"futuresTrackingSeekDuration":-47000,"cancelOrdersOnShutdown":true,"respectOrderHistoryLimits":true}}`
out, err := new(Version5).DowngradeConfig(context.Background(), []byte(in))
out, err := new(v5.Version).DowngradeConfig(context.Background(), []byte(in))
require.NoError(t, err)
assert.Equal(t, exp, string(out), "DowngradeConfig should just reverse the futuresTrackingSeekDuration")
out, err = new(Version5).DowngradeConfig(context.Background(), []byte(exp))
out, err = new(v5.Version).DowngradeConfig(context.Background(), []byte(exp))
require.NoError(t, err)
assert.Equal(t, exp, string(out), "DowngradeConfig should leave an already negative futuresTrackingSeekDuration alone")
}

View File

@@ -1,26 +1,21 @@
package versions
package v6
import (
"context"
"errors"
"github.com/buger/jsonparser"
v6 "github.com/thrasher-corp/gocryptotrader/config/versions/v6"
)
// Version6 implements ConfigVersion
type Version6 struct{}
func init() {
Manager.registerVersion(6, &Version6{})
}
// Version implements ConfigVersion
type Version struct{}
// UpgradeConfig checks and upgrades the portfolioAddresses.providers field
func (v *Version6) UpgradeConfig(_ context.Context, e []byte) ([]byte, error) {
func (*Version) UpgradeConfig(_ context.Context, e []byte) ([]byte, error) {
_, valueType, _, err := jsonparser.Get(e, "portfolioAddresses", "providers")
switch {
case errors.Is(err, jsonparser.KeyPathNotFoundError), valueType == jsonparser.Null:
return jsonparser.Set(e, v6.DefaultConfig, "portfolioAddresses", "providers")
return jsonparser.Set(e, DefaultConfig, "portfolioAddresses", "providers")
case err != nil:
return e, err
}
@@ -28,7 +23,7 @@ func (v *Version6) UpgradeConfig(_ context.Context, e []byte) ([]byte, error) {
}
// DowngradeConfig removes the portfolioAddresses.providers field
func (v *Version6) DowngradeConfig(_ context.Context, e []byte) ([]byte, error) {
func (*Version) DowngradeConfig(_ context.Context, e []byte) ([]byte, error) {
e = jsonparser.Delete(e, "portfolioAddresses", "providers")
return e, nil
}

View File

@@ -1,4 +1,4 @@
package versions
package v6_test
import (
"bytes"
@@ -11,30 +11,30 @@ import (
v6 "github.com/thrasher-corp/gocryptotrader/config/versions/v6"
)
func TestVersion6Upgrade(t *testing.T) {
func TestUpgradeConfig(t *testing.T) {
t.Parallel()
in := []byte(`
{"portfolioAddresses":{"addresses":[{"Address":"1JCe8z4jJVNXSjohjM4i9Hh813dLCNx2Sy","CoinType":"BTC","Balance":0.00108832,"Description":"","WhiteListed":false,"ColdStorage":false,"SupportedExchanges":""}]}}
`)
r, err := new(Version6).UpgradeConfig(context.Background(), in)
r, err := new(v6.Version).UpgradeConfig(context.Background(), in)
require.NoError(t, err, "UpgradeConfig must not error")
require.True(t, bytes.Contains(r, v6.DefaultConfig))
r2, err := new(Version6).UpgradeConfig(context.Background(), r)
r2, err := new(v6.Version).UpgradeConfig(context.Background(), r)
require.NoError(t, err, "UpgradeConfig must not error")
assert.Equal(t, r, r2, "UpgradeConfig should not affect an already upgraded config")
}
func TestVersion6Downgrade(t *testing.T) {
func TestDowngradeConfig(t *testing.T) {
t.Parallel()
in := []byte(`
{"portfolioAddresses":{"addresses":[{"Address":"1JCe8z4jJVNXSjohjM4i9Hh813dLCNx2Sy","CoinType":"BTC","Balance":0.00108832,"Description":"","WhiteListed":false,"ColdStorage":false,"SupportedExchanges":""}],"providers":[{"name":"Ethplorer","enabled":true},{"name":"XRPScan","enabled":true},{"name":"CryptoID","enabled":false,"apiKey":"Key"}]}}
`)
r, err := new(Version6).DowngradeConfig(context.Background(), in)
r, err := new(v6.Version).DowngradeConfig(context.Background(), in)
require.NoError(t, err, "DowngradeConfig must not error")
_, _, _, err = jsonparser.Get(r, "portfolioAddresses", "providers") //nolint:dogsled // Return values not needed
assert.ErrorIs(t, err, jsonparser.KeyPathNotFoundError, "providers should be removed")

View File

@@ -32,6 +32,7 @@ const UseLatestVersion = math.MaxUint16
var (
errVersionIncompatible = errors.New("version does not implement ConfigVersion or ExchangeVersion")
errAlreadyRegistered = errors.New("version is already registered")
errModifyingExchange = errors.New("error modifying exchange config")
errNoVersions = errors.New("error retrieving latest config version: No config versions are registered")
errApplyingVersion = errors.New("error applying version")
@@ -40,7 +41,6 @@ var (
errConfigVersionUnavail = errors.New("version is higher than the latest available version")
errConfigVersionNegative = errors.New("version is negative")
errConfigVersionMax = errors.New("version is above max versions")
errUpgrading = errors.New("error upgrading")
)
// ConfigVersion is a version that affects the general configuration
@@ -211,15 +211,28 @@ func exchangeDeploy(ctx context.Context, patch ExchangeVersion, method func(Exch
}
// registerVersion takes instances of config versions and adds them to the registry
func (m *manager) registerVersion(ver int, v any) {
func (m *manager) registerVersion(ver uint16, v any) {
m.m.Lock()
defer m.m.Unlock()
if ver >= len(m.versions) {
m.versions = slices.Grow(m.versions, ver+1)[:ver+1]
if int(ver) >= len(m.versions) {
m.versions = slices.Grow(m.versions, int(ver+1))[:ver+1]
}
if m.versions[ver] != nil {
panic(fmt.Errorf("%w: %d", errAlreadyRegistered, ver))
}
m.versions[ver] = v
}
// Version returns a version registered by init or nil if nothing has been registered with that version number
func (m *manager) Version(version uint16) any {
m.m.RLock()
defer m.m.RUnlock()
if int(version) < len(m.versions) {
return m.versions[version]
}
return nil
}
// latest returns the highest version number
func (m *manager) latest() (uint16, error) {
m.m.RLock()

View File

@@ -2,12 +2,17 @@ package versions
import (
"context"
"fmt"
"math"
"testing"
"github.com/buger/jsonparser"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/thrasher-corp/gocryptotrader/common"
v0 "github.com/thrasher-corp/gocryptotrader/config/versions/v0"
v1 "github.com/thrasher-corp/gocryptotrader/config/versions/v1"
v2 "github.com/thrasher-corp/gocryptotrader/config/versions/v2"
)
func TestDeploy(t *testing.T) {
@@ -22,8 +27,8 @@ func TestDeploy(t *testing.T) {
m = manager{}
m.registerVersion(0, &Version0{})
m.registerVersion(1, &Version1{})
m.registerVersion(0, &v0.Version{})
m.registerVersion(1, &v1.Version{})
_, err = m.Deploy(context.Background(), []byte(`not an object`), UseLatestVersion)
require.ErrorIs(t, err, jsonparser.KeyPathNotFoundError, "Must throw the correct error trying to add version to bad json")
require.ErrorIs(t, err, common.ErrSettingField, "Must throw the correct error trying to add version to bad json")
@@ -95,7 +100,7 @@ func TestRegisterVersion(t *testing.T) {
t.Parallel()
m := manager{}
m.registerVersion(0, &Version0{})
m.registerVersion(0, &v0.Version{})
assert.NotEmpty(t, m.versions)
m.registerVersion(2, &TestVersion2{})
@@ -106,6 +111,10 @@ func TestRegisterVersion(t *testing.T) {
m.registerVersion(1, &TestVersion1{})
require.Equal(t, 3, len(m.versions), "Must leave len alone when registering out-of-sequence")
require.NotNil(t, m.versions[1], "Must put Version 1 in the correct slot")
assert.PanicsWithError(t, fmt.Sprintf("%s: %d", errAlreadyRegistered, 2), func() {
m.registerVersion(2, &TestVersion2{})
}, "registeringVersion must panic registering an existing version")
}
func TestLatest(t *testing.T) {
@@ -114,14 +123,25 @@ func TestLatest(t *testing.T) {
_, err := m.latest()
require.ErrorIs(t, err, errNoVersions)
m.registerVersion(0, &Version0{})
m.registerVersion(1, &Version1{})
m.registerVersion(0, &v0.Version{})
m.registerVersion(1, &v1.Version{})
v, err := m.latest()
require.NoError(t, err)
assert.Equal(t, uint16(1), v)
m.registerVersion(2, &Version2{})
m.registerVersion(2, &v2.Version{})
v, err = m.latest()
require.NoError(t, err)
assert.Equal(t, uint16(2), v)
}
func TestVersion(t *testing.T) {
t.Parallel()
m := manager{}
m.registerVersion(0, &v0.Version{})
l, err := m.latest()
require.NoError(t, err, "latest must not error")
assert.Nil(t, m.Version(l-1))
assert.NotNil(t, m.Version(l))
assert.Nil(t, m.Version(math.MaxUint16))
}