Page MenuHomePhorge

No OneTemporary

Size
29 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/tesla.ex b/lib/tesla.ex
index 7980d36..35ebee7 100644
--- a/lib/tesla.ex
+++ b/lib/tesla.ex
@@ -1,215 +1,233 @@
defmodule Tesla.Error do
defexception message: "", reason: nil
end
defmodule Tesla.Env do
@type client :: Tesla.Client.t() | (t, stack -> t)
@type method :: :head | :get | :delete | :trace | :options | :post | :put | :patch
@type url :: binary
@type param :: binary | [{binary | atom, param}]
@type query :: [{binary | atom, param}]
@type headers :: %{binary => binary}
#
@type body :: any
@type status :: integer
@type opts :: [any]
@type __module__ :: atom
@type __client__ :: function
@type stack :: [{atom, atom, any} | {atom, atom} | {:fn, (t -> t)} | {:fn, (t, stack -> t)}]
@type t :: %__MODULE__{
method: method,
query: query,
url: url,
headers: headers,
body: body,
status: status,
opts: opts,
__module__: __module__,
__client__: __client__
}
defstruct method: nil,
url: "",
query: [],
headers: [],
body: nil,
status: nil,
opts: [],
__module__: nil,
__client__: nil
end
defmodule Tesla.Client do
@type t :: %__MODULE__{
fun: (Tesla.Env.t(), Tesla.Env.stack() -> Tesla.Env.t()) | nil,
pre: Tesla.Env.stack(),
post: Tesla.Env.stack()
}
defstruct fun: nil,
pre: [],
post: []
end
defmodule Tesla.Middleware do
@callback call(env :: Tesla.Env.t(), next :: Tesla.Env.stack(), options :: any) :: Tesla.Env.t()
end
defmodule Tesla.Adapter do
@callback call(env :: Tesla.Env.t(), options :: any) :: Tesla.Env.t()
end
defmodule Tesla do
use Tesla.Builder
+ @default_adapter Tesla.Adapter.Httpc
+
@moduledoc """
A HTTP toolkit for building API clients using middlewares
Include Tesla module in your api client:
```ex
defmodule ExampleApi do
use Tesla
plug Tesla.Middleware.BaseUrl, "http://api.example.com"
plug Tesla.Middleware.JSON
end
"""
defmacro __using__(opts \\ []) do
quote do
use Tesla.Builder, unquote(opts)
end
end
- def perform_request(module, client \\ nil, options) do
- %{fun: fun, pre: pre, post: post} = client || %Tesla.Client{}
-
- stack = pre ++ wrapfun(fun) ++ module.__middleware__ ++ post ++ [module.__adapter__]
-
+ @doc false
+ def execute(module, %{fun: fun, pre: pre, post: post} = client, options) do
env = struct(Tesla.Env, options ++ [__module__: module, __client__: client])
+ stack = pre ++ wrapfun(fun) ++ module.__middleware__ ++ post ++ [effective_adapter(module)]
run(env, stack)
end
defp wrapfun(nil), do: []
defp wrapfun(fun), do: [{:fn, fun}]
+ @doc false
+ def effective_adapter(module) do
+ with nil <- adapter_per_module_from_config(module),
+ nil <- adapter_per_module(module),
+ nil <- adapter_from_config() do
+ adapter_default()
+ end
+ end
+
+ defp adapter_per_module_from_config(module) do
+ case Application.get_env(:tesla, module, [])[:adapter] do
+ nil -> nil
+ {adapter, opts} -> {adapter, :call, [opts]}
+ adapter -> {adapter, :call, [[]]}
+ end
+ end
+
+ defp adapter_per_module(module) do
+ module.__adapter__
+ end
+
+ defp adapter_from_config do
+ case Application.get_env(:tesla, :adapter) do
+ nil -> nil
+ {adapter, opts} -> {adapter, :call, [opts]}
+ adapter -> {adapter, :call, [[]]}
+ end
+ end
+
+ defp adapter_default do
+ {@default_adapter, :call, [[]]}
+ end
+
+ def run_default_adapter(env, opts \\ []) do
+ apply(@default_adapter, :call, [env, opts])
+ end
+
+
# empty stack case is useful for reusing/testing middlewares (just pass [] as next)
def run(env, []), do: env
# last item in stack is adapter - skip passing rest of stack
def run(env, [{:fn, f}]), do: apply(f, [env])
def run(env, [{m, f, a}]), do: apply(m, f, [env | a])
# for all other elements pass (env, next, opts)
def run(env, [{:fn, f} | rest]), do: apply(f, [env, rest])
def run(env, [{m, f, a} | rest]), do: apply(m, f, [env, rest | a])
# useful helper fuctions
def put_opt(env, key, value) do
Map.update!(env, :opts, &Keyword.put(&1, key, value))
end
@spec get_header(Tesla.Env.t, binary) :: binary | nil
def get_header(%Tesla.Env{headers: headers}, key) when is_list(headers) do
case List.keyfind(headers, key, 0) do
{_, value} -> value
_ -> nil
end
end
@spec get_headers(Tesla.Env.t, binary) :: [binary]
def get_headers(%Tesla.Env{headers: headers}, key) do
for {k,v} <- headers, k == key, do: v
end
@spec put_header(Tesla.Env.t, binary, binary) :: Tesla.Env.t
def put_header(env, key, value) do
headers = List.keystore(env.headers, key, 0, {key, value})
%{env | headers: headers}
end
@spec put_headers(Tesla.Env.t, [{binary, binary}]) :: Tesla.Env.t
def put_headers(env, list) when is_list(list) do
%{env | headers: env.headers ++ list}
end
@spec delete_header(Tesla.Env.t, binary) :: Tesla.Env.t
def delete_header(env, key) do
headers = for {k,v} <- env.headers, k != key, do: {k,v}
%{env | headers: headers}
end
- def adapter(module, custom) do
- cond do
- mod = module_adapter_from_config(module) -> {mod, :call, [[]]}
- custom -> custom
- true -> {default_adapter(), :call, [[]]}
- end
- end
-
- defp module_adapter_from_config(module) do
- Application.get_env(:tesla, module, [])[:adapter]
- end
-
- def default_adapter do
- Application.get_env(:tesla, :adapter, Tesla.Adapter.Httpc)
- end
-
- def run_default_adapter(env, opts \\ []) do
- apply(default_adapter(), :call, [env, opts])
- end
-
@doc """
Dynamically build client from list of middlewares.
```ex
defmodule ExampleAPI do
use Tesla
def new(token) do
Tesla.build_client([
{Tesla.Middleware.Headers, %{"Authorization" => token}}
])
end
end
client = ExampleAPI.new(token: "abc")
client |> ExampleAPI.get("/me")
```
"""
defmacro build_client(pre, post \\ []) do
quote do
require Tesla.Builder
Tesla.Builder.client(unquote(pre), unquote(post))
end
end
def build_adapter(fun) do
%Tesla.Client{post: [{:fn, fn env, _next -> fun.(env) end}]}
end
def build_url(url, []), do: url
def build_url(url, query) do
join = if String.contains?(url, "?"), do: "&", else: "?"
url <> join <> encode_query(query)
end
defp encode_query(query) do
query
|> Enum.flat_map(&encode_pair/1)
|> URI.encode_query()
end
defp encode_pair({key, value}) when is_list(value) do
if Keyword.keyword?(value) do
Enum.flat_map(value, fn {k, v} -> encode_pair({"#{key}[#{k}]", v}) end)
else
Enum.map(value, fn e -> {"#{key}[]", e} end)
end
end
defp encode_pair({key, value}), do: [{key, value}]
end
diff --git a/lib/tesla/builder.ex b/lib/tesla/builder.ex
index 9de8b7f..9ed7c7b 100644
--- a/lib/tesla/builder.ex
+++ b/lib/tesla/builder.ex
@@ -1,331 +1,331 @@
defmodule Tesla.Builder do
@http_verbs ~w(head get delete trace options post put patch)a
defmacro __using__(opts \\ []) do
opts = Macro.prewalk(opts, &Macro.expand(&1, __CALLER__))
docs = Keyword.get(opts, :docs, true)
quote do
Module.register_attribute(__MODULE__, :__middleware__, accumulate: true)
Module.register_attribute(__MODULE__, :__adapter__, [])
if unquote(docs) do
@type option ::
{:method, Tesla.Env.method()}
| {:url, Tesla.Env.url()}
| {:query, Tesla.Env.query()}
| {:headers, Tesla.Env.headers()}
| {:body, Tesla.Env.body()}
| {:opts, Tesla.Env.opts()}
@doc """
Perform a request using client function
Options:
- `:method` - the request method, one of [:head, :get, :delete, :trace, :options, :post, :put, :patch]
- `:url` - either full url e.g. "http://example.com/some/path" or just "/some/path" if using `Tesla.Middleware.BaseUrl`
- `:query` - a keyword list of query params, e.g. `[page: 1, per_page: 100]`
- `:headers` - a keyworld list of headers, e.g. `[{"content-type", "text/plain"}]`
- `:body` - depends on used middleware:
- by default it can be a binary
- if using e.g. JSON encoding middleware it can be a nested map
- if adapter supports it it can be a Stream with any of the above
- `:opts` - custom, per-request middleware or adapter options
Examples:
ExampleApi.request(method: :get, url: "/users/path")
You can also use shortcut methods like:
ExampleApi.get("/users/1")
or
myclient |> ExampleApi.post("/users", %{name: "Jon"})
"""
@spec request(Tesla.Env.client(), [option]) :: Tesla.Env.t()
else
@doc false
end
def request(%Tesla.Client{} = client, options) do
- Tesla.perform_request(__MODULE__, client, options)
+ Tesla.execute(__MODULE__, client, options)
end
if unquote(docs) do
@doc """
Perform a request. See `request/2` for available options.
"""
@spec request([option]) :: Tesla.Env.t()
else
@doc false
end
def request(options) do
- Tesla.perform_request(__MODULE__, options)
+ Tesla.execute(__MODULE__, %Tesla.Client{}, options)
end
unquote(generate_http_verbs(opts))
import Tesla.Builder, only: [plug: 1, plug: 2, adapter: 1, adapter: 2]
@before_compile Tesla.Builder
end
end
@doc """
Attach middleware to your API client
```ex
defmodule ExampleApi do
use Tesla
# plug middleware module with options
plug Tesla.Middleware.BaseUrl, "http://api.example.com"
plug Tesla.Middleware.JSON, engine: Poison
# plug middleware function
plug :handle_errors
# middleware function gets two parameters: Tesla.Env and the rest of middleware call stack
# and must return Tesla.Env
def handle_errors(env, next) do
env
|> modify_env_before_request
|> Tesla.run(next) # run the rest of stack
|> modify_env_after_request
end
end
"""
defmacro plug(middleware, opts \\ nil) do
quote do: @__middleware__ {unquote(Macro.escape(middleware)), unquote(Macro.escape(opts))}
end
@doc """
Choose adapter for your API client
```ex
defmodule ExampleApi do
use Tesla
# set adapter as module
adapter Tesla.Adapter.Hackney
# set adapter as function
adapter :local_adapter
# set adapter as anonymous function
adapter fn env ->
...
env
end
# adapter function gets Tesla.Env as parameter and must return Tesla.Env
def local_adapter(env) do
...
env
end
end
"""
defmacro adapter(adapter, opts \\ nil) do
quote do: @__adapter__ {unquote(Macro.escape(adapter)), unquote(Macro.escape(opts))}
end
defp generate_http_verbs(opts) do
only = Keyword.get(opts, :only, @http_verbs)
except = Keyword.get(opts, :except, [])
@http_verbs
|> Enum.filter(&(&1 in only && not &1 in except))
|> Enum.map(&generate_api(&1, Keyword.get(opts, :docs, true)))
end
defp generate_api(method, docs) when method in [:post, :put, :patch] do
quote do
if unquote(docs) do
@doc """
Perform a #{unquote(method |> to_string |> String.upcase())} request.
See `request/1` or `request/2` for options definition.
Example
myclient |> ExampleApi.#{unquote(method)}("/users", %{name: "Jon"}, query: [scope: "admin"])
"""
@spec unquote(method)(Tesla.Env.client(), Tesla.Env.url(), Tesla.Env.body(), [option]) ::
Tesla.Env.t()
else
@doc false
end
def unquote(method)(%Tesla.Client{} = client, url, body, options) when is_list(options) do
request(client, [method: unquote(method), url: url, body: body] ++ options)
end
# fallback to keep backward compatibility
def unquote(method)(fun, url, body, options) when is_function(fun) and is_list(options) do
unquote(method)(%Tesla.Client{fun: fun}, url, body, options)
end
if unquote(docs) do
@doc """
Perform a #{unquote(method |> to_string |> String.upcase())} request.
See `request/1` or `request/2` for options definition.
Example
myclient |> ExampleApi.#{unquote(method)}("/users", %{name: "Jon"})
ExampleApi.#{unquote(method)}("/users", %{name: "Jon"}, query: [scope: "admin"])
"""
@spec unquote(method)(Tesla.Env.client(), Tesla.Env.url(), Tesla.Env.body()) ::
Tesla.Env.t()
else
@doc false
end
def unquote(method)(%Tesla.Client{} = client, url, body) do
request(client, method: unquote(method), url: url, body: body)
end
# fallback to keep backward compatibility
def unquote(method)(fun, url, body) when is_function(fun) do
unquote(method)(%Tesla.Client{fun: fun}, url, body)
end
if unquote(docs) do
@spec unquote(method)(Tesla.Env.url(), Tesla.Env.body(), [option]) :: Tesla.Env.t()
else
@doc false
end
def unquote(method)(url, body, options) when is_list(options) do
request([method: unquote(method), url: url, body: body] ++ options)
end
if unquote(docs) do
@doc """
Perform a #{unquote(method |> to_string |> String.upcase())} request.
See `request/1` or `request/2` for options definition.
Example
ExampleApi.#{unquote(method)}("/users", %{name: "Jon"})
"""
@spec unquote(method)(Tesla.Env.url(), Tesla.Env.body()) :: Tesla.Env.t()
else
@doc false
end
def unquote(method)(url, body) do
request(method: unquote(method), url: url, body: body)
end
end
end
defp generate_api(method, docs) when method in [:head, :get, :delete, :trace, :options] do
quote do
if unquote(docs) do
@doc """
Perform a #{unquote(method |> to_string |> String.upcase())} request.
See `request/1` or `request/2` for options definition.
Example
myclient |> ExampleApi.#{unquote(method)}("/users", query: [page: 1])
"""
@spec unquote(method)(Tesla.Env.client(), Tesla.Env.url(), [option]) :: Tesla.Env.t()
else
@doc false
end
def unquote(method)(%Tesla.Client{} = client, url, options) when is_list(options) do
request(client, [method: unquote(method), url: url] ++ options)
end
# fallback to keep backward compatibility
def unquote(method)(fun, url, options) when is_function(fun) and is_list(options) do
unquote(method)(%Tesla.Client{fun: fun}, url, options)
end
if unquote(docs) do
@doc """
Perform a #{unquote(method |> to_string |> String.upcase())} request.
See `request/1` or `request/2` for options definition.
Example
myclient |> ExampleApi.#{unquote(method)}("/users")
ExampleApi.#{unquote(method)}("/users", query: [page: 1])
"""
@spec unquote(method)(Tesla.Env.client(), Tesla.Env.url()) :: Tesla.Env.t()
else
@doc false
end
def unquote(method)(%Tesla.Client{} = client, url) do
request(client, method: unquote(method), url: url)
end
# fallback to keep backward compatibility
def unquote(method)(fun, url) when is_function(fun) do
unquote(method)(%Tesla.Client{fun: fun}, url)
end
if unquote(docs) do
@spec unquote(method)(Tesla.Env.url(), [option]) :: Tesla.Env.t()
else
@doc false
end
def unquote(method)(url, options) when is_list(options) do
request([method: unquote(method), url: url] ++ options)
end
if unquote(docs) do
@doc """
Perform a #{unquote(method |> to_string |> String.upcase())} request.
See `request/1` or `request/2` for options definition.
Example
ExampleApi.#{unquote(method)}("/users")
"""
@spec unquote(method)(Tesla.Env.url()) :: Tesla.Env.t()
else
@doc false
end
def unquote(method)(url) do
request(method: unquote(method), url: url)
end
end
end
defmacro __before_compile__(env) do
adapter =
env.module
|> Module.get_attribute(:__adapter__)
|> prepare()
middleware =
env.module
|> Module.get_attribute(:__middleware__)
|> Enum.reverse()
|> prepare()
quote do
def __middleware__, do: unquote(middleware)
- def __adapter__, do: Tesla.adapter(__MODULE__, unquote(adapter))
+ def __adapter__, do: unquote(adapter)
end
end
defmacro client(pre, post) do
quote do
%Tesla.Client{
pre: unquote(prepare(pre)),
post: unquote(prepare(post))
}
end
end
defp prepare(list) when is_list(list), do: Enum.map(list, &prepare/1)
defp prepare(nil), do: nil
defp prepare({{:fn, _, _} = fun, nil}), do: {:fn, fun}
defp prepare({{:__aliases__, _, _} = name, opts}), do: quote do: {unquote(name), :call, [unquote(opts)]}
defp prepare({name, nil}) when is_atom(name), do: quote do: {__MODULE__, unquote(name), []}
defp prepare(name), do: prepare({name, nil})
end
diff --git a/test/tesla/builder_test.exs b/test/tesla/builder_test.exs
index 2383c9d..2f6f15c 100644
--- a/test/tesla/builder_test.exs
+++ b/test/tesla/builder_test.exs
@@ -1,105 +1,105 @@
defmodule Tesla.BuilderTest do
use ExUnit.Case
alias Tesla.Builder
describe "Compilation" do
defmodule TestClientPlug do
use Tesla.Builder
@attr "value"
plug FirstMiddleware, @attr
plug SecondMiddleware, options: :are, fun: 1
plug ThirdMiddleware
plug :local_middleware
plug fn env, _next -> env end
end
defmodule TestClientModule do
use Tesla.Builder
adapter TheAdapter, hello: "world"
end
defmodule TestClientFunction do
use Tesla.Builder
adapter :local_adapter
end
defmodule TestClientAnon do
use Tesla.Builder
adapter fn env -> env end
end
test "generate __middleware__/0" do
assert [
{FirstMiddleware, :call, ["value"]},
{SecondMiddleware, :call, [[options: :are, fun: 1]]},
{ThirdMiddleware, :call, [nil]},
{TestClientPlug, :local_middleware, []},
{:fn, fun}
] = TestClientPlug.__middleware__
assert is_function(fun)
end
- # test "generate __adapter__/0 - adapter not set" do
- # assert TestClientPlug.__adapter__ == nil
- # end
+ test "generate __adapter__/0 - adapter not set" do
+ assert TestClientPlug.__adapter__ == nil
+ end
test "generate __adapter__/0 - adapter as module" do
assert TestClientModule.__adapter__ == {TheAdapter, :call, [[hello: "world"]]}
end
test "generate __adapter__/0 - adapter as module function" do
assert TestClientFunction.__adapter__ == {TestClientFunction, :local_adapter, []}
end
test "generate __adapter__/0 - adapter as anonymous function" do
assert {:fn, fun} = TestClientAnon.__adapter__
assert is_function(fun)
end
end
describe ":only/:except options" do
defmodule OnlyGetClient do
use Builder, only: [:get]
end
defmodule ExceptDeleteClient do
use Builder, except: ~w(delete)a
end
@http_verbs ~w(head get delete trace options post put patch)a
test "limit generated functions (only)" do
functions = OnlyGetClient.__info__(:functions) |> Keyword.keys() |> Enum.uniq()
assert :get in functions
refute Enum.any?(@http_verbs -- [:get], &(&1 in functions))
end
test "limit generated functions (except)" do
functions = ExceptDeleteClient.__info__(:functions) |> Keyword.keys() |> Enum.uniq()
refute :delete in functions
assert Enum.all?(@http_verbs -- [:delete], &(&1 in functions))
end
end
describe ":docs option" do
# Code.get_docs/2 requires .beam file of given module to exist in file system
# See test/support/docs.ex file for definitions of TeslaDocsTest.* modules
test "generate docs by default" do
docs = Code.get_docs(TeslaDocsTest.Default, :docs)
assert {_, _, _, _, doc} = Enum.find(docs, &match?({{:get, 1}, _, :def, _, _}, &1))
assert doc != false
end
test "do not generate docs for HTTP methods when docs: false" do
docs = Code.get_docs(TeslaDocsTest.NoDocs, :docs)
assert {_, _, _, _, false} = Enum.find(docs, &match?({{:get, 1}, _, :def, _, _}, &1))
assert {_, _, _, _, doc} = Enum.find(docs, &match?({{:custom, 1}, _, :def, _, _}, &1))
assert doc =~ ~r/something/
end
end
end
diff --git a/test/tesla_test.exs b/test/tesla_test.exs
index a1d754e..0f0dff1 100644
--- a/test/tesla_test.exs
+++ b/test/tesla_test.exs
@@ -1,298 +1,298 @@
defmodule TeslaTest do
use ExUnit.Case
require Tesla
@url "http://localhost:#{Application.get_env(:httparrot, :http_port)}"
describe "Adapters" do
defmodule ModuleAdapter do
def call(env, opts \\ []) do
Map.put(env, :url, env.url <> "/module/" <> opts[:with])
end
end
defmodule EmptyClient do
use Tesla
end
defmodule ModuleAdapterClient do
use Tesla
adapter ModuleAdapter, with: "someopt"
end
defmodule LocalAdapterClient do
use Tesla
adapter :local_adapter
def local_adapter(env) do
Map.put(env, :url, env.url <> "/local")
end
end
defmodule FunAdapterClient do
use Tesla
adapter fn env ->
Map.put(env, :url, env.url <> "/anon")
end
end
setup do
# clean config
Application.delete_env(:tesla, EmptyClient)
Application.delete_env(:tesla, ModuleAdapterClient)
:ok
end
test "defauilt adapter" do
- assert EmptyClient.__adapter__() == {Tesla.Adapter.Httpc, :call, [[]]}
+ assert Tesla.effective_adapter(EmptyClient) == {Tesla.Adapter.Httpc, :call, [[]]}
end
test "execute module adapter" do
response = ModuleAdapterClient.request(url: "test")
assert response.url == "test/module/someopt"
end
test "execute local function adapter" do
response = LocalAdapterClient.request(url: "test")
assert response.url == "test/local"
end
test "execute anonymous function adapter" do
response = FunAdapterClient.request(url: "test")
assert response.url == "test/anon"
end
test "use adapter override from config" do
Application.put_env(:tesla, EmptyClient, adapter: Tesla.Mock)
- assert EmptyClient.__adapter__() == {Tesla.Mock, :call, [[]]}
+ assert Tesla.effective_adapter(EmptyClient) == {Tesla.Mock, :call, [[]]}
end
test "prefer config over module setting" do
Application.put_env(:tesla, ModuleAdapterClient, adapter: Tesla.Mock)
- assert ModuleAdapterClient.__adapter__() == {Tesla.Mock, :call, [[]]}
+ assert Tesla.effective_adapter(ModuleAdapterClient) == {Tesla.Mock, :call, [[]]}
end
end
describe "Middleware" do
defmodule AppendOne do
@behaviour Tesla.Middleware
def call(env, next, _opts) do
env
|> Map.put(:url, "#{env.url}/1")
|> Tesla.run(next)
end
end
defmodule AppendWith do
@behaviour Tesla.Middleware
def call(env, next, opts) do
env
|> Map.update!(:url, fn url -> url <> "/MB" <> opts[:with] end)
|> Tesla.run(next)
|> Map.update!(:url, fn url -> url <> "/MA" <> opts[:with] end)
end
end
defmodule AppendClient do
use Tesla
plug AppendOne
plug AppendWith, with: "1"
plug AppendWith, with: "2"
plug :local_middleware
adapter fn env -> env end
def local_middleware(env, next) do
env
|> Map.update!(:url, fn url -> url <> "/LB" end)
|> Tesla.run(next)
|> Map.update!(:url, fn url -> url <> "/LA" end)
end
end
test "execute middleware top down" do
response = AppendClient.get("one")
assert response.url == "one/1/MB1/MB2/LB/LA/MA2/MA1"
end
end
describe "Dynamic client" do
defmodule DynamicClient do
use Tesla
adapter fn env ->
if String.ends_with?(env.url, "/cached") do
%{env | body: "cached", status: 304}
else
Tesla.run_default_adapter(env)
end
end
def help(client \\ %Tesla.Client{}) do
get(client, "/help")
end
end
test "override adapter - Tesla.build_client" do
client =
Tesla.build_client([], [
fn env, _next ->
%{env | body: "new"}
end
])
assert %{body: "new"} = DynamicClient.help(client)
end
test "override adapter - Tesla.build_adapter" do
client =
Tesla.build_adapter(fn env ->
%{env | body: "new"}
end)
assert %{body: "new"} = DynamicClient.help(client)
end
test "statically override adapter" do
assert %{status: 200} = DynamicClient.get(@url <> "/ip")
assert %{status: 304} = DynamicClient.get(@url <> "/cached")
end
end
describe "request API" do
defmodule SimpleClient do
use Tesla
adapter fn env ->
env
end
end
test "basic request" do
response = SimpleClient.request(url: "/", method: :post, query: [page: 1], body: "data")
assert response.method == :post
assert response.url == "/"
assert response.query == [page: 1]
assert response.body == "data"
end
test "shortcut function" do
response = SimpleClient.get("/get")
assert response.method == :get
assert response.url == "/get"
end
test "shortcut function with body" do
response = SimpleClient.post("/post", "some-data")
assert response.method == :post
assert response.url == "/post"
assert response.body == "some-data"
end
test "request with client" do
client = fn env, next ->
env
|> Map.put(:url, "/prefix" <> env.url)
|> Tesla.run(next)
end
response = SimpleClient.get("/")
assert response.url == "/"
refute response.__client__
response = client |> SimpleClient.get("/")
assert response.url == "/prefix/"
assert response.__client__ == %Tesla.Client{fun: client}
end
test "better errors when given nil opts" do
assert_raise FunctionClauseError, fn ->
Tesla.get("/", nil)
end
end
end
alias Tesla.Env
import Tesla
describe "get_header/2" do
test "non existing header" do
env = %Env{headers: [{"server", "Cowboy"}]}
assert get_header(env, "some-key") == nil
end
test "existing header" do
env = %Env{headers: [{"server", "Cowboy"}]}
assert get_header(env, "server") == "Cowboy"
end
test "first of multiple headers with the same name" do
env = %Env{headers: [{"cookie", "chocolate"}, {"cookie", "biscuits"}]}
assert get_header(env, "cookie") == "chocolate"
end
end
describe "get_headers/2" do
test "none matching" do
env = %Env{headers: [{"server", "Cowboy"}]}
assert get_headers(env, "cookie") == []
end
test "multiple matches matching" do
env = %Env{headers: [{"cookie", "chocolate"}, {"cookie", "biscuits"}]}
assert get_headers(env, "cookie") == ["chocolate", "biscuits"]
end
end
describe "put_header/3" do
test "add new header" do
env = %Env{}
env = put_header(env, "server", "Cowboy")
assert get_header(env, "server") == "Cowboy"
end
test "override existing header" do
env = %Env{headers: [{"server", "Cowboy"}]}
env = put_header(env, "server", "nginx")
assert get_header(env, "server") == "nginx"
end
end
describe "put_headers/2" do
test "add headers to env existing header" do
env = %Env{}
assert get_header(env, "server") == nil
env = Tesla.put_headers(env, [{"server", "Cowboy"}, {"content-length", "100"}])
assert get_header(env, "server") == "Cowboy"
assert get_header(env, "content-length") == "100"
env = Tesla.put_headers(env, [{"server", "nginx"}, {"content-type", "text/plain"}])
assert get_header(env, "server") == "Cowboy"
assert get_header(env, "content-length") == "100"
assert get_header(env, "content-type") == "text/plain"
end
test "add multiple headers with the same name" do
env = %Env{}
env = Tesla.put_headers(env, [{"cookie", "chocolate"}, {"cookie", "biscuits"}])
assert get_headers(env, "cookie") == ["chocolate", "biscuits"]
end
end
describe "delete_header/2" do
test "delete all headers with given name" do
env = %Env{headers: [{"cookie", "chocolate"}, {"server", "Cowboy"}, {"cookie", "biscuits"}]}
env = delete_header(env, "cookie")
assert get_header(env, "cookie") == nil
assert get_header(env, "server") == "Cowboy"
end
end
end

File Metadata

Mime Type
text/x-diff
Expires
Mon, Nov 25, 1:21 AM (1 d, 7 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39530
Default Alt Text
(29 KB)

Event Timeline