defmodule BinanceHttp.Api do
alias BinanceHttp.Api.Endpoint
alias BinanceHttp.Http.{Request, Response, Client}
defmacro __using__(_opts \\ []) do
quote do
use BinanceHttp.Api.BehaviourDef
import BinanceHttp.Api
end
end
defmacro action(name, opts) do
{endpoint, opts} = Keyword.pop(opts, :endpoint)
{params, opts} = Keyword.pop(opts, :params)
{params_fn, opts} = Keyword.pop(opts, :params_fn)
{method, path} = check_endpoint!(endpoint)
params_ast_fun = params_ast(params)
maybe_params_fn = params_process_ast(params_fn)
execute_ast = fn(method, path) ->
quote do
BinanceHttp.Api.execute(unquote(method), unquote(path), params_transformed, auth_type, opts)
end
end
ast = quote do
BinanceHttp.Api.BehaviourDef.define(__MODULE__, unquote(name))
def unquote(name)(), do: unquote(name)(%{})
def unquote(name)(params), do: unquote(name)(params, [])
def unquote(name)(params, opts) do
with {:ok, params} <- unquote(params_ast_fun) do
opts = Keyword.merge(unquote(opts), opts)
auth_type = Keyword.get(opts, :auth_type, :none)
params_transformed = unquote(maybe_params_fn)
unquote(execute_ast.(method, path))
else
:error ->
{:error, {:primitive_type_error, params}}
{:error, reason} ->
{:error, reason}
end
end
end
if Keyword.get(opts, :dbg) do
IO.puts(Macro.to_string(ast))
end
ast
end
def execute(method, path, params, auth_type, opts) do
query =
Keyword.get(opts, :url_query, %{})
|> maybe_merge_query_params(params, method)
|> filter_params()
url = Endpoint.build(path, query, auth_type, Keyword.get(opts, :auth_secret))
body =
params
|> filter_params()
|> Request.build_body(opts)
headers = Request.build_headers([], auth_type, Keyword.get(opts, :auth_key))
case Client.request(method, url, body, headers, []) do
{:ok, body, headers} ->
{:ok, Response.prepare_response(body, headers)}
error ->
error
end
end
def __after_compile__(env, _bytecode) do
BinanceHttp.Api.BehaviourDef.define_module(env)
end
defp check_endpoint!({_method, _path} = endpoint), do: endpoint
defp check_endpoint!(invalid), do: raise "method accept {http_method, path} provided: #{inspect(invalid)}"
defp params_ast({:primitive, type}), do: primitive_type_ast(type)
defp params_ast({:%{}, _, _} = type), do: make_raw_types_ast(type)
defp params_ast(type) when is_list(type), do: make_raw_types_ast(type)
defp params_ast({:__aliases__, _, _} = type), do: make_module_ast(type)
defp params_ast(nil), do: quote(do: {:ok, params})
defp params_process_ast(nil), do: quote(do: params)
defp params_process_ast(:empty), do: quote(do: %{})
defp params_process_ast(fun), do: quote(do: unquote(fun).(params))
defp primitive_type_ast(type) do
quote do: Construct.Type.cast(unquote(type), params)
end
defp make_raw_types_ast(types) do
quote do: Construct.Cast.make(unquote(types), params)
end
defp make_module_ast(type) do
quote do: unquote(type).make(params, make_map: true)
end
defp maybe_merge_query_params(query, params, method) when method in [:get] do
merge_params(query, params)
end
defp maybe_merge_query_params(query, _, _), do: query
defp merge_params(query, params) when is_map(params) do
Map.merge(query, params)
end
defp merge_params(query, _), do: query
defp filter_params(params) when is_struct(params) do
Map.from_struct(params) |> filter_params()
end
defp filter_params(params) do
for {_k, v} = p <- params, v != nil, into: %{}, do: p
end
end