Page MenuHomePhorge

No OneTemporary

Size
14 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/tesla.ex b/lib/tesla.ex
index 3a2c990..b8ed9b0 100644
--- a/lib/tesla.ex
+++ b/lib/tesla.ex
@@ -1,292 +1,284 @@
defmodule Tesla.Env do
defstruct url: "",
method: nil,
status: nil,
headers: %{},
body: nil,
opts: [],
_client: nil,
_module: 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
- # 4 args
- def unquote(method)(fun, url, query, opts) when is_function(fun) and is_map(query) do
- request(fun, unquote(method), url, query, nil, opts)
- end
-
- # 3 args
- def unquote(method)(fun, url, query) when is_function(fun) and is_map(query) do
- request(fun, unquote(method), url, query, nil, [])
+ 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)
+ request(fun, unquote(method), {url, nil}, nil, opts)
end
- def unquote(method)(url, query, opts) when is_map(query) and is_map(query) do
- request(unquote(method), url, query, nil, opts)
+ def unquote(method)(fun, {url, query}) when is_function(fun) do
+ request(fun, unquote(method), {url, query}, nil, [])
end
- # 2 args
def unquote(method)(fun, url) when is_function(fun) do
- request(fun, unquote(method), url, nil, nil, [])
+ request(fun, unquote(method), {url, nil}, nil, [])
end
- def unquote(method)(url, query) when is_map(query) do
- request(unquote(method), url, query, nil, [])
+ 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)
+ request(unquote(method), {url, nil}, nil, opts)
+ end
+
+ def unquote(method)({url, query}) do
+ request(unquote(method), {url, query}, nil, [])
end
- # 1 args
def unquote(method)(url) do
- request(unquote(method), url, nil, nil, [])
+ request(unquote(method), {url, nil}, nil, [])
end
end
end
method_defs_with_body = for method <- [:post, :put, :patch] do
quote do
- # 5 args
- def unquote(method)(fun, url, query, body, opts) when is_function(fun) and is_map(query) do
- request(fun, unquote(method), url, query, body, opts)
- end
-
- # 4 args
- def unquote(method)(fun, url, query, body) when is_function(fun) and is_map(query) do
- request(fun, unquote(method), url, query, body, [])
+ 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)
+ request(fun, unquote(method), {url, nil}, body, opts)
end
- def unquote(method)(url, query, body, opts) when is_map(query) do
- request(unquote(method), url, query, body, opts)
+ def unquote(method)(fun, {url, query}, body) when is_function(fun) do
+ request(fun, unquote(method), {url, query}, body, [])
end
- # 3 args
def unquote(method)(fun, url, body) when is_function(fun) do
- request(fun, unquote(method), url, nil, body, [])
+ request(fun, unquote(method), {url, nil}, body, nil)
end
- def unquote(method)(url, query, body) when is_map(query) do
- request(unquote(method), url, query, body, [])
+ 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)
+ request(unquote(method), {url, nil}, body, opts)
+ end
+
+ def unquote(method)({url, query}, body) do
+ request(unquote(method), {url, query}, body, [])
end
- # 2 args
def unquote(method)(url, body) do
- request(unquote(method), url, nil, body, [])
+ 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
+ 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)
+ 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}
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
diff --git a/test/tesla_test.exs b/test/tesla_test.exs
index 470c9d5..34cfa43 100644
--- a/test/tesla_test.exs
+++ b/test/tesla_test.exs
@@ -1,217 +1,221 @@
defmodule TeslaTest do
use ExUnit.Case
defmodule ClientWithAdapterFun do
use Tesla.Builder
adapter fn (_) ->
{201, %{}, "function adapter"}
end
end
defmodule ModuleAdapter do
def call(env) do
%{env | status: 202}
end
end
defmodule ClientWithAdapterMod do
use Tesla.Builder
adapter ModuleAdapter
end
test "client with adapter as function" do
assert ClientWithAdapterFun.get("/").status == 201
end
test "client with adapter as module" do
assert ClientWithAdapterMod.get("/").status == 202
end
defmodule Client do
use Tesla.Builder
adapter fn (_) ->
{200, %{'Content-Type' => 'text/plain'}, "hello"}
end
end
test "return :status 200" do
assert Client.get("/").status == 200
end
test "return content type header" do
assert Client.get("/").headers == %{'Content-Type' => 'text/plain'}
end
test "return 'hello' as body" do
assert Client.get("/").body == "hello"
end
test "GET request" do
assert Client.get("/").method == :get
end
test "HEAD request" do
assert Client.head("/").method == :head
end
test "POST request" do
assert Client.post("/", "").method == :post
end
test "PUT request" do
assert Client.put("/", "").method == :put
end
test "PATCH request" do
assert Client.patch("/", "").method == :patch
end
test "DELETE request" do
assert Client.delete("/").method == :delete
end
test "TRACE request" do
assert Client.trace("/").method == :trace
end
test "OPTIONS request" do
assert Client.options("/").method == :options
end
test "path + query" do
- assert Client.get("/foo", %{a: 1, b: "foo"}).url == "/foo?a=1&b=foo"
+ assert Client.get({"/foo", %{a: 1, b: "foo"}}).url == "/foo?a=1&b=foo"
end
test "path with query + query" do
- assert Client.get("/foo?c=4", %{a: 1, b: "foo"}).url == "/foo?c=4&a=1&b=foo"
+ assert Client.get({"/foo?c=4", %{a: 1, b: "foo"}}).url == "/foo?c=4&a=1&b=foo"
+ end
+
+ test "path + query with multiple values" do
+ assert Client.get({"/foo", [a: 1, b: "foo", b: "bar"]}).url == "/foo?a=1&b=foo&b=bar"
end
test "insert request middleware function at runtime" do
fun = fn (env, run) ->
run.(%{env | url: env.url <> ".json"})
end
res = fun |> Client.get("/foo")
assert res.url == "/foo.json"
end
test "insert response middleware function at runtime" do
fun = fn (env, run) ->
env = run.(env)
%{env | body: env.body <> env.body}
end
res = fun |> Client.get("/foo")
assert res.body == "hellohello"
end
end
defmodule MiddlewareTest do
use ExUnit.Case
defmodule Client do
use Tesla.Builder
plug Tesla.Middleware.BaseUrl, "http://example.com"
adapter fn (env) ->
cond do
List.last(String.split(env.url, "/")) == "secret" ->
{200, %{}, env.headers['Authorization']}
true ->
{200, %{'Content-Type' => 'text/plain'}, "hello"}
end
end
def new(token) do
Tesla.build_client [
{Tesla.Middleware.Headers, %{'Authorization' => "token: " <> token }}
]
end
end
test "make use of base url" do
assert Client.get("/").url == "http://example.com/"
end
test "build client" do
c = Client.new("xxyyzz")
res = c |> Client.get("/secret")
assert res.body == "token: xxyyzz"
end
end
defmodule ClientWrapTest do
use ExUnit.Case
defmodule Client do
use Tesla.Builder
adapter fn (_env) ->
{200, %{'Content-Type' => 'text/plain'}, "hello"}
end
defwrap custom_simple_get(a,b,c) do
get("/#{a}/#{b}/#{c}")
end
defwrap custom_complex_get_head(a,b) do
c = "#{b}-#{a}"
head("/#{a}-#{b}").url <> " | " <> get("/#{c}").url
end
# defwrap custom_post_with_defaults(opts \\ []) do
# post("/" <> opts[:arg], "nothing")
# end
def new(token) do
Tesla.build_client [
{Tesla.Middleware.BaseUrl, "http://example.com"},
{Tesla.Middleware.Headers, %{"Authorization" => "token #{token}"}}
]
end
end
test "custom_simple_get - static" do
env = Client.custom_simple_get("x","y","z")
assert env.url == "/x/y/z"
assert env.method == :get
end
test "custom_simple_get - dynamic" do
client = Client.new("abc")
env = client |> Client.custom_simple_get("x","y","z")
assert env.url == "http://example.com/x/y/z"
assert env.method == :get
end
test "custom_complex_get_head - static" do
res = Client.custom_complex_get_head("x","y")
assert res == "/x-y | /y-x"
end
test "custom_complex_get_head - dynamic" do
client = Client.new("abc")
res = client |> Client.custom_complex_get_head("x","y")
assert res == "http://example.com/x-y | http://example.com/y-x"
end
# test "custom_post_with_defaults - static" do
# env = Client.custom_post_with_defaults(arg: "foo")
# assert env.url == "/foo"
# assert env.method == :post
# end
# test "custom_post_with_defaults - dynamic" do
# client = Client.new("abc")
# env = client |> Client.custom_post_with_defaults(arg: "foo")
# assert env.url == "http://example.com/foo"
# assert env.method == :post
# end
end

File Metadata

Mime Type
text/x-diff
Expires
Sat, Sep 19, 12:38 PM (1 d, 22 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1769485
Default Alt Text
(14 KB)

Event Timeline