1 |
|
-module(safely). |
2 |
|
%% This module preserves the return types of a `catch' statement, |
3 |
|
%% but doesn't silently convert `throw's to normal values. |
4 |
|
%% The issue is that throws have different semantics than errors: |
5 |
|
%% https://erlang.org/doc/reference_manual/expressions.html#catch-and-throw, |
6 |
|
%% so a throw is actually a control flow escape, while an error is an actual crash. |
7 |
|
%% If it is an error, we want the stacktrace, if not, we don't. |
8 |
|
%% For more information, see usage in test/safely_SUITE.erl |
9 |
|
|
10 |
|
-include_lib("kernel/include/logger.hrl"). |
11 |
|
|
12 |
|
-export([apply/2, apply/3]). |
13 |
|
-export([apply_and_log/3, apply_and_log/4]). |
14 |
|
-compile({no_auto_import, [apply/2, apply/3]}). |
15 |
|
-ignore_xref([apply/3, apply_and_log/4]). |
16 |
|
|
17 |
|
-type error_class() :: error | exit | throw. |
18 |
|
-type catch_result(A) :: A | {error_class(), term()}. |
19 |
|
|
20 |
|
-define(MATCH_EXCEPTIONS_DO_LOG(F, Context), |
21 |
|
try F catch |
22 |
|
error:R:S -> |
23 |
|
?LOG_ERROR(Context#{class => error, reason => R, stacktrace => S}), |
24 |
|
{error, {R, S}}; |
25 |
|
throw:R -> |
26 |
|
?LOG_ERROR(Context#{class => throw, reason => R}), |
27 |
|
{throw, R}; |
28 |
|
exit:R:S -> |
29 |
|
?LOG_ERROR(Context#{class => exit, reason => R, stacktrace => S}), |
30 |
|
{exit, {R, S}} |
31 |
|
end). |
32 |
|
|
33 |
|
-define(MATCH_EXCEPTIONS(F), |
34 |
|
try F catch |
35 |
|
error:R:S -> {error, {R, S}}; |
36 |
|
throw:R -> {throw, R}; |
37 |
|
exit:R:S -> {exit, {R, S}} |
38 |
|
end). |
39 |
|
|
40 |
|
-spec apply(fun((...) -> A), [term()]) -> catch_result(A). |
41 |
|
apply(Function, Args) when is_function(Function), is_list(Args) -> |
42 |
351583 |
?MATCH_EXCEPTIONS(erlang:apply(Function, Args)). |
43 |
|
|
44 |
|
-spec apply(atom(), atom(), [term()]) -> catch_result(any()). |
45 |
|
apply(Module, Function, Args) when is_atom(Function), is_list(Args) -> |
46 |
:-( |
?MATCH_EXCEPTIONS(erlang:apply(Module, Function, Args)). |
47 |
|
|
48 |
|
-spec apply_and_log(fun((...) -> A), [term()], map()) -> catch_result(A). |
49 |
|
apply_and_log(Function, Args, Context) |
50 |
|
when is_function(Function), is_list(Args), is_map(Context) -> |
51 |
546 |
?MATCH_EXCEPTIONS_DO_LOG(erlang:apply(Function, Args), Context). |
52 |
|
|
53 |
|
-spec apply_and_log(atom(), atom(), [term()], map()) -> catch_result(any()). |
54 |
|
apply_and_log(Module, Function, Args, Context) |
55 |
|
when is_atom(Function), is_list(Args), is_map(Context) -> |
56 |
1712 |
?MATCH_EXCEPTIONS_DO_LOG(erlang:apply(Module, Function, Args), Context). |