Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F113887
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
38 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/tesla.ex b/lib/tesla.ex
index a800667..9a07382 100644
--- a/lib/tesla.ex
+++ b/lib/tesla.ex
@@ -1,236 +1,237 @@
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 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__: atom,
__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) :: {:ok, Tesla.Env.t()} | {:error, any}
+ @callback call(env :: Tesla.Env.t(), next :: Tesla.Env.stack(), options :: any) ::
+ {:ok, Tesla.Env.t()} | {:error, any}
end
defmodule Tesla.Adapter do
@callback call(env :: Tesla.Env.t(), options :: any) :: {:ok, Tesla.Env.t()} | {:error, any}
end
defmodule Tesla do
use Tesla.Builder
alias Tesla.Env
require Tesla.Adapter.Httpc
@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
@doc false
def execute(module, %{fun: fun, pre: pre, post: post} = client, options) do
env = struct(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: {:ok, 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(Env.t(), binary) :: binary | nil
def get_header(%Env{headers: headers}, key) do
case List.keyfind(headers, key, 0) do
{_, value} -> value
_ -> nil
end
end
@spec get_headers(Env.t(), binary) :: [binary]
def get_headers(%Env{headers: headers}, key) do
for {k, v} <- headers, k == key, do: v
end
@spec put_header(Env.t(), binary, binary) :: Env.t()
def put_header(%Env{} = env, key, value) do
headers = List.keystore(env.headers, key, 0, {key, value})
%{env | headers: headers}
end
@spec put_headers(Env.t(), [{binary, binary}]) :: Env.t()
def put_headers(%Env{} = env, list) when is_list(list) do
%{env | headers: env.headers ++ list}
end
@spec delete_header(Env.t(), binary) :: Env.t()
def delete_header(%Env{} = env, key) do
headers = for {k, v} <- env.headers, k != key, do: {k, v}
%{env | headers: headers}
end
@spec put_body(Env.t(), Env.body()) :: Env.t()
def put_body(%Env{} = env, body), do: %{env | body: body}
@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/adapter/ibrowse.ex b/lib/tesla/adapter/ibrowse.ex
index dcf2861..4166831 100644
--- a/lib/tesla/adapter/ibrowse.ex
+++ b/lib/tesla/adapter/ibrowse.ex
@@ -1,95 +1,96 @@
if Code.ensure_loaded?(:ibrowse) do
defmodule Tesla.Adapter.Ibrowse do
@moduledoc """
Adapter for [ibrowse](https://github.com/cmullaparthi/ibrowse)
Remember to add `{:ibrowse, "~> 4.2"}` to dependencies (and `:ibrowse` to applications in `mix.exs`)
Also, you need to recompile tesla after adding `:ibrowse` dependency:
```
mix deps.clean tesla
mix deps.compile tesla
```
### Example usage
```
# set globally in config/config.exs
config :tesla, :adapter, :ibrowse
# set per module
defmodule MyClient do
use Tesla
adapter :ibrowse
end
```
"""
@behaviour Tesla.Adapter
import Tesla.Adapter.Shared, only: [stream_to_fun: 1, next_chunk: 1]
alias Tesla.Multipart
def call(env, opts) do
with {:ok, status, headers, body} <- request(env, opts || []) do
- {:ok, %{
- env
- | status: format_status(status),
- headers: format_headers(headers),
- body: format_body(body)
- }}
+ {:ok,
+ %{
+ env
+ | status: format_status(status),
+ headers: format_headers(headers),
+ body: format_body(body)
+ }}
end
end
defp format_status(status) when is_list(status) do
status |> to_string() |> String.to_integer()
end
defp format_headers(headers) do
for {key, value} <- headers do
{String.downcase(to_string(key)), to_string(value)}
end
end
defp format_body(data) when is_list(data), do: IO.iodata_to_binary(data)
defp format_body(data) when is_binary(data), do: data
defp request(env, opts) do
body = env.body || []
handle(
request(
Tesla.build_url(env.url, env.query) |> to_charlist,
env.headers,
env.method,
body,
opts ++ env.opts
)
)
end
defp request(url, headers, method, %Multipart{} = mp, opts) do
headers = headers ++ Multipart.headers(mp)
body = stream_to_fun(Multipart.body(mp))
request(url, headers, method, body, opts)
end
defp request(url, headers, method, %Stream{} = body, opts) do
fun = stream_to_fun(body)
request(url, headers, method, fun, opts)
end
defp request(url, headers, method, body, opts) when is_function(body) do
body = {&next_chunk/1, body}
opts = Keyword.put(opts, :transfer_encoding, :chunked)
request(url, headers, method, body, opts)
end
defp request(url, headers, method, body, opts) do
:ibrowse.send_req(url, headers, method, body, opts)
end
defp handle({:error, {:conn_failed, error}}), do: error
defp handle(response), do: response
end
end
diff --git a/lib/tesla/middleware/compression.ex b/lib/tesla/middleware/compression.ex
index a4f5fd1..134f030 100644
--- a/lib/tesla/middleware/compression.ex
+++ b/lib/tesla/middleware/compression.ex
@@ -1,94 +1,95 @@
defmodule Tesla.Middleware.Compression do
@behaviour Tesla.Middleware
@moduledoc """
Compress requests and decompress responses.
Supports "gizp" and "deflate" encodings using erlang's built-in `:zlib` module.
### Example usage
```
defmodule MyClient do
use Tesla
plug Tesla.Middleware.Compression, format: "gzip"
end
```
### Options
- `:format` - request compression format, `"gzip"` (default) or `"defalte"`
"""
def call(env, next, opts) do
env
|> compress(opts)
|> Tesla.run(next)
|> decompress()
end
defp compressable?(body), do: is_binary(body)
@doc """
Compress request, used by `Tesla.Middleware.CompressRequest`
"""
def compress(env, opts) do
if compressable?(env.body) do
format = Keyword.get(opts || [], :format, "gzip")
env
|> Tesla.put_body(compress_body(env.body, format))
|> Tesla.put_headers([{"content-encoding", format}])
else
env
end
end
defp compress_body(body, "gzip"), do: :zlib.gzip(body)
defp compress_body(body, "deflate"), do: :zlib.zip(body)
@doc """
Decompress response, used by `Tesla.Middleware.DecompressResponse`
"""
def decompress({:ok, env}), do: {:ok, decompress(env)}
def decompress({:error, reasonn}), do: {:error, reasonn}
+
def decompress(env) do
env
|> Tesla.put_body(decompress_body(env.body, Tesla.get_header(env, "content-encoding")))
end
defp decompress_body(<<31, 139, 8, _::binary>> = body, "gzip"), do: :zlib.gunzip(body)
defp decompress_body(body, "deflate"), do: :zlib.unzip(body)
defp decompress_body(body, _content_encoding), do: body
end
defmodule Tesla.Middleware.CompressRequest do
@behaviour Tesla.Middleware
@moduledoc """
Only compress request.
See `Tesla.Middleware.Compression` for options.
"""
def call(env, next, opts) do
env
|> Tesla.Middleware.Compression.compress(opts)
|> Tesla.run(next)
end
end
defmodule Tesla.Middleware.DecompressResponse do
@behaviour Tesla.Middleware
@moduledoc """
Only decompress response.
See `Tesla.Middleware.Compression` for options.
"""
def call(env, next, _opts) do
env
|> Tesla.run(next)
|> Tesla.Middleware.Compression.decompress()
end
end
diff --git a/lib/tesla/middleware/decode_rels.ex b/lib/tesla/middleware/decode_rels.ex
index 1385fc5..a4b6ecb 100644
--- a/lib/tesla/middleware/decode_rels.ex
+++ b/lib/tesla/middleware/decode_rels.ex
@@ -1,51 +1,52 @@
defmodule Tesla.Middleware.DecodeRels do
@behaviour Tesla.Middleware
@moduledoc """
Decode `Link` Hypermedia HTTP header into `opts[:rels]` field in response.
### Example usage
```
defmodule MyClient do
use Tesla
plug Tesla.Middleware.DecodeRels
end
env = MyClient.get("/...")
env.opts[:rels] # => %{"Next" => "http://...", "Prev" => "..."}
```
"""
def call(env, next, _opts) do
env
|> Tesla.run(next)
|> parse_rels
end
defp parse_rels({:ok, env}), do: {:ok, parse_rels(env)}
defp parse_rels({:error, reason}), do: {:error, reason}
+
defp parse_rels(env) do
if link = Tesla.get_header(env, "link") do
Tesla.put_opt(env, :rels, rels(link))
else
env
end
end
defp rels(link) do
link
|> String.split(",")
|> Enum.map(&String.trim/1)
|> Enum.map(&rel/1)
|> Enum.into(%{})
end
defp rel(item) do
Regex.run(~r/\A<(.+)>; rel="(.+)"\z/, item, capture: :all_but_first)
|> Enum.reverse()
|> List.to_tuple()
end
end
diff --git a/lib/tesla/middleware/follow_redirects.ex b/lib/tesla/middleware/follow_redirects.ex
index ba62a9b..ad74925 100644
--- a/lib/tesla/middleware/follow_redirects.ex
+++ b/lib/tesla/middleware/follow_redirects.ex
@@ -1,67 +1,68 @@
defmodule Tesla.Middleware.FollowRedirects do
@behaviour Tesla.Middleware
@moduledoc """
Follow 3xx redirects
### Example
```
defmodule MyClient do
use Tesla
plug Tesla.Middleware.FollowRedirects, max_redirects: 3 # defaults to 5
end
```
### Options
- `:max_redirects` - limit number of redirects (default: `5`)
"""
@max_redirects 5
@redirect_statuses [301, 302, 307, 308]
def call(env, next, opts \\ []) do
max = Keyword.get(opts || [], :max_redirects, @max_redirects)
redirect(env, next, max)
end
defp redirect(env, next, left) when left == 0 do
case Tesla.run(env, next) do
- {:ok, %{status: status} = env} when not status in @redirect_statuses ->
+ {:ok, %{status: status} = env} when not (status in @redirect_statuses) ->
{:ok, env}
{:ok, env} ->
{:error, {__MODULE__, :too_many_redirects}}
error ->
error
end
end
defp redirect(env, next, left) do
case Tesla.run(env, next) do
{:ok, %{status: status} = env} when status in @redirect_statuses ->
case Tesla.get_header(env, "location") do
nil ->
{:ok, env}
+
location ->
location = parse_location(location, env)
redirect(%{env | url: location}, next, left - 1)
end
other ->
other
end
end
defp parse_location("/" <> _rest = location, env) do
env.url
|> URI.parse()
|> URI.merge(location)
|> URI.to_string()
end
defp parse_location(location, _env), do: location
end
diff --git a/lib/tesla/middleware/fuse.ex b/lib/tesla/middleware/fuse.ex
index 4128d14..af1f9fd 100644
--- a/lib/tesla/middleware/fuse.ex
+++ b/lib/tesla/middleware/fuse.ex
@@ -1,61 +1,62 @@
if Code.ensure_loaded?(:fuse) do
defmodule Tesla.Middleware.Fuse do
@behaviour Tesla.Middleware
@moduledoc """
Circuit Breaker middleware using [fuse](https://github.com/jlouis/fuse)
Remember to add `{:fuse, "~> 2.4"}` to dependencies (and `:fuse` to applications in `mix.exs`)
Also, you need to recompile tesla after adding `:fuse` dependency:
```
mix deps.clean tesla
mix deps.compile tesla
```
### Example usage
```
defmodule MyClient do
use Tesla
plug Tesla.Middleware.Fuse, opts: {{:standard, 2, 10_000}, {:reset, 60_000}}
end
```
### Options
- `:name` - fuse name (defaults to module name)
- `:opts` - fuse options (see fuse docs for reference)
"""
# options borrowed from http://blog.rokkincat.com/circuit-breakers-in-elixir/
# most probably not valid for your use case
@defaults {{:standard, 2, 10_000}, {:reset, 60_000}}
def call(env, next, opts) do
opts = opts || []
name = Keyword.get(opts, :name, env.__module__)
case :fuse.ask(name, :sync) do
:ok ->
run(env, next, name)
:blown ->
{:error, :unavailable}
{:error, :not_found} ->
:fuse.install(name, Keyword.get(opts, :opts, @defaults))
run(env, next, name)
end
end
defp run(env, next, name) do
case Tesla.run(env, next) do
{:ok, env} ->
{:ok, env}
+
{:error, reason} ->
:fuse.melt(name)
{:error, :unavailable}
end
end
end
end
diff --git a/lib/tesla/middleware/method_override.ex b/lib/tesla/middleware/method_override.ex
index 5f8c252..b25d8b0 100644
--- a/lib/tesla/middleware/method_override.ex
+++ b/lib/tesla/middleware/method_override.ex
@@ -1,48 +1,48 @@
defmodule Tesla.Middleware.MethodOverride do
@behaviour Tesla.Middleware
@moduledoc """
Middleware that adds X-Http-Method-Override header with original request
method and sends the request as post.
Useful when there's an issue with sending non-post request.
### Example
```
defmodule MyClient do
use Tesla
plug Tesla.Middleware.MethodOverride
end
```
### Options
- `:override` - list of http methods that should be overriden,
everything except `:get` and `:post` if not specified
"""
def call(env, next, opts) do
if overridable?(env, opts) do
env
|> override
|> Tesla.run(next)
else
env
|> Tesla.run(next)
end
end
defp override(env) do
env
|> Tesla.put_headers([{"x-http-method-override", "#{env.method}"}])
|> Map.put(:method, :post)
end
defp overridable?(env, opts) do
if opts[:override] do
env.method in opts[:override]
else
- not env.method in [:get, :post]
+ not (env.method in [:get, :post])
end
end
end
diff --git a/lib/tesla/middleware/retry.ex b/lib/tesla/middleware/retry.ex
index eede27f..1f7ea0d 100644
--- a/lib/tesla/middleware/retry.ex
+++ b/lib/tesla/middleware/retry.ex
@@ -1,47 +1,48 @@
defmodule Tesla.Middleware.Retry do
@behaviour Tesla.Middleware
@moduledoc """
Retry few times in case of connection refused error.
### Example
```
defmodule MyClient do
use Tesla
plug Tesla.Middleware.Retry, delay: 500, max_retries: 10
end
```
### Options
- `:delay` - number of milliseconds to wait before retrying (defaults to 1000)
- `:max_retries` - maximum number of retries (defaults to 5)
"""
@defaults [
delay: 1000,
max_retries: 5
]
def call(env, next, opts) do
opts = opts || []
delay = Keyword.get(opts, :delay, @defaults[:delay])
max_retries = Keyword.get(opts, :max_retries, @defaults[:max_retries])
retry(env, next, delay, max_retries)
end
defp retry(env, next, _delay, retries) when retries <= 1 do
Tesla.run(env, next)
end
defp retry(env, next, delay, retries) do
case Tesla.run(env, next) do
{:ok, env} ->
{:ok, env}
+
{:error, reason} ->
:timer.sleep(delay)
retry(env, next, delay, retries - 1)
end
end
end
diff --git a/mix.exs b/mix.exs
index 89edbc7..b1cecf1 100644
--- a/mix.exs
+++ b/mix.exs
@@ -1,78 +1,77 @@
defmodule Tesla.Mixfile do
use Mix.Project
def project do
[
app: :tesla,
version: "0.10.0",
description: description(),
package: package(),
source_url: "https://github.com/teamon/tesla",
elixir: "~> 1.3",
elixirc_paths: elixirc_paths(Mix.env()),
deps: deps(),
lockfile: lockfile(System.get_env("LOCKFILE")),
test_coverage: [tool: ExCoveralls],
dialyzer: [
plt_add_apps: [:inets],
plt_add_deps: :project
],
docs: [
main: "readme",
extras: ["README.md"]
]
]
end
# Configuration for the OTP application
#
# Type `mix help compile.app` for more information
def application do
[applications: applications(Mix.env())]
end
def applications(:test), do: applications(:dev) ++ [:httparrot, :hackney, :ibrowse]
def applications(_), do: [:logger, :ssl, :inets]
defp description do
"HTTP client library, with support for middleware and multiple adapters."
end
defp package do
[
maintainers: ["Tymon Tobolski"],
licenses: ["MIT"],
links: %{"GitHub" => "https://github.com/teamon/tesla"}
]
end
# Specifies which paths to compile per environment.
defp elixirc_paths(:test), do: ["lib", "test/support"]
defp elixirc_paths(_), do: ["lib"]
defp lockfile(nil), do: "mix.lock"
defp lockfile(lockfile), do: "test/lockfiles/#{lockfile}.lock"
defp deps do
[
{:mime, "~> 1.0"},
# http clients
{:ibrowse, "~> 4.4.0", optional: true},
{:hackney, "~> 1.6", optional: true},
# json parsers
{:poison, ">= 1.0.0", optional: true},
-
{:fuse, "~> 2.4", optional: true},
# testing & docs
{:excoveralls, "~> 0.8.0", only: :test},
{:httparrot, "~> 1.0", only: :test},
{:ex_doc, "~> 0.18.0", only: :dev},
{:mix_test_watch, "~> 0.5.0", only: :dev},
{:dialyxir, "~> 0.5.0", only: :dev},
{:inch_ex, "~> 0.5.6", only: :docs}
]
end
end
diff --git a/test/tesla/adapter/hackney_test.exs b/test/tesla/adapter/hackney_test.exs
index c66235b..f721d88 100644
--- a/test/tesla/adapter/hackney_test.exs
+++ b/test/tesla/adapter/hackney_test.exs
@@ -1,23 +1,25 @@
defmodule Tesla.Adapter.HackneyTest do
use ExUnit.Case
use Tesla.AdapterCase, adapter: Tesla.Adapter.Hackney
use Tesla.AdapterCase.Basic
use Tesla.AdapterCase.Multipart
use Tesla.AdapterCase.StreamRequestBody
- use Tesla.AdapterCase.SSL, ssl_options: [
- cacertfile: "#{:code.priv_dir(:httparrot)}/ssl/server-ca.crt"
- ]
+
+ use Tesla.AdapterCase.SSL,
+ ssl_options: [
+ cacertfile: "#{:code.priv_dir(:httparrot)}/ssl/server-ca.crt"
+ ]
alias Tesla.Env
test "get with `with_body: true` option" do
request = %Env{
method: :get,
url: "#{@http}/ip"
}
assert {:ok, %Env{} = response} = call(request, with_body: true)
assert response.status == 200
end
end
diff --git a/test/tesla/middleware/base_url_test.exs b/test/tesla/middleware/base_url_test.exs
index 5a5d893..55aca8f 100644
--- a/test/tesla/middleware/base_url_test.exs
+++ b/test/tesla/middleware/base_url_test.exs
@@ -1,31 +1,31 @@
defmodule Tesla.Middleware.BaseUrlTest do
use ExUnit.Case
alias Tesla.Env
@middleware Tesla.Middleware.BaseUrl
test "base without slash, path without slash" do
- {:ok, env} = @middleware.call(%Env{url: "path"}, [], "http://example.com")
+ assert {:ok, env} = @middleware.call(%Env{url: "path"}, [], "http://example.com")
assert env.url == "http://example.com/path"
end
test "base without slash, path with slash" do
- {:ok, env} = @middleware.call(%Env{url: "/path"}, [], "http://example.com")
+ assert {:ok, env} = @middleware.call(%Env{url: "/path"}, [], "http://example.com")
assert env.url == "http://example.com/path"
end
test "base with slash, path without slash" do
- {:ok, env} = @middleware.call(%Env{url: "path"}, [], "http://example.com/")
+ assert {:ok, env} = @middleware.call(%Env{url: "path"}, [], "http://example.com/")
assert env.url == "http://example.com/path"
end
test "base with slash, path with slash" do
- {:ok, env} = @middleware.call(%Env{url: "/path"}, [], "http://example.com/")
+ assert {:ok, env} = @middleware.call(%Env{url: "/path"}, [], "http://example.com/")
assert env.url == "http://example.com/path"
end
test "skip double append" do
- {:ok, env} = @middleware.call(%Env{url: "http://other.foo"}, [], "http://example.com")
+ assert {:ok, env} = @middleware.call(%Env{url: "http://other.foo"}, [], "http://example.com")
assert env.url == "http://other.foo"
end
end
diff --git a/test/tesla/middleware/decode_rels_test.exs b/test/tesla/middleware/decode_rels_test.exs
index 603a38c..6a32ff5 100644
--- a/test/tesla/middleware/decode_rels_test.exs
+++ b/test/tesla/middleware/decode_rels_test.exs
@@ -1,37 +1,38 @@
defmodule Tesla.Middleware.DecodeRelsTest do
use ExUnit.Case
defmodule Client do
use Tesla
plug Tesla.Middleware.DecodeRels
adapter fn env ->
- {:ok, case env.url do
- "/rels" ->
- Tesla.put_headers(env, [
- {"link", ~s(<https://api.github.com/resource?page=2>; rel="next",
+ {:ok,
+ case env.url do
+ "/rels" ->
+ Tesla.put_headers(env, [
+ {"link", ~s(<https://api.github.com/resource?page=2>; rel="next",
<https://api.github.com/resource?page=5>; rel="last")}
- ])
+ ])
- _ ->
- env
- end}
+ _ ->
+ env
+ end}
end
end
test "deocde rels" do
assert {:ok, env} = Client.get("/rels")
assert env.opts[:rels] == %{
"next" => "https://api.github.com/resource?page=2",
"last" => "https://api.github.com/resource?page=5"
}
end
test "skip if no Link header" do
assert {:ok, env} = Client.get("/")
assert env.opts[:rels] == nil
end
end
diff --git a/test/tesla/middleware/follow_redirects_test.exs b/test/tesla/middleware/follow_redirects_test.exs
index a1974a3..378bf88 100644
--- a/test/tesla/middleware/follow_redirects_test.exs
+++ b/test/tesla/middleware/follow_redirects_test.exs
@@ -1,108 +1,110 @@
defmodule Tesla.Middleware.FollowRedirectsTest do
use ExUnit.Case
defmodule Client do
use Tesla
plug Tesla.Middleware.FollowRedirects
adapter fn env ->
{status, headers, body} =
case env.url do
"http://example.com/0" ->
{200, [{"content-type", "text/plain"}], "foo bar"}
"http://example.com/" <> n ->
next = String.to_integer(n) - 1
{301, [{"location", "http://example.com/#{next}"}], ""}
end
{:ok, %{env | status: status, headers: headers, body: body}}
end
end
test "redirects if default max redirects isn't exceeded" do
- assert {:ok, env} = Client.get("http://example.com/5")
+ assert {:ok, env} = Client.get("http://example.com/5")
assert env.status == 200
end
test "raise error when redirect default max redirects is exceeded" do
- assert {:error, {Tesla.Middleware.FollowRedirects, :too_many_redirects}} == Client.get("http://example.com/6")
+ assert {:error, {Tesla.Middleware.FollowRedirects, :too_many_redirects}} ==
+ Client.get("http://example.com/6")
end
defmodule CustomMaxRedirectsClient do
use Tesla
plug Tesla.Middleware.FollowRedirects, max_redirects: 1
adapter fn env ->
{status, headers, body} =
case env.url do
"http://example.com/0" ->
{200, [{"content-type", "text/plain"}], "foo bar"}
"http://example.com/" <> n ->
next = String.to_integer(n) - 1
{301, [{"location", "http://example.com/#{next}"}], ""}
end
{:ok, %{env | status: status, headers: headers, body: body}}
end
end
alias CustomMaxRedirectsClient, as: CMRClient
test "redirects if custom max redirects isn't exceeded" do
assert {:ok, env} = CMRClient.get("http://example.com/1")
assert env.status == 200
end
test "raise error when custom max redirects is exceeded" do
- assert {:error, {Tesla.Middleware.FollowRedirects, :too_many_redirects}} == CMRClient.get("http://example.com/2")
+ assert {:error, {Tesla.Middleware.FollowRedirects, :too_many_redirects}} ==
+ CMRClient.get("http://example.com/2")
end
defmodule RelativeLocationClient do
use Tesla
plug Tesla.Middleware.FollowRedirects
adapter fn env ->
{status, headers, body} =
case env.url do
"https://example.com/pl" ->
{200, [{"content-type", "text/plain"}], "foo bar"}
"http://example.com" ->
{301, [{"location", "https://example.com"}], ""}
"https://example.com" ->
{301, [{"location", "/pl"}], ""}
"https://example.com/" ->
{301, [{"location", "/pl"}], ""}
"https://example.com/article" ->
{301, [{"location", "/pl"}], ""}
end
{:ok, %{env | status: status, headers: headers, body: body}}
end
end
alias RelativeLocationClient, as: RLClient
test "supports relative address in location header" do
assert {:ok, env} = RLClient.get("http://example.com")
assert env.status == 200
end
test "doesn't create double slashes inside new url" do
assert {:ok, env} = RLClient.get("https://example.com/")
assert env.url == "https://example.com/pl"
end
test "rewrites URLs to their root" do
assert {:ok, env} = RLClient.get("https://example.com/article")
assert env.url == "https://example.com/pl"
end
end
diff --git a/test/tesla/middleware/header_test.exs b/test/tesla/middleware/header_test.exs
index a36c539..1be9efc 100644
--- a/test/tesla/middleware/header_test.exs
+++ b/test/tesla/middleware/header_test.exs
@@ -1,15 +1,15 @@
defmodule Tesla.Middleware.HeadersTest do
use ExUnit.Case
alias Tesla.Env
@middleware Tesla.Middleware.Headers
test "merge headers" do
assert {:ok, env} =
- @middleware.call(%Env{headers: [{"authorization", "secret"}]}, [], [
- {"content-type", "text/plain"}
- ])
+ @middleware.call(%Env{headers: [{"authorization", "secret"}]}, [], [
+ {"content-type", "text/plain"}
+ ])
assert env.headers == [{"authorization", "secret"}, {"content-type", "text/plain"}]
end
end
diff --git a/test/tesla_test.exs b/test/tesla_test.exs
index d59d55c..697b53b 100644
--- a/test/tesla_test.exs
+++ b/test/tesla_test.exs
@@ -1,308 +1,312 @@
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
{:ok, 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
{:ok, Map.put(env, :url, env.url <> "/local")}
end
end
defmodule FunAdapterClient do
use Tesla
adapter fn env ->
{:ok, 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 Tesla.effective_adapter(EmptyClient) == {Tesla.Adapter.Httpc, :call, [[]]}
end
test "use adapter override from config" do
Application.put_env(:tesla, EmptyClient, adapter: Tesla.Mock)
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 Tesla.effective_adapter(ModuleAdapterClient) == {Tesla.Mock, :call, [[]]}
end
test "execute module adapter" do
assert {:ok, response} = ModuleAdapterClient.request(url: "test")
assert response.url == "test/module/someopt"
end
test "execute local function adapter" do
assert {:ok, response} = LocalAdapterClient.request(url: "test")
assert response.url == "test/local"
end
test "execute anonymous function adapter" do
assert {:ok, response} = FunAdapterClient.request(url: "test")
assert response.url == "test/anon"
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)
|> case do
{:ok, env} ->
{:ok, Map.update!(env, :url, fn url -> url <> "/MA" <> opts[:with] end)}
+
error ->
error
end
end
end
defmodule AppendClient do
use Tesla
plug AppendOne
plug AppendWith, with: "1"
plug AppendWith, with: "2"
plug :local_middleware
adapter fn env -> {:ok, env} end
def local_middleware(env, next) do
env
|> Map.update!(:url, fn url -> url <> "/LB" end)
|> Tesla.run(next)
|> case do
{:ok, env} ->
{:ok, Map.update!(env, :url, fn url -> url <> "/LA" end)}
+
error ->
error
end
end
end
test "execute middleware top down" do
assert {:ok, 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
{:ok, %{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 ->
{:ok, %{env | body: "new"}}
end
])
assert {:ok, %{body: "new"}} = DynamicClient.help(client)
end
test "override adapter - Tesla.build_adapter" do
client =
Tesla.build_adapter(fn env ->
{:ok, %{env | body: "new"}}
end)
assert {:ok, %{body: "new"}} = DynamicClient.help(client)
end
test "statically override adapter" do
assert {:ok, %{status: 200}} = DynamicClient.get(@url <> "/ip")
assert {:ok, %{status: 304}} = DynamicClient.get(@url <> "/cached")
end
end
describe "request API" do
defmodule SimpleClient do
use Tesla
adapter fn env ->
{:ok, env}
end
end
test "basic request" do
- assert {:ok, response} = SimpleClient.request(url: "/", method: :post, query: [page: 1], body: "data")
+ assert {:ok, 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
assert {:ok, response} = SimpleClient.get("/get")
assert response.method == :get
assert response.url == "/get"
end
test "shortcut function with body" do
assert {:ok, 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
assert {:ok, response} = SimpleClient.get("/")
assert response.url == "/"
assert response.__client__ == %Tesla.Client{}
assert {:ok, 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
Details
Attached
Mime Type
text/x-diff
Expires
Mon, Nov 25, 3:09 PM (1 d, 5 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39939
Default Alt Text
(38 KB)
Attached To
Mode
R28 tesla
Attached
Detach File
Event Timeline
Log In to Comment