mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-13 23:16:45 +00:00
* Subscriptions: Add List.AssetPairs * Subscriptions: Add Template and QualifiedChannel These fields separate the concept of what the channel is from the qualified resource name * Subscriptions: Add List.SetStates() * Subscriptions: Add List.QualifiedChannels * Subscriptions: Rename testsubs.EqualLists * Binance: Switch to ExpandTemplates * Binance: Update ConfigTest format * Subscriptions: Test Coverage improvements * Subscriptions: Reenterant List.ExpandTemplates * Subscriptions: Move templates from subscriptions to exchanges * Binance: Inline subscription template and improvements
151 lines
4.3 KiB
Go
151 lines
4.3 KiB
Go
package subscription
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
|
|
"github.com/thrasher-corp/gocryptotrader/currency"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
|
)
|
|
|
|
const (
|
|
groupSeparator = "\x1D"
|
|
recordSeparator = "\x1E"
|
|
)
|
|
|
|
var (
|
|
errInvalidAssetExpandPairs = errors.New("subscription template containing PairSeparator with must contain either specific Asset or AssetSeparator")
|
|
errAssetRecords = errors.New("subscription template did not generate the expected number of asset records")
|
|
errPairRecords = errors.New("subscription template did not generate the expected number of pair records")
|
|
errAssetTemplateWithoutAll = errors.New("sub.Asset must be set to All if AssetSeparator is used in Channel template")
|
|
errNoTemplateContent = errors.New("subscription template did not generate content")
|
|
errInvalidTemplate = errors.New("GetSubscriptionTemplate did not return a template")
|
|
)
|
|
|
|
type tplCtx struct {
|
|
S *Subscription
|
|
AssetPairs assetPairs
|
|
PairSeparator string
|
|
AssetSeparator string
|
|
}
|
|
|
|
// ExpandTemplates returns a list of Subscriptions with Template expanded
|
|
// May be called on already expanded subscriptions: Passes $s through unprocessed if QualifiedChannel is already populated
|
|
// Calls e.GetSubscriptionTemplate to find a template for each subscription
|
|
// Filters out Authenticated subscriptions if !e.CanUseAuthenticatedEndpoints
|
|
// See README.md for more details
|
|
func (l List) ExpandTemplates(e iExchange) (List, error) {
|
|
if !slices.ContainsFunc(l, func(s *Subscription) bool { return s.QualifiedChannel == "" }) {
|
|
// Empty list, or already processed
|
|
return slices.Clone(l), nil
|
|
}
|
|
|
|
if !e.CanUseAuthenticatedWebsocketEndpoints() {
|
|
n := List{}
|
|
for _, s := range l {
|
|
if !s.Authenticated {
|
|
n = append(n, s)
|
|
}
|
|
}
|
|
l = n
|
|
}
|
|
|
|
ap, err := l.assetPairs(e)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
assets := make(asset.Items, 0, len(ap))
|
|
for k := range ap {
|
|
assets = append(assets, k)
|
|
}
|
|
slices.Sort(assets) // text/template ranges maps in sorted order
|
|
subs := List{}
|
|
|
|
for _, s := range l {
|
|
if s.QualifiedChannel != "" {
|
|
subs = append(subs, s)
|
|
continue
|
|
}
|
|
|
|
subCtx := &tplCtx{
|
|
S: s,
|
|
AssetPairs: ap,
|
|
PairSeparator: recordSeparator,
|
|
AssetSeparator: groupSeparator,
|
|
}
|
|
|
|
t, err := e.GetSubscriptionTemplate(s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if t == nil {
|
|
return nil, errInvalidTemplate
|
|
}
|
|
|
|
buf := &bytes.Buffer{}
|
|
if err := t.Execute(buf, subCtx); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
out := buf.String()
|
|
|
|
subAssets := assets
|
|
xpandPairs := strings.Contains(out, subCtx.PairSeparator)
|
|
if xpandAssets := strings.Contains(out, subCtx.AssetSeparator); xpandAssets {
|
|
if s.Asset != asset.All {
|
|
return nil, errAssetTemplateWithoutAll
|
|
}
|
|
} else {
|
|
if xpandPairs && (s.Asset == asset.All || s.Asset == asset.Empty) {
|
|
// We don't currently support expanding Pairs without expanding Assets for All or Empty assets, but we could; waiting for a use-case
|
|
return nil, errInvalidAssetExpandPairs
|
|
}
|
|
// No expansion so update expected Assets for consistent behaviour below
|
|
subAssets = []asset.Item{s.Asset}
|
|
}
|
|
|
|
out = strings.TrimRight(out, " \n\r\t"+subCtx.PairSeparator+subCtx.AssetSeparator)
|
|
|
|
assetRecords := strings.Split(out, subCtx.AssetSeparator)
|
|
if len(assetRecords) != len(subAssets) {
|
|
return nil, fmt.Errorf("%w: Got %d; Expected %d", errAssetRecords, len(assetRecords), len(subAssets))
|
|
}
|
|
|
|
for i, assetChannels := range assetRecords {
|
|
a := subAssets[i]
|
|
assetChannels = strings.TrimRight(assetChannels, " \n\r\t"+recordSeparator)
|
|
pairLines := strings.Split(assetChannels, subCtx.PairSeparator)
|
|
pairs, ok := ap[a]
|
|
if xpandPairs {
|
|
if !ok {
|
|
return nil, fmt.Errorf("%w: %s", asset.ErrInvalidAsset, a)
|
|
}
|
|
if len(pairLines) != len(pairs) {
|
|
return nil, fmt.Errorf("%w: Got %d; Expected %d", errPairRecords, len(pairLines), len(pairs))
|
|
}
|
|
}
|
|
for j, channel := range pairLines {
|
|
c := s.Clone()
|
|
c.Asset = a
|
|
channel = strings.TrimSpace(channel)
|
|
if channel == "" {
|
|
return nil, fmt.Errorf("%w: %s", errNoTemplateContent, s)
|
|
}
|
|
c.QualifiedChannel = strings.TrimSpace(channel)
|
|
if xpandPairs {
|
|
c.Pairs = currency.Pairs{pairs[j]}
|
|
} else {
|
|
c.Pairs = pairs
|
|
}
|
|
subs = append(subs, c)
|
|
}
|
|
}
|
|
}
|
|
|
|
return subs, nil
|
|
}
|