From bd9e56693eaead643cf7e76f849897b1a22eee19 Mon Sep 17 00:00:00 2001 From: Nathan Alderson Date: Wed, 23 Nov 2022 11:58:50 -0600 Subject: [PATCH 1/9] Make after & else clauses optional for retry macro --- lib/retry.ex | 69 ++++++++++++++++++++++++++++++++++++++------- test/retry_test.exs | 42 +++++++++++++++++++++++++-- 2 files changed, 98 insertions(+), 13 deletions(-) diff --git a/lib/retry.ex b/lib/retry.ex index 6c9329b..897243a 100644 --- a/lib/retry.ex +++ b/lib/retry.ex @@ -33,6 +33,22 @@ defmodule Retry do """ @default_retry_options [atoms: [:error], rescue_only: [RuntimeError]] + @required_retry_options [:with] + @allowed_retry_options @required_retry_options ++ Keyword.keys(@default_retry_options) + + @default_retry_else_clause (quote do error -> raise error end) + @default_retry_after_clause (quote do result -> result end) + @default_retry_clauses [after: @default_retry_after_clause, else: @default_retry_else_clause] + @required_retry_clauses [:do] + @allowed_retry_clauses @required_retry_clauses ++ Keyword.keys(@default_retry_clauses) + + @retry_usage """ + Invalid Syntax. Usage: + + retry with: ... do + ... + end + """ @doc false defmacro __using__(_opts) do @@ -72,14 +88,11 @@ defmodule Retry do end """ - defmacro retry( - [{:with, stream_builder} | opts], - do: do_clause, - after: after_clause, - else: else_clause - ) do - opts = Keyword.merge(@default_retry_options, opts) - atoms = Keyword.get(opts, :atoms) + defmacro retry(opts, clauses) when is_list(opts) and is_list(clauses) do + opts = parse_opts(opts) + [do_clause, after_clause, else_clause] = parse_clauses(clauses) + stream_builder = Keyword.fetch!(opts, :with) + atoms = Keyword.fetch!(opts, :atoms) quote generated: true do fun = unquote(block_runner(do_clause, opts)) @@ -113,8 +126,8 @@ defmodule Retry do end end - defmacro retry(_stream_builder, _clauses) do - raise(ArgumentError, ~s(invalid syntax, only "retry", "after" and "else" are permitted)) + defmacro retry(_, _) do + raise(ArgumentError, @retry_usage) end @doc """ @@ -279,4 +292,40 @@ defmodule Retry do [0] |> Stream.concat(delays) end end + + defp parse_opts(opts) do + cond do + !Keyword.keyword?(opts) -> + raise(ArgumentError, @retry_usage) + + missing_opt = Enum.find(@required_retry_options, &(&1 not in Keyword.keys(opts))) -> + raise(ArgumentError, ~s(invalid syntax: you must provide the "#{missing_opt}" option)) + + invalid_opt = Enum.find(Keyword.keys(opts), &(&1 not in @allowed_retry_options)) -> + raise(ArgumentError, ~s(invalid syntax: option "#{invalid_opt}" is not supported)) + + true -> + Keyword.merge(@default_retry_options, opts) + end + end + + defp parse_clauses(clauses) do + cond do + !Keyword.keyword?(clauses) -> + raise(ArgumentError, @retry_usage) + + missing_clause = Enum.find(@required_retry_clauses, &(&1 not in Keyword.keys(clauses))) -> + raise(ArgumentError, ~s(invalid syntax: you must provide a "#{missing_clause}" clause)) + + invalid_clause = Enum.find(Keyword.keys(clauses), &(&1 not in @allowed_retry_clauses)) -> + raise( + ArgumentError, + ~s(invalid syntax: clause "#{invalid_clause}" is not supported) + ) + + true -> + clauses_with_defaults = Keyword.merge(@default_retry_clauses, clauses) + Enum.map(@allowed_retry_clauses, &Keyword.get(clauses_with_defaults, &1)) + end + end end diff --git a/test/retry_test.exs b/test/retry_test.exs index 01d5989..103cf07 100644 --- a/test/retry_test.exs +++ b/test/retry_test.exs @@ -151,6 +151,28 @@ defmodule RetryTest do assert result == {:ok, "Everything's so awesome!"} end + test "uses the default 'after' action" do + result = + retry with: linear_backoff(50, 1) |> take(5) do + {:ok, "Everything's so awesome!"} + end + + assert result == {:ok, "Everything's so awesome!"} + end + + test "uses the default 'else' action" do + {elapsed, _} = + :timer.tc(fn -> + assert_raise CustomError, fn -> + retry with: linear_backoff(50, 1) |> take(5) do + raise CustomError + end + end + end) + + assert elapsed / 1_000 < 250 + end + test "stream builder works with any Enum" do {elapsed, _} = :timer.tc(fn -> @@ -170,11 +192,25 @@ defmodule RetryTest do end test "with invalid clauses raises argument error" do - error_message = ~s/invalid syntax, only "retry", "after" and "else" are permitted/ - - assert_raise ArgumentError, error_message, fn -> + assert_raise ArgumentError, ~r/Invalid Syntax. Usage:/, fn -> Code.eval_string("retry [1, 2, 3], foo: :invalid, bar: :not_ok", [], __ENV__) end + + assert_raise ArgumentError, ~r/you must provide the "with" option/, fn -> + Code.eval_string("retry [foo: :invalid], bar: :not_ok", [], __ENV__) + end + + assert_raise ArgumentError, ~r/option "foo" is not supported/, fn -> + Code.eval_string("retry [with: :ok, foo: :invalid], bar: :not_ok", [], __ENV__) + end + + assert_raise ArgumentError, ~r/you must provide a "do" clause/, fn -> + Code.eval_string("retry [with: [1]], bar: :not_ok", [], __ENV__) + end + + assert_raise ArgumentError, ~r/clause "foo" is not supported/, fn -> + Code.eval_string("retry [with: [1]], do: :ok, foo: :invalid", [], __ENV__) + end end end From 54add38f11a01449b1f26383128f35cb10bf48c6 Mon Sep 17 00:00:00 2001 From: Nathan Alderson Date: Wed, 23 Nov 2022 12:27:44 -0600 Subject: [PATCH 2/9] Run formatter --- lib/retry.ex | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/retry.ex b/lib/retry.ex index 897243a..da2b3c2 100644 --- a/lib/retry.ex +++ b/lib/retry.ex @@ -36,8 +36,12 @@ defmodule Retry do @required_retry_options [:with] @allowed_retry_options @required_retry_options ++ Keyword.keys(@default_retry_options) - @default_retry_else_clause (quote do error -> raise error end) - @default_retry_after_clause (quote do result -> result end) + @default_retry_else_clause (quote do + error -> raise error + end) + @default_retry_after_clause (quote do + result -> result + end) @default_retry_clauses [after: @default_retry_after_clause, else: @default_retry_else_clause] @required_retry_clauses [:do] @allowed_retry_clauses @required_retry_clauses ++ Keyword.keys(@default_retry_clauses) From 91a9338ea354d96e7675ea06971639d0a7224f72 Mon Sep 17 00:00:00 2001 From: Nathan Alderson Date: Thu, 24 Nov 2022 00:21:27 -0600 Subject: [PATCH 3/9] Support flexible rescue_only parameters --- .tool-versions | 2 +- lib/retry.ex | 26 ++++++++++++++++++++++++-- test/retry_test.exs | 40 +++++++++++++++++++++++++--------------- 3 files changed, 50 insertions(+), 18 deletions(-) diff --git a/.tool-versions b/.tool-versions index a59c0ac..7b69108 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -elixir 1.13.4 +elixir 1.13.4-otp-24 erlang 24.3 diff --git a/lib/retry.ex b/lib/retry.ex index da2b3c2..e5ffa88 100644 --- a/lib/retry.ex +++ b/lib/retry.ex @@ -258,9 +258,24 @@ defmodule Retry do defp block_runner(block, opts) do atoms = Keyword.get(opts, :atoms) - exceptions = Keyword.get(opts, :rescue_only) + rescue_onlies = Keyword.get(opts, :rescue_only) quote generated: true do + call_partial = fn f, e -> + try do + f.(e) + rescue + FunctionClauseError -> false + end + end + + should_retry = fn + _e, :all -> true + e, s when is_atom(s) -> is_struct(e, s) + e, f when is_function(f) -> call_partial.(f, e) + _, _ -> true + end + fn -> try do case unquote(block) do @@ -280,7 +295,14 @@ defmodule Retry do end rescue e -> - if e.__struct__ in unquote(exceptions) do + retry? = + if is_list(unquote(rescue_onlies)) do + Enum.any?(unquote(rescue_onlies), &should_retry.(e, &1)) + else + should_retry.(e, unquote(rescue_onlies)) + end + + if retry? do {:cont, {:exception, e}} else reraise e, __STACKTRACE__ diff --git a/test/retry_test.exs b/test/retry_test.exs index 103cf07..9577d0d 100644 --- a/test/retry_test.exs +++ b/test/retry_test.exs @@ -102,23 +102,33 @@ defmodule RetryTest do assert elapsed / 1_000 >= 250 end - test "retries execution when a whitelisted exception is raised" do - custom_error_list = [CustomError] - - {elapsed, _} = - :timer.tc(fn -> - assert_raise CustomError, fn -> - retry with: linear_backoff(50, 1) |> take(5), rescue_only: custom_error_list do - raise CustomError - after - _ -> :ok - else - error -> raise error + test "retries execution when an allowed exception is raised" do + testcases = [ + CustomError, + [OtherThing, CustomError], + :all, + [:other_thing, :all], + fn _ -> true end, + [fn _ -> false end, fn _ -> true end], + [fn :partial -> true end, fn _ -> true end] + ] + + for testcase <- testcases do + {elapsed, _} = + :timer.tc(fn -> + assert_raise CustomError, fn -> + retry with: linear_backoff(50, 1) |> take(5), rescue_only: testcase do + raise CustomError + after + _ -> :ok + else + error -> raise error + end end - end - end) + end) - assert elapsed / 1_000 >= 250 + assert elapsed / 1_000 >= 250 + end end test "does not retry execution when an unknown exception is raised" do From dbaead11e18e94429d42848b523264a33a2673d8 Mon Sep 17 00:00:00 2001 From: Nathan Alderson Date: Thu, 24 Nov 2022 17:17:13 -0600 Subject: [PATCH 4/9] Support flexible atoms parameters --- lib/retry.ex | 45 +++++++++++++----------------- test/retry_test.exs | 67 +++++++++++++++++++++------------------------ 2 files changed, 50 insertions(+), 62 deletions(-) diff --git a/lib/retry.ex b/lib/retry.ex index e5ffa88..bf5db50 100644 --- a/lib/retry.ex +++ b/lib/retry.ex @@ -96,7 +96,6 @@ defmodule Retry do opts = parse_opts(opts) [do_clause, after_clause, else_clause] = parse_clauses(clauses) stream_builder = Keyword.fetch!(opts, :with) - atoms = Keyword.fetch!(opts, :atoms) quote generated: true do fun = unquote(block_runner(do_clause, opts)) @@ -112,12 +111,7 @@ defmodule Retry do unquote(else_clause) end - e = {atom, _} when atom in unquote(atoms) -> - case e do - unquote(else_clause) - end - - e when is_atom(e) and e in unquote(atoms) -> + {:retriable, e} -> case e do unquote(else_clause) end @@ -261,37 +255,36 @@ defmodule Retry do rescue_onlies = Keyword.get(opts, :rescue_only) quote generated: true do - call_partial = fn f, e -> + call_partial = fn f, x -> try do - f.(e) + !!f.(x) rescue FunctionClauseError -> false end end should_retry = fn - _e, :all -> true - e, s when is_atom(s) -> is_struct(e, s) - e, f when is_function(f) -> call_partial.(f, e) - _, _ -> true + _x, :all -> true + x, a when is_atom(x) and is_atom(a) -> x == a + x, a when is_struct(x) and is_atom(a) -> is_struct(x, a) + {x, _}, a when is_atom(x) and is_atom(a) -> x == a + x, f when is_function(f) -> call_partial.(f, x) + _, _ -> false end fn -> try do - case unquote(block) do - {atom, _} = result -> - if atom in unquote(atoms) do - {:cont, result} - else - {:halt, result} - end + retry? = + if is_list(unquote(atoms)) do + Enum.any?(unquote(atoms), &should_retry.(unquote(block), &1)) + else + should_retry.(unquote(block), unquote(atoms)) + end - result -> - if is_atom(result) and result in unquote(atoms) do - {:cont, result} - else - {:halt, result} - end + if retry? do + {:cont, {:retriable, unquote(block)}} + else + {:halt, unquote(block)} end rescue e -> diff --git a/test/retry_test.exs b/test/retry_test.exs index 9577d0d..4862453 100644 --- a/test/retry_test.exs +++ b/test/retry_test.exs @@ -7,6 +7,7 @@ defmodule RetryTest do doctest Retry defmodule(CustomError, do: defexception(message: "custom error!")) + defmodule(NotOkay, do: defstruct([])) describe "retry" do test "retries execution for specified attempts when result is error tuple" do @@ -45,44 +46,38 @@ defmodule RetryTest do assert elapsed / 1_000 >= 250 end - test "retries execution for specified attempts when result is a specified atom" do - retry_atom = :not_ok - - {elapsed, _} = - :timer.tc(fn -> - result = - retry with: linear_backoff(50, 1) |> take(5), atoms: [retry_atom] do - retry_atom - after - _ -> :ok - else - error -> error - end - - assert result == retry_atom - end) - - assert elapsed / 1_000 >= 250 - end - - test "retries execution for specified attempts when result is a tuple with a specified atom" do - retry_atom = :not_ok + test "retries execution for specified attempts when allowed result is returned" do + testcases = [ + {:not_ok, :all}, + {:not_ok, [:foo, :all]}, + {:not_ok, :not_ok}, + {:not_ok, [:foo, :not_ok]}, + {{:not_ok, :foo}, [:foo, :not_ok]}, + {%NotOkay{}, NotOkay}, + {%NotOkay{}, [Foo, NotOkay]}, + {:not_ok, fn _ -> true end}, + {:not_ok, [fn _ -> false end, fn _ -> true end]}, + {:not_ok, [fn _ -> nil end, fn _ -> 1 end]}, + {:not_ok, [fn :partial -> false end, fn _ -> true end]} + ] - {elapsed, _} = - :timer.tc(fn -> - result = - retry with: linear_backoff(50, 1) |> take(5), atoms: [retry_atom] do - {retry_atom, "Some error message"} - after - _ -> :ok - else - error -> error - end + for {rval, atoms} <- testcases do + {elapsed, _} = + :timer.tc(fn -> + result = + retry with: linear_backoff(50, 1) |> take(5), atoms: atoms do + rval + after + _ -> :ok + else + error -> error + end - assert result == {retry_atom, "Some error message"} - end) + assert result == rval + end) - assert elapsed / 1_000 >= 250 + assert elapsed / 1_000 >= 250 + end end test "retries execution for specified attempts when error is raised" do @@ -110,7 +105,7 @@ defmodule RetryTest do [:other_thing, :all], fn _ -> true end, [fn _ -> false end, fn _ -> true end], - [fn :partial -> true end, fn _ -> true end] + [fn :partial -> false end, fn _ -> true end] ] for testcase <- testcases do From 4d3a7b689f2c75dc2776455763be2ab23d25d24c Mon Sep 17 00:00:00 2001 From: Nathan Alderson Date: Fri, 31 Mar 2023 13:30:53 -0500 Subject: [PATCH 5/9] Fix extra runs --- lib/retry.ex | 10 ++++++---- test/retry_test.exs | 21 +++++++++++++-------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/lib/retry.ex b/lib/retry.ex index 84cd56a..dc887bc 100644 --- a/lib/retry.ex +++ b/lib/retry.ex @@ -329,17 +329,19 @@ defmodule Retry do fn -> try do + result = unquote(block) + retry? = if is_list(unquote(atoms)) do - Enum.any?(unquote(atoms), &should_retry.(unquote(block), &1)) + Enum.any?(unquote(atoms), &should_retry.(result, &1)) else - should_retry.(unquote(block), unquote(atoms)) + should_retry.(result, unquote(atoms)) end if retry? do - {:cont, {:retriable, unquote(block)}} + {:cont, {:retriable, result}} else - {:halt, unquote(block)} + {:halt, result} end rescue e -> diff --git a/test/retry_test.exs b/test/retry_test.exs index 71feb0b..da6ef5c 100644 --- a/test/retry_test.exs +++ b/test/retry_test.exs @@ -3,6 +3,8 @@ defmodule RetryTest do use Retry import Stream + import ExUnit.CaptureLog + require Logger doctest Retry @@ -144,16 +146,19 @@ defmodule RetryTest do end test "does not have to retry execution when there is no error" do - result = - retry with: linear_backoff(50, 1) |> take(5) do - {:ok, "Everything's so awesome!"} - after - result -> result - else - _ -> :error + f = fn -> + retry with: linear_backoff(50, 1) |> take(5) do + Logger.info("running") + {:ok, "Everything's so awesome!"} + after + result -> result + else + _ -> :error + end end - assert result == {:ok, "Everything's so awesome!"} + assert f.() == {:ok, "Everything's so awesome!"} + assert Regex.scan(~r/running/, capture_log(f)) |> length == 1 end test "uses the default 'after' action" do From 85dcb9fb02cb96188a08f1fb4e4e2ca06f90e73f Mon Sep 17 00:00:00 2001 From: Nathan Alderson Date: Wed, 12 Apr 2023 09:15:25 -0500 Subject: [PATCH 6/9] Add a few test cases --- test/retry_test.exs | 50 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/test/retry_test.exs b/test/retry_test.exs index da6ef5c..49f7c3c 100644 --- a/test/retry_test.exs +++ b/test/retry_test.exs @@ -60,7 +60,12 @@ defmodule RetryTest do {:not_ok, fn _ -> true end}, {:not_ok, [fn _ -> false end, fn _ -> true end]}, {:not_ok, [fn _ -> nil end, fn _ -> 1 end]}, - {:not_ok, [fn :partial -> false end, fn _ -> true end]} + {:not_ok, [fn :partial -> false end, fn _ -> true end]}, + {:not_ok, + fn + :partial -> false + :not_ok -> true + end} ] for {rval, atoms} <- testcases do @@ -82,6 +87,22 @@ defmodule RetryTest do end end + test "does not retry on :error if atoms is specified" do + f = fn -> + retry with: linear_backoff(50, 1) |> take(5), atoms: :not_ok do + Logger.info("running") + :error + after + result -> result + else + error -> :not_this + end + end + + assert f.() == :error + assert Regex.scan(~r/running/, capture_log(f)) |> length == 1 + end + test "retries execution for specified attempts when error is raised" do {elapsed, _} = :timer.tc(fn -> @@ -145,17 +166,34 @@ defmodule RetryTest do assert elapsed / 1_000 < 250 end - test "does not have to retry execution when there is no error" do + test "does not retry on RuntimeError if some other rescue_only is specified" do f = fn -> - retry with: linear_backoff(50, 1) |> take(5) do + assert_raise RuntimeError, fn -> + retry with: linear_backoff(50, 1) |> take(5), rescue_only: CustomError do Logger.info("running") - {:ok, "Everything's so awesome!"} + raise RuntimeError after - result -> result + _ -> :ok else - _ -> :error + error -> raise error end end + end + + assert Regex.scan(~r/running/, capture_log(f)) |> length == 1 + end + + test "does not have to retry execution when there is no error" do + f = fn -> + retry with: linear_backoff(50, 1) |> take(5) do + Logger.info("running") + {:ok, "Everything's so awesome!"} + after + result -> result + else + _ -> :error + end + end assert f.() == {:ok, "Everything's so awesome!"} assert Regex.scan(~r/running/, capture_log(f)) |> length == 1 From 22f1bd9071ce2843ec75ff943e88293cb90cd911 Mon Sep 17 00:00:00 2001 From: Nathan Alderson Date: Wed, 12 Apr 2023 10:30:16 -0500 Subject: [PATCH 7/9] Update documentation --- README.md | 18 ++++++++++++++++-- lib/retry.ex | 30 ++++++++++++++++++++++++------ 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index e022a64..99ab358 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,23 @@ Check out the [API reference](https://hexdocs.pm/retry/api-reference.html) for t The `retry([with: _,] do: _, after: _, else: _)` macro provides a way to retry a block of code on failure with a variety of delay and give up behaviors. By default, the execution of a block is considered a failure if it returns `:error`, `{:error, _}` or raises a runtime error. -An optional list of atoms can be specified in `:atoms` if you need to retry anything other than `:error` or `{:error, _}`, e.g. `retry([with: _, atoms: [:not_ok]], do: _, after: _, else: _)`. +Both the values and exceptions that will be retried can be customized. To control which values will be retried, provide the `atoms` option. To control which exceptions are retried, provide the `rescue_only` option. For example: -Similarly, an optional list of exceptions can be specified in `:rescue_only` if you need to retry anything other than `RuntimeError`, e.g. `retry([with: _, rescue_only: [CustomError]], do: _, after: _, else: _)`. +``` +retry with: ..., atoms: [:not_ok], rescue_only: [CustomError] do + ... +end +``` + +Both `atoms` and `rescue_only` can accept a number of different types: + +* An atom (for example: `:not_okay`, `SomeStruct`, or `CustomError`). In this case, the `do` block will be retried in any of the following cases: + * The atom itself is returned + * The atom is returned in the first position of a two-tuple (for example, `{:not_okay, _}`) + * A struct of that type is returned/raised +* The special atom `:all`. In this case, all values/exceptions will be retried. +* A function (for example: `fn val -> val.starts_with("ok") end`) or partial function (for example: `fn {:error, %SomeStruct{reason: "busy"}} -> true`). The function will be called with the return value and the `do` block will be retried if the function returns a truthy value. If the function returns a falsy value or if no function clause matches, the `do` block will not be retried. +* A list of any of the above. The `do` block will be retried if any of the items in the list matches. The `after` block evaluates only when the `do` block returns a valid value before timeout. On the other hand, the `else` block evaluates only when the `do` block remains erroneous after timeout. Both are optional. By default, the `else` clause will return the last erroneous value or re-raise the last exception. The default `after` clause will simply return the last successful value. diff --git a/lib/retry.ex b/lib/retry.ex index dc887bc..05de332 100644 --- a/lib/retry.ex +++ b/lib/retry.ex @@ -103,13 +103,31 @@ defmodule Retry do Retry a block of code delaying between each attempt the duration specified by the next item in the `with` delay stream. - If the block returns any of the atoms specified in `atoms`, a retry will be attempted. - Other atoms or atom-result tuples will not be retried. If `atoms` is not specified, - it defaults to `[:error]`. + Both the values and exceptions that will be retried can be customized. To control which values + will be retried, provide the `atoms` option. To control which exceptions are retried, provide + the `rescue_only` option. For example: - Similary, if the block raises any of the exceptions specified in `rescue_only`, a retry - will be attempted. Other exceptions will not be retried. If `rescue_only` is - not specified, it defaults to `[RuntimeError]`. + ``` + retry with: ..., atoms: [:not_ok], rescue_only: [CustomError] do + ... + end + ``` + + Both `atoms` and `rescue_only` can accept a number of different types: + + * An atom (for example: `:not_okay`, `SomeStruct`, or `CustomError`). In this case, the `do` + block will be retried in any of the following cases: + * The atom itself is returned + * The atom is returned in the first position of a two-tuple (for example, `{:not_okay, _}`) + * A struct of that type is returned/raised + * The special atom `:all`. In this case, all values/exceptions will be retried. + * A function (for example: `fn val -> val.starts_with("ok") end`) or partial function (for + example: `fn {:error, %SomeStruct{reason: "busy"}} -> true`). The function will be called with + the return value and the `do` block will be retried if the function returns a truthy value. + If the function returns a falsy value or if no function clause matches, the `do` block + will not be retried. + * A list of any of the above. The `do` block will be retried if any of the items in the list + matches. The `after` block evaluates only when the `do` block returns a valid value before timeout. From 72a210d05dcd32f35707305e6dc902dda4a5db18 Mon Sep 17 00:00:00 2001 From: Nathan Alderson Date: Wed, 12 Apr 2023 20:50:37 -0500 Subject: [PATCH 8/9] Fix unused variable warning --- test/retry_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/retry_test.exs b/test/retry_test.exs index 49f7c3c..c9e7140 100644 --- a/test/retry_test.exs +++ b/test/retry_test.exs @@ -95,7 +95,7 @@ defmodule RetryTest do after result -> result else - error -> :not_this + _error -> :not_this end end From ee87ac30e93a59f6931d442053c8ffb31a91f022 Mon Sep 17 00:00:00 2001 From: Nathan Alderson Date: Wed, 12 Apr 2023 20:53:22 -0500 Subject: [PATCH 9/9] Fix example code in docs --- README.md | 2 +- lib/retry.ex | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 99ab358..a837392 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ Both `atoms` and `rescue_only` can accept a number of different types: * The atom is returned in the first position of a two-tuple (for example, `{:not_okay, _}`) * A struct of that type is returned/raised * The special atom `:all`. In this case, all values/exceptions will be retried. -* A function (for example: `fn val -> val.starts_with("ok") end`) or partial function (for example: `fn {:error, %SomeStruct{reason: "busy"}} -> true`). The function will be called with the return value and the `do` block will be retried if the function returns a truthy value. If the function returns a falsy value or if no function clause matches, the `do` block will not be retried. +* A function (for example: `fn val -> String.starts_with?(val, "ok") end`) or partial function (for example: `fn {:error, %SomeStruct{reason: "busy"}} -> true`). The function will be called with the return value and the `do` block will be retried if the function returns a truthy value. If the function returns a falsy value or if no function clause matches, the `do` block will not be retried. * A list of any of the above. The `do` block will be retried if any of the items in the list matches. The `after` block evaluates only when the `do` block returns a valid value before timeout. On the other hand, the `else` block evaluates only when the `do` block remains erroneous after timeout. Both are optional. By default, the `else` clause will return the last erroneous value or re-raise the last exception. The default `after` clause will simply return the last successful value. diff --git a/lib/retry.ex b/lib/retry.ex index 05de332..2c5372a 100644 --- a/lib/retry.ex +++ b/lib/retry.ex @@ -121,9 +121,9 @@ defmodule Retry do * The atom is returned in the first position of a two-tuple (for example, `{:not_okay, _}`) * A struct of that type is returned/raised * The special atom `:all`. In this case, all values/exceptions will be retried. - * A function (for example: `fn val -> val.starts_with("ok") end`) or partial function (for - example: `fn {:error, %SomeStruct{reason: "busy"}} -> true`). The function will be called with - the return value and the `do` block will be retried if the function returns a truthy value. + * A function (for example: `fn val -> String.starts_with?(val, "ok") end`) or partial function + (for example: `fn {:error, %SomeStruct{reason: "busy"}} -> true`). The function will be called + with the return value and the `do` block will be retried if the function returns a truthy value. If the function returns a falsy value or if no function clause matches, the `do` block will not be retried. * A list of any of the above. The `do` block will be retried if any of the items in the list