diff --git a/README.md b/README.md new file mode 100644 index 0000000..db54903 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +## go-ydfinance + +[![Go Report Card](https://goreportcard.com/badge/github.com/pieterclaerhout/go-ydfinance)](https://goreportcard.com/report/github.com/pieterclaerhout/go-ydfinance) +[![Documentation](https://godoc.org/github.com/pieterclaerhout/go-ydfinance?status.svg)](http://godoc.org/github.com/pieterclaerhout/go-ydfinance) +[![GitHub issues](https://img.shields.io/github/issues/pieterclaerhout/go-ydfinance.svg)](https://github.com/pieterclaerhout/go-ydfinance/issues) + +This is a [Golang](https://golang.org) library which contains finance related functions. + +Currently, the only thing it supports is getting the current exchange rates from the [ECB](https://www.ecb.europa.eu). \ No newline at end of file diff --git a/exchange_rates.go b/exchange_rates.go new file mode 100644 index 0000000..8fad7fb --- /dev/null +++ b/exchange_rates.go @@ -0,0 +1,45 @@ +package ydfinance + +import ( + "encoding/xml" + "io/ioutil" + "net/http" + "time" +) + +// DefaultRatesURL defines the default URL to fetch the exchange rates from +const DefaultRatesURL = "https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml" + +// RatesURL is the URL where to fetch the rates from +var RatesURL = DefaultRatesURL + +// DefaultTimeout is the default tiemout for the HTTP client +var DefaultTimeout = 5 * time.Second + +// ExchangeRates returs the list exchange rates +func ExchangeRates() (ExchangeRate, error) { + + var rates ExchangeRate + + client := &http.Client{} + client.Timeout = DefaultTimeout + + resp, err := client.Get(RatesURL) + if err != nil { + return rates, err + } + defer resp.Body.Close() + + rawData, err := ioutil.ReadAll(resp.Body) + if err != nil { + return rates, err + } + + err = xml.Unmarshal(rawData, &rates) + if err != nil { + return rates, err + } + + return rates, nil + +} diff --git a/exchange_rates_test.go b/exchange_rates_test.go new file mode 100644 index 0000000..c669ce3 --- /dev/null +++ b/exchange_rates_test.go @@ -0,0 +1,100 @@ +package ydfinance_test + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/pieterclaerhout/go-ydfinance" +) + +func Test_ExchangeRates_Valid(t *testing.T) { + + rates, err := ydfinance.ExchangeRates() + + assert.NoErrorf(t, err, "err should be nil, is: %v", err) + assert.NotNilf(t, rates, "rates should not be nil") + assert.NotEmpty(t, rates) + +} + +func Test_ExchangeRates_InvalidURL(t *testing.T) { + + ydfinance.RatesURL = "ht&@-tp://:aa" + defer resetRatesURL() + + rates, err := ydfinance.ExchangeRates() + + assert.Error(t, err) + assert.Empty(t, rates) + +} + +func Test_ExchangeRates_Timeout(t *testing.T) { + + ydfinance.DefaultTimeout = 250 * time.Millisecond + + s := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + time.Sleep(500 * time.Millisecond) + w.Header().Set("Content-Type", "text/plain") + w.Write([]byte("hello")) + }), + ) + defer s.Close() + + ydfinance.RatesURL = s.URL + defer resetRatesURL() + + rates, err := ydfinance.ExchangeRates() + + assert.Error(t, err) + assert.Empty(t, rates) + +} + +func Test_ExchangeRates_ReadBodyError(t *testing.T) { + + s := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Length", "1") + }), + ) + defer s.Close() + + ydfinance.RatesURL = s.URL + defer resetRatesURL() + + rates, err := ydfinance.ExchangeRates() + + assert.Error(t, err) + assert.Empty(t, rates) + +} + +func Test_ExchangeRates_InvalidXML(t *testing.T) { + + s := httptest.NewServer( + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/plain") + w.Write([]byte("hello")) + }), + ) + defer s.Close() + + ydfinance.RatesURL = s.URL + defer resetRatesURL() + + rates, err := ydfinance.ExchangeRates() + + assert.Error(t, err) + assert.Empty(t, rates) + +} + +func resetRatesURL() { + ydfinance.RatesURL = ydfinance.DefaultRatesURL +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1bbaf4f --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/pieterclaerhout/go-ydfinance + +go 1.13 + +require github.com/stretchr/testify v1.4.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e863f51 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/model.go b/model.go new file mode 100644 index 0000000..fa645bc --- /dev/null +++ b/model.go @@ -0,0 +1,24 @@ +package ydfinance + +// ExchangeRate defines the exchange rate +type ExchangeRate struct { + Time string `xml:"time"` + Cubes []ExchangeRateCube `xml:"Cube"` +} + +// ExchangeRateCube defines an exchange rate cube +type ExchangeRateCube struct { + TimedCubes []ExchangeRateTimedCube `xml:"Cube"` +} + +// ExchangeRateTimedCube defines an exchange rate timed cube +type ExchangeRateTimedCube struct { + Time string `xml:"time,attr"` + Rates []ExchangeRateCurrencyCube `xml:"Cube"` +} + +// ExchangeRateCurrencyCube defines an exchange rate currency cube +type ExchangeRateCurrencyCube struct { + Currency string `xml:"currency,attr"` + Rate float64 `xml:"rate,attr"` +}