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."
|
||||
)
|
||||
|
||||
@merge_with List.wrap(opts[:merge_with])
|
||||
|
||||
if Enum.empty?(opts[:error_classes]) do
|
||||
raise ArgumentError,
|
||||
"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
|
||||
[to_error(values, Keyword.delete(opts, :bread_crumbs))]
|
||||
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
|
||||
|
||||
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)
|
||||
else
|
||||
values
|
||||
|> flatten_preserving_keywords()
|
||||
errors
|
||||
|> flatten_errors()
|
||||
|> Enum.uniq_by(&clear_stacktraces/1)
|
||||
|> Enum.map(fn value ->
|
||||
if splode_error?(value, __MODULE__) do
|
||||
Map.put(value, :splode, __MODULE__)
|
||||
if Enum.any?([__MODULE__ | @merge_with], &splode_error?(value, &1)) do
|
||||
Map.put(value, :splode, value.splode || __MODULE__)
|
||||
else
|
||||
exception_opts =
|
||||
if opts[:stacktrace] do
|
||||
|
@ -219,16 +229,17 @@ defmodule Splode do
|
|||
end
|
||||
|
||||
defp choose_error(errors) do
|
||||
errors = Enum.map(errors, &to_error/1)
|
||||
|
||||
[error | other_errors] =
|
||||
Enum.sort_by(errors, fn error ->
|
||||
# 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__}
|
||||
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
|
||||
%{error | errors: (error.errors || []) ++ other_errors}
|
||||
|
@ -271,16 +282,15 @@ defmodule Splode do
|
|||
|
||||
def to_error(other, opts) do
|
||||
cond do
|
||||
splode_error?(other, __MODULE__) ->
|
||||
Enum.any?([__MODULE__ | @merge_with], &splode_error?(other, &1)) ->
|
||||
other
|
||||
|> Map.put(:splode, __MODULE__)
|
||||
|> Map.put(:splode, other.splode || __MODULE__)
|
||||
|> add_stacktrace(opts[:stacktrace])
|
||||
|> accumulate_bread_crumbs(opts[:bread_crumbs])
|
||||
|
||||
is_exception(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])
|
||||
|
||||
|
@ -293,6 +303,22 @@ defmodule Splode do
|
|||
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
|
||||
if Keyword.keyword?(list) do
|
||||
[list]
|
||||
|
|
|
@ -13,6 +13,11 @@ defmodule SplodeTest do
|
|||
use Splode.ErrorClass, class: :sw
|
||||
end
|
||||
|
||||
defmodule ContainerErrorClass do
|
||||
@moduledoc false
|
||||
use Splode.ErrorClass, class: :ui
|
||||
end
|
||||
|
||||
# Errors
|
||||
|
||||
defmodule CpuError do
|
||||
|
@ -45,6 +50,18 @@ defmodule SplodeTest do
|
|||
def message(err), do: err |> inspect()
|
||||
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
|
||||
@moduledoc false
|
||||
use Splode,
|
||||
|
@ -55,6 +72,28 @@ defmodule SplodeTest do
|
|||
unknown_error: UnknownError
|
||||
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
|
||||
refute SystemError.splode_error?(:error)
|
||||
refute SystemError.splode_error?(%{})
|
||||
|
@ -83,8 +122,15 @@ defmodule SplodeTest do
|
|||
ram = RamError.exception() |> SystemError.to_error()
|
||||
div = DivByZeroException.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
|
||||
|
||||
test "wraps errors in error class with same class", %{
|
||||
|
@ -123,6 +169,31 @@ defmodule SplodeTest do
|
|||
|
||||
assert error == error |> SystemError.to_class()
|
||||
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
|
||||
|
||||
test "to_error" do
|
||||
|
|
Loading…
Reference in a new issue