Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
OTP_VERSION=24.3.4
REBAR_VERSION=3.18
OTP_VERSION=28.5.0
REBAR_VERSION=3.26
THRIFT_VERSION=0.14.2.3
3 changes: 2 additions & 1 deletion .github/workflows/erlang-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,10 @@ jobs:
run:
name: Run checks
needs: setup
uses: valitydev/erlang-workflows/.github/workflows/erlang-parallel-build.yml@v1.0.10
uses: valitydev/erlang-workflows/.github/workflows/erlang-parallel-build.yml@v2
with:
otp-version: ${{ needs.setup.outputs.otp-version }}
rebar-version: ${{ needs.setup.outputs.rebar-version }}
use-thrift: true
thrift-version: ${{ needs.setup.outputs.thrift-version }}
upload-coverage: false
2 changes: 2 additions & 0 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
rebar 3.26.0
erlang 28.5
8 changes: 6 additions & 2 deletions elvis.config
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
{elvis_text_style, line_length, #{limit => 120}},
{elvis_style, nesting_level, #{level => 3}},
{elvis_style, function_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*$"}},
{elvis_style, no_if_expression, disable}
{elvis_style, no_if_expression, disable},
{elvis_style, export_used_types, disable},
{elvis_style, no_catch_expressions, disable}
]
},
#{
Expand All @@ -32,7 +34,9 @@
{elvis_text_style, no_tabs},
{elvis_text_style, no_trailing_whitespace},
%% Temporarily disabled till regex pattern is available
{elvis_project, no_deps_master_rebar, disable}
{elvis_project, no_deps_master_rebar, disable},
%% TODO Remove it after locking damsel w/ tag
{elvis_project, no_branch_deps, disable}
]
},
#{
Expand Down
9 changes: 4 additions & 5 deletions rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@

% Common project dependencies.
{deps, [
{damsel, {git, "https://github.com/valitydev/damsel.git", {branch, "master"}}}
{damsel, {git, "https://github.com/valitydev/damsel.git", {branch, "XYZ-451/ft/limit-overflow-failure-details"}}}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Зависит от valitydev/damsel#221

]}.

%% XRef checks
Expand All @@ -45,7 +45,6 @@
% mandatory
unmatched_returns,
error_handling,
race_conditions,
unknown
]},
{plt_apps, all_deps}
Expand All @@ -62,9 +61,9 @@
]}.

{project_plugins, [
{rebar3_lint, "1.0.1"},
{erlfmt, "1.0.0"},
{covertool, "2.0.4"}
{rebar3_lint, "3.2.6"},
{erlfmt, "1.6.2"},
{covertool, "2.0.7"}
]}.

%% Linter config.
Expand Down
2 changes: 1 addition & 1 deletion rebar.lock
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[{<<"damsel">>,
{git,"https://github.com/valitydev/damsel.git",
{ref,"dac2cb599499cc0701e60856f4092c9ab283eedf"}},
{ref,"61e7a831b19b47284a1bd2fdd9cd12bfefb5e306"}},
0}].
28 changes: 25 additions & 3 deletions src/payproc_errors.erl
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,25 @@ sub_error_to_static(_, undefined) ->
sub_error_to_static(Type, #domain_SubFailure{code = Code, sub = SDE}) ->
to_static(Code, Type, SDE).

-spec to_static(dynamic_code(), type(), dynamic_sub_error()) -> {static_code(), static_sub_error()}.
-spec to_static(dynamic_code(), type(), dynamic_sub_error()) ->
{static_code(), static_sub_error()} | static_sub_error().
to_static(Code, Type, SDE) ->
StaticCode = code_to_static(Code),
case type_by_field(StaticCode, Type) of
SubType when SubType =/= undefined ->
{StaticCode, sub_error_to_static(SubType, SDE)};
%% NOTE `GeneralFailure` is a terminal struct: it declares no
%% struct-typed fields, so `type_by_field/2` above yields `undefined`
%% for _any_ code sitting below it. Such a code is an arbitrary reason
%% code by construction, so we decode it as one unconditionally.
%%
%% Deciding this by `binary_to_existing_atom/2` instead would make the
%% result depend on which modules happen to be loaded in this VM: the
%% very same `domain.Failure` would decode to a `reason_code` on one
%% node and to `{unknown_error, _}` on another, forcing every consumer
%% to match both shapes forever. See `general_failure_*` testcases.
undefined when Type =:= 'GeneralFailure' ->
#payproc_error_GeneralFailure{reason_code = Code};
undefined ->
{{unknown_error, Code}, #payproc_error_GeneralFailure{}}
end.
Expand Down Expand Up @@ -121,9 +134,18 @@ code_to_dynamic(Code) ->
-spec to_dynamic(type(), static_sub_error()) -> {dynamic_code(), type() | undefined, static_sub_error()}.
to_dynamic(_, {Code = {unknown_error, _}, #payproc_error_GeneralFailure{}}) ->
{code_to_dynamic(Code), undefined, undefined};
to_dynamic(Type, {Code, #payproc_error_GeneralFailure{}}) ->
to_dynamic(Type, {Code, #payproc_error_GeneralFailure{reason_code = ReasonCode}}) ->
'GeneralFailure' = check_type(type_by_field(Code, Type)),
{code_to_dynamic(Code), undefined, undefined};
case ReasonCode of
undefined ->
{code_to_dynamic(Code), undefined, undefined};
_ ->
%% NOTE It's a special case when general failure have an arbitrary sub code. But
%% when we transform subcode we need another special clause to handle it.
{code_to_dynamic(Code), 'GeneralFailure', {reason_code, ReasonCode}}
end;
to_dynamic('GeneralFailure', {reason_code, ReasonCode}) when is_binary(ReasonCode) ->
{ReasonCode, undefined, undefined};
to_dynamic(Type, {Code, SSE}) ->
{code_to_dynamic(Code), check_type(type_by_field(Code, Type)), SSE}.

Expand Down
58 changes: 58 additions & 0 deletions test/payproc_errors_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
-export([known_error_test/1]).
-export([unknown_error_test/1]).
-export([unknown_error_atom_test/1]).
-export([general_failure_with_reason_code_test/1]).
-export([general_failure_with_reason_code_existing_atom_test/1]).
-export([general_failure_reason_code_notation_test/1]).
-export([bad_static_type_test/1]).
-export([formatting_test/1]).
-export([from_notation_test/1]).
Expand All @@ -25,6 +28,9 @@ all() ->
known_error_test,
unknown_error_test,
unknown_error_atom_test,
general_failure_with_reason_code_test,
general_failure_with_reason_code_existing_atom_test,
general_failure_reason_code_notation_test,
bad_static_type_test,
formatting_test,
from_notation_test,
Expand Down Expand Up @@ -72,6 +78,58 @@ unknown_error_test(_C) ->
DE = payproc_errors:construct('PaymentFailure', SE),
ok = payproc_errors:match('PaymentFailure', DE, fun(E) when SE =:= E -> ok end).

-spec general_failure_with_reason_code_test(config()) -> _.
general_failure_with_reason_code_test(_C) ->
DE = #domain_Failure{
code = <<"no_route_found">>,
sub = #domain_SubFailure{
code = <<"rejected">>,
sub = #domain_SubFailure{
code = <<"limit_overflow">>,
sub = #domain_SubFailure{
code = <<"limit-that-overflowed">>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это строка и так никогда атомом стать не сможет, давай какой-то существующий атом в отдельный тест.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Добавил тесткейс с этим случаем. Там трансляция из динамической в статическую происходит по кодпасу дающему {unknown_error, <<"...">>}, что сохраняет старое поведение, но так же остаётся прежнее поведение, что при обратной трансляции из статической ошибки в динамическую превращает в ту же самую динамическую ошибку.

}
}
}
},
SE =
{no_route_found,
{rejected, {limit_overflow, #payproc_error_GeneralFailure{reason_code = <<"limit-that-overflowed">>}}}},
DE = payproc_errors:construct('PaymentFailure', SE),
ok = payproc_errors:match('PaymentFailure', DE, fun(E) when SE =:= E -> ok end).

-spec general_failure_with_reason_code_existing_atom_test(config()) -> _.
general_failure_with_reason_code_existing_atom_test(_C) ->
%% Ensures atom exists
_ = 'Code existing as atom',
DE = #domain_Failure{
code = <<"no_route_found">>,
sub = #domain_SubFailure{
code = <<"rejected">>,
sub = #domain_SubFailure{
code = <<"limit_overflow">>,
sub = #domain_SubFailure{
code = <<"Code existing as atom">>
}
}
}
},
SE =
{no_route_found,
{rejected, {limit_overflow, #payproc_error_GeneralFailure{reason_code = <<"Code existing as atom">>}}}},
DE = payproc_errors:construct('PaymentFailure', SE),
ok = payproc_errors:match('PaymentFailure', DE, fun(E) when SE =:= E -> ok end).

-spec general_failure_reason_code_notation_test(config()) -> _.
general_failure_reason_code_notation_test(_C) ->
%% NOTE Notation is what downstream cascade and transient error matching
%% keys off, so an arbitrary reason code has to survive the
%% dynamic -> static -> dynamic round trip that `format/2` performs.
Notation = <<"no_route_found:rejected:limit_overflow:limit-that-overflowed">>,
Notation = erlang:list_to_binary(
payproc_errors:format('PaymentFailure', payproc_errors:from_notation(Notation))
).

-spec bad_static_type_test(config()) -> _.
bad_static_type_test(_C) ->
Bad = {qwe, #payproc_error_GeneralFailure{}},
Expand Down
Loading