Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Currency support] Adding a debug endpoint #822

Merged
merged 1 commit into from
Mar 13, 2019
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
5 changes: 5 additions & 0 deletions currencies/constant_rates.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,8 @@ func (r *ConstantRates) GetRate(from string, to string) (float64, error) {

return 1, nil
}

// GetRates returns current rates
func (r *ConstantRates) GetRates() *map[string]map[string]float64 {
return nil
}
45 changes: 45 additions & 0 deletions currencies/converter_info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package currencies

import "time"

// ConverterInfo holds information about converter setup
type ConverterInfo interface {
Source() string
FetchingInterval() time.Duration
LastUpdated() time.Time
Rates() *map[string]map[string]float64
AdditionalInfo() interface{}
}

type converterInfo struct {
source string
fetchingInterval time.Duration
lastUpdated time.Time
rates *map[string]map[string]float64
additionalInfo interface{}
}

// Source returns converter's URL source
func (ci converterInfo) Source() string {
return ci.source
}

// FetchingInterval returns converter's fetching interval in nanoseconds
func (ci converterInfo) FetchingInterval() time.Duration {
return ci.fetchingInterval
}

// LastUpdated returns converter's last updated time
func (ci converterInfo) LastUpdated() time.Time {
return ci.lastUpdated
}

// Rates returns converter's internal rates
func (ci converterInfo) Rates() *map[string]map[string]float64 {
return ci.rates
}

// AdditionalInfo returns converter's additional infos
func (ci converterInfo) AdditionalInfo() interface{} {
return ci.additionalInfo
}
11 changes: 11 additions & 0 deletions currencies/rate_converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@ func (rc *RateConverter) Rates() Conversions {
return nil
}

// GetInfo returns setup information about the converter
func (rc *RateConverter) GetInfo() ConverterInfo {
return converterInfo{
source: rc.syncSourceURL,
fetchingInterval: rc.fetchingInterval,
lastUpdated: rc.LastUpdated(),
rates: rc.Rates().GetRates(),
}
}

type httpClient interface {
Do(req *http.Request) (*http.Response, error)
}
Expand All @@ -179,4 +189,5 @@ type httpClient interface {
// currencies, then an err is returned and rate is 0.
type Conversions interface {
GetRate(from string, to string) (float64, error)
GetRates() *map[string]map[string]float64
}
5 changes: 5 additions & 0 deletions currencies/rates.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,8 @@ func (r *Rates) GetRate(from string, to string) (float64, error) {
}
return 0, errors.New("rates are nil")
}

// GetRates returns current rates
func (r *Rates) GetRates() *map[string]map[string]float64 {
return &r.Conversions
}
7 changes: 6 additions & 1 deletion docs/developers/currency-converter.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,9 @@ This configuration can be changed:
| 1 | USD | 1 | NO | 1 | YES |
| 1 | EUR | 1.13 | YES | 1.13 | YES |
| 1 | EUR | N/A | YES | N/A | NO |
| 1 | EUR | 1.13 | NO | N/A | NO |
| 1 | EUR | 1.13 | NO | N/A | NO |

## Debug

A dedicated endpoint will allow you to see what's happening within the currency converter.
See [currency rates endpoint](../endpoints/currency_rates.md) for more details.
111 changes: 111 additions & 0 deletions docs/endpoints/currency_rates.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
## `GET /currency/rates`

This endpoint exposes active currency rate converter information in the server.
Information are:
- `info.active`: true if currency converter is active
- `info.source`: URL from which rates are fetched
- `info.fetchingIntervalNs`: Fetching interval from source in nanoseconds
- `info.lastUpdated`: Datetime when the rates where updated
- `info.rates`: Internal rates values

### Sample responses
#### Rate converter active
```json
{
"active": true,
"info": {
"source": "https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json",
"fetchingIntervalNs": 60000000000,
"lastUpdated": "2019-03-02T14:18:41.221063+01:00",
"rates": {
"GBP": {
"AUD": 1.8611576401,
"BGN": 2.2750325703,
"BRL": 5.0061650847,
"CAD": 1.7414619393,
"CHF": 1.3217708915,
"CNY": 8.8791178113,
"CZK": 29.8203982877,
"DKK": 8.6791596873,
"EUR": 1.163223525,
"GBP": 1,
"HKD": 10.3927042621,
"HRK": 8.645077238,
"HUF": 367.6484273218,
"IDR": 18689.5123766983,
"ILS": 4.8077191513,
"INR": 93.8663223525,
"ISK": 158.0820770519,
"JPY": 148.1365159129,
"KRW": 1491.3921459148,
"MXN": 25.5839382096,
"MYR": 5.394332775,
"NOK": 11.3144425833,
"NZD": 1.9374651033,
"PHP": 68.6139028476,
"PLN": 5.0130281035,
"RON": 5.5172855016,
"RUB": 87.2333891681,
"SEK": 12.2141959799,
"SGD": 1.7908989391,
"THB": 42.0074911595,
"TRY": 7.1224176438,
"USD": 1.3240973385,
"ZAR": 18.7774520752
},
"USD": {
"AUD": 1.4056048493,
"BGN": 1.7181762277,
"BRL": 3.7808134938,
"CAD": 1.3152068875,
"CHF": 0.9982429939,
"CNY": 6.705789335,
"CZK": 22.5213036985,
"DKK": 6.554774664,
"EUR": 0.8785030308,
"GBP": 0.7552314855,
"HKD": 7.8488974787,
"HRK": 6.5290345252,
"HUF": 277.6596679259,
"IDR": 14114.9081964333,
"ILS": 3.6309408767,
"INR": 70.8908020733,
"ISK": 119.3885618905,
"JPY": 111.8773609769,
"KRW": 1126.3463058948,
"MXN": 19.3217956602,
"MYR": 4.0739699552,
"NOK": 8.5450232803,
"NZD": 1.4632346482,
"PHP": 51.8193797769,
"PLN": 3.7859966617,
"RON": 4.1668277256,
"RUB": 65.8814020908,
"SEK": 9.2245453747,
"SGD": 1.3525432663,
"THB": 31.7253799526,
"TRY": 5.3790740578,
"USD": 1,
"ZAR": 14.1813230256
}
}
}
}
```

#### Rate converter set with constant rates
```json
{
"active": true,
"source": "",
"fetchingIntervalNs": 0,
"lastUpdated": "0001-01-01T00:00:00Z"
}
```

#### Rate converter not set
```json
{
"active": false
}
```
74 changes: 74 additions & 0 deletions endpoints/currency_rates.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package endpoints

import (
"encoding/json"
"net/http"
"time"

"github.com/golang/glog"
"github.com/prebid/prebid-server/currencies"
)

// currencyRatesInfo holds currency rates information.
type currencyRatesInfo struct {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Took me a while to figure out why you have two info structs. Finally realized because they are private, but in different packages.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes indeed, I introduced it in order not to have the endpoint package tied to the currency package implementation :)
It's also easier to test

Active bool `json:"active"`
Source *string `json:"source,omitempty"`
FetchingInterval *time.Duration `json:"fetchingIntervalNs,omitempty"`
LastUpdated *time.Time `json:"lastUpdated,omitempty"`
Rates *map[string]map[string]float64 `json:"rates,omitempty"`
AdditionalInfo interface{} `json:"additionalInfo,omitempty"`
}

type rateConverter interface {
GetInfo() currencies.ConverterInfo
}

// newCurrencyRatesInfo creates a new CurrencyRatesInfo instance.
func newCurrencyRatesInfo(rateConverter rateConverter) currencyRatesInfo {

currencyRatesInfo := currencyRatesInfo{
Active: false,
}

if rateConverter == nil {
return currencyRatesInfo
}

currencyRatesInfo.Active = true

infos := rateConverter.GetInfo()
if infos == nil {
return currencyRatesInfo
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it make sense for Active to be true but nothing else in the struct to be defined in this case?

}

source := infos.Source()
currencyRatesInfo.Source = &source

fetchingInterval := infos.FetchingInterval()
currencyRatesInfo.FetchingInterval = &fetchingInterval

lastUpdated := infos.LastUpdated()
currencyRatesInfo.LastUpdated = &lastUpdated

currencyRatesInfo.Rates = infos.Rates()
currencyRatesInfo.AdditionalInfo = infos.AdditionalInfo()

return currencyRatesInfo
}

// NewCurrencyRatesEndpoint returns current currency rates applied by the PBS server.
func NewCurrencyRatesEndpoint(rateConverter rateConverter) func(w http.ResponseWriter, r *http.Request) {
currencyRateInfo := newCurrencyRatesInfo(rateConverter)

return func(w http.ResponseWriter, r *http.Request) {
jsonOutput, err := json.Marshal(currencyRateInfo)
if err != nil {
glog.Errorf("/currency/rates Critical error when trying to marshal currencyRateInfo: %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
}

w.Header().Set("Content-Type", "application/json")
w.Write(jsonOutput)
}
}
Loading