defmodule Exceptional.Value do @moduledoc ~S""" Provide an escape hatch for propagating unraised exceptions ## Convenience `use`s Everything: use Exceptional.Value Only named functions (`exception_or_continue`): use Exceptional.Value, only: :named_functions Only operators (`~>`): use Exceptional.Value, only: :operators """ defmacro __using__(only: :named_functions) do quote do import unquote(__MODULE__), except: [~>: 2] end end defmacro __using__(only: :operators) do quote do import unquote(__MODULE__), only: [~>: 2] end end defmacro __using__(_) do quote do import unquote(__MODULE__) end end @doc ~S""" If an exception, return the exception, otherwise continue computation. Essentially an `Either` construct for `Exception`s. Note that this does not rescue `raise`s. If you want that behaviour, please see `Exceptional.Safe.safe/1` ## Examples iex> 1 |> exception_or_continue(fn value -> value * 100 end.()) 100 iex> %ArgumentError{message: "exception handled"} ...> |> exception_or_continue(fn value -> value * 100 end.()) %ArgumentError{message: "exception handled"} iex> %ArgumentError{message: "exception handled"} ...> |> exception_or_continue(fn x -> x + 1 end.()) ...> |> exception_or_continue(fn y -> y - 10 end.()) %ArgumentError{message: "exception handled"} iex> %ArgumentError{message: "exception not caught"} ...> |> raise ...> |> exception_or_continue(fn value -> value * 100 end.()) ** (ArgumentError) exception not caught iex> Enum.fetch!([], 9) |> exception_or_continue(fn v -> v * 10 end.()) ** (Enum.OutOfBoundsError) out of bounds error """ # @lint {Credo.Check.Design.AliasUsage, false} @spec exception_or_continue(Exception.t() | any, fun) :: Exception.t() | any defmacro exception_or_continue(maybe_exception, continue) do quote do require Exceptional.Control Exceptional.Control.branch unquote(maybe_exception), value_do: unquote(continue), exception_do: fn exception -> exception end.() end end @doc ~S""" Operator alias for `exception_or_continue` ## Examples iex> 1 ~> fn value -> value * 100 end.() 100 iex> exception = %Enum.OutOfBoundsError{message: "exception"} ...> exception ~> fn x -> x + 1 end.() %Enum.OutOfBoundsError{message: "exception"} ...> exception ...> ~> fn x -> x + 1 end.() ...> ~> fn y -> y - 10 end.() %Enum.OutOfBoundsError{message: "exception"} ...> raise(exception) ~> fn x -> x + 1 end.() ** (Enum.OutOfBoundsError) out of bounds error iex> Enum.fetch!([], 9) ~> fn x -> x + 1 end.() ** (Enum.OutOfBoundsError) out of bounds error """ defmacro maybe_exception ~> continue do quote do require Exceptional.Control Exceptional.Control.branch unquote(maybe_exception), value_do: unquote(continue), exception_do: fn exception -> exception end.() end end end