lib/maxwell/middleware/fuse.ex

if Code.ensure_loaded?(:fuse) do
  defmodule Maxwell.Middleware.Fuse do
    @moduledoc """
    A circuit breaker middleware which uses [fuse](https://github.com/jlouis/fuse) under the covers.

    To use this middleware, you will need to add `{:fuse, "~> 2.4"}` to your dependencies, and
    the `:fuse` application to your applications list in `mix.exs`.

    Example:

        defmodule CircuitBreakerClient do
          use Maxwell.Builder

          middleware Maxwell.Middleware.Fuse,
            name: __MODULE__,
            fuse_opts: {{:standard, 2, 10_000}, {:reset, 60_000}}
        end

    Options:

        - `:name` - The name of the fuse, required
        - `:fuse_opts` - Options to pass along to `fuse`. See `fuse` docs for more information.
    """
    use Maxwell.Middleware

    # These options were borrowed from http://blog.rokkincat.com/circuit-breakers-in-elixir/
    # You should tweak them for your use case, as these defaults are likely unsuitable.
    @default_fuse_opts {{:standard, 2, 10_000}, {:reset, 60_000}}

    @default_opts [fuse_opts: @default_fuse_opts]

    def init(opts) do
      _name = Keyword.fetch!(opts, :name)
      Keyword.merge(@default_opts, opts)
    end

    def call(conn, next, opts) do
      name = Keyword.get(opts, :name)

      conn =
        case :fuse.ask(name, :sync) do
          :ok ->
            run(conn, next, name)

          :blown ->
            {:error, :econnrefused, conn}

          {:error, :not_found} ->
            :fuse.install(name, Keyword.get(opts, :fuse_opts))
            run(conn, next, name)
        end

      case conn do
        %Maxwell.Conn{} = conn -> response(conn, opts)
        err -> err
      end
    end

    defp run(conn, next, name) do
      case next.(conn) do
        {:error, _reason, _conn} = err ->
          :fuse.melt(name)
          err

        res ->
          res
      end
    end
  end
end