Current section

Files

Jump to
tesla lib tesla.ex
Raw

lib/tesla.ex

defmodule Tesla.Env do
defstruct url: "",
method: nil,
status: nil,
headers: %{},
body: nil,
opts: [],
_client: nil,
_module: nil
end
defmodule Tesla.Error do
defexception message: nil
end
defmodule Tesla.Builder do
@http_methods [:get, :head, :delete, :trace, :options, :post, :put, :patch]
defmacro __using__(_what) do
method_defs = for method <- [:get, :head, :delete, :trace, :options] do
quote do
def unquote(method)(fun, {url, query}, opts) when is_function(fun) do
request(fun, unquote(method), {url, query}, nil, opts)
end
def unquote(method)(fun, url, opts) when is_function(fun) do
request(fun, unquote(method), {url, nil}, nil, opts)
end
def unquote(method)(fun, {url, query}) when is_function(fun) do
request(fun, unquote(method), {url, query}, nil, [])
end
def unquote(method)(fun, url) when is_function(fun) do
request(fun, unquote(method), {url, nil}, nil, [])
end
def unquote(method)({url, query}, opts) do
request(unquote(method), {url, query}, nil, opts)
end
def unquote(method)(url, opts) do
request(unquote(method), {url, nil}, nil, opts)
end
def unquote(method)({url, query}) do
request(unquote(method), {url, query}, nil, [])
end
def unquote(method)(url) do
request(unquote(method), {url, nil}, nil, [])
end
end
end
method_defs_with_body = for method <- [:post, :put, :patch] do
quote do
def unquote(method)(fun, {url, query}, body, opts) when is_function(fun) do
request(fun, unquote(method), {url, query}, body, opts)
end
def unquote(method)(fun, url, body, opts) when is_function(fun) do
request(fun, unquote(method), {url, nil}, body, opts)
end
def unquote(method)(fun, {url, query}, body) when is_function(fun) do
request(fun, unquote(method), {url, query}, body, [])
end
def unquote(method)(fun, url, body) when is_function(fun) do
request(fun, unquote(method), {url, nil}, body, nil)
end
def unquote(method)({url, query}, body, opts) do
request(unquote(method), {url, query}, body, opts)
end
def unquote(method)(url, body, opts) do
request(unquote(method), {url, nil}, body, opts)
end
def unquote(method)({url, query}, body) do
request(unquote(method), {url, query}, body, [])
end
def unquote(method)(url, body) do
request(unquote(method), {url, nil}, body, nil)
end
end
end
quote do
unquote(method_defs)
unquote(method_defs_with_body)
@adapter nil
defp request(fun, method, {url, query}, body, opts) when is_function(fun) do
env = %Tesla.Env{
method: method,
url: Tesla.Builder.append_query_string(url, query),
body: body,
opts: opts,
_client: fun,
_module: __MODULE__
}
fun.(env, &call_middleware/1)
end
defp request(method, {url, query}, body, opts) do
request(fn (env, run) -> run.(env) end, method, {url, query}, body, opts)
end
defp call_with_adapter(env) do
case call_adapter(env) do
{status, headers, body} ->
%{env | status: status, headers: headers, body: body}
{:error, reason} ->
raise Tesla.Error, message: inspect({reason, env})
e -> e
end
end
import Tesla.Builder
Module.register_attribute(__MODULE__, :middleware, accumulate: true)
@before_compile Tesla.Builder
end
end
defp generate_call_middleware(env) do
middleware = Module.get_attribute(env.module, :middleware)
reduced = Enum.reduce(middleware, (quote do: call_with_adapter(env)), fn {mid, args}, acc ->
args = Macro.escape(args)
quote do
unquote(mid).call(env, fn(env) -> unquote(acc) end, unquote(args))
end
end)
quote do
def call_middleware(env) do
unquote(reduced)
end
end
end
defp generate_call_adapter(env) do
adapter = Module.get_attribute(env.module, :adapter)
case adapter do
nil ->
quote do
def call_adapter(env) do
Tesla.call_module_adapter(nil, env)
end
end
{:fn, _, _} ->
quote do
def call_adapter(env) do
unquote(adapter).(env)
end
end
mod when is_atom(mod) ->
quote do
def call_adapter(env) do
Tesla.call_module_adapter(unquote(mod), env)
end
end
_ ->
raise "Adapter must be either Function or Module"
end
end
defmacro __before_compile__(env) do
[
generate_call_adapter(env),
generate_call_middleware(env)
]
end
defmacro adapter({:fn, _, _} = ad) do # pattern match for function
escaped = Macro.escape(ad)
quote do
@adapter unquote(escaped)
end
end
defmacro adapter(adapter) do
quote do
@adapter unquote(adapter)
end
end
defmacro plug(middleware, opts \\ []) do
quote do
@middleware {unquote(middleware), unquote(opts)}
end
end
defmacro defwrap(head, do: body) do
fun_var = Macro.var(:client_fun, __MODULE__)
head_with_client = case head do
{:when, ctx1, [{name, ctx2, args} | tl]} -> {:when, ctx1, [{name, ctx2, [fun_var | args]} | tl]}
{name, ctx, args} -> {:when, ctx, [{name, ctx, [fun_var | args]}, {:is_function, ctx, [fun_var]}]}
end
body_with_client = Macro.prewalk body, fn (ast) ->
case ast do
{fun, ctx, args} when fun in @http_methods -> {fun, ctx, [fun_var | args]}
other -> other
end
end
def_with_client = quote do
def unquote(head_with_client) do
unquote(body_with_client)
end
end
normal = quote do
def unquote(head) do
unquote(body)
end
end
[def_with_client, normal]
end
# Utility functions
def append_query_string(url, query) do
if query do
query_string = URI.encode_query(query)
if url |> String.contains?("?") do
url <> "&" <> query_string
else
url <> "?" <> query_string
end
else
url
end
end
end
defmodule Tesla do
use Tesla.Builder
@adapters [
ibrowse: Tesla.Adapter.Ibrowse,
httpc: Tesla.Adapter.Httpc,
hackney: Tesla.Adapter.Hackney
]
def build_client(stack) do
fn (env, run) ->
f = Enum.reduce(stack, run, fn ({mid, args}, acc) ->
fn (env) -> mid.call(env, fn(e) -> acc.(e) end, args) end
end)
f.(env)
end
end
def call_module_adapter(nil, env) do
call_module_adapter(default_adapter, env)
end
def call_module_adapter(mod, env) do
if Keyword.has_key?(@adapters, mod) do
@adapters[mod].call(env)
else
apply(mod, :call, [env])
end
end
def default_adapter do
Application.get_env(:tesla, :adapter) || :httpc
end
end