Current section
Files
Jump to
Current section
Files
lib/requester.ex
defmodule EXRequester do
@moduledoc """
`EXRequester` requester main class
"""
defmacro __using__(_) do
quote do
import unquote(__MODULE__)
def client(url) do
%{url: url}
end
end
end
@doc """
Define a request function
Parameters:
* `head` - the function header AST
## Example
To define an api:
defmodule SampleAPI do
use EXRequester
@headers [
Authorization: :auth,
Key1: :key1
]
@get "/path/to/resource/{resource_id}"
defreq get_resource(resource_id: resource_id, auth: auth, key1: key1)
end
Then to call it:
SampleAPI.client("http://base_url.com")
|> SampleAPI.get_resource(resource_id: 123, auth1: "1", key1: "2")
This will hit
`http://base_url.com/path/to/resource/123`
The `Authorization` and `Key1` headers will also be set.
If you want to decode the response, pass a decoder as a parameter when calling `get_resource`
For example:
SampleAPI.client("http://base_url.com")
|> SampleAPI.get_resource(resource_id: 123, auth1: "1", key1: "2", decoder: fn response ->
"Value is " <> response.body
end)
The anonymous function passed to decoder will receive an `EXRequester.Response`. This function can parse the response and return a parsed response. The parsed response will be finally returned.
The example above returns `"Value is Content of body"`
"""
defmacro defreq(head) do
post_defreq(head)
end
def post_defreq({function_name, _, [params| _]}) do
define_functions(function_name, params)
end
def post_defreq({function_name, _, _}) do
define_functions(function_name, nil)
end
defp define_functions(function_name, params) do
params = params || []
function_params = params && Keyword.keys(params)
quote bind_quoted: [
function_name: function_name,
function_params: function_params] do
[{request_method, request_path}] = get_request_path_and_method
headers = get_request_headers
query = get_request_query
request = quote do
EXRequester.Request.new(method: unquote(request_method), path: unquote(request_path))
|> EXRequester.Request.add_headers_keys(unquote(headers))
|> EXRequester.Request.add_query_keys(unquote(query))
end
proposed_method =
EXRequest.ParamsChecker.propsed_method_invocation(func_name: function_name, url: request_path)
defintion_result = EXRequest.ParamsChecker.check_definition_params(
function_name,
function_params -- query,
request_path,
Keyword.values(headers)
)
define_function(defintion_result, function_params, function_name, proposed_method, request)
clear_attributes
end
end
@doc """
Define the function to call
Parameters:
* `defintion_result` - the function definition result
* `params` - the function parameters used
* `function` - the function name to define
* `proposed` - the proposesd function definition to use
* `request` - `EXRequester.request` to use
"""
defmacro define_function(defintion_result, params, function, proposed, request) do
quote do
case unquote(defintion_result) do
{:error, error} ->
raise ArgumentError, error
:ok ->
unquote(define_function(params, function, request))
unquote(define_catch_error_function(params, function, proposed))
unquote(define_catch_error_for_empty(function, proposed))
end
end
end
@doc """
Define the function to call
Parameters:
* `params` - the function parameters used
* `function` - the function name to define
* `request` - `EXRequester.request` to use
"""
def define_function(nil, function_name, request) do
request = Macro.escape(request)
quote bind_quoted: [
function_name: function_name,
request: request] do
def unquote(function_name)(client) do
request = unquote(request)
|> EXRequester.Request.add_base_url(client.url)
perform_request_and_parse(unquote(function_name), nil, request)
end
end
end
def define_function(_, function_name, request) do
quote bind_quoted: [function_name:
function_name,
request: request] do
def unquote(function_name)(client, params) do
request = unquote(request)
|> EXRequester.Request.add_body(params[:body])
|> EXRequester.Request.add_base_url(client.url)
perform_request_and_parse(unquote(function_name), params, request)
end
end
end
def perform_request_and_parse(function_name, params, request) do
check_called_correctly(function_name, params, request)
request_performer.do_request(request, params)
|> parse_response(params[:decoder])
end
defp request_performer do
Application.get_env(:exrequester, :request_performer, EXRequester.Performer.HTTPotion)
end
defp parse_response(response, nil), do: response
defp parse_response(response, decoder) do
decoder.(response)
end
@doc """
Check that the function was called correctly
Parameters:
* `function_name` - the function name to define
* `params` - the function parameters used
* `request` - `EXRequester.request` to use
"""
def check_called_correctly(function_name, params, request) do
params = params || []
headers_template = request.headers_template || []
case EXRequest.ParamsChecker.check_invocation_params(
function_name,
Keyword.keys(params) -- request.query_keys,
request.path,
Keyword.values(headers_template))
do
:ok -> :ok
{:error, error} ->
raise RuntimeError, error
end
end
@doc """
Define a catch error function
Parameters:
* `function_name` - the function name to define
* `proposed_function` - the proposed function to define
"""
def define_catch_error_for_empty(function_name, proposed_function) do
quote bind_quoted: [
function_name: function_name,
proposed_function: proposed_function] do
def unquote(function_name)() do
called_function = "#{unquote(function_name)}"
raise RuntimeError, exception_to_raise(called_function, unquote(proposed_function))
end
end
end
@doc """
Define a catch error function
Parameters:
* `params` - the function parameters used
* `function_name` - the function name to define
* `proposed_function` - the proposed function to define
"""
def define_catch_error_function(nil, function_name, proposed_function) do
quote bind_quoted: [
function_name: function_name,
proposed_function: proposed_function] do
def unquote(function_name)(client, other) do
called_function = "#{unquote(function_name)}(client, other...)"
raise RuntimeError, exception_to_raise(called_function, unquote(proposed_function))
end
end
end
def define_catch_error_function(_, function_name, proposed_function) do
quote bind_quoted: [
function_name: function_name,
proposed_function: proposed_function] do
def unquote(function_name)(client) do
called_function = "#{unquote(function_name)}(client)"
raise RuntimeError, exception_to_raise(called_function, unquote(proposed_function))
end
end
end
@doc """
Read the module attribute that define the request HTTP method and path defined
"""
defmacro get_request_path_and_method do
quote do
[:get, :post, :put, :delete]
|> Enum.map(fn method ->
{method, Module.get_attribute(__MODULE__, method)}
end)
|> Enum.filter(fn {_, item} -> item != nil end)
|> check_has_http_method
end
end
@doc """
Read the module attribute that defines the headers
"""
defmacro get_request_headers do
quote do
Module.get_attribute(__MODULE__, :headers) || []
end
end
@doc """
Read the module attribute that the query keys
"""
defmacro get_request_query do
quote do
Module.get_attribute(__MODULE__, :query) || []
end
end
@doc """
Exception to raise if the function was called incorrectly
Parameters:
* `called_function` - the function called
* `correct_function` - the correct function to call
"""
def exception_to_raise(called_function, correct_function) do
"You are trying to call the wrong function" <>
"\n#{called_function}" <>
"\nplease instead call:\n#{correct_function}"
end
@doc """
Ensure that the http method was defined
"""
def check_has_http_method([]) do
raise ArgumentError,
"Missing http method/url attribute\n" <>
"Example:\n" <>
" @get(\"user/{user_id}/pictures/{picture_id}\")\n" <>
" @post(\"user/{user_id}/upload_picture\")"
end
def check_has_http_method(arr), do: arr
@doc """
Return all the possible attributes to define in the method
"""
def possible_attributes do
[:get, :post, :put, :delete, :headers, :body]
end
@doc """
Clear the module attributes after defining the functions
"""
defmacro clear_attributes do
quote do
Enum.each(possible_attributes, fn attr ->
Module.delete_attribute(__MODULE__, attr)
end)
end
end
end