Skip to content

Commit

Permalink
improve json unmarshaling
Browse files Browse the repository at this point in the history
  • Loading branch information
karalef committed Jul 19, 2022
1 parent 7d16175 commit 8bfcf56
Show file tree
Hide file tree
Showing 8 changed files with 26 additions and 37 deletions.
10 changes: 5 additions & 5 deletions assets.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,20 @@ func (c *Client) AssetsSearch(search string, trim *TrimParams) ([]Asset, Timesta
}
trim.setTo(&q)

return requestArray[Asset](c, "assets", q)
return request[[]Asset](c, "assets", q)
}

// AssetsSearchByIDs returns a list of CoinCap assets.
func (c *Client) AssetsSearchByIDs(ids []string) ([]Asset, Timestamp, error) {
if ids == nil {
return nil, 0, nil
}
return requestArray[Asset](c, "assets", url.Values{"ids": ids})
return request[[]Asset](c, "assets", url.Values{"ids": ids})
}

// AssetByID returns an asset by its ID.
func (c *Client) AssetByID(id string) (*Asset, Timestamp, error) {
return request[Asset](c, "assets/"+id, nil)
return request[*Asset](c, "assets/"+id, nil)
}

// AssetHistory contains the USD price of an asset at a given timestamp.
Expand All @@ -59,7 +59,7 @@ func (c *Client) AssetHistory(id string, interval *IntervalParams) ([]AssetHisto
if err != nil {
return nil, 0, err
}
return requestArray[AssetHistory](c, "assets/"+id+"/history", q)
return request[[]AssetHistory](c, "assets/"+id+"/history", q)
}

// AssetMarket contains the markets info of an asset.
Expand All @@ -78,5 +78,5 @@ type AssetMarket struct {
func (c *Client) AssetMarkets(id string, trim *TrimParams) ([]AssetMarket, Timestamp, error) {
q := make(url.Values)
trim.setTo(&q)
return requestArray[AssetMarket](c, "assets/"+id+"/markets", q)
return request[[]AssetMarket](c, "assets/"+id+"/markets", q)
}
2 changes: 1 addition & 1 deletion candles.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,5 +48,5 @@ func (c *Client) Candles(params CandlesRequest, interval *IntervalParams, trim *
return nil, 0, err
}
trim.setTo(&q)
return requestArray[Candle](c, "candles", q)
return request[[]Candle](c, "candles", q)
}
27 changes: 8 additions & 19 deletions coincap.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,7 @@ type Client struct {
ws *websocket.Dialer
}

func requestArray[T any](c *Client, endpoint string, query url.Values) ([]T, Timestamp, error) {
r, t, err := request[[]T](c, endpoint, query)
return *r, t, err
}

func request[T any](c *Client, endpoint string, query url.Values) (*T, Timestamp, error) {
func request[T any](c *Client, endpoint string, query url.Values) (T, Timestamp, error) {
resp, err := c.http.Do(&http.Request{
Method: http.MethodGet,
URL: &url.URL{
Expand All @@ -54,28 +49,22 @@ func request[T any](c *Client, endpoint string, query url.Values) (*T, Timestamp
},
})
if err != nil {
return nil, 0, err
var t T
return t, 0, err
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
if resp.StatusCode == http.StatusNotFound {
return nil, 0, nil
}
return nil, 0, errors.New("unexpected http status code: " + resp.Status)
}

r, b, err := decodeJSON[struct {
Data json.RawMessage `json:"data"`
Timestamp Timestamp `json:"timestamp"`
Data T `json:"data"`
Timestamp Timestamp `json:"timestamp"`
}](resp.Body)

if err != nil {
return nil, 0, errors.New("coincap: unexpected response:\n" + string(b))
var t T
return t, 0, errors.New("coincap (" + resp.Status + "): " + string(b))
}

var v T
return &v, r.Timestamp, json.Unmarshal(r.Data, &v)
return r.Data, r.Timestamp, nil
}

func decodeJSON[T any](r io.Reader) (*T, []byte, error) {
Expand Down
4 changes: 2 additions & 2 deletions exchanges.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ type Exchange struct {

// Exchanges returns information about all exchanges currently tracked by CoinCap.
func (c *Client) Exchanges() ([]Exchange, Timestamp, error) {
return requestArray[Exchange](c, "exchanges", nil)
return request[[]Exchange](c, "exchanges", nil)
}

// ExchangeByID returns exchange data for an exchange with the given unique ID.
func (c *Client) ExchangeByID(id string) (*Exchange, Timestamp, error) {
return request[Exchange](c, "exchanges/"+id, nil)
return request[*Exchange](c, "exchanges/"+id, nil)
}
2 changes: 1 addition & 1 deletion markets.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,5 @@ func (c *Client) Markets(params MarketsRequest, trim *TrimParams) ([]Market, Tim
q.Set("assetId", params.AssetID)
}

return requestArray[Market](c, "markets", q)
return request[[]Market](c, "markets", q)
}
4 changes: 2 additions & 2 deletions rates.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ type Rate struct {

// Rates returns currency rates standardized in USD.
func (c *Client) Rates() ([]Rate, Timestamp, error) {
return requestArray[Rate](c, "rates", nil)
return request[[]Rate](c, "rates", nil)
}

// RateByID returns the USD rate for the given asset identifier.
func (c *Client) RateByID(id string) (*Rate, Timestamp, error) {
return request[Rate](c, "rates/"+id, nil)
return request[*Rate](c, "rates/"+id, nil)
}
12 changes: 6 additions & 6 deletions stream.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type Trade struct {
// response 'socket':true/false.
// The trades websocket is the only way to receive individual
// trade data through CoinCap.
func (c *Client) Trades(exchange string) (*Stream[Trade], error) {
func (c *Client) Trades(exchange string) (*Stream[*Trade], error) {
e, _, err := c.ExchangeByID(exchange)
if err != nil {
return nil, err
Expand All @@ -40,7 +40,7 @@ func (c *Client) Trades(exchange string) (*Stream[Trade], error) {
return nil, errors.New("exchange '" + exchange + "' does not support websockets")
}
const u = "wss://ws.coincap.io/trades/"
return dial[Trade](c.ws, u+exchange)
return dial[*Trade](c.ws, u+exchange)
}

// Price implements Unmarshaler interface for float64.
Expand Down Expand Up @@ -80,15 +80,15 @@ func (c *Client) Prices(assets ...string) (*Stream[map[string]Price], error) {
// Stream streams data from websocket conneсtion.
type Stream[T any] struct {
conn *websocket.Conn
ch chan *T
ch chan T
stop chan struct{}
mut sync.Mutex
err error
}

// DataChannel returns data channel.
// It will be closed if there is an error or if the stream is closed.
func (s *Stream[T]) DataChannel() <-chan *T {
func (s *Stream[T]) DataChannel() <-chan T {
return s.ch
}

Expand Down Expand Up @@ -128,7 +128,7 @@ func (s *Stream[T]) dial() {
select {
case <-s.stop:
return
case s.ch <- v:
case s.ch <- *v:
}
}
s.mut.Lock()
Expand All @@ -150,7 +150,7 @@ func dial[T any](ws *websocket.Dialer, u string) (*Stream[T], error) {

s := Stream[T]{
conn: conn,
ch: make(chan *T),
ch: make(chan T),
stop: make(chan struct{}),
}
go s.dial()
Expand Down
2 changes: 1 addition & 1 deletion stream_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func TestPrices(t *testing.T) {
t.Fatal(s.Err())
}

_, ok = (*d)["bitcoin"]
_, ok = d["bitcoin"]
if !ok {
t.Fatal("result has no expected field")
}
Expand Down

0 comments on commit 8bfcf56

Please sign in to comment.