Current section
Files
Jump to
Current section
Files
lib/simplehttp.ex
defmodule SimpleHttp do
alias SimpleHttp.Request
alias SimpleHttp.Response
alias SimpleHttp.Exception.BadArgument
defmacro __using__(_opts) do
quote do
import SimpleHttp
end
end
@doc """
Create virtual methods such as:
SimpleHttp.get(...)
SimpleHttp.post(...)
SimpleHttp.delete(...)
SimpleHttp.put(...)
SimpleHttp.put(...)
"""
@methods ["get", "post", "delete", "put", "options"]
Enum.each(@methods, fn method ->
def unquote(:"#{method}")(url, args \\ []) do
request(String.to_atom(unquote(method)), url, args)
end
end)
@spec request(atom(), String.t(), keyword()) :: {:error, any()} | {:ok, SimpleHttp.Response.t()}
def request(method, url, args \\ []) do
request = create_request(method, url, args)
execute(request)
end
@spec execute(SimpleHttp.Request.t()) :: {:error, any()} | {:ok, SimpleHttp.Response.t()}
defp execute(%Request{} = request) do
httpc_response = apply(:httpc, :request, params_for_httpc(request))
case httpc_response do
{:ok, {{_, status, _}, headers, body}} ->
response = %Response{
status: status,
headers: headers,
body: cast_body(body)
}
{:ok, response}
{:error, error} ->
{:error, error}
end
end
defp cast_body(body) when is_binary(body), do: body
defp cast_body(body) when is_list(body), do: to_string(body)
defp cast_body(_body), do: raise(BadArgument)
defp params_for_httpc(%Request{} = request) do
base_params = {request.url, request.headers}
params =
case request.body do
nil ->
base_params
_ ->
base_params
|> Tuple.append(request.content_type)
|> Tuple.append(request.body)
end
[
request.method,
params,
request.http_options,
request.options
]
end
defp create_request(method, url, args) do
%Request{}
|> add_method_to_request(method)
|> add_url_to_request(url, args)
|> add_headers_to_request(args)
|> add_http_options_to_request(args)
|> add_body_or_params_to_request(args)
|> debug?(args)
end
defp add_method_to_request(%Request{} = request, method), do: %{request | method: method}
defp add_url_to_request(%Request{} = request, url, args) do
url =
if String.valid?(url) do
query_params = Keyword.get(args, :query_params)
if is_map(query_params) || Keyword.keyword?(query_params) do
(url <> "?" <> URI.encode_query(query_params)) |> to_charlist
else
url |> to_charlist
end
else
raise BadArgument
end
%{request | url: url}
end
defp add_headers_to_request(%Request{} = request, args) do
content_type_key = "Content-Type"
content_type = args[:headers][content_type_key]
headers =
args[:headers][content_type_key]
|> pop_in
|> elem(1)
|> Keyword.get(:headers, %{})
|> Map.to_list()
|> Enum.map(fn {x, y} ->
{to_charlist(x), to_charlist(y)}
end)
request =
case String.valid?(content_type) do
true ->
%{request | content_type: to_charlist(content_type)}
false ->
request
end
case Enum.empty?(headers) do
true ->
request
false ->
%{request | headers: headers}
end
end
defp add_http_options_to_request(%Request{} = request, args) do
keys = [:timeout, :connect_timeout, :autoredirect]
http_options =
Enum.reduce(keys, [], fn key, acc ->
case args[key] do
nil -> acc
value -> acc ++ [{key, value}]
end
end)
%{request | http_options: http_options}
end
defp add_body_or_params_to_request(%Request{} = request, args) do
with body <- Keyword.get(args, :body, nil),
params <- Keyword.get(args, :params, nil) do
if body do
%{request | body: body}
else
query =
if params do
params
|> URI.encode_query()
|> to_charlist
else
nil
end
%{request | body: query}
end
end
end
defp debug?(%Request{} = request, args) do
case Keyword.get(args, :debug) do
nil -> request
_ -> IO.inspect(request)
end
end
end