Files
ezbookkeeping/pkg/exchangerates/central_bank_of_hungary_datasource.go
T
2025-05-24 23:27:09 +08:00

209 lines
8.2 KiB
Go

package exchangerates
import (
"bytes"
"encoding/xml"
"math"
"net/http"
"strings"
"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 centralBankOfHungaryExchangeRateServiceUrl = "http://www.mnb.hu/arfolyamok.asmx"
const centralBankOfHungaryExchangeRateServiceCurrentExchangeRatesSoapAction = "http://www.mnb.hu/webservices/MNBArfolyamServiceSoap/GetCurrentExchangeRates"
const centralBankOfHungaryExchangeRateReferenceUrl = "https://www.mnb.hu/en/arfolyamok"
const centralBankOfHungaryDataSource = "Magyar Nemzeti Bank"
const centralBankOfHungaryBaseCurrency = "HUF"
const centralBankOfHungaryUpdateDateFormat = "2006-01-02 15"
const centralBankOfHungaryUpdateDateTimezone = "Europe/Budapest"
// CentralBankOfHungaryDataSource defines the structure of exchange rates data source of central bank of Hungary
type CentralBankOfHungaryDataSource struct {
HttpExchangeRatesDataSource
}
// CentralBankOfHungaryExchangeRateServiceResponse represents the response data of exchange rate service for central bank of Hungary
type CentralBankOfHungaryExchangeRateServiceResponse struct {
XMLName xml.Name `xml:"Envelope"`
GetCurrentExchangeRatesResult string `xml:"Body>GetCurrentExchangeRatesResponse>GetCurrentExchangeRatesResult"`
}
// CentralBankOfHungaryCurrentExchangeRatesResult represents the current exchange rate result data from central bank of Hungary
type CentralBankOfHungaryCurrentExchangeRatesResult struct {
XMLName xml.Name `xml:"MNBCurrentExchangeRates"`
AllExchangeRates []*CentralBankOfHungaryExchangeRates `xml:"Day"`
}
// CentralBankOfHungaryExchangeRates represents the exchange rates data from Danmarks Nationalbank
type CentralBankOfHungaryExchangeRates struct {
Date string `xml:"date,attr"`
ExchangeRates []*CentralBankOfHungaryExchangeRate `xml:"Rate"`
}
// CentralBankOfHungaryExchangeRate represents the exchange rate data from central bank of Hungary
type CentralBankOfHungaryExchangeRate struct {
Currency string `xml:"curr,attr"`
Unit string `xml:"unit,attr"`
Rate string `xml:",chardata"`
}
// ToLatestExchangeRateResponse returns a view-object according to original data from central bank of Hungary
func (e *CentralBankOfHungaryCurrentExchangeRatesResult) ToLatestExchangeRateResponse(c core.Context) *models.LatestExchangeRateResponse {
if len(e.AllExchangeRates) < 1 {
log.Errorf(c, "[central_bank_of_hungary_datasource.ToLatestExchangeRateResponse] all exchange rates is empty")
return nil
}
latestCentralBankOfHungaryExchangeRate := e.AllExchangeRates[0]
if len(latestCentralBankOfHungaryExchangeRate.ExchangeRates) < 1 {
log.Errorf(c, "[central_bank_of_hungary_datasource.ToLatestExchangeRateResponse] exchange rates is empty")
return nil
}
exchangeRates := make(models.LatestExchangeRateSlice, 0, len(e.AllExchangeRates))
for i := 0; i < len(latestCentralBankOfHungaryExchangeRate.ExchangeRates); i++ {
exchangeRate := latestCentralBankOfHungaryExchangeRate.ExchangeRates[i]
if _, exists := validators.AllCurrencyNames[exchangeRate.Currency]; !exists {
continue
}
finalExchangeRate := exchangeRate.ToLatestExchangeRate(c)
if finalExchangeRate == nil {
continue
}
exchangeRates = append(exchangeRates, finalExchangeRate)
}
timezone, err := time.LoadLocation(centralBankOfHungaryUpdateDateTimezone)
if err != nil {
log.Errorf(c, "[central_bank_of_hungary_datasource.ToLatestExchangeRateResponse] failed to get timezone, timezone name is %s", centralBankOfHungaryUpdateDateTimezone)
return nil
}
updateDateTime := latestCentralBankOfHungaryExchangeRate.Date + " 11" // The exchange rates are fixed at 11 am.
updateTime, err := time.ParseInLocation(centralBankOfHungaryUpdateDateFormat, updateDateTime, timezone)
if err != nil {
log.Errorf(c, "[central_bank_of_hungary_datasource.ToLatestExchangeRateResponse] failed to parse update date, datetime is %s", updateDateTime)
return nil
}
latestExchangeRateResp := &models.LatestExchangeRateResponse{
DataSource: centralBankOfHungaryDataSource,
ReferenceUrl: centralBankOfHungaryExchangeRateReferenceUrl,
UpdateTime: updateTime.Unix(),
BaseCurrency: centralBankOfHungaryBaseCurrency,
ExchangeRates: exchangeRates,
}
return latestExchangeRateResp
}
// ToLatestExchangeRate returns a data pair according to original data from central bank of Hungary
func (e *CentralBankOfHungaryExchangeRate) ToLatestExchangeRate(c core.Context) *models.LatestExchangeRate {
rate, err := utils.StringToFloat64(strings.ReplaceAll(e.Rate, ",", "."))
if err != nil {
log.Warnf(c, "[central_bank_of_hungary_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_hungary_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_hungary_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_hungary_datasource.ToLatestExchangeRate] unit is less or equal zero, currency is %s, unit is %s", e.Currency, e.Unit)
return nil
}
finalRate := unit / rate
if math.IsInf(finalRate, 0) {
return nil
}
return &models.LatestExchangeRate{
Currency: e.Currency,
Rate: utils.Float64ToString(finalRate),
}
}
// BuildRequests returns the central bank of Hungary exchange rates http requests
func (e *CentralBankOfHungaryDataSource) BuildRequests() ([]*http.Request, error) {
req, err := http.NewRequest("POST", centralBankOfHungaryExchangeRateServiceUrl, bytes.NewReader([]byte(
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">"+
"<s:Body>"+
"<GetCurrentExchangeRates xmlns=\"http://www.mnb.hu/webservices/\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\"/>"+
"</s:Body>"+
"</s:Envelope>")))
if err != nil {
return nil, err
}
req.Header.Set("Content-Type", "text/xml; charset=utf-8")
req.Header.Set("SOAPAction", centralBankOfHungaryExchangeRateServiceCurrentExchangeRatesSoapAction)
return []*http.Request{req}, nil
}
// Parse returns the common response entity according to the central bank of Hungary data source raw response
func (e *CentralBankOfHungaryDataSource) Parse(c core.Context, content []byte) (*models.LatestExchangeRateResponse, error) {
responseXmlDecoder := xml.NewDecoder(bytes.NewReader(content))
centralBankOfHungaryServiceResponse := &CentralBankOfHungaryExchangeRateServiceResponse{}
err := responseXmlDecoder.Decode(centralBankOfHungaryServiceResponse)
if err != nil {
log.Errorf(c, "[central_bank_of_hungary_datasource.Parse] failed to parse service response xml data, content is %s, because %s", string(content), err.Error())
return nil, errs.ErrFailedToRequestRemoteApi
}
if len(centralBankOfHungaryServiceResponse.GetCurrentExchangeRatesResult) < 1 {
log.Errorf(c, "[central_bank_of_hungary_datasource.Parse] exchange rates response is empty")
return nil, errs.ErrFailedToRequestRemoteApi
}
resultXmlDecoder := xml.NewDecoder(strings.NewReader(centralBankOfHungaryServiceResponse.GetCurrentExchangeRatesResult))
centralBankOfHungaryExchangeRatesResult := &CentralBankOfHungaryCurrentExchangeRatesResult{}
err = resultXmlDecoder.Decode(centralBankOfHungaryExchangeRatesResult)
if err != nil {
log.Errorf(c, "[central_bank_of_hungary_datasource.Parse] failed to parse exchange rates response xml data, content is %s, because %s", string(content), err.Error())
return nil, errs.ErrFailedToRequestRemoteApi
}
latestExchangeRateResponse := centralBankOfHungaryExchangeRatesResult.ToLatestExchangeRateResponse(c)
if latestExchangeRateResponse == nil {
log.Errorf(c, "[central_bank_of_hungary_datasource.Parse] failed to parse latest exchange rate data, content is %s", string(content))
return nil, errs.ErrFailedToRequestRemoteApi
}
return latestExchangeRateResponse, nil
}