Skip to content

Commit

Permalink
update: api client (#31)
Browse files Browse the repository at this point in the history
* add: options

* update: refactored to new client structure
add: options

* add: coins endpoints
  • Loading branch information
JulianToledano authored Dec 28, 2024
1 parent ff22ecc commit 1ac956e
Show file tree
Hide file tree
Showing 27 changed files with 989 additions and 193 deletions.
62 changes: 62 additions & 0 deletions api/client/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package client

import (
"net/http"

"github.com/JulianToledano/goingecko/api"

"github.com/JulianToledano/goingecko/api/coins"
geckohttp "github.com/JulianToledano/goingecko/http"
)

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

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

// Client wraps the CoinGecko API client functionality
type Client struct {
*coins.Client

url string
}

// NewDefaultClient creates a new Client using the default HTTP client and base URL
func NewDefaultClient() *Client {
return newClient(
geckohttp.NewClient(geckohttp.WithHttpClient(http.DefaultClient)),
api.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))),
api.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))),
api.ProBaseURL,
)
}

// newClient creates a new Client with the provided HTTP client and base URL
func newClient(c *geckohttp.Client, url string) *Client {
return &Client{
Client: coins.NewCoinsClient(c, url),
}
}
18 changes: 18 additions & 0 deletions api/client/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package client

import (
"context"
"testing"
)

func TestClient_CoinsList(t *testing.T) {
c := NewDefaultClient()

got, err := c.CoinsList(context.Background())
if err != nil {
t.Fatal(err)
}
if got != nil {
t.Fatal("nil response")
}
}
22 changes: 22 additions & 0 deletions api/coins/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package coins

import (
geckohttp "github.com/JulianToledano/goingecko/http"
)

type Client struct {
*geckohttp.Client

url string
}

func NewCoinsClient(c *geckohttp.Client, url string) *Client {
return &Client{
c,
url,
}
}

func (c *Client) coinsUrl() string {
return c.url + "/coins"
}
121 changes: 121 additions & 0 deletions api/coins/id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package coins

import (
"context"
"encoding/json"
"fmt"
"net/url"
"strconv"

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

// coinsIdOption is specific to the CoinsId function
type coinsIdOption interface {
api.Option
isCoinsIdOption()
}

// WithLocalization includes localized data in the response if true.
// Default: true
func WithLocalization(localization bool) coinsIdOption { return localizationOption{localization} }

// WithTickers includes tickers data in the response if true.
// Default: true
func WithTickers(tickers bool) coinsIdOption { return tickersOption{tickers} }

// WithMarketData includes market data in the response if true.
// Default: true
func WithMarketData(marketData bool) coinsIdOption { return marketDataOption{marketData} }

// WithCommunityData includes community data in the response if true.
// Default: true
func WithCommunityData(communityData bool) coinsIdOption {
return communityDataOption{communityData}
}

// WithDeveloperData includes developer data in the response if true.
// Default: true
func WithDeveloperData(developerData bool) coinsIdOption {
return developerDataOption{developerData}
}

// WithCoinSparkline includes sparkline data in the response if true.
// Default: false
func WithCoinSparkline(sparkline bool) coinsIdOption { return coinSparklineOption{sparkline} }

// CoinsId allows you to query all the coin data of a coin (name, price, market .... including exchange tickers) on
// CoinGecko coin page based on a particular coin id.
//
// 👍 Tips
//
// You may obtain the coin id (api id) via several ways:
// refers to respective coin page and find ‘api id’
// refers to /coins/list endpoint
// refers to google sheets here
// You may also flag to include more data such as tickers, market data, community data, developer data and sparkline
// You may refer to last_updated in the endpoint response to check whether the price is stale
//
// 📘 Notes
//
// Tickers are limited to 100 items, to get more tickers, please go to /coins/{id}/tickers
// Cache/Update Frequency:
// Every 60 seconds for all the API plans
// Community data for Twitter and Telegram will be updated on weekly basis (Reddit community data is no longer supported)
func (c *Client) CoinsId(ctx context.Context, id string, options ...coinsIdOption) (*types.CoinID, error) {
params := url.Values{}

// Apply all the options
for _, opt := range options {
opt.Apply(&params)
}

rUrl := fmt.Sprintf("%s/%s?%s", c.coinsUrl(), id, params.Encode())
resp, err := c.MakeReq(ctx, rUrl)
if err != nil {
return nil, err
}

var data *types.CoinID
err = json.Unmarshal(resp, &data)
if err != nil {
return nil, err
}

return data, nil
}

// Define option types
type localizationOption struct{ localization bool }
type tickersOption struct{ tickers bool }
type marketDataOption struct{ marketData bool }
type communityDataOption struct{ communityData bool }
type developerDataOption struct{ developerData bool }
type coinSparklineOption struct{ sparkline bool }

// Implement Option interface
func (o localizationOption) Apply(v *url.Values) {
v.Set("localization", strconv.FormatBool(o.localization))
}
func (o tickersOption) Apply(v *url.Values) { v.Set("tickers", strconv.FormatBool(o.tickers)) }
func (o marketDataOption) Apply(v *url.Values) {
v.Set("market_data", strconv.FormatBool(o.marketData))
}
func (o communityDataOption) Apply(v *url.Values) {
v.Set("community_data", strconv.FormatBool(o.communityData))
}
func (o developerDataOption) Apply(v *url.Values) {
v.Set("developer_data", strconv.FormatBool(o.developerData))
}
func (o coinSparklineOption) Apply(v *url.Values) {
v.Set("sparkline", strconv.FormatBool(o.sparkline))
}

// Implement CoinsIdOption interface
func (localizationOption) isCoinsIdOption() {}
func (tickersOption) isCoinsIdOption() {}
func (marketDataOption) isCoinsIdOption() {}
func (communityDataOption) isCoinsIdOption() {}
func (developerDataOption) isCoinsIdOption() {}
func (coinSparklineOption) isCoinsIdOption() {}
72 changes: 72 additions & 0 deletions api/coins/id_history.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package coins

import (
"context"
"encoding/json"
"fmt"
"net/url"
"strconv"

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

// idHistoryOption is an interface that extends api.Option to provide
// specific options for the CoinsIdHistory endpoint. It includes a marker
// method isIdHistoryOption() to ensure type safety for history-specific options.
type idHistoryOption interface {
api.Option
isIdHistoryOption()
}

// WithLocalizationIdHistoryOption sets whether to include localized data.
// If true, returns localized data in response (name, description, etc.)
// If false, returns data in English.
func WithLocalizationIdHistoryOption(loc bool) idHistoryOption {
return localizationIdHistoryOption{loc}
}

// CoinsIdHistory allows you to query the historical data (price, market cap, 24hrs volume, etc) at a given date for a
// coin based on a particular coin id.
//
// 👍 Tips
//
// You may obtain the coin id (api id) via several ways:
// refers to respective coin page and find ‘api id’
// refers to /coins/list endpoint
// refers to google sheets here
//
// 📘 Notes
//
// The data returned is at 00:00:00 UTC
// The last completed UTC day (00:00) is available 35 minutes after midnight on the next UTC day (00:35)
func (c *Client) CoinsIdHistory(ctx context.Context, id, date string, options ...idHistoryOption) (*types.History, error) {
params := url.Values{}
params.Set("date", date)

// Apply all the options
for _, opt := range options {
opt.Apply(&params)
}

rUrl := fmt.Sprintf("%s/%s/%s?%s", c.coinsUrl(), id, "history", params.Encode())
resp, err := c.MakeReq(ctx, rUrl)
if err != nil {
return nil, err
}

var data *types.History
err = json.Unmarshal(resp, &data)
if err != nil {
return nil, err
}

return data, nil
}

type localizationIdHistoryOption struct{ localization bool }

func (o localizationIdHistoryOption) Apply(v *url.Values) {
v.Set("localization", strconv.FormatBool(o.localization))
}
func (localizationIdHistoryOption) isIdHistoryOption() {}
88 changes: 88 additions & 0 deletions api/coins/id_market_chart.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package coins

import (
"context"
"encoding/json"
"fmt"
"net/url"

"github.com/JulianToledano/goingecko/api"
"github.com/JulianToledano/goingecko/types"
)

// idMarketChartOption is an interface that extends api.Option to provide
// specific options for the CoinsIdMarketChart endpoint. It includes a marker
// method isIdMarketChartOptions() to ensure type safety for market chart-specific options.
type idMarketChartOption interface {
api.Option
isIdMarketChartOption()
}

// WithIntervalIdMarketChart sets the interval between data points in the response.
// Valid values: 5m, hourly, daily
func WithIntervalIdMarketChart(interval string) idMarketChartOption {
return intervalIdMarketChartOptions{interval}
}

// WithPrecisionIdMarketChart sets the number of decimal places in the response data.
// Valid values: from 1 to 18
func WithPrecisionIdMarketChart(precision string) idMarketChartOption {
return precisionIdMarketChartOptions{precision}
}

// CoinsIdMarketChart allows you to get the historical chart data of a coin including time in UNIX, price, market cap
// and 24hrs volume based on particular coin id.
//
// 👍Tips
//
// You may obtain the coin id (api id) via several ways:
// refers to respective coin page and find ‘api id’
// refers to /coins/list endpoint
// refers to google sheets here
// You may use tools like epoch converter to convert human readable date to UNIX timestamp
//
// 📘Notes
// You may leave the interval params as empty for automatic granularity:
// 1 day from current time = 5-minutely data
// 2 - 90 days from current time = hourly data
// above 90 days from current time = daily data (00:00 UTC)
// For non-Enterprise plan subscribers who would like to get hourly data, please leave the interval params empty for auto granularity
// The 5-minutely and hourly interval params are also exclusively available to Enterprise plan subscribers, bypassing auto-granularity:
// interval=5m: 5-minutely historical data (responses include information from the past 10 days, up until 2 days ago)
// interval=hourly: hourly historical data
(responses include information from the past 100 days, up until now)
// Cache / Update Frequency:
// every 30 seconds for all the API plans (for last data point)
// The last completed UTC day (00:00) data is available 10 minutes after midnight on the next UTC day (00:10).
func (c *Client) CoinsIdMarketChart(ctx context.Context, id, vsCurrency, days string, options ...idMarketChartOption) (*types.MarketChart, error) {
params := url.Values{}
params.Add("vs_currency", vsCurrency)
params.Add("days", days)
params.Add("interval", "daily")

for _, opt := range options {
opt.Apply(&params)
}

rUrl := fmt.Sprintf("%s/%s/%s?%s", c.coinsUrl(), id, "market_chart", params.Encode())
resp, err := c.MakeReq(ctx, rUrl)
if err != nil {
return nil, err
}

var data *types.MarketChart
err = json.Unmarshal(resp, &data)
if err != nil {
return nil, err
}

return data, nil
}

type intervalIdMarketChartOptions struct{ interval string }
type precisionIdMarketChartOptions struct{ precision string }

func (o intervalIdMarketChartOptions) Apply(v *url.Values) { v.Set("interval", o.interval) }
func (o precisionIdMarketChartOptions) Apply(v *url.Values) { v.Set("precision", o.precision) }

func (o intervalIdMarketChartOptions) isIdMarketChartOption() {}
func (o precisionIdMarketChartOptions) isIdMarketChartOption() {}
Loading

0 comments on commit 1ac956e

Please sign in to comment.