mirror of
https://github.com/ash-project/splode.git
synced 2024-09-19 12:52:46 +12:00
Modifications to merge logic (#9)
This commit is contained in:
parent
b58526cb5c
commit
906a82e711
2 changed files with 111 additions and 14 deletions
|
@ -57,6 +57,8 @@ defmodule Splode do
|
||||||
"must supply the `unknown_error` option, pointing at a splode error to use in situations where we cannot convert an error."
|
"must supply the `unknown_error` option, pointing at a splode error to use in situations where we cannot convert an error."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@merge_with List.wrap(opts[:merge_with])
|
||||||
|
|
||||||
if Enum.empty?(opts[:error_classes]) do
|
if Enum.empty?(opts[:error_classes]) do
|
||||||
raise ArgumentError,
|
raise ArgumentError,
|
||||||
"must supply at least one error class to `use Splode`, via `use Splode, error_classes: [class: ModuleForClass]`"
|
"must supply at least one error class to `use Splode`, via `use Splode, error_classes: [class: ModuleForClass]`"
|
||||||
|
@ -180,19 +182,27 @@ defmodule Splode do
|
||||||
if Keyword.keyword?(values) && values != [] do
|
if Keyword.keyword?(values) && values != [] do
|
||||||
[to_error(values, Keyword.delete(opts, :bread_crumbs))]
|
[to_error(values, Keyword.delete(opts, :bread_crumbs))]
|
||||||
else
|
else
|
||||||
Enum.map(values, &to_error(&1, Keyword.delete(opts, :bread_crumbs)))
|
values
|
||||||
|
|> flatten_preserving_keywords()
|
||||||
|
|> Enum.map(fn error ->
|
||||||
|
if Enum.any?([__MODULE__ | @merge_with], &splode_error?(error, &1)) do
|
||||||
|
error
|
||||||
|
else
|
||||||
|
to_error(error, Keyword.delete(opts, :bread_crumbs))
|
||||||
|
end
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
if Enum.count_until(errors, 2) == 1 &&
|
if Enum.count_until(errors, 2) == 1 &&
|
||||||
Enum.at(errors, 0).class == :special do
|
(Enum.at(errors, 0).class == :special || Enum.at(errors, 0).__struct__.error_class?()) do
|
||||||
List.first(errors)
|
List.first(errors)
|
||||||
else
|
else
|
||||||
values
|
errors
|
||||||
|> flatten_preserving_keywords()
|
|> flatten_errors()
|
||||||
|> Enum.uniq_by(&clear_stacktraces/1)
|
|> Enum.uniq_by(&clear_stacktraces/1)
|
||||||
|> Enum.map(fn value ->
|
|> Enum.map(fn value ->
|
||||||
if splode_error?(value, __MODULE__) do
|
if Enum.any?([__MODULE__ | @merge_with], &splode_error?(value, &1)) do
|
||||||
Map.put(value, :splode, __MODULE__)
|
Map.put(value, :splode, value.splode || __MODULE__)
|
||||||
else
|
else
|
||||||
exception_opts =
|
exception_opts =
|
||||||
if opts[:stacktrace] do
|
if opts[:stacktrace] do
|
||||||
|
@ -219,16 +229,17 @@ defmodule Splode do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp choose_error(errors) do
|
defp choose_error(errors) do
|
||||||
errors = Enum.map(errors, &to_error/1)
|
|
||||||
|
|
||||||
[error | other_errors] =
|
[error | other_errors] =
|
||||||
Enum.sort_by(errors, fn error ->
|
Enum.sort_by(errors, fn error ->
|
||||||
# the second element here sorts errors that are already parent errors
|
# the second element here sorts errors that are already parent errors
|
||||||
{Map.get(@error_class_indices, error.class),
|
{Map.get(@error_class_indices, error.class) ||
|
||||||
|
Map.get(@error_class_indices, :unknown),
|
||||||
@error_classes[error.class] != error.__struct__}
|
@error_classes[error.class] != error.__struct__}
|
||||||
end)
|
end)
|
||||||
|
|
||||||
parent_error_module = @error_classes[error.class]
|
parent_error_module =
|
||||||
|
@error_classes[error.class] || Keyword.get(@error_classes, :unknown) ||
|
||||||
|
Splode.Error.Unknown
|
||||||
|
|
||||||
if parent_error_module == error.__struct__ do
|
if parent_error_module == error.__struct__ do
|
||||||
%{error | errors: (error.errors || []) ++ other_errors}
|
%{error | errors: (error.errors || []) ++ other_errors}
|
||||||
|
@ -271,16 +282,15 @@ defmodule Splode do
|
||||||
|
|
||||||
def to_error(other, opts) do
|
def to_error(other, opts) do
|
||||||
cond do
|
cond do
|
||||||
splode_error?(other, __MODULE__) ->
|
Enum.any?([__MODULE__ | @merge_with], &splode_error?(other, &1)) ->
|
||||||
other
|
other
|
||||||
|> Map.put(:splode, __MODULE__)
|
|> Map.put(:splode, other.splode || __MODULE__)
|
||||||
|> add_stacktrace(opts[:stacktrace])
|
|> add_stacktrace(opts[:stacktrace])
|
||||||
|> accumulate_bread_crumbs(opts[:bread_crumbs])
|
|> accumulate_bread_crumbs(opts[:bread_crumbs])
|
||||||
|
|
||||||
is_exception(other) ->
|
is_exception(other) ->
|
||||||
[error: Exception.format(:error, other), splode: __MODULE__]
|
[error: Exception.format(:error, other), splode: __MODULE__]
|
||||||
|> @unknown_error.exception()
|
|> @unknown_error.exception()
|
||||||
|> Map.put(:stacktrace, nil)
|
|
||||||
|> add_stacktrace(opts[:stacktrace])
|
|> add_stacktrace(opts[:stacktrace])
|
||||||
|> accumulate_bread_crumbs(opts[:bread_crumbs])
|
|> accumulate_bread_crumbs(opts[:bread_crumbs])
|
||||||
|
|
||||||
|
@ -293,6 +303,22 @@ defmodule Splode do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp flatten_errors(errors) do
|
||||||
|
errors
|
||||||
|
|> Enum.flat_map(&List.wrap/1)
|
||||||
|
|> Enum.flat_map(fn error ->
|
||||||
|
if Enum.any?([__MODULE__ | @merge_with], &splode_error?(error, &1)) do
|
||||||
|
if error.__struct__.error_class?() do
|
||||||
|
flatten_errors(error.errors)
|
||||||
|
else
|
||||||
|
[error]
|
||||||
|
end
|
||||||
|
else
|
||||||
|
[error]
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
defp flatten_preserving_keywords(list) do
|
defp flatten_preserving_keywords(list) do
|
||||||
if Keyword.keyword?(list) do
|
if Keyword.keyword?(list) do
|
||||||
[list]
|
[list]
|
||||||
|
|
|
@ -13,6 +13,11 @@ defmodule SplodeTest do
|
||||||
use Splode.ErrorClass, class: :sw
|
use Splode.ErrorClass, class: :sw
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defmodule ContainerErrorClass do
|
||||||
|
@moduledoc false
|
||||||
|
use Splode.ErrorClass, class: :ui
|
||||||
|
end
|
||||||
|
|
||||||
# Errors
|
# Errors
|
||||||
|
|
||||||
defmodule CpuError do
|
defmodule CpuError do
|
||||||
|
@ -45,6 +50,18 @@ defmodule SplodeTest do
|
||||||
def message(err), do: err |> inspect()
|
def message(err), do: err |> inspect()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defmodule ExampleContainerError do
|
||||||
|
@moduledoc false
|
||||||
|
use Splode.Error, fields: [:description], class: :ui
|
||||||
|
def message(err), do: err |> inspect()
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule ContainerUnknownError do
|
||||||
|
@moduledoc false
|
||||||
|
use Splode.Error, fields: [:error], class: :unknown
|
||||||
|
def message(err), do: err |> inspect()
|
||||||
|
end
|
||||||
|
|
||||||
defmodule SystemError do
|
defmodule SystemError do
|
||||||
@moduledoc false
|
@moduledoc false
|
||||||
use Splode,
|
use Splode,
|
||||||
|
@ -55,6 +72,28 @@ defmodule SplodeTest do
|
||||||
unknown_error: UnknownError
|
unknown_error: UnknownError
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defmodule ContainerError do
|
||||||
|
@moduledoc false
|
||||||
|
use Splode,
|
||||||
|
error_classes: [
|
||||||
|
interaction: ContainerErrorClass,
|
||||||
|
hw: HwError,
|
||||||
|
sw: SwError
|
||||||
|
],
|
||||||
|
unknown_error: ContainerUnknownError,
|
||||||
|
merge_with: [SystemError]
|
||||||
|
end
|
||||||
|
|
||||||
|
defmodule ContainerWithoutMergeWith do
|
||||||
|
@moduledoc false
|
||||||
|
use Splode,
|
||||||
|
error_classes: [
|
||||||
|
interaction: ContainerErrorClass
|
||||||
|
],
|
||||||
|
unknown_error: ContainerUnknownError,
|
||||||
|
merge_with: []
|
||||||
|
end
|
||||||
|
|
||||||
test "splode_error?" do
|
test "splode_error?" do
|
||||||
refute SystemError.splode_error?(:error)
|
refute SystemError.splode_error?(:error)
|
||||||
refute SystemError.splode_error?(%{})
|
refute SystemError.splode_error?(%{})
|
||||||
|
@ -83,8 +122,15 @@ defmodule SplodeTest do
|
||||||
ram = RamError.exception() |> SystemError.to_error()
|
ram = RamError.exception() |> SystemError.to_error()
|
||||||
div = DivByZeroException.exception() |> SystemError.to_error()
|
div = DivByZeroException.exception() |> SystemError.to_error()
|
||||||
null = NullReferenceException.exception() |> SystemError.to_error()
|
null = NullReferenceException.exception() |> SystemError.to_error()
|
||||||
|
example_container_error = ExampleContainerError.exception() |> ContainerError.to_error()
|
||||||
|
|
||||||
%{cpu: cpu, ram: ram, div: div, null: null}
|
%{
|
||||||
|
cpu: cpu,
|
||||||
|
ram: ram,
|
||||||
|
div: div,
|
||||||
|
null: null,
|
||||||
|
example_container_error: example_container_error
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
test "wraps errors in error class with same class", %{
|
test "wraps errors in error class with same class", %{
|
||||||
|
@ -123,6 +169,31 @@ defmodule SplodeTest do
|
||||||
|
|
||||||
assert error == error |> SystemError.to_class()
|
assert error == error |> SystemError.to_class()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "to_error flattens nested errors when included in merge_with", %{
|
||||||
|
cpu: cpu,
|
||||||
|
ram: ram,
|
||||||
|
example_container_error: example_container_error
|
||||||
|
} do
|
||||||
|
hw_error = [cpu, ram] |> SystemError.to_class()
|
||||||
|
|
||||||
|
interaction_error = ContainerError.to_class([hw_error, example_container_error])
|
||||||
|
|
||||||
|
assert %{errors: [^cpu, ^ram, ^example_container_error]} = interaction_error
|
||||||
|
end
|
||||||
|
|
||||||
|
test "to_error doesn't flatten nested errors when not included in merge_with", %{
|
||||||
|
cpu: cpu,
|
||||||
|
ram: ram,
|
||||||
|
example_container_error: example_container_error
|
||||||
|
} do
|
||||||
|
hw_error = [cpu, ram] |> SystemError.to_class()
|
||||||
|
|
||||||
|
interaction_error = ContainerWithoutMergeWith.to_class([hw_error, example_container_error])
|
||||||
|
|
||||||
|
assert %{errors: [%SplodeTest.ContainerUnknownError{}, %SplodeTest.ContainerUnknownError{}]} =
|
||||||
|
interaction_error
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "to_error" do
|
test "to_error" do
|
||||||
|
|
Loading…
Reference in a new issue