diff --git a/README.md b/README.md index f1a557cd04f..ef5263697f3 100644 --- a/README.md +++ b/README.md @@ -141,12 +141,12 @@ Binaries will be published once the codebase reaches a stable condition. |User|Contribution Amount| |--|--| -| [thrasher-](https://github.com/thrasher-) | 692 | -| [shazbert](https://github.com/shazbert) | 333 | -| [dependabot[bot]](https://github.com/apps/dependabot) | 293 | +| [thrasher-](https://github.com/thrasher-) | 696 | +| [shazbert](https://github.com/shazbert) | 338 | +| [dependabot[bot]](https://github.com/apps/dependabot) | 301 | | [gloriousCode](https://github.com/gloriousCode) | 234 | | [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | 88 | -| [gbjk](https://github.com/gbjk) | 80 | +| [gbjk](https://github.com/gbjk) | 82 | | [xtda](https://github.com/xtda) | 47 | | [lrascao](https://github.com/lrascao) | 27 | | [Beadko](https://github.com/Beadko) | 17 | diff --git a/cmd/documentation/exchanges_templates/hitbtc.tmpl b/cmd/documentation/exchanges_templates/hitbtc.tmpl index 02d26c0647a..e8d9f488e80 100644 --- a/cmd/documentation/exchanges_templates/hitbtc.tmpl +++ b/cmd/documentation/exchanges_templates/hitbtc.tmpl @@ -93,12 +93,24 @@ if err != nil { } ``` -### How to do Websocket public/private calls +### Subscriptions -```go - // Exchanges will be abstracted out in further updates and examples will be - // supplied then -``` +Subscriptions are for [v2 api](https://hitbtc-com.github.io/hitbtc-api/#socket-api-reference) + +All subscriptions are for spot. + +Default Public Subscriptions: +- Ticker +- Orderbook +- Candles ( Interval: 30 minutes, History: 100 ) +- All Trades ( History: 100 ) + +Default Authenticated Subscriptions: +- My Account events + +Subscriptions are subject to enabled assets and pairs. + +Configure Levels for number of history entries to return for applicable APIs. ### Please click GoDocs chevron above to view current GoDoc information for this package {{template "contributions"}} diff --git a/exchanges/hitbtc/README.md b/exchanges/hitbtc/README.md index da67c56bf62..2c92c5db954 100644 --- a/exchanges/hitbtc/README.md +++ b/exchanges/hitbtc/README.md @@ -111,12 +111,24 @@ if err != nil { } ``` -### How to do Websocket public/private calls +### Subscriptions -```go - // Exchanges will be abstracted out in further updates and examples will be - // supplied then -``` +Subscriptions are for [v2 api](https://hitbtc-com.github.io/hitbtc-api/#socket-api-reference) + +All subscriptions are for spot. + +Default Public Subscriptions: +- Ticker +- Orderbook +- Candles ( Interval: 30 minutes, History: 100 ) +- All Trades ( History: 100 ) + +Default Authenticated Subscriptions: +- My Account events + +Subscriptions are subject to enabled assets and pairs. + +Configure Levels for number of history entries to return for applicable APIs. ### Please click GoDocs chevron above to view current GoDoc information for this package diff --git a/exchanges/hitbtc/hitbtc_test.go b/exchanges/hitbtc/hitbtc_test.go index df6f8ac4683..68de6bfb9b2 100644 --- a/exchanges/hitbtc/hitbtc_test.go +++ b/exchanges/hitbtc/hitbtc_test.go @@ -21,7 +21,9 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/stream" + "github.com/thrasher-corp/gocryptotrader/exchanges/subscription" testexch "github.com/thrasher-corp/gocryptotrader/internal/testing/exchange" + testsubs "github.com/thrasher-corp/gocryptotrader/internal/testing/subscriptions" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -1007,7 +1009,7 @@ func Test_FormatExchangeKlineInterval(t *testing.T) { test := testCases[x] t.Run(test.name, func(t *testing.T) { t.Parallel() - ret, err := h.FormatExchangeKlineInterval(test.interval) + ret, err := formatExchangeKlineInterval(test.interval) if err != nil { t.Fatal(err) } @@ -1090,3 +1092,37 @@ func TestGetCurrencyTradeURL(t *testing.T) { assert.NotEmpty(t, resp) } } + +func TestGenerateSubscriptions(t *testing.T) { + t.Parallel() + + h := new(HitBTC) + require.NoError(t, testexch.Setup(h), "Test instance Setup must not error") + + h.Websocket.SetCanUseAuthenticatedEndpoints(true) + require.True(t, h.Websocket.CanUseAuthenticatedEndpoints(), "CanUseAuthenticatedEndpoints must return true") + subs, err := h.generateSubscriptions() + require.NoError(t, err, "generateSubscriptions should not error") + exp := subscription.List{} + pairs, err := h.GetEnabledPairs(asset.Spot) + require.NoErrorf(t, err, "GetEnabledPairs must not error") + for _, s := range h.Features.Subscriptions { + for _, p := range pairs.Format(currency.PairFormat{Uppercase: true}) { + s = s.Clone() + s.Pairs = currency.Pairs{p} + n := subscriptionNames[s.Channel] + switch s.Channel { + case subscription.MyAccountChannel: + s.QualifiedChannel = `{"method":"` + n + `"}` + case subscription.CandlesChannel: + s.QualifiedChannel = `{"method":"` + n + `","params":{"symbol":"` + p.String() + `","period":"M30","limit":100}}` + case subscription.AllTradesChannel: + s.QualifiedChannel = `{"method":"` + n + `","params":{"symbol":"` + p.String() + `","limit":100}}` + default: + s.QualifiedChannel = `{"method":"` + n + `","params":{"symbol":"` + p.String() + `"}}` + } + exp = append(exp, s) + } + } + testsubs.EqualLists(t, exp, subs) +} diff --git a/exchanges/hitbtc/hitbtc_types.go b/exchanges/hitbtc/hitbtc_types.go index 6ed9f5aaa47..59ddfc9c064 100644 --- a/exchanges/hitbtc/hitbtc_types.go +++ b/exchanges/hitbtc/hitbtc_types.go @@ -294,24 +294,17 @@ type ResponseError struct { // WsRequest defines a request obj for the JSON-RPC and gets a websocket response type WsRequest struct { - Method string `json:"method"` - Params WsParams `json:"params,omitempty"` - ID int64 `json:"id"` -} - -// WsNotification defines a notification obj for the JSON-RPC this does not get -// a websocket response -type WsNotification struct { - JSONRPCVersion string `json:"jsonrpc,omitempty"` - Method string `json:"method"` - Params WsParams `json:"params"` + JSONRPCVersion string `json:"jsonrpc,omitempty"` + Method string `json:"method"` + Params *WsParams `json:"params,omitempty"` + ID int64 `json:"id,omitempty"` } // WsParams are websocket params for a request type WsParams struct { Symbol string `json:"symbol,omitempty"` Period string `json:"period,omitempty"` - Limit int64 `json:"limit,omitempty"` + Limit int `json:"limit,omitempty"` Symbols []string `json:"symbols,omitempty"` } diff --git a/exchanges/hitbtc/hitbtc_websocket.go b/exchanges/hitbtc/hitbtc_websocket.go index 51cc1806f88..619b947a31b 100644 --- a/exchanges/hitbtc/hitbtc_websocket.go +++ b/exchanges/hitbtc/hitbtc_websocket.go @@ -8,13 +8,16 @@ import ( "net/http" "strconv" "strings" + "text/template" "time" + "github.com/Masterminds/sprig/v3" "github.com/gorilla/websocket" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -31,6 +34,22 @@ const ( errAuthFailed = 1002 ) +var subscriptionNames = map[string]string{ + subscription.TickerChannel: "Ticker", + subscription.OrderbookChannel: "Orderbook", + subscription.CandlesChannel: "Candles", + subscription.AllTradesChannel: "Trades", + subscription.MyAccountChannel: "Reports", +} + +var defaultSubscriptions = subscription.List{ + {Enabled: true, Asset: asset.Spot, Channel: subscription.TickerChannel}, + {Enabled: true, Asset: asset.Spot, Channel: subscription.OrderbookChannel}, + {Enabled: true, Asset: asset.Spot, Channel: subscription.CandlesChannel, Interval: kline.ThirtyMin, Levels: 100}, + {Enabled: true, Asset: asset.Spot, Channel: subscription.AllTradesChannel, Levels: 100}, + {Enabled: true, Asset: asset.Spot, Channel: subscription.MyAccountChannel, Authenticated: true}, +} + // WsConnect starts a new connection with the websocket API func (h *HitBTC) WsConnect() error { if !h.Websocket.IsEnabled() || !h.IsEnabled() { @@ -465,104 +484,54 @@ func (h *HitBTC) WsProcessOrderbookUpdate(update *WsOrderbook) error { }) } -// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() -func (h *HitBTC) GenerateDefaultSubscriptions() (subscription.List, error) { - var channels = []string{ - "Ticker", - "Orderbook", - "Trades", - "Candles", - } - - var subscriptions subscription.List - if h.Websocket.CanUseAuthenticatedEndpoints() { - subscriptions = append(subscriptions, &subscription.Subscription{Channel: "Reports"}) - } - pairs, err := h.GetEnabledPairs(asset.Spot) - if err != nil { - return nil, err - } - pairFmt, err := h.GetPairFormat(asset.Spot, true) - if err != nil { - return nil, err - } - pairFmt.Delimiter = "" - pairs = pairs.Format(pairFmt) - for i := range channels { - for j := range pairs { - subscriptions = append(subscriptions, &subscription.Subscription{ - Channel: channels[i], - Pairs: currency.Pairs{pairs[j]}, - Asset: asset.Spot, - }) - } - } - return subscriptions, nil +// generateSubscriptions returns a list of subscriptions from the configured subscriptions feature +func (h *HitBTC) generateSubscriptions() (subscription.List, error) { + return h.Features.Subscriptions.ExpandTemplates(h) } -// Subscribe sends a websocket message to receive data from the channel -func (h *HitBTC) Subscribe(channelsToSubscribe subscription.List) error { - var errs error - for _, s := range channelsToSubscribe { - if len(s.Pairs) != 1 { - return subscription.ErrNotSinglePair - } - pair := s.Pairs[0] +// GetSubscriptionTemplate returns a subscription channel template +func (h *HitBTC) GetSubscriptionTemplate(_ *subscription.Subscription) (*template.Template, error) { + return template.New("master.tmpl").Funcs(sprig.FuncMap()).Funcs(template.FuncMap{ + "subToReq": subToReq, + "isSymbolChannel": isSymbolChannel, + }).Parse(subTplText) +} - r := WsRequest{ - Method: "subscribe" + s.Channel, - ID: h.Websocket.Conn.GenerateMessageID(false), - Params: WsParams{ - Symbol: pair.String(), - }, - } - switch s.Channel { - case "Trades": - r.Params.Limit = 100 - case "Candles": - r.Params.Period = "M30" - r.Params.Limit = 100 - } +const ( + subscribeOp = "subscribe" + unsubscribeOp = "unsubscribe" +) - err := h.Websocket.Conn.SendJSONMessage(context.TODO(), request.Unset, r) - if err == nil { - err = h.Websocket.AddSuccessfulSubscriptions(s) - } - if err != nil { - errs = common.AppendError(errs, err) - } - } - return errs +// Subscribe sends a websocket message to receive data from the channel +func (h *HitBTC) Subscribe(subs subscription.List) error { + return h.ParallelChanOp(subs, func(subs subscription.List) error { return h.manageSubs(subscribeOp, subs) }, 1) } // Unsubscribe sends a websocket message to stop receiving data from the channel func (h *HitBTC) Unsubscribe(subs subscription.List) error { + return h.ParallelChanOp(subs, func(subs subscription.List) error { return h.manageSubs(unsubscribeOp, subs) }, 1) +} + +func (h *HitBTC) manageSubs(op string, subs subscription.List) error { var errs error + subs, errs = subs.ExpandTemplates(h) for _, s := range subs { - if len(s.Pairs) != 1 { - return subscription.ErrNotSinglePair - } - pair := s.Pairs[0] - - r := WsNotification{ + r := WsRequest{ JSONRPCVersion: rpcVersion, - Method: "unsubscribe" + s.Channel, - Params: WsParams{ - Symbol: pair.String(), - }, + ID: h.Websocket.Conn.GenerateMessageID(false), } - - switch s.Channel { - case "Trades": - r.Params.Limit = 100 - case "Candles": - r.Params.Period = "M30" - r.Params.Limit = 100 + if err := json.Unmarshal([]byte(s.QualifiedChannel), &r); err != nil { + errs = common.AppendError(errs, err) + continue } - - err := h.Websocket.Conn.SendJSONMessage(context.TODO(), request.Unset, r) + r.Method = op + r.Method + err := h.Websocket.Conn.SendJSONMessage(context.TODO(), request.Unset, r) // v2 api does not return an ID with errors, so we don't use ReturnResponse if err == nil { - err = h.Websocket.RemoveSubscriptions(s) + if op == subscribeOp { + err = h.Websocket.AddSuccessfulSubscriptions(s) + } else { + err = h.Websocket.RemoveSubscriptions(s) + } } if err != nil { errs = common.AppendError(errs, err) @@ -838,3 +807,50 @@ func (h *HitBTC) wsGetTrades(c currency.Pair, limit int64, sort, by string) (*Ws } return &response, nil } + +// subToReq returns the subscription as a map to populate WsRequest +func subToReq(s *subscription.Subscription, maybePair ...currency.Pair) *WsRequest { + name, ok := subscriptionNames[s.Channel] + if !ok { + panic(fmt.Errorf("%w: %s", subscription.ErrNotSupported, s.Channel)) + } + r := &WsRequest{ + Method: name, + } + if len(maybePair) != 0 { + r.Params = &WsParams{ + Symbol: maybePair[0].String(), + Limit: s.Levels, + } + if s.Interval != 0 { + var err error + if r.Params.Period, err = formatExchangeKlineInterval(s.Interval); err != nil { + panic(err) + } + } + } else if s.Levels != 0 { + r.Params = &WsParams{ + Limit: s.Levels, + } + } + return r +} + +// isSymbolChannel returns if the channel expects receive a symbol +func isSymbolChannel(s *subscription.Subscription) bool { + return s.Channel != subscription.MyAccountChannel +} + +const subTplText = ` +{{- if isSymbolChannel $.S }} + {{ range $asset, $pairs := $.AssetPairs }} + {{- range $p := $pairs -}} + {{- subToReq $.S $p | mustToJson }} + {{ $.PairSeparator }} + {{- end }} + {{ $.AssetSeparator }} + {{- end }} +{{- else }} + {{- subToReq $.S | mustToJson }} +{{- end }} +` diff --git a/exchanges/hitbtc/hitbtc_wrapper.go b/exchanges/hitbtc/hitbtc_wrapper.go index 4d23ba0193e..5d4809ca1ab 100644 --- a/exchanges/hitbtc/hitbtc_wrapper.go +++ b/exchanges/hitbtc/hitbtc_wrapper.go @@ -108,6 +108,7 @@ func (h *HitBTC) SetDefaults() { GlobalResultLimit: 1000, }, }, + Subscriptions: defaultSubscriptions.Clone(), } h.Requester, err = request.New(h.Name, @@ -157,7 +158,7 @@ func (h *HitBTC) Setup(exch *config.Exchange) error { Connector: h.WsConnect, Subscriber: h.Subscribe, Unsubscriber: h.Unsubscribe, - GenerateSubscriptions: h.GenerateDefaultSubscriptions, + GenerateSubscriptions: h.generateSubscriptions, Features: &h.Features.Supports.WebsocketCapabilities, OrderbookBufferConfig: buffer.Config{ SortBuffer: true, @@ -780,8 +781,8 @@ func (h *HitBTC) ValidateAPICredentials(ctx context.Context, assetType asset.Ite return h.CheckTransientError(err) } -// FormatExchangeKlineInterval returns Interval to exchange formatted string -func (h *HitBTC) FormatExchangeKlineInterval(in kline.Interval) (string, error) { +// formatExchangeKlineInterval returns Interval to exchange formatted string +func formatExchangeKlineInterval(in kline.Interval) (string, error) { switch in { case kline.OneMin: return "M1", nil @@ -814,7 +815,7 @@ func (h *HitBTC) GetHistoricCandles(ctx context.Context, pair currency.Pair, a a return nil, err } - formattedInterval, err := h.FormatExchangeKlineInterval(req.ExchangeInterval) + formattedInterval, err := formatExchangeKlineInterval(req.ExchangeInterval) if err != nil { return nil, err } @@ -850,7 +851,7 @@ func (h *HitBTC) GetHistoricCandlesExtended(ctx context.Context, pair currency.P return nil, err } - formattedInterval, err := h.FormatExchangeKlineInterval(req.ExchangeInterval) + formattedInterval, err := formatExchangeKlineInterval(req.ExchangeInterval) if err != nil { return nil, err } diff --git a/exchanges/subscription/subscription.go b/exchanges/subscription/subscription.go index 516466363eb..eb42291e1f6 100644 --- a/exchanges/subscription/subscription.go +++ b/exchanges/subscription/subscription.go @@ -31,6 +31,7 @@ const ( AllTradesChannel = "allTrades" MyTradesChannel = "myTrades" MyOrdersChannel = "myOrders" + MyAccountChannel = "myAccount" ) // Public errors @@ -40,6 +41,7 @@ var ( ErrInStateAlready = errors.New("subscription already in state") ErrInvalidState = errors.New("invalid subscription state") ErrDuplicate = errors.New("duplicate subscription") + ErrNotSupported = errors.New("subscription channel not supported") ) // State tracks the status of a subscription channel diff --git a/go.mod b/go.mod index 3916ad97078..54ce8686704 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/thrasher-corp/gocryptotrader go 1.23.0 require ( + github.com/Masterminds/sprig/v3 v3.3.0 github.com/buger/jsonparser v1.1.1 github.com/d5/tengo/v2 v2.17.0 github.com/gofrs/uuid v4.4.0+incompatible @@ -33,14 +34,21 @@ require ( ) require ( + dario.cat/mergo v1.0.1 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect github.com/boombuler/barcode v1.0.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/friendsofgo/errors v0.9.2 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/huandu/xstrings v1.5.0 // indirect github.com/magiconair/properties v1.8.7 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect @@ -48,7 +56,7 @@ require ( github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/cast v1.7.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect github.com/volatiletech/inflect v0.0.1 // indirect diff --git a/go.sum b/go.sum index 767b310d54c..381591107df 100644 --- a/go.sum +++ b/go.sum @@ -3,9 +3,17 @@ cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= cloud.google.com/go/compute v1.24.0 h1:phWcR2eWzRJaL/kOiJwfFsPs4BaKq1j6vnpZrc1YlVg= cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -83,6 +91,8 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= @@ -97,6 +107,8 @@ github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjw github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= @@ -125,10 +137,14 @@ github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3v github.com/mattn/go-sqlite3 v1.14.23 h1:gbShiuAP1W5j9UOksQ06aiiqPMxYecovVGwmTxWtuw0= github.com/mattn/go-sqlite3 v1.14.23/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= @@ -179,8 +195,8 @@ github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTd github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= -github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=