mirror of
https://github.com/ash-project/splode.git
synced 2024-09-19 12:52:46 +12:00
improvement!: message/1 instead of splode_message/1
improvement: add `use Splode.ErrorClass` improvement: store the module that created an error in the `splode` key
This commit is contained in:
parent
a59673a281
commit
cd27664388
5 changed files with 141 additions and 60 deletions
|
@ -17,29 +17,21 @@ end
|
|||
|
||||
# Error classes are splode errors with an `errors` key.
|
||||
defmodule MyApp.Errors.Invalid do
|
||||
use Splode.Error, fields: [:errors], class: :invalid
|
||||
|
||||
def splode_message(%{errors: errors}) do
|
||||
Splode.ErrorClass.error_messages(errors)
|
||||
end
|
||||
use Splode.ErrorClass, class: :invalid
|
||||
end
|
||||
|
||||
# You will want to define an unknown error class,
|
||||
# otherwise splode will use its own
|
||||
defmodule MyApp.Errors.Unknown do
|
||||
use Splode.Error, fields: [:errors], class: :unknown
|
||||
|
||||
def splode_message(%{errors: errors}) do
|
||||
Splode.ErrorClass.error_messages(errors)
|
||||
end
|
||||
use Splode.ErrorClass, class: :unknown
|
||||
end
|
||||
|
||||
# This fallback exception will be used for unknown errors
|
||||
defmodule MyApp.Errors.Unknown.Unknown do
|
||||
use Splode.Error, fields: [:error], class: :unknown
|
||||
use Splode.Error, class: :unknown
|
||||
|
||||
# your unknown message should have an `error` key
|
||||
def splode_message(%{error: error}) do
|
||||
def message(%{error: error}) do
|
||||
if is_binary(error) do
|
||||
to_string(error)
|
||||
else
|
||||
|
@ -53,7 +45,7 @@ end
|
|||
defmodule MyApp.Errors.InvalidArgument do
|
||||
use Splode.Error, fields: [:name, :message], class: :invalid
|
||||
|
||||
def splode_message(%{name: name, message: message}) do
|
||||
def message(%{name: name, message: message}) do
|
||||
"Invalid argument #{name}: #{message}"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -139,10 +139,27 @@ defmodule Splode do
|
|||
|
||||
def splode_error?(_), do: false
|
||||
|
||||
def splode_error?(%struct{splode: splode}, splode) do
|
||||
struct.splode_error?()
|
||||
rescue
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
|
||||
def splode_error?(%struct{splode: nil}, _splode) do
|
||||
struct.splode_error?()
|
||||
rescue
|
||||
_ ->
|
||||
false
|
||||
end
|
||||
|
||||
def splode_error?(_, _), do: false
|
||||
|
||||
@impl true
|
||||
def to_class(value, opts \\ [])
|
||||
|
||||
def to_class(%struct{errors: [error]} = class, _opts) when struct in @class_modules do
|
||||
def to_class(%struct{errors: [error]} = class, _opts)
|
||||
when struct in @class_modules do
|
||||
if error.class == :special do
|
||||
error
|
||||
else
|
||||
|
@ -152,7 +169,7 @@ defmodule Splode do
|
|||
|
||||
def to_class(value, opts) when not is_list(value) do
|
||||
if splode_error?(value) && value.class == :special do
|
||||
value
|
||||
Map.put(value, :splode, __MODULE__)
|
||||
else
|
||||
to_class([value], opts)
|
||||
end
|
||||
|
@ -174,14 +191,18 @@ defmodule Splode do
|
|||
|> flatten_preserving_keywords()
|
||||
|> Enum.uniq_by(&clear_stacktraces/1)
|
||||
|> Enum.map(fn value ->
|
||||
if splode_error?(value) do
|
||||
value
|
||||
if splode_error?(value, __MODULE__) do
|
||||
Map.put(value, :splode, __MODULE__)
|
||||
else
|
||||
exception_opts =
|
||||
if opts[:stacktrace] do
|
||||
[error: value, stacktrace: %Splode.Stacktrace{stacktrace: opts[:stacktrace]}]
|
||||
[
|
||||
error: value,
|
||||
stacktrace: %Splode.Stacktrace{stacktrace: opts[:stacktrace]},
|
||||
splode: __MODULE__
|
||||
]
|
||||
else
|
||||
[error: value]
|
||||
[error: value, splode: __MODULE__]
|
||||
end
|
||||
|
||||
@unknown_error.exception(exception_opts)
|
||||
|
@ -189,11 +210,12 @@ defmodule Splode do
|
|||
end)
|
||||
|> choose_error()
|
||||
|> accumulate_bread_crumbs(opts[:bread_crumbs])
|
||||
|> Map.put(:splode, __MODULE__)
|
||||
end
|
||||
end
|
||||
|
||||
defp choose_error([]) do
|
||||
@error_classes[:unknown].exception([])
|
||||
@error_classes[:unknown].exception(splode: __MODULE__)
|
||||
end
|
||||
|
||||
defp choose_error(errors) do
|
||||
|
@ -211,7 +233,7 @@ defmodule Splode do
|
|||
if parent_error_module == error.__struct__ do
|
||||
%{error | errors: (error.errors || []) ++ other_errors}
|
||||
else
|
||||
parent_error_module.exception(errors: errors)
|
||||
parent_error_module.exception(errors: errors, splode: __MODULE__)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -224,6 +246,7 @@ defmodule Splode do
|
|||
|> Keyword.take([:error, :vars])
|
||||
|> Keyword.put_new(:error, list[:message])
|
||||
|> Keyword.put_new(:value, list)
|
||||
|> Keyword.put(:splode, __MODULE__)
|
||||
|> @unknown_error.exception()
|
||||
|> add_stacktrace(opts[:stacktrace])
|
||||
|> accumulate_bread_crumbs(opts[:bread_crumbs])
|
||||
|
@ -239,7 +262,7 @@ defmodule Splode do
|
|||
end
|
||||
|
||||
def to_error(error, opts) when is_binary(error) do
|
||||
[error: error]
|
||||
[error: error, splode: __MODULE__]
|
||||
|> @unknown_error.exception()
|
||||
|> Map.put(:stacktrace, nil)
|
||||
|> add_stacktrace(opts[:stacktrace])
|
||||
|
@ -248,20 +271,21 @@ defmodule Splode do
|
|||
|
||||
def to_error(other, opts) do
|
||||
cond do
|
||||
splode_error?(other) ->
|
||||
splode_error?(other, __MODULE__) ->
|
||||
other
|
||||
|> Map.put(:splode, __MODULE__)
|
||||
|> add_stacktrace(opts[:stacktrace])
|
||||
|> accumulate_bread_crumbs(opts[:bread_crumbs])
|
||||
|
||||
is_exception(other) ->
|
||||
[error: Exception.format(:error, other)]
|
||||
[error: Exception.format(:error, other), splode: __MODULE__]
|
||||
|> @unknown_error.exception()
|
||||
|> Map.put(:stacktrace, nil)
|
||||
|> add_stacktrace(opts[:stacktrace])
|
||||
|> accumulate_bread_crumbs(opts[:bread_crumbs])
|
||||
|
||||
true ->
|
||||
[error: "unknown error: #{inspect(other)}"]
|
||||
[error: "unknown error: #{inspect(other)}", splode: __MODULE__]
|
||||
|> @unknown_error.exception()
|
||||
|> Map.put(:stacktrace, nil)
|
||||
|> add_stacktrace(opts[:stacktrace])
|
||||
|
|
|
@ -8,7 +8,7 @@ defmodule Splode.Error do
|
|||
defmodule MyApp.Errors.InvalidArgument do
|
||||
use Splode.Error, fields: [:name, :message], class: :invalid
|
||||
|
||||
def splode_message(%{name: name, message: message}) do
|
||||
def message(%{name: name, message: message}) do
|
||||
"Invalid argument \#{name}: \#{message}"
|
||||
end
|
||||
end
|
||||
|
@ -16,7 +16,7 @@ defmodule Splode.Error do
|
|||
"""
|
||||
@callback splode_error?() :: boolean()
|
||||
@callback from_json(map) :: struct()
|
||||
@callback splode_message(struct()) :: String.t()
|
||||
@callback error_class?() :: boolean()
|
||||
@type t :: Exception.t()
|
||||
|
||||
@doc false
|
||||
|
@ -28,8 +28,9 @@ defmodule Splode.Error do
|
|||
end
|
||||
|
||||
defmacro __using__(opts) do
|
||||
quote generated: true, bind_quoted: [opts: opts] do
|
||||
quote generated: true, bind_quoted: [opts: opts, mod: __MODULE__] do
|
||||
@behaviour Splode.Error
|
||||
@error_class !!opts[:error_class?]
|
||||
|
||||
if !opts[:class] do
|
||||
raise "Must provide an error class for a splode error, i.e `use Splode.Error, class: :invalid`"
|
||||
|
@ -37,6 +38,7 @@ defmodule Splode.Error do
|
|||
|
||||
defexception List.wrap(opts[:fields]) ++
|
||||
[
|
||||
splode: nil,
|
||||
bread_crumbs: [],
|
||||
vars: [],
|
||||
path: [],
|
||||
|
@ -44,43 +46,47 @@ defmodule Splode.Error do
|
|||
class: opts[:class]
|
||||
]
|
||||
|
||||
@before_compile mod
|
||||
|
||||
@impl Splode.Error
|
||||
def splode_error?, do: true
|
||||
|
||||
@impl Exception
|
||||
def message(%{vars: vars} = exception) do
|
||||
string = splode_message(exception)
|
||||
@impl Splode.Error
|
||||
def error_class?, do: @error_class
|
||||
|
||||
string =
|
||||
case Splode.ErrorClass.bread_crumb(exception.bread_crumbs) do
|
||||
"" ->
|
||||
string
|
||||
|
||||
context ->
|
||||
context <> "\n" <> string
|
||||
end
|
||||
|
||||
Enum.reduce(List.wrap(vars), string, fn {key, value}, acc ->
|
||||
if String.contains?(acc, "%{#{key}}") do
|
||||
String.replace(acc, "%{#{key}}", to_string(value))
|
||||
else
|
||||
acc
|
||||
end
|
||||
end)
|
||||
end
|
||||
def exception, do: exception([])
|
||||
|
||||
@impl Exception
|
||||
def exception(opts) do
|
||||
opts =
|
||||
if is_nil(opts[:stacktrace]) do
|
||||
{:current_stacktrace, stacktrace} = Process.info(self(), :current_stacktrace)
|
||||
if @error_class && match?([%error{class: :special} = special], opts[:errors]) do
|
||||
special_error = Enum.at(opts[:errors], 0)
|
||||
|
||||
Keyword.put(opts, :stacktrace, %Splode.Stacktrace{stacktrace: stacktrace})
|
||||
if special_error.__struct__.splode_error?() do
|
||||
special_error
|
||||
else
|
||||
opts
|
||||
end
|
||||
opts =
|
||||
if is_nil(opts[:stacktrace]) do
|
||||
{:current_stacktrace, stacktrace} = Process.info(self(), :current_stacktrace)
|
||||
|
||||
super(opts) |> Map.update(:vars, [], &Splode.Error.clean_vars/1)
|
||||
Keyword.put(opts, :stacktrace, %Splode.Stacktrace{stacktrace: stacktrace})
|
||||
else
|
||||
opts
|
||||
end
|
||||
|
||||
super(opts) |> Map.update(:vars, [], &Splode.Error.clean_vars/1)
|
||||
end
|
||||
else
|
||||
opts =
|
||||
if is_nil(opts[:stacktrace]) do
|
||||
{:current_stacktrace, stacktrace} = Process.info(self(), :current_stacktrace)
|
||||
|
||||
Keyword.put(opts, :stacktrace, %Splode.Stacktrace{stacktrace: stacktrace})
|
||||
else
|
||||
opts
|
||||
end
|
||||
|
||||
super(opts) |> Map.update(:vars, [], &Splode.Error.clean_vars/1)
|
||||
end
|
||||
end
|
||||
|
||||
@impl Splode.Error
|
||||
|
@ -97,6 +103,36 @@ defmodule Splode.Error do
|
|||
end
|
||||
end
|
||||
|
||||
defmacro __before_compile__(env) do
|
||||
if Module.defines?(env.module, {:message, 1}, :def) do
|
||||
quote generated: true do
|
||||
defoverridable message: 1
|
||||
|
||||
@impl true
|
||||
def message(%{vars: vars} = exception) do
|
||||
string = super(exception)
|
||||
|
||||
string =
|
||||
case Splode.ErrorClass.bread_crumb(exception.bread_crumbs) do
|
||||
"" ->
|
||||
string
|
||||
|
||||
context ->
|
||||
context <> "\n" <> string
|
||||
end
|
||||
|
||||
Enum.reduce(List.wrap(vars), string, fn {key, value}, acc ->
|
||||
if String.contains?(acc, "%{#{key}}") do
|
||||
String.replace(acc, "%{#{key}}", to_string(value))
|
||||
else
|
||||
acc
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc false
|
||||
def clean_vars(vars) when is_map(vars) do
|
||||
clean_vars(Map.to_list(vars))
|
||||
|
|
|
@ -1,6 +1,38 @@
|
|||
defmodule Splode.ErrorClass do
|
||||
@moduledoc "Tools for working with error classes"
|
||||
|
||||
defmacro __using__(opts) do
|
||||
quote bind_quoted: [opts: opts] do
|
||||
opts =
|
||||
Keyword.update(opts, :fields, [errors: []], fn fields ->
|
||||
has_error_fields? =
|
||||
Enum.any?(fields, fn
|
||||
:errors ->
|
||||
true
|
||||
|
||||
{:errors, _} ->
|
||||
true
|
||||
|
||||
_ ->
|
||||
false
|
||||
end)
|
||||
|
||||
if has_error_fields? do
|
||||
fields
|
||||
else
|
||||
fields ++ [errors: []]
|
||||
end
|
||||
end)
|
||||
|> Keyword.put(:error_class?, true)
|
||||
|
||||
use Splode.Error, opts
|
||||
|
||||
def message(%{errors: errors}) do
|
||||
Splode.ErrorClass.error_messages(errors)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@doc "Creates a long form composite error message for a list of errors"
|
||||
def error_messages(errors, opts \\ []) do
|
||||
custom_message = opts[:custom_message]
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
defmodule Splode.Error.Unknown do
|
||||
@moduledoc "The default top level unknown error container"
|
||||
use Splode.Error, fields: [:errors], class: :unknown
|
||||
|
||||
def splode_message(exception) do
|
||||
Splode.ErrorClass.error_messages(exception.errors)
|
||||
end
|
||||
use Splode.ErrorClass, class: :unknown
|
||||
|
||||
@impl true
|
||||
def exception(opts) do
|
||||
if opts[:error] do
|
||||
super(Keyword.update(opts, :errors, [opts[:error]], &[opts[:error] | &1]))
|
||||
|
|
Loading…
Reference in a new issue