From b94b64a305829819f74a098fc63d2c5ddae84761 Mon Sep 17 00:00:00 2001 From: shinchan-zhai Date: Mon, 23 Mar 2026 11:00:24 +0800 Subject: [PATCH] robustness: add HTTP status code checks to market data & provider APIs - market/api_client.go: check StatusCode for exchangeInfo, klines, price endpoints + add truncateBody helper for safe error messages - market/data.go: check StatusCode for openInterest and premiumIndex - provider/coinank: check StatusCode for GET and POST requests - provider/twelvedata: check StatusCode for time series and quote APIs - Prevents confusing JSON unmarshal errors when APIs return 429/500/etc --- market/api_client.go | 17 +++++++++++++++++ market/data.go | 9 ++++++++- provider/coinank/coinank_api/kline.go | 3 +++ provider/coinank/coinank_http.go | 6 ++++++ provider/twelvedata/kline.go | 6 ++++++ 5 files changed, 40 insertions(+), 1 deletion(-) diff --git a/market/api_client.go b/market/api_client.go index cf74bec8..f3058482 100644 --- a/market/api_client.go +++ b/market/api_client.go @@ -15,6 +15,14 @@ const ( baseURL = "https://fapi.binance.com" ) +// truncateBody returns the first 512 bytes of body for error messages. +func truncateBody(body []byte) string { + if len(body) > 512 { + return string(body[:512]) + "..." + } + return string(body) +} + type APIClient struct { client *http.Client } @@ -47,6 +55,9 @@ func (c *APIClient) GetExchangeInfo() (*ExchangeInfo, error) { if err != nil { return nil, err } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("Binance API error (status %d): %s", resp.StatusCode, truncateBody(body)) + } var exchangeInfo ExchangeInfo err = json.Unmarshal(body, &exchangeInfo) if err != nil { @@ -79,6 +90,9 @@ func (c *APIClient) GetKlines(symbol, interval string, limit int) ([]Kline, erro if err != nil { return nil, err } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("Binance klines API error (status %d): %s", resp.StatusCode, truncateBody(body)) + } var klineResponses []KlineResponse err = json.Unmarshal(body, &klineResponses) @@ -144,6 +158,9 @@ func (c *APIClient) GetCurrentPrice(symbol string) (float64, error) { if err != nil { return 0, err } + if resp.StatusCode != http.StatusOK { + return 0, fmt.Errorf("Binance price API error (status %d): %s", resp.StatusCode, truncateBody(body)) + } var ticker PriceTicker err = json.Unmarshal(body, &ticker) diff --git a/market/data.go b/market/data.go index 30b1d6b6..9230b8ac 100644 --- a/market/data.go +++ b/market/data.go @@ -1,11 +1,12 @@ package market import ( - "nofx/safe" "encoding/json" "fmt" "math" + "net/http" "nofx/logger" + "nofx/safe" "strconv" "strings" "sync" @@ -270,6 +271,9 @@ func getOpenInterestData(symbol string) (*OIData, error) { if err != nil { return nil, err } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("Binance openInterest API error (status %d): %s", resp.StatusCode, truncateBody(body)) + } var result struct { OpenInterest string `json:"openInterest"` @@ -315,6 +319,9 @@ func getFundingRate(symbol string) (float64, error) { if err != nil { return 0, err } + if resp.StatusCode != http.StatusOK { + return 0, fmt.Errorf("Binance premiumIndex API error (status %d): %s", resp.StatusCode, truncateBody(body)) + } var result struct { Symbol string `json:"symbol"` diff --git a/provider/coinank/coinank_api/kline.go b/provider/coinank/coinank_api/kline.go index 2fca3d1a..4ea28bc0 100644 --- a/provider/coinank/coinank_api/kline.go +++ b/provider/coinank/coinank_api/kline.go @@ -71,6 +71,9 @@ func get(ctx context.Context, path string, paramsMap map[string]string) (string, if err != nil { return "", err } + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("CoinAnk API error (status %d): %.512s", resp.StatusCode, string(body)) + } return string(body), nil } diff --git a/provider/coinank/coinank_http.go b/provider/coinank/coinank_http.go index 11a653e9..c795ef1e 100644 --- a/provider/coinank/coinank_http.go +++ b/provider/coinank/coinank_http.go @@ -63,6 +63,9 @@ func (c *CoinankClient) Get(ctx context.Context, path string, paramsMap map[stri if err != nil { return "", err } + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("CoinAnk GET error (status %d): %.512s", resp.StatusCode, string(body)) + } return string(body), nil } @@ -88,6 +91,9 @@ func (c *CoinankClient) Post(ctx context.Context, path string, data any) (string if err != nil { return "", err } + if resp.StatusCode != http.StatusOK { + return "", fmt.Errorf("CoinAnk POST error (status %d): %.512s", resp.StatusCode, string(body)) + } return string(body), nil } diff --git a/provider/twelvedata/kline.go b/provider/twelvedata/kline.go index d31d9e31..064144f8 100644 --- a/provider/twelvedata/kline.go +++ b/provider/twelvedata/kline.go @@ -129,6 +129,9 @@ func (c *Client) GetTimeSeries(ctx context.Context, symbol string, interval stri if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("TwelveData API error (status %d): %.512s", resp.StatusCode, string(body)) + } // Parse response var result TimeSeriesResponse @@ -176,6 +179,9 @@ func (c *Client) GetQuote(ctx context.Context, symbol string) (*QuoteResponse, e if err != nil { return nil, fmt.Errorf("failed to read response: %w", err) } + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("TwelveData API error (status %d): %.512s", resp.StatusCode, string(body)) + } // Parse response var result QuoteResponse