-
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 Bank of Israel exchange rates data source
- Loading branch information
Showing
5 changed files
with
321 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
package exchangerates | ||
|
||
import ( | ||
"encoding/xml" | ||
"math" | ||
"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 bankOfIsraelExchangeRateUrl = "https://boi.org.il/PublicApi/GetExchangeRates?asXml=true" | ||
const bankOfIsraelExchangeRateReferenceUrl = "https://www.boi.org.il/en/economic-roles/financial-markets/exchange-rates/" | ||
const bankOfIsraelDataSource = "Bank of Israel" | ||
const bankOfIsraelBaseCurrency = "ILS" | ||
|
||
const bankOfIsraelDataUpdateDateFormat = "2006-01-02T15:04:05.9999999Z" | ||
|
||
// BankOfIsraelDataSource defines the structure of exchange rates data source of bank of Israel | ||
type BankOfIsraelDataSource struct { | ||
ExchangeRatesDataSource | ||
} | ||
|
||
// bankOfIsraelExchangeRateData represents the whole data from bank of Israel | ||
type bankOfIsraelExchangeRateData struct { | ||
XMLName xml.Name `xml:"ExchangeRatesResponseCollectioDTO"` | ||
AllExchangeRates []*bankOfIsraelExchangeRate `xml:"ExchangeRates>ExchangeRateResponseDTO"` | ||
} | ||
|
||
// bankOfIsraelExchangeRate represents the exchange rate data from bank of Israel | ||
type bankOfIsraelExchangeRate struct { | ||
Currency string `xml:"Key"` | ||
Rate string `xml:"CurrentExchangeRate"` | ||
LastUpdate string `xml:"LastUpdate"` | ||
Unit string `xml:"Unit"` | ||
} | ||
|
||
// ToLatestExchangeRateResponse returns a view-object according to original data from bank of Israel | ||
func (e *bankOfIsraelExchangeRateData) ToLatestExchangeRateResponse(c core.Context) *models.LatestExchangeRateResponse { | ||
if len(e.AllExchangeRates) < 1 { | ||
log.Errorf(c, "[bank_of_israel_datasource.ToLatestExchangeRateResponse] all exchange rates is empty") | ||
return nil | ||
} | ||
|
||
latestUpdateDate := "" | ||
exchangeRates := make(models.LatestExchangeRateSlice, 0, len(e.AllExchangeRates)) | ||
|
||
for i := 0; i < len(e.AllExchangeRates); i++ { | ||
exchangeRate := e.AllExchangeRates[i] | ||
|
||
if latestUpdateDate == "" { | ||
latestUpdateDate = exchangeRate.LastUpdate | ||
} | ||
|
||
if _, exists := validators.AllCurrencyNames[exchangeRate.Currency]; !exists { | ||
continue | ||
} | ||
|
||
rate, err := utils.StringToFloat64(exchangeRate.Rate) | ||
|
||
if err != nil { | ||
log.Warnf(c, "[bank_of_israel_datasource.ToLatestExchangeRateResponse] failed to parse rate, rate is %s", exchangeRate.Rate) | ||
continue | ||
} | ||
|
||
if rate <= 0 { | ||
log.Warnf(c, "[bank_of_israel_datasource.ToLatestExchangeRateResponse] rate is invalid, rate is %s", exchangeRate.Rate) | ||
continue | ||
} | ||
|
||
unit, err := utils.StringToFloat64(exchangeRate.Unit) | ||
|
||
if err != nil { | ||
log.Warnf(c, "[bank_of_israel_datasource.ToLatestExchangeRateResponse] failed to parse unit, unit is %s", exchangeRate.Unit) | ||
continue | ||
} | ||
|
||
finalRate := unit / rate | ||
|
||
if math.IsInf(finalRate, 0) { | ||
continue | ||
} | ||
|
||
exchangeRates = append(exchangeRates, &models.LatestExchangeRate{ | ||
Currency: exchangeRate.Currency, | ||
Rate: utils.Float64ToString(finalRate), | ||
}) | ||
} | ||
|
||
updateTime, err := time.Parse(bankOfIsraelDataUpdateDateFormat, latestUpdateDate) | ||
|
||
if err != nil { | ||
log.Errorf(c, "[bank_of_israel_datasource.ToLatestExchangeRateResponse] failed to parse update date, datetime is %s", latestUpdateDate) | ||
return nil | ||
} | ||
|
||
latestExchangeRateResp := &models.LatestExchangeRateResponse{ | ||
DataSource: bankOfIsraelDataSource, | ||
ReferenceUrl: bankOfIsraelExchangeRateReferenceUrl, | ||
UpdateTime: updateTime.Unix(), | ||
BaseCurrency: bankOfIsraelBaseCurrency, | ||
ExchangeRates: exchangeRates, | ||
} | ||
|
||
return latestExchangeRateResp | ||
} | ||
|
||
// ToLatestExchangeRate returns a data pair according to original data from bank of Israel | ||
func (e *bankOfIsraelExchangeRate) ToLatestExchangeRate() *models.LatestExchangeRate { | ||
return &models.LatestExchangeRate{ | ||
Currency: e.Currency, | ||
Rate: e.Rate, | ||
} | ||
} | ||
|
||
// GetRequestUrls returns the bank of Israel data source urls | ||
func (e *BankOfIsraelDataSource) GetRequestUrls() []string { | ||
return []string{bankOfIsraelExchangeRateUrl} | ||
} | ||
|
||
// Parse returns the common response entity according to the bank of Israel data source raw response | ||
func (e *BankOfIsraelDataSource) Parse(c core.Context, content []byte) (*models.LatestExchangeRateResponse, error) { | ||
bankOfIsraelData := &bankOfIsraelExchangeRateData{} | ||
err := xml.Unmarshal(content, bankOfIsraelData) | ||
|
||
if err != nil { | ||
log.Errorf(c, "[bank_of_israel_datasource.Parse] failed to parse xml data, content is %s, because %s", string(content), err.Error()) | ||
return nil, errs.ErrFailedToRequestRemoteApi | ||
} | ||
|
||
latestExchangeRateResponse := bankOfIsraelData.ToLatestExchangeRateResponse(c) | ||
|
||
if latestExchangeRateResponse == nil { | ||
log.Errorf(c, "[bank_of_israel_datasource.Parse] failed to parse latest exchange rate data, content is %s", string(content)) | ||
return nil, errs.ErrFailedToRequestRemoteApi | ||
} | ||
|
||
return latestExchangeRateResponse, nil | ||
} |
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,171 @@ | ||
package exchangerates | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
|
||
"github.com/mayswind/ezbookkeeping/pkg/core" | ||
"github.com/mayswind/ezbookkeeping/pkg/models" | ||
) | ||
|
||
const bankOfIsraelMinimumRequiredContent = "" + | ||
"<ExchangeRatesResponseCollectioDTO xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://schemas.datacontract.org/2004/07/BOI.Core.Models.HotData\">\n" + | ||
" <ExchangeRates>\n" + | ||
" <ExchangeRateResponseDTO>\n" + | ||
" <CurrentExchangeRate>3.733</CurrentExchangeRate>\n" + | ||
" <Key>USD</Key>\n" + | ||
" <LastUpdate>2024-11-11T13:26:05.6590204Z</LastUpdate>\n" + | ||
" <Unit>1</Unit>\n" + | ||
" </ExchangeRateResponseDTO>\n" + | ||
" <ExchangeRateResponseDTO>\n" + | ||
" <CurrentExchangeRate>2.4287</CurrentExchangeRate>\n" + | ||
" <Key>JPY</Key>\n" + | ||
" <LastUpdate>2024-11-11T13:26:05.6590204Z</LastUpdate>\n" + | ||
" <Unit>100</Unit>\n" + | ||
" </ExchangeRateResponseDTO>\n" + | ||
" </ExchangeRates>\n" + | ||
"</ExchangeRatesResponseCollectioDTO>" | ||
|
||
func TestBankOfIsraelDataSource_StandardDataExtractBaseCurrency(t *testing.T) { | ||
dataSource := &BankOfIsraelDataSource{} | ||
context := core.NewNullContext() | ||
|
||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte(bankOfIsraelMinimumRequiredContent)) | ||
assert.Equal(t, nil, err) | ||
assert.Equal(t, "ILS", actualLatestExchangeRateResponse.BaseCurrency) | ||
} | ||
|
||
func TestBankOfIsraelDataSource_StandardDataExtractExchangeRates(t *testing.T) { | ||
dataSource := &BankOfIsraelDataSource{} | ||
context := core.NewNullContext() | ||
|
||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte(bankOfIsraelMinimumRequiredContent)) | ||
assert.Equal(t, nil, err) | ||
assert.Contains(t, actualLatestExchangeRateResponse.ExchangeRates, &models.LatestExchangeRate{ | ||
Currency: "USD", | ||
Rate: "0.2678810608090008", | ||
}) | ||
assert.Contains(t, actualLatestExchangeRateResponse.ExchangeRates, &models.LatestExchangeRate{ | ||
Currency: "JPY", | ||
Rate: "41.17429077284144", | ||
}) | ||
} | ||
|
||
func TestBankOfIsraelDataSource_BlankContent(t *testing.T) { | ||
dataSource := &BankOfIsraelDataSource{} | ||
context := core.NewNullContext() | ||
|
||
_, err := dataSource.Parse(context, []byte("")) | ||
assert.NotEqual(t, nil, err) | ||
} | ||
|
||
func TestBankOfIsraelDataSource_EmptyExchangeRatesResponseCollectioDTO(t *testing.T) { | ||
dataSource := &BankOfIsraelDataSource{} | ||
context := core.NewNullContext() | ||
|
||
_, err := dataSource.Parse(context, []byte("<ExchangeRatesResponseCollectioDTO xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://schemas.datacontract.org/2004/07/BOI.Core.Models.HotData\">\n"+ | ||
"</ExchangeRatesResponseCollectioDTO>")) | ||
assert.NotEqual(t, nil, err) | ||
} | ||
|
||
func TestBankOfIsraelDataSource_InvalidCurrency(t *testing.T) { | ||
dataSource := &BankOfIsraelDataSource{} | ||
context := core.NewNullContext() | ||
|
||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("<ExchangeRatesResponseCollectioDTO xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://schemas.datacontract.org/2004/07/BOI.Core.Models.HotData\">\n"+ | ||
" <ExchangeRates>\n"+ | ||
" <ExchangeRateResponseDTO>\n"+ | ||
" <CurrentExchangeRate>1</CurrentExchangeRate>\n"+ | ||
" <Key>XXX</Key>\n"+ | ||
" <LastUpdate>2024-11-11T13:26:05.6590204Z</LastUpdate>\n"+ | ||
" <Unit>1</Unit>\n"+ | ||
" </ExchangeRateResponseDTO>\n"+ | ||
" </ExchangeRates>\n"+ | ||
"</ExchangeRatesResponseCollectioDTO>")) | ||
assert.Equal(t, nil, err) | ||
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0) | ||
} | ||
|
||
func TestBankOfIsraelDataSource_EmptyRate(t *testing.T) { | ||
dataSource := &BankOfIsraelDataSource{} | ||
context := core.NewNullContext() | ||
|
||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("<ExchangeRatesResponseCollectioDTO xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://schemas.datacontract.org/2004/07/BOI.Core.Models.HotData\">\n"+ | ||
" <ExchangeRates>\n"+ | ||
" <ExchangeRateResponseDTO>\n"+ | ||
" <Key>USD</Key>\n"+ | ||
" <LastUpdate>2024-11-11T13:26:05.6590204Z</LastUpdate>\n"+ | ||
" <Unit>1</Unit>\n"+ | ||
" </ExchangeRateResponseDTO>\n"+ | ||
" </ExchangeRates>\n"+ | ||
"</ExchangeRatesResponseCollectioDTO>")) | ||
assert.Equal(t, nil, err) | ||
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0) | ||
} | ||
|
||
func TestBankOfIsraelDataSource_InvalidRate(t *testing.T) { | ||
dataSource := &BankOfIsraelDataSource{} | ||
context := core.NewNullContext() | ||
|
||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("<ExchangeRatesResponseCollectioDTO xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://schemas.datacontract.org/2004/07/BOI.Core.Models.HotData\">\n"+ | ||
" <ExchangeRates>\n"+ | ||
" <ExchangeRateResponseDTO>\n"+ | ||
" <CurrentExchangeRate>null</CurrentExchangeRate>\n"+ | ||
" <Key>USD</Key>\n"+ | ||
" <LastUpdate>2024-11-11T13:26:05.6590204Z</LastUpdate>\n"+ | ||
" <Unit>1</Unit>\n"+ | ||
" </ExchangeRateResponseDTO>\n"+ | ||
" </ExchangeRates>\n"+ | ||
"</ExchangeRatesResponseCollectioDTO>")) | ||
assert.Equal(t, nil, err) | ||
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0) | ||
|
||
actualLatestExchangeRateResponse, err = dataSource.Parse(context, []byte("<ExchangeRatesResponseCollectioDTO xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://schemas.datacontract.org/2004/07/BOI.Core.Models.HotData\">\n"+ | ||
" <ExchangeRates>\n"+ | ||
" <ExchangeRateResponseDTO>\n"+ | ||
" <CurrentExchangeRate>0</CurrentExchangeRate>\n"+ | ||
" <Key>USD</Key>\n"+ | ||
" <LastUpdate>2024-11-11T13:26:05.6590204Z</LastUpdate>\n"+ | ||
" <Unit>1</Unit>\n"+ | ||
" </ExchangeRateResponseDTO>\n"+ | ||
" </ExchangeRates>\n"+ | ||
"</ExchangeRatesResponseCollectioDTO>")) | ||
assert.Equal(t, nil, err) | ||
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0) | ||
} | ||
|
||
func TestBankOfIsraelDataSource_EmptyUnit(t *testing.T) { | ||
dataSource := &BankOfIsraelDataSource{} | ||
context := core.NewNullContext() | ||
|
||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("<ExchangeRatesResponseCollectioDTO xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://schemas.datacontract.org/2004/07/BOI.Core.Models.HotData\">\n"+ | ||
" <ExchangeRates>\n"+ | ||
" <ExchangeRateResponseDTO>\n"+ | ||
" <CurrentExchangeRate>1</CurrentExchangeRate>\n"+ | ||
" <Key>USD</Key>\n"+ | ||
" <LastUpdate>2024-11-11T13:26:05.6590204Z</LastUpdate>\n"+ | ||
" </ExchangeRateResponseDTO>\n"+ | ||
" </ExchangeRates>\n"+ | ||
"</ExchangeRatesResponseCollectioDTO>")) | ||
assert.Equal(t, nil, err) | ||
assert.Len(t, actualLatestExchangeRateResponse.ExchangeRates, 0) | ||
} | ||
|
||
func TestBankOfIsraelDataSource_InvalidUnit(t *testing.T) { | ||
dataSource := &BankOfIsraelDataSource{} | ||
context := core.NewNullContext() | ||
|
||
actualLatestExchangeRateResponse, err := dataSource.Parse(context, []byte("<ExchangeRatesResponseCollectioDTO xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns=\"http://schemas.datacontract.org/2004/07/BOI.Core.Models.HotData\">\n"+ | ||
" <ExchangeRates>\n"+ | ||
" <ExchangeRateResponseDTO>\n"+ | ||
" <CurrentExchangeRate>1</CurrentExchangeRate>\n"+ | ||
" <Key>USD</Key>\n"+ | ||
" <LastUpdate>2024-11-11T13:26:05.6590204Z</LastUpdate>\n"+ | ||
" <Unit>null</Unit>\n"+ | ||
" </ExchangeRateResponseDTO>\n"+ | ||
" </ExchangeRates>\n"+ | ||
"</ExchangeRatesResponseCollectioDTO>")) | ||
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