Skip to content

Commit 906a82e

Browse files
authored
Modifications to merge logic (#9)
1 parent b58526c commit 906a82e

File tree

2 files changed

+112
-15
lines changed

2 files changed

+112
-15
lines changed

lib/splode.ex

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ defmodule Splode do
5757
"must supply the `unknown_error` option, pointing at a splode error to use in situations where we cannot convert an error."
5858
)
5959

60+
@merge_with List.wrap(opts[:merge_with])
61+
6062
if Enum.empty?(opts[:error_classes]) do
6163
raise ArgumentError,
6264
"must supply at least one error class to `use Splode`, via `use Splode, error_classes: [class: ModuleForClass]`"
@@ -180,19 +182,27 @@ defmodule Splode do
180182
if Keyword.keyword?(values) && values != [] do
181183
[to_error(values, Keyword.delete(opts, :bread_crumbs))]
182184
else
183-
Enum.map(values, &to_error(&1, Keyword.delete(opts, :bread_crumbs)))
185+
values
186+
|> flatten_preserving_keywords()
187+
|> Enum.map(fn error ->
188+
if Enum.any?([__MODULE__ | @merge_with], &splode_error?(error, &1)) do
189+
error
190+
else
191+
to_error(error, Keyword.delete(opts, :bread_crumbs))
192+
end
193+
end)
184194
end
185195

186196
if Enum.count_until(errors, 2) == 1 &&
187-
Enum.at(errors, 0).class == :special do
197+
(Enum.at(errors, 0).class == :special || Enum.at(errors, 0).__struct__.error_class?()) do
188198
List.first(errors)
189199
else
190-
values
191-
|> flatten_preserving_keywords()
200+
errors
201+
|> flatten_errors()
192202
|> Enum.uniq_by(&clear_stacktraces/1)
193203
|> Enum.map(fn value ->
194-
if splode_error?(value, __MODULE__) do
195-
Map.put(value, :splode, __MODULE__)
204+
if Enum.any?([__MODULE__ | @merge_with], &splode_error?(value, &1)) do
205+
Map.put(value, :splode, value.splode || __MODULE__)
196206
else
197207
exception_opts =
198208
if opts[:stacktrace] do
@@ -219,16 +229,17 @@ defmodule Splode do
219229
end
220230

221231
defp choose_error(errors) do
222-
errors = Enum.map(errors, &to_error/1)
223-
224232
[error | other_errors] =
225233
Enum.sort_by(errors, fn error ->
226234
# the second element here sorts errors that are already parent errors
227-
{Map.get(@error_class_indices, error.class),
235+
{Map.get(@error_class_indices, error.class) ||
236+
Map.get(@error_class_indices, :unknown),
228237
@error_classes[error.class] != error.__struct__}
229238
end)
230239

231-
parent_error_module = @error_classes[error.class]
240+
parent_error_module =
241+
@error_classes[error.class] || Keyword.get(@error_classes, :unknown) ||
242+
Splode.Error.Unknown
232243

233244
if parent_error_module == error.__struct__ do
234245
%{error | errors: (error.errors || []) ++ other_errors}
@@ -271,16 +282,15 @@ defmodule Splode do
271282

272283
def to_error(other, opts) do
273284
cond do
274-
splode_error?(other, __MODULE__) ->
285+
Enum.any?([__MODULE__ | @merge_with], &splode_error?(other, &1)) ->
275286
other
276-
|> Map.put(:splode, __MODULE__)
287+
|> Map.put(:splode, other.splode || __MODULE__)
277288
|> add_stacktrace(opts[:stacktrace])
278289
|> accumulate_bread_crumbs(opts[:bread_crumbs])
279290

280291
is_exception(other) ->
281292
[error: Exception.format(:error, other), splode: __MODULE__]
282293
|> @unknown_error.exception()
283-
|> Map.put(:stacktrace, nil)
284294
|> add_stacktrace(opts[:stacktrace])
285295
|> accumulate_bread_crumbs(opts[:bread_crumbs])
286296

@@ -293,6 +303,22 @@ defmodule Splode do
293303
end
294304
end
295305

306+
defp flatten_errors(errors) do
307+
errors
308+
|> Enum.flat_map(&List.wrap/1)
309+
|> Enum.flat_map(fn error ->
310+
if Enum.any?([__MODULE__ | @merge_with], &splode_error?(error, &1)) do
311+
if error.__struct__.error_class?() do
312+
flatten_errors(error.errors)
313+
else
314+
[error]
315+
end
316+
else
317+
[error]
318+
end
319+
end)
320+
end
321+
296322
defp flatten_preserving_keywords(list) do
297323
if Keyword.keyword?(list) do
298324
[list]

test/splode_test.exs

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ defmodule SplodeTest do
1313
use Splode.ErrorClass, class: :sw
1414
end
1515

16+
defmodule ContainerErrorClass do
17+
@moduledoc false
18+
use Splode.ErrorClass, class: :ui
19+
end
20+
1621
# Errors
1722

1823
defmodule CpuError do
@@ -45,6 +50,18 @@ defmodule SplodeTest do
4550
def message(err), do: err |> inspect()
4651
end
4752

53+
defmodule ExampleContainerError do
54+
@moduledoc false
55+
use Splode.Error, fields: [:description], class: :ui
56+
def message(err), do: err |> inspect()
57+
end
58+
59+
defmodule ContainerUnknownError do
60+
@moduledoc false
61+
use Splode.Error, fields: [:error], class: :unknown
62+
def message(err), do: err |> inspect()
63+
end
64+
4865
defmodule SystemError do
4966
@moduledoc false
5067
use Splode,
@@ -55,6 +72,28 @@ defmodule SplodeTest do
5572
unknown_error: UnknownError
5673
end
5774

75+
defmodule ContainerError do
76+
@moduledoc false
77+
use Splode,
78+
error_classes: [
79+
interaction: ContainerErrorClass,
80+
hw: HwError,
81+
sw: SwError
82+
],
83+
unknown_error: ContainerUnknownError,
84+
merge_with: [SystemError]
85+
end
86+
87+
defmodule ContainerWithoutMergeWith do
88+
@moduledoc false
89+
use Splode,
90+
error_classes: [
91+
interaction: ContainerErrorClass
92+
],
93+
unknown_error: ContainerUnknownError,
94+
merge_with: []
95+
end
96+
5897
test "splode_error?" do
5998
refute SystemError.splode_error?(:error)
6099
refute SystemError.splode_error?(%{})
@@ -83,8 +122,15 @@ defmodule SplodeTest do
83122
ram = RamError.exception() |> SystemError.to_error()
84123
div = DivByZeroException.exception() |> SystemError.to_error()
85124
null = NullReferenceException.exception() |> SystemError.to_error()
86-
87-
%{cpu: cpu, ram: ram, div: div, null: null}
125+
example_container_error = ExampleContainerError.exception() |> ContainerError.to_error()
126+
127+
%{
128+
cpu: cpu,
129+
ram: ram,
130+
div: div,
131+
null: null,
132+
example_container_error: example_container_error
133+
}
88134
end
89135

90136
test "wraps errors in error class with same class", %{
@@ -123,6 +169,31 @@ defmodule SplodeTest do
123169

124170
assert error == error |> SystemError.to_class()
125171
end
172+
173+
test "to_error flattens nested errors when included in merge_with", %{
174+
cpu: cpu,
175+
ram: ram,
176+
example_container_error: example_container_error
177+
} do
178+
hw_error = [cpu, ram] |> SystemError.to_class()
179+
180+
interaction_error = ContainerError.to_class([hw_error, example_container_error])
181+
182+
assert %{errors: [^cpu, ^ram, ^example_container_error]} = interaction_error
183+
end
184+
185+
test "to_error doesn't flatten nested errors when not included in merge_with", %{
186+
cpu: cpu,
187+
ram: ram,
188+
example_container_error: example_container_error
189+
} do
190+
hw_error = [cpu, ram] |> SystemError.to_class()
191+
192+
interaction_error = ContainerWithoutMergeWith.to_class([hw_error, example_container_error])
193+
194+
assert %{errors: [%SplodeTest.ContainerUnknownError{}, %SplodeTest.ContainerUnknownError{}]} =
195+
interaction_error
196+
end
126197
end
127198

128199
test "to_error" do

0 commit comments

Comments
 (0)