Page MenuHomePhorge

No OneTemporary

Size
6 KB
Referenced Files
None
Subscribers
None
diff --git a/lib/tesla/middleware/fuse.ex b/lib/tesla/middleware/fuse.ex
index 83a17d9..541160f 100644
--- a/lib/tesla/middleware/fuse.ex
+++ b/lib/tesla/middleware/fuse.ex
@@ -1,77 +1,90 @@
if Code.ensure_loaded?(:fuse) do
defmodule Tesla.Middleware.Fuse do
@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}}
+ plug Tesla.Middleware.Fuse,
+ opts: {{:standard, 2, 10_000}, {:reset, 60_000}}
+ should_melt: fn
+ {:ok, %{status: status}} when status in [428, 500, 504] -> true
+ {:ok, _} -> false
+ {:error, _} -> true
+ end
end
```
## Options
- `:name` - fuse name (defaults to module name)
- `:opts` - fuse options (see fuse docs for reference)
+ - `:should_melt` - function to determine if request should melt the fuse
## SASL logger
fuse library uses [SASL (System Architecture Support Libraries)](http://erlang.org/doc/man/sasl_app.html).
You can disable its logger output using:
```
config :sasl, sasl_error_logger: :false
```
Read more at [jlouis/fuse#32](https://github.com/jlouis/fuse/issues/32) and [jlouis/fuse#19](https://github.com/jlouis/fuse/issues/19).
"""
@behaviour Tesla.Middleware
# 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}}
@impl Tesla.Middleware
def call(env, next, opts) do
opts = opts || []
- name = Keyword.get(opts, :name, env.__module__)
- case :fuse.ask(name, :sync) do
+ context = %{
+ name: Keyword.get(opts, :name, env.__module__),
+ should_melt: Keyword.get(opts, :should_melt, &match?({:error, _}, &1))
+ }
+
+ case :fuse.ask(context.name, :sync) do
:ok ->
- run(env, next, name)
+ run(env, next, context)
:blown ->
{:error, :unavailable}
{:error, :not_found} ->
- :fuse.install(name, Keyword.get(opts, :opts, @defaults))
- run(env, next, name)
+ :fuse.install(context.name, Keyword.get(opts, :opts, @defaults))
+ run(env, next, context)
end
end
- defp run(env, next, name) do
- case Tesla.run(env, next) do
- {:ok, env} ->
- {:ok, env}
+ defp run(env, next, context) do
+ Tesla.run(env, next)
+ |> maybe_melt_fuse(context)
+ end
- {:error, _reason} ->
- :fuse.melt(name)
- {:error, :unavailable}
+ defp maybe_melt_fuse(res, context) do
+ if context.should_melt.(res) do
+ :fuse.melt(context.name)
end
+
+ res
end
end
end
diff --git a/test/tesla/middleware/fuse_test.exs b/test/tesla/middleware/fuse_test.exs
index 053b894..7a32e9d 100644
--- a/test/tesla/middleware/fuse_test.exs
+++ b/test/tesla/middleware/fuse_test.exs
@@ -1,52 +1,114 @@
defmodule Tesla.Middleware.FuseTest do
use ExUnit.Case, async: false
defmodule Report do
def call(env, next, _) do
send(self(), :request_made)
Tesla.run(env, next)
end
end
defmodule Client do
use Tesla
plug Tesla.Middleware.Fuse
plug Report
adapter fn env ->
case env.url do
"/ok" ->
{:ok, env}
"/unavailable" ->
{:error, :econnrefused}
end
end
end
+ defmodule ClientWithCustomShouldMeltFunction do
+ use Tesla
+
+ plug Tesla.Middleware.Fuse,
+ should_melt: fn
+ {:ok, %{status: status}} when status in [504] -> true
+ {:ok, _} -> false
+ {:error, _} -> true
+ end
+
+ plug Report
+
+ adapter fn env ->
+ case env.url do
+ "/ok" ->
+ {:ok, env}
+
+ "/error_500" ->
+ {:ok, %{env | status: 500}}
+
+ "/error_504" ->
+ {:ok, %{env | status: 504}}
+
+ "/unavailable" ->
+ {:error, :econnrefused}
+ end
+ end
+ end
+
setup do
Application.ensure_all_started(:fuse)
:fuse.reset(Client)
+ :fuse.reset(ClientWithCustomShouldMeltFunction)
:ok
end
test "regular endpoint" do
assert {:ok, %Tesla.Env{url: "/ok"}} = Client.get("/ok")
end
+ test "custom should_melt function - not melting 500" do
+ custom_client = ClientWithCustomShouldMeltFunction
+
+ assert {:ok, %Tesla.Env{status: 500}} = custom_client.get("/error_500")
+ assert_receive :request_made
+ assert {:ok, %Tesla.Env{status: 500}} = custom_client.get("/error_500")
+ assert_receive :request_made
+ assert {:ok, %Tesla.Env{status: 500}} = custom_client.get("/error_500")
+ assert_receive :request_made
+
+ assert {:ok, %Tesla.Env{status: 500}} = custom_client.get("/error_500")
+ assert_receive :request_made
+ assert {:ok, %Tesla.Env{status: 500}} = custom_client.get("/error_500")
+ assert_receive :request_made
+ end
+
+ test "custom should_melt function - melting 504" do
+ custom_client = ClientWithCustomShouldMeltFunction
+
+ assert {:ok, %Tesla.Env{status: 504}} = custom_client.get("/error_504")
+ assert_receive :request_made
+ assert {:ok, %Tesla.Env{status: 504}} = custom_client.get("/error_504")
+ assert_receive :request_made
+ assert {:ok, %Tesla.Env{status: 504}} = custom_client.get("/error_504")
+ assert_receive :request_made
+
+ assert {:error, :unavailable} = custom_client.get("/error_504")
+ refute_receive :request_made
+ assert {:error, :unavailable} = custom_client.get("/error_504")
+ refute_receive :request_made
+ end
+
test "unavailable endpoint" do
- assert {:error, :unavailable} = Client.get("/unavailable")
+ assert {:error, :econnrefused} = Client.get("/unavailable")
assert_receive :request_made
- assert {:error, :unavailable} = Client.get("/unavailable")
+ assert {:error, :econnrefused} = Client.get("/unavailable")
assert_receive :request_made
- assert {:error, :unavailable} = Client.get("/unavailable")
+ assert {:error, :econnrefused} = Client.get("/unavailable")
assert_receive :request_made
assert {:error, :unavailable} = Client.get("/unavailable")
refute_receive :request_made
assert {:error, :unavailable} = Client.get("/unavailable")
refute_receive :request_made
end
end

File Metadata

Mime Type
text/x-diff
Expires
Tue, Nov 26, 9:32 PM (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
40487
Default Alt Text
(6 KB)

Event Timeline