From 1614e3b6b31116a27e117e87f1f34874a2896d3e Mon Sep 17 00:00:00 2001 From: Zach Daniel Date: Tue, 29 Oct 2024 15:40:25 -0400 Subject: [PATCH] improvement: support `.unwrap!/2` on generated splode modules --- .../tutorials/get-started-with-splode.md | 10 ++--- lib/splode.ex | 41 +++++++++++++++++++ test/splode_test.exs | 16 ++++++++ 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/documentation/tutorials/get-started-with-splode.md b/documentation/tutorials/get-started-with-splode.md index d650996..2f1893f 100644 --- a/documentation/tutorials/get-started-with-splode.md +++ b/documentation/tutorials/get-started-with-splode.md @@ -147,15 +147,13 @@ end # Raising Exceptions -To make a `!` version of a function that returns one of these errors is quite simple: +To make a `!` version of a function, use `.unwrap!/2` on your splode module. ```elixir def get_user!(user_id) do - with {:ok, user} <- get_user(user_id) do - {:ok, user} - else - {:error, error} -> raise MyApp.Errors.to_class(error) - end + user_id + |> get_user() + |> MyApp.Errors.unwrap!() end def get_user(user_id) do diff --git a/lib/splode.ex b/lib/splode.ex index 6d65f8c..32e85da 100644 --- a/lib/splode.ex +++ b/lib/splode.ex @@ -97,6 +97,47 @@ defmodule Splode do @error_class_indices @error_classes |> Keyword.keys() |> Enum.with_index() |> Enum.into(%{}) + @doc """ + Raises an error if the result is an error, otherwise returns the result + + Alternatively, you can use the `defsplode` macro, which does this automatically. + + ### Options + + - `:error_opts` - Options to pass to `to_error/2` when converting the returned error + - `:unknown_error_opts` - Options to pass to the unknown error if the function returns only `:error`. + not necessary if your function always returns `{:error, error}`. + + ### Examples + + def function(arg) do + case do_something(arg) do + :success -> :ok + {:success, result} -> {:ok, result} + {:error, error} -> {:error, error} + end + end + + def function!!(arg) do + YourErrors.unwrap!(function(arg)) + end + """ + def unwrap!(result, opts \\ nil) + def unwrap!({:ok, result}, _opts), do: result + def unwrap!(:ok, _), do: :ok + + def unwrap!({:error, error}, opts), do: raise(to_error(error, opts[:error_opts] || [])) + + def unwrap!(:error, opts), + do: raise(@error_classes[:unknown].exception(opts[:unknown_error_opts] || [])) + + def unwrap!(other, opts), + do: + raise( + ArgumentError, + "Invalid value provided to `splode!/2`:\n\n#{inspect(other)}" + ) + @impl true def set_path(errors, path) when is_list(errors) do Enum.map(errors, &set_path(&1, path)) diff --git a/test/splode_test.exs b/test/splode_test.exs index 1eea7ae..33c7627 100644 --- a/test/splode_test.exs +++ b/test/splode_test.exs @@ -94,6 +94,22 @@ defmodule SplodeTest do merge_with: [] end + defmodule Example do + def function() do + {:error, "Error"} + end + + def function!() do + SystemError.unwrap!(function()) + end + end + + test "splode functions work" do + assert_raise SplodeTest.UnknownError, ~r/error: "Error"/, fn -> + Example.function!() + end + end + test "splode_error?" do refute SystemError.splode_error?(:error) refute SystemError.splode_error?(%{})