Current section
Files
Jump to
Current section
Files
lib/oxr.ex
defmodule OXR do
@moduledoc """
A thin wrapper for the Open Exchange Rates API
Requires an app id, which you can receive at https://openexchangerates.org
"""
@url "https://openexchangerates.org/api"
## endpoints
@latest "/latest.json"
@historical "/historical"
@currencies "/currencies.json"
@time_series "/time-series.json"
@convert "/convert"
@usage "/usage.json"
## constants
@date ~r/[0-9]{4}-[01][0-9]-[0-3][0-9]/
## messages
@unknown "An unknown error has occurred"
@invalid_date_format "Invalid date format\nMust match YYYY-MM-DD"
@type timestamp :: pos_integer
@type rate :: number
@type rates :: %{String.t => rate}
@type currencies :: %{String.t => String.t}
@type converted :: number
@type http_status :: 200..511
@type error_message :: String.t
@type error_description :: String.t
@doc """
Get latest exchange rates
Default base is USD
"""
@spec latest(String.t, String.t, [String.t, ...]) :: {:ok, timestamp, rates} | {:error, http_status, error_message, error_description} | {:error, error_description}
def latest(app_id, base \\ "", symbols \\ []) do
request = "#{@url}#{@latest}?app_id=#{app_id}"
# set base parameter
if base != nil && String.length(base) > 0 do
request = request <> "&base=#{base}"
end
# set symbols parameter
if symbols != nil && Enum.count(symbols) > 0 do
request = request <> "&symbols=" <> Enum.join(symbols, ",")
end
# get response
response = HTTPoison.get!(request)
body = Poison.decode!(response.body)
status = response.status_code
# return rates
cond do
status === 200 ->
{:ok, body["timestamp"], body["rates"]}
status >= 300 ->
{:error, status, body["message"], body["description"]}
true ->
{:error, @unknown}
end
end
@doc """
Get historical exchange rates for any date available
Default base is USD
"""
@spec historical(String.t, String.t, [String.t, ...]) :: {:ok, timestamp, rates} | {:error, http_status, error_message, error_description} | {:error, error_description}
def historical(app_id, date, base \\ "", symbols \\ []) do
# check for valid date format
if !Regex.match?(@date, date) do
raise @invalid_date_format
end
request = "#{@url}#{@historical}#{date}.json?app_id=#{app_id}"
# set base parameter
if base != nil && String.length(base) > 0 do
request = request <> "&base=#{base}"
end
# set symbols parameter
if symbols != nil && Enum.count(symbols) > 0 do
request = request <> "&symbols=" <> Enum.join(symbols, ",")
end
# get response
response = HTTPoison.get!(request)
body = Poison.decode!(response.body)
status = response.status_code
# return historical data
cond do
status === 200 ->
{:ok, body["timestamp"], body["rates"]}
status >= 300 ->
{:error, status, body["message"], body["description"]}
true ->
{:error, @unknown}
end
end
@doc """
Get all currencies available
"""
@spec currencies(boolean) :: {:ok, currencies} | {:error, http_status, error_message, error_description} | {:error, error_description}
def currencies(show_experimental \\ false) do
request = "#{@url}#{@currencies}"
# set show_experimental parameter
if show_experimental do
request = request <> "?show_experimental=1"
end
# get response
response = HTTPoison.get!(request)
body = Poison.decode!(response.body)
status = response.status_code
# return currencies
cond do
status === 200 ->
{:ok, body}
status >= 300 ->
{:error, status, body["message"], body["description"]}
true ->
{:error, @unknown}
end
end
@doc """
Get historical exchange rates for a given time period
Default base is USD
"""
@spec time_series(String.t, String.t, [String.t, ...], String.t, String.t) :: {:ok, rates} | {:error, http_status, error_message, error_description} | {:error, error_description}
def time_series(app_id, start, stop, base \\ "",symbols \\ []) do
# check for valid date format
if !(Regex.match?(@date, start) || Regex.match?(@date, stop)) do
raise @invalid_date_format
end
request = "#{@url}#{@time_series}?app_id=#{app_id}&start=#{start}&end=#{stop}"
# set base parameter
if base != nil && String.length(base) > 0 do
request = request <> "&base=#{base}"
end
# set symbols parameter
if symbols != nil && Enum.count(symbols) > 0 do
request = request <> "&symbols=" <> Enum.join(symbols, ",")
end
# get response
response = HTTPoison.get!(request)
body = Poison.decode!(response.body)
status = response.status_code
# return rates
cond do
status === 200 ->
{:ok, body["rates"]}
status >= 300 ->
{:error, status, body["message"], body["description"]}
true ->
{:error, @unknown}
end
end
@doc """
Convert money value from one currency to another
"""
@spec convert(String.t, number, String.t, String.t) :: {:ok, timestamp, rate, converted} | {:error, http_status, error_message, error_description} | {:error, error_description}
def convert(app_id, value, from, to) do
request = "#{@url}#{@convert}/#{value}/#{from}/#{to}?app_id=#{app_id}"
# get response
response = HTTPoison.get!(request)
body = Poison.decode!(response.body)
status = response.status_code
# return converted currency
cond do
status == 200 ->
timestamp = body["meta"]["timestamp"]
rate = body["meta"]["rate"]
converted = body["response"]
{:ok, timestamp, rate, converted}
status >= 300 ->
{:error, status, body["message"], body["description"]}
true ->
{:error, @unknown}
end
end
@doc """
Get basic plan information and usage statistics
Warning: this endpoint is still in beta
"""
@spec usage(String.t) :: {:ok, %Data{}} | {:error, http_status, String.t, String.t} | {:error, String.t}
def usage(app_id) do
request = "#{@url}#{@usage}?app_id=#{app_id}"
# get response
response = HTTPoison.get!(request)
body = response.body
status = Poison.Parser.parse!(body) |> Map.get("status")
# return usage data
cond do
status === 200 ->
{:ok, parse_usage_data(body)}
status >= 300 ->
{:error, status, body["message"], body["description"]}
true ->
{:error, @unknown}
end
end
@spec parse_usage_data(%{}) :: %Data{}
defp parse_usage_data(body) do
parsed = Poison.Parser.parse!(body) |> Map.get("data")
plan = parsed["plan"]
feat = plan["features"]
usage = parsed["usage"]
# return usage data
%Data{app_id: parsed["app_id"], status: parsed["status"],
plan: %Data.Plan{name: plan["name"], quota: plan["quota"], update_frequency: plan["update_frequency"],
features: %Data.Plan.Features{base: feat["base"], symbols: feat["symbols"], experimental: feat["experimental"], time_series: feat["time-series"], convert: feat["convert"]}},
usage: %Data.Usage{requests: usage["requests"], requests_quota: usage["requests_quota"], requests_remaining: usage["requests_remaining"], days_elapsed: usage["days_elapsed"], days_remaining: usage["days_remaining"], daily_average: usage["daily_average"]}}
end
end