-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add the Central Bank of the Republic of Uzbekistan exchange rates dat…
…a source
- Loading branch information
Showing
6 changed files
with
334 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
163 changes: 163 additions & 0 deletions
163
pkg/exchangerates/central_bank_of_uzbekistan_datasource.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
package exchangerates | ||
|
||
import ( | ||
"encoding/json" | ||
"math" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/mayswind/ezbookkeeping/pkg/core" | ||
"github.com/mayswind/ezbookkeeping/pkg/errs" | ||
"github.com/mayswind/ezbookkeeping/pkg/log" | ||
"github.com/mayswind/ezbookkeeping/pkg/models" | ||
"github.com/mayswind/ezbookkeeping/pkg/utils" | ||
"github.com/mayswind/ezbookkeeping/pkg/validators" | ||
) | ||
|
||
const centralBankOfUzbekistanExchangeRateUrl = "https://cbu.uz/ru/arkhiv-kursov-valyut/json/" | ||
const centralBankOfUzbekistanExchangeRateReferenceUrl = "https://cbu.uz/en/arkhiv-kursov-valyut/" | ||
const centralBankOfUzbekistanDataSource = "O‘zbekiston Respublikasi Markaziy banki" | ||
const centralBankOfUzbekistanBaseCurrency = "UZS" | ||
|
||
const centralBankOfUzbekistanUpdateDateFormat = "02.01.2006" | ||
const centralBankOfUzbekistanUpdateDateTimezone = "Asia/Samarkand" | ||
|
||
// CentralBankOfUzbekistanDataSource defines the structure of exchange rates data source of the central bank of the Republic of Uzbekistan | ||
type CentralBankOfUzbekistanDataSource struct { | ||
ExchangeRatesDataSource | ||
} | ||
|
||
// CentralBankOfUzbekistanExchangeRates represents the exchange rates data from the central bank of the Republic of Uzbekistan | ||
type CentralBankOfUzbekistanExchangeRates []*CentralBankOfUzbekistanExchangeRate | ||
|
||
// CentralBankOfUzbekistanExchangeRate represents the exchange rate data from the central bank of the Republic of Uzbekistan | ||
type CentralBankOfUzbekistanExchangeRate struct { | ||
Currency string `json:"Ccy"` | ||
Unit string `json:"Nominal"` | ||
Rate string `json:"Rate"` | ||
Date string `json:"Date"` | ||
} | ||
|
||
// ToLatestExchangeRateResponse returns a view-object according to original data from the central bank of the Republic of Uzbekistan | ||
func (e CentralBankOfUzbekistanExchangeRates) ToLatestExchangeRateResponse(c core.Context) *models.LatestExchangeRateResponse { | ||
if len(e) < 1 { | ||
log.Errorf(c, "[central_bank_of_uzbekistan_datasource.ToLatestExchangeRateResponse] exchange rates is empty") | ||
return nil | ||
} | ||
|
||
timezone, err := time.LoadLocation(centralBankOfUzbekistanUpdateDateTimezone) | ||
|
||
if err != nil { | ||
log.Errorf(c, "[central_bank_of_uzbekistan_datasource.ToLatestExchangeRateResponse] failed to get timezone, timezone name is %s", danmarksNationalbankDataUpdateDateTimezone) | ||
return nil | ||
} | ||
|
||
exchangeRates := make(models.LatestExchangeRateSlice, 0, len(e)) | ||
latestUpdateTime := int64(0) | ||
|
||
for i := 0; i < len(e); i++ { | ||
exchangeRate := e[i] | ||
|
||
if _, exists := validators.AllCurrencyNames[exchangeRate.Currency]; !exists { | ||
continue | ||
} | ||
|
||
updateTime, err := time.ParseInLocation(centralBankOfUzbekistanUpdateDateFormat, exchangeRate.Date, timezone) | ||
|
||
if err != nil { | ||
log.Errorf(c, "[central_bank_of_uzbekistan_datasource.ToLatestExchangeRateResponse] failed to parse update date, datetime is %s", exchangeRate.Date) | ||
return nil | ||
} | ||
|
||
if updateTime.Unix() > latestUpdateTime { | ||
latestUpdateTime = updateTime.Unix() | ||
} | ||
|
||
finalExchangeRate := exchangeRate.ToLatestExchangeRate(c) | ||
|
||
if finalExchangeRate == nil { | ||
continue | ||
} | ||
|
||
exchangeRates = append(exchangeRates, finalExchangeRate) | ||
} | ||
|
||
latestExchangeRateResp := &models.LatestExchangeRateResponse{ | ||
DataSource: centralBankOfUzbekistanDataSource, | ||
ReferenceUrl: centralBankOfUzbekistanExchangeRateReferenceUrl, | ||
UpdateTime: latestUpdateTime, | ||
BaseCurrency: centralBankOfUzbekistanBaseCurrency, | ||
ExchangeRates: exchangeRates, | ||
} | ||
|
||
return latestExchangeRateResp | ||
} | ||
|
||
// ToLatestExchangeRate returns a data pair according to original data from the central bank of the Republic of Uzbekistan | ||
func (e *CentralBankOfUzbekistanExchangeRate) ToLatestExchangeRate(c core.Context) *models.LatestExchangeRate { | ||
rate, err := utils.StringToFloat64(e.Rate) | ||
|
||
if err != nil { | ||
log.Warnf(c, "[central_bank_of_uzbekistan_datasource.ToLatestExchangeRate] failed to parse rate, currency is %s, rate is %s", e.Currency, e.Rate) | ||
return nil | ||
} | ||
|
||
if rate <= 0 { | ||
log.Warnf(c, "[central_bank_of_uzbekistan_datasource.ToLatestExchangeRate] rate is invalid, currency is %s, rate is %s", e.Currency, e.Rate) | ||
return nil | ||
} | ||
|
||
unit, err := utils.StringToFloat64(e.Unit) | ||
|
||
if err != nil { | ||
log.Warnf(c, "[central_bank_of_uzbekistan_datasource.ToLatestExchangeRate] failed to parse unit, currency is %s, unit is %s", e.Currency, e.Unit) | ||
return nil | ||
} | ||
|
||
if unit <= 0 { | ||
log.Warnf(c, "[central_bank_of_uzbekistan_datasource.ToLatestExchangeRate] unit is less or equal zero, currency is %s, unit is %s", e.Currency, e.Unit) | ||
return nil | ||
} | ||
|
||
finalRate := 1000 * unit / rate | ||
|
||
if math.IsInf(finalRate, 0) { | ||
return nil | ||
} | ||
|
||
return &models.LatestExchangeRate{ | ||
Currency: e.Currency, | ||
Rate: utils.Float64ToString(finalRate), | ||
} | ||
} | ||
|
||
// BuildRequests returns the the central bank of the Republic of Uzbekistan exchange rates http requests | ||
func (e *CentralBankOfUzbekistanDataSource) BuildRequests() ([]*http.Request, error) { | ||
req, err := http.NewRequest("GET", centralBankOfUzbekistanExchangeRateUrl, nil) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return []*http.Request{req}, nil | ||
} | ||
|
||
// Parse returns the common response entity according to the the central bank of the Republic of Uzbekistan data source raw response | ||
func (e *CentralBankOfUzbekistanDataSource) Parse(c core.Context, content []byte) (*models.LatestExchangeRateResponse, error) { | ||
centralBankOfUzbekistanData := &CentralBankOfUzbekistanExchangeRates{} | ||
err := json.Unmarshal(content, centralBankOfUzbekistanData) | ||
|
||
if err != nil { | ||
log.Errorf(c, "[central_bank_of_uzbekistan_datasource.Parse] failed to parse xml data, content is %s, because %s", string(content), err.Error()) | ||
return nil, errs.ErrFailedToRequestRemoteApi | ||
} | ||
|
||
latestExchangeRateResponse := centralBankOfUzbekistanData.ToLatestExchangeRateResponse(c) | ||
|
||
if latestExchangeRateResponse == nil { | ||
log.Errorf(c, "[central_bank_of_uzbekistan_datasource.Parse] failed to parse latest exchange rate data, content is %s", string(content)) | ||
return nil, errs.ErrFailedToRequestRemoteApi | ||
} | ||
|
||
return latestExchangeRateResponse, nil | ||
} |
145 changes: 145 additions & 0 deletions
145
pkg/exchangerates/central_bank_of_uzbekistan_datasource_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
package exchangerates | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
|
||
"github.com/mayswind/ezbookkeeping/pkg/core" | ||
"github.com/mayswind/ezbookkeeping/pkg/models" | ||
) | ||
|
||
const centralBankOfUzbekistanMinimumRequiredContent = "[\n" + | ||
" {\n" + | ||
" \"Ccy\": \"USD\",\n" + | ||
" \"Nominal\": \"1\",\n" + | ||
" \"Rate\": \"12800.13\",\n" + | ||
" \"Date\": \"15.11.2024\"\n" + | ||
" },\n" + | ||
" {\n" + | ||
" \"Ccy\": \"VND\",\n" + | ||
" \"Nominal\": \"10\",\n" + | ||
" \"Rate\": \"5.04\",\n" + | ||
" \"Date\": \"15.11.2024\"\n" + | ||
" }\n" + | ||
"]" | ||
|
||
func TestCentralBankOfUzbekistanDataSource_StandardDataExtractBaseCurrency(t *testing.T) { | ||
dataSource := &CentralBankOfUzbekistanDataSource{} | ||
context := core.NewNullContext() | ||
|
||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte(centralBankOfUzbekistanMinimumRequiredContent)) | ||
assert.Equal(t, nil, err) | ||
assert.Equal(t, "UZS", actualLatestExchangeRateResponse.BaseCurrency) | ||
} | ||
|
||
func TestCentralBankOfUzbekistanDataSource_StandardDataExtractUpdateTime(t *testing.T) { | ||
dataSource := &CentralBankOfUzbekistanDataSource{} | ||
context := core.NewNullContext() | ||
|
||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte(centralBankOfUzbekistanMinimumRequiredContent)) | ||
assert.Equal(t, nil, err) | ||
assert.Equal(t, int64(1731610800), actualLatestExchangeRateResponse.UpdateTime) | ||
} | ||
|
||
func TestCentralBankOfUzbekistanDataSource_StandardDataExtractExchangeRates(t *testing.T) { | ||
dataSource := &CentralBankOfUzbekistanDataSource{} | ||
context := core.NewNullContext() | ||
|
||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte(centralBankOfUzbekistanMinimumRequiredContent)) | ||
assert.Equal(t, nil, err) | ||
assert.Contains(t, actualLatestExchangeRateResponse.ExchangeRates, &models.LatestExchangeRate{ | ||
Currency: "USD", | ||
Rate: "0.07812420655102723", | ||
}) | ||
assert.Contains(t, actualLatestExchangeRateResponse.ExchangeRates, &models.LatestExchangeRate{ | ||
Currency: "VND", | ||
Rate: "1984.126984126984", | ||
}) | ||
} | ||
|
||
func TestCentralBankOfUzbekistanDataSource_BlankContent(t *testing.T) { | ||
dataSource := &CentralBankOfUzbekistanDataSource{} | ||
context := core.NewNullContext() | ||
|
||
_, err := dataSource.Parse(context, []byte("")) | ||
assert.NotEqual(t, nil, err) | ||
} | ||
|
||
func TestCentralBankOfUzbekistanDataSource_EmptyData(t *testing.T) { | ||
dataSource := &CentralBankOfUzbekistanDataSource{} | ||
context := core.NewNullContext() | ||
|
||
_, err := dataSource.Parse(context, []byte("[]")) | ||
assert.NotEqual(t, nil, err) | ||
} | ||
|
||
func TestCentralBankOfUzbekistanDataSource_InvalidCurrency(t *testing.T) { | ||
dataSource := &CentralBankOfUzbekistanDataSource{} | ||
context := core.NewNullContext() | ||
|
||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("[\n"+ | ||
" {\n"+ | ||
" \"Ccy\": \"XXX\",\n"+ | ||
" \"Nominal\": \"1\",\n"+ | ||
" \"Rate\": \"1\",\n"+ | ||
" \"Date\": \"15.11.2024\"\n"+ | ||
" }\n"+ | ||
"]")) | ||
assert.Equal(t, nil, err) | ||
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0) | ||
} | ||
|
||
func TestCentralBankOfUzbekistanDataSource_InvalidNominal(t *testing.T) { | ||
dataSource := &CentralBankOfUzbekistanDataSource{} | ||
context := core.NewNullContext() | ||
|
||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("[\n"+ | ||
" {\n"+ | ||
" \"Ccy\": \"USD\",\n"+ | ||
" \"Nominal\": null,\n"+ | ||
" \"Rate\": \"12800.13\",\n"+ | ||
" \"Date\": \"15.11.2024\"\n"+ | ||
" }\n"+ | ||
"]")) | ||
assert.Equal(t, nil, err) | ||
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0) | ||
|
||
actualLatestExchangeRateResponse, err = dataSource.Parse(context, []byte("[\n"+ | ||
" {\n"+ | ||
" \"Ccy\": \"USD\",\n"+ | ||
" \"Nominal\": \"0\",\n"+ | ||
" \"Rate\": \"12800.13\",\n"+ | ||
" \"Date\": \"15.11.2024\"\n"+ | ||
" }\n"+ | ||
"]")) | ||
assert.Equal(t, nil, err) | ||
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0) | ||
} | ||
|
||
func TestCentralBankOfUzbekistanDataSource_InvalidRate(t *testing.T) { | ||
dataSource := &CentralBankOfUzbekistanDataSource{} | ||
context := core.NewNullContext() | ||
|
||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("[\n"+ | ||
" {\n"+ | ||
" \"Ccy\": \"USD\",\n"+ | ||
" \"Nominal\": \"1\",\n"+ | ||
" \"Rate\": null,\n"+ | ||
" \"Date\": \"15.11.2024\"\n"+ | ||
" }\n"+ | ||
"]")) | ||
assert.Equal(t, nil, err) | ||
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0) | ||
|
||
actualLatestExchangeRateResponse, err = dataSource.Parse(context, []byte("[\n"+ | ||
" {\n"+ | ||
" \"Ccy\": \"USD\",\n"+ | ||
" \"Nominal\": \"1\",\n"+ | ||
" \"Rate\": \"0\",\n"+ | ||
" \"Date\": \"15.11.2024\"\n"+ | ||
" }\n"+ | ||
"]")) | ||
assert.Equal(t, nil, err) | ||
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters