2017-07-15 11:20:25 +00:00
|
|
|
defmodule Vultr.Request do
|
2017-07-30 09:37:35 +00:00
|
|
|
@moduledoc false
|
2017-07-15 11:20:25 +00:00
|
|
|
|
2017-07-30 09:37:35 +00:00
|
|
|
@special_params [
|
|
|
|
:subid, :dcid, :recordid, :vpsplanid, :appid, :osid,
|
|
|
|
:isoid, :scriptid, :snapshotid, :sshkeyid, :backupid, :userid,
|
|
|
|
]
|
2017-07-15 11:20:25 +00:00
|
|
|
|
2017-10-13 10:18:43 +00:00
|
|
|
defmacro __using__(options) do
|
|
|
|
module = __CALLER__.module
|
|
|
|
|
|
|
|
Module.register_attribute(module, :__request_opts__, persist: true)
|
|
|
|
Module.put_attribute(module, :__request_opts__, options)
|
|
|
|
|
2017-07-15 11:20:25 +00:00
|
|
|
quote do
|
2017-10-13 10:18:43 +00:00
|
|
|
import Vultr.Request, only: [request: 4]
|
2017-07-15 11:20:25 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-10-13 10:18:43 +00:00
|
|
|
defmacro request(endpoint_method, endpoint_version, endpoint_path, endpoint_opts) do
|
|
|
|
request_opts = Module.get_attribute(__CALLER__.module, :__request_opts__)
|
|
|
|
base_url = Keyword.fetch!(request_opts, :base_url)
|
|
|
|
|
2017-07-15 11:20:25 +00:00
|
|
|
endpoint_params = Keyword.get(endpoint_opts, :params, {nil, nil, []}) |> normalize_params
|
2017-10-13 10:18:43 +00:00
|
|
|
endpoint_versioned_path = Path.join([endpoint_version, endpoint_path])
|
|
|
|
endpoint_url = URI.merge(base_url, endpoint_versioned_path) |> to_string
|
2017-07-15 11:20:25 +00:00
|
|
|
endpoint_description = Keyword.fetch!(endpoint_opts, :desc)
|
|
|
|
endpoint_requires_api_key = Keyword.get(endpoint_opts, :api_key, nil)
|
|
|
|
endpoint_required_access = Keyword.get(endpoint_opts, :required_access, nil)
|
|
|
|
endpoint_has_params = (length(endpoint_params) > 0)
|
|
|
|
|
2017-10-13 10:18:43 +00:00
|
|
|
common_args = [endpoint_method, endpoint_url]
|
2017-07-15 11:20:25 +00:00
|
|
|
|
2017-10-13 10:18:43 +00:00
|
|
|
func = &__MODULE__.perform_request/4
|
2017-07-30 09:37:35 +00:00
|
|
|
|
|
|
|
func_name =
|
|
|
|
endpoint_path
|
|
|
|
|> String.replace("/", "_")
|
|
|
|
|> String.to_atom
|
|
|
|
|
|
|
|
func_doc = gen_doc(
|
2017-10-13 10:18:43 +00:00
|
|
|
endpoint_method, endpoint_versioned_path,
|
2017-07-30 09:37:35 +00:00
|
|
|
endpoint_description, endpoint_params,
|
|
|
|
endpoint_required_access, endpoint_requires_api_key
|
|
|
|
)
|
|
|
|
|
|
|
|
func_body =
|
2017-07-15 11:20:25 +00:00
|
|
|
cond do
|
|
|
|
endpoint_requires_api_key && endpoint_has_params ->
|
2017-07-30 09:37:35 +00:00
|
|
|
quote do
|
2017-10-13 10:18:43 +00:00
|
|
|
def unquote(func_name)(api_key, params \\ []) when is_list(params) do
|
|
|
|
unquote(func).(unquote_splicing(common_args), api_key, params)
|
2017-07-30 09:37:35 +00:00
|
|
|
end
|
|
|
|
end
|
2017-07-15 11:20:25 +00:00
|
|
|
endpoint_requires_api_key ->
|
2017-07-30 09:37:35 +00:00
|
|
|
quote do
|
2017-10-13 10:18:43 +00:00
|
|
|
def unquote(func_name)(api_key) do
|
|
|
|
unquote(func).(unquote_splicing(common_args), api_key, [])
|
2017-07-30 09:37:35 +00:00
|
|
|
end
|
|
|
|
end
|
2017-07-15 11:20:25 +00:00
|
|
|
endpoint_has_params ->
|
2017-07-30 09:37:35 +00:00
|
|
|
quote do
|
|
|
|
def unquote(func_name)(params \\ []) when is_list(params) do
|
|
|
|
unquote(func).(unquote_splicing(common_args), nil, params)
|
|
|
|
end
|
|
|
|
end
|
2017-07-15 11:20:25 +00:00
|
|
|
true ->
|
2017-07-30 09:37:35 +00:00
|
|
|
quote do
|
|
|
|
def unquote(func_name)() do
|
|
|
|
unquote(func).(unquote_splicing(common_args), nil, [])
|
|
|
|
end
|
|
|
|
end
|
2017-07-15 11:20:25 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
quote do
|
2017-07-30 09:37:35 +00:00
|
|
|
@doc unquote(func_doc)
|
|
|
|
unquote(func_body)
|
2017-07-15 11:20:25 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-10-13 10:18:43 +00:00
|
|
|
def perform_request(method, url, api_key, params) do
|
|
|
|
headers = prepare_api_key_header(api_key)
|
|
|
|
opts = [headers: headers] ++ prepare_params(method, params)
|
|
|
|
resp = HTTPotion.request(method, url, opts)
|
|
|
|
parsed_body = parse_body(resp)
|
|
|
|
|
|
|
|
case resp.status_code do
|
|
|
|
200 -> {:ok, parsed_body}
|
|
|
|
400 -> {:error, :invalid_api_location, parsed_body}
|
|
|
|
403 -> {:error, :invalid_api_key, parsed_body}
|
|
|
|
405 -> {:error, :invalid_http_method, parsed_body}
|
|
|
|
412 -> {:error, :bad_request, parsed_body}
|
|
|
|
500 -> {:error, :server_error, parsed_body}
|
|
|
|
503 -> {:error, :rate_limit, parsed_body}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
defp parse_body(%HTTPotion.Response{ body: body, headers: headers }) do
|
|
|
|
case HTTPotion.Headers.fetch(headers, "content-type") do
|
|
|
|
{:ok, "application/json"} ->
|
|
|
|
Poison.decode!(body)
|
|
|
|
_ ->
|
|
|
|
body
|
2017-07-30 09:37:35 +00:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-10-13 10:18:43 +00:00
|
|
|
defp prepare_api_key_header(nil), do: []
|
|
|
|
defp prepare_api_key_header(api_key), do: ["API-Key": api_key]
|
|
|
|
|
|
|
|
defp prepare_params(:get, params), do: [query: prepare_query(params)]
|
|
|
|
defp prepare_params(_, params), do: [body: prepare_body(params)]
|
|
|
|
|
|
|
|
defp prepare_body(nil), do: ""
|
|
|
|
defp prepare_body(params), do: capitalize_special_params(params) |> Enum.into(%{}) |> Poison.encode!
|
|
|
|
|
|
|
|
defp prepare_query(nil), do: false
|
|
|
|
defp prepare_query(params), do: capitalize_special_params(params)
|
2017-07-30 09:37:35 +00:00
|
|
|
|
|
|
|
defp capitalize_special_params(params) do
|
|
|
|
Enum.map(params, fn {k, v} ->
|
|
|
|
is_special_param =
|
|
|
|
Enum.any?(@special_params, fn special_param ->
|
|
|
|
special_param == k
|
|
|
|
end)
|
|
|
|
|
|
|
|
if is_special_param do
|
2017-07-30 09:53:23 +00:00
|
|
|
{k |> Atom.to_string |> String.upcase, v}
|
2017-07-30 09:37:35 +00:00
|
|
|
else
|
|
|
|
{k, v}
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
end
|
2017-07-15 11:20:25 +00:00
|
|
|
|
|
|
|
defp normalize_param({name, opts}) do
|
|
|
|
type = Keyword.fetch!(opts, :type)
|
|
|
|
%{
|
|
|
|
name: name,
|
|
|
|
default: Keyword.get(opts, :default, nil),
|
|
|
|
name_atom: (name |> String.downcase |> String.to_atom),
|
|
|
|
optional: Keyword.get(opts, :optional, false),
|
|
|
|
type: type,
|
|
|
|
type_string: (type |> Atom.to_string),
|
|
|
|
desc: Keyword.fetch!(opts, :desc),
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
defp normalize_params({_, _, []}), do: []
|
|
|
|
defp normalize_params({_, _, params}), do: params |> Enum.map(&normalize_param/1)
|
|
|
|
|
|
|
|
##################################################
|
|
|
|
# Documentation helpers
|
|
|
|
|
2017-07-30 09:37:35 +00:00
|
|
|
defp gen_doc(method, path, desc, params, required_access, api_key) do
|
2017-07-15 11:20:25 +00:00
|
|
|
"""
|
2017-10-13 10:18:43 +00:00
|
|
|
#{desc}
|
|
|
|
#{doc_params(params)}
|
2017-07-15 11:20:25 +00:00
|
|
|
### Backend
|
|
|
|
- Method: `#{doc_method(method)}`
|
2017-10-13 10:18:43 +00:00
|
|
|
- Path: `#{path}`
|
2017-07-15 11:20:25 +00:00
|
|
|
- API Key: `#{doc_api_key(api_key)}`
|
|
|
|
- Required Access: `#{doc_required_access(required_access)}`
|
|
|
|
"""
|
|
|
|
end
|
|
|
|
|
2017-10-13 10:18:43 +00:00
|
|
|
defp doc_params([]), do: ""
|
|
|
|
defp doc_params(params) do
|
|
|
|
param_rows =
|
|
|
|
params
|
|
|
|
|> Enum.map(&doc_param/1)
|
|
|
|
|> Enum.join("")
|
|
|
|
|
|
|
|
"""
|
|
|
|
### Params
|
|
|
|
| Name | Type | Optional | Description |
|
|
|
|
| ---- | ---- | -------- | ----------- |
|
|
|
|
#{param_rows}
|
|
|
|
"""
|
|
|
|
end
|
2017-07-15 11:20:25 +00:00
|
|
|
|
2017-10-13 10:18:43 +00:00
|
|
|
defp doc_param(param) do
|
|
|
|
columns = [
|
|
|
|
"`#{String.downcase(param.name)}`",
|
|
|
|
param.type_string,
|
|
|
|
doc_optional_default(param.optional, param.default),
|
|
|
|
String.replace(param.desc, "\n", "<br>"),
|
|
|
|
]
|
|
|
|
|
|
|
|
"| #{Enum.join(columns, " | ")} |"
|
|
|
|
end
|
2017-07-15 11:20:25 +00:00
|
|
|
|
|
|
|
defp doc_optional_default(optional, default) do
|
|
|
|
if optional do
|
|
|
|
optional = "Yes"
|
|
|
|
|
|
|
|
if default == nil do
|
|
|
|
optional
|
|
|
|
else
|
|
|
|
optional <> "<br>(Default `#{inspect default}`)"
|
|
|
|
end
|
|
|
|
else
|
|
|
|
"No"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-10-13 10:18:43 +00:00
|
|
|
defp doc_method(method), do: method |> Atom.to_string |> String.upcase
|
2017-07-30 09:37:35 +00:00
|
|
|
|
|
|
|
defp doc_api_key(nil), do: "No"
|
|
|
|
defp doc_api_key(atm), do: atom_to_word(atm)
|
|
|
|
|
|
|
|
defp doc_required_access(nil), do: "None"
|
|
|
|
defp doc_required_access(atm), do: Atom.to_string(atm)
|
|
|
|
|
|
|
|
defp atom_to_word(atm), do: atm |> Atom.to_string |> String.capitalize
|
|
|
|
end
|