Page MenuHomePhorge

No OneTemporary

Size
38 KB
Referenced Files
None
Subscribers
None
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

Mime Type
text/x-diff
Expires
Mon, Nov 25, 3:09 PM (1 d, 7 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
39939
Default Alt Text
(38 KB)

Event Timeline