diff --git a/config/versions/register.go b/config/versions/register.go new file mode 100644 index 00000000..0f346b1b --- /dev/null +++ b/config/versions/register.go @@ -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{}) +} diff --git a/config/versions/register_test.go b/config/versions/register_test.go new file mode 100644 index 00000000..da871474 --- /dev/null +++ b/config/versions/register_test.go @@ -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") +} diff --git a/config/versions/v0.go b/config/versions/v0.go deleted file mode 100644 index bc994377..00000000 --- a/config/versions/v0.go +++ /dev/null @@ -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 -} diff --git a/config/versions/v0/v0.go b/config/versions/v0/v0.go new file mode 100644 index 00000000..0c21e2e5 --- /dev/null +++ b/config/versions/v0/v0.go @@ -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 +} diff --git a/config/versions/v0/v0_test.go b/config/versions/v0/v0_test.go new file mode 100644 index 00000000..afdc476e --- /dev/null +++ b/config/versions/v0/v0_test.go @@ -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) +} diff --git a/config/versions/v1.go b/config/versions/v1/v1.go similarity index 69% rename from config/versions/v1.go rename to config/versions/v1/v1.go index 80755553..645d920a 100644 --- a/config/versions/v1.go +++ b/config/versions/v1/v1.go @@ -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 } diff --git a/config/versions/v1_test.go b/config/versions/v1/v1_test.go similarity index 72% rename from config/versions/v1_test.go rename to config/versions/v1/v1_test.go index 24791653..c4edaf72 100644 --- a/config/versions/v1_test.go +++ b/config/versions/v1/v1_test.go @@ -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) } diff --git a/config/versions/v2.go b/config/versions/v2/v2.go similarity index 57% rename from config/versions/v2.go rename to config/versions/v2/v2.go index e7f8af32..5e7483f1 100644 --- a/config/versions/v2.go +++ b/config/versions/v2/v2.go @@ -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") } diff --git a/config/versions/v2_test.go b/config/versions/v2/v2_test.go similarity index 67% rename from config/versions/v2_test.go rename to config/versions/v2/v2_test.go index 23ee35ef..edb5e715 100644 --- a/config/versions/v2_test.go +++ b/config/versions/v2/v2_test.go @@ -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]) diff --git a/config/versions/v3.go b/config/versions/v3/v3.go similarity index 64% rename from config/versions/v3.go rename to config/versions/v3/v3.go index a8d52ce5..7c9c5bbe 100644 --- a/config/versions/v3.go +++ b/config/versions/v3/v3.go @@ -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 } diff --git a/config/versions/v3_test.go b/config/versions/v3/v3_test.go similarity index 63% rename from config/versions/v3_test.go rename to config/versions/v3/v3_test.go index e816effc..796c30fc 100644 --- a/config/versions/v3_test.go +++ b/config/versions/v3/v3_test.go @@ -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()) } diff --git a/config/versions/v4.go b/config/versions/v4/v4.go similarity index 79% rename from config/versions/v4.go rename to config/versions/v4/v4.go index 0c439043..c9b124b8 100644 --- a/config/versions/v4.go +++ b/config/versions/v4/v4.go @@ -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 { diff --git a/config/versions/v4_test.go b/config/versions/v4/v4_test.go similarity index 75% rename from config/versions/v4_test.go rename to config/versions/v4/v4_test.go index 33008251..a48e8915 100644 --- a/config/versions/v4_test.go +++ b/config/versions/v4/v4_test.go @@ -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) diff --git a/config/versions/v5.go b/config/versions/v5/v5.go similarity index 73% rename from config/versions/v5.go rename to config/versions/v5/v5.go index 7b5ec82a..b434523e 100644 --- a/config/versions/v5.go +++ b/config/versions/v5/v5.go @@ -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 diff --git a/config/versions/v5_test.go b/config/versions/v5/v5_test.go similarity index 86% rename from config/versions/v5_test.go rename to config/versions/v5/v5_test.go index cace3d09..d2351e61 100644 --- a/config/versions/v5_test.go +++ b/config/versions/v5/v5_test.go @@ -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") } diff --git a/config/versions/v6.go b/config/versions/v6/v6.go similarity index 53% rename from config/versions/v6.go rename to config/versions/v6/v6.go index b30ebbe6..59d23c31 100644 --- a/config/versions/v6.go +++ b/config/versions/v6/v6.go @@ -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 } diff --git a/config/versions/v6_test.go b/config/versions/v6/v6_test.go similarity index 82% rename from config/versions/v6_test.go rename to config/versions/v6/v6_test.go index 26d4377d..0e22d851 100644 --- a/config/versions/v6_test.go +++ b/config/versions/v6/v6_test.go @@ -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") diff --git a/config/versions/versions.go b/config/versions/versions.go index 6726dd70..384de27c 100644 --- a/config/versions/versions.go +++ b/config/versions/versions.go @@ -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() diff --git a/config/versions/versions_test.go b/config/versions/versions_test.go index f420fd28..7cd656ab 100644 --- a/config/versions/versions_test.go +++ b/config/versions/versions_test.go @@ -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)) +}