Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F85712819
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
14 KB
Referenced Files
None
Subscribers
None
View Options
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
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Sep 19, 12:38 PM (1 d, 23 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1769485
Default Alt Text
(14 KB)
Attached To
Mode
R28 tesla
Attached
Detach File
Event Timeline
Log In to Comment