Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ Ref: https://keepachangelog.com/en/1.0.0/

## Unreleased

### Features

* [47](https://github.com/JulianToledano/goingecko/pull/47) Adds a rate limited client with configurable request limits and exponential backoff retry policy.

## [v3.0.3](https://github.com/JulianToledano/goingecko/releases/tag/v3.0.3) - 2025-04-18

### Improvements
Expand Down
2 changes: 1 addition & 1 deletion api/assetPlatforms/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type AssetPlatformsClient struct {
*internal.Client
}

func NewClient(c *geckohttp.Client, url string) *AssetPlatformsClient {
func NewClient(c geckohttp.HttpClient, url string) *AssetPlatformsClient {
return &AssetPlatformsClient{
internal.NewClient(c, url),
}
Expand Down
2 changes: 1 addition & 1 deletion api/categories/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type CategoriesClient struct {
*internal.Client
}

func NewClient(c *geckohttp.Client, url string) *CategoriesClient {
func NewClient(c geckohttp.HttpClient, url string) *CategoriesClient {
return &CategoriesClient{
internal.NewClient(c, url),
}
Expand Down
78 changes: 72 additions & 6 deletions api/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package api

import (
"net/http"
"time"

"github.com/JulianToledano/goingecko/v3/api/assetPlatforms"
"github.com/JulianToledano/goingecko/v3/api/categories"
Expand All @@ -20,17 +21,22 @@ import (
geckohttp "github.com/JulianToledano/goingecko/v3/http"
)

const (
proApiKeyHeaderKey = "x-cg-pro-api-key"
demoApiKeyHeaderKey = "x-cg-demo-api-key"
)

// proApiHeader returns a function that sets the Pro API key header on requests
func proApiHeader(apiKey string) geckohttp.ApiHeaderFn {
return func(r *http.Request) {
r.Header.Set("x-cg-pro-api-key", apiKey)
r.Header.Set(proApiKeyHeaderKey, apiKey)
}
}

// demoApiHeader returns a function that sets the Demo API key header on requests
func demoApiHeader(apiKey string) geckohttp.ApiHeaderFn {
return func(r *http.Request) {
r.Header.Set("x-cg-demo-api-key", apiKey)
r.Header.Set(demoApiKeyHeaderKey, apiKey)
}
}

Expand All @@ -52,32 +58,92 @@ type Client struct {
*companies.CompaniesClient
}

// NewClient creates a new Client with the given gecko HTTP client and base URL.
func NewClient(c geckohttp.HttpClient, url string) *Client {
return newClient(c, url)
}

// NewDefaultClient creates a new Client using the default HTTP client and base URL
func NewDefaultClient() *Client {
return newClient(
geckohttp.NewClient(geckohttp.WithHttpClient(http.DefaultClient)),
geckohttp.NewClient(
geckohttp.WithHttpClient[*geckohttp.Client](http.DefaultClient),
),
BaseURL,
)
}

// NewDefaultRateLimitedClient creates a new Client using the default HTTP client and base URL
func NewDefaultRateLimitedClient() *Client {
return newClient(
geckohttp.NewRateLimitedClient(
geckohttp.WithHttpClient[*geckohttp.RateLimitedClient](http.DefaultClient),
geckohttp.WithRateLimit[*geckohttp.RateLimitedClient](15),
geckohttp.WithRetryPolicy[*geckohttp.RateLimitedClient](5, 2),
),
BaseURL,
)
}

// NewDemoApiClient creates a new Client configured for the Demo API with the provided API key and HTTP client
func NewDemoApiClient(apiKey string, c *http.Client) *Client {
return newClient(
geckohttp.NewClient(geckohttp.WithHttpClient(c), geckohttp.WithApiHeaderFn(demoApiHeader(apiKey))),
geckohttp.NewClient(
geckohttp.WithHttpClient[*geckohttp.Client](c),
geckohttp.WithApiHeaderFn[*geckohttp.Client](demoApiHeader(apiKey)),
),
BaseURL,
)
}

// NewDemoApiRateLimitedClient creates a new Client configured for the Demo API with rate limiting.
// It takes an API key, HTTP client, and rate limiting configuration:
// - apiKey: The API key for authentication
// - c: The HTTP client to use for requests
// - reqPerMinute: Maximum number of requests allowed per minute
//
// The client is configured with 5 retry attempts and a base delay of 2 seconds between retries.
func NewDemoApiRateLimitedClient(apiKey string, c *http.Client, reqPerMinute int) *Client {
return newClient(
geckohttp.NewRateLimitedClient(
geckohttp.WithHttpClient[*geckohttp.RateLimitedClient](c),
geckohttp.WithApiHeaderFn[*geckohttp.RateLimitedClient](demoApiHeader(apiKey)),
geckohttp.WithRateLimit[*geckohttp.RateLimitedClient](reqPerMinute),
geckohttp.WithRetryPolicy[*geckohttp.RateLimitedClient](5, 2)),
BaseURL,
)
}

// NewProApiClient creates a new Client configured for the Pro API with the provided API key and HTTP client
func NewProApiClient(apiKey string, c *http.Client) *Client {
return newClient(
geckohttp.NewClient(geckohttp.WithHttpClient(c), geckohttp.WithApiHeaderFn(proApiHeader(apiKey))),
geckohttp.NewClient(
geckohttp.WithHttpClient[*geckohttp.Client](c),
geckohttp.WithApiHeaderFn[*geckohttp.Client](proApiHeader(apiKey)),
),
ProBaseURL,
)
}

// NewProApiRateLimitedClient creates a new Client configured for the Pro API with rate limiting.
// It takes an API key, HTTP client, and rate limiting configuration parameters:
// - reqPerMinute: Maximum number of requests allowed per minute
// - maxRetries: Maximum number of retry attempts for failed requests
// - baseDelay: Initial delay between retries, which will be exponentially increased
func NewProApiRateLimitedClient(apiKey string, c *http.Client, reqPerMinute, maxRetries int, baseDelay time.Duration) *Client {
return newClient(
geckohttp.NewRateLimitedClient(
geckohttp.WithHttpClient[*geckohttp.RateLimitedClient](c),
geckohttp.WithApiHeaderFn[*geckohttp.RateLimitedClient](proApiHeader(apiKey)),
geckohttp.WithRateLimit[*geckohttp.RateLimitedClient](reqPerMinute),
geckohttp.WithRetryPolicy[*geckohttp.RateLimitedClient](maxRetries, baseDelay),
),
ProBaseURL,
)
}

// newClient creates a new Client with the provided HTTP client and base URL
func newClient(c *geckohttp.Client, url string) *Client {
func newClient(c geckohttp.HttpClient, url string) *Client {
return &Client{
PingClient: ping.NewClient(c, url),
SimpleClient: simple.NewClient(c, url),
Expand Down
2 changes: 1 addition & 1 deletion api/coins/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type CoinsClient struct {
*internal.Client
}

func NewClient(c *geckohttp.Client, url string) *CoinsClient {
func NewClient(c geckohttp.HttpClient, url string) *CoinsClient {
return &CoinsClient{
internal.NewClient(c, url),
}
Expand Down
4 changes: 1 addition & 3 deletions api/coins/id_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ package coins

import (
"context"
"net/http"
"testing"

"github.com/JulianToledano/goingecko/v3/api/internal"
geckohttp "github.com/JulianToledano/goingecko/v3/http"
)

func TestCoinsId(t *testing.T) {
Expand All @@ -23,7 +21,7 @@ func TestCoinsId(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
c := &CoinsClient{
internal.NewClient(
geckohttp.NewClient(geckohttp.WithHttpClient(http.DefaultClient)),
internal.CommonTestClient,
internal.BaseURL,
),
}
Expand Down
4 changes: 1 addition & 3 deletions api/coins/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ package coins

import (
"context"
"net/http"
"testing"

"github.com/JulianToledano/goingecko/v3/api/internal"
geckohttp "github.com/JulianToledano/goingecko/v3/http"
)

func TestCoinsClient_CoinsList(t *testing.T) {
Expand All @@ -21,7 +19,7 @@ func TestCoinsClient_CoinsList(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
c := &CoinsClient{
internal.NewClient(
geckohttp.NewClient(geckohttp.WithHttpClient(http.DefaultClient)),
internal.CommonTestClient,
internal.BaseURL,
),
}
Expand Down
2 changes: 1 addition & 1 deletion api/companies/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type CompaniesClient struct {
*internal.Client
}

func NewClient(c *geckohttp.Client, url string) *CompaniesClient {
func NewClient(c geckohttp.HttpClient, url string) *CompaniesClient {
return &CompaniesClient{
internal.NewClient(c, url),
}
Expand Down
2 changes: 1 addition & 1 deletion api/contract/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type ContractClient struct {
*internal.Client
}

func NewClient(c *geckohttp.Client, url string) *ContractClient {
func NewClient(c geckohttp.HttpClient, url string) *ContractClient {
return &ContractClient{
internal.NewClient(c, url),
}
Expand Down
2 changes: 1 addition & 1 deletion api/derivatives/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type DerivativesClient struct {
*internal.Client
}

func NewClient(c *geckohttp.Client, url string) *DerivativesClient {
func NewClient(c geckohttp.HttpClient, url string) *DerivativesClient {
return &DerivativesClient{
internal.NewClient(c, url),
}
Expand Down
2 changes: 1 addition & 1 deletion api/exchangeRates/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type ExchangeRatesClient struct {
*internal.Client
}

func NewClient(c *geckohttp.Client, url string) *ExchangeRatesClient {
func NewClient(c geckohttp.HttpClient, url string) *ExchangeRatesClient {
return &ExchangeRatesClient{
internal.NewClient(c, url),
}
Expand Down
2 changes: 1 addition & 1 deletion api/exchanges/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type ExchangesClient struct {
*internal.Client
}

func NewClient(c *geckohttp.Client, url string) *ExchangesClient {
func NewClient(c geckohttp.HttpClient, url string) *ExchangesClient {
return &ExchangesClient{
internal.NewClient(c, url),
}
Expand Down
2 changes: 1 addition & 1 deletion api/global/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type GlobalClient struct {
*internal.Client
}

func NewClient(c *geckohttp.Client, url string) *GlobalClient {
func NewClient(c geckohttp.HttpClient, url string) *GlobalClient {
return &GlobalClient{
internal.NewClient(c, url),
}
Expand Down
21 changes: 16 additions & 5 deletions api/internal/client.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
package internal

import geckohttp "github.com/JulianToledano/goingecko/v3/http"
import (
"net/http"

geckohttp "github.com/JulianToledano/goingecko/v3/http"
)

type Client struct {
*geckohttp.Client
geckohttp.HttpClient

URL string
}

func NewClient(httpClient *geckohttp.Client, url string) *Client {
func NewClient(httpClient geckohttp.HttpClient, url string) *Client {
return &Client{
Client: httpClient,
URL: url,
HttpClient: httpClient,
URL: url,
}
}

// CommonTestClient is a test client for use in tests so rate limiting is not an issue
var CommonTestClient = geckohttp.NewRateLimitedClient(
geckohttp.WithHttpClient[*geckohttp.RateLimitedClient](http.DefaultClient),
geckohttp.WithRateLimit[*geckohttp.RateLimitedClient](15),
geckohttp.WithRetryPolicy[*geckohttp.RateLimitedClient](5, 2),
)
2 changes: 1 addition & 1 deletion api/nfts/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ type NftsClient struct {
*internal.Client
}

func NewClient(c *geckohttp.Client, url string) *NftsClient {
func NewClient(c geckohttp.HttpClient, url string) *NftsClient {
return &NftsClient{
internal.NewClient(c, url),
}
Expand Down
2 changes: 1 addition & 1 deletion api/ping/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type PingClient struct {
*internal.Client
}

func NewClient(c *geckohttp.Client, url string) *PingClient {
func NewClient(c geckohttp.HttpClient, url string) *PingClient {
return &PingClient{
internal.NewClient(c, url),
}
Expand Down
2 changes: 1 addition & 1 deletion api/search/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type SearchClient struct {
*internal.Client
}

func NewClient(c *geckohttp.Client, url string) *SearchClient {
func NewClient(c geckohttp.HttpClient, url string) *SearchClient {
return &SearchClient{
internal.NewClient(c, url),
}
Expand Down
2 changes: 1 addition & 1 deletion api/simple/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type SimpleClient struct {
*internal.Client
}

func NewClient(c *geckohttp.Client, url string) *SimpleClient {
func NewClient(c geckohttp.HttpClient, url string) *SimpleClient {
return &SimpleClient{
internal.NewClient(c, url),
}
Expand Down
2 changes: 1 addition & 1 deletion api/trending/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ type TrendingClient struct {
*internal.Client
}

func NewClient(c *geckohttp.Client, url string) *TrendingClient {
func NewClient(c geckohttp.HttpClient, url string) *TrendingClient {
return &TrendingClient{
internal.NewClient(c, url),
}
Expand Down
7 changes: 3 additions & 4 deletions docs/examples/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ module goingeckoExamples

go 1.22.4

require github.com/JulianToledano/goingecko/v3 v3.0.0-20241230130119-db5fd588cc6e // indirect
require github.com/JulianToledano/goingecko/v3 v3.0.3

require golang.org/x/time v0.5.0 // indirect

replace (
github.com/JulianToledano/goingecko/v3 => ../..
)
replace github.com/JulianToledano/goingecko/v3 => ../..
4 changes: 2 additions & 2 deletions docs/examples/go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
github.com/JulianToledano/goingecko/v3 v3.0.0-20241230130119-db5fd588cc6e h1:fM3bCPQ4Rc3nnso5VJSa52UvbSO1NeNM6gknQdH0iFA=
github.com/JulianToledano/goingecko/v3 v3.0.0-20241230130119-db5fd588cc6e/go.mod h1:yNA3hc3eMxa0nrQGknJZDTCXo/azd9gEJyPofbJ7vTw=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
22 changes: 22 additions & 0 deletions docs/examples/rateLimitClient/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package main

import (
"context"
"fmt"

"github.com/JulianToledano/goingecko/v3/api"
"github.com/JulianToledano/goingecko/v3/api/coins"
)

func main() {
cgClient := api.NewDefaultRateLimitedClient()

for i := 0; i < 100; i++ {
data, err := cgClient.CoinsId(context.Background(), "bitcoin", coins.WithTickers(false))
if err != nil {
fmt.Printf("Error: %v\n", err)
continue
}
fmt.Printf("%d | Bitcoin price is: %f$\n", i, data.MarketData.CurrentPrice.Usd)
}
}
6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ module github.com/JulianToledano/goingecko/v3

go 1.22.4

require golang.org/x/time v0.5.0

retract (
v3.0.1 // Wrong Categories endpoint.
v3.0.0 // Wrong Categories endpoint.
v3.0.1 // Wrong Categories endpoint.
)
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
Loading