From b9a84e3f804e3d69f58df53c2b5d2a628487091c Mon Sep 17 00:00:00 2001 From: WWW_cool Date: Wed, 17 Feb 2021 12:43:49 +0300 Subject: [PATCH 01/54] added ignore --- .gitignore | 15 +++++++++++++++ .gitmodules | 4 ++++ 2 files changed, 19 insertions(+) create mode 100644 .gitignore create mode 100644 .gitmodules diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8ae71e1c --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# general +log +/_build/ +/_checkouts/ +*~ +erl_crash.dump +.tags* +*.sublime-workspace +.edts +.DS_Store +Dockerfile +docker-compose.yml +/.idea/ +*.beam +rebar3.crashdump \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..ca5a761f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "build_utils"] + path = build_utils + url = git@github.com:rbkmoney/build_utils.git + branch = master From 439b1c65b055da06b7cc9b467026142f79ddc259 Mon Sep 17 00:00:00 2001 From: WWW_cool Date: Wed, 17 Feb 2021 12:47:57 +0300 Subject: [PATCH 02/54] initial --- Dockerfile.sh | 24 +++++ Jenkinsfile | 22 ++++ LICENSE | 176 +++++++++++++++++++++++++++++++ Makefile | 77 ++++++++++++++ README.md | 2 + apps/limiter/src/limiter.app.src | 18 ++++ config/sys.config | 96 +++++++++++++++++ config/vm.args | 3 + docker-compose.sh | 45 ++++++++ elvis.config | 89 ++++++++++++++++ rebar.config | 121 +++++++++++++++++++++ 11 files changed, 673 insertions(+) create mode 100755 Dockerfile.sh create mode 100644 Jenkinsfile create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 apps/limiter/src/limiter.app.src create mode 100644 config/sys.config create mode 100644 config/vm.args create mode 100755 docker-compose.sh create mode 100644 elvis.config create mode 100644 rebar.config diff --git a/Dockerfile.sh b/Dockerfile.sh new file mode 100755 index 00000000..e9d6f661 --- /dev/null +++ b/Dockerfile.sh @@ -0,0 +1,24 @@ +#!/bin/bash +cat < +RUN mkdir -p /var/log/limiter +COPY ./_build/prod/rel/limiter /opt/limiter +WORKDIR /opt/limiter +CMD /opt/limiter/bin/limiter foreground +EXPOSE 8022 +LABEL com.rbkmoney.$SERVICE_NAME.parent=$BASE_IMAGE_NAME \ + com.rbkmoney.$SERVICE_NAME.parent_tag=$BASE_IMAGE_TAG \ + com.rbkmoney.$SERVICE_NAME.build_img=build \ + com.rbkmoney.$SERVICE_NAME.build_img_tag=$BUILD_IMAGE_TAG \ + com.rbkmoney.$SERVICE_NAME.commit_id=$(git rev-parse HEAD) \ + com.rbkmoney.$SERVICE_NAME.commit_number=$(git rev-list --count HEAD) \ + com.rbkmoney.$SERVICE_NAME.branch=$( \ + if [ "HEAD" != $(git rev-parse --abbrev-ref HEAD) ]; then \ + echo $(git rev-parse --abbrev-ref HEAD); \ + elif [ -n "$BRANCH_NAME" ]; then \ + echo $BRANCH_NAME; \ + else \ + echo $(git name-rev --name-only HEAD); \ + fi) +EOF diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 00000000..3265915c --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,22 @@ +#!groovy +// -*- mode: groovy -*- + +def finalHook = { + runStage('store CT logs') { + archive '_build/test/logs/' + } +} + +build('limiter', 'docker-host', finalHook) { + checkoutRepo() + loadBuildUtils() + + def pipeErlangService + runStage('load pipeline') { + env.JENKINS_LIB = "build_utils/jenkins_lib" + env.SH_TOOLS = "build_utils/sh" + pipeErlangService = load("${env.JENKINS_LIB}/pipeErlangService.groovy") + } + + pipeErlangService.runPipe(true, true) +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..2bb9ad24 --- /dev/null +++ b/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..d3069263 --- /dev/null +++ b/Makefile @@ -0,0 +1,77 @@ +REBAR := $(shell which rebar3 2>/dev/null || which ./rebar3) +SUBMODULES = build_utils +SUBTARGETS = $(patsubst %,%/.git,$(SUBMODULES)) + +UTILS_PATH := build_utils +TEMPLATES_PATH := . + +# Name of the service +SERVICE_NAME := limiter +# Service image default tag +SERVICE_IMAGE_TAG ?= $(shell git rev-parse HEAD) +# The tag for service image to be pushed with +SERVICE_IMAGE_PUSH_TAG ?= $(SERVICE_IMAGE_TAG) + +# Base image for the service +BASE_IMAGE_NAME := service-erlang +BASE_IMAGE_TAG := 54a794b4875ad79f90dba0a7708190b3b37d584f + +# Build image tag to be used +BUILD_IMAGE_NAME := build-erlang +BUILD_IMAGE_TAG := 491bc06c745a07c6fe9e8b5dbbe958e8e0b82c4c + +CALL_ANYWHERE := all submodules rebar-update compile xref lint dialyze \ + release clean distclean check format check_format + +CALL_W_CONTAINER := $(CALL_ANYWHERE) test + +all: compile + +-include $(UTILS_PATH)/make_lib/utils_container.mk +-include $(UTILS_PATH)/make_lib/utils_image.mk + +.PHONY: $(CALL_W_CONTAINER) + +# CALL_ANYWHERE +$(SUBTARGETS): %/.git: % + git submodule update --init $< + touch $@ + +submodules: $(SUBTARGETS) + +rebar-update: + $(REBAR) update + +compile: submodules rebar-update + $(REBAR) compile + +xref: submodules + $(REBAR) xref + +lint: + elvis rock + +check_format: + $(REBAR) fmt -c + +format: + $(REBAR) fmt -w + +dialyze: + $(REBAR) dialyzer + +check: xref lint dialyze + +release: distclean + $(REBAR) as prod release + +clean: + $(REBAR) clean + +distclean: + $(REBAR) clean + rm -rf _build + +# CALL_W_CONTAINER +test: submodules + $(REBAR) ct diff --git a/README.md b/README.md new file mode 100644 index 00000000..d3850770 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# limiter +Service for limits calculating diff --git a/apps/limiter/src/limiter.app.src b/apps/limiter/src/limiter.app.src new file mode 100644 index 00000000..13d83dc4 --- /dev/null +++ b/apps/limiter/src/limiter.app.src @@ -0,0 +1,18 @@ +{application, limiter, [ + {description, "Proto limiter service"}, + {vsn, "1.0.0"}, + {registered, []}, + {applications, [ + kernel, + stdlib, + damsel, + woody, + how_are_you, % must be after ranch and before any woody usage + scoper, % should be before any scoper event handler usage + erl_health, + prometheus, + prometheus_cowboy + ]}, + {mod, {limiter, []}}, + {env, []} +]}. diff --git a/config/sys.config b/config/sys.config new file mode 100644 index 00000000..00510dda --- /dev/null +++ b/config/sys.config @@ -0,0 +1,96 @@ +[ + {limiter, [ + {ip, "::"}, + {port, 8022}, + {services, #{ + limiter => #{ + path => <<"/v1/limiter">> + }, + configurator => #{ + path => <<"/v1/configurator">> + } + }}, + {service_clients, #{ + accounter => #{ + url => <<"http://shumway:8022/accounter">> + } + }}, + {protocol_opts, #{ + % How much to wait for another request before closing a keepalive connection? (ms) + request_timeout => 5000, + % Should be greater than any other timeouts + idle_timeout => infinity + }}, + {transport_opts, #{ + % Maximum number of simultaneous connections. (default = 1024) + max_connections => 8000, + % Size of the acceptor pool. (default = 10) + num_acceptors => 100 + }}, + % How much to wait for outstanding requests completion when asked to shut down? (ms) + {shutdown_timeout, 1000}, + + {woody_event_handlers, [ + hay_woody_event_handler, + {scoper_woody_event_handler, #{ + event_handler_opts => #{ + formatter_opts => #{ + max_length => 1000, + max_printable_string_length => 80 + } + } + }} + ]}, + + {health_check, #{ + % disk => {erl_health, disk , ["/", 99]}, + % memory => {erl_health, cg_memory, [99]}, + % service => {erl_health, service , [<<"limiter">>]} + }} + ]}, + + {kernel, [ + {logger_sasl_compatible, false}, + {logger_level, debug}, + {logger, [ + {handler, default, logger_std_h, #{ + level => error, + config => #{ + type => standard_error + }, + formatter => {logger_formatter, #{ + depth => 30 + }} + }}, + {handler, console, logger_std_h, #{ + config => #{ + type => {file, "/var/log/limiter/log.json"} + }, + formatter => {logger_logstash_formatter, #{}} + }} + ]} + ]}, + + % {how_are_you, [ + % {metrics_publishers, [ + % % {hay_statsd_publisher, #{ + % % key_prefix => <<"limiter.">>, + % % host => "localhost", + % % port => 8125 + % % }} + % ]} + % ]}, + + {os_mon, [ + % for better compatibility with busybox coreutils + {disksup_posix_only, true} + ]}, + + {scoper, [ + {storage, scoper_storage_logger} + ]}, + + {prometheus, [ + {collectors, [default]} + ]} +]. diff --git a/config/vm.args b/config/vm.args new file mode 100644 index 00000000..cd2c15ba --- /dev/null +++ b/config/vm.args @@ -0,0 +1,3 @@ +-sname limiter + +-setcookie limiter_cookie \ No newline at end of file diff --git a/docker-compose.sh b/docker-compose.sh new file mode 100755 index 00000000..91f111bf --- /dev/null +++ b/docker-compose.sh @@ -0,0 +1,45 @@ +#!/bin/bash +cat < ["apps/*/src"], + filter => "*.erl", + rules => [ + {elvis_style, line_length, #{limit => 120, skip_comments => false}}, + {elvis_style, no_tabs}, + {elvis_style, no_trailing_whitespace}, + {elvis_style, macro_module_names}, + {elvis_style, operator_spaces, #{rules => [{right, ","}, {right, "++"}, {left, "++"}]}}, + {elvis_style, nesting_level, #{level => 3}}, + {elvis_style, god_modules, #{limit => 25}}, + {elvis_style, no_if_expression}, + {elvis_style, invalid_dynamic_call, #{ignore => [elvis]}}, + {elvis_style, used_ignored_variable}, + {elvis_style, no_behavior_info}, + {elvis_style, module_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$"}}, + {elvis_style, function_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*$"}}, + {elvis_style, state_record_and_type}, + {elvis_style, no_spec_with_records}, + {elvis_style, dont_repeat_yourself, #{min_complexity => 10}}, + {elvis_style, no_debug_call, #{ignore => [elvis, elvis_utils]}} + ] + }, + #{ + dirs => ["apps/*/test"], + filter => "*.erl", + rules => [ + {elvis_style, line_length, #{limit => 120, skip_comments => false}}, + {elvis_style, no_tabs}, + {elvis_style, no_trailing_whitespace}, + {elvis_style, macro_module_names}, + {elvis_style, operator_spaces, #{rules => [{right, ","}, {right, "++"}, {left, "++"}]}}, + {elvis_style, nesting_level, #{level => 3}}, + {elvis_style, god_modules, #{limit => 25}}, + {elvis_style, no_if_expression}, + {elvis_style, invalid_dynamic_call, #{ignore => [elvis]}}, + {elvis_style, used_ignored_variable}, + {elvis_style, no_behavior_info}, + {elvis_style, module_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$"}}, + {elvis_style, function_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*$"}}, + {elvis_style, state_record_and_type}, + {elvis_style, no_spec_with_records}, + {elvis_style, dont_repeat_yourself, #{min_complexity => 10}}, + {elvis_style, no_debug_call, #{ignore => [elvis, elvis_utils]}} + ] + }, + #{ + dirs => ["."], + filter => "Makefile", + ruleset => makefiles + }, + #{ + dirs => ["."], + filter => "elvis.config", + ruleset => elvis_config + }, + #{ + dirs => ["."], + filter => "rebar.config", + rules => [ + {elvis_style, line_length, #{limit => 120, skip_comments => false}}, + {elvis_style, no_tabs}, + {elvis_style, no_trailing_whitespace} + ] + }, + #{ + dirs => ["."], + filter => "rebar.config", + rules => [ + {elvis_style, line_length, #{limit => 120, skip_comments => false}}, + {elvis_style, no_tabs}, + {elvis_style, no_trailing_whitespace} + ] + }, + #{ + dirs => ["src"], + filter => "*.app.src", + rules => [ + {elvis_style, line_length, #{limit => 120, skip_comments => false}}, + {elvis_style, no_tabs}, + {elvis_style, no_trailing_whitespace} + ] + } + ]} + ]} +]. diff --git a/rebar.config b/rebar.config new file mode 100644 index 00000000..ad1f9d33 --- /dev/null +++ b/rebar.config @@ -0,0 +1,121 @@ +%% Common project erlang options. +{erl_opts, [ + % mandatory + debug_info, + warnings_as_errors, + warn_export_all, + warn_missing_spec, + warn_untyped_record, + warn_export_vars, + + % by default + warn_unused_record, + warn_bif_clash, + warn_obsolete_guard, + warn_unused_vars, + warn_shadow_vars, + warn_unused_import, + warn_unused_function, + warn_deprecated_function + + % at will + % bin_opt_info + % no_auto_import + % warn_missing_spec_all +]}. + +%% Common project dependencies. +{deps, [ + {damsel, + {git, "https://github.com/rbkmoney/damsel.git", + {branch, "release/erlang/master"} + } + }, + {erl_health, + {git, "https://github.com/rbkmoney/erlang-health.git", + {branch, "master"}} + }, + {genlib , + {git, "https://github.com/rbkmoney/genlib.git", + {branch, "master"}} + }, + {scoper, + {git, "https://github.com/rbkmoney/scoper.git", + {branch, "master"}} + }, + {how_are_you, + {git, "https://github.com/rbkmoney/how_are_you.git", + {branch, "master"}} + }, + {woody, + {git, "https://github.com/rbkmoney/woody_erlang.git", + {branch, "master"}} + }, + {prometheus, "4.6.0"}, + {prometheus_cowboy, "0.1.8"} +]}. + + +%% XRef checks +{xref_checks, [ + undefined_function_calls, + undefined_functions, + deprecated_functions_calls, + deprecated_functions +]}. + +%% Tests +{cover_enabled, true}. + +%% Dialyzer static analyzing +{dialyzer, [ + {warnings, [ + % mandatory + unmatched_returns, + error_handling, + race_conditions, + unknown + % hardcore mode + % overspecs, + % underspecs + ]}, + {plt_apps, all_deps} +]}. + +{profiles, [ + {prod, [ + {deps, [ + {logger_logstash_formatter, + {git, "https://github.com/rbkmoney/logger_logstash_formatter.git", + {ref, "87e52c755"}} + }, + % for introspection on production + {recon, "2.3.2"} + ]}, + {relx, [ + {release, {limiter, "1.0.0"}, [ + {recon, load}, % tools for introspection + {runtime_tools, load}, % debugger + {tools, load}, % profiler + {logger_logstash_formatter, load}, % log formatter + sasl, + limiter + ]}, + {sys_config, "./config/sys.config"}, + {vm_args, "./config/vm.args"}, + {mode, minimal}, + {extended_start_script, true} + ]} + ]} +]}. + +{plugins, [ + {erlfmt, "0.8.0"}, + {rebar3_thrift_compiler, + {git, "https://github.com/rbkmoney/rebar3_thrift_compiler.git", {tag, "0.3.1"}}} +]}. + +{erlfmt, [ + {print_width, 120}, + {files, "apps/*/{src,include,test}/*.{hrl,erl}"} +]}. From bdaca98e1a6167a64fd48ae72b8b0776c4a142c2 Mon Sep 17 00:00:00 2001 From: WWW_cool Date: Wed, 17 Feb 2021 12:49:36 +0300 Subject: [PATCH 03/54] added rebar lock --- rebar.lock | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 rebar.lock diff --git a/rebar.lock b/rebar.lock new file mode 100644 index 00000000..94d4f019 --- /dev/null +++ b/rebar.lock @@ -0,0 +1,102 @@ +{"1.2.0", +[{<<"accept">>,{pkg,<<"accept">>,<<"0.3.5">>},2}, + {<<"bear">>,{pkg,<<"bear">>,<<"0.8.7">>},2}, + {<<"cache">>,{pkg,<<"cache">>,<<"2.2.0">>},1}, + {<<"certifi">>,{pkg,<<"certifi">>,<<"2.5.1">>},2}, + {<<"cg_mon">>, + {git,"https://github.com/rbkmoney/cg_mon.git", + {ref,"5a87a37694e42b6592d3b4164ae54e0e87e24e18"}}, + 1}, + {<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.8.0">>},1}, + {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.9.1">>},2}, + {<<"damsel">>, + {git,"https://github.com/rbkmoney/damsel.git", + {ref,"9e0e884bfeaf8ad1cadd01802200f4e204cf27e3"}}, + 0}, + {<<"erl_health">>, + {git,"https://github.com/rbkmoney/erlang-health.git", + {ref,"982af88738ca062eea451436d830eef8c1fbe3f9"}}, + 0}, + {<<"folsom">>, + {git,"https://github.com/folsom-project/folsom.git", + {ref,"eeb1cc467eb64bd94075b95b8963e80d8b4df3df"}}, + 1}, + {<<"genlib">>, + {git,"https://github.com/rbkmoney/genlib.git", + {ref,"4565a8d73f34a0b78cca32c9cd2b97d298bdadf8"}}, + 0}, + {<<"gproc">>,{pkg,<<"gproc">>,<<"0.8.0">>},1}, + {<<"hackney">>,{pkg,<<"hackney">>,<<"1.15.2">>},1}, + {<<"how_are_you">>, + {git,"https://github.com/rbkmoney/how_are_you.git", + {ref,"29f9d3d7c35f7a2d586c8571f572838df5ec91dd"}}, + 0}, + {<<"idna">>,{pkg,<<"idna">>,<<"6.0.0">>},2}, + {<<"jsx">>,{pkg,<<"jsx">>,<<"3.0.0">>},1}, + {<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},2}, + {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.2.0">>},2}, + {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.0">>},3}, + {<<"prometheus">>,{pkg,<<"prometheus">>,<<"4.6.0">>},0}, + {<<"prometheus_cowboy">>,{pkg,<<"prometheus_cowboy">>,<<"0.1.8">>},0}, + {<<"prometheus_httpd">>,{pkg,<<"prometheus_httpd">>,<<"2.1.11">>},1}, + {<<"ranch">>,{pkg,<<"ranch">>,<<"1.7.1">>},2}, + {<<"scoper">>, + {git,"https://github.com/rbkmoney/scoper.git", + {ref,"89a973bf3cedc5a48c9fd89d719d25e79fe10027"}}, + 0}, + {<<"snowflake">>, + {git,"https://github.com/rbkmoney/snowflake.git", + {ref,"7f379ad5e389e1c96389a8d60bae8117965d6a6d"}}, + 1}, + {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.5">>},2}, + {<<"thrift">>, + {git,"https://github.com/rbkmoney/thrift_erlang.git", + {ref,"846a0819d9b6d09d0c31f160e33a78dbad2067b4"}}, + 1}, + {<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.4.1">>},3}, + {<<"woody">>, + {git,"https://github.com/rbkmoney/woody_erlang.git", + {ref,"58f56b462429ab1fee65e1bdb34b73512406ba00"}}, + 0}]}. +[ +{pkg_hash,[ + {<<"accept">>, <<"B33B127ABCA7CC948BBE6CAA4C263369ABF1347CFA9D8E699C6D214660F10CD1">>}, + {<<"bear">>, <<"16264309AE5D005D03718A5C82641FCC259C9E8F09ADEB6FD79CA4271168656F">>}, + {<<"cache">>, <<"3C11DBF4CD8FCD5787C95A5FB2A04038E3729CFCA0386016EEA8C953AB48A5AB">>}, + {<<"certifi">>, <<"867CE347F7C7D78563450A18A6A28A8090331E77FA02380B4A21962A65D36EE5">>}, + {<<"cowboy">>, <<"F3DC62E35797ECD9AC1B50DB74611193C29815401E53BAC9A5C0577BD7BC667D">>}, + {<<"cowlib">>, <<"61A6C7C50CF07FDD24B2F45B89500BB93B6686579B069A89F88CB211E1125C78">>}, + {<<"gproc">>, <<"CEA02C578589C61E5341FCE149EA36CCEF236CC2ECAC8691FBA408E7EA77EC2F">>}, + {<<"hackney">>, <<"07E33C794F8F8964EE86CEBEC1A8ED88DB5070E52E904B8F12209773C1036085">>}, + {<<"idna">>, <<"689C46CBCDF3524C44D5F3DDE8001F364CD7608A99556D8FBD8239A5798D4C10">>}, + {<<"jsx">>, <<"20A170ABD4335FC6DB24D5FAD1E5D677C55DADF83D1B20A8A33B5FE159892A39">>}, + {<<"metrics">>, <<"25F094DEA2CDA98213CECC3AEFF09E940299D950904393B2A29D191C346A8486">>}, + {<<"mimerl">>, <<"67E2D3F571088D5CFD3E550C383094B47159F3EEE8FFA08E64106CDF5E981BE3">>}, + {<<"parse_trans">>, <<"09765507A3C7590A784615CFD421D101AEC25098D50B89D7AA1D66646BC571C1">>}, + {<<"prometheus">>, <<"20510F381DB1CCAB818B4CF2FAC5FA6AB5CC91BC364A154399901C001465F46F">>}, + {<<"prometheus_cowboy">>, <<"CFCE0BC7B668C5096639084FCD873826E6220EA714BF60A716F5BD080EF2A99C">>}, + {<<"prometheus_httpd">>, <<"F616ED9B85B536B195D94104063025A91F904A4CFC20255363F49A197D96C896">>}, + {<<"ranch">>, <<"6B1FAB51B49196860B733A49C07604465A47BDB78AA10C1C16A3D199F7F8C881">>}, + {<<"ssl_verify_fun">>, <<"6EAF7AD16CB568BB01753DBBD7A95FF8B91C7979482B95F38443FE2C8852A79B">>}, + {<<"unicode_util_compat">>, <<"D869E4C68901DD9531385BB0C8C40444EBF624E60B6962D95952775CAC5E90CD">>}]}, +{pkg_hash_ext,[ + {<<"accept">>, <<"11B18C220BCC2EAB63B5470C038EF10EB6783BCB1FCDB11AA4137DEFA5AC1BB8">>}, + {<<"bear">>, <<"534217DCE6A719D59E54FB0EB7A367900DBFC5F85757E8C1F94269DF383F6D9B">>}, + {<<"cache">>, <<"3E7D6706DE5DF76C4D71C895B4BE62B01C3DE6EDB63197035E465C3BCE63F19B">>}, + {<<"certifi">>, <<"805ABD97539CAF89EC6D4732C91E62BA9DA0CDA51AC462380BBD28EE697A8C42">>}, + {<<"cowboy">>, <<"4643E4FBA74AC96D4D152C75803DE6FAD0B3FA5DF354C71AFDD6CBEEB15FAC8A">>}, + {<<"cowlib">>, <<"E4175DC240A70D996156160891E1C62238EDE1729E45740BDD38064DAD476170">>}, + {<<"gproc">>, <<"580ADAFA56463B75263EF5A5DF4C86AF321F68694E7786CB057FD805D1E2A7DE">>}, + {<<"hackney">>, <<"E0100F8EF7D1124222C11AD362C857D3DF7CB5F4204054F9F0F4A728666591FC">>}, + {<<"idna">>, <<"4BDD305EB64E18B0273864920695CB18D7A2021F31A11B9C5FBCD9A253F936E2">>}, + {<<"jsx">>, <<"37BECA0435F5CA8A2F45F76A46211E76418FBEF80C36F0361C249FC75059DC6D">>}, + {<<"metrics">>, <<"69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16">>}, + {<<"mimerl">>, <<"F278585650AA581986264638EBF698F8BB19DF297F66AD91B18910DFC6E19323">>}, + {<<"parse_trans">>, <<"17EF63ABDE837AD30680EA7F857DD9E7CED9476CDD7B0394432AF4BFC241B960">>}, + {<<"prometheus">>, <<"4905FD2992F8038ECCD7AA0CD22F40637ED618C0BED1F75C05AACEC15B7545DE">>}, + {<<"prometheus_cowboy">>, <<"BA286BECA9302618418892D37BCD5DC669A6CC001F4EB6D6AF85FF81F3F4F34C">>}, + {<<"prometheus_httpd">>, <<"0BBE831452CFDF9588538EB2F570B26F30C348ADAE5E95A7D87F35A5910BCF92">>}, + {<<"ranch">>, <<"451D8527787DF716D99DC36162FCA05934915DB0B6141BBDAC2EA8D3C7AFC7D7">>}, + {<<"ssl_verify_fun">>, <<"13104D7897E38ED7F044C4DE953A6C28597D1C952075EB2E328BC6D6F2BFC496">>}, + {<<"unicode_util_compat">>, <<"1D1848C40487CDB0B30E8ED975E34E025860C02E419CB615D255849F3427439D">>}]} +]. From 563fb97ea623dec7d6ca22a82cc3f3998cb22266 Mon Sep 17 00:00:00 2001 From: WWW_cool Date: Wed, 17 Feb 2021 12:52:20 +0300 Subject: [PATCH 04/54] added build_utils --- .gitmodules | 2 +- build_utils | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 160000 build_utils diff --git a/.gitmodules b/.gitmodules index ca5a761f..a3ea65b2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "build_utils"] path = build_utils - url = git@github.com:rbkmoney/build_utils.git + url = ../build_utils.git branch = master diff --git a/build_utils b/build_utils new file mode 160000 index 00000000..e1318727 --- /dev/null +++ b/build_utils @@ -0,0 +1 @@ +Subproject commit e1318727d4d0c3e48f5122bf3197158b6695f50e From 9bf3e296360ddc5679f60374629a3165181ec9ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D0=B5=D0=BC?= Date: Mon, 17 May 2021 13:34:16 +0300 Subject: [PATCH 05/54] P2C-14: Limiter (#1) * draft * added format * added backend stuff * added requested changes except batch in range machine * added error sort in handler, add lim_body and its validation * added start time and shard size, implemented shard id generation and time range calculation * fixed xref * fixed format * added unit tests * refactored errors * updated to new proto * added config marshaling * fixed format * added base tests * added exchange tests * fixed linter * added requested changes * updated proto ref --- Makefile | 2 +- apps/limiter/src/lim_accounting.erl | 166 ++++ apps/limiter/src/lim_body.erl | 81 ++ apps/limiter/src/lim_client_woody.erl | 57 ++ apps/limiter/src/lim_config_codec.erl | 225 +++++ apps/limiter/src/lim_config_machine.erl | 768 ++++++++++++++++++ .../src/lim_config_machinery_schema.erl | 112 +++ apps/limiter/src/lim_configurator.erl | 93 +++ apps/limiter/src/lim_context.erl | 305 +++++++ apps/limiter/src/lim_handler.erl | 153 ++++ apps/limiter/src/lim_maybe.erl | 53 ++ apps/limiter/src/lim_p_transfer.erl | 94 +++ apps/limiter/src/lim_pipeline.erl | 74 ++ apps/limiter/src/lim_proto_utils.erl | 67 ++ apps/limiter/src/lim_range_codec.erl | 178 ++++ apps/limiter/src/lim_range_machine.erl | 268 ++++++ .../src/lim_range_machinery_schema.erl | 119 +++ apps/limiter/src/lim_rates.erl | 71 ++ apps/limiter/src/lim_router.erl | 17 + apps/limiter/src/lim_string.erl | 26 + apps/limiter/src/lim_time.erl | 27 + apps/limiter/src/lim_turnover_processor.erl | 181 +++++ apps/limiter/src/lim_utils.erl | 30 + apps/limiter/src/limiter.app.src | 3 + apps/limiter/src/limiter.erl | 154 ++++ apps/limiter/test/lim_client.erl | 78 ++ apps/limiter/test/lim_configurator_SUITE.erl | 104 +++ apps/limiter/test/lim_mock.erl | 85 ++ apps/limiter/test/lim_mock_service.erl | 9 + apps/limiter/test/lim_turnover_SUITE.erl | 321 ++++++++ config/sys.config | 22 + docker-compose.sh | 15 + elvis.config | 4 +- rebar.config | 14 + rebar.lock | 16 + test/machinegun/config.yaml | 17 + test/machinegun/cookie | 1 + 37 files changed, 4007 insertions(+), 3 deletions(-) create mode 100644 apps/limiter/src/lim_accounting.erl create mode 100644 apps/limiter/src/lim_body.erl create mode 100644 apps/limiter/src/lim_client_woody.erl create mode 100644 apps/limiter/src/lim_config_codec.erl create mode 100644 apps/limiter/src/lim_config_machine.erl create mode 100644 apps/limiter/src/lim_config_machinery_schema.erl create mode 100644 apps/limiter/src/lim_configurator.erl create mode 100644 apps/limiter/src/lim_context.erl create mode 100644 apps/limiter/src/lim_handler.erl create mode 100644 apps/limiter/src/lim_maybe.erl create mode 100644 apps/limiter/src/lim_p_transfer.erl create mode 100644 apps/limiter/src/lim_pipeline.erl create mode 100644 apps/limiter/src/lim_proto_utils.erl create mode 100644 apps/limiter/src/lim_range_codec.erl create mode 100644 apps/limiter/src/lim_range_machine.erl create mode 100644 apps/limiter/src/lim_range_machinery_schema.erl create mode 100644 apps/limiter/src/lim_rates.erl create mode 100644 apps/limiter/src/lim_router.erl create mode 100644 apps/limiter/src/lim_string.erl create mode 100644 apps/limiter/src/lim_time.erl create mode 100644 apps/limiter/src/lim_turnover_processor.erl create mode 100644 apps/limiter/src/lim_utils.erl create mode 100644 apps/limiter/src/limiter.erl create mode 100644 apps/limiter/test/lim_client.erl create mode 100644 apps/limiter/test/lim_configurator_SUITE.erl create mode 100644 apps/limiter/test/lim_mock.erl create mode 100644 apps/limiter/test/lim_mock_service.erl create mode 100644 apps/limiter/test/lim_turnover_SUITE.erl create mode 100644 test/machinegun/config.yaml create mode 100644 test/machinegun/cookie diff --git a/Makefile b/Makefile index d3069263..b496da3e 100644 --- a/Makefile +++ b/Makefile @@ -74,4 +74,4 @@ distclean: # CALL_W_CONTAINER test: submodules - $(REBAR) ct + $(REBAR) do eunit, ct diff --git a/apps/limiter/src/lim_accounting.erl b/apps/limiter/src/lim_accounting.erl new file mode 100644 index 00000000..6362fc24 --- /dev/null +++ b/apps/limiter/src/lim_accounting.erl @@ -0,0 +1,166 @@ +-module(lim_accounting). + +-include_lib("damsel/include/dmsl_accounter_thrift.hrl"). +-include_lib("damsel/include/dmsl_base_thrift.hrl"). + +-export([plan/3]). +-export([hold/3]). +-export([commit/3]). +-export([rollback/3]). +-export([get_plan/2]). +-export([get_balance/2]). +-export([get_default_currency/0]). +-export([create_account/2]). + +-type currency() :: dmsl_domain_thrift:'CurrencySymbolicCode'(). +-type amount() :: dmsl_domain_thrift:'Amount'(). +-type plan_id() :: dmsl_accounter_thrift:'PlanID'(). +-type batch_id() :: dmsl_accounter_thrift:'BatchID'(). +-type posting() :: dmsl_accounter_thrift:'Posting'(). +-type batch() :: {batch_id(), [posting()]}. +-type account_id() :: dmsl_accounter_thrift:'AccountID'(). +-type lim_context() :: lim_context:t(). + +-type balance() :: #{ + account_id := account_id(), + own_amount := amount(), + min_available_amount := amount(), + max_available_amount := amount(), + currency := currency() +}. + +-type invalid_request_error() :: {invalid_request, list(binary())}. + +-export_type([account_id/0]). +-export_type([amount/0]). +-export_type([balance/0]). +-export_type([plan_id/0]). +-export_type([batch/0]). +-export_type([posting/0]). +-export_type([batch_id/0]). +-export_type([invalid_request_error/0]). + +-define(DEFAULT_CURRENCY, <<"RUB">>). + +-spec plan(plan_id(), [batch()], lim_context()) -> ok | {error, invalid_request_error()}. +plan(_PlanID, [], _LimitContext) -> + error(badarg); +plan(_PlanID, Batches, _LimitContext) when not is_list(Batches) -> + error(badarg); +plan(PlanID, Batches, LimitContext) -> + lists:foldl( + fun(Batch, _) -> hold(PlanID, Batch, LimitContext) end, + undefined, + Batches + ). + +-spec hold(plan_id(), batch(), lim_context()) -> ok | {error, invalid_request_error()}. +hold(PlanID, Batch, LimitContext) -> + do('Hold', construct_plan_change(PlanID, Batch), LimitContext). + +-spec commit(plan_id(), [batch()], lim_context()) -> ok | {error, invalid_request_error()}. +commit(PlanID, Batches, LimitContext) -> + do('CommitPlan', construct_plan(PlanID, Batches), LimitContext). + +-spec rollback(plan_id(), [batch()], lim_context()) -> ok | {error, invalid_request_error()}. +rollback(PlanID, Batches, LimitContext) -> + do('RollbackPlan', construct_plan(PlanID, Batches), LimitContext). + +-spec get_plan(plan_id(), lim_context()) -> {ok, [batch()]} | {error, notfound}. +get_plan(PlanID, LimitContext) -> + case call_accounter('GetPlan', {PlanID}, LimitContext) of + {ok, #accounter_PostingPlan{batch_list = BatchList}} -> + {ok, decode_batch_list(BatchList)}; + {exception, #accounter_PlanNotFound{}} -> + {error, notfound} + end. + +-spec get_balance(account_id(), lim_context()) -> {ok, balance()} | {error, notfound}. +get_balance(AccountID, LimitContext) -> + case call_accounter('GetAccountByID', {AccountID}, LimitContext) of + {ok, Result} -> + {ok, construct_balance(AccountID, Result)}; + {exception, #accounter_AccountNotFound{}} -> + {error, notfound} + end. + +do(Op, Plan, LimitContext) -> + case call_accounter(Op, {Plan}, LimitContext) of + {ok, _Clock} -> + ok; + {exception, Exception} -> + {error, {invalid_request, convert_exception(Exception)}} + end. + +construct_plan_change(PlanID, {BatchID, Postings}) -> + #accounter_PostingPlanChange{ + id = PlanID, + batch = #accounter_PostingBatch{ + id = BatchID, + postings = Postings + } + }. + +construct_plan(PlanID, Batches) -> + #accounter_PostingPlan{ + id = PlanID, + batch_list = [ + #accounter_PostingBatch{ + id = BatchID, + postings = Postings + } + || {BatchID, Postings} <- Batches + ] + }. + +decode_batch_list(BatchList) -> + [{BatchID, Postings} || #accounter_PostingBatch{id = BatchID, postings = Postings} <- BatchList]. + +construct_balance( + AccountID, + #accounter_Account{ + own_amount = OwnAmount, + min_available_amount = MinAvailableAmount, + max_available_amount = MaxAvailableAmount, + currency_sym_code = Currency + } +) -> + #{ + account_id => AccountID, + own_amount => OwnAmount, + min_available_amount => MinAvailableAmount, + max_available_amount => MaxAvailableAmount, + currency => Currency + }. + +-spec get_default_currency() -> currency(). +get_default_currency() -> + ?DEFAULT_CURRENCY. + +-spec create_account(currency(), lim_context()) -> {ok, account_id()}. +create_account(CurrencyCode, LimitContext) -> + create_account(CurrencyCode, undefined, LimitContext). + +create_account(CurrencyCode, Description, LimitContext) -> + call_accounter( + 'CreateAccount', + {construct_prototype(CurrencyCode, Description)}, + LimitContext + ). + +construct_prototype(CurrencyCode, Description) -> + #accounter_AccountPrototype{ + currency_sym_code = CurrencyCode, + description = Description + }. + +%% + +call_accounter(Function, Args, LimitContext) -> + {ok, WoodyContext} = lim_context:woody_context(LimitContext), + lim_client_woody:call(accounter, Function, Args, WoodyContext). + +convert_exception(#'InvalidRequest'{errors = Errors}) -> + Errors; +convert_exception(#accounter_InvalidPostingParams{wrong_postings = Errors}) -> + maps:fold(fun(_, Error, Acc) -> [Error | Acc] end, [], Errors). diff --git a/apps/limiter/src/lim_body.erl b/apps/limiter/src/lim_body.erl new file mode 100644 index 00000000..903d448f --- /dev/null +++ b/apps/limiter/src/lim_body.erl @@ -0,0 +1,81 @@ +-module(lim_body). + +-include_lib("limiter_proto/include/lim_base_thrift.hrl"). +-include_lib("limiter_proto/include/lim_limiter_thrift.hrl"). + +-export([get_body/3]). +-export([create_body_from_cash/2]). + +-type t() :: {amount, amount()} | {cash, cash()}. + +-type amount() :: integer(). +-type cash() :: #{ + amount := amount(), + currency := currency() +}. + +-type currency() :: lim_base_thrift:'CurrencySymbolicCode'(). +-type config() :: lim_config_machine:config(). +-type body_type() :: full | partial. +-type get_body_error() :: notfound | lim_rates:convertation_error(). + +-export_type([t/0]). +-export_type([cash/0]). +-export_type([get_body_error/0]). + +-spec get_body(body_type(), config(), lim_context:t()) -> {ok, t()} | {error, get_body_error()}. +get_body(BodyType, Config = #{body_type := {cash, ConfigCurrency}}, LimitContext) -> + ContextType = lim_config_machine:context_type(Config), + {ok, Operation} = lim_context:get_operation(ContextType, LimitContext), + case get_body_for_operation(BodyType, Operation, Config, LimitContext) of + {ok, {cash, #{currency := ConfigCurrency}}} = Result -> + Result; + {ok, {cash, #{amount := Amount, currency := Currency}}} -> + case lim_rates:get_converted_amount({Amount, Currency}, Config, LimitContext) of + {ok, ConvertedAmount} -> + {ok, create_body_from_cash(ConvertedAmount, ConfigCurrency)}; + Error -> + Error + end; + Error -> + Error + end. + +-spec get_body_for_operation(body_type(), lim_context:context_operation(), config(), lim_context:t()) -> + {ok, t()} | {error, notfound}. +get_body_for_operation(full, invoice = Operation, Config, LimitContext) -> + ContextType = lim_config_machine:context_type(Config), + lim_context:get_from_context(ContextType, cost, Operation, LimitContext); +get_body_for_operation(full, invoice_adjustment, Config, LimitContext) -> + ContextType = lim_config_machine:context_type(Config), + lim_context:get_from_context(ContextType, cost, invoice, LimitContext); +get_body_for_operation(full, invoice_payment = Operation, Config, LimitContext) -> + ContextType = lim_config_machine:context_type(Config), + lim_context:get_from_context(ContextType, cost, Operation, LimitContext); +get_body_for_operation(full, invoice_payment_adjustment, Config, LimitContext) -> + ContextType = lim_config_machine:context_type(Config), + lim_context:get_from_context(ContextType, cost, invoice_payment, LimitContext); +get_body_for_operation(full, invoice_payment_refund, Config, LimitContext) -> + ContextType = lim_config_machine:context_type(Config), + lim_context:get_from_context(ContextType, cost, invoice_payment, LimitContext); +get_body_for_operation(full, invoice_payment_chargeback = Operation, Config, LimitContext) -> + ContextType = lim_config_machine:context_type(Config), + lim_context:get_from_context(ContextType, body, Operation, LimitContext); +get_body_for_operation(partial, invoice, _Config, _LimitContext) -> + {error, notfound}; +get_body_for_operation(partial, invoice_adjustment, _Config, _LimitContext) -> + {error, notfound}; +get_body_for_operation(partial, invoice_payment = Operation, Config, LimitContext) -> + ContextType = lim_config_machine:context_type(Config), + lim_context:get_from_context(ContextType, capture_cost, Operation, LimitContext); +get_body_for_operation(partial, invoice_payment_adjustment, _Config, _LimitContext) -> + {error, notfound}; +get_body_for_operation(partial, invoice_payment_refund = Operation, Config, LimitContext) -> + ContextType = lim_config_machine:context_type(Config), + lim_context:get_from_context(ContextType, cost, Operation, LimitContext); +get_body_for_operation(partial, invoice_payment_chargeback, _Config, _LimitContext) -> + {error, notfound}. + +-spec create_body_from_cash(amount(), currency()) -> t(). +create_body_from_cash(Amount, Currency) -> + {cash, #{amount => Amount, currency => Currency}}. diff --git a/apps/limiter/src/lim_client_woody.erl b/apps/limiter/src/lim_client_woody.erl new file mode 100644 index 00000000..c877427a --- /dev/null +++ b/apps/limiter/src/lim_client_woody.erl @@ -0,0 +1,57 @@ +-module(lim_client_woody). + +-export([call/4]). +-export([call/5]). +-export([get_service_client_url/1]). + +-define(APP, limiter). +-define(DEFAULT_DEADLINE, 5000). + +%% +-type service_name() :: atom(). + +-spec call(service_name(), woody:func(), woody:args(), woody_context:ctx()) -> woody:result(). +call(ServiceName, Function, Args, Context) -> + EventHandler = scoper_woody_event_handler, + call(ServiceName, Function, Args, Context, EventHandler). + +-spec call(service_name(), woody:func(), woody:args(), woody_context:ctx(), woody:ev_handler()) -> woody:result(). +call(ServiceName, Function, Args, Context0, EventHandler) -> + Deadline = get_service_deadline(ServiceName), + Context1 = set_deadline(Deadline, Context0), + Url = get_service_client_url(ServiceName), + Service = get_service_modname(ServiceName), + Request = {Service, Function, Args}, + woody_client:call( + Request, + #{url => Url, event_handler => EventHandler}, + Context1 + ). + +get_service_client_config(ServiceName) -> + ServiceClients = genlib_app:env(?APP, service_clients, #{}), + maps:get(ServiceName, ServiceClients, #{}). + +-spec get_service_client_url(atom()) -> lim_maybe:maybe(woody:url()). +get_service_client_url(ServiceName) -> + maps:get(url, get_service_client_config(ServiceName), undefined). + +-spec get_service_modname(service_name()) -> woody:service(). +get_service_modname(xrates) -> + {xrates_rate_thrift, 'Rates'}; +get_service_modname(accounter) -> + {dmsl_accounter_thrift, 'Accounter'}. + +-spec get_service_deadline(service_name()) -> undefined | woody_deadline:deadline(). +get_service_deadline(ServiceName) -> + ServiceClient = get_service_client_config(ServiceName), + Timeout = maps:get(deadline, ServiceClient, ?DEFAULT_DEADLINE), + woody_deadline:from_timeout(Timeout). + +set_deadline(Deadline, Context) -> + case woody_context:get_deadline(Context) of + undefined -> + woody_context:set_deadline(Deadline, Context); + _AlreadySet -> + Context + end. diff --git a/apps/limiter/src/lim_config_codec.erl b/apps/limiter/src/lim_config_codec.erl new file mode 100644 index 00000000..5a145f12 --- /dev/null +++ b/apps/limiter/src/lim_config_codec.erl @@ -0,0 +1,225 @@ +-module(lim_config_codec). + +-include_lib("limiter_proto/include/lim_limiter_config_thrift.hrl"). + +-export([marshal/2]). +-export([unmarshal/2]). +-export([marshal_config/1]). +-export([unmarshal_body_type/1]). + +%% Types + +-type type_name() :: atom() | {list, atom()} | {set, atom()}. + +-type encoded_value() :: encoded_value(any()). +-type encoded_value(T) :: T. + +-type decoded_value() :: decoded_value(any()). +-type decoded_value(T) :: T. + +maybe_apply(undefined, _) -> + undefined; +maybe_apply(Value, Fun) -> + Fun(Value). + +%% API + +-spec marshal(type_name(), decoded_value()) -> encoded_value(). +marshal(timestamped_change, {ev, Timestamp, Change}) -> + #limiter_config_TimestampedChange{ + change = marshal_change(Change), + occured_at = marshal_timestamp(Timestamp) + }. + +marshal_timestamp({DateTime, USec}) -> + DateTimeinSeconds = genlib_time:daytime_to_unixtime(DateTime), + {TimeinUnit, Unit} = + case USec of + 0 -> + {DateTimeinSeconds, second}; + USec -> + MicroSec = erlang:convert_time_unit(DateTimeinSeconds, second, microsecond), + {MicroSec + USec, microsecond} + end, + genlib_rfc3339:format_relaxed(TimeinUnit, Unit). + +marshal_change({created, Config}) -> + {created, #limiter_config_CreatedChange{limit_config = marshal_config(Config)}}. + +-spec marshal_config(decoded_value()) -> encoded_value(). +marshal_config(Config) -> + #limiter_config_LimitConfig{ + id = lim_config_machine:id(Config), + processor_type = lim_config_machine:processor_type(Config), + description = lim_config_machine:description(Config), + body_type = marshal_body_type(lim_config_machine:body_type(Config)), + created_at = lim_config_machine:created_at(Config), + started_at = lim_config_machine:started_at(Config), + shard_size = lim_config_machine:shard_size(Config), + time_range_type = marshal_time_range_type(lim_config_machine:time_range_type(Config)), + context_type = marshal_context_type(lim_config_machine:context_type(Config)), + type = maybe_apply(lim_config_machine:type(Config), fun marshal_type/1), + scope = maybe_apply(lim_config_machine:scope(Config), fun marshal_scope/1) + }. + +marshal_body_type(amount) -> + {amount, #limiter_config_LimitBodyTypeAmount{}}; +marshal_body_type({cash, Currency}) -> + {cash, #limiter_config_LimitBodyTypeCash{currency = Currency}}. + +marshal_time_range_type({calendar, CalendarType}) -> + {calendar, marshal_calendar_time_range_type(CalendarType)}; +marshal_time_range_type({interval, Amount}) -> + {interval, #time_range_TimeRangeTypeInterval{amount = Amount}}. + +marshal_calendar_time_range_type(day) -> + {day, #time_range_TimeRangeTypeCalendarDay{}}; +marshal_calendar_time_range_type(week) -> + {week, #time_range_TimeRangeTypeCalendarWeek{}}; +marshal_calendar_time_range_type(month) -> + {month, #time_range_TimeRangeTypeCalendarMonth{}}; +marshal_calendar_time_range_type(year) -> + {year, #time_range_TimeRangeTypeCalendarYear{}}. + +marshal_context_type(payment_processing) -> + {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}. + +marshal_type(turnover) -> + {turnover, #limiter_config_LimitTypeTurnover{}}. + +marshal_scope({scope, Type}) -> + {scope, marshal_scope_type(Type)}; +marshal_scope(global) -> + {scope_global, #limiter_config_LimitScopeGlobal{}}. + +marshal_scope_type(party) -> + {party, #limiter_config_LimitScopeTypeParty{}}; +marshal_scope_type(shop) -> + {shop, #limiter_config_LimitScopeTypeShop{}}; +marshal_scope_type(wallet) -> + {wallet, #limiter_config_LimitScopeTypeWallet{}}; +marshal_scope_type(identity) -> + {identity, #limiter_config_LimitScopeTypeIdentity{}}. + +%% + +-spec unmarshal(type_name(), encoded_value()) -> decoded_value(). +unmarshal(timestamped_change, TimestampedChange) -> + Timestamp = unmarshal_timestamp(TimestampedChange#limiter_config_TimestampedChange.occured_at), + Change = unmarshal_change(TimestampedChange#limiter_config_TimestampedChange.change), + {ev, Timestamp, Change}. + +unmarshal_timestamp(Timestamp) when is_binary(Timestamp) -> + try + MicroSeconds = genlib_rfc3339:parse(Timestamp, microsecond), + case genlib_rfc3339:is_utc(Timestamp) of + false -> + erlang:error({bad_timestamp, not_utc}, [Timestamp]); + true -> + USec = MicroSeconds rem 1000000, + DateTime = calendar:system_time_to_universal_time(MicroSeconds, microsecond), + {DateTime, USec} + end + catch + error:Error:St -> + erlang:raise(error, {bad_timestamp, Timestamp, Error}, St) + end. + +unmarshal_change({created, #limiter_config_CreatedChange{limit_config = Config}}) -> + {created, unmarshal_config(Config)}. + +unmarshal_config(#limiter_config_LimitConfig{ + id = ID, + processor_type = ProcessorType, + description = Description, + body_type = BodyType, + created_at = CreatedAt, + started_at = StartedAt, + shard_size = ShardSize, + time_range_type = TimeRangeType, + context_type = ContextType, + type = Type, + scope = Scope +}) -> + genlib_map:compact(#{ + id => ID, + processor_type => ProcessorType, + created_at => lim_time:from_rfc3339(CreatedAt), + body_type => unmarshal_body_type(BodyType), + started_at => StartedAt, + shard_size => ShardSize, + time_range_type => unmarshal_time_range_type(TimeRangeType), + context_type => unmarshal_context_type(ContextType), + type => maybe_apply(Type, fun unmarshal_type/1), + scope => maybe_apply(Scope, fun unmarshal_scope/1), + description => Description + }). + +-spec unmarshal_body_type(encoded_value()) -> decoded_value(). +unmarshal_body_type({amount, #limiter_config_LimitBodyTypeAmount{}}) -> + amount; +unmarshal_body_type({cash, #limiter_config_LimitBodyTypeCash{currency = Currency}}) -> + {cash, Currency}. + +unmarshal_time_range_type({calendar, CalendarType}) -> + {calendar, unmarshal_calendar_time_range_type(CalendarType)}; +unmarshal_time_range_type({interval, #time_range_TimeRangeTypeInterval{amount = Amount}}) -> + {interval, Amount}. + +unmarshal_calendar_time_range_type({day, _}) -> + day; +unmarshal_calendar_time_range_type({week, _}) -> + week; +unmarshal_calendar_time_range_type({month, _}) -> + month; +unmarshal_calendar_time_range_type({year, _}) -> + year. + +unmarshal_context_type({payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}) -> + payment_processing. + +unmarshal_type({turnover, #limiter_config_LimitTypeTurnover{}}) -> + turnover. + +unmarshal_scope({scope, Type}) -> + {scope, unmarshal_scope_type(Type)}; +unmarshal_scope({scope_global, #limiter_config_LimitScopeGlobal{}}) -> + global. + +unmarshal_scope_type({party, _}) -> + party; +unmarshal_scope_type({shop, _}) -> + shop; +unmarshal_scope_type({wallet, _}) -> + wallet; +unmarshal_scope_type({identity, _}) -> + identity. + +%% + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +-spec test() -> _. + +-spec marshal_unmarshal_created_test() -> _. + +marshal_unmarshal_created_test() -> + Created = + {created, #{ + id => <<"id">>, + processor_type => <<"type">>, + created_at => lim_time:now(), + body_type => {cash, <<"RUB">>}, + started_at => <<"2000-01-01T00:00:00Z">>, + shard_size => 7, + time_range_type => {calendar, day}, + context_type => payment_processing, + type => turnover, + scope => {scope, party}, + description => <<"description">> + }}, + Event = {ev, lim_time:machinery_now(), Created}, + ?assertEqual(Event, unmarshal(timestamped_change, marshal(timestamped_change, Event))). + +-endif. diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl new file mode 100644 index 00000000..3b1345b6 --- /dev/null +++ b/apps/limiter/src/lim_config_machine.erl @@ -0,0 +1,768 @@ +-module(lim_config_machine). + +-include_lib("limiter_proto/include/lim_limiter_thrift.hrl"). +-include_lib("limiter_proto/include/lim_base_thrift.hrl"). + +%% Accessors + +-export([created_at/1]). +-export([id/1]). +-export([description/1]). +-export([body_type/1]). +-export([started_at/1]). +-export([shard_size/1]). +-export([time_range_type/1]). +-export([processor_type/1]). +-export([type/1]). +-export([scope/1]). +-export([context_type/1]). + +%% API + +-export([start/3]). +-export([get/2]). + +-export([get_limit/2]). +-export([hold/2]). +-export([commit/2]). +-export([rollback/2]). + +-export([calculate_shard_id/2]). +-export([calculate_time_range/2]). +-export([mk_scope_prefix/2]). + +-type woody_context() :: woody_context:ctx(). +-type lim_context() :: lim_context:t(). +-type processor_type() :: lim_router:processor_type(). +-type processor() :: lim_router:processor(). +-type description() :: binary(). + +-type limit_type() :: turnover. +-type limit_scope() :: global | {scope, party | shop | wallet | identity}. +-type body_type() :: {cash, currency()} | amount. +-type shard_size() :: pos_integer(). +-type shard_id() :: binary(). +-type prefix() :: binary(). +-type time_range_type() :: {calendar, year | month | week | day} | {interval, pos_integer()}. +-type time_range() :: #{ + upper := timestamp(), + lower := timestamp() +}. + +-type context_type() :: lim_context:context_type(). + +-type config() :: #{ + id := lim_id(), + processor_type := processor_type(), + created_at := lim_time:timestamp_ms(), + body_type := body_type(), + started_at := timestamp(), + shard_size := shard_size(), + time_range_type := time_range_type(), + context_type := context_type(), + type => limit_type(), + scope => limit_scope(), + description => description() +}. + +-type create_params() :: #{ + processor_type := processor_type(), + body_type := body_type(), + started_at := timestamp(), + shard_size := shard_size(), + time_range_type := time_range_type(), + context_type := context_type(), + type => limit_type(), + scope => limit_scope(), + description => description() +}. + +-type lim_id() :: lim_limiter_thrift:'LimitID'(). +-type lim_change() :: lim_limiter_thrift:'LimitChange'(). +-type limit() :: lim_limiter_thrift:'Limit'(). +-type timestamp() :: lim_base_thrift:'Timestamp'(). +-type currency() :: lim_base_thrift:'CurrencySymbolicCode'(). + +-export_type([config/0]). +-export_type([body_type/0]). +-export_type([limit_type/0]). +-export_type([limit_scope/0]). +-export_type([time_range_type/0]). +-export_type([time_range/0]). +-export_type([create_params/0]). +-export_type([currency/0]). +-export_type([lim_id/0]). +-export_type([lim_change/0]). +-export_type([limit/0]). +-export_type([timestamp/0]). + +%% Machinery callbacks + +-behaviour(machinery). + +-export([init/4]). +-export([process_call/4]). +-export([process_timeout/3]). +-export([process_repair/4]). + +-type timestamped_event(T) :: + {ev, machinery:timestamp(), T}. + +-type event() :: + {created, config()}. + +-type args(T) :: machinery:args(T). +-type machine() :: machinery:machine(event(), _). +-type handler_args() :: machinery:handler_args(_). +-type handler_opts() :: machinery:handler_opts(_). +-type result() :: machinery:result(timestamped_event(event()), none()). + +-export_type([timestamped_event/1]). +-export_type([event/0]). + +-define(NS, 'lim/config_v1'). + +%% Handler behaviour + +-callback get_limit( + ID :: lim_id(), + Config :: config(), + LimitContext :: lim_context() +) -> {ok, limit()} | {error, get_limit_error()}. + +-callback hold( + LimitChange :: lim_change(), + Config :: config(), + LimitContext :: lim_context() +) -> ok | {error, hold_error()}. + +-callback commit( + LimitChange :: lim_change(), + Config :: config(), + LimitContext :: lim_context() +) -> ok | {error, commit_error()}. + +-callback rollback( + LimitChange :: lim_change(), + Config :: config(), + LimitContext :: lim_context() +) -> ok | {error, rollback_error()}. + +-type get_limit_error() :: lim_turnover_processor:get_limit_error(). +-type hold_error() :: lim_turnover_processor:hold_error(). +-type commit_error() :: lim_turnover_processor:commit_error(). +-type rollback_error() :: lim_turnover_processor:rollback_error(). + +-type config_error() :: {config, notfound}. + +-import(lim_pipeline, [do/1, unwrap/1, unwrap/2]). + +%% Accessors + +-spec created_at(config()) -> timestamp(). +created_at(#{created_at := CreatedAt}) -> + lim_time:to_rfc3339(CreatedAt). + +-spec id(config()) -> lim_id(). +id(#{id := ID}) -> + ID. + +-spec description(config()) -> lim_maybe:maybe(description()). +description(#{description := ID}) -> + ID; +description(_) -> + undefined. + +-spec body_type(config()) -> body_type(). +body_type(#{body_type := BodyType}) -> + BodyType. + +-spec started_at(config()) -> timestamp(). +started_at(#{started_at := Value}) -> + Value. + +-spec shard_size(config()) -> shard_size(). +shard_size(#{shard_size := Value}) -> + Value. + +-spec time_range_type(config()) -> time_range_type(). +time_range_type(#{time_range_type := Value}) -> + Value. + +-spec processor_type(config()) -> processor_type(). +processor_type(#{processor_type := Value}) -> + Value. + +-spec type(config()) -> lim_maybe:maybe(limit_type()). +type(#{type := Value}) -> + Value; +type(_) -> + undefined. + +-spec scope(config()) -> lim_maybe:maybe(limit_scope()). +scope(#{scope := Value}) -> + Value; +scope(_) -> + undefined. + +-spec context_type(config()) -> context_type(). +context_type(#{context_type := Value}) -> + Value. + +%% + +-spec start(lim_id(), create_params(), lim_context()) -> {ok, config()}. +start(ID, Params, LimitContext) -> + {ok, WoodyCtx} = lim_context:woody_context(LimitContext), + Config = genlib_map:compact(Params#{id => ID, created_at => lim_time:now()}), + case machinery:start(?NS, ID, [{created, Config}], get_backend(WoodyCtx)) of + ok -> + {ok, Config}; + {error, exists} -> + {ok, Machine} = machinery:get(?NS, ID, get_backend(WoodyCtx)), + {ok, collapse(Machine)} + end. + +-spec get(lim_id(), lim_context()) -> {ok, config()} | {error, notfound}. +get(ID, LimitContext) -> + do(fun() -> + {ok, WoodyCtx} = lim_context:woody_context(LimitContext), + Machine = unwrap(machinery:get(?NS, ID, get_backend(WoodyCtx))), + collapse(Machine) + end). + +-spec get_limit(lim_id(), lim_context()) -> {ok, limit()} | {error, config_error() | {processor(), get_limit_error()}}. +get_limit(ID, LimitContext) -> + do(fun() -> + {Handler, Config} = unwrap(get_handler(ID, LimitContext)), + unwrap(Handler, Handler:get_limit(ID, Config, LimitContext)) + end). + +-spec hold(lim_change(), lim_context()) -> ok | {error, config_error() | {processor(), hold_error()}}. +hold(LimitChange = #limiter_LimitChange{id = ID}, LimitContext) -> + do(fun() -> + {Handler, Config} = unwrap(get_handler(ID, LimitContext)), + unwrap(Handler, Handler:hold(LimitChange, Config, LimitContext)) + end). + +-spec commit(lim_change(), lim_context()) -> ok | {error, config_error() | {processor(), commit_error()}}. +commit(LimitChange = #limiter_LimitChange{id = ID}, LimitContext) -> + do(fun() -> + {Handler, Config} = unwrap(get_handler(ID, LimitContext)), + unwrap(Handler, Handler:commit(LimitChange, Config, LimitContext)) + end). + +-spec rollback(lim_change(), lim_context()) -> ok | {error, config_error() | {processor(), rollback_error()}}. +rollback(LimitChange = #limiter_LimitChange{id = ID}, LimitContext) -> + do(fun() -> + {Handler, Config} = unwrap(get_handler(ID, LimitContext)), + unwrap(Handler, Handler:rollback(LimitChange, Config, LimitContext)) + end). + +get_handler(ID, LimitContext) -> + do(fun() -> + Config = #{processor_type := ProcessorType} = unwrap(config, get(ID, LimitContext)), + {ok, Handler} = lim_router:get_handler(ProcessorType), + {Handler, Config} + end). + +-spec calculate_time_range(timestamp(), config()) -> time_range(). +calculate_time_range(Timestamp, Config) -> + StartedAt = started_at(Config), + {StartDateTime, USec} = lim_range_codec:parse_timestamp(StartedAt), + {CurrentDateTime, USec} = lim_range_codec:parse_timestamp(Timestamp), + CurrentSec = calendar:datetime_to_gregorian_seconds(CurrentDateTime), + case time_range_type(Config) of + {calendar, Range} -> + calculate_calendar_time_range(Range, CurrentSec, CurrentDateTime, StartDateTime); + {interval, _Interval} -> + erlang:error({interval_time_range_not_implemented, Config}) + end. + +calculate_calendar_time_range(year, CurrentSec, {CurrentDate, _CurrentTime}, {StartDate, StartTime}) -> + {_StartYear, StartMonth, StartDay} = StartDate, + {CurrentYear, _CurrentMonth, _} = CurrentDate, + ClampedStartDay = clamp_month_start_day(CurrentYear, StartMonth, StartDay), + LowerSec = calendar:datetime_to_gregorian_seconds( + {{CurrentYear, StartMonth, ClampedStartDay}, StartTime} + ), + NextYearDay = clamp_month_start_day(CurrentYear + 1, StartMonth, StartDay), + UpperSec = calendar:datetime_to_gregorian_seconds( + {{CurrentYear + 1, StartMonth, NextYearDay}, StartTime} + ), + calculate_year_time_range(CurrentSec, LowerSec, UpperSec); +calculate_calendar_time_range(month, CurrentSec, {CurrentDate, _CurrentTime}, {StartDate, StartTime}) -> + {_StartYear, _StartMonth, StartDay} = StartDate, + {CurrentYear, CurrentMonth, _} = CurrentDate, + ClampedStartDay = clamp_month_start_day(CurrentYear, CurrentMonth, StartDay), + LowerSec = calendar:datetime_to_gregorian_seconds( + {{CurrentYear, CurrentMonth, ClampedStartDay}, StartTime} + ), + UpperSec = + case CurrentMonth < 12 of + true -> + NextMonthDay = clamp_month_start_day(CurrentYear, CurrentMonth + 1, StartDay), + calendar:datetime_to_gregorian_seconds( + {{CurrentYear, CurrentMonth + 1, NextMonthDay}, StartTime} + ); + false -> + NextYearDay = clamp_month_start_day(CurrentYear + 1, CurrentMonth, StartDay), + calendar:datetime_to_gregorian_seconds( + {{CurrentYear + 1, 1, NextYearDay}, StartTime} + ) + end, + calculate_month_time_range(CurrentSec, LowerSec, UpperSec); +calculate_calendar_time_range(week, CurrentSec, {CurrentDate, _CurrentTime}, {StartDate, StartTime}) -> + StartWeekRem = calendar:date_to_gregorian_days(StartDate) rem 7, + LowerWeek = (calendar:date_to_gregorian_days(CurrentDate) div 7) * 7 + StartWeekRem, + UpperWeek = LowerWeek + 7, + LowerSec = calendar:datetime_to_gregorian_seconds( + {calendar:gregorian_days_to_date(LowerWeek), StartTime} + ), + UpperSec = calendar:datetime_to_gregorian_seconds( + {calendar:gregorian_days_to_date(UpperWeek), StartTime} + ), + calculate_week_time_range(CurrentSec, LowerSec, UpperSec); +calculate_calendar_time_range(day, CurrentSec, {CurrentDate, _CurrentTime}, {_StartDate, StartTime}) -> + Lower = calendar:date_to_gregorian_days(CurrentDate), + UpperDate = calendar:gregorian_days_to_date(Lower + 1), + LowerSec = calendar:datetime_to_gregorian_seconds({CurrentDate, StartTime}), + UpperSec = calendar:datetime_to_gregorian_seconds({UpperDate, StartTime}), + calculate_day_time_range(CurrentSec, LowerSec, UpperSec). + +clamp_month_start_day(Year, Month, StartDay) -> + Last = calendar:last_day_of_the_month(Year, Month), + case StartDay > Last of + true -> + Last; + false -> + StartDay + end. + +calculate_year_time_range(CurrentSec, LowerSec, UpperSec) when + CurrentSec >= LowerSec andalso + CurrentSec < UpperSec +-> + mk_time_range(LowerSec, UpperSec); +calculate_year_time_range(CurrentSec, LowerSec, _UpperSec) when CurrentSec < LowerSec -> + {{Year, Month, Day}, Time} = calendar:gregorian_seconds_to_datetime(LowerSec), + PrevYearDay = clamp_month_start_day(Year - 1, Month, Day), + LowerDate = {Year - 1, Month, PrevYearDay}, + #{ + lower => marshal_timestamp({LowerDate, Time}), + upper => marshal_timestamp(calendar:gregorian_seconds_to_datetime(LowerSec)) + }. + +calculate_month_time_range(CurrentSec, LowerSec, UpperSec) when + CurrentSec >= LowerSec andalso + CurrentSec < UpperSec +-> + mk_time_range(LowerSec, UpperSec); +calculate_month_time_range(CurrentSec, LowerSec, _UpperSec) when CurrentSec < LowerSec -> + {{Year, Month, Day}, Time} = calendar:gregorian_seconds_to_datetime(LowerSec), + LowerDate = + case Month =:= 1 of + true -> + PrevYearDay = clamp_month_start_day(Year - 1, 12, Day), + {Year - 1, 12, PrevYearDay}; + false -> + PrevMonthDay = clamp_month_start_day(Year, Month - 1, Day), + {Year, Month - 1, PrevMonthDay} + end, + #{ + lower => marshal_timestamp({LowerDate, Time}), + upper => marshal_timestamp(calendar:gregorian_seconds_to_datetime(LowerSec)) + }. + +calculate_week_time_range(CurrentSec, LowerSec, UpperSec) when + CurrentSec >= LowerSec andalso + CurrentSec < UpperSec +-> + mk_time_range(LowerSec, UpperSec); +calculate_week_time_range(CurrentSec, LowerSec, _UpperSec) when CurrentSec < LowerSec -> + {Date, Time} = calendar:gregorian_seconds_to_datetime(LowerSec), + Days = calendar:date_to_gregorian_days(Date), + LowerDate = calendar:gregorian_days_to_date(Days - 7), + #{ + lower => marshal_timestamp({LowerDate, Time}), + upper => marshal_timestamp(calendar:gregorian_seconds_to_datetime(LowerSec)) + }. + +calculate_day_time_range(CurrentSec, LowerSec, UpperSec) when + CurrentSec >= LowerSec andalso + CurrentSec < UpperSec +-> + mk_time_range(LowerSec, UpperSec); +calculate_day_time_range(CurrentSec, LowerSec, _UpperSec) when CurrentSec < LowerSec -> + {Date, Time} = calendar:gregorian_seconds_to_datetime(LowerSec), + Days = calendar:date_to_gregorian_days(Date), + LowerDate = calendar:gregorian_days_to_date(Days - 1), + #{ + lower => marshal_timestamp({LowerDate, Time}), + upper => marshal_timestamp(calendar:gregorian_seconds_to_datetime(LowerSec)) + }. + +mk_time_range(LowerSec, UpperSec) -> + #{ + lower => marshal_timestamp(calendar:gregorian_seconds_to_datetime(LowerSec)), + upper => marshal_timestamp(calendar:gregorian_seconds_to_datetime(UpperSec)) + }. + +marshal_timestamp(DateTime) -> + lim_range_codec:marshal(timestamp, {DateTime, 0}). + +-spec calculate_shard_id(timestamp(), config()) -> shard_id(). +calculate_shard_id(Timestamp, Config) -> + StartedAt = started_at(Config), + ShardSize = shard_size(Config), + {StartDateTime, USec} = lim_range_codec:parse_timestamp(StartedAt), + {CurrentDateTime, USec} = lim_range_codec:parse_timestamp(Timestamp), + case time_range_type(Config) of + {calendar, Range} -> + Units = calculate_time_units(Range, CurrentDateTime, StartDateTime), + SignPrefix = mk_sign_prefix(Units), + RangePrefix = mk_prefix(Range), + mk_shard_id(<>, Units, ShardSize); + {interval, _Interval} -> + erlang:error({interval_time_range_not_implemented, Config}) + end. + +calculate_time_units(year, {CurrentDate, CurrentTime}, {StartDate, StartTime}) -> + {StartYear, _, _} = StartDate, + {CurrentYear, _, _} = CurrentDate, + + StartSecBase = calendar:datetime_to_gregorian_seconds({{StartYear, 1, 1}, {0, 0, 0}}), + StartSec = calendar:datetime_to_gregorian_seconds({StartDate, StartTime}), + CurrentSecBase = calendar:datetime_to_gregorian_seconds({{CurrentYear, 1, 1}, {0, 0, 0}}), + CurrentSec = calendar:datetime_to_gregorian_seconds({CurrentDate, CurrentTime}), + + StartDelta = StartSec - StartSecBase, + CurrentDelta = CurrentSec - (CurrentSecBase + StartDelta), + + case CurrentDelta >= 0 of + true -> + CurrentYear - StartYear; + false -> + CurrentYear - StartYear - 1 + end; +calculate_time_units(month, {CurrentDate, CurrentTime}, {StartDate, StartTime}) -> + {StartYear, StartMonth, _} = StartDate, + {CurrentYear, CurrentMonth, _} = CurrentDate, + + StartSecBase = calendar:datetime_to_gregorian_seconds({{StartYear, StartMonth, 1}, {0, 0, 0}}), + StartSec = calendar:datetime_to_gregorian_seconds({StartDate, StartTime}), + CurrentSecBase = calendar:datetime_to_gregorian_seconds({{CurrentYear, CurrentMonth, 1}, {0, 0, 0}}), + CurrentSec = calendar:datetime_to_gregorian_seconds({CurrentDate, CurrentTime}), + + StartDelta = StartSec - StartSecBase, + CurrentDelta = CurrentSec - (CurrentSecBase + StartDelta), + + YearDiff = CurrentYear - StartYear, + MonthDiff = CurrentMonth - StartMonth, + + case CurrentDelta >= 0 of + true -> + YearDiff * 12 + MonthDiff; + false -> + YearDiff * 12 + MonthDiff - 1 + end; +calculate_time_units(week, {CurrentDate, CurrentTime}, {StartDate, StartTime}) -> + StartWeekRem = calendar:date_to_gregorian_days(StartDate) rem 7, + StartWeekBase = (calendar:date_to_gregorian_days(StartDate) div 7) * 7, + CurrentWeekBase = (calendar:date_to_gregorian_days(CurrentDate) div 7) * 7, + + StartSecBase = calendar:datetime_to_gregorian_seconds( + {calendar:gregorian_days_to_date(StartWeekBase), {0, 0, 0}} + ), + StartSec = calendar:datetime_to_gregorian_seconds( + {calendar:gregorian_days_to_date(StartWeekBase + StartWeekRem), StartTime} + ), + CurrentSecBase = calendar:datetime_to_gregorian_seconds( + {calendar:gregorian_days_to_date(CurrentWeekBase), {0, 0, 0}} + ), + CurrentSec = calendar:datetime_to_gregorian_seconds( + {calendar:gregorian_days_to_date(CurrentWeekBase + StartWeekRem), CurrentTime} + ), + + StartDelta = StartSec - StartSecBase, + CurrentDelta = CurrentSec - (CurrentSecBase + StartDelta), + + StartWeeks = calendar:date_to_gregorian_days(StartDate) div 7, + CurrentWeeks = calendar:date_to_gregorian_days(CurrentDate) div 7, + case CurrentDelta >= 0 of + true -> + CurrentWeeks - StartWeeks; + false -> + CurrentWeeks - StartWeeks - 1 + end; +calculate_time_units(day, {CurrentDate, CurrentTime}, {StartDate, StartTime}) -> + StartSecBase = calendar:datetime_to_gregorian_seconds({StartDate, {0, 0, 0}}), + StartSec = calendar:datetime_to_gregorian_seconds({StartDate, StartTime}), + CurrentSecBase = calendar:datetime_to_gregorian_seconds({CurrentDate, {0, 0, 0}}), + CurrentSec = calendar:datetime_to_gregorian_seconds({CurrentDate, CurrentTime}), + StartDelta = StartSec - StartSecBase, + CurrentDelta = CurrentSec - (CurrentSecBase + StartDelta), + StartDays = calendar:date_to_gregorian_days(StartDate), + CurrentDays = calendar:date_to_gregorian_days(CurrentDate), + case CurrentDelta >= 0 of + true -> + CurrentDays - StartDays; + false -> + CurrentDays - StartDays - 1 + end. + +mk_prefix(day) -> <<"day">>; +mk_prefix(week) -> <<"week">>; +mk_prefix(month) -> <<"month">>; +mk_prefix(year) -> <<"year">>. + +mk_sign_prefix(Units) when Units >= 0 -> <<"future">>; +mk_sign_prefix(_) -> <<"past">>. + +mk_shard_id(Prefix, Units0, ShardSize) -> + Units1 = abs(Units0), + ID = list_to_binary(integer_to_list(Units1 div ShardSize)), + <>. + +-spec mk_scope_prefix(config(), lim_context()) -> {ok, prefix()}. +mk_scope_prefix(#{scope := global}, _LimitContext) -> + {ok, <<>>}; +mk_scope_prefix(#{scope := {scope, party}}, LimitContext) -> + {ok, PartyID} = lim_context:get_from_context(payment_processing, owner_id, invoice, LimitContext), + {ok, <<"/", PartyID/binary>>}; +mk_scope_prefix(#{scope := {scope, shop}}, LimitContext) -> + {ok, PartyID} = lim_context:get_from_context(payment_processing, owner_id, invoice, LimitContext), + {ok, ShopID} = lim_context:get_from_context(payment_processing, shop_id, invoice, LimitContext), + {ok, <<"/", PartyID/binary, "/", ShopID/binary>>}. + +%%% Machinery callbacks + +-spec init(args([event()]), machine(), handler_args(), handler_opts()) -> result(). +init(Events, _Machine, _HandlerArgs, _HandlerOpts) -> + #{ + events => emit_events(Events) + }. + +-spec process_call(args(_), machine(), handler_args(), handler_opts()) -> no_return(). +process_call(_Args, _Machine, _HandlerArgs, _HandlerOpts) -> + not_implemented(call). + +-spec process_timeout(machine(), handler_args(), handler_opts()) -> no_return(). +process_timeout(_Machine, _HandlerArgs, _HandlerOpts) -> + not_implemented(timeout). + +-spec process_repair(args(_), machine(), handler_args(), handler_opts()) -> no_return(). +process_repair(_Args, _Machine, _HandlerArgs, _HandlerOpts) -> + not_implemented(repair). + +%%% Internal functions + +emit_events(Events) -> + emit_timestamped_events(Events, lim_time:machinery_now()). + +emit_timestamped_events(Events, Ts) -> + [{ev, Ts, Body} || Body <- Events]. + +collapse(#{history := History}) -> + lists:foldl(fun(Ev, St) -> apply_event(Ev, St) end, undefined, History). + +-spec get_backend(woody_context()) -> machinery_mg_backend:backend(). +get_backend(WoodyCtx) -> + lim_utils:get_backend(?NS, WoodyCtx). + +-spec not_implemented(any()) -> no_return(). +not_implemented(What) -> + erlang:error({not_implemented, What}). + +%% + +%% + +-spec apply_event(machinery:event(timestamped_event(event())), lim_maybe:maybe(config())) -> config(). +apply_event({_ID, _Ts, {ev, _EvTs, Event}}, Config) -> + apply_event_(Event, Config). + +-spec apply_event_(event(), lim_maybe:maybe(config())) -> config(). +apply_event_({created, Config}, undefined) -> + Config. + +%% + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +-spec test() -> _. + +-spec check_sign_prefix_test() -> _. + +check_sign_prefix_test() -> + ?assertEqual(<<"past">>, mk_sign_prefix(-10)), + ?assertEqual(<<"future">>, mk_sign_prefix(0)), + ?assertEqual(<<"future">>, mk_sign_prefix(10)). + +-spec check_calculate_day_time_range_test() -> _. +check_calculate_day_time_range_test() -> + Config0 = #{ + started_at => <<"2000-01-01T00:00:00Z">>, + time_range_type => {calendar, day} + }, + ?assertEqual( + #{lower => <<"2000-01-01T00:00:00Z">>, upper => <<"2000-01-02T00:00:00Z">>}, + calculate_time_range(<<"2000-01-01T02:00:00Z">>, Config0) + ), + ?assertEqual( + #{lower => <<"1999-12-31T00:00:00Z">>, upper => <<"2000-01-01T00:00:00Z">>}, + calculate_time_range(<<"1999-12-31T02:00:00Z">>, Config0) + ), + ?assertEqual( + #{lower => <<"2000-01-10T00:00:00Z">>, upper => <<"2000-01-11T00:00:00Z">>}, + calculate_time_range(<<"2000-01-10T02:00:00Z">>, Config0) + ), + Config1 = Config0#{started_at => <<"2000-01-01T03:00:00Z">>}, + ?assertEqual( + #{lower => <<"1999-12-31T03:00:00Z">>, upper => <<"2000-01-01T03:00:00Z">>}, + calculate_time_range(<<"2000-01-01T02:00:00Z">>, Config1) + ). + +-spec check_calculate_week_time_range_test() -> _. +check_calculate_week_time_range_test() -> + Config0 = #{ + started_at => <<"2000-01-01T00:00:00Z">>, + time_range_type => {calendar, week} + }, + ?assertEqual( + #{lower => <<"2000-01-01T00:00:00Z">>, upper => <<"2000-01-08T00:00:00Z">>}, + calculate_time_range(<<"2000-01-01T02:00:00Z">>, Config0) + ), + ?assertEqual( + #{lower => <<"1999-12-25T00:00:00Z">>, upper => <<"2000-01-01T00:00:00Z">>}, + calculate_time_range(<<"1999-12-31T02:00:00Z">>, Config0) + ), + ?assertEqual( + #{lower => <<"2000-09-30T00:00:00Z">>, upper => <<"2000-10-07T00:00:00Z">>}, + calculate_time_range(<<"2000-10-03T02:00:00Z">>, Config0) + ), + Config1 = Config0#{started_at => <<"2000-01-01T03:00:00Z">>}, + ?assertEqual( + #{lower => <<"1999-12-25T03:00:00Z">>, upper => <<"2000-01-01T03:00:00Z">>}, + calculate_time_range(<<"2000-01-01T02:00:00Z">>, Config1) + ). + +-spec check_calculate_month_time_range_test() -> _. +check_calculate_month_time_range_test() -> + Config0 = #{ + started_at => <<"2000-01-01T00:00:00Z">>, + time_range_type => {calendar, month} + }, + ?assertEqual( + #{lower => <<"2000-01-01T00:00:00Z">>, upper => <<"2000-02-01T00:00:00Z">>}, + calculate_time_range(<<"2000-01-01T02:00:00Z">>, Config0) + ), + ?assertEqual( + #{lower => <<"1999-12-01T00:00:00Z">>, upper => <<"2000-01-01T00:00:00Z">>}, + calculate_time_range(<<"1999-12-31T02:00:00Z">>, Config0) + ), + ?assertEqual( + #{lower => <<"2000-10-01T00:00:00Z">>, upper => <<"2000-11-01T00:00:00Z">>}, + calculate_time_range(<<"2000-10-03T02:00:00Z">>, Config0) + ), + Config1 = Config0#{started_at => <<"2000-01-01T03:00:00Z">>}, + ?assertEqual( + #{lower => <<"1999-12-01T03:00:00Z">>, upper => <<"2000-01-01T03:00:00Z">>}, + calculate_time_range(<<"2000-01-01T02:00:00Z">>, Config1) + ). + +-spec check_calculate_year_time_range_test() -> _. +check_calculate_year_time_range_test() -> + Config0 = #{ + started_at => <<"2000-01-01T00:00:00Z">>, + time_range_type => {calendar, year} + }, + ?assertEqual( + #{lower => <<"2000-01-01T00:00:00Z">>, upper => <<"2001-01-01T00:00:00Z">>}, + calculate_time_range(<<"2000-01-01T02:00:00Z">>, Config0) + ), + ?assertEqual( + #{lower => <<"1999-01-01T00:00:00Z">>, upper => <<"2000-01-01T00:00:00Z">>}, + calculate_time_range(<<"1999-12-31T02:00:00Z">>, Config0) + ), + ?assertEqual( + #{lower => <<"2010-01-01T00:00:00Z">>, upper => <<"2011-01-01T00:00:00Z">>}, + calculate_time_range(<<"2010-10-03T02:00:00Z">>, Config0) + ), + Config1 = Config0#{started_at => <<"2000-01-01T03:00:00Z">>}, + ?assertEqual( + #{lower => <<"1999-01-01T03:00:00Z">>, upper => <<"2000-01-01T03:00:00Z">>}, + calculate_time_range(<<"2000-01-01T02:00:00Z">>, Config1) + ). + +-spec check_calculate_day_shard_id_test() -> _. +check_calculate_day_shard_id_test() -> + Config0 = #{ + started_at => <<"2000-01-01T00:00:00Z">>, + shard_size => 1, + time_range_type => {calendar, day} + }, + ?assertEqual(<<"future/day/0">>, calculate_shard_id(<<"2000-01-01T00:00:00Z">>, Config0)), + ?assertEqual(<<"future/day/2">>, calculate_shard_id(<<"2000-01-03T00:00:00Z">>, Config0)), + ?assertEqual(<<"past/day/1">>, calculate_shard_id(<<"1999-12-31T00:00:00Z">>, Config0)), + ?assertEqual(<<"future/day/1">>, calculate_shard_id(<<"2000-01-02T23:59:59Z">>, Config0)), + ?assertEqual(<<"future/day/1">>, calculate_shard_id(<<"2000-01-04T00:00:00Z">>, Config0#{shard_size => 2})), + ?assertEqual(<<"future/day/366">>, calculate_shard_id(<<"2001-01-01T00:00:00Z">>, Config0)), + ?assertEqual(<<"future/day/12">>, calculate_shard_id(<<"2001-01-01T00:00:00Z">>, Config0#{shard_size => 30})), + Config1 = Config0#{started_at => <<"2000-01-01T03:00:00Z">>}, + ?assertEqual(<<"past/day/1">>, calculate_shard_id(<<"2000-01-01T00:00:00Z">>, Config1)), + ?assertEqual(<<"future/day/1">>, calculate_shard_id(<<"2000-01-03T00:00:00Z">>, Config1)). + +-spec check_calculate_week_shard_id_test() -> _. +check_calculate_week_shard_id_test() -> + Config0 = #{ + started_at => <<"2000-01-01T00:00:00Z">>, + shard_size => 1, + time_range_type => {calendar, week} + }, + ?assertEqual(<<"future/week/0">>, calculate_shard_id(<<"2000-01-01T00:00:00Z">>, Config0)), + ?assertEqual(<<"past/week/1">>, calculate_shard_id(<<"1999-12-31T00:00:00Z">>, Config0)), + ?assertEqual(<<"future/week/1">>, calculate_shard_id(<<"2000-01-08T00:00:00Z">>, Config0)), + ?assertEqual(<<"future/week/1">>, calculate_shard_id(<<"2000-01-15T00:00:00Z">>, Config0#{shard_size => 2})), + ?assertEqual(<<"future/week/52">>, calculate_shard_id(<<"2001-01-01T00:00:00Z">>, Config0)), + ?assertEqual(<<"future/week/13">>, calculate_shard_id(<<"2001-01-01T00:00:00Z">>, Config0#{shard_size => 4})), + Config1 = Config0#{started_at => <<"2000-01-02T03:00:00Z">>}, + ?assertEqual(<<"past/week/1">>, calculate_shard_id(<<"2000-01-02T00:00:00Z">>, Config1)), + ?assertEqual(<<"future/week/0">>, calculate_shard_id(<<"2000-01-09T00:00:00Z">>, Config1)). + +-spec check_calculate_month_shard_id_test() -> _. +check_calculate_month_shard_id_test() -> + Config0 = #{ + started_at => <<"2000-01-01T00:00:00Z">>, + shard_size => 1, + time_range_type => {calendar, month} + }, + ?assertEqual(<<"future/month/0">>, calculate_shard_id(<<"2000-01-01T00:00:00Z">>, Config0)), + ?assertEqual(<<"past/month/1">>, calculate_shard_id(<<"1999-12-31T00:00:00Z">>, Config0)), + ?assertEqual(<<"future/month/1">>, calculate_shard_id(<<"2000-02-01T00:00:00Z">>, Config0)), + ?assertEqual(<<"future/month/1">>, calculate_shard_id(<<"2000-03-01T00:00:00Z">>, Config0#{shard_size => 2})), + ?assertEqual(<<"future/month/12">>, calculate_shard_id(<<"2001-01-01T00:00:00Z">>, Config0)), + ?assertEqual(<<"future/month/1">>, calculate_shard_id(<<"2001-01-01T00:00:00Z">>, Config0#{shard_size => 12})), + Config1 = Config0#{started_at => <<"2000-01-02T03:00:00Z">>}, + ?assertEqual(<<"past/month/1">>, calculate_shard_id(<<"2000-01-02T00:00:00Z">>, Config1)), + ?assertEqual(<<"future/month/0">>, calculate_shard_id(<<"2000-02-02T00:00:00Z">>, Config1)). + +-spec check_calculate_year_shard_id_test() -> _. +check_calculate_year_shard_id_test() -> + Config0 = #{ + started_at => <<"2000-01-01T00:00:00Z">>, + shard_size => 1, + time_range_type => {calendar, year} + }, + ?assertEqual(<<"future/year/0">>, calculate_shard_id(<<"2000-01-01T00:00:00Z">>, Config0)), + ?assertEqual(<<"past/year/1">>, calculate_shard_id(<<"1999-12-31T00:00:00Z">>, Config0)), + ?assertEqual(<<"future/year/1">>, calculate_shard_id(<<"2001-01-01T00:00:00Z">>, Config0)), + ?assertEqual(<<"future/year/1">>, calculate_shard_id(<<"2003-01-01T00:00:00Z">>, Config0#{shard_size => 2})), + ?assertEqual(<<"future/year/10">>, calculate_shard_id(<<"2010-01-01T00:00:00Z">>, Config0)), + ?assertEqual(<<"future/year/2">>, calculate_shard_id(<<"2020-01-01T00:00:00Z">>, Config0#{shard_size => 10})), + Config1 = Config0#{started_at => <<"2000-01-02T03:00:00Z">>}, + ?assertEqual(<<"past/year/1">>, calculate_shard_id(<<"2000-01-01T00:00:00Z">>, Config1)), + ?assertEqual(<<"future/year/0">>, calculate_shard_id(<<"2001-01-01T00:00:00Z">>, Config1)). + +-endif. diff --git a/apps/limiter/src/lim_config_machinery_schema.erl b/apps/limiter/src/lim_config_machinery_schema.erl new file mode 100644 index 00000000..819e97a7 --- /dev/null +++ b/apps/limiter/src/lim_config_machinery_schema.erl @@ -0,0 +1,112 @@ +-module(lim_config_machinery_schema). + +%% Storage schema behaviour +-behaviour(machinery_mg_schema). + +-export([get_version/1]). +-export([marshal/3]). +-export([unmarshal/3]). + +%% Constants + +-define(CURRENT_EVENT_FORMAT_VERSION, 1). + +%% Internal types + +-type type() :: machinery_mg_schema:t(). +-type value(T) :: machinery_mg_schema:v(T). +-type value_type() :: machinery_mg_schema:vt(). +-type context() :: machinery_mg_schema:context(). + +-type event() :: lim_config_machine:timestamped_event(lim_config_machine:event()). +-type aux_state() :: term(). +-type call_args() :: term(). +-type call_response() :: term(). + +-type data() :: + aux_state() + | event() + | call_args() + | call_response(). + +%% machinery_mg_schema callbacks + +-spec get_version(value_type()) -> machinery_mg_schema:version(). +get_version(event) -> + ?CURRENT_EVENT_FORMAT_VERSION; +get_version(aux_state) -> + undefined. + +-spec marshal(type(), value(data()), context()) -> {machinery_msgpack:t(), context()}. +marshal({event, FormatVersion}, TimestampedChange, Context) -> + marshal_event(FormatVersion, TimestampedChange, Context); +marshal(T, V, C) when + T =:= {args, init} orelse + T =:= {args, call} orelse + T =:= {args, repair} orelse + T =:= {aux_state, undefined} orelse + T =:= {response, call} orelse + T =:= {response, {repair, success}} orelse + T =:= {response, {repair, failure}} +-> + machinery_mg_schema_generic:marshal(T, V, C). + +-spec unmarshal(type(), machinery_msgpack:t(), context()) -> {data(), context()}. +unmarshal({event, FormatVersion}, EncodedChange, Context) -> + unmarshal_event(FormatVersion, EncodedChange, Context); +unmarshal(T, V, C) when + T =:= {args, init} orelse + T =:= {args, call} orelse + T =:= {args, repair} orelse + T =:= {aux_state, undefined} orelse + T =:= {response, call} orelse + T =:= {response, {repair, success}} orelse + T =:= {response, {repair, failure}} +-> + machinery_mg_schema_generic:unmarshal(T, V, C). + +%% Internals + +-spec marshal_event(machinery_mg_schema:version(), event(), context()) -> {machinery_msgpack:t(), context()}. +marshal_event(1, TimestampedChange, Context) -> + ThriftChange = lim_config_codec:marshal(timestamped_change, TimestampedChange), + Type = {struct, struct, {lim_limiter_config_thrift, 'TimestampedChange'}}, + {{bin, lim_proto_utils:serialize(Type, ThriftChange)}, Context}. + +-spec unmarshal_event(machinery_mg_schema:version(), machinery_msgpack:t(), context()) -> {event(), context()}. +unmarshal_event(1, EncodedChange, Context) -> + {bin, EncodedThriftChange} = EncodedChange, + Type = {struct, struct, {lim_limiter_config_thrift, 'TimestampedChange'}}, + ThriftChange = lim_proto_utils:deserialize(Type, EncodedThriftChange), + {lim_config_codec:unmarshal(timestamped_change, ThriftChange), Context}. + +%% + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +-spec test() -> _. + +-spec marshal_unmarshal_created_test() -> _. + +marshal_unmarshal_created_test() -> + Created = + {created, #{ + id => <<"id">>, + processor_type => <<"type">>, + created_at => lim_time:now(), + body_type => {cash, <<"RUB">>}, + started_at => <<"2000-01-01T00:00:00Z">>, + shard_size => 7, + time_range_type => {calendar, day}, + context_type => payment_processing, + type => turnover, + scope => {scope, party}, + description => <<"description">> + }}, + Event = {ev, lim_time:machinery_now(), Created}, + {Marshaled, _} = marshal_event(1, Event, {}), + {Unmarshaled, _} = unmarshal_event(1, Marshaled, {}), + ?assertEqual(Event, Unmarshaled). + +-endif. diff --git a/apps/limiter/src/lim_configurator.erl b/apps/limiter/src/lim_configurator.erl new file mode 100644 index 00000000..898d60e2 --- /dev/null +++ b/apps/limiter/src/lim_configurator.erl @@ -0,0 +1,93 @@ +-module(lim_configurator). + +-include_lib("limiter_proto/include/lim_configurator_thrift.hrl"). + +%% Woody handler + +-behaviour(woody_server_thrift_handler). + +-export([handle_function/4]). + +%% + +-type lim_context() :: lim_context:t(). + +%% + +-spec handle_function(woody:func(), woody:args(), woody_context:ctx(), woody:options()) -> {ok, woody:result()}. +handle_function(Fn, Args, WoodyCtx, Opts) -> + {ok, LimitContext} = lim_context:create(WoodyCtx), + scoper:scope( + configurator, + fun() -> handle_function_(Fn, Args, LimitContext, Opts) end + ). + +-spec handle_function_(woody:func(), woody:args(), lim_context(), woody:options()) -> {ok, woody:result()}. +handle_function_( + 'Create', + {#limiter_cfg_LimitCreateParams{ + id = ID, + name = Name, + description = Description, + started_at = StartedAt, + body_type = BodyType + }}, + LimitContext, + _Opts +) -> + case mk_limit_config(Name) of + {ok, Config} -> + {ok, LimitConfig} = lim_config_machine:start( + ID, + Config#{ + description => Description, + started_at => StartedAt, + body_type => lim_config_codec:unmarshal_body_type(BodyType) + }, + LimitContext + ), + {ok, lim_config_codec:marshal_config(LimitConfig)}; + {error, {name, notfound}} -> + woody_error:raise( + business, + #limiter_cfg_LimitConfigNameNotFound{} + ) + end; +handle_function_('Get', {LimitID}, LimitContext, _Opts) -> + scoper:add_meta(#{limit_config_id => LimitID}), + case lim_config_machine:get(LimitID, LimitContext) of + {ok, LimitConfig} -> + {ok, lim_config_codec:marshal_config(LimitConfig)}; + {error, notfound} -> + woody_error:raise(business, #limiter_cfg_LimitConfigNotFound{}) + end. + +mk_limit_config(<<"ShopMonthTurnover">>) -> + {ok, #{ + processor_type => <<"TurnoverProcessor">>, + type => turnover, + scope => {scope, shop}, + shard_size => 12, + context_type => payment_processing, + time_range_type => {calendar, month} + }}; +mk_limit_config(<<"PartyMonthTurnover">>) -> + {ok, #{ + processor_type => <<"TurnoverProcessor">>, + type => turnover, + scope => {scope, party}, + shard_size => 12, + context_type => payment_processing, + time_range_type => {calendar, month} + }}; +mk_limit_config(<<"GlobalMonthTurnover">>) -> + {ok, #{ + processor_type => <<"TurnoverProcessor">>, + type => turnover, + scope => global, + shard_size => 12, + context_type => payment_processing, + time_range_type => {calendar, month} + }}; +mk_limit_config(_) -> + {error, {name, notfound}}. diff --git a/apps/limiter/src/lim_context.erl b/apps/limiter/src/lim_context.erl new file mode 100644 index 00000000..b7192e39 --- /dev/null +++ b/apps/limiter/src/lim_context.erl @@ -0,0 +1,305 @@ +-module(lim_context). + +-include_lib("limiter_proto/include/lim_limiter_context_thrift.hrl"). + +-export([create/1]). +-export([woody_context/1]). +-export([get_operation/2]). +-export([get_from_context/3]). +-export([get_from_context/4]). + +-export([set_context/2]). +-export([set_clock/2]). + +-export([clock/1]). + +-type woody_context() :: woody_context:ctx(). +-type timestamp() :: binary(). +-type thrift_context() :: lim_limiter_thrift:'LimitContext'(). +-type clock() :: lim_limiter_thrift:'Clock'(). +-type id() :: binary(). +-type cash() :: lim_body:cash(). + +-type t() :: #{ + woody_context := woody_context(), + context => context(), + clock => clock() +}. + +-type context_type() :: payment_processing. +-type context_operation() :: payment_processing_operation(). + +-type context() :: #{ + payment_processing => payment_processing_context() +}. + +-type payment_processing_context() :: #{ + op := payment_processing_operation(), + invoice => payment_processing_invoice() +}. + +-type payment_processing_operation() :: + invoice + | invoice_adjustment + | invoice_payment + | invoice_payment_adjustment + | invoice_payment_refund + | invoice_payment_chargeback. + +-type payment_processing_invoice() :: #{ + id => id(), + owner_id => id(), + shop_id => id(), + cost => cash(), + created_at => timestamp(), + effective_adjustment => payment_processing_adjustment(), + effective_payment => payment_processing_payment() +}. + +-type payment_processing_adjustment() :: #{ + id => id() +}. + +-type payment_processing_payment() :: #{ + id => id(), + owner_id => id(), + shop_id => id(), + cost => cash(), + capture_cost => cash(), + created_at => timestamp(), + flow => instant | hold, + payer => payment_resource | customer | recurrent, + effective_adjustment => payment_processing_payment_adjustment(), + effective_refund => payment_processing_payment_refund(), + effective_chargeback => payment_processing_payment_chargeback() +}. + +-type payment_processing_payment_adjustment() :: #{ + id => id(), + created_at => timestamp() +}. + +-type payment_processing_payment_refund() :: #{ + id => id(), + cost => cash(), + created_at => timestamp() +}. + +-type payment_processing_payment_chargeback() :: #{ + id => id(), + levy => cash(), + body => cash(), + created_at => timestamp() +}. + +-export_type([t/0]). +-export_type([context_type/0]). +-export_type([context_operation/0]). + +-spec create(woody_context()) -> {ok, t()}. +create(WoodyContext) -> + {ok, #{woody_context => WoodyContext}}. + +-spec woody_context(t()) -> {ok, woody_context()}. +woody_context(Context) -> + {ok, maps:get(woody_context, Context)}. + +-spec clock(t()) -> {ok, clock()} | {error, notfound}. +clock(#{clock := Clock}) -> + {ok, Clock}; +clock(_) -> + {error, notfound}. + +-spec set_context(thrift_context(), t()) -> t(). +set_context(Context, LimContext) -> + LimContext#{context => unmarshal_context(Context)}. + +-spec set_clock(clock(), t()) -> t(). +set_clock(Clock, LimContext) -> + LimContext#{clock => Clock}. + +-spec get_operation(context_type(), t()) -> {ok, atom()} | {error, notfound}. +get_operation(Type, #{context := Context}) -> + case maps:get(Type, Context, undefined) of + undefined -> + {error, notfound}; + #{op := Operation} -> + {ok, Operation} + end. + +-spec get_from_context(context_type(), atom(), t()) -> {ok, term()} | {error, notfound}. +get_from_context(payment_processing, ValueName, LimContext = #{context := Context}) -> + case maps:get(payment_processing, Context, undefined) of + undefined -> + {error, notfound}; + #{op := Operation} -> + get_from_context(payment_processing, ValueName, Operation, LimContext) + end; +get_from_context(_, _ValueName, _LimContext) -> + {error, notfound}. + +-spec get_from_context(context_type(), atom(), context_operation(), t()) -> {ok, term()} | {error, notfound}. +get_from_context(payment_processing, ValueName, Op, #{context := #{payment_processing := Context}}) -> + case get_payment_processing_operation_context(Op, Context) of + {ok, OperationContext} -> + case maps:get(ValueName, OperationContext, undefined) of + undefined -> + {error, notfound}; + Value -> + {ok, Value} + end; + Error -> + Error + end; +get_from_context(_, _ValueName, _Op, _LimContext) -> + {error, notfound}. + +get_payment_processing_operation_context(invoice, #{invoice := Invoice}) -> + {ok, Invoice}; +get_payment_processing_operation_context(invoice_adjustment, #{invoice := #{effective_adjustment := Adjustment}}) -> + {ok, Adjustment}; +get_payment_processing_operation_context(invoice_payment, #{invoice := #{effective_payment := Payment}}) -> + {ok, Payment}; +get_payment_processing_operation_context( + invoice_payment_adjustment, + #{invoice := #{effective_payment := #{effective_adjustment := Adjustment}}} +) -> + {ok, Adjustment}; +get_payment_processing_operation_context( + invoice_payment_refund, + #{invoice := #{effective_payment := #{effective_refund := Refund}}} +) -> + {ok, Refund}; +get_payment_processing_operation_context( + invoice_payment_chargeback, + #{invoice := #{effective_payment := #{effective_chargeback := Chargeback}}} +) -> + {ok, Chargeback}; +get_payment_processing_operation_context(_, _) -> + {error, notfound}. + +%% + +unmarshal_context(#limiter_context_LimitContext{payment_processing = PaymentProcessing}) -> + #{payment_processing => unmarshal_payment_processing_context(PaymentProcessing)}; +unmarshal_context(_) -> + #{}. + +unmarshal_payment_processing_context(#limiter_context_ContextPaymentProcessing{ + op = {Operation, _}, + invoice = Invoice +}) -> + genlib_map:compact(#{ + op => Operation, + invoice => maybe_unmarshal(Invoice, fun unmarshal_payment_processing_invoice/1) + }). + +unmarshal_payment_processing_invoice(#limiter_context_Invoice{ + id = ID, + owner_id = OwnerID, + shop_id = ShopID, + cost = Cost, + created_at = CreatedAt, + effective_payment = EffectivePayment, + effective_adjustment = EffectiveAdjustment +}) -> + genlib_map:compact(#{ + id => maybe_unmarshal(ID, fun unmarshal_string/1), + owner_id => maybe_unmarshal(OwnerID, fun unmarshal_string/1), + shop_id => maybe_unmarshal(ShopID, fun unmarshal_string/1), + cost => maybe_unmarshal(Cost, fun unmarshal_cash/1), + created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1), + effective_adjustment => maybe_unmarshal( + EffectiveAdjustment, + fun unmarshal_payment_processing_invoice_adjustment/1 + ), + effective_payment => maybe_unmarshal(EffectivePayment, fun unmarshal_payment_processing_invoice_payment/1) + }). + +unmarshal_payment_processing_invoice_adjustment(#limiter_context_InvoiceAdjustment{id = ID}) -> + genlib_map:compact(#{ + id => maybe_unmarshal(ID, fun unmarshal_string/1) + }). + +unmarshal_payment_processing_invoice_payment(#limiter_context_InvoicePayment{ + id = ID, + owner_id = OwnerID, + shop_id = ShopID, + cost = Cost, + capture_cost = CaptureCost, + created_at = CreatedAt, + flow = Flow, + payer = Payer, + effective_adjustment = EffectiveAdjustment, + effective_refund = EffectiveRefund, + effective_chargeback = EffectiveChargeback +}) -> + genlib_map:compact(#{ + id => maybe_unmarshal(ID, fun unmarshal_string/1), + owner_id => maybe_unmarshal(OwnerID, fun unmarshal_string/1), + shop_id => maybe_unmarshal(ShopID, fun unmarshal_string/1), + cost => maybe_unmarshal(Cost, fun unmarshal_cash/1), + capture_cost => maybe_unmarshal(CaptureCost, fun unmarshal_cash/1), + created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1), + flow => maybe_unmarshal(Flow, fun unmarshal_payment_processing_invoice_payment_flow/1), + payer => maybe_unmarshal(Payer, fun unmarshal_payment_processing_invoice_payment_payer/1), + effective_adjustment => maybe_unmarshal( + EffectiveAdjustment, + fun unmarshal_payment_processing_invoice_payment_adjustment/1 + ), + effective_refund => maybe_unmarshal(EffectiveRefund, fun unmarshal_payment_processing_invoice_payment_refund/1), + effective_chargeback => maybe_unmarshal( + EffectiveChargeback, + fun unmarshal_payment_processing_invoice_payment_chargeback/1 + ) + }). + +unmarshal_payment_processing_invoice_payment_flow({Flow, _}) -> + Flow. + +unmarshal_payment_processing_invoice_payment_payer({Payer, _}) -> + Payer. + +unmarshal_payment_processing_invoice_payment_adjustment(#limiter_context_InvoicePaymentAdjustment{ + id = ID, + created_at = CreatedAt +}) -> + genlib_map:compact(#{ + id => maybe_unmarshal(ID, fun unmarshal_string/1), + created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1) + }). + +unmarshal_payment_processing_invoice_payment_refund(#limiter_context_InvoicePaymentRefund{ + id = ID, + cost = Cost, + created_at = CreatedAt +}) -> + genlib_map:compact(#{ + id => maybe_unmarshal(ID, fun unmarshal_string/1), + cost => maybe_unmarshal(Cost, fun unmarshal_cash/1), + created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1) + }). + +unmarshal_payment_processing_invoice_payment_chargeback(#limiter_context_InvoicePaymentChargeback{ + id = ID, + levy = Levy, + body = Body, + created_at = CreatedAt +}) -> + genlib_map:compact(#{ + id => maybe_unmarshal(ID, fun unmarshal_string/1), + levy => maybe_unmarshal(Levy, fun unmarshal_cash/1), + body => maybe_unmarshal(Body, fun unmarshal_cash/1), + created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1) + }). + +unmarshal_cash(#limiter_base_Cash{amount = Amount, currency = #limiter_base_CurrencyRef{symbolic_code = Currency}}) -> + lim_body:create_body_from_cash(Amount, Currency). + +unmarshal_string(Value) -> + Value. + +maybe_unmarshal(undefined, _) -> + undefined; +maybe_unmarshal(Value, UnmarshalFun) -> + UnmarshalFun(Value). diff --git a/apps/limiter/src/lim_handler.erl b/apps/limiter/src/lim_handler.erl new file mode 100644 index 00000000..ce83b7e4 --- /dev/null +++ b/apps/limiter/src/lim_handler.erl @@ -0,0 +1,153 @@ +-module(lim_handler). + +-include_lib("limiter_proto/include/lim_limiter_thrift.hrl"). + +%% Woody handler + +-behaviour(woody_server_thrift_handler). + +-export([handle_function/4]). + +%% + +-type lim_context() :: lim_context:t(). + +-define(LIMIT_CHANGE(ID), #limiter_LimitChange{id = ID}). + +-define(CASH( + Amount, + Currency +), + #limiter_base_Cash{amount = Amount, currency = #limiter_base_CurrencyRef{symbolic_code = Currency}} +). + +%% + +-spec handle_function(woody:func(), woody:args(), woody_context:ctx(), woody:options()) -> {ok, woody:result()}. +handle_function(Fn, Args, WoodyCtx, Opts) -> + {ok, LimitContext} = lim_context:create(WoodyCtx), + scoper:scope( + limiter, + fun() -> handle_function_(Fn, Args, LimitContext, Opts) end + ). + +-spec handle_function_(woody:func(), woody:args(), lim_context(), woody:options()) -> {ok, woody:result()}. +handle_function_('Get', {LimitID, Clock, Context}, LimitContext, _Opts) -> + scoper:add_meta(#{limit_id => LimitID}), + case + lim_config_machine:get_limit( + LimitID, + lim_context:set_context(Context, lim_context:set_clock(Clock, LimitContext)) + ) + of + {ok, Limit} -> + {ok, Limit}; + {error, Error} -> + handle_get_error(Error) + end; +handle_function_('Hold', {LimitChange = ?LIMIT_CHANGE(LimitID), Clock, Context}, LimitContext, _Opts) -> + scoper:add_meta(#{limit_id => LimitID}), + case + lim_config_machine:hold( + LimitChange, + lim_context:set_context(Context, lim_context:set_clock(Clock, LimitContext)) + ) + of + ok -> + {ok, {vector, #limiter_VectorClock{state = <<>>}}}; + {error, Error} -> + handle_hold_error(Error) + end; +handle_function_('Commit', {LimitChange = ?LIMIT_CHANGE(LimitID), Clock, Context}, LimitContext, _Opts) -> + scoper:add_meta(#{limit_id => LimitID}), + case + lim_config_machine:commit( + LimitChange, + lim_context:set_context(Context, lim_context:set_clock(Clock, LimitContext)) + ) + of + ok -> + {ok, {vector, #limiter_VectorClock{state = <<>>}}}; + {error, Error} -> + handle_commit_error(Error) + end; +handle_function_('Rollback', {LimitChange = ?LIMIT_CHANGE(LimitID), Clock, Context}, LimitContext, _Opts) -> + scoper:add_meta(#{limit_id => LimitID}), + case + lim_config_machine:rollback( + LimitChange, + lim_context:set_context(Context, lim_context:set_clock(Clock, LimitContext)) + ) + of + ok -> + {ok, {vector, #limiter_VectorClock{state = <<>>}}}; + {error, Error} -> + handle_rollback_error(Error) + end. + +-spec handle_get_error(_) -> no_return(). +handle_get_error({_, {limit, notfound}}) -> + woody_error:raise(business, #limiter_LimitNotFound{}); +handle_get_error({_, {range, notfound}}) -> + woody_error:raise(business, #limiter_LimitNotFound{}); +handle_get_error(Error) -> + handle_default_error(Error). + +-spec handle_hold_error(_) -> no_return(). +handle_hold_error({_, {invalid_request, Errors}}) -> + woody_error:raise(business, #limiter_base_InvalidRequest{errors = Errors}); +handle_hold_error(Error) -> + handle_default_error(Error). + +-spec handle_commit_error(_) -> no_return(). +handle_commit_error({_, {forbidden_operation_amount, Error}}) -> + handle_forbidden_operation_amount_error(Error); +handle_commit_error({_, {plan, notfound}}) -> + woody_error:raise(business, #limiter_LimitChangeNotFound{}); +handle_commit_error({_, {invalid_request, Errors}}) -> + woody_error:raise(business, #limiter_base_InvalidRequest{errors = Errors}); +handle_commit_error(Error) -> + handle_default_error(Error). + +-spec handle_rollback_error(_) -> no_return(). +handle_rollback_error({_, {plan, notfound}}) -> + woody_error:raise(business, #limiter_LimitChangeNotFound{}); +handle_rollback_error({_, {invalid_request, Errors}}) -> + woody_error:raise(business, #limiter_base_InvalidRequest{errors = Errors}); +handle_rollback_error(Error) -> + handle_default_error(Error). + +-spec handle_default_error(_) -> no_return(). +handle_default_error({config, notfound}) -> + woody_error:raise(business, #limiter_LimitNotFound{}); +handle_default_error(Error) -> + handle_unknown_error(Error). + +-spec handle_unknown_error(_) -> no_return(). +handle_unknown_error(Error) -> + erlang:error({unknown_error, Error}). + +-spec handle_forbidden_operation_amount_error(_) -> no_return(). +handle_forbidden_operation_amount_error(#{ + type := Type, + partial := Partial, + full := Full +}) -> + case Type of + positive -> + woody_error:raise(business, #limiter_ForbiddenOperationAmount{ + amount = Partial, + allowed_range = #limiter_base_AmountRange{ + upper = {inclusive, Full}, + lower = {inclusive, 0} + } + }); + negative -> + woody_error:raise(business, #limiter_ForbiddenOperationAmount{ + amount = Partial, + allowed_range = #limiter_base_AmountRange{ + upper = {inclusive, 0}, + lower = {inclusive, Full} + } + }) + end. diff --git a/apps/limiter/src/lim_maybe.erl b/apps/limiter/src/lim_maybe.erl new file mode 100644 index 00000000..f43cfafb --- /dev/null +++ b/apps/limiter/src/lim_maybe.erl @@ -0,0 +1,53 @@ +%%% +%%% Call me maybe +%%% + +-module(lim_maybe). + +-type maybe(T) :: + undefined | T. + +-export_type([maybe/1]). + +-export([from_result/1]). +-export([to_list/1]). +-export([apply/2]). +-export([apply/3]). +-export([get_defined/1]). +-export([get_defined/2]). + +%% + +-spec from_result({ok, T} | {error, _}) -> maybe(T). +from_result({ok, T}) -> + T; +from_result({error, _}) -> + undefined. + +-spec to_list(maybe(T)) -> [T]. +to_list(undefined) -> + []; +to_list(T) -> + [T]. + +-spec apply(fun(), Arg :: undefined | term()) -> term(). +apply(Fun, Arg) -> + lim_maybe:apply(Fun, Arg, undefined). + +-spec apply(fun(), Arg :: undefined | term(), Default :: term()) -> term(). +apply(Fun, Arg, _Default) when Arg =/= undefined -> + Fun(Arg); +apply(_Fun, undefined, Default) -> + Default. + +-spec get_defined([maybe(T)]) -> T. +get_defined([]) -> + erlang:error(badarg); +get_defined([Value | _Tail]) when Value =/= undefined -> + Value; +get_defined([undefined | Tail]) -> + get_defined(Tail). + +-spec get_defined(maybe(T), maybe(T)) -> T. +get_defined(V1, V2) -> + get_defined([V1, V2]). diff --git a/apps/limiter/src/lim_p_transfer.erl b/apps/limiter/src/lim_p_transfer.erl new file mode 100644 index 00000000..e2e19857 --- /dev/null +++ b/apps/limiter/src/lim_p_transfer.erl @@ -0,0 +1,94 @@ +-module(lim_p_transfer). + +-include_lib("damsel/include/dmsl_accounter_thrift.hrl"). +-include_lib("damsel/include/dmsl_base_thrift.hrl"). + +-export([construct_postings/3]). +-export([reverse_postings/1]). +-export([assert_partial_posting_amount/2]). + +-type amount() :: integer(). +-type currency() :: binary(). +-type account_id() :: lim_accounting:account_id(). +-type posting() :: lim_accounting:posting(). +-type body() :: lim_body:t(). + +-type forbidden_operation_amount_error() :: #{ + type := positive | negative, + partial := amount(), + full := amount(), + currency := currency() +}. + +-export_type([forbidden_operation_amount_error/0]). + +-spec construct_postings(account_id(), account_id(), body()) -> [posting()]. +construct_postings(AccountFrom, AccountTo, {cash, #{amount := Amount, currency := Currency}}) -> + [ + #accounter_Posting{ + from_id = AccountFrom, + to_id = AccountTo, + amount = Amount, + currency_sym_code = Currency, + description = <<>> + } + ]; +construct_postings(AccountFrom, AccountTo, {amount, Amount}) -> + [ + #accounter_Posting{ + from_id = AccountFrom, + to_id = AccountTo, + amount = Amount, + currency_sym_code = lim_accounting:get_default_currency(), + description = <<>> + } + ]. + +-spec reverse_postings([posting()]) -> [posting()]. +reverse_postings(Postings) -> + [ + Posting#accounter_Posting{ + from_id = AccountTo, + to_id = AccountFrom + } + || Posting = #accounter_Posting{from_id = AccountFrom, to_id = AccountTo} <- Postings + ]. + +-spec assert_partial_posting_amount([posting()], [posting()]) -> ok | {error, forbidden_operation_amount_error()}. +assert_partial_posting_amount( + [#accounter_Posting{amount = Partial, currency_sym_code = Currency} | _Rest], + [#accounter_Posting{amount = Full, currency_sym_code = Currency} | _Rest] +) -> + compare_amount(Partial, Full, Currency); +assert_partial_posting_amount( + [#accounter_Posting{amount = Partial, currency_sym_code = PartialCurrency} | _Rest], + [#accounter_Posting{amount = Full, currency_sym_code = FullCurrency} | _Rest] +) -> + erlang:error({invalid_partial_cash, {Partial, PartialCurrency}, {Full, FullCurrency}}). + +compare_amount(Partial, Full, Currency) when Full > 0 -> + case Partial =< Full of + true -> + ok; + false -> + {error, + {forbidden_operation_amount, #{ + type => positive, + partial => Partial, + full => Full, + currency => Currency + }}} + end; +compare_amount(Partial, Full, Currency) when Full < 0 -> + case Partial >= Full of + true -> + ok; + false -> + {error, + {forbidden_operation_amount, #{ + type => negative, + partial => Partial, + full => Full, + currency => Currency + }}} + end. diff --git a/apps/limiter/src/lim_pipeline.erl b/apps/limiter/src/lim_pipeline.erl new file mode 100644 index 00000000..4759f1b2 --- /dev/null +++ b/apps/limiter/src/lim_pipeline.erl @@ -0,0 +1,74 @@ +%%% +%%% Pipeline +%%% + +-module(lim_pipeline). + +-export([do/1]). +-export([do/2]). +-export([unwrap/1]). +-export([unwrap/2]). +-export([expect/2]). +-export([valid/2]). + +%% + +-type thrown(_E) :: + no_return(). + +-type result(T, E) :: + {ok, T} | {error, E}. + +-spec do(fun(() -> ok | T | thrown(E))) -> ok | result(T, E). +do(Fun) -> + try Fun() of + ok -> + ok; + R -> + {ok, R} + catch + Thrown -> {error, Thrown} + end. + +-spec do(Tag, fun(() -> ok | T | thrown(E))) -> ok | result(T, {Tag, E}). +do(Tag, Fun) -> + do(fun() -> unwrap(Tag, do(Fun)) end). + +-spec unwrap + (ok) -> ok; + ({ok, V}) -> V; + ({error, E}) -> thrown(E). +unwrap(ok) -> + ok; +unwrap({ok, V}) -> + V; +unwrap({error, E}) -> + throw(E). + +-spec expect + (_E, ok) -> ok; + (_E, {ok, V}) -> V; + (E, {error, _}) -> thrown(E). +expect(_, ok) -> + ok; +expect(_, {ok, V}) -> + V; +expect(E, {error, _}) -> + throw(E). + +-spec unwrap + (_Tag, ok) -> ok; + (_Tag, {ok, V}) -> V; + (Tag, {error, E}) -> thrown({Tag, E}). +unwrap(_, ok) -> + ok; +unwrap(_, {ok, V}) -> + V; +unwrap(Tag, {error, E}) -> + throw({Tag, E}). + +-spec valid(T, T) -> ok | {error, T}. +valid(V, V) -> + ok; +valid(_, V) -> + {error, V}. diff --git a/apps/limiter/src/lim_proto_utils.erl b/apps/limiter/src/lim_proto_utils.erl new file mode 100644 index 00000000..ec7d2c99 --- /dev/null +++ b/apps/limiter/src/lim_proto_utils.erl @@ -0,0 +1,67 @@ +-module(lim_proto_utils). + +-export([serialize/2]). +-export([deserialize/2]). + +-type thrift_type() :: + thrift_base_type() + | thrift_collection_type() + | thrift_enum_type() + | thrift_struct_type(). + +-type thrift_base_type() :: + bool + | double + | i8 + | i16 + | i32 + | i64 + | string. + +-type thrift_collection_type() :: + {list, thrift_type()} + | {set, thrift_type()} + | {map, thrift_type(), thrift_type()}. + +-type thrift_enum_type() :: + {enum, thrift_type_ref()}. + +-type thrift_struct_type() :: + {struct, thrift_struct_flavor(), thrift_type_ref() | thrift_struct_def()}. + +-type thrift_struct_flavor() :: struct | union | exception. + +-type thrift_type_ref() :: {module(), Name :: atom()}. + +-type thrift_struct_def() :: list({ + Tag :: pos_integer(), + Requireness :: required | optional | undefined, + Type :: thrift_struct_type(), + Name :: atom(), + Default :: any() +}). + +-spec serialize(thrift_type(), term()) -> binary(). +serialize(Type, Data) -> + Codec0 = thrift_strict_binary_codec:new(), + case thrift_strict_binary_codec:write(Codec0, Type, Data) of + {ok, Codec1} -> + thrift_strict_binary_codec:close(Codec1); + {error, Reason} -> + erlang:error({thrift, {protocol, Reason}}) + end. + +-spec deserialize(thrift_type(), binary()) -> term(). +deserialize(Type, Data) -> + Codec0 = thrift_strict_binary_codec:new(Data), + case thrift_strict_binary_codec:read(Codec0, Type) of + {ok, Result, Codec1} -> + case thrift_strict_binary_codec:close(Codec1) of + <<>> -> + Result; + Leftovers -> + erlang:error({thrift, {protocol, {excess_binary_data, Leftovers}}}) + end; + {error, Reason} -> + erlang:error({thrift, {protocol, Reason}}) + end. diff --git a/apps/limiter/src/lim_range_codec.erl b/apps/limiter/src/lim_range_codec.erl new file mode 100644 index 00000000..7767f69a --- /dev/null +++ b/apps/limiter/src/lim_range_codec.erl @@ -0,0 +1,178 @@ +-module(lim_range_codec). + +-include_lib("limiter_proto/include/lim_limiter_range_thrift.hrl"). + +-export([marshal/2]). +-export([unmarshal/2]). +-export([parse_timestamp/1]). + +%% Types + +-type type_name() :: atom() | {list, atom()} | {set, atom()}. + +-type encoded_value() :: encoded_value(any()). +-type encoded_value(T) :: T. + +-type decoded_value() :: decoded_value(any()). +-type decoded_value(T) :: T. + +%% API + +-spec marshal(type_name(), decoded_value()) -> encoded_value(). +marshal(timestamped_change, {ev, Timestamp, Change}) -> + #limiter_range_TimestampedChange{ + change = marshal(change, Change), + occured_at = marshal(timestamp, Timestamp) + }; +marshal(change, {created, Range}) -> + {created, #limiter_range_CreatedChange{limit_range = marshal(range, Range)}}; +marshal(change, {time_range_created, TimeRange}) -> + {time_range_created, #limiter_range_TimeRangeCreatedChange{time_range = marshal(time_range, TimeRange)}}; +marshal( + range, + Range = #{ + id := ID, + type := Type, + created_at := CreatedAt + } +) -> + #limiter_range_LimitRange{ + id = ID, + type = marshal(time_range_type, Type), + created_at = CreatedAt, + currency = lim_range_machine:currency(Range) + }; +marshal(time_range, #{ + upper := Upper, + lower := Lower, + account_id_from := AccountIDFrom, + account_id_to := AccountIDTo +}) -> + #time_range_TimeRange{ + upper = Upper, + lower = Lower, + account_id_from = AccountIDFrom, + account_id_to = AccountIDTo + }; +marshal(time_range_type, {calendar, SubType}) -> + {calendar, marshal(time_range_sub_type, SubType)}; +marshal(time_range_type, {interval, Interval}) -> + {interval, #time_range_TimeRangeTypeInterval{amount = Interval}}; +marshal(time_range_sub_type, year) -> + {year, #time_range_TimeRangeTypeCalendarYear{}}; +marshal(time_range_sub_type, month) -> + {month, #time_range_TimeRangeTypeCalendarMonth{}}; +marshal(time_range_sub_type, week) -> + {week, #time_range_TimeRangeTypeCalendarWeek{}}; +marshal(time_range_sub_type, day) -> + {day, #time_range_TimeRangeTypeCalendarDay{}}; +marshal(timestamp, {DateTime, USec}) -> + DateTimeinSeconds = genlib_time:daytime_to_unixtime(DateTime), + {TimeinUnit, Unit} = + case USec of + 0 -> + {DateTimeinSeconds, second}; + USec -> + MicroSec = erlang:convert_time_unit(DateTimeinSeconds, second, microsecond), + {MicroSec + USec, microsecond} + end, + genlib_rfc3339:format_relaxed(TimeinUnit, Unit). + +-spec unmarshal(type_name(), encoded_value()) -> decoded_value(). +unmarshal(timestamped_change, TimestampedChange) -> + Timestamp = unmarshal(timestamp, TimestampedChange#limiter_range_TimestampedChange.occured_at), + Change = unmarshal(change, TimestampedChange#limiter_range_TimestampedChange.change), + {ev, Timestamp, Change}; +unmarshal(change, {created, #limiter_range_CreatedChange{limit_range = Range}}) -> + {created, unmarshal(range, Range)}; +unmarshal(change, {time_range_created, #limiter_range_TimeRangeCreatedChange{time_range = Range}}) -> + {time_range_created, unmarshal(time_range, Range)}; +unmarshal(range, #limiter_range_LimitRange{ + id = ID, + type = Type, + created_at = CreatedAt, + currency = Currency +}) -> + genlib_map:compact(#{ + id => ID, + type => unmarshal(time_range_type, Type), + created_at => CreatedAt, + currency => Currency + }); +unmarshal(time_range, #time_range_TimeRange{ + upper = Upper, + lower = Lower, + account_id_from = AccountIDFrom, + account_id_to = AccountIDTo +}) -> + #{ + upper => Upper, + lower => Lower, + account_id_from => AccountIDFrom, + account_id_to => AccountIDTo + }; +unmarshal(time_range_type, {calendar, SubType}) -> + {calendar, unmarshal(time_range_sub_type, SubType)}; +unmarshal(time_range_type, {interval, #time_range_TimeRangeTypeInterval{amount = Interval}}) -> + {interval, Interval}; +unmarshal(time_range_sub_type, {year, _}) -> + year; +unmarshal(time_range_sub_type, {month, _}) -> + month; +unmarshal(time_range_sub_type, {week, _}) -> + week; +unmarshal(time_range_sub_type, {day, _}) -> + day; +unmarshal(timestamp, Timestamp) when is_binary(Timestamp) -> + parse_timestamp(Timestamp). + +-spec parse_timestamp(binary()) -> machinery:timestamp(). +parse_timestamp(Bin) -> + try + MicroSeconds = genlib_rfc3339:parse(Bin, microsecond), + case genlib_rfc3339:is_utc(Bin) of + false -> + erlang:error({bad_timestamp, not_utc}, [Bin]); + true -> + USec = MicroSeconds rem 1000000, + DateTime = calendar:system_time_to_universal_time(MicroSeconds, microsecond), + {DateTime, USec} + end + catch + error:Error:St -> + erlang:raise(error, {bad_timestamp, Bin, Error}, St) + end. + +%% + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +-spec test() -> _. + +-spec marshal_unmarshal_created_test() -> _. + +marshal_unmarshal_created_test() -> + Created = + {created, #{ + id => <<"id">>, + type => {calendar, day}, + created_at => <<"2000-01-01T00:00:00Z">>, + currency => <<"USD">> + }}, + Event = {ev, lim_time:machinery_now(), Created}, + ?assertEqual(Event, unmarshal(timestamped_change, marshal(timestamped_change, Event))). + +-spec marshal_unmarshal_time_range_created_test() -> _. +marshal_unmarshal_time_range_created_test() -> + TimeRangeCreated = + {time_range_created, #{ + account_id_from => 25, + account_id_to => 175, + upper => <<"2000-01-01T00:00:00Z">>, + lower => <<"2000-01-01T00:00:00Z">> + }}, + Event = {ev, lim_time:machinery_now(), TimeRangeCreated}, + ?assertEqual(Event, unmarshal(timestamped_change, marshal(timestamped_change, Event))). + +-endif. diff --git a/apps/limiter/src/lim_range_machine.erl b/apps/limiter/src/lim_range_machine.erl new file mode 100644 index 00000000..eac9093d --- /dev/null +++ b/apps/limiter/src/lim_range_machine.erl @@ -0,0 +1,268 @@ +-module(lim_range_machine). + +%% Accessors + +-export([id/1]). +-export([created_at/1]). +-export([type/1]). +-export([ranges/1]). +-export([currency/1]). + +%% API + +-export([get/2]). +-export([ensure_exist/2]). +-export([get_range/2]). +-export([get_range_balance/3]). +-export([ensure_range_exist/3]). +-export([ensure_range_exist_in_state/3]). + +%% Machinery callbacks + +-behaviour(machinery). + +-export([init/4]). +-export([process_call/4]). +-export([process_timeout/3]). +-export([process_repair/4]). + +-type args(T) :: machinery:args(T). +-type machine() :: machinery:machine(event(), _). +-type handler_args() :: machinery:handler_args(_). +-type handler_opts() :: machinery:handler_opts(_). +-type result() :: machinery:result(timestamped_event(event()), none()). +-type response(T) :: machinery:response(T). + +%% + +-type woody_context() :: woody_context:ctx(). +-type lim_context() :: lim_context:t(). +-type timestamp() :: lim_config_machine:timestamp(). +-type lim_id() :: lim_config_machine:lim_id(). +-type time_range_type() :: lim_config_machine:time_range_type(). +-type time_range() :: lim_config_machine:time_range(). +-type currency() :: lim_config_machine:currency(). + +-type limit_range_state() :: #{ + id := lim_id(), + type := time_range_type(), + created_at := timestamp(), + currency => currency(), + ranges => [time_range_ext()] +}. + +-type timestamped_event(T) :: + {ev, machinery:timestamp(), T}. + +-type event() :: + {created, limit_range()} + | {time_range_created, time_range_ext()}. + +-type limit_range() :: #{ + id := lim_id(), + type := time_range_type(), + created_at := timestamp(), + currency => currency() +}. + +-type time_range_ext() :: #{ + account_id_from := lim_accounting:account_id(), + account_id_to := lim_accounting:account_id(), + upper := timestamp(), + lower := timestamp() +}. + +-type create_params() :: #{ + id := lim_id(), + type := time_range_type(), + created_at := timestamp(), + currency => currency() +}. + +-type range_call() :: + {add_range, time_range()}. + +-export_type([timestamped_event/1]). +-export_type([event/0]). + +-define(NS, 'lim/range_v1'). + +-import(lim_pipeline, [do/1, unwrap/1, unwrap/2]). + +%% Accessors + +-spec id(limit_range_state()) -> lim_id(). +id(State) -> + maps:get(id, State). + +-spec created_at(limit_range_state()) -> timestamp(). +created_at(State) -> + maps:get(created_at, State). + +-spec type(limit_range_state()) -> time_range_type(). +type(State) -> + maps:get(type, State). + +-spec ranges(limit_range_state()) -> [time_range_ext()]. +ranges(#{ranges := Ranges}) -> + Ranges; +ranges(_State) -> + []. + +-spec currency(limit_range_state()) -> currency(). +currency(#{currency := Currency}) -> + Currency; +currency(_State) -> + lim_accounting:get_default_currency(). + +%%% API + +-spec get(lim_id(), lim_context()) -> {ok, limit_range_state()} | {error, notfound}. +get(ID, LimitContext) -> + {ok, WoodyCtx} = lim_context:woody_context(LimitContext), + get_state(ID, WoodyCtx). + +-spec ensure_exist(create_params(), lim_context()) -> {ok, limit_range_state()}. +ensure_exist(Params = #{id := ID}, LimitContext) -> + {ok, WoodyCtx} = lim_context:woody_context(LimitContext), + case get_state(ID, WoodyCtx) of + {ok, State} -> + {ok, State}; + {error, notfound} -> + _ = start(ID, Params, WoodyCtx), + case get_state(ID, WoodyCtx) of + {ok, State} -> + {ok, State}; + {error, notfound} -> + erlang:error({cant_get_after_start, ID}) + end + end. + +-spec get_range(time_range(), limit_range_state()) -> {ok, time_range_ext()} | {error, notfound}. +get_range(TimeRange, State) -> + find_time_range(TimeRange, ranges(State)). + +-spec get_range_balance(time_range(), limit_range_state(), lim_context()) -> + {ok, lim_accounting:balance()} + | {error, {range, notfound}}. +get_range_balance(TimeRange, State, LimitContext) -> + do(fun() -> + #{account_id_to := AccountID} = unwrap(range, find_time_range(TimeRange, ranges(State))), + {ok, Balance} = lim_accounting:get_balance(AccountID, LimitContext), + Balance + end). + +-spec ensure_range_exist(lim_id(), time_range(), lim_context()) -> + {ok, time_range_ext()} + | {error, {limit_range, notfound}}. +ensure_range_exist(ID, TimeRange, LimitContext) -> + do(fun() -> + {ok, WoodyCtx} = lim_context:woody_context(LimitContext), + State = unwrap(limit_range, get_state(ID, WoodyCtx)), + unwrap(ensure_range_exist_in_state(TimeRange, State, LimitContext)) + end). + +-spec ensure_range_exist_in_state(time_range(), limit_range_state(), lim_context()) -> {ok, time_range_ext()}. +ensure_range_exist_in_state(TimeRange, State, LimitContext) -> + {ok, WoodyCtx} = lim_context:woody_context(LimitContext), + case find_time_range(TimeRange, ranges(State)) of + {error, notfound} -> + call(id(State), {add_range, TimeRange}, WoodyCtx); + {ok, Range} -> + {ok, Range} + end. + +%%% Machinery callbacks + +-spec init(args([event()]), machine(), handler_args(), handler_opts()) -> result(). +init(Events, _Machine, _HandlerArgs, _HandlerOpts) -> + #{ + events => emit_events(Events) + }. + +-spec process_call(args(range_call()), machine(), handler_args(), handler_opts()) -> + {response(time_range_ext()), result()} | no_return(). +process_call({add_range, TimeRange0}, Machine, _HandlerArgs, #{woody_ctx := WoodyCtx}) -> + State = collapse(Machine), + case find_time_range(TimeRange0, ranges(State)) of + {error, notfound} -> + Currency = currency(State), + {ok, LimitContext} = lim_context:create(WoodyCtx), + {ok, AccountIDFrom} = lim_accounting:create_account(Currency, LimitContext), + {ok, AccountIDTo} = lim_accounting:create_account(Currency, LimitContext), + TimeRange1 = TimeRange0#{ + account_id_from => AccountIDFrom, + account_id_to => AccountIDTo + }, + {TimeRange1, #{events => emit_events([{time_range_created, TimeRange1}])}}; + {ok, Range} -> + {Range, #{}} + end. + +-spec process_timeout(machine(), handler_args(), handler_opts()) -> no_return(). +process_timeout(_Machine, _HandlerArgs, _HandlerOpts) -> + not_implemented(timeout). + +-spec process_repair(args(_), machine(), handler_args(), handler_opts()) -> no_return(). +process_repair(_Args, _Machine, _HandlerArgs, _HandlerOpts) -> + not_implemented(repair). + +%%% Internal functions + +find_time_range(_TimeRange, []) -> + {error, notfound}; +find_time_range(#{lower := Lower}, [Head = #{lower := Lower} | _Rest]) -> + {ok, Head}; +find_time_range(TimeRange, [_Head | Rest]) -> + find_time_range(TimeRange, Rest). + +%% + +-spec start(lim_id(), create_params(), woody_context()) -> ok | {error, exists}. +start(ID, Params, WoodyCtx) -> + machinery:start(?NS, ID, [{created, Params}], get_backend(WoodyCtx)). + +-spec call(lim_id(), range_call(), woody_context()) -> {ok, response(_)} | {error, notfound}. +call(ID, Msg, WoodyCtx) -> + machinery:call(?NS, ID, Msg, get_backend(WoodyCtx)). + +-spec get_state(lim_id(), woody_context()) -> {ok, limit_range_state()} | {error, notfound}. +get_state(ID, WoodyCtx) -> + case machinery:get(?NS, ID, get_backend(WoodyCtx)) of + {ok, Machine} -> + {ok, collapse(Machine)}; + {error, notfound} = Error -> + Error + end. + +emit_events(Events) -> + emit_timestamped_events(Events, lim_time:machinery_now()). + +emit_timestamped_events(Events, Ts) -> + [{ev, Ts, Body} || Body <- Events]. + +collapse(#{history := History}) -> + lists:foldl(fun(Ev, St) -> apply_event(Ev, St) end, undefined, History). + +-spec get_backend(woody_context()) -> machinery_mg_backend:backend(). +get_backend(WoodyCtx) -> + lim_utils:get_backend(?NS, WoodyCtx). + +-spec not_implemented(any()) -> no_return(). +not_implemented(What) -> + erlang:error({not_implemented, What}). + +%% + +-spec apply_event(machinery:event(timestamped_event(event())), lim_maybe:maybe(limit_range_state())) -> + limit_range_state(). +apply_event({_ID, _Ts, {ev, _EvTs, Event}}, Config) -> + apply_event_(Event, Config). + +-spec apply_event_(event(), lim_maybe:maybe(limit_range_state())) -> limit_range_state(). +apply_event_({created, LimitRange}, undefined) -> + LimitRange; +apply_event_({time_range_created, TimeRange}, LimitRange = #{ranges := Ranges}) -> + LimitRange#{ranges => [TimeRange | Ranges]}; +apply_event_({time_range_created, TimeRange}, LimitRange) -> + LimitRange#{ranges => [TimeRange]}. diff --git a/apps/limiter/src/lim_range_machinery_schema.erl b/apps/limiter/src/lim_range_machinery_schema.erl new file mode 100644 index 00000000..cb98e5e0 --- /dev/null +++ b/apps/limiter/src/lim_range_machinery_schema.erl @@ -0,0 +1,119 @@ +-module(lim_range_machinery_schema). + +%% Storage schema behaviour +-behaviour(machinery_mg_schema). + +-export([get_version/1]). +-export([marshal/3]). +-export([unmarshal/3]). + +%% Constants + +-define(CURRENT_EVENT_FORMAT_VERSION, 1). + +%% Internal types + +-type type() :: machinery_mg_schema:t(). +-type value(T) :: machinery_mg_schema:v(T). +-type value_type() :: machinery_mg_schema:vt(). +-type context() :: machinery_mg_schema:context(). + +-type event() :: lim_range_machine:timestamped_event(lim_range_machine:event()). +-type aux_state() :: term(). +-type call_args() :: term(). +-type call_response() :: term(). + +-type data() :: + aux_state() + | event() + | call_args() + | call_response(). + +%% machinery_mg_schema callbacks + +-spec get_version(value_type()) -> machinery_mg_schema:version(). +get_version(event) -> + ?CURRENT_EVENT_FORMAT_VERSION; +get_version(aux_state) -> + undefined. + +-spec marshal(type(), value(data()), context()) -> {machinery_msgpack:t(), context()}. +marshal({event, FormatVersion}, TimestampedChange, Context) -> + marshal_event(FormatVersion, TimestampedChange, Context); +marshal(T, V, C) when + T =:= {args, init} orelse + T =:= {args, call} orelse + T =:= {args, repair} orelse + T =:= {aux_state, undefined} orelse + T =:= {response, call} orelse + T =:= {response, {repair, success}} orelse + T =:= {response, {repair, failure}} +-> + machinery_mg_schema_generic:marshal(T, V, C). + +-spec unmarshal(type(), machinery_msgpack:t(), context()) -> {data(), context()}. +unmarshal({event, FormatVersion}, EncodedChange, Context) -> + unmarshal_event(FormatVersion, EncodedChange, Context); +unmarshal(T, V, C) when + T =:= {args, init} orelse + T =:= {args, call} orelse + T =:= {args, repair} orelse + T =:= {aux_state, undefined} orelse + T =:= {response, call} orelse + T =:= {response, {repair, success}} orelse + T =:= {response, {repair, failure}} +-> + machinery_mg_schema_generic:unmarshal(T, V, C). + +%% Internals + +-spec marshal_event(machinery_mg_schema:version(), event(), context()) -> {machinery_msgpack:t(), context()}. +marshal_event(1, TimestampedChange, Context) -> + ThriftChange = lim_range_codec:marshal(timestamped_change, TimestampedChange), + Type = {struct, struct, {lim_limiter_range_thrift, 'TimestampedChange'}}, + {{bin, lim_proto_utils:serialize(Type, ThriftChange)}, Context}. + +-spec unmarshal_event(machinery_mg_schema:version(), machinery_msgpack:t(), context()) -> {event(), context()}. +unmarshal_event(1, EncodedChange, Context) -> + {bin, EncodedThriftChange} = EncodedChange, + Type = {struct, struct, {lim_limiter_range_thrift, 'TimestampedChange'}}, + ThriftChange = lim_proto_utils:deserialize(Type, EncodedThriftChange), + {lim_range_codec:unmarshal(timestamped_change, ThriftChange), Context}. + +%% + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +-spec test() -> _. + +-spec marshal_unmarshal_created_test() -> _. + +marshal_unmarshal_created_test() -> + Created = + {created, #{ + id => <<"id">>, + type => {calendar, day}, + created_at => <<"2000-01-01T00:00:00Z">>, + currency => <<"USD">> + }}, + Event = {ev, lim_time:machinery_now(), Created}, + {Marshaled, _} = marshal_event(1, Event, {}), + {Unmarshaled, _} = unmarshal_event(1, Marshaled, {}), + ?assertEqual(Event, Unmarshaled). + +-spec marshal_unmarshal_time_range_created_test() -> _. +marshal_unmarshal_time_range_created_test() -> + TimeRangeCreated = + {time_range_created, #{ + account_id_from => 25, + account_id_to => 175, + upper => <<"2000-01-01T00:00:00Z">>, + lower => <<"2000-01-01T00:00:00Z">> + }}, + Event = {ev, lim_time:machinery_now(), TimeRangeCreated}, + {Marshaled, _} = marshal_event(1, Event, {}), + {Unmarshaled, _} = unmarshal_event(1, Marshaled, {}), + ?assertEqual(Event, Unmarshaled). + +-endif. diff --git a/apps/limiter/src/lim_rates.erl b/apps/limiter/src/lim_rates.erl new file mode 100644 index 00000000..2dc1adda --- /dev/null +++ b/apps/limiter/src/lim_rates.erl @@ -0,0 +1,71 @@ +-module(lim_rates). + +-include_lib("xrates_proto/include/xrates_rate_thrift.hrl"). +-include_lib("limiter_proto/include/lim_base_thrift.hrl"). +-include_lib("limiter_proto/include/lim_limiter_thrift.hrl"). + +-export([get_converted_amount/3]). + +-type amount() :: dmsl_domain_thrift:'Amount'(). +-type currency() :: dmsl_domain_thrift:'CurrencySymbolicCode'(). +-type limit_context() :: lim_context:t(). +-type config() :: lim_config_machine:config(). + +-type convertation_error() :: quote_not_found | currency_not_found. + +-export_type([convertation_error/0]). + +-define(APP, limiter). +-define(DEFAULT_FACTOR, 1.1). +-define(DEFAULT_FACTOR_NAME, <<"DEFAULT">>). + +-spec get_converted_amount({amount(), currency()}, config(), limit_context()) -> + {ok, amount()} + | {error, convertation_error()}. +get_converted_amount(Cash = {_Amount, Currency}, Config, LimitContext) -> + Factor = get_exchange_factor(Currency), + case + call_rates( + 'GetConvertedAmount', + {<<"CBR">>, construct_conversion_request(Cash, Config, LimitContext)}, + LimitContext + ) + of + {ok, #base_Rational{p = P, q = Q}} -> + Rational = genlib_rational:new(P, Q), + {ok, genlib_rational:round(genlib_rational:mul(Rational, Factor))}; + {exception, #rate_QuoteNotFound{}} -> + {error, quote_not_found}; + {exception, #rate_CurrencyNotFound{}} -> + {error, currency_not_found} + end. + +construct_conversion_request({Amount, Currency}, Config = #{body_type := {cash, DestinationCurrency}}, LimitContext) -> + ContextType = lim_config_machine:context_type(Config), + {ok, Timestamp} = lim_context:get_from_context(ContextType, created_at, LimitContext), + #rate_ConversionRequest{ + source = Currency, + destination = DestinationCurrency, + amount = Amount, + datetime = Timestamp + }. + +get_exchange_factor(Currency) -> + Factors = genlib_app:env(?APP, exchange_factors, #{}), + case maps:get(Currency, Factors, undefined) of + undefined -> + case maps:get(?DEFAULT_FACTOR_NAME, Factors, undefined) of + undefined -> + ?DEFAULT_FACTOR; + DefaultFactor -> + DefaultFactor + end; + Factor -> + Factor + end. + +%% + +call_rates(Function, Args, LimitContext) -> + {ok, WoodyContext} = lim_context:woody_context(LimitContext), + lim_client_woody:call(xrates, Function, Args, WoodyContext). diff --git a/apps/limiter/src/lim_router.erl b/apps/limiter/src/lim_router.erl new file mode 100644 index 00000000..51e59576 --- /dev/null +++ b/apps/limiter/src/lim_router.erl @@ -0,0 +1,17 @@ +-module(lim_router). + +-export([get_handler/1]). + +-type processor_type() :: binary(). +-type processor() :: module(). + +-export_type([processor_type/0]). +-export_type([processor/0]). + +-spec get_handler(processor_type()) -> + {ok, processor()} + | {error, notfound}. +get_handler(<<"TurnoverProcessor">>) -> + {ok, lim_turnover_processor}; +get_handler(_) -> + {error, notfound}. diff --git a/apps/limiter/src/lim_string.erl b/apps/limiter/src/lim_string.erl new file mode 100644 index 00000000..4f58d176 --- /dev/null +++ b/apps/limiter/src/lim_string.erl @@ -0,0 +1,26 @@ +%%% +%%% String manipultion facilities. + +-module(lim_string). + +-export([join/1]). +-export([join/2]). + +%% + +-type fragment() :: + iodata() + | char() + | atom() + | number(). + +-spec join([fragment()]) -> binary(). +join(Fragments) -> + join(<<>>, Fragments). + +-spec join(Delim, [fragment()]) -> binary() when + Delim :: + char() + | iodata(). +join(Delim, Fragments) -> + genlib_string:join(Delim, lists:map(fun genlib:to_binary/1, Fragments)). diff --git a/apps/limiter/src/lim_time.erl b/apps/limiter/src/lim_time.erl new file mode 100644 index 00000000..56e8bb12 --- /dev/null +++ b/apps/limiter/src/lim_time.erl @@ -0,0 +1,27 @@ +-module(lim_time). + +-export([now/0]). +-export([to_rfc3339/1]). +-export([from_rfc3339/1]). +-export([machinery_now/0]). + +-type timestamp_ms() :: integer(). + +-export_type([timestamp_ms/0]). + +-spec now() -> timestamp_ms(). +now() -> + erlang:system_time(millisecond). + +-spec to_rfc3339(timestamp_ms()) -> binary(). +to_rfc3339(Timestamp) -> + genlib_rfc3339:format_relaxed(Timestamp, millisecond). + +-spec from_rfc3339(binary()) -> timestamp_ms(). +from_rfc3339(BTimestamp) -> + genlib_rfc3339:parse(BTimestamp, millisecond). + +-spec machinery_now() -> machinery:timestamp(). +machinery_now() -> + Now = {_, _, USec} = os:timestamp(), + {calendar:now_to_universal_time(Now), USec}. diff --git a/apps/limiter/src/lim_turnover_processor.erl b/apps/limiter/src/lim_turnover_processor.erl new file mode 100644 index 00000000..851e953d --- /dev/null +++ b/apps/limiter/src/lim_turnover_processor.erl @@ -0,0 +1,181 @@ +-module(lim_turnover_processor). + +-include_lib("limiter_proto/include/lim_base_thrift.hrl"). +-include_lib("limiter_proto/include/lim_limiter_thrift.hrl"). +-include_lib("damsel/include/dmsl_accounter_thrift.hrl"). + +-behaviour(lim_config_machine). + +-export([get_limit/3]). +-export([hold/3]). +-export([commit/3]). +-export([rollback/3]). + +-type lim_context() :: lim_context:t(). +-type lim_id() :: lim_config_machine:lim_id(). +-type lim_change() :: lim_config_machine:lim_change(). +-type limit() :: lim_config_machine:limit(). +-type config() :: lim_config_machine:config(). +-type amount() :: integer(). + +-type forbidden_operation_amount_error() :: #{ + type := positive | negative, + partial := amount(), + full := amount() +}. + +-type get_limit_error() :: {limit | range, notfound}. + +-type hold_error() :: + lim_body:get_body_error() + | lim_accounting:invalid_request_error(). + +-type commit_error() :: + {forbidden_operation_amount, forbidden_operation_amount_error()} + | {plan, notfound} + | {full | partial, lim_body:get_body_error()} + | lim_accounting:invalid_request_error(). + +-type rollback_error() :: {plan, notfound} | lim_accounting:invalid_request_error(). + +-export_type([get_limit_error/0]). +-export_type([hold_error/0]). +-export_type([commit_error/0]). +-export_type([rollback_error/0]). + +-import(lim_pipeline, [do/1, unwrap/1, unwrap/2]). + +-spec get_limit(lim_id(), config(), lim_context()) -> {ok, limit()} | {error, get_limit_error()}. +get_limit(LimitID, Config, LimitContext) -> + do(fun() -> + {ok, Timestamp} = lim_context:get_from_context(payment_processing, created_at, LimitContext), + LimitRangeID = construct_range_id(LimitID, Timestamp, Config, LimitContext), + LimitRange = unwrap(limit, lim_range_machine:get(LimitRangeID, LimitContext)), + TimeRange = lim_config_machine:calculate_time_range(Timestamp, Config), + #{own_amount := Amount} = + unwrap(lim_range_machine:get_range_balance(TimeRange, LimitRange, LimitContext)), + #limiter_Limit{ + id = LimitRangeID, + amount = Amount, + creation_time = lim_config_machine:created_at(Config), + description = lim_config_machine:description(Config) + } + end). + +-spec hold(lim_change(), config(), lim_context()) -> ok | {error, hold_error()}. +hold(LimitChange = #limiter_LimitChange{id = LimitID}, Config = #{body_type := BodyType}, LimitContext) -> + do(fun() -> + {ok, Timestamp} = lim_context:get_from_context(payment_processing, created_at, LimitContext), + {ok, Body} = lim_body:get_body(full, Config, LimitContext), + LimitRangeID = construct_range_id(LimitID, Timestamp, Config, LimitContext), + Currency = + case BodyType of + {cash, CashCurrency} -> CashCurrency; + amount -> undefined + end, + CreateParams = genlib_map:compact(#{ + id => LimitRangeID, + type => lim_config_machine:time_range_type(Config), + created_at => Timestamp, + currency => Currency + }), + {ok, LimitRangeState} = lim_range_machine:ensure_exist(CreateParams, LimitContext), + TimeRange = lim_config_machine:calculate_time_range(Timestamp, Config), + {ok, #{account_id_from := AccountIDFrom, account_id_to := AccountIDTo}} = + lim_range_machine:ensure_range_exist_in_state(TimeRange, LimitRangeState, LimitContext), + Postings = lim_p_transfer:construct_postings(AccountIDFrom, AccountIDTo, Body), + lim_accounting:hold(construct_plan_id(LimitChange), {1, Postings}, LimitContext) + end). + +-spec commit(lim_change(), config(), lim_context()) -> ok | {error, commit_error()}. +commit(LimitChange, Config, LimitContext) -> + do(fun() -> + case lim_body:get_body(partial, Config, LimitContext) of + {ok, Body} -> + unwrap(partial_commit(Body, LimitChange, Config, LimitContext)); + {error, notfound} -> + PlanID = construct_plan_id(LimitChange), + [Batch] = unwrap(plan, lim_accounting:get_plan(PlanID, LimitContext)), + unwrap(lim_accounting:commit(PlanID, [Batch], LimitContext)) + end + end). + +-spec rollback(lim_change(), config(), lim_context()) -> ok | {error, rollback_error()}. +rollback(LimitChange, _Config, LimitContext) -> + do(fun() -> + PlanID = construct_plan_id(LimitChange), + BatchList = unwrap(plan, lim_accounting:get_plan(PlanID, LimitContext)), + unwrap(lim_accounting:rollback(PlanID, BatchList, LimitContext)) + end). + +construct_plan_id(#limiter_LimitChange{change_id = ChangeID}) -> + ChangeID. + +construct_range_id(LimitID, Timestamp, Config, LimitContext) -> + {ok, Prefix} = lim_config_machine:mk_scope_prefix(Config, LimitContext), + ShardID = lim_config_machine:calculate_shard_id(Timestamp, Config), + <>. + +partial_commit(PartialBody, LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) -> + do(fun() -> + {ok, Timestamp} = lim_context:get_from_context(payment_processing, created_at, LimitContext), + {ok, FullBody} = lim_body:get_body(full, Config, LimitContext), + ok = unwrap(assert_partial_body(PartialBody, FullBody)), + + LimitRangeID = construct_range_id(LimitID, Timestamp, Config, LimitContext), + {ok, LimitRangeState} = lim_range_machine:get( + LimitRangeID, + LimitContext + ), + TimeRange = lim_config_machine:calculate_time_range(Timestamp, Config), + {ok, #{account_id_from := AccountIDFrom, account_id_to := AccountIDTo}} = + lim_range_machine:get_range(TimeRange, LimitRangeState), + + PartialPostings = lim_p_transfer:construct_postings(AccountIDFrom, AccountIDTo, PartialBody), + FullPostings = lim_p_transfer:construct_postings(AccountIDFrom, AccountIDTo, FullBody), + NewBatchList = [{2, lim_p_transfer:reverse_postings(FullPostings)} | [{3, PartialPostings}]], + + PlanID = construct_plan_id(LimitChange), + unwrap(lim_accounting:plan(PlanID, NewBatchList, LimitContext)), + unwrap(lim_accounting:commit(PlanID, [{1, FullPostings} | NewBatchList], LimitContext)) + end). + +assert_partial_body( + {cash, #{amount := Partial, currency := Currency}}, + {cash, #{amount := Full, currency := Currency}} +) -> + compare_amount(Partial, Full, Currency); +assert_partial_body( + {cash, #{amount := Partial, currency := PartialCurrency}}, + {cash, #{amount := Full, currency := FullCurrency}} +) -> + erlang:error({invalid_partial_cash, {Partial, PartialCurrency}, {Full, FullCurrency}}). + +compare_amount(Partial, Full, Currency) when Full > 0 -> + case Partial =< Full of + true -> + ok; + false -> + {error, + {forbidden_operation_amount, + genlib_map:compact(#{ + type => positive, + partial => Partial, + full => Full, + currency => Currency + })}} + end; +compare_amount(Partial, Full, Currency) when Full < 0 -> + case Partial >= Full of + true -> + ok; + false -> + {error, + {forbidden_operation_amount, + genlib_map:compact(#{ + type => negative, + partial => Partial, + full => Full, + currency => Currency + })}} + end. diff --git a/apps/limiter/src/lim_utils.erl b/apps/limiter/src/lim_utils.erl new file mode 100644 index 00000000..a227861a --- /dev/null +++ b/apps/limiter/src/lim_utils.erl @@ -0,0 +1,30 @@ +-module(lim_utils). + +-export([get_backend/2]). +-export([get_woody_client/1]). + +-type woody_context() :: woody_context:ctx(). + +-spec get_backend(atom(), woody_context()) -> machinery_mg_backend:backend(). +get_backend(NS, WoodyCtx) -> + Backend = maps:get(NS, genlib_app:env(limiter, backends, #{})), + {Mod, Opts} = machinery_utils:get_backend(Backend), + {Mod, Opts#{ + woody_ctx => WoodyCtx + }}. + +%%% Internal functions + +-spec get_woody_client(woody:url()) -> machinery_mg_client:woody_client(). +get_woody_client(Url) -> + genlib_map:compact(#{ + url => Url, + event_handler => get_woody_event_handlers() + }). + +-spec get_woody_event_handlers() -> woody:ev_handlers(). +get_woody_event_handlers() -> + genlib_app:env(limiter, woody_event_handlers, [ + scoper_woody_event_handler, + hay_woody_event_handler + ]). diff --git a/apps/limiter/src/limiter.app.src b/apps/limiter/src/limiter.app.src index 13d83dc4..e45aade1 100644 --- a/apps/limiter/src/limiter.app.src +++ b/apps/limiter/src/limiter.app.src @@ -6,6 +6,9 @@ kernel, stdlib, damsel, + limiter_proto, + xrates_proto, + machinery, woody, how_are_you, % must be after ranch and before any woody usage scoper, % should be before any scoper event handler usage diff --git a/apps/limiter/src/limiter.erl b/apps/limiter/src/limiter.erl new file mode 100644 index 00000000..aaf6c4eb --- /dev/null +++ b/apps/limiter/src/limiter.erl @@ -0,0 +1,154 @@ +-module(limiter). + +%% Application callbacks + +-behaviour(application). + +-export([start/2]). +-export([stop/1]). + +%% Supervisor callbacks + +-behaviour(supervisor). + +-export([init/1]). + +%% + +-spec start(normal, any()) -> {ok, pid()} | {error, any()}. +start(_StartType, _StartArgs) -> + supervisor:start_link({local, ?MODULE}, ?MODULE, []). + +-spec stop(any()) -> ok. +stop(_State) -> + ok. + +%% + +-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. +init([]) -> + ServiceOpts = genlib_app:env(?MODULE, services, #{}), + Healthcheck = enable_health_logging(genlib_app:env(?MODULE, health_check, #{})), + + {Backends, MachineHandlers, ModernizerHandlers} = lists:unzip3([ + contruct_backend_childspec('lim/range_v1', lim_range_machine), + contruct_backend_childspec('lim/config_v1', lim_config_machine) + ]), + ok = application:set_env(limiter, backends, maps:from_list(Backends)), + + RouteOptsEnv = genlib_app:env(?MODULE, route_opts, #{}), + EventHandlers = genlib_app:env(?MODULE, woody_event_handlers, [woody_event_handler_default]), + EventHandlerOpts = genlib_app:env(?MODULE, scoper_event_handler_options, #{}), + RouteOpts = RouteOptsEnv#{event_handler => {scoper_woody_event_handler, EventHandlerOpts}}, + + ChildSpec = woody_server:child_spec( + ?MODULE, + #{ + ip => get_ip_address(), + port => get_port(), + protocol_opts => get_protocol_opts(), + transport_opts => get_transport_opts(), + shutdown_timeout => get_shutdown_timeout(), + event_handler => EventHandlers, + handlers => get_handler_specs(ServiceOpts), + additional_routes => + machinery_mg_backend:get_routes(MachineHandlers, RouteOpts) ++ + machinery_modernizer_mg_backend:get_routes(ModernizerHandlers, RouteOpts) ++ + [erl_health_handle:get_route(Healthcheck)] ++ get_prometheus_route() + } + ), + {ok, + { + #{strategy => one_for_all, intensity => 6, period => 30}, + [ChildSpec] + }}. + +-spec get_ip_address() -> inet:ip_address(). +get_ip_address() -> + {ok, Address} = inet:parse_address(genlib_app:env(?MODULE, ip, "::")), + Address. + +-spec get_port() -> inet:port_number(). +get_port() -> + genlib_app:env(?MODULE, port, 8022). + +-spec get_protocol_opts() -> woody_server_thrift_http_handler:protocol_opts(). +get_protocol_opts() -> + genlib_app:env(?MODULE, protocol_opts, #{}). + +-spec get_transport_opts() -> woody_server_thrift_http_handler:transport_opts(). +get_transport_opts() -> + genlib_app:env(?MODULE, transport_opts, #{}). + +-spec get_shutdown_timeout() -> timeout(). +get_shutdown_timeout() -> + genlib_app:env(?MODULE, shutdown_timeout, 0). + +-spec get_handler_specs(map()) -> [woody:http_handler(woody:th_handler())]. +get_handler_specs(ServiceOpts) -> + LimiterService = maps:get(limiter, ServiceOpts, #{}), + ConfiguratorService = maps:get(configurator, ServiceOpts, #{}), + [ + { + maps:get(path, LimiterService, <<"/v1/limiter">>), + {{lim_limiter_thrift, 'Limiter'}, lim_handler} + }, + { + maps:get(path, ConfiguratorService, <<"/v1/configurator">>), + {{lim_configurator_thrift, 'Configurator'}, lim_configurator} + } + ]. + +%% + +-spec enable_health_logging(erl_health:check()) -> erl_health:check(). +enable_health_logging(Check) -> + EvHandler = {erl_health_event_handler, []}, + maps:map( + fun(_, Runner) -> #{runner => Runner, event_handler => EvHandler} end, + Check + ). + +-spec get_prometheus_route() -> [{iodata(), module(), _Opts :: any()}]. +get_prometheus_route() -> + [{"/metrics/[:registry]", prometheus_cowboy2_handler, []}]. + +contruct_backend_childspec(NS, Handler) -> + Schema = get_namespace_schema(NS), + { + construct_machinery_backend_spec(NS, Schema), + construct_machinery_handler_spec(NS, Handler, Schema), + construct_machinery_modernizer_spec(NS, Schema) + }. + +construct_machinery_backend_spec(NS, Schema) -> + {NS, + {machinery_mg_backend, #{ + schema => Schema, + client => get_service_client(automaton) + }}}. + +construct_machinery_handler_spec(NS, Handler, Schema) -> + {Handler, #{ + path => lim_string:join(["/v1/stateproc/", NS]), + backend_config => #{schema => Schema} + }}. + +construct_machinery_modernizer_spec(NS, Schema) -> + #{ + path => lim_string:join(["/v1/modernizer/", NS]), + backend_config => #{schema => Schema} + }. + +get_namespace_schema('lim/range_v1') -> + lim_range_machinery_schema; +get_namespace_schema('lim/config_v1') -> + lim_config_machinery_schema. + +get_service_client(ServiceID) -> + case lim_client_woody:get_service_client_url(ServiceID) of + undefined -> + error({unknown_service, ServiceID}); + Url -> + lim_utils:get_woody_client(Url) + end. diff --git a/apps/limiter/test/lim_client.erl b/apps/limiter/test/lim_client.erl new file mode 100644 index 00000000..7e89099a --- /dev/null +++ b/apps/limiter/test/lim_client.erl @@ -0,0 +1,78 @@ +-module(lim_client). + +-include_lib("limiter_proto/include/lim_limiter_thrift.hrl"). +-include_lib("limiter_proto/include/lim_configurator_thrift.hrl"). + +-export([new/0]). +-export([get/3]). +-export([hold/3]). +-export([commit/3]). + +-export([create_config/2]). +-export([get_config/2]). + +-type client() :: woody_context:ctx(). + +-type limit_id() :: lim_limiter_thrift:'LimitID'(). +-type limit_change() :: lim_limiter_thrift:'LimitChange'(). +-type limit_context() :: lim_limiter_thrift:'LimitContext'(). +-type clock() :: lim_limiter_thrift:'Clock'(). +-type limit_config_params() :: lim_limiter_config_thrift:'LimitCreateParams'(). + +%%% API + +-spec new() -> client(). +new() -> + woody_context:new(). + +-spec get(limit_id(), limit_context(), client()) -> woody:result() | no_return(). +get(LimitID, Context, Client) -> + call('Get', {LimitID, clock(), Context}, Client). + +-spec hold(limit_change(), limit_context(), client()) -> woody:result() | no_return(). +hold(LimitChange, Context, Client) -> + call('Hold', {LimitChange, clock(), Context}, Client). + +-spec commit(limit_change(), limit_context(), client()) -> woody:result() | no_return(). +commit(LimitChange, Context, Client) -> + call('Commit', {LimitChange, clock(), Context}, Client). + +%% + +-spec create_config(limit_config_params(), client()) -> woody:result() | no_return(). +create_config(LimitCreateParams, Client) -> + call_configurator('Create', {LimitCreateParams}, Client). + +-spec get_config(limit_id(), client()) -> woody:result() | no_return(). +get_config(LimitConfigID, Client) -> + call_configurator('Get', {LimitConfigID}, Client). + +%%% Internal functions + +-spec call(atom(), tuple(), client()) -> woody:result() | no_return(). +call(Function, Args, Client) -> + Call = {{lim_limiter_thrift, 'Limiter'}, Function, Args}, + Opts = #{ + url => <<"http://limiter:8022/v1/limiter">>, + event_handler => scoper_woody_event_handler, + transport_opts => #{ + max_connections => 10000 + } + }, + woody_client:call(Call, Opts, Client). + +-spec call_configurator(atom(), tuple(), client()) -> woody:result() | no_return(). +call_configurator(Function, Args, Client) -> + Call = {{lim_configurator_thrift, 'Configurator'}, Function, Args}, + Opts = #{ + url => <<"http://limiter:8022/v1/configurator">>, + event_handler => scoper_woody_event_handler, + transport_opts => #{ + max_connections => 10000 + } + }, + woody_client:call(Call, Opts, Client). + +-spec clock() -> clock(). +clock() -> + {vector, #limiter_VectorClock{state = <<>>}}. diff --git a/apps/limiter/test/lim_configurator_SUITE.erl b/apps/limiter/test/lim_configurator_SUITE.erl new file mode 100644 index 00000000..df1e419d --- /dev/null +++ b/apps/limiter/test/lim_configurator_SUITE.erl @@ -0,0 +1,104 @@ +-module(lim_configurator_SUITE). + +-include_lib("stdlib/include/assert.hrl"). +-include_lib("common_test/include/ct.hrl"). + +-include_lib("limiter_proto/include/lim_configurator_thrift.hrl"). + +-export([all/0]). + +-export([groups/0]). +-export([init_per_suite/1]). +-export([end_per_suite/1]). +-export([init_per_testcase/2]). +-export([end_per_testcase/2]). + +-export([create_config/1]). +-export([get_config/1]). + +-type test_case_name() :: atom(). + +-define(RATE_SOURCE_ID, <<"dummy_source_id">>). + +%% tests descriptions + +-spec all() -> [test_case_name()]. +all() -> + [ + {group, default} + ]. + +-spec groups() -> [{atom(), list(), [test_case_name()]}]. +groups() -> + [ + {default, [], [ + create_config, + get_config + ]} + ]. + +-type config() :: [{atom(), any()}]. + +-spec init_per_suite(config()) -> config(). +init_per_suite(Config) -> + % dbg:tracer(), dbg:p(all, c), + % dbg:tpl({machinery, '_', '_'}, x), + Apps = + genlib_app:start_application_with(limiter, [ + {service_clients, #{ + accounter => #{ + url => <<"http://shumway:8022/accounter">> + }, + automaton => #{ + url => <<"http://machinegun:8022/v1/automaton">> + } + }} + ]), + [{apps, Apps}] ++ Config. + +-spec end_per_suite(config()) -> _. +end_per_suite(Config) -> + [application:stop(App) || App <- proplists:get_value(apps, Config)], + Config. + +-spec init_per_testcase(test_case_name(), config()) -> config(). +init_per_testcase(_Name, C) -> + C. + +-spec end_per_testcase(test_case_name(), config()) -> config(). +end_per_testcase(_Name, _C) -> + ok. + +%% + +-spec create_config(config()) -> _. +create_config(_C) -> + Client = lim_client:new(), + Params = #limiter_cfg_LimitCreateParams{ + id = <<"ID">>, + name = <<"GlobalMonthTurnover">>, + description = <<"description">>, + started_at = <<"2000-01-01T00:00:00Z">>, + body_type = {cash, #limiter_config_LimitBodyTypeCash{currency = <<"RUB">>}} + }, + {ok, #limiter_config_LimitConfig{}} = lim_client:create_config(Params, Client). + +-spec get_config(config()) -> _. +get_config(C) -> + ID = <<"ID">>, + #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), + {ok, #limiter_config_LimitConfig{id = ID}} = lim_client:get_config(ID, Client). + +%% + +prepare_environment(ID, LimitName, _C) -> + Client = lim_client:new(), + Params = #limiter_cfg_LimitCreateParams{ + id = ID, + name = LimitName, + description = <<"description">>, + started_at = <<"2000-01-01T00:00:00Z">>, + body_type = {cash, #limiter_config_LimitBodyTypeCash{currency = <<"RUB">>}} + }, + {ok, LimitConfig} = lim_client:create_config(Params, Client), + #{config => LimitConfig, client => Client}. diff --git a/apps/limiter/test/lim_mock.erl b/apps/limiter/test/lim_mock.erl new file mode 100644 index 00000000..1b749cb7 --- /dev/null +++ b/apps/limiter/test/lim_mock.erl @@ -0,0 +1,85 @@ +-module(lim_mock). + +-include_lib("common_test/include/ct.hrl"). + +-export([start_mocked_service_sup/0]). +-export([stop_mocked_service_sup/1]). +-export([mock_services/2]). + +-define(APP, limiter). + +-spec start_mocked_service_sup() -> _. +start_mocked_service_sup() -> + {ok, SupPid} = genlib_adhoc_supervisor:start_link(#{}, []), + _ = unlink(SupPid), + SupPid. + +-spec stop_mocked_service_sup(pid()) -> _. +stop_mocked_service_sup(SupPid) -> + exit(SupPid, shutdown). + +-define(HOST_IP, "::"). +-define(HOST_PORT, 8080). +-define(HOST_NAME, "localhost"). +-define(HOST_URL, ?HOST_NAME ++ ":" ++ integer_to_list(?HOST_PORT)). + +-spec mock_services(_, _) -> _. +mock_services(Services, SupOrConfig) -> + maps:map(fun set_cfg/2, mock_services_(Services, SupOrConfig)). + +set_cfg(Service, Url) -> + {ok, Clients} = application:get_env(?APP, service_clients), + #{Service := Cfg} = Clients, + ok = application:set_env( + ?APP, + service_clients, + Clients#{Service => Cfg#{url => Url}} + ). + +mock_services_(Services, Config) when is_list(Config) -> + mock_services_(Services, ?config(test_sup, Config)); +mock_services_(Services, SupPid) when is_pid(SupPid) -> + Name = lists:map(fun get_service_name/1, Services), + + {ok, IP} = inet:parse_address(?HOST_IP), + ServerID = {dummy, Name}, + Options = #{ + ip => IP, + port => 0, + event_handler => scoper_woody_event_handler, + handlers => lists:map(fun mock_service_handler/1, Services), + transport_opts => #{num_acceptors => 1} + }, + ChildSpec = woody_server:child_spec(ServerID, Options), + {ok, _} = supervisor:start_child(SupPid, ChildSpec), + {IP, Port} = woody_server:get_addr(ServerID, Options), + lists:foldl( + fun(Service, Acc) -> + ServiceName = get_service_name(Service), + Acc#{ServiceName => make_url(ServiceName, Port)} + end, + #{}, + Services + ). + +get_service_name({ServiceName, _Fun}) -> + ServiceName; +get_service_name({ServiceName, _WoodyService, _Fun}) -> + ServiceName. + +mock_service_handler({ServiceName, Fun}) -> + mock_service_handler(ServiceName, get_service_modname(ServiceName), Fun); +mock_service_handler({ServiceName, WoodyService, Fun}) -> + mock_service_handler(ServiceName, WoodyService, Fun). + +mock_service_handler(ServiceName, WoodyService, Fun) -> + {make_path(ServiceName), {WoodyService, {lim_mock_service, #{function => Fun}}}}. + +get_service_modname(xrates) -> + {xrates_rate_thrift, 'Rates'}. + +make_url(ServiceName, Port) -> + iolist_to_binary(["http://", ?HOST_NAME, ":", integer_to_list(Port), make_path(ServiceName)]). + +make_path(ServiceName) -> + "/" ++ atom_to_list(ServiceName). diff --git a/apps/limiter/test/lim_mock_service.erl b/apps/limiter/test/lim_mock_service.erl new file mode 100644 index 00000000..ba2298a6 --- /dev/null +++ b/apps/limiter/test/lim_mock_service.erl @@ -0,0 +1,9 @@ +-module(lim_mock_service). + +-behaviour(woody_server_thrift_handler). + +-export([handle_function/4]). + +-spec handle_function(woody:func(), woody:args(), woody_context:ctx(), #{}) -> {ok, term()}. +handle_function(FunName, Args, _, #{function := Fun}) -> + Fun(FunName, Args). diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl new file mode 100644 index 00000000..f3ce6423 --- /dev/null +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -0,0 +1,321 @@ +-module(lim_turnover_SUITE). + +-include_lib("stdlib/include/assert.hrl"). +-include_lib("common_test/include/ct.hrl"). + +-include_lib("limiter_proto/include/lim_configurator_thrift.hrl"). +-include_lib("xrates_proto/include/xrates_rate_thrift.hrl"). + +-export([all/0]). + +-export([groups/0]). +-export([init_per_suite/1]). +-export([end_per_suite/1]). +-export([init_per_testcase/2]). +-export([end_per_testcase/2]). + +-export([commit_with_default_exchange/1]). +-export([partial_commit_with_exchange/1]). +-export([commit_with_exchange/1]). +-export([get_rate/1]). +-export([get_limit_notfound/1]). +-export([hold_ok/1]). +-export([commit_ok/1]). +-export([rollback_ok/1]). +-export([get_config_ok/1]). + +-type test_case_name() :: atom(). + +-define(RATE_SOURCE_ID, <<"dummy_source_id">>). + +%% tests descriptions + +-spec all() -> [test_case_name()]. +all() -> + [ + {group, default} + ]. + +-spec groups() -> [{atom(), list(), [test_case_name()]}]. +groups() -> + [ + {default, [], [ + commit_with_default_exchange, + partial_commit_with_exchange, + commit_with_exchange, + get_rate, + get_limit_notfound, + hold_ok, + commit_ok, + rollback_ok, + get_config_ok + ]} + ]. + +-type config() :: [{atom(), any()}]. + +-spec init_per_suite(config()) -> config(). +init_per_suite(Config) -> + % dbg:tracer(), dbg:p(all, c), + % dbg:tpl({lim_handler, '_', '_'}, x), + Apps = + genlib_app:start_application_with(limiter, [ + {service_clients, #{ + accounter => #{ + url => <<"http://shumway:8022/accounter">> + }, + automaton => #{ + url => <<"http://machinegun:8022/v1/automaton">> + }, + xrates => #{ + url => <<"http://xrates:8022/xrates">> + } + }}, + {exchange_factors, #{ + <<"DEFAULT">> => {1, 1}, + <<"USD">> => {105, 100}, + <<"EUR">> => {12, 10} + }} + ]), + [{apps, Apps}] ++ Config. + +-spec end_per_suite(config()) -> _. +end_per_suite(Config) -> + [application:stop(App) || App <- proplists:get_value(apps, Config)], + Config. + +-spec init_per_testcase(test_case_name(), config()) -> config(). +init_per_testcase(_Name, C) -> + [{test_sup, lim_mock:start_mocked_service_sup()} | C]. + +-spec end_per_testcase(test_case_name(), config()) -> config(). +end_per_testcase(_Name, C) -> + lim_mock:stop_mocked_service_sup(?config(test_sup, C)), + ok. + +%% + +-spec commit_with_default_exchange(config()) -> _. +commit_with_default_exchange(C) -> + Rational = #base_Rational{p = 1000000, q = 100}, + mock_exchange(Rational, C), + ID = lim_time:to_rfc3339(lim_time:now()), + #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), + Context = #limiter_context_LimitContext{ + payment_processing = #limiter_context_ContextPaymentProcessing{ + op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, + invoice = #limiter_context_Invoice{ + created_at = <<"2000-01-01T00:00:00Z">>, + cost = #limiter_base_Cash{ + amount = 10000, + currency = #limiter_base_CurrencyRef{symbolic_code = <<"SOME_CURRENCY">>} + } + } + } + }, + Timestamp = lim_time:to_rfc3339(lim_time:now()), + LimitChangeID = <>, + Change = #limiter_LimitChange{ + id = ID, + change_id = LimitChangeID + }, + {ok, {vector, _}} = hold_and_commit(Change, Context, Client), + {ok, #limiter_Limit{amount = 10000}} = lim_client:get(ID, Context, Client). + +-spec partial_commit_with_exchange(config()) -> _. +partial_commit_with_exchange(C) -> + Rational = #base_Rational{p = 800000, q = 100}, + mock_exchange(Rational, C), + ID = lim_time:to_rfc3339(lim_time:now()), + #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), + Context = #limiter_context_LimitContext{ + payment_processing = #limiter_context_ContextPaymentProcessing{ + op = {invoice_payment, #limiter_context_PaymentProcessingOperationInvoicePayment{}}, + invoice = #limiter_context_Invoice{ + effective_payment = #limiter_context_InvoicePayment{ + created_at = <<"2000-01-01T00:00:00Z">>, + cost = #limiter_base_Cash{ + amount = 10000, + currency = #limiter_base_CurrencyRef{symbolic_code = <<"USD">>} + }, + capture_cost = #limiter_base_Cash{ + amount = 8000, + currency = #limiter_base_CurrencyRef{symbolic_code = <<"USD">>} + } + } + } + } + }, + Timestamp = lim_time:to_rfc3339(lim_time:now()), + LimitChangeID = <>, + Change = #limiter_LimitChange{ + id = ID, + change_id = LimitChangeID + }, + {ok, {vector, _}} = hold_and_commit(Change, Context, Client), + {ok, #limiter_Limit{amount = 8400}} = lim_client:get(ID, Context, Client). + +-spec commit_with_exchange(config()) -> _. +commit_with_exchange(C) -> + Rational = #base_Rational{p = 1000000, q = 100}, + mock_exchange(Rational, C), + ID = lim_time:to_rfc3339(lim_time:now()), + #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), + Context = #limiter_context_LimitContext{ + payment_processing = #limiter_context_ContextPaymentProcessing{ + op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, + invoice = #limiter_context_Invoice{ + created_at = <<"2000-01-01T00:00:00Z">>, + cost = #limiter_base_Cash{ + amount = 10000, + currency = #limiter_base_CurrencyRef{symbolic_code = <<"USD">>} + } + } + } + }, + Timestamp = lim_time:to_rfc3339(lim_time:now()), + LimitChangeID = <>, + Change = #limiter_LimitChange{ + id = ID, + change_id = LimitChangeID + }, + {ok, {vector, _}} = hold_and_commit(Change, Context, Client), + {ok, #limiter_Limit{amount = 10500}} = lim_client:get(ID, Context, Client). + +-spec get_rate(config()) -> _. +get_rate(C) -> + Rational = #base_Rational{p = 10, q = 10}, + mock_exchange(Rational, C), + Request = #rate_ConversionRequest{ + source = <<"RUB">>, + destination = <<"USD">>, + amount = 100, + datetime = <<"Timestamp">> + }, + WoodyContext = woody_context:new(), + {ok, Rational} = lim_client_woody:call( + xrates, + 'GetConvertedAmount', + {?RATE_SOURCE_ID, Request}, + WoodyContext + ). + +-spec get_limit_notfound(config()) -> _. +get_limit_notfound(C) -> + ID = lim_time:to_rfc3339(lim_time:now()), + #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), + Context = #limiter_context_LimitContext{ + payment_processing = #limiter_context_ContextPaymentProcessing{ + op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, + invoice = #limiter_context_Invoice{created_at = <<"2000-01-01T00:00:00Z">>} + } + }, + {exception, #limiter_LimitNotFound{}} = lim_client:get(ID, Context, Client). + +-spec hold_ok(config()) -> _. +hold_ok(C) -> + ID = <<"ID">>, + #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), + Context = #limiter_context_LimitContext{ + payment_processing = #limiter_context_ContextPaymentProcessing{ + op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, + invoice = #limiter_context_Invoice{ + created_at = <<"2000-01-01T00:00:00Z">>, + cost = #limiter_base_Cash{ + amount = 10, + currency = #limiter_base_CurrencyRef{symbolic_code = <<"RUB">>} + } + } + } + }, + Timestamp = lim_time:to_rfc3339(lim_time:now()), + LimitChangeID = <>, + Change = #limiter_LimitChange{ + id = ID, + change_id = LimitChangeID + }, + {ok, {vector, #limiter_VectorClock{}}} = lim_client:hold(Change, Context, Client), + {ok, #limiter_Limit{}} = lim_client:get(ID, Context, Client). + +-spec commit_ok(config()) -> _. +commit_ok(C) -> + ID = <<"ID">>, + #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), + Context = #limiter_context_LimitContext{ + payment_processing = #limiter_context_ContextPaymentProcessing{ + op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, + invoice = #limiter_context_Invoice{ + created_at = <<"2000-01-01T00:00:00Z">>, + cost = #limiter_base_Cash{ + amount = 10, + currency = #limiter_base_CurrencyRef{symbolic_code = <<"RUB">>} + } + } + } + }, + Timestamp = lim_time:to_rfc3339(lim_time:now()), + LimitChangeID = <>, + Change = #limiter_LimitChange{ + id = ID, + change_id = LimitChangeID + }, + {ok, {vector, _}} = hold_and_commit(Change, Context, Client), + {ok, #limiter_Limit{}} = lim_client:get(ID, Context, Client). + +-spec rollback_ok(config()) -> _. +rollback_ok(C) -> + ID = <<"ID">>, + #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), + Context = #limiter_context_LimitContext{ + payment_processing = #limiter_context_ContextPaymentProcessing{ + op = {invoice_payment, #limiter_context_PaymentProcessingOperationInvoicePayment{}}, + invoice = #limiter_context_Invoice{ + effective_payment = #limiter_context_InvoicePayment{ + created_at = <<"2000-01-01T00:00:00Z">>, + cost = #limiter_base_Cash{ + amount = 10, + currency = #limiter_base_CurrencyRef{symbolic_code = <<"RUB">>} + }, + capture_cost = #limiter_base_Cash{ + amount = 0, + currency = #limiter_base_CurrencyRef{symbolic_code = <<"RUB">>} + } + } + } + } + }, + Timestamp = lim_time:to_rfc3339(lim_time:now()), + LimitChangeID = <>, + Change = #limiter_LimitChange{ + id = ID, + change_id = LimitChangeID + }, + {ok, {vector, _}} = hold_and_commit(Change, Context, Client), + {ok, #limiter_Limit{}} = lim_client:get(ID, Context, Client). + +-spec get_config_ok(config()) -> _. +get_config_ok(C) -> + ID = <<"ID">>, + #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), + {ok, #limiter_config_LimitConfig{}} = lim_client:get_config(ID, Client). + +%% + +hold_and_commit(Change, Context, Client) -> + {ok, {vector, _}} = lim_client:hold(Change, Context, Client), + {ok, {vector, _}} = lim_client:commit(Change, Context, Client). + +mock_exchange(Rational, C) -> + lim_mock:mock_services([{xrates, fun('GetConvertedAmount', _) -> {ok, Rational} end}], C). + +prepare_environment(ID, LimitName, _C) -> + Client = lim_client:new(), + Params = #limiter_cfg_LimitCreateParams{ + id = ID, + name = LimitName, + description = <<"description">>, + started_at = <<"2000-01-01T00:00:00Z">>, + body_type = {cash, #limiter_config_LimitBodyTypeCash{currency = <<"RUB">>}} + }, + {ok, LimitConfig} = lim_client:create_config(Params, Client), + #{config => LimitConfig, client => Client}. diff --git a/config/sys.config b/config/sys.config index 00510dda..17880989 100644 --- a/config/sys.config +++ b/config/sys.config @@ -13,8 +13,21 @@ {service_clients, #{ accounter => #{ url => <<"http://shumway:8022/accounter">> + }, + automaton => #{ + url => <<"http://machinegun:8022/v1/automaton">> + }, + xrates => #{ + url => <<"http://xrates:8022/xrates">> } }}, + + {exchange_factors, #{ + <<"DEFAULT">> => {1, 1}, + <<"USD">> => {105, 100}, + <<"EUR">> => {12, 10} + }}, + {protocol_opts, #{ % How much to wait for another request before closing a keepalive connection? (ms) request_timeout => 5000, @@ -42,6 +55,15 @@ }} ]}, + {scoper_event_handler_options, #{ + event_handler_opts => #{ + formatter_opts => #{ + max_length => 1000, + max_printable_string_length => 80 + } + } + }}, + {health_check, #{ % disk => {erl_health, disk , ["/", 99]}, % memory => {erl_health, cg_memory, [99]}, diff --git a/docker-compose.sh b/docker-compose.sh index 91f111bf..a2f63c6f 100755 --- a/docker-compose.sh +++ b/docker-compose.sh @@ -11,9 +11,24 @@ services: working_dir: $PWD command: /sbin/init depends_on: + machinegun: + condition: service_healthy shumway: condition: service_healthy + machinegun: + image: dr2.rbkmoney.com/rbkmoney/machinegun:0da2ffc23221e1e3f8557b03d48d11d2dd18fac0 + command: /opt/machinegun/bin/machinegun foreground + volumes: + - ./test/machinegun/config.yaml:/opt/machinegun/etc/config.yaml + - ./test/log/machinegun:/var/log/machinegun + - ./test/machinegun/cookie:/opt/machinegun/etc/cookie + healthcheck: + test: "curl http://localhost:8022/" + interval: 5s + timeout: 1s + retries: 10 + shumway: image: dr2.rbkmoney.com/rbkmoney/shumway:e946e83703e02f4359cd536b15fb94457f9bfb20 restart: unless-stopped diff --git a/elvis.config b/elvis.config index 34f20aae..5a524d32 100644 --- a/elvis.config +++ b/elvis.config @@ -20,7 +20,7 @@ {elvis_style, function_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*$"}}, {elvis_style, state_record_and_type}, {elvis_style, no_spec_with_records}, - {elvis_style, dont_repeat_yourself, #{min_complexity => 10}}, + {elvis_style, dont_repeat_yourself, #{min_complexity => 10, ignore => [lim_config_machine]}}, {elvis_style, no_debug_call, #{ignore => [elvis, elvis_utils]}} ] }, @@ -43,7 +43,7 @@ {elvis_style, function_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*$"}}, {elvis_style, state_record_and_type}, {elvis_style, no_spec_with_records}, - {elvis_style, dont_repeat_yourself, #{min_complexity => 10}}, + {elvis_style, dont_repeat_yourself, #{min_complexity => 10, ignore => [lim_turnover_SUITE]}}, {elvis_style, no_debug_call, #{ignore => [elvis, elvis_utils]}} ] }, diff --git a/rebar.config b/rebar.config index ad1f9d33..86e90744 100644 --- a/rebar.config +++ b/rebar.config @@ -31,6 +31,20 @@ {branch, "release/erlang/master"} } }, + {limiter_proto, + {git, "git@github.com:rbkmoney/limiter-proto.git", + {branch, "master"} + } + }, + {xrates_proto, + {git, "git@github.com:rbkmoney/xrates-proto.git", + {branch, "master"} + } + }, + {machinery, + {git, "https://github.com/rbkmoney/machinery.git", + {branch, "master"}} + }, {erl_health, {git, "https://github.com/rbkmoney/erlang-health.git", {branch, "master"}} diff --git a/rebar.lock b/rebar.lock index 94d4f019..6b7d09bb 100644 --- a/rebar.lock +++ b/rebar.lock @@ -33,7 +33,19 @@ 0}, {<<"idna">>,{pkg,<<"idna">>,<<"6.0.0">>},2}, {<<"jsx">>,{pkg,<<"jsx">>,<<"3.0.0">>},1}, + {<<"limiter_proto">>, + {git,"git@github.com:rbkmoney/limiter-proto.git", + {ref,"84cc6b7355aa838c2a91dfab64d000f57ff63bf7"}}, + 0}, + {<<"machinery">>, + {git,"https://github.com/rbkmoney/machinery.git", + {ref,"db7c94b9913451e9558afa19f2fe77bf48d391da"}}, + 0}, {<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},2}, + {<<"mg_proto">>, + {git,"https://github.com/rbkmoney/machinegun_proto.git", + {ref,"d814d6948d4ff13f6f41d12c6613f59c805750b2"}}, + 1}, {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.2.0">>},2}, {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.0">>},3}, {<<"prometheus">>,{pkg,<<"prometheus">>,<<"4.6.0">>},0}, @@ -57,6 +69,10 @@ {<<"woody">>, {git,"https://github.com/rbkmoney/woody_erlang.git", {ref,"58f56b462429ab1fee65e1bdb34b73512406ba00"}}, + 0}, + {<<"xrates_proto">>, + {git,"git@github.com:rbkmoney/xrates-proto.git", + {ref,"66906cd0a8ee9a00fb447f3c3e5b09d3c6fab942"}}, 0}]}. [ {pkg_hash,[ diff --git a/test/machinegun/config.yaml b/test/machinegun/config.yaml new file mode 100644 index 00000000..319ce7f8 --- /dev/null +++ b/test/machinegun/config.yaml @@ -0,0 +1,17 @@ +service_name: machinegun + +erlang: + secret_cookie_file: "/opt/machinegun/etc/cookie" + +namespaces: + lim/config_v1: + processor: + url: http://limiter:8022/v1/stateproc/lim/config_v1 + pool_size: 500 + lim/range_v1: + processor: + url: http://limiter:8022/v1/stateproc/lim/range_v1 + pool_size: 500 + +storage: + type: memory diff --git a/test/machinegun/cookie b/test/machinegun/cookie new file mode 100644 index 00000000..24b9aa4a --- /dev/null +++ b/test/machinegun/cookie @@ -0,0 +1 @@ +BENDER-COOKIE-MONSTER From c1aa30005f755dd59e4eff8e1904e7e90f598259 Mon Sep 17 00:00:00 2001 From: 0x42 Date: Tue, 1 Jun 2021 10:41:39 +0300 Subject: [PATCH 06/54] Bump to damsel rbkmoney/damsel@d4b40ea --- rebar.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebar.lock b/rebar.lock index 6b7d09bb..40230908 100644 --- a/rebar.lock +++ b/rebar.lock @@ -35,7 +35,7 @@ {<<"jsx">>,{pkg,<<"jsx">>,<<"3.0.0">>},1}, {<<"limiter_proto">>, {git,"git@github.com:rbkmoney/limiter-proto.git", - {ref,"84cc6b7355aa838c2a91dfab64d000f57ff63bf7"}}, + {ref,"d4b40ead589dd4dbd9d442397239c635d2a8382e"}}, 0}, {<<"machinery">>, {git,"https://github.com/rbkmoney/machinery.git", From d6561f9ddfefec8d456bc7360e6bc48219a64077 Mon Sep 17 00:00:00 2001 From: 0x42 Date: Mon, 7 Jun 2021 14:37:33 +0300 Subject: [PATCH 07/54] fix bug calc shard id(same usec); add in config day limit --- apps/limiter/src/lim_config_machine.erl | 4 ++-- apps/limiter/src/lim_configurator.erl | 9 +++++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index 3b1345b6..c8b81724 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -415,8 +415,8 @@ marshal_timestamp(DateTime) -> calculate_shard_id(Timestamp, Config) -> StartedAt = started_at(Config), ShardSize = shard_size(Config), - {StartDateTime, USec} = lim_range_codec:parse_timestamp(StartedAt), - {CurrentDateTime, USec} = lim_range_codec:parse_timestamp(Timestamp), + {StartDateTime, _USec0} = lim_range_codec:parse_timestamp(StartedAt), + {CurrentDateTime, _USec1} = lim_range_codec:parse_timestamp(Timestamp), case time_range_type(Config) of {calendar, Range} -> Units = calculate_time_units(Range, CurrentDateTime, StartDateTime), diff --git a/apps/limiter/src/lim_configurator.erl b/apps/limiter/src/lim_configurator.erl index 898d60e2..02b3d42b 100644 --- a/apps/limiter/src/lim_configurator.erl +++ b/apps/limiter/src/lim_configurator.erl @@ -62,6 +62,15 @@ handle_function_('Get', {LimitID}, LimitContext, _Opts) -> woody_error:raise(business, #limiter_cfg_LimitConfigNotFound{}) end. +mk_limit_config(<<"ShopDayTurnover">>) -> + {ok, #{ + processor_type => <<"TurnoverProcessor">>, + type => turnover, + scope => {scope, shop}, + shard_size => 12, + context_type => payment_processing, + time_range_type => {calendar, day} + }}; mk_limit_config(<<"ShopMonthTurnover">>) -> {ok, #{ processor_type => <<"TurnoverProcessor">>, From 9e83224506461da9c94ba4cfcea26314f3261603 Mon Sep 17 00:00:00 2001 From: 0x42 Date: Mon, 7 Jun 2021 15:58:21 +0300 Subject: [PATCH 08/54] fix same usec --- apps/limiter/src/lim_config_machine.erl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index c8b81724..53784e79 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -269,8 +269,8 @@ get_handler(ID, LimitContext) -> -spec calculate_time_range(timestamp(), config()) -> time_range(). calculate_time_range(Timestamp, Config) -> StartedAt = started_at(Config), - {StartDateTime, USec} = lim_range_codec:parse_timestamp(StartedAt), - {CurrentDateTime, USec} = lim_range_codec:parse_timestamp(Timestamp), + {StartDateTime, _USec0} = lim_range_codec:parse_timestamp(StartedAt), + {CurrentDateTime, _USec1} = lim_range_codec:parse_timestamp(Timestamp), CurrentSec = calendar:datetime_to_gregorian_seconds(CurrentDateTime), case time_range_type(Config) of {calendar, Range} -> From 4beba47b5ce66edca07518442a071f8254202d1a Mon Sep 17 00:00:00 2001 From: 0x42 Date: Mon, 21 Jun 2021 13:04:50 +0300 Subject: [PATCH 09/54] add subtraction behaviour in limit amount computation --- apps/limiter/src/lim_body.erl | 4 +- apps/limiter/src/lim_config_codec.erl | 53 +++++++++++++++++-- apps/limiter/src/lim_config_machine.erl | 18 ++++++- apps/limiter/src/lim_configurator.erl | 3 +- apps/limiter/src/lim_turnover_processor.erl | 26 ++++++--- apps/limiter/test/lim_ct_helper.hrl | 49 +++++++++++++++++ apps/limiter/test/lim_turnover_SUITE.erl | 58 +++++++++++++-------- rebar.config | 3 +- rebar.lock | 2 +- 9 files changed, 179 insertions(+), 37 deletions(-) create mode 100644 apps/limiter/test/lim_ct_helper.hrl diff --git a/apps/limiter/src/lim_body.erl b/apps/limiter/src/lim_body.erl index 903d448f..26bf3d4e 100644 --- a/apps/limiter/src/lim_body.erl +++ b/apps/limiter/src/lim_body.erl @@ -30,7 +30,7 @@ get_body(BodyType, Config = #{body_type := {cash, ConfigCurrency}}, LimitContext case get_body_for_operation(BodyType, Operation, Config, LimitContext) of {ok, {cash, #{currency := ConfigCurrency}}} = Result -> Result; - {ok, {cash, #{amount := Amount, currency := Currency}}} -> + {ok, {cash, #{amount := Amount, currency := Currency}} = _Body} -> case lim_rates:get_converted_amount({Amount, Currency}, Config, LimitContext) of {ok, ConvertedAmount} -> {ok, create_body_from_cash(ConvertedAmount, ConfigCurrency)}; @@ -57,7 +57,7 @@ get_body_for_operation(full, invoice_payment_adjustment, Config, LimitContext) - lim_context:get_from_context(ContextType, cost, invoice_payment, LimitContext); get_body_for_operation(full, invoice_payment_refund, Config, LimitContext) -> ContextType = lim_config_machine:context_type(Config), - lim_context:get_from_context(ContextType, cost, invoice_payment, LimitContext); + lim_context:get_from_context(ContextType, cost, invoice_payment_refund, LimitContext); get_body_for_operation(full, invoice_payment_chargeback = Operation, Config, LimitContext) -> ContextType = lim_config_machine:context_type(Config), lim_context:get_from_context(ContextType, body, Operation, LimitContext); diff --git a/apps/limiter/src/lim_config_codec.erl b/apps/limiter/src/lim_config_codec.erl index 5a145f12..ab5f78b7 100644 --- a/apps/limiter/src/lim_config_codec.erl +++ b/apps/limiter/src/lim_config_codec.erl @@ -59,9 +59,31 @@ marshal_config(Config) -> time_range_type = marshal_time_range_type(lim_config_machine:time_range_type(Config)), context_type = marshal_context_type(lim_config_machine:context_type(Config)), type = maybe_apply(lim_config_machine:type(Config), fun marshal_type/1), - scope = maybe_apply(lim_config_machine:scope(Config), fun marshal_scope/1) + scope = maybe_apply(lim_config_machine:scope(Config), fun marshal_scope/1), + op_behaviour = maybe_apply(lim_config_machine:op_behaviour(Config), fun marshal_op_behaviour/1) }. +marshal_op_behaviour(OpBehaviour) -> + Invoice = maps:get(invoice, OpBehaviour, undefined), + Payment = maps:get(invoice_payment, OpBehaviour, undefined), + Adjustment = maps:get(invoice_adjustment, OpBehaviour, undefined), + PaymentAdjustment = maps:get(invoice_payment_adjustment, OpBehaviour, undefined), + PaymentRefund = maps:get(invoice_payment_refund, OpBehaviour, undefined), + PaymentChargeback = maps:get(invoice_payment_adjustment, OpBehaviour, undefined), + #limiter_config_OperationLimitBehaviour{ + invoice = maybe_apply(Invoice, fun marshal_behaviour/1), + invoice_adjustment = maybe_apply(Adjustment, fun marshal_behaviour/1), + invoice_payment = maybe_apply(Payment, fun marshal_behaviour/1), + invoice_payment_adjustment = maybe_apply(PaymentAdjustment, fun marshal_behaviour/1), + invoice_payment_refund = maybe_apply(PaymentRefund, fun marshal_behaviour/1), + invoice_payment_chargeback = maybe_apply(PaymentChargeback, fun marshal_behaviour/1) + }. + +marshal_behaviour(subtraction) -> + {subtraction, #limiter_config_Subtraction{}}; +marshal_behaviour(addition) -> + {addition, #limiter_config_Addition{}}. + marshal_body_type(amount) -> {amount, #limiter_config_LimitBodyTypeAmount{}}; marshal_body_type({cash, Currency}) -> @@ -139,7 +161,8 @@ unmarshal_config(#limiter_config_LimitConfig{ time_range_type = TimeRangeType, context_type = ContextType, type = Type, - scope = Scope + scope = Scope, + op_behaviour = OpBehaviour }) -> genlib_map:compact(#{ id => ID, @@ -152,9 +175,33 @@ unmarshal_config(#limiter_config_LimitConfig{ context_type => unmarshal_context_type(ContextType), type => maybe_apply(Type, fun unmarshal_type/1), scope => maybe_apply(Scope, fun unmarshal_scope/1), - description => Description + description => Description, + op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1) }). +unmarshal_op_behaviour(OpBehaviour) -> + #limiter_config_OperationLimitBehaviour{ + invoice = Invoice, + invoice_adjustment = Adjustment, + invoice_payment = Payment, + invoice_payment_adjustment = PaymentAdjustment, + invoice_payment_refund = Refund, + invoice_payment_chargeback = Chargeback + } = OpBehaviour, + genlib_map:compact(#{ + invoice => maybe_apply(Invoice, fun unmarshal_behaviour/1), + invoice_adjustment => maybe_apply(Adjustment, fun unmarshal_behaviour/1), + invoice_payment => maybe_apply(Payment, fun unmarshal_behaviour/1), + invoice_payment_adjustment => maybe_apply(PaymentAdjustment, fun unmarshal_behaviour/1), + invoice_payment_refund => maybe_apply(Refund, fun unmarshal_behaviour/1), + invoice_payment_chargeback => maybe_apply(Chargeback, fun unmarshal_behaviour/1) + }). + +unmarshal_behaviour({subtraction, #limiter_config_Subtraction{}}) -> + subtraction; +unmarshal_behaviour({addition, #limiter_config_Addition{}}) -> + addition. + -spec unmarshal_body_type(encoded_value()) -> decoded_value(). unmarshal_body_type({amount, #limiter_config_LimitBodyTypeAmount{}}) -> amount; diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index 3b1345b6..cc1424d6 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -16,6 +16,7 @@ -export([type/1]). -export([scope/1]). -export([context_type/1]). +-export([op_behaviour/1]). %% API @@ -62,7 +63,8 @@ context_type := context_type(), type => limit_type(), scope => limit_scope(), - description => description() + description => description(), + op_behaviour => op_behaviour() }. -type create_params() :: #{ @@ -77,6 +79,14 @@ description => description() }. +-type op_behaviour() :: #{operation_type() := addition | subtraction}. +-type operation_type() :: invoice + | invoice_adjustment + | invoice_payment + | invoice_payment_adjustment + | invoice_payment_refund + | invoice_payment_chargeback. + -type lim_id() :: lim_limiter_thrift:'LimitID'(). -type lim_change() :: lim_limiter_thrift:'LimitChange'(). -type limit() :: lim_limiter_thrift:'Limit'(). @@ -209,6 +219,12 @@ scope(_) -> context_type(#{context_type := Value}) -> Value. +-spec op_behaviour(config()) -> lim_maybe:maybe(op_behaviour()). +op_behaviour(#{op_behaviour := Value}) -> + Value; +op_behaviour(_) -> + undefined. + %% -spec start(lim_id(), create_params(), lim_context()) -> {ok, config()}. diff --git a/apps/limiter/src/lim_configurator.erl b/apps/limiter/src/lim_configurator.erl index 898d60e2..6cda1fed 100644 --- a/apps/limiter/src/lim_configurator.erl +++ b/apps/limiter/src/lim_configurator.erl @@ -69,7 +69,8 @@ mk_limit_config(<<"ShopMonthTurnover">>) -> scope => {scope, shop}, shard_size => 12, context_type => payment_processing, - time_range_type => {calendar, month} + time_range_type => {calendar, month}, + op_behaviour => #{invoice_payment_refund => subtraction} }}; mk_limit_config(<<"PartyMonthTurnover">>) -> {ok, #{ diff --git a/apps/limiter/src/lim_turnover_processor.erl b/apps/limiter/src/lim_turnover_processor.erl index 851e953d..fae35475 100644 --- a/apps/limiter/src/lim_turnover_processor.erl +++ b/apps/limiter/src/lim_turnover_processor.erl @@ -84,7 +84,8 @@ hold(LimitChange = #limiter_LimitChange{id = LimitID}, Config = #{body_type := B {ok, #{account_id_from := AccountIDFrom, account_id_to := AccountIDTo}} = lim_range_machine:ensure_range_exist_in_state(TimeRange, LimitRangeState, LimitContext), Postings = lim_p_transfer:construct_postings(AccountIDFrom, AccountIDTo, Body), - lim_accounting:hold(construct_plan_id(LimitChange), {1, Postings}, LimitContext) + Postings1 = maybe_inverse_posting(Postings, LimitContext, Config), + lim_accounting:hold(construct_plan_id(LimitChange), {1, Postings1}, LimitContext) end). -spec commit(lim_change(), config(), lim_context()) -> ok | {error, commit_error()}. @@ -130,16 +131,27 @@ partial_commit(PartialBody, LimitChange = #limiter_LimitChange{id = LimitID}, Co TimeRange = lim_config_machine:calculate_time_range(Timestamp, Config), {ok, #{account_id_from := AccountIDFrom, account_id_to := AccountIDTo}} = lim_range_machine:get_range(TimeRange, LimitRangeState), - - PartialPostings = lim_p_transfer:construct_postings(AccountIDFrom, AccountIDTo, PartialBody), - FullPostings = lim_p_transfer:construct_postings(AccountIDFrom, AccountIDTo, FullBody), - NewBatchList = [{2, lim_p_transfer:reverse_postings(FullPostings)} | [{3, PartialPostings}]], - + PartialPostings0 = lim_p_transfer:construct_postings(AccountIDFrom, AccountIDTo, PartialBody), + FullPostings0 = lim_p_transfer:construct_postings(AccountIDFrom, AccountIDTo, FullBody), + PartialPostings1 = maybe_inverse_posting(PartialPostings0, LimitContext, Config), + FullPostings1 = maybe_inverse_posting(FullPostings0, LimitContext, Config), + NewBatchList = [{2, lim_p_transfer:reverse_postings(FullPostings1)} | [{3, PartialPostings1}]], PlanID = construct_plan_id(LimitChange), unwrap(lim_accounting:plan(PlanID, NewBatchList, LimitContext)), - unwrap(lim_accounting:commit(PlanID, [{1, FullPostings} | NewBatchList], LimitContext)) + unwrap(lim_accounting:commit(PlanID, [{1, FullPostings1} | NewBatchList], LimitContext)) end). +maybe_inverse_posting(Posting, LimitContext, #{op_behaviour := ComputationConfig}) -> + {ok, Operation} = lim_context:get_operation(payment_processing, LimitContext), + case maps:get(Operation, ComputationConfig, undefined) of + subtraction -> + lim_p_transfer:reverse_postings(Posting); + Type when Type =:= undefined orelse Type =:= additional -> + Posting + end; +maybe_inverse_posting(Body, _LimitContext, _Config) -> + Body. + assert_partial_body( {cash, #{amount := Partial, currency := Currency}}, {cash, #{amount := Full, currency := Currency}} diff --git a/apps/limiter/test/lim_ct_helper.hrl b/apps/limiter/test/lim_ct_helper.hrl new file mode 100644 index 00000000..4640056b --- /dev/null +++ b/apps/limiter/test/lim_ct_helper.hrl @@ -0,0 +1,49 @@ +-ifndef(__limiter_ct_helper__). +-define(__limiter_ct_helper__, 42). + +-include_lib("limiter_proto/include/lim_configurator_thrift.hrl"). + +-define(cash(Amount), #limiter_base_Cash{ + amount = Amount, + currency = #limiter_base_CurrencyRef{symbolic_code = <<"RUB">>} +}). + + +-define(ctx_invoice_payment(Cost, CaptureCost), ?ctx_invoice_payment(undefined, undefined, Cost, CaptureCost)). + +-define(ctx_invoice_payment(OwnerID, ShopID, Cost, CaptureCost), #limiter_context_LimitContext{ + payment_processing = #limiter_context_ContextPaymentProcessing{ + op = {invoice_payment, #limiter_context_PaymentProcessingOperationInvoicePayment{}}, + invoice = #limiter_context_Invoice{ + owner_id = OwnerID, + shop_id = ShopID, + effective_payment = #limiter_context_InvoicePayment{ + created_at = <<"2000-01-01T00:00:00Z">>, + cost = Cost, + capture_cost = CaptureCost + } + } + } +}). + +-define(ctx_invoice_payment_refund(OwnerID, ShopID, Cost, CaptureCost, RefundCost), #limiter_context_LimitContext{ + payment_processing = #limiter_context_ContextPaymentProcessing{ + op = {invoice_payment_refund, #limiter_context_PaymentProcessingOperationInvoicePaymentRefund{}}, + invoice = #limiter_context_Invoice{ + owner_id = OwnerID, + shop_id = ShopID, + effective_payment = #limiter_context_InvoicePayment{ + created_at = <<"2000-01-01T00:00:00Z">>, + cost = Cost, + capture_cost = CaptureCost, + effective_refund = #limiter_context_InvoicePaymentRefund{ + cost = RefundCost, + created_at = <<"2000-01-01T00:00:00Z">> + + } + } + } + } +}). + +-endif. diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index f3ce6423..f172c852 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -2,6 +2,7 @@ -include_lib("stdlib/include/assert.hrl"). -include_lib("common_test/include/ct.hrl"). +-include_lib("lim_ct_helper.hrl"). -include_lib("limiter_proto/include/lim_configurator_thrift.hrl"). -include_lib("xrates_proto/include/xrates_rate_thrift.hrl"). @@ -22,6 +23,7 @@ -export([hold_ok/1]). -export([commit_ok/1]). -export([rollback_ok/1]). +-export([refund_ok/1]). -export([get_config_ok/1]). -type test_case_name() :: atom(). @@ -48,7 +50,8 @@ groups() -> hold_ok, commit_ok, rollback_ok, - get_config_ok + get_config_ok, + refund_ok ]} ]. @@ -266,32 +269,45 @@ commit_ok(C) -> rollback_ok(C) -> ID = <<"ID">>, #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), - Context = #limiter_context_LimitContext{ - payment_processing = #limiter_context_ContextPaymentProcessing{ - op = {invoice_payment, #limiter_context_PaymentProcessingOperationInvoicePayment{}}, - invoice = #limiter_context_Invoice{ - effective_payment = #limiter_context_InvoicePayment{ - created_at = <<"2000-01-01T00:00:00Z">>, - cost = #limiter_base_Cash{ - amount = 10, - currency = #limiter_base_CurrencyRef{symbolic_code = <<"RUB">>} - }, - capture_cost = #limiter_base_Cash{ - amount = 0, - currency = #limiter_base_CurrencyRef{symbolic_code = <<"RUB">>} - } - } - } - } - }, + Context0 = ?ctx_invoice_payment(?cash(10), ?cash(10)), + Context1 = ?ctx_invoice_payment(?cash(10), ?cash(0)), + Timestamp = lim_time:to_rfc3339(lim_time:now()), LimitChangeID = <>, Change = #limiter_LimitChange{ id = ID, change_id = LimitChangeID }, - {ok, {vector, _}} = hold_and_commit(Change, Context, Client), - {ok, #limiter_Limit{}} = lim_client:get(ID, Context, Client). + {ok, {vector, _}} = lim_client:hold(Change, Context0, Client), + {ok, {vector, _}} = lim_client:commit(Change, Context1, Client). + +-spec refund_ok(config()) -> _. +refund_ok(C) -> + ID = lim_time:to_rfc3339(lim_time:now()), + OwnerID = <<"WWWcool Ltd">>, + ShopID = <<"shop">>, + #{client := Client} = _LimitConfig = prepare_environment(ID, <<"ShopMonthTurnover">>, C), + Context0 = ?ctx_invoice_payment(OwnerID, ShopID, ?cash(15), ?cash(15)), + RefundContext1 = ?ctx_invoice_payment_refund(OwnerID, ShopID, ?cash(10), ?cash(10), ?cash(10)), + Timestamp = lim_time:to_rfc3339(lim_time:now()), + LimitChangeID = <>, + + Change = #limiter_LimitChange{ + id = ID, + change_id = LimitChangeID + }, + {ok, {vector, _}} = hold_and_commit(Change, Context0, Client), + + Timestamp2 = lim_time:to_rfc3339(lim_time:now()), + LimitChangeID2 = <>, + Change2 = #limiter_LimitChange{ + id = ID, + change_id = LimitChangeID2 + }, + + {ok, {vector, _}} = hold_and_commit(Change2, RefundContext1, Client), + {ok, #limiter_Limit{} = Limit2} = lim_client:get(ID, RefundContext1, Client), + ?assertEqual(Limit2#limiter_Limit.amount, 5). -spec get_config_ok(config()) -> _. get_config_ok(C) -> diff --git a/rebar.config b/rebar.config index 86e90744..c066132d 100644 --- a/rebar.config +++ b/rebar.config @@ -33,7 +33,8 @@ }, {limiter_proto, {git, "git@github.com:rbkmoney/limiter-proto.git", - {branch, "master"} + % {branch, "master"} + {branch, "ED-181/ft/add_computation_type_in_config"} } }, {xrates_proto, diff --git a/rebar.lock b/rebar.lock index 6b7d09bb..cf06bf18 100644 --- a/rebar.lock +++ b/rebar.lock @@ -35,7 +35,7 @@ {<<"jsx">>,{pkg,<<"jsx">>,<<"3.0.0">>},1}, {<<"limiter_proto">>, {git,"git@github.com:rbkmoney/limiter-proto.git", - {ref,"84cc6b7355aa838c2a91dfab64d000f57ff63bf7"}}, + {ref,"a53f50853fc888898ca25dbdc0089a4851213be6"}}, 0}, {<<"machinery">>, {git,"https://github.com/rbkmoney/machinery.git", From 5a86c525be096817e3f9675074f76b5a2590da2c Mon Sep 17 00:00:00 2001 From: 0x42 Date: Mon, 21 Jun 2021 13:18:30 +0300 Subject: [PATCH 10/54] format --- apps/limiter/src/lim_config_machine.erl | 13 +++++++------ apps/limiter/test/lim_ct_helper.hrl | 2 -- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index a5786b05..0b2482fe 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -80,12 +80,13 @@ }. -type op_behaviour() :: #{operation_type() := addition | subtraction}. --type operation_type() :: invoice - | invoice_adjustment - | invoice_payment - | invoice_payment_adjustment - | invoice_payment_refund - | invoice_payment_chargeback. +-type operation_type() :: + invoice + | invoice_adjustment + | invoice_payment + | invoice_payment_adjustment + | invoice_payment_refund + | invoice_payment_chargeback. -type lim_id() :: lim_limiter_thrift:'LimitID'(). -type lim_change() :: lim_limiter_thrift:'LimitChange'(). diff --git a/apps/limiter/test/lim_ct_helper.hrl b/apps/limiter/test/lim_ct_helper.hrl index 4640056b..f421210a 100644 --- a/apps/limiter/test/lim_ct_helper.hrl +++ b/apps/limiter/test/lim_ct_helper.hrl @@ -8,7 +8,6 @@ currency = #limiter_base_CurrencyRef{symbolic_code = <<"RUB">>} }). - -define(ctx_invoice_payment(Cost, CaptureCost), ?ctx_invoice_payment(undefined, undefined, Cost, CaptureCost)). -define(ctx_invoice_payment(OwnerID, ShopID, Cost, CaptureCost), #limiter_context_LimitContext{ @@ -39,7 +38,6 @@ effective_refund = #limiter_context_InvoicePaymentRefund{ cost = RefundCost, created_at = <<"2000-01-01T00:00:00Z">> - } } } From fb6c6bc65e5ea6a084e314f5d5ae4ab7726d32d5 Mon Sep 17 00:00:00 2001 From: 0x42 Date: Tue, 22 Jun 2021 15:12:03 +0300 Subject: [PATCH 11/54] upd proto, param op_behaviour set from api --- apps/limiter/src/lim_body.erl | 2 +- apps/limiter/src/lim_config_codec.erl | 30 ++++++------------------ apps/limiter/src/lim_config_machine.erl | 3 ++- apps/limiter/src/lim_configurator.erl | 16 ++++++++----- apps/limiter/test/lim_turnover_SUITE.erl | 5 +++- rebar.config | 3 +-- rebar.lock | 2 +- 7 files changed, 26 insertions(+), 35 deletions(-) diff --git a/apps/limiter/src/lim_body.erl b/apps/limiter/src/lim_body.erl index 26bf3d4e..6c3ab28c 100644 --- a/apps/limiter/src/lim_body.erl +++ b/apps/limiter/src/lim_body.erl @@ -57,7 +57,7 @@ get_body_for_operation(full, invoice_payment_adjustment, Config, LimitContext) - lim_context:get_from_context(ContextType, cost, invoice_payment, LimitContext); get_body_for_operation(full, invoice_payment_refund, Config, LimitContext) -> ContextType = lim_config_machine:context_type(Config), - lim_context:get_from_context(ContextType, cost, invoice_payment_refund, LimitContext); + lim_context:get_from_context(ContextType, cost, invoice_payment, LimitContext); get_body_for_operation(full, invoice_payment_chargeback = Operation, Config, LimitContext) -> ContextType = lim_config_machine:context_type(Config), lim_context:get_from_context(ContextType, body, Operation, LimitContext); diff --git a/apps/limiter/src/lim_config_codec.erl b/apps/limiter/src/lim_config_codec.erl index ab5f78b7..7a99c2a3 100644 --- a/apps/limiter/src/lim_config_codec.erl +++ b/apps/limiter/src/lim_config_codec.erl @@ -6,6 +6,8 @@ -export([unmarshal/2]). -export([marshal_config/1]). -export([unmarshal_body_type/1]). +-export([unmarshal_op_behaviour/1]). +-export([maybe_apply/2]). %% Types @@ -17,6 +19,7 @@ -type decoded_value() :: decoded_value(any()). -type decoded_value(T) :: T. +-spec maybe_apply(any(), function()) -> any(). maybe_apply(undefined, _) -> undefined; maybe_apply(Value, Fun) -> @@ -64,19 +67,9 @@ marshal_config(Config) -> }. marshal_op_behaviour(OpBehaviour) -> - Invoice = maps:get(invoice, OpBehaviour, undefined), - Payment = maps:get(invoice_payment, OpBehaviour, undefined), - Adjustment = maps:get(invoice_adjustment, OpBehaviour, undefined), - PaymentAdjustment = maps:get(invoice_payment_adjustment, OpBehaviour, undefined), PaymentRefund = maps:get(invoice_payment_refund, OpBehaviour, undefined), - PaymentChargeback = maps:get(invoice_payment_adjustment, OpBehaviour, undefined), #limiter_config_OperationLimitBehaviour{ - invoice = maybe_apply(Invoice, fun marshal_behaviour/1), - invoice_adjustment = maybe_apply(Adjustment, fun marshal_behaviour/1), - invoice_payment = maybe_apply(Payment, fun marshal_behaviour/1), - invoice_payment_adjustment = maybe_apply(PaymentAdjustment, fun marshal_behaviour/1), - invoice_payment_refund = maybe_apply(PaymentRefund, fun marshal_behaviour/1), - invoice_payment_chargeback = maybe_apply(PaymentChargeback, fun marshal_behaviour/1) + invoice_payment_refund = maybe_apply(PaymentRefund, fun marshal_behaviour/1) }. marshal_behaviour(subtraction) -> @@ -179,22 +172,13 @@ unmarshal_config(#limiter_config_LimitConfig{ op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1) }). +-spec unmarshal_op_behaviour(encoded_value()) -> decoded_value(). unmarshal_op_behaviour(OpBehaviour) -> #limiter_config_OperationLimitBehaviour{ - invoice = Invoice, - invoice_adjustment = Adjustment, - invoice_payment = Payment, - invoice_payment_adjustment = PaymentAdjustment, - invoice_payment_refund = Refund, - invoice_payment_chargeback = Chargeback + invoice_payment_refund = Refund } = OpBehaviour, genlib_map:compact(#{ - invoice => maybe_apply(Invoice, fun unmarshal_behaviour/1), - invoice_adjustment => maybe_apply(Adjustment, fun unmarshal_behaviour/1), - invoice_payment => maybe_apply(Payment, fun unmarshal_behaviour/1), - invoice_payment_adjustment => maybe_apply(PaymentAdjustment, fun unmarshal_behaviour/1), - invoice_payment_refund => maybe_apply(Refund, fun unmarshal_behaviour/1), - invoice_payment_chargeback => maybe_apply(Chargeback, fun unmarshal_behaviour/1) + invoice_payment_refund => maybe_apply(Refund, fun unmarshal_behaviour/1) }). unmarshal_behaviour({subtraction, #limiter_config_Subtraction{}}) -> diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index 0b2482fe..ae01ce7a 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -76,7 +76,8 @@ context_type := context_type(), type => limit_type(), scope => limit_scope(), - description => description() + description => description(), + op_behaviour => op_behaviour() }. -type op_behaviour() :: #{operation_type() := addition | subtraction}. diff --git a/apps/limiter/src/lim_configurator.erl b/apps/limiter/src/lim_configurator.erl index 5594e415..b6dd4166 100644 --- a/apps/limiter/src/lim_configurator.erl +++ b/apps/limiter/src/lim_configurator.erl @@ -30,7 +30,8 @@ handle_function_( name = Name, description = Description, started_at = StartedAt, - body_type = BodyType + body_type = BodyType, + op_behaviour = OpBehaviour }}, LimitContext, _Opts @@ -39,11 +40,15 @@ handle_function_( {ok, Config} -> {ok, LimitConfig} = lim_config_machine:start( ID, - Config#{ + genlib_map:compact(Config#{ description => Description, started_at => StartedAt, - body_type => lim_config_codec:unmarshal_body_type(BodyType) - }, + body_type => lim_config_codec:unmarshal_body_type(BodyType), + op_behaviour => lim_config_codec:maybe_apply( + OpBehaviour, + fun lim_config_codec:unmarshal_op_behaviour/1 + ) + }), LimitContext ), {ok, lim_config_codec:marshal_config(LimitConfig)}; @@ -69,8 +74,7 @@ mk_limit_config(<<"ShopDayTurnover">>) -> scope => {scope, shop}, shard_size => 7, context_type => payment_processing, - time_range_type => {calendar, day}, - op_behaviour => #{invoice_payment_refund => subtraction} + time_range_type => {calendar, day} }}; mk_limit_config(<<"ShopMonthTurnover">>) -> {ok, #{ diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index f5f5fd05..f34af0c1 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -331,7 +331,10 @@ prepare_environment(ID, LimitName, _C) -> name = LimitName, description = <<"description">>, started_at = <<"2000-01-01T00:00:00Z">>, - body_type = {cash, #limiter_config_LimitBodyTypeCash{currency = <<"RUB">>}} + body_type = {cash, #limiter_config_LimitBodyTypeCash{currency = <<"RUB">>}}, + op_behaviour = #limiter_config_OperationLimitBehaviour{ + invoice_payment_refund = {subtraction, #limiter_config_Subtraction{}} + } }, {ok, LimitConfig} = lim_client:create_config(Params, Client), #{config => LimitConfig, client => Client}. diff --git a/rebar.config b/rebar.config index c066132d..86e90744 100644 --- a/rebar.config +++ b/rebar.config @@ -33,8 +33,7 @@ }, {limiter_proto, {git, "git@github.com:rbkmoney/limiter-proto.git", - % {branch, "master"} - {branch, "ED-181/ft/add_computation_type_in_config"} + {branch, "master"} } }, {xrates_proto, diff --git a/rebar.lock b/rebar.lock index cf06bf18..1606da5d 100644 --- a/rebar.lock +++ b/rebar.lock @@ -35,7 +35,7 @@ {<<"jsx">>,{pkg,<<"jsx">>,<<"3.0.0">>},1}, {<<"limiter_proto">>, {git,"git@github.com:rbkmoney/limiter-proto.git", - {ref,"a53f50853fc888898ca25dbdc0089a4851213be6"}}, + {ref,"9c0653ff7281ff443f515d2fddf9673fe837a5be"}}, 0}, {<<"machinery">>, {git,"https://github.com/rbkmoney/machinery.git", From 731859da801b322eea6fc5f6799fba4f16983878 Mon Sep 17 00:00:00 2001 From: 0x42 Date: Wed, 23 Jun 2021 11:39:01 +0300 Subject: [PATCH 12/54] delete junk code, refactor --- apps/limiter/src/lim_body.erl | 2 +- apps/limiter/src/lim_config_machine.erl | 8 +------- apps/limiter/src/lim_turnover_processor.erl | 10 +++++----- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/apps/limiter/src/lim_body.erl b/apps/limiter/src/lim_body.erl index 6c3ab28c..903d448f 100644 --- a/apps/limiter/src/lim_body.erl +++ b/apps/limiter/src/lim_body.erl @@ -30,7 +30,7 @@ get_body(BodyType, Config = #{body_type := {cash, ConfigCurrency}}, LimitContext case get_body_for_operation(BodyType, Operation, Config, LimitContext) of {ok, {cash, #{currency := ConfigCurrency}}} = Result -> Result; - {ok, {cash, #{amount := Amount, currency := Currency}} = _Body} -> + {ok, {cash, #{amount := Amount, currency := Currency}}} -> case lim_rates:get_converted_amount({Amount, Currency}, Config, LimitContext) of {ok, ConvertedAmount} -> {ok, create_body_from_cash(ConvertedAmount, ConfigCurrency)}; diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index ae01ce7a..7ef93af5 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -81,13 +81,7 @@ }. -type op_behaviour() :: #{operation_type() := addition | subtraction}. --type operation_type() :: - invoice - | invoice_adjustment - | invoice_payment - | invoice_payment_adjustment - | invoice_payment_refund - | invoice_payment_chargeback. +-type operation_type() :: invoice_payment_refund. -type lim_id() :: lim_limiter_thrift:'LimitID'(). -type lim_change() :: lim_limiter_thrift:'LimitChange'(). diff --git a/apps/limiter/src/lim_turnover_processor.erl b/apps/limiter/src/lim_turnover_processor.erl index fae35475..2ef8933d 100644 --- a/apps/limiter/src/lim_turnover_processor.erl +++ b/apps/limiter/src/lim_turnover_processor.erl @@ -84,7 +84,7 @@ hold(LimitChange = #limiter_LimitChange{id = LimitID}, Config = #{body_type := B {ok, #{account_id_from := AccountIDFrom, account_id_to := AccountIDTo}} = lim_range_machine:ensure_range_exist_in_state(TimeRange, LimitRangeState, LimitContext), Postings = lim_p_transfer:construct_postings(AccountIDFrom, AccountIDTo, Body), - Postings1 = maybe_inverse_posting(Postings, LimitContext, Config), + Postings1 = apply_op_behaviour(Postings, LimitContext, Config), lim_accounting:hold(construct_plan_id(LimitChange), {1, Postings1}, LimitContext) end). @@ -133,15 +133,15 @@ partial_commit(PartialBody, LimitChange = #limiter_LimitChange{id = LimitID}, Co lim_range_machine:get_range(TimeRange, LimitRangeState), PartialPostings0 = lim_p_transfer:construct_postings(AccountIDFrom, AccountIDTo, PartialBody), FullPostings0 = lim_p_transfer:construct_postings(AccountIDFrom, AccountIDTo, FullBody), - PartialPostings1 = maybe_inverse_posting(PartialPostings0, LimitContext, Config), - FullPostings1 = maybe_inverse_posting(FullPostings0, LimitContext, Config), + PartialPostings1 = apply_op_behaviour(PartialPostings0, LimitContext, Config), + FullPostings1 = apply_op_behaviour(FullPostings0, LimitContext, Config), NewBatchList = [{2, lim_p_transfer:reverse_postings(FullPostings1)} | [{3, PartialPostings1}]], PlanID = construct_plan_id(LimitChange), unwrap(lim_accounting:plan(PlanID, NewBatchList, LimitContext)), unwrap(lim_accounting:commit(PlanID, [{1, FullPostings1} | NewBatchList], LimitContext)) end). -maybe_inverse_posting(Posting, LimitContext, #{op_behaviour := ComputationConfig}) -> +apply_op_behaviour(Posting, LimitContext, #{op_behaviour := ComputationConfig}) -> {ok, Operation} = lim_context:get_operation(payment_processing, LimitContext), case maps:get(Operation, ComputationConfig, undefined) of subtraction -> @@ -149,7 +149,7 @@ maybe_inverse_posting(Posting, LimitContext, #{op_behaviour := ComputationConfig Type when Type =:= undefined orelse Type =:= additional -> Posting end; -maybe_inverse_posting(Body, _LimitContext, _Config) -> +apply_op_behaviour(Body, _LimitContext, _Config) -> Body. assert_partial_body( From c7e96068a56da444e78cc7739a902da8e268dc63 Mon Sep 17 00:00:00 2001 From: Boris Date: Mon, 19 Jul 2021 17:44:09 +0300 Subject: [PATCH 13/54] fix (#6) --- apps/limiter/src/lim_turnover_processor.erl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/limiter/src/lim_turnover_processor.erl b/apps/limiter/src/lim_turnover_processor.erl index 2ef8933d..5d86c5e7 100644 --- a/apps/limiter/src/lim_turnover_processor.erl +++ b/apps/limiter/src/lim_turnover_processor.erl @@ -52,7 +52,7 @@ get_limit(LimitID, Config, LimitContext) -> LimitRangeID = construct_range_id(LimitID, Timestamp, Config, LimitContext), LimitRange = unwrap(limit, lim_range_machine:get(LimitRangeID, LimitContext)), TimeRange = lim_config_machine:calculate_time_range(Timestamp, Config), - #{own_amount := Amount} = + #{max_available_amount := Amount} = unwrap(lim_range_machine:get_range_balance(TimeRange, LimitRange, LimitContext)), #limiter_Limit{ id = LimitRangeID, From 689aea69432ce37b47178598d5427d11425679d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D0=B5=D0=BC?= Date: Wed, 20 Oct 2021 13:42:03 +0300 Subject: [PATCH 14/54] ED-297: New create method (#7) * added new create method * added party day config --- apps/limiter/src/lim_config_codec.erl | 27 ++++++++++++++++++ apps/limiter/src/lim_configurator.erl | 29 +++++++++++++++++++- apps/limiter/test/lim_client.erl | 5 ++++ apps/limiter/test/lim_configurator_SUITE.erl | 27 ++++++++++++++++-- apps/limiter/test/lim_turnover_SUITE.erl | 2 +- rebar.lock | 2 +- 6 files changed, 86 insertions(+), 6 deletions(-) diff --git a/apps/limiter/src/lim_config_codec.erl b/apps/limiter/src/lim_config_codec.erl index 7a99c2a3..518ab105 100644 --- a/apps/limiter/src/lim_config_codec.erl +++ b/apps/limiter/src/lim_config_codec.erl @@ -7,6 +7,7 @@ -export([marshal_config/1]). -export([unmarshal_body_type/1]). -export([unmarshal_op_behaviour/1]). +-export([unmarshal_params/1]). -export([maybe_apply/2]). %% Types @@ -140,6 +141,32 @@ unmarshal_timestamp(Timestamp) when is_binary(Timestamp) -> erlang:raise(error, {bad_timestamp, Timestamp, Error}, St) end. +-spec unmarshal_params(encoded_value()) -> decoded_value(). +unmarshal_params(#limiter_config_LimitConfigParams{ + id = ID, + description = Description, + body_type = BodyType, + started_at = StartedAt, + shard_size = ShardSize, + time_range_type = TimeRangeType, + context_type = ContextType, + type = Type, + scope = Scope, + op_behaviour = OpBehaviour +}) -> + genlib_map:compact(#{ + id => ID, + body_type => unmarshal_body_type(BodyType), + started_at => StartedAt, + shard_size => ShardSize, + time_range_type => unmarshal_time_range_type(TimeRangeType), + context_type => unmarshal_context_type(ContextType), + type => maybe_apply(Type, fun unmarshal_type/1), + scope => maybe_apply(Scope, fun unmarshal_scope/1), + description => Description, + op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1) + }). + unmarshal_change({created, #limiter_config_CreatedChange{limit_config = Config}}) -> {created, unmarshal_config(Config)}. diff --git a/apps/limiter/src/lim_configurator.erl b/apps/limiter/src/lim_configurator.erl index b6dd4166..fd430cdf 100644 --- a/apps/limiter/src/lim_configurator.erl +++ b/apps/limiter/src/lim_configurator.erl @@ -24,7 +24,7 @@ handle_function(Fn, Args, WoodyCtx, Opts) -> -spec handle_function_(woody:func(), woody:args(), lim_context(), woody:options()) -> {ok, woody:result()}. handle_function_( - 'Create', + 'CreateLegacy', {#limiter_cfg_LimitCreateParams{ id = ID, name = Name, @@ -58,6 +58,16 @@ handle_function_( #limiter_cfg_LimitConfigNameNotFound{} ) end; +handle_function_('Create', {Params}, LimitContext, _Opts) -> + Config = #{id := ID, type := Type} = lim_config_codec:unmarshal_params(Params), + {ok, LimitConfig} = lim_config_machine:start( + ID, + genlib_map:compact(Config#{ + processor_type => map_type(Type) + }), + LimitContext + ), + {ok, lim_config_codec:marshal_config(LimitConfig)}; handle_function_('Get', {LimitID}, LimitContext, _Opts) -> scoper:add_meta(#{limit_config_id => LimitID}), case lim_config_machine:get(LimitID, LimitContext) of @@ -67,6 +77,14 @@ handle_function_('Get', {LimitID}, LimitContext, _Opts) -> woody_error:raise(business, #limiter_cfg_LimitConfigNotFound{}) end. +map_type(turnover) -> + <<"TurnoverProcessor">>; +map_type(_) -> + woody_error:raise( + business, + #limiter_base_InvalidRequest{errors = [<<"Config type not found.">>]} + ). + mk_limit_config(<<"ShopDayTurnover">>) -> {ok, #{ processor_type => <<"TurnoverProcessor">>, @@ -76,6 +94,15 @@ mk_limit_config(<<"ShopDayTurnover">>) -> context_type => payment_processing, time_range_type => {calendar, day} }}; +mk_limit_config(<<"PartyDayTurnover">>) -> + {ok, #{ + processor_type => <<"TurnoverProcessor">>, + type => turnover, + scope => {scope, party}, + shard_size => 7, + context_type => payment_processing, + time_range_type => {calendar, day} + }}; mk_limit_config(<<"ShopMonthTurnover">>) -> {ok, #{ processor_type => <<"TurnoverProcessor">>, diff --git a/apps/limiter/test/lim_client.erl b/apps/limiter/test/lim_client.erl index 7e89099a..08066d94 100644 --- a/apps/limiter/test/lim_client.erl +++ b/apps/limiter/test/lim_client.erl @@ -8,6 +8,7 @@ -export([hold/3]). -export([commit/3]). +-export([legacy_create_config/2]). -export([create_config/2]). -export([get_config/2]). @@ -39,6 +40,10 @@ commit(LimitChange, Context, Client) -> %% +-spec legacy_create_config(limit_config_params(), client()) -> woody:result() | no_return(). +legacy_create_config(LimitCreateParams, Client) -> + call_configurator('CreateLegacy', {LimitCreateParams}, Client). + -spec create_config(limit_config_params(), client()) -> woody:result() | no_return(). create_config(LimitCreateParams, Client) -> call_configurator('Create', {LimitCreateParams}, Client). diff --git a/apps/limiter/test/lim_configurator_SUITE.erl b/apps/limiter/test/lim_configurator_SUITE.erl index df1e419d..c750a8b3 100644 --- a/apps/limiter/test/lim_configurator_SUITE.erl +++ b/apps/limiter/test/lim_configurator_SUITE.erl @@ -13,6 +13,7 @@ -export([init_per_testcase/2]). -export([end_per_testcase/2]). +-export([legacy_create_config/1]). -export([create_config/1]). -export([get_config/1]). @@ -32,6 +33,7 @@ all() -> groups() -> [ {default, [], [ + legacy_create_config, create_config, get_config ]} @@ -71,8 +73,8 @@ end_per_testcase(_Name, _C) -> %% --spec create_config(config()) -> _. -create_config(_C) -> +-spec legacy_create_config(config()) -> _. +legacy_create_config(_C) -> Client = lim_client:new(), Params = #limiter_cfg_LimitCreateParams{ id = <<"ID">>, @@ -81,6 +83,25 @@ create_config(_C) -> started_at = <<"2000-01-01T00:00:00Z">>, body_type = {cash, #limiter_config_LimitBodyTypeCash{currency = <<"RUB">>}} }, + {ok, #limiter_config_LimitConfig{}} = lim_client:legacy_create_config(Params, Client). + +-spec create_config(config()) -> _. +create_config(_C) -> + Client = lim_client:new(), + Params = #limiter_config_LimitConfigParams{ + id = <<"ID">>, + description = <<"description">>, + started_at = <<"2000-01-01T00:00:00Z">>, + body_type = {cash, #limiter_config_LimitBodyTypeCash{currency = <<"RUB">>}}, + shard_size = 4, + time_range_type = {calendar, {week, #time_range_TimeRangeTypeCalendarWeek{}}}, + type = {turnover, #limiter_config_LimitTypeTurnover{}}, + scope = {scope, {shop, #limiter_config_LimitScopeTypeShop{}}}, + op_behaviour = #limiter_config_OperationLimitBehaviour{ + invoice_payment_refund = {addition, #limiter_config_Addition{}} + }, + context_type = {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}} + }, {ok, #limiter_config_LimitConfig{}} = lim_client:create_config(Params, Client). -spec get_config(config()) -> _. @@ -100,5 +121,5 @@ prepare_environment(ID, LimitName, _C) -> started_at = <<"2000-01-01T00:00:00Z">>, body_type = {cash, #limiter_config_LimitBodyTypeCash{currency = <<"RUB">>}} }, - {ok, LimitConfig} = lim_client:create_config(Params, Client), + {ok, LimitConfig} = lim_client:legacy_create_config(Params, Client), #{config => LimitConfig, client => Client}. diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index f34af0c1..7442091e 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -336,5 +336,5 @@ prepare_environment(ID, LimitName, _C) -> invoice_payment_refund = {subtraction, #limiter_config_Subtraction{}} } }, - {ok, LimitConfig} = lim_client:create_config(Params, Client), + {ok, LimitConfig} = lim_client:legacy_create_config(Params, Client), #{config => LimitConfig, client => Client}. diff --git a/rebar.lock b/rebar.lock index 1606da5d..211e8883 100644 --- a/rebar.lock +++ b/rebar.lock @@ -35,7 +35,7 @@ {<<"jsx">>,{pkg,<<"jsx">>,<<"3.0.0">>},1}, {<<"limiter_proto">>, {git,"git@github.com:rbkmoney/limiter-proto.git", - {ref,"9c0653ff7281ff443f515d2fddf9673fe837a5be"}}, + {ref,"2e2cdab859222648e389dc74867b5273e73583e9"}}, 0}, {<<"machinery">>, {git,"https://github.com/rbkmoney/machinery.git", From 9a5ae488c2bab5b3e0398eccede4aaea37842dc3 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Thu, 14 Apr 2022 17:51:40 +0300 Subject: [PATCH 15/54] Drop proprietary CI stuff --- .gitignore | 2 -- .gitmodules | 4 --- Dockerfile.sh | 24 --------------- Jenkinsfile | 22 -------------- Makefile | 77 ----------------------------------------------- build_utils | 1 - docker-compose.sh | 60 ------------------------------------ 7 files changed, 190 deletions(-) delete mode 100644 .gitmodules delete mode 100755 Dockerfile.sh delete mode 100644 Jenkinsfile delete mode 100644 Makefile delete mode 160000 build_utils delete mode 100755 docker-compose.sh diff --git a/.gitignore b/.gitignore index 8ae71e1c..e86fc9d0 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,6 @@ erl_crash.dump *.sublime-workspace .edts .DS_Store -Dockerfile -docker-compose.yml /.idea/ *.beam rebar3.crashdump \ No newline at end of file diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index a3ea65b2..00000000 --- a/.gitmodules +++ /dev/null @@ -1,4 +0,0 @@ -[submodule "build_utils"] - path = build_utils - url = ../build_utils.git - branch = master diff --git a/Dockerfile.sh b/Dockerfile.sh deleted file mode 100755 index e9d6f661..00000000 --- a/Dockerfile.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -cat < -RUN mkdir -p /var/log/limiter -COPY ./_build/prod/rel/limiter /opt/limiter -WORKDIR /opt/limiter -CMD /opt/limiter/bin/limiter foreground -EXPOSE 8022 -LABEL com.rbkmoney.$SERVICE_NAME.parent=$BASE_IMAGE_NAME \ - com.rbkmoney.$SERVICE_NAME.parent_tag=$BASE_IMAGE_TAG \ - com.rbkmoney.$SERVICE_NAME.build_img=build \ - com.rbkmoney.$SERVICE_NAME.build_img_tag=$BUILD_IMAGE_TAG \ - com.rbkmoney.$SERVICE_NAME.commit_id=$(git rev-parse HEAD) \ - com.rbkmoney.$SERVICE_NAME.commit_number=$(git rev-list --count HEAD) \ - com.rbkmoney.$SERVICE_NAME.branch=$( \ - if [ "HEAD" != $(git rev-parse --abbrev-ref HEAD) ]; then \ - echo $(git rev-parse --abbrev-ref HEAD); \ - elif [ -n "$BRANCH_NAME" ]; then \ - echo $BRANCH_NAME; \ - else \ - echo $(git name-rev --name-only HEAD); \ - fi) -EOF diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 3265915c..00000000 --- a/Jenkinsfile +++ /dev/null @@ -1,22 +0,0 @@ -#!groovy -// -*- mode: groovy -*- - -def finalHook = { - runStage('store CT logs') { - archive '_build/test/logs/' - } -} - -build('limiter', 'docker-host', finalHook) { - checkoutRepo() - loadBuildUtils() - - def pipeErlangService - runStage('load pipeline') { - env.JENKINS_LIB = "build_utils/jenkins_lib" - env.SH_TOOLS = "build_utils/sh" - pipeErlangService = load("${env.JENKINS_LIB}/pipeErlangService.groovy") - } - - pipeErlangService.runPipe(true, true) -} diff --git a/Makefile b/Makefile deleted file mode 100644 index b496da3e..00000000 --- a/Makefile +++ /dev/null @@ -1,77 +0,0 @@ -REBAR := $(shell which rebar3 2>/dev/null || which ./rebar3) -SUBMODULES = build_utils -SUBTARGETS = $(patsubst %,%/.git,$(SUBMODULES)) - -UTILS_PATH := build_utils -TEMPLATES_PATH := . - -# Name of the service -SERVICE_NAME := limiter -# Service image default tag -SERVICE_IMAGE_TAG ?= $(shell git rev-parse HEAD) -# The tag for service image to be pushed with -SERVICE_IMAGE_PUSH_TAG ?= $(SERVICE_IMAGE_TAG) - -# Base image for the service -BASE_IMAGE_NAME := service-erlang -BASE_IMAGE_TAG := 54a794b4875ad79f90dba0a7708190b3b37d584f - -# Build image tag to be used -BUILD_IMAGE_NAME := build-erlang -BUILD_IMAGE_TAG := 491bc06c745a07c6fe9e8b5dbbe958e8e0b82c4c - -CALL_ANYWHERE := all submodules rebar-update compile xref lint dialyze \ - release clean distclean check format check_format - -CALL_W_CONTAINER := $(CALL_ANYWHERE) test - -all: compile - --include $(UTILS_PATH)/make_lib/utils_container.mk --include $(UTILS_PATH)/make_lib/utils_image.mk - -.PHONY: $(CALL_W_CONTAINER) - -# CALL_ANYWHERE -$(SUBTARGETS): %/.git: % - git submodule update --init $< - touch $@ - -submodules: $(SUBTARGETS) - -rebar-update: - $(REBAR) update - -compile: submodules rebar-update - $(REBAR) compile - -xref: submodules - $(REBAR) xref - -lint: - elvis rock - -check_format: - $(REBAR) fmt -c - -format: - $(REBAR) fmt -w - -dialyze: - $(REBAR) dialyzer - -check: xref lint dialyze - -release: distclean - $(REBAR) as prod release - -clean: - $(REBAR) clean - -distclean: - $(REBAR) clean - rm -rf _build - -# CALL_W_CONTAINER -test: submodules - $(REBAR) do eunit, ct diff --git a/build_utils b/build_utils deleted file mode 160000 index e1318727..00000000 --- a/build_utils +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e1318727d4d0c3e48f5122bf3197158b6695f50e diff --git a/docker-compose.sh b/docker-compose.sh deleted file mode 100755 index a2f63c6f..00000000 --- a/docker-compose.sh +++ /dev/null @@ -1,60 +0,0 @@ -#!/bin/bash -cat < Date: Mon, 18 Apr 2022 11:00:48 +0300 Subject: [PATCH 16/54] TD-275: Add CI through GH Actions (#1) * Switch to valitydev upstreams * valitydev/thrift_erlang@c280ff2 * valitydev/scoper@7f3183d * valitydev/genlib@82c5ff3 * valitydev/erlang-health@5958e2f * valitydev/damsel@1d60b20 * Drop how_are_you --- .env | 4 + .github/workflows/build-image.yml | 53 ++++ .github/workflows/erlang-checks.yml | 39 +++ .gitignore | 6 +- Dockerfile | 42 +++ Dockerfile.dev | 17 ++ Makefile | 108 ++++++++ apps/limiter/src/lim_accounting.erl | 2 +- apps/limiter/src/lim_config_machine.erl | 254 ++++++++---------- .../src/lim_config_machinery_schema.erl | 8 +- apps/limiter/src/lim_p_transfer.erl | 10 +- apps/limiter/src/lim_pipeline.erl | 2 +- .../src/lim_range_machinery_schema.erl | 13 +- apps/limiter/src/lim_utils.erl | 5 +- apps/limiter/src/limiter.app.src | 5 +- apps/limiter/test/lim_client.erl | 6 +- apps/limiter/test/lim_configurator_SUITE.erl | 8 +- apps/limiter/test/lim_mock_service.erl | 6 +- apps/limiter/test/lim_turnover_SUITE.erl | 17 +- compose.yml | 60 +++++ config/sys.config | 19 +- elvis.config | 76 ++---- rebar.config | 110 ++++---- rebar.lock | 129 ++++----- test/machinegun/config.yaml | 3 + 25 files changed, 610 insertions(+), 392 deletions(-) create mode 100644 .env create mode 100644 .github/workflows/build-image.yml create mode 100644 .github/workflows/erlang-checks.yml create mode 100644 Dockerfile create mode 100644 Dockerfile.dev create mode 100644 Makefile create mode 100644 compose.yml diff --git a/.env b/.env new file mode 100644 index 00000000..aa650996 --- /dev/null +++ b/.env @@ -0,0 +1,4 @@ +SERVICE_NAME=limiter +OTP_VERSION=24.2.0 +REBAR_VERSION=3.18 +THRIFT_VERSION=0.14.2.2 diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml new file mode 100644 index 00000000..6f39e7c0 --- /dev/null +++ b/.github/workflows/build-image.yml @@ -0,0 +1,53 @@ +name: Build Docker image + +on: + push: + branches: + - 'master' + - 'epic/**' + pull_request: + branches: ['**'] + +env: + REGISTRY: ghcr.io + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Setup Buildx + uses: docker/setup-buildx-action@v1 + + # https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions#setting-an-environment-variable + - name: Update environment variables + run: grep -v '^#' .env >> $GITHUB_ENV + + - name: Log in to the Container registry + uses: docker/login-action@v1.12.0 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Construct tags / labels for an image + id: meta + uses: docker/metadata-action@v3.6.2 + with: + images: | + ${{ env.REGISTRY }}/${{ github.repository }} + tags: | + type=sha + + - name: Build and push Docker image + uses: docker/build-push-action@v2.9.0 + with: + push: ${{ github.event_name == 'push' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + SERVICE_NAME=${{ env.SERVICE_NAME }} + OTP_VERSION=${{ env.OTP_VERSION }} + THRIFT_VERSION=${{ env.THRIFT_VERSION }} diff --git a/.github/workflows/erlang-checks.yml b/.github/workflows/erlang-checks.yml new file mode 100644 index 00000000..4bb7abf8 --- /dev/null +++ b/.github/workflows/erlang-checks.yml @@ -0,0 +1,39 @@ +name: Erlang CI Checks + +on: + push: + branches: + - 'master' + - 'epic/**' + pull_request: + branches: ['**'] + +jobs: + setup: + name: Load .env + runs-on: ubuntu-latest + outputs: + otp-version: ${{ steps.otp-version.outputs.version }} + rebar-version: ${{ steps.rebar-version.outputs.version }} + thrift-version: ${{ steps.thrift-version.outputs.version }} + steps: + - name: Checkout repository + uses: actions/checkout@v2 + - run: grep -v '^#' .env >> $GITHUB_ENV + - id: otp-version + run: echo "::set-output name=version::$OTP_VERSION" + - id: rebar-version + run: echo "::set-output name=version::$REBAR_VERSION" + - id: thrift-version + run: echo "::set-output name=version::$THRIFT_VERSION" + + run: + name: Run checks + needs: setup + uses: valitydev/erlang-workflows/.github/workflows/erlang-parallel-build.yml@v1.0.1 + 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 }} + run-ct-with-compose: true diff --git a/.gitignore b/.gitignore index e86fc9d0..b57e87bf 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,8 @@ erl_crash.dump .DS_Store /.idea/ *.beam -rebar3.crashdump \ No newline at end of file +rebar3.crashdump + +# make stuff +/.image.* +Makefile.env diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..ec0732d9 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,42 @@ +ARG OTP_VERSION + +# Build the release +FROM docker.io/library/erlang:${OTP_VERSION} AS builder +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +# Install thrift compiler +ARG THRIFT_VERSION +ARG TARGETARCH +RUN wget -q -O- "https://github.com/valitydev/thrift/releases/download/${THRIFT_VERSION}/thrift-${THRIFT_VERSION}-linux-${TARGETARCH}.tar.gz" \ + | tar -xvz -C /usr/local/bin/ + +# Copy sources +RUN mkdir /build +COPY . /build/ + +# Build the release +WORKDIR /build +RUN rebar3 compile && \ + rebar3 as prod release + +# Make a runner image +FROM docker.io/library/erlang:${OTP_VERSION}-slim + +ARG SERVICE_NAME + +# Set env +ENV CHARSET=UTF-8 +ENV LANG=C.UTF-8 + +# Set runtime +WORKDIR /opt/${SERVICE_NAME} + +COPY --from=builder /build/_build/prod/rel/${SERVICE_NAME} /opt/${SERVICE_NAME} + +RUN echo "#!/bin/sh" >> /entrypoint.sh && \ + echo "exec /opt/${SERVICE_NAME}/bin/${SERVICE_NAME} foreground" >> /entrypoint.sh && \ + chmod +x /entrypoint.sh +ENTRYPOINT [] +CMD ["/entrypoint.sh"] + +EXPOSE 8022 diff --git a/Dockerfile.dev b/Dockerfile.dev new file mode 100644 index 00000000..e4cfa536 --- /dev/null +++ b/Dockerfile.dev @@ -0,0 +1,17 @@ +ARG OTP_VERSION + +FROM docker.io/library/erlang:${OTP_VERSION} +SHELL ["/bin/bash", "-o", "pipefail", "-c"] + +# Install thrift compiler +ARG THRIFT_VERSION +ARG TARGETARCH +RUN wget -q -O- "https://github.com/valitydev/thrift/releases/download/${THRIFT_VERSION}/thrift-${THRIFT_VERSION}-linux-${TARGETARCH}.tar.gz" \ + | tar -xvz -C /usr/local/bin/ + +# Set env +ENV CHARSET=UTF-8 +ENV LANG=C.UTF-8 + +# Set runtime +CMD ["/bin/bash"] diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..f3fc564f --- /dev/null +++ b/Makefile @@ -0,0 +1,108 @@ +# HINT +# Use this file to override variables here. +# For example, to run with podman put `DOCKER=podman` there. +-include Makefile.env + +# NOTE +# Variables specified in `.env` file are used to pick and setup specific +# component versions, both when building a development image and when running +# CI workflows on GH Actions. This ensures that tasks run with `wc-` prefix +# (like `wc-dialyze`) are reproducible between local machine and CI runners. +DOTENV := $(shell grep -v '^\#' .env) + +# Development images +DEV_IMAGE_TAG = limiter-dev +DEV_IMAGE_ID = $(file < .image.dev) + +DOCKER ?= docker +DOCKERCOMPOSE ?= docker-compose +DOCKERCOMPOSE_W_ENV = DEV_IMAGE_TAG=$(DEV_IMAGE_TAG) $(DOCKERCOMPOSE) +REBAR ?= rebar3 +TEST_CONTAINER_NAME ?= testrunner + +all: compile + +.PHONY: dev-image clean-dev-image wc-shell test + +dev-image: .image.dev + +.image.dev: Dockerfile.dev .env + env $(DOTENV) $(DOCKERCOMPOSE_W_ENV) build $(TEST_CONTAINER_NAME) + $(DOCKER) image ls -q -f "reference=$(DEV_IMAGE_TAG)" | head -n1 > $@ + +clean-dev-image: +ifneq ($(DEV_IMAGE_ID),) + $(DOCKER) image rm -f $(DEV_IMAGE_TAG) + rm .image.dev +endif + +DOCKER_WC_OPTIONS := -v $(PWD):$(PWD) --workdir $(PWD) +DOCKER_WC_EXTRA_OPTIONS ?= --rm +DOCKER_RUN = $(DOCKER) run -t $(DOCKER_WC_OPTIONS) $(DOCKER_WC_EXTRA_OPTIONS) + +DOCKERCOMPOSE_RUN = $(DOCKERCOMPOSE_W_ENV) run --rm $(DOCKER_WC_OPTIONS) + +# Utility tasks + +wc-shell: dev-image + $(DOCKER_RUN) --interactive --tty $(DEV_IMAGE_TAG) + +wc-%: dev-image + $(DOCKER_RUN) $(DEV_IMAGE_TAG) make $* + +# TODO docker compose down doesn't work yet +wdeps-shell: dev-image + $(DOCKERCOMPOSE_RUN) $(TEST_CONTAINER_NAME) su; \ + $(DOCKERCOMPOSE_W_ENV) down + +wdeps-%: dev-image + $(DOCKERCOMPOSE_RUN) -T $(TEST_CONTAINER_NAME) make $*; \ + res=$$?; \ + $(DOCKERCOMPOSE_W_ENV) down; \ + exit $$res + +# Rebar tasks + +rebar-shell: + $(REBAR) shell + +compile: + $(REBAR) compile + +xref: + $(REBAR) xref + +lint: + $(REBAR) lint + +check-format: + $(REBAR) fmt -c + +dialyze: + $(REBAR) as test dialyzer + +release: + $(REBAR) as prod release + +eunit: + $(REBAR) eunit --cover + +common-test: + $(REBAR) ct --cover + +cover: + $(REBAR) covertool generate + +format: + $(REBAR) fmt -w + +clean: + $(REBAR) clean + +distclean: clean-build-image + rm -rf _build + +test: eunit common-test + +cover-report: + $(REBAR) cover diff --git a/apps/limiter/src/lim_accounting.erl b/apps/limiter/src/lim_accounting.erl index 6362fc24..3fe0130e 100644 --- a/apps/limiter/src/lim_accounting.erl +++ b/apps/limiter/src/lim_accounting.erl @@ -109,7 +109,7 @@ construct_plan(PlanID, Batches) -> id = BatchID, postings = Postings } - || {BatchID, Postings} <- Batches + || {BatchID, Postings} <- Batches ] }. diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index 7ef93af5..2fe204e5 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -281,16 +281,19 @@ get_handler(ID, LimitContext) -> -spec calculate_time_range(timestamp(), config()) -> time_range(). calculate_time_range(Timestamp, Config) -> StartedAt = started_at(Config), - {StartDateTime, _USec0} = lim_range_codec:parse_timestamp(StartedAt), - {CurrentDateTime, _USec1} = lim_range_codec:parse_timestamp(Timestamp), - CurrentSec = calendar:datetime_to_gregorian_seconds(CurrentDateTime), case time_range_type(Config) of {calendar, Range} -> - calculate_calendar_time_range(Range, CurrentSec, CurrentDateTime, StartDateTime); + calculate_calendar_time_range(Range, Timestamp, StartedAt); {interval, _Interval} -> erlang:error({interval_time_range_not_implemented, Config}) end. +calculate_calendar_time_range(Range, Timestamp, StartedAt) -> + {StartDatetime, _USec0} = lim_range_codec:parse_timestamp(StartedAt), + {CurrentDatetime, _USec1} = lim_range_codec:parse_timestamp(Timestamp), + CurrentSec = calendar:datetime_to_gregorian_seconds(CurrentDatetime), + calculate_calendar_time_range(Range, CurrentSec, CurrentDatetime, StartDatetime). + calculate_calendar_time_range(year, CurrentSec, {CurrentDate, _CurrentTime}, {StartDate, StartTime}) -> {_StartYear, StartMonth, StartDay} = StartDate, {CurrentYear, _CurrentMonth, _} = CurrentDate, @@ -427,57 +430,43 @@ marshal_timestamp(DateTime) -> calculate_shard_id(Timestamp, Config) -> StartedAt = started_at(Config), ShardSize = shard_size(Config), - {StartDateTime, _USec0} = lim_range_codec:parse_timestamp(StartedAt), - {CurrentDateTime, _USec1} = lim_range_codec:parse_timestamp(Timestamp), case time_range_type(Config) of {calendar, Range} -> - Units = calculate_time_units(Range, CurrentDateTime, StartDateTime), - SignPrefix = mk_sign_prefix(Units), - RangePrefix = mk_prefix(Range), - mk_shard_id(<>, Units, ShardSize); + calculate_calendar_shard_id(Range, Timestamp, StartedAt, ShardSize); {interval, _Interval} -> erlang:error({interval_time_range_not_implemented, Config}) end. -calculate_time_units(year, {CurrentDate, CurrentTime}, {StartDate, StartTime}) -> - {StartYear, _, _} = StartDate, - {CurrentYear, _, _} = CurrentDate, +calculate_calendar_shard_id(Range, Timestamp, StartedAt, ShardSize) -> + {StartDatetime, _USec0} = lim_range_codec:parse_timestamp(StartedAt), + {CurrentDatetime, _USec1} = lim_range_codec:parse_timestamp(Timestamp), + Units = calculate_time_units(Range, CurrentDatetime, StartDatetime), + SignPrefix = mk_sign_prefix(Units), + RangePrefix = mk_prefix(Range), + mk_shard_id(<>, Units, ShardSize). - StartSecBase = calendar:datetime_to_gregorian_seconds({{StartYear, 1, 1}, {0, 0, 0}}), - StartSec = calendar:datetime_to_gregorian_seconds({StartDate, StartTime}), - CurrentSecBase = calendar:datetime_to_gregorian_seconds({{CurrentYear, 1, 1}, {0, 0, 0}}), - CurrentSec = calendar:datetime_to_gregorian_seconds({CurrentDate, CurrentTime}), +calculate_time_units(year, CurrentDatetime, StartDatetime) -> + StartSecBase = calculate_start_of_year_seconds(StartDatetime), + StartSec = calendar:datetime_to_gregorian_seconds(StartDatetime), + CurrentSecBase = calculate_start_of_year_seconds(CurrentDatetime), + CurrentSec = calendar:datetime_to_gregorian_seconds(CurrentDatetime), StartDelta = StartSec - StartSecBase, CurrentDelta = CurrentSec - (CurrentSecBase + StartDelta), - - case CurrentDelta >= 0 of - true -> - CurrentYear - StartYear; - false -> - CurrentYear - StartYear - 1 - end; -calculate_time_units(month, {CurrentDate, CurrentTime}, {StartDate, StartTime}) -> - {StartYear, StartMonth, _} = StartDate, - {CurrentYear, CurrentMonth, _} = CurrentDate, - - StartSecBase = calendar:datetime_to_gregorian_seconds({{StartYear, StartMonth, 1}, {0, 0, 0}}), - StartSec = calendar:datetime_to_gregorian_seconds({StartDate, StartTime}), - CurrentSecBase = calendar:datetime_to_gregorian_seconds({{CurrentYear, CurrentMonth, 1}, {0, 0, 0}}), - CurrentSec = calendar:datetime_to_gregorian_seconds({CurrentDate, CurrentTime}), + maybe_previous_unit(CurrentDelta, year(CurrentDatetime) - year(StartDatetime)); +calculate_time_units(month, CurrentDatetime, StartDatetime) -> + StartSecBase = calculate_start_of_month_seconds(StartDatetime), + StartSec = calendar:datetime_to_gregorian_seconds(StartDatetime), + CurrentSecBase = calculate_start_of_month_seconds(CurrentDatetime), + CurrentSec = calendar:datetime_to_gregorian_seconds(CurrentDatetime), StartDelta = StartSec - StartSecBase, CurrentDelta = CurrentSec - (CurrentSecBase + StartDelta), - YearDiff = CurrentYear - StartYear, - MonthDiff = CurrentMonth - StartMonth, + YearDiff = year(CurrentDatetime) - year(StartDatetime), + MonthDiff = month(CurrentDatetime) - month(StartDatetime), - case CurrentDelta >= 0 of - true -> - YearDiff * 12 + MonthDiff; - false -> - YearDiff * 12 + MonthDiff - 1 - end; + maybe_previous_unit(CurrentDelta, YearDiff * 12 + MonthDiff); calculate_time_units(week, {CurrentDate, CurrentTime}, {StartDate, StartTime}) -> StartWeekRem = calendar:date_to_gregorian_days(StartDate) rem 7, StartWeekBase = (calendar:date_to_gregorian_days(StartDate) div 7) * 7, @@ -501,12 +490,7 @@ calculate_time_units(week, {CurrentDate, CurrentTime}, {StartDate, StartTime}) - StartWeeks = calendar:date_to_gregorian_days(StartDate) div 7, CurrentWeeks = calendar:date_to_gregorian_days(CurrentDate) div 7, - case CurrentDelta >= 0 of - true -> - CurrentWeeks - StartWeeks; - false -> - CurrentWeeks - StartWeeks - 1 - end; + maybe_previous_unit(CurrentDelta, CurrentWeeks - StartWeeks); calculate_time_units(day, {CurrentDate, CurrentTime}, {StartDate, StartTime}) -> StartSecBase = calendar:datetime_to_gregorian_seconds({StartDate, {0, 0, 0}}), StartSec = calendar:datetime_to_gregorian_seconds({StartDate, StartTime}), @@ -516,12 +500,24 @@ calculate_time_units(day, {CurrentDate, CurrentTime}, {StartDate, StartTime}) -> CurrentDelta = CurrentSec - (CurrentSecBase + StartDelta), StartDays = calendar:date_to_gregorian_days(StartDate), CurrentDays = calendar:date_to_gregorian_days(CurrentDate), - case CurrentDelta >= 0 of - true -> - CurrentDays - StartDays; - false -> - CurrentDays - StartDays - 1 - end. + maybe_previous_unit(CurrentDelta, CurrentDays - StartDays). + +maybe_previous_unit(Delta, Unit) when Delta < 0 -> + Unit - 1; +maybe_previous_unit(_Delta, Unit) -> + Unit. + +calculate_start_of_year_seconds({{Year, _, _}, _Time}) -> + calendar:datetime_to_gregorian_seconds({{Year, 1, 1}, {0, 0, 0}}). + +calculate_start_of_month_seconds({{Year, Month, _}, _Time}) -> + calendar:datetime_to_gregorian_seconds({{Year, Month, 1}, {0, 0, 0}}). + +year({{Year, _, _}, _Time}) -> + Year. + +month({{_Year, Month, _}, _Time}) -> + Month. mk_prefix(day) -> <<"day">>; mk_prefix(week) -> <<"week">>; @@ -614,167 +610,135 @@ check_sign_prefix_test() -> -spec check_calculate_day_time_range_test() -> _. check_calculate_day_time_range_test() -> - Config0 = #{ - started_at => <<"2000-01-01T00:00:00Z">>, - time_range_type => {calendar, day} - }, + StartedAt1 = <<"2000-01-01T00:00:00Z">>, ?assertEqual( #{lower => <<"2000-01-01T00:00:00Z">>, upper => <<"2000-01-02T00:00:00Z">>}, - calculate_time_range(<<"2000-01-01T02:00:00Z">>, Config0) + calculate_calendar_time_range(day, <<"2000-01-01T02:00:00Z">>, StartedAt1) ), ?assertEqual( #{lower => <<"1999-12-31T00:00:00Z">>, upper => <<"2000-01-01T00:00:00Z">>}, - calculate_time_range(<<"1999-12-31T02:00:00Z">>, Config0) + calculate_calendar_time_range(day, <<"1999-12-31T02:00:00Z">>, StartedAt1) ), ?assertEqual( #{lower => <<"2000-01-10T00:00:00Z">>, upper => <<"2000-01-11T00:00:00Z">>}, - calculate_time_range(<<"2000-01-10T02:00:00Z">>, Config0) + calculate_calendar_time_range(day, <<"2000-01-10T02:00:00Z">>, StartedAt1) ), - Config1 = Config0#{started_at => <<"2000-01-01T03:00:00Z">>}, ?assertEqual( #{lower => <<"1999-12-31T03:00:00Z">>, upper => <<"2000-01-01T03:00:00Z">>}, - calculate_time_range(<<"2000-01-01T02:00:00Z">>, Config1) + calculate_calendar_time_range(day, <<"2000-01-01T02:00:00Z">>, <<"2000-01-01T03:00:00Z">>) ). -spec check_calculate_week_time_range_test() -> _. check_calculate_week_time_range_test() -> - Config0 = #{ - started_at => <<"2000-01-01T00:00:00Z">>, - time_range_type => {calendar, week} - }, + StartedAt = <<"2000-01-01T00:00:00Z">>, ?assertEqual( #{lower => <<"2000-01-01T00:00:00Z">>, upper => <<"2000-01-08T00:00:00Z">>}, - calculate_time_range(<<"2000-01-01T02:00:00Z">>, Config0) + calculate_calendar_time_range(week, <<"2000-01-01T02:00:00Z">>, StartedAt) ), ?assertEqual( #{lower => <<"1999-12-25T00:00:00Z">>, upper => <<"2000-01-01T00:00:00Z">>}, - calculate_time_range(<<"1999-12-31T02:00:00Z">>, Config0) + calculate_calendar_time_range(week, <<"1999-12-31T02:00:00Z">>, StartedAt) ), ?assertEqual( #{lower => <<"2000-09-30T00:00:00Z">>, upper => <<"2000-10-07T00:00:00Z">>}, - calculate_time_range(<<"2000-10-03T02:00:00Z">>, Config0) + calculate_calendar_time_range(week, <<"2000-10-03T02:00:00Z">>, StartedAt) ), - Config1 = Config0#{started_at => <<"2000-01-01T03:00:00Z">>}, ?assertEqual( #{lower => <<"1999-12-25T03:00:00Z">>, upper => <<"2000-01-01T03:00:00Z">>}, - calculate_time_range(<<"2000-01-01T02:00:00Z">>, Config1) + calculate_calendar_time_range(week, <<"2000-01-01T02:00:00Z">>, <<"2000-01-01T03:00:00Z">>) ). -spec check_calculate_month_time_range_test() -> _. check_calculate_month_time_range_test() -> - Config0 = #{ - started_at => <<"2000-01-01T00:00:00Z">>, - time_range_type => {calendar, month} - }, + StartedAt = <<"2000-01-01T00:00:00Z">>, ?assertEqual( #{lower => <<"2000-01-01T00:00:00Z">>, upper => <<"2000-02-01T00:00:00Z">>}, - calculate_time_range(<<"2000-01-01T02:00:00Z">>, Config0) + calculate_calendar_time_range(month, <<"2000-01-01T02:00:00Z">>, StartedAt) ), ?assertEqual( #{lower => <<"1999-12-01T00:00:00Z">>, upper => <<"2000-01-01T00:00:00Z">>}, - calculate_time_range(<<"1999-12-31T02:00:00Z">>, Config0) + calculate_calendar_time_range(month, <<"1999-12-31T02:00:00Z">>, StartedAt) ), ?assertEqual( #{lower => <<"2000-10-01T00:00:00Z">>, upper => <<"2000-11-01T00:00:00Z">>}, - calculate_time_range(<<"2000-10-03T02:00:00Z">>, Config0) + calculate_calendar_time_range(month, <<"2000-10-03T02:00:00Z">>, StartedAt) ), - Config1 = Config0#{started_at => <<"2000-01-01T03:00:00Z">>}, ?assertEqual( #{lower => <<"1999-12-01T03:00:00Z">>, upper => <<"2000-01-01T03:00:00Z">>}, - calculate_time_range(<<"2000-01-01T02:00:00Z">>, Config1) + calculate_calendar_time_range(month, <<"2000-01-01T02:00:00Z">>, <<"2000-01-01T03:00:00Z">>) ). -spec check_calculate_year_time_range_test() -> _. check_calculate_year_time_range_test() -> - Config0 = #{ - started_at => <<"2000-01-01T00:00:00Z">>, - time_range_type => {calendar, year} - }, + StartedAt = <<"2000-01-01T00:00:00Z">>, ?assertEqual( #{lower => <<"2000-01-01T00:00:00Z">>, upper => <<"2001-01-01T00:00:00Z">>}, - calculate_time_range(<<"2000-01-01T02:00:00Z">>, Config0) + calculate_calendar_time_range(year, <<"2000-01-01T02:00:00Z">>, StartedAt) ), ?assertEqual( #{lower => <<"1999-01-01T00:00:00Z">>, upper => <<"2000-01-01T00:00:00Z">>}, - calculate_time_range(<<"1999-12-31T02:00:00Z">>, Config0) + calculate_calendar_time_range(year, <<"1999-12-31T02:00:00Z">>, StartedAt) ), ?assertEqual( #{lower => <<"2010-01-01T00:00:00Z">>, upper => <<"2011-01-01T00:00:00Z">>}, - calculate_time_range(<<"2010-10-03T02:00:00Z">>, Config0) + calculate_calendar_time_range(year, <<"2010-10-03T02:00:00Z">>, StartedAt) ), - Config1 = Config0#{started_at => <<"2000-01-01T03:00:00Z">>}, ?assertEqual( #{lower => <<"1999-01-01T03:00:00Z">>, upper => <<"2000-01-01T03:00:00Z">>}, - calculate_time_range(<<"2000-01-01T02:00:00Z">>, Config1) + calculate_calendar_time_range(year, <<"2000-01-01T02:00:00Z">>, <<"2000-01-01T03:00:00Z">>) ). -spec check_calculate_day_shard_id_test() -> _. check_calculate_day_shard_id_test() -> - Config0 = #{ - started_at => <<"2000-01-01T00:00:00Z">>, - shard_size => 1, - time_range_type => {calendar, day} - }, - ?assertEqual(<<"future/day/0">>, calculate_shard_id(<<"2000-01-01T00:00:00Z">>, Config0)), - ?assertEqual(<<"future/day/2">>, calculate_shard_id(<<"2000-01-03T00:00:00Z">>, Config0)), - ?assertEqual(<<"past/day/1">>, calculate_shard_id(<<"1999-12-31T00:00:00Z">>, Config0)), - ?assertEqual(<<"future/day/1">>, calculate_shard_id(<<"2000-01-02T23:59:59Z">>, Config0)), - ?assertEqual(<<"future/day/1">>, calculate_shard_id(<<"2000-01-04T00:00:00Z">>, Config0#{shard_size => 2})), - ?assertEqual(<<"future/day/366">>, calculate_shard_id(<<"2001-01-01T00:00:00Z">>, Config0)), - ?assertEqual(<<"future/day/12">>, calculate_shard_id(<<"2001-01-01T00:00:00Z">>, Config0#{shard_size => 30})), - Config1 = Config0#{started_at => <<"2000-01-01T03:00:00Z">>}, - ?assertEqual(<<"past/day/1">>, calculate_shard_id(<<"2000-01-01T00:00:00Z">>, Config1)), - ?assertEqual(<<"future/day/1">>, calculate_shard_id(<<"2000-01-03T00:00:00Z">>, Config1)). + StartedAt1 = <<"2000-01-01T00:00:00Z">>, + ?assertEqual(<<"future/day/0">>, calculate_calendar_shard_id(day, <<"2000-01-01T00:00:00Z">>, StartedAt1, 1)), + ?assertEqual(<<"future/day/2">>, calculate_calendar_shard_id(day, <<"2000-01-03T00:00:00Z">>, StartedAt1, 1)), + ?assertEqual(<<"past/day/1">>, calculate_calendar_shard_id(day, <<"1999-12-31T00:00:00Z">>, StartedAt1, 1)), + ?assertEqual(<<"future/day/1">>, calculate_calendar_shard_id(day, <<"2000-01-02T23:59:59Z">>, StartedAt1, 1)), + ?assertEqual(<<"future/day/1">>, calculate_calendar_shard_id(day, <<"2000-01-04T00:00:00Z">>, StartedAt1, 2)), + ?assertEqual(<<"future/day/366">>, calculate_calendar_shard_id(day, <<"2001-01-01T00:00:00Z">>, StartedAt1, 1)), + ?assertEqual(<<"future/day/12">>, calculate_calendar_shard_id(day, <<"2001-01-01T00:00:00Z">>, StartedAt1, 30)), + StartedAt2 = <<"2000-01-01T03:00:00Z">>, + ?assertEqual(<<"past/day/1">>, calculate_calendar_shard_id(day, <<"2000-01-01T00:00:00Z">>, StartedAt2, 1)), + ?assertEqual(<<"future/day/1">>, calculate_calendar_shard_id(day, <<"2000-01-03T00:00:00Z">>, StartedAt2, 1)). -spec check_calculate_week_shard_id_test() -> _. check_calculate_week_shard_id_test() -> - Config0 = #{ - started_at => <<"2000-01-01T00:00:00Z">>, - shard_size => 1, - time_range_type => {calendar, week} - }, - ?assertEqual(<<"future/week/0">>, calculate_shard_id(<<"2000-01-01T00:00:00Z">>, Config0)), - ?assertEqual(<<"past/week/1">>, calculate_shard_id(<<"1999-12-31T00:00:00Z">>, Config0)), - ?assertEqual(<<"future/week/1">>, calculate_shard_id(<<"2000-01-08T00:00:00Z">>, Config0)), - ?assertEqual(<<"future/week/1">>, calculate_shard_id(<<"2000-01-15T00:00:00Z">>, Config0#{shard_size => 2})), - ?assertEqual(<<"future/week/52">>, calculate_shard_id(<<"2001-01-01T00:00:00Z">>, Config0)), - ?assertEqual(<<"future/week/13">>, calculate_shard_id(<<"2001-01-01T00:00:00Z">>, Config0#{shard_size => 4})), - Config1 = Config0#{started_at => <<"2000-01-02T03:00:00Z">>}, - ?assertEqual(<<"past/week/1">>, calculate_shard_id(<<"2000-01-02T00:00:00Z">>, Config1)), - ?assertEqual(<<"future/week/0">>, calculate_shard_id(<<"2000-01-09T00:00:00Z">>, Config1)). + StartedAt1 = <<"2000-01-01T00:00:00Z">>, + ?assertEqual(<<"future/week/0">>, calculate_calendar_shard_id(week, <<"2000-01-01T00:00:00Z">>, StartedAt1, 1)), + ?assertEqual(<<"past/week/1">>, calculate_calendar_shard_id(week, <<"1999-12-31T00:00:00Z">>, StartedAt1, 1)), + ?assertEqual(<<"future/week/1">>, calculate_calendar_shard_id(week, <<"2000-01-08T00:00:00Z">>, StartedAt1, 1)), + ?assertEqual(<<"future/week/1">>, calculate_calendar_shard_id(week, <<"2000-01-15T00:00:00Z">>, StartedAt1, 2)), + ?assertEqual(<<"future/week/52">>, calculate_calendar_shard_id(week, <<"2001-01-01T00:00:00Z">>, StartedAt1, 1)), + ?assertEqual(<<"future/week/13">>, calculate_calendar_shard_id(week, <<"2001-01-01T00:00:00Z">>, StartedAt1, 4)), + StartedAt2 = <<"2000-01-02T03:00:00Z">>, + ?assertEqual(<<"past/week/1">>, calculate_calendar_shard_id(week, <<"2000-01-02T00:00:00Z">>, StartedAt2, 1)), + ?assertEqual(<<"future/week/0">>, calculate_calendar_shard_id(week, <<"2000-01-09T00:00:00Z">>, StartedAt2, 1)). -spec check_calculate_month_shard_id_test() -> _. check_calculate_month_shard_id_test() -> - Config0 = #{ - started_at => <<"2000-01-01T00:00:00Z">>, - shard_size => 1, - time_range_type => {calendar, month} - }, - ?assertEqual(<<"future/month/0">>, calculate_shard_id(<<"2000-01-01T00:00:00Z">>, Config0)), - ?assertEqual(<<"past/month/1">>, calculate_shard_id(<<"1999-12-31T00:00:00Z">>, Config0)), - ?assertEqual(<<"future/month/1">>, calculate_shard_id(<<"2000-02-01T00:00:00Z">>, Config0)), - ?assertEqual(<<"future/month/1">>, calculate_shard_id(<<"2000-03-01T00:00:00Z">>, Config0#{shard_size => 2})), - ?assertEqual(<<"future/month/12">>, calculate_shard_id(<<"2001-01-01T00:00:00Z">>, Config0)), - ?assertEqual(<<"future/month/1">>, calculate_shard_id(<<"2001-01-01T00:00:00Z">>, Config0#{shard_size => 12})), - Config1 = Config0#{started_at => <<"2000-01-02T03:00:00Z">>}, - ?assertEqual(<<"past/month/1">>, calculate_shard_id(<<"2000-01-02T00:00:00Z">>, Config1)), - ?assertEqual(<<"future/month/0">>, calculate_shard_id(<<"2000-02-02T00:00:00Z">>, Config1)). + StartedAt1 = <<"2000-01-01T00:00:00Z">>, + ?assertEqual(<<"future/month/0">>, calculate_calendar_shard_id(month, <<"2000-01-01T00:00:00Z">>, StartedAt1, 1)), + ?assertEqual(<<"past/month/1">>, calculate_calendar_shard_id(month, <<"1999-12-31T00:00:00Z">>, StartedAt1, 1)), + ?assertEqual(<<"future/month/1">>, calculate_calendar_shard_id(month, <<"2000-02-01T00:00:00Z">>, StartedAt1, 1)), + ?assertEqual(<<"future/month/1">>, calculate_calendar_shard_id(month, <<"2000-03-01T00:00:00Z">>, StartedAt1, 2)), + ?assertEqual(<<"future/month/12">>, calculate_calendar_shard_id(month, <<"2001-01-01T00:00:00Z">>, StartedAt1, 1)), + ?assertEqual(<<"future/month/1">>, calculate_calendar_shard_id(month, <<"2001-01-01T00:00:00Z">>, StartedAt1, 12)), + StartedAt2 = <<"2000-01-02T03:00:00Z">>, + ?assertEqual(<<"past/month/1">>, calculate_calendar_shard_id(month, <<"2000-01-02T00:00:00Z">>, StartedAt2, 1)), + ?assertEqual(<<"future/month/0">>, calculate_calendar_shard_id(month, <<"2000-02-02T00:00:00Z">>, StartedAt2, 1)). -spec check_calculate_year_shard_id_test() -> _. check_calculate_year_shard_id_test() -> - Config0 = #{ - started_at => <<"2000-01-01T00:00:00Z">>, - shard_size => 1, - time_range_type => {calendar, year} - }, - ?assertEqual(<<"future/year/0">>, calculate_shard_id(<<"2000-01-01T00:00:00Z">>, Config0)), - ?assertEqual(<<"past/year/1">>, calculate_shard_id(<<"1999-12-31T00:00:00Z">>, Config0)), - ?assertEqual(<<"future/year/1">>, calculate_shard_id(<<"2001-01-01T00:00:00Z">>, Config0)), - ?assertEqual(<<"future/year/1">>, calculate_shard_id(<<"2003-01-01T00:00:00Z">>, Config0#{shard_size => 2})), - ?assertEqual(<<"future/year/10">>, calculate_shard_id(<<"2010-01-01T00:00:00Z">>, Config0)), - ?assertEqual(<<"future/year/2">>, calculate_shard_id(<<"2020-01-01T00:00:00Z">>, Config0#{shard_size => 10})), - Config1 = Config0#{started_at => <<"2000-01-02T03:00:00Z">>}, - ?assertEqual(<<"past/year/1">>, calculate_shard_id(<<"2000-01-01T00:00:00Z">>, Config1)), - ?assertEqual(<<"future/year/0">>, calculate_shard_id(<<"2001-01-01T00:00:00Z">>, Config1)). + StartedAt1 = <<"2000-01-01T00:00:00Z">>, + ?assertEqual(<<"future/year/0">>, calculate_calendar_shard_id(year, <<"2000-01-01T00:00:00Z">>, StartedAt1, 1)), + ?assertEqual(<<"past/year/1">>, calculate_calendar_shard_id(year, <<"1999-12-31T00:00:00Z">>, StartedAt1, 1)), + ?assertEqual(<<"future/year/1">>, calculate_calendar_shard_id(year, <<"2001-01-01T00:00:00Z">>, StartedAt1, 1)), + ?assertEqual(<<"future/year/1">>, calculate_calendar_shard_id(year, <<"2003-01-01T00:00:00Z">>, StartedAt1, 2)), + ?assertEqual(<<"future/year/10">>, calculate_calendar_shard_id(year, <<"2010-01-01T00:00:00Z">>, StartedAt1, 1)), + ?assertEqual(<<"future/year/2">>, calculate_calendar_shard_id(year, <<"2020-01-01T00:00:00Z">>, StartedAt1, 10)), + StartedAt2 = <<"2000-01-02T03:00:00Z">>, + ?assertEqual(<<"past/year/1">>, calculate_calendar_shard_id(year, <<"2000-01-01T00:00:00Z">>, StartedAt2, 1)), + ?assertEqual(<<"future/year/0">>, calculate_calendar_shard_id(year, <<"2001-01-01T00:00:00Z">>, StartedAt2, 1)). -endif. diff --git a/apps/limiter/src/lim_config_machinery_schema.erl b/apps/limiter/src/lim_config_machinery_schema.erl index 819e97a7..36e5c9c2 100644 --- a/apps/limiter/src/lim_config_machinery_schema.erl +++ b/apps/limiter/src/lim_config_machinery_schema.erl @@ -90,9 +90,10 @@ unmarshal_event(1, EncodedChange, Context) -> -spec marshal_unmarshal_created_test() -> _. marshal_unmarshal_created_test() -> + ID = <<"id">>, Created = {created, #{ - id => <<"id">>, + id => ID, processor_type => <<"type">>, created_at => lim_time:now(), body_type => {cash, <<"RUB">>}, @@ -104,9 +105,10 @@ marshal_unmarshal_created_test() -> scope => {scope, party}, description => <<"description">> }}, + Context = #{machine_ref => ID, machine_ns => config}, Event = {ev, lim_time:machinery_now(), Created}, - {Marshaled, _} = marshal_event(1, Event, {}), - {Unmarshaled, _} = unmarshal_event(1, Marshaled, {}), + {Marshaled, _} = marshal_event(1, Event, Context), + {Unmarshaled, _} = unmarshal_event(1, Marshaled, Context), ?assertEqual(Event, Unmarshaled). -endif. diff --git a/apps/limiter/src/lim_p_transfer.erl b/apps/limiter/src/lim_p_transfer.erl index e2e19857..666465ae 100644 --- a/apps/limiter/src/lim_p_transfer.erl +++ b/apps/limiter/src/lim_p_transfer.erl @@ -51,18 +51,18 @@ reverse_postings(Postings) -> from_id = AccountTo, to_id = AccountFrom } - || Posting = #accounter_Posting{from_id = AccountFrom, to_id = AccountTo} <- Postings + || Posting = #accounter_Posting{from_id = AccountFrom, to_id = AccountTo} <- Postings ]. -spec assert_partial_posting_amount([posting()], [posting()]) -> ok | {error, forbidden_operation_amount_error()}. assert_partial_posting_amount( - [#accounter_Posting{amount = Partial, currency_sym_code = Currency} | _Rest], - [#accounter_Posting{amount = Full, currency_sym_code = Currency} | _Rest] + [#accounter_Posting{amount = Partial, currency_sym_code = Currency} | _], + [#accounter_Posting{amount = Full, currency_sym_code = Currency} | _] ) -> compare_amount(Partial, Full, Currency); assert_partial_posting_amount( - [#accounter_Posting{amount = Partial, currency_sym_code = PartialCurrency} | _Rest], - [#accounter_Posting{amount = Full, currency_sym_code = FullCurrency} | _Rest] + [#accounter_Posting{amount = Partial, currency_sym_code = PartialCurrency} | _], + [#accounter_Posting{amount = Full, currency_sym_code = FullCurrency} | _] ) -> erlang:error({invalid_partial_cash, {Partial, PartialCurrency}, {Full, FullCurrency}}). diff --git a/apps/limiter/src/lim_pipeline.erl b/apps/limiter/src/lim_pipeline.erl index 4759f1b2..707b99a4 100644 --- a/apps/limiter/src/lim_pipeline.erl +++ b/apps/limiter/src/lim_pipeline.erl @@ -27,7 +27,7 @@ do(Fun) -> R -> {ok, R} catch - Thrown -> {error, Thrown} + throw:Thrown -> {error, Thrown} end. -spec do(Tag, fun(() -> ok | T | thrown(E))) -> ok | result(T, {Tag, E}). diff --git a/apps/limiter/src/lim_range_machinery_schema.erl b/apps/limiter/src/lim_range_machinery_schema.erl index cb98e5e0..db8d84d5 100644 --- a/apps/limiter/src/lim_range_machinery_schema.erl +++ b/apps/limiter/src/lim_range_machinery_schema.erl @@ -90,16 +90,18 @@ unmarshal_event(1, EncodedChange, Context) -> -spec marshal_unmarshal_created_test() -> _. marshal_unmarshal_created_test() -> + ID = <<"id">>, Created = {created, #{ - id => <<"id">>, + id => ID, type => {calendar, day}, created_at => <<"2000-01-01T00:00:00Z">>, currency => <<"USD">> }}, + Context = #{machine_ref => ID, machine_ns => limrange}, Event = {ev, lim_time:machinery_now(), Created}, - {Marshaled, _} = marshal_event(1, Event, {}), - {Unmarshaled, _} = unmarshal_event(1, Marshaled, {}), + {Marshaled, _} = marshal_event(1, Event, Context), + {Unmarshaled, _} = unmarshal_event(1, Marshaled, Context), ?assertEqual(Event, Unmarshaled). -spec marshal_unmarshal_time_range_created_test() -> _. @@ -111,9 +113,10 @@ marshal_unmarshal_time_range_created_test() -> upper => <<"2000-01-01T00:00:00Z">>, lower => <<"2000-01-01T00:00:00Z">> }}, + Context = #{machine_ref => <<"id">>, machine_ns => limrange}, Event = {ev, lim_time:machinery_now(), TimeRangeCreated}, - {Marshaled, _} = marshal_event(1, Event, {}), - {Unmarshaled, _} = unmarshal_event(1, Marshaled, {}), + {Marshaled, _} = marshal_event(1, Event, Context), + {Unmarshaled, _} = unmarshal_event(1, Marshaled, Context), ?assertEqual(Event, Unmarshaled). -endif. diff --git a/apps/limiter/src/lim_utils.erl b/apps/limiter/src/lim_utils.erl index a227861a..e3da1324 100644 --- a/apps/limiter/src/lim_utils.erl +++ b/apps/limiter/src/lim_utils.erl @@ -24,7 +24,4 @@ get_woody_client(Url) -> -spec get_woody_event_handlers() -> woody:ev_handlers(). get_woody_event_handlers() -> - genlib_app:env(limiter, woody_event_handlers, [ - scoper_woody_event_handler, - hay_woody_event_handler - ]). + genlib_app:env(limiter, woody_event_handlers, [scoper_woody_event_handler]). diff --git a/apps/limiter/src/limiter.app.src b/apps/limiter/src/limiter.app.src index e45aade1..0e0bd831 100644 --- a/apps/limiter/src/limiter.app.src +++ b/apps/limiter/src/limiter.app.src @@ -10,11 +10,8 @@ xrates_proto, machinery, woody, - how_are_you, % must be after ranch and before any woody usage scoper, % should be before any scoper event handler usage - erl_health, - prometheus, - prometheus_cowboy + erl_health ]}, {mod, {limiter, []}}, {env, []} diff --git a/apps/limiter/test/lim_client.erl b/apps/limiter/test/lim_client.erl index 08066d94..5a14e139 100644 --- a/apps/limiter/test/lim_client.erl +++ b/apps/limiter/test/lim_client.erl @@ -1,7 +1,6 @@ -module(lim_client). -include_lib("limiter_proto/include/lim_limiter_thrift.hrl"). --include_lib("limiter_proto/include/lim_configurator_thrift.hrl"). -export([new/0]). -export([get/3]). @@ -18,7 +17,8 @@ -type limit_change() :: lim_limiter_thrift:'LimitChange'(). -type limit_context() :: lim_limiter_thrift:'LimitContext'(). -type clock() :: lim_limiter_thrift:'Clock'(). --type limit_config_params() :: lim_limiter_config_thrift:'LimitCreateParams'(). +-type legacy_create_params() :: lim_configurator_thrift:'LimitCreateParams'(). +-type limit_config_params() :: lim_limiter_config_thrift:'LimitConfigParams'(). %%% API @@ -40,7 +40,7 @@ commit(LimitChange, Context, Client) -> %% --spec legacy_create_config(limit_config_params(), client()) -> woody:result() | no_return(). +-spec legacy_create_config(legacy_create_params(), client()) -> woody:result() | no_return(). legacy_create_config(LimitCreateParams, Client) -> call_configurator('CreateLegacy', {LimitCreateParams}, Client). diff --git a/apps/limiter/test/lim_configurator_SUITE.erl b/apps/limiter/test/lim_configurator_SUITE.erl index c750a8b3..0bfff38c 100644 --- a/apps/limiter/test/lim_configurator_SUITE.erl +++ b/apps/limiter/test/lim_configurator_SUITE.erl @@ -17,13 +17,14 @@ -export([create_config/1]). -export([get_config/1]). +-type group_name() :: atom(). -type test_case_name() :: atom(). -define(RATE_SOURCE_ID, <<"dummy_source_id">>). %% tests descriptions --spec all() -> [test_case_name()]. +-spec all() -> [{group, group_name()}]. all() -> [ {group, default} @@ -60,14 +61,13 @@ init_per_suite(Config) -> -spec end_per_suite(config()) -> _. end_per_suite(Config) -> - [application:stop(App) || App <- proplists:get_value(apps, Config)], - Config. + _ = [application:stop(App) || App <- proplists:get_value(apps, Config)]. -spec init_per_testcase(test_case_name(), config()) -> config(). init_per_testcase(_Name, C) -> C. --spec end_per_testcase(test_case_name(), config()) -> config(). +-spec end_per_testcase(test_case_name(), config()) -> ok. end_per_testcase(_Name, _C) -> ok. diff --git a/apps/limiter/test/lim_mock_service.erl b/apps/limiter/test/lim_mock_service.erl index ba2298a6..f9585cb8 100644 --- a/apps/limiter/test/lim_mock_service.erl +++ b/apps/limiter/test/lim_mock_service.erl @@ -4,6 +4,10 @@ -export([handle_function/4]). --spec handle_function(woody:func(), woody:args(), woody_context:ctx(), #{}) -> {ok, term()}. +-type opts() :: #{ + function := fun((woody:func(), woody:args()) -> woody:result()) +}. + +-spec handle_function(woody:func(), woody:args(), woody_context:ctx(), opts()) -> {ok, term()}. handle_function(FunName, Args, _, #{function := Fun}) -> Fun(FunName, Args). diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index 7442091e..7b9b7c1e 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -26,13 +26,14 @@ -export([refund_ok/1]). -export([get_config_ok/1]). +-type group_name() :: atom(). -type test_case_name() :: atom(). -define(RATE_SOURCE_ID, <<"dummy_source_id">>). %% tests descriptions --spec all() -> [test_case_name()]. +-spec all() -> [{group, group_name()}]. all() -> [ {group, default} @@ -84,16 +85,16 @@ init_per_suite(Config) -> -spec end_per_suite(config()) -> _. end_per_suite(Config) -> - [application:stop(App) || App <- proplists:get_value(apps, Config)], + _ = [application:stop(App) || App <- proplists:get_value(apps, Config)], Config. -spec init_per_testcase(test_case_name(), config()) -> config(). init_per_testcase(_Name, C) -> [{test_sup, lim_mock:start_mocked_service_sup()} | C]. --spec end_per_testcase(test_case_name(), config()) -> config(). +-spec end_per_testcase(test_case_name(), config()) -> ok. end_per_testcase(_Name, C) -> - lim_mock:stop_mocked_service_sup(?config(test_sup, C)), + _ = lim_mock:stop_mocked_service_sup(?config(test_sup, C)), ok. %% @@ -101,7 +102,7 @@ end_per_testcase(_Name, C) -> -spec commit_with_default_exchange(config()) -> _. commit_with_default_exchange(C) -> Rational = #base_Rational{p = 1000000, q = 100}, - mock_exchange(Rational, C), + _ = mock_exchange(Rational, C), ID = lim_time:to_rfc3339(lim_time:now()), #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), Context = #limiter_context_LimitContext{ @@ -128,7 +129,7 @@ commit_with_default_exchange(C) -> -spec partial_commit_with_exchange(config()) -> _. partial_commit_with_exchange(C) -> Rational = #base_Rational{p = 800000, q = 100}, - mock_exchange(Rational, C), + _ = mock_exchange(Rational, C), ID = lim_time:to_rfc3339(lim_time:now()), #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), Context = #limiter_context_LimitContext{ @@ -161,7 +162,7 @@ partial_commit_with_exchange(C) -> -spec commit_with_exchange(config()) -> _. commit_with_exchange(C) -> Rational = #base_Rational{p = 1000000, q = 100}, - mock_exchange(Rational, C), + _ = mock_exchange(Rational, C), ID = lim_time:to_rfc3339(lim_time:now()), #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), Context = #limiter_context_LimitContext{ @@ -188,7 +189,7 @@ commit_with_exchange(C) -> -spec get_rate(config()) -> _. get_rate(C) -> Rational = #base_Rational{p = 10, q = 10}, - mock_exchange(Rational, C), + _ = mock_exchange(Rational, C), Request = #rate_ConversionRequest{ source = <<"RUB">>, destination = <<"USD">>, diff --git a/compose.yml b/compose.yml new file mode 100644 index 00000000..e82f70af --- /dev/null +++ b/compose.yml @@ -0,0 +1,60 @@ +version: '3' +services: + + testrunner: + image: $DEV_IMAGE_TAG + build: + dockerfile: Dockerfile.dev + context: . + args: + OTP_VERSION: $OTP_VERSION + THRIFT_VERSION: $THRIFT_VERSION + volumes: + - .:$PWD + hostname: $SERVICE_NAME + working_dir: $PWD + command: /sbin/init + depends_on: + machinegun: + condition: service_healthy + shumway: + condition: service_healthy + + machinegun: + image: ghcr.io/valitydev/machinegun:sha-7e785cd + volumes: + - ./test/machinegun/config.yaml:/opt/machinegun/etc/config.yaml + - ./test/log/machinegun:/var/log/machinegun + - ./test/machinegun/cookie:/opt/machinegun/etc/cookie + healthcheck: + test: "/opt/machinegun/bin/machinegun ping" + interval: 5s + timeout: 1s + retries: 10 + + shumway: + image: docker.io/rbkmoney/shumway:44eb989065b27be619acd16b12ebdb2288b46c36 + restart: unless-stopped + entrypoint: + - java + - -Xmx512m + - -jar + - /opt/shumway/shumway.jar + - --spring.datasource.url=jdbc:postgresql://shumway-db:5432/shumway + - --spring.datasource.username=postgres + - --spring.datasource.password=postgres + - --management.metrics.export.statsd.enabled=false + depends_on: + - shumway-db + healthcheck: + test: "curl http://localhost:8022/actuator/health" + interval: 5s + timeout: 1s + retries: 20 + + shumway-db: + image: docker.io/library/postgres:9.6 + environment: + - POSTGRES_DB=shumway + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres diff --git a/config/sys.config b/config/sys.config index 17880989..9751edcd 100644 --- a/config/sys.config +++ b/config/sys.config @@ -44,7 +44,6 @@ {shutdown_timeout, 1000}, {woody_event_handlers, [ - hay_woody_event_handler, {scoper_woody_event_handler, #{ event_handler_opts => #{ formatter_opts => #{ @@ -77,12 +76,8 @@ {logger, [ {handler, default, logger_std_h, #{ level => error, - config => #{ - type => standard_error - }, - formatter => {logger_formatter, #{ - depth => 30 - }} + config => #{type => standard_error}, + formatter => {logger_formatter, #{depth => 30}} }}, {handler, console, logger_std_h, #{ config => #{ @@ -93,16 +88,6 @@ ]} ]}, - % {how_are_you, [ - % {metrics_publishers, [ - % % {hay_statsd_publisher, #{ - % % key_prefix => <<"limiter.">>, - % % host => "localhost", - % % port => 8125 - % % }} - % ]} - % ]}, - {os_mon, [ % for better compatibility with busybox coreutils {disksup_posix_only, true} diff --git a/elvis.config b/elvis.config index 5a524d32..22ae88d4 100644 --- a/elvis.config +++ b/elvis.config @@ -1,50 +1,30 @@ [ {elvis, [ + {verbose, true}, {config, [ #{ - dirs => ["apps/*/src"], + dirs => ["apps/*/src", "apps/*/include"], filter => "*.erl", + ruleset => erl_files, rules => [ - {elvis_style, line_length, #{limit => 120, skip_comments => false}}, - {elvis_style, no_tabs}, - {elvis_style, no_trailing_whitespace}, - {elvis_style, macro_module_names}, - {elvis_style, operator_spaces, #{rules => [{right, ","}, {right, "++"}, {left, "++"}]}}, + {elvis_text_style, line_length, #{limit => 120}}, {elvis_style, nesting_level, #{level => 3}}, - {elvis_style, god_modules, #{limit => 25}}, - {elvis_style, no_if_expression}, - {elvis_style, invalid_dynamic_call, #{ignore => [elvis]}}, - {elvis_style, used_ignored_variable}, - {elvis_style, no_behavior_info}, - {elvis_style, module_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$"}}, - {elvis_style, function_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*$"}}, - {elvis_style, state_record_and_type}, - {elvis_style, no_spec_with_records}, - {elvis_style, dont_repeat_yourself, #{min_complexity => 10, ignore => [lim_config_machine]}}, - {elvis_style, no_debug_call, #{ignore => [elvis, elvis_utils]}} + {elvis_style, no_if_expression, disable} ] }, #{ dirs => ["apps/*/test"], filter => "*.erl", + ruleset => erl_files, rules => [ - {elvis_style, line_length, #{limit => 120, skip_comments => false}}, - {elvis_style, no_tabs}, - {elvis_style, no_trailing_whitespace}, - {elvis_style, macro_module_names}, - {elvis_style, operator_spaces, #{rules => [{right, ","}, {right, "++"}, {left, "++"}]}}, - {elvis_style, nesting_level, #{level => 3}}, - {elvis_style, god_modules, #{limit => 25}}, - {elvis_style, no_if_expression}, - {elvis_style, invalid_dynamic_call, #{ignore => [elvis]}}, - {elvis_style, used_ignored_variable}, - {elvis_style, no_behavior_info}, - {elvis_style, module_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*(_SUITE)?$"}}, - {elvis_style, function_naming_convention, #{regex => "^([a-z][a-z0-9]*_?)*$"}}, - {elvis_style, state_record_and_type}, - {elvis_style, no_spec_with_records}, - {elvis_style, dont_repeat_yourself, #{min_complexity => 10, ignore => [lim_turnover_SUITE]}}, - {elvis_style, no_debug_call, #{ignore => [elvis, elvis_utils]}} + {elvis_text_style, line_length, #{limit => 120}}, + % We want to use `ct:pal/2` and friends in test code. + {elvis_style, no_debug_call, disable}, + % Assert macros can trigger use of ignored binding, yet we want them for better + % readability. + {elvis_style, used_ignored_variable, disable}, + % Tests are usually more comprehensible when a bit more verbose. + {elvis_style, dont_repeat_yourself, #{min_complexity => 20}} ] }, #{ @@ -58,30 +38,24 @@ ruleset => elvis_config }, #{ - dirs => ["."], - filter => "rebar.config", - rules => [ - {elvis_style, line_length, #{limit => 120, skip_comments => false}}, - {elvis_style, no_tabs}, - {elvis_style, no_trailing_whitespace} - ] - }, - #{ - dirs => ["."], + dirs => [".", "apps/*"], filter => "rebar.config", + ruleset => rebar_config, rules => [ - {elvis_style, line_length, #{limit => 120, skip_comments => false}}, - {elvis_style, no_tabs}, - {elvis_style, no_trailing_whitespace} + {elvis_text_style, line_length, #{limit => 120}}, + {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} ] }, #{ - dirs => ["src"], + dirs => ["apps/*/src"], filter => "*.app.src", rules => [ - {elvis_style, line_length, #{limit => 120, skip_comments => false}}, - {elvis_style, no_tabs}, - {elvis_style, no_trailing_whitespace} + {elvis_text_style, line_length, #{limit => 120}}, + {elvis_text_style, no_tabs}, + {elvis_text_style, no_trailing_whitespace} ] } ]} diff --git a/rebar.config b/rebar.config index 86e90744..7cdc517e 100644 --- a/rebar.config +++ b/rebar.config @@ -26,50 +26,16 @@ %% Common project dependencies. {deps, [ - {damsel, - {git, "https://github.com/rbkmoney/damsel.git", - {branch, "release/erlang/master"} - } - }, - {limiter_proto, - {git, "git@github.com:rbkmoney/limiter-proto.git", - {branch, "master"} - } - }, - {xrates_proto, - {git, "git@github.com:rbkmoney/xrates-proto.git", - {branch, "master"} - } - }, - {machinery, - {git, "https://github.com/rbkmoney/machinery.git", - {branch, "master"}} - }, - {erl_health, - {git, "https://github.com/rbkmoney/erlang-health.git", - {branch, "master"}} - }, - {genlib , - {git, "https://github.com/rbkmoney/genlib.git", - {branch, "master"}} - }, - {scoper, - {git, "https://github.com/rbkmoney/scoper.git", - {branch, "master"}} - }, - {how_are_you, - {git, "https://github.com/rbkmoney/how_are_you.git", - {branch, "master"}} - }, - {woody, - {git, "https://github.com/rbkmoney/woody_erlang.git", - {branch, "master"}} - }, - {prometheus, "4.6.0"}, - {prometheus_cowboy, "0.1.8"} + {damsel, {git, "https://github.com/valitydev/damsel.git", {branch, "master"}}}, + {limiter_proto, {git, "https://github.com/valitydev/limiter-proto.git", {branch, "master"}}}, + {xrates_proto, {git, "https://github.com/valitydev/xrates-proto.git", {branch, "master"}}}, + {machinery, {git, "https://github.com/valitydev/machinery.git", {branch, "master"}}}, + {erl_health, {git, "https://github.com/valitydev/erlang-health.git", {branch, "master"}}}, + {genlib, {git, "https://github.com/valitydev/genlib.git", {branch, "master"}}}, + {scoper, {git, "https://github.com/valitydev/scoper.git", {branch, "master"}}}, + {woody, {git, "https://github.com/valitydev/woody_erlang.git", {branch, "master"}}} ]}. - %% XRef checks {xref_checks, [ undefined_function_calls, @@ -78,9 +44,6 @@ deprecated_functions ]}. -%% Tests -{cover_enabled, true}. - %% Dialyzer static analyzing {dialyzer, [ {warnings, [ @@ -89,9 +52,6 @@ error_handling, race_conditions, unknown - % hardcore mode - % overspecs, - % underspecs ]}, {plt_apps, all_deps} ]}. @@ -99,37 +59,61 @@ {profiles, [ {prod, [ {deps, [ - {logger_logstash_formatter, - {git, "https://github.com/rbkmoney/logger_logstash_formatter.git", - {ref, "87e52c755"}} - }, % for introspection on production - {recon, "2.3.2"} + {recon, "2.5.2"}, + % Because of a dependency conflict, prometheus libs are only included in the prod profile for now + % https://github.com/project-fifo/rebar3_lint/issues/42 + % https://github.com/valitydev/hellgate/pull/2/commits/884724c1799703cee4d1033850fe32c17f986d9e + {prometheus, "4.8.1"}, + {prometheus_cowboy, "0.1.8"}, + {logger_logstash_formatter, + {git, "https://github.com/valitydev/logger_logstash_formatter.git", {ref, "2c7b716"}}}, + {iosetopts, {git, "https://github.com/valitydev/iosetopts.git", {ref, "edb445c"}}} ]}, {relx, [ {release, {limiter, "1.0.0"}, [ - {recon, load}, % tools for introspection - {runtime_tools, load}, % debugger - {tools, load}, % profiler - {logger_logstash_formatter, load}, % log formatter + iosetopts, + {recon, load}, + {runtime_tools, load}, + {tools, load}, + {logger_logstash_formatter, load}, + prometheus, + prometheus_cowboy, sasl, limiter ]}, + {mode, minimal}, {sys_config, "./config/sys.config"}, {vm_args, "./config/vm.args"}, - {mode, minimal}, {extended_start_script, true} ]} + ]}, + {test, [ + {cover_enabled, true}, + {deps, []}, + {dialyzer, [ + {plt_extra_apps, [eunit, common_test]} + ]} ]} ]}. -{plugins, [ - {erlfmt, "0.8.0"}, - {rebar3_thrift_compiler, - {git, "https://github.com/rbkmoney/rebar3_thrift_compiler.git", {tag, "0.3.1"}}} +{project_plugins, [ + {rebar3_lint, "1.0.1"}, + {erlfmt, "1.0.0"}, + {covertool, "2.0.4"} ]}. +%% Linter config. +{elvis_output_format, colors}. + {erlfmt, [ {print_width, 120}, - {files, "apps/*/{src,include,test}/*.{hrl,erl}"} + {files, ["apps/*/{src,include,test}/*.{hrl,erl}", "rebar.config", "elvis.config", "config/sys.config"]} +]}. + +{covertool, [ + {coverdata_files, [ + "eunit.coverdata", + "ct.coverdata" + ]} ]}. diff --git a/rebar.lock b/rebar.lock index 211e8883..60f4c74d 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,44 +1,34 @@ {"1.2.0", -[{<<"accept">>,{pkg,<<"accept">>,<<"0.3.5">>},2}, - {<<"bear">>,{pkg,<<"bear">>,<<"0.8.7">>},2}, - {<<"cache">>,{pkg,<<"cache">>,<<"2.2.0">>},1}, - {<<"certifi">>,{pkg,<<"certifi">>,<<"2.5.1">>},2}, +[{<<"cache">>,{pkg,<<"cache">>,<<"2.3.3">>},1}, + {<<"certifi">>,{pkg,<<"certifi">>,<<"2.8.0">>},2}, {<<"cg_mon">>, {git,"https://github.com/rbkmoney/cg_mon.git", {ref,"5a87a37694e42b6592d3b4164ae54e0e87e24e18"}}, 1}, - {<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.8.0">>},1}, - {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.9.1">>},2}, + {<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.9.0">>},1}, + {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.11.0">>},2}, {<<"damsel">>, - {git,"https://github.com/rbkmoney/damsel.git", - {ref,"9e0e884bfeaf8ad1cadd01802200f4e204cf27e3"}}, + {git,"https://github.com/valitydev/damsel.git", + {ref,"1d60b20f2136938c43902dd38a80ae70f03e4a14"}}, 0}, {<<"erl_health">>, - {git,"https://github.com/rbkmoney/erlang-health.git", - {ref,"982af88738ca062eea451436d830eef8c1fbe3f9"}}, + {git,"https://github.com/valitydev/erlang-health.git", + {ref,"5958e2f35cd4d09f40685762b82b82f89b4d9333"}}, 0}, - {<<"folsom">>, - {git,"https://github.com/folsom-project/folsom.git", - {ref,"eeb1cc467eb64bd94075b95b8963e80d8b4df3df"}}, - 1}, {<<"genlib">>, - {git,"https://github.com/rbkmoney/genlib.git", - {ref,"4565a8d73f34a0b78cca32c9cd2b97d298bdadf8"}}, - 0}, - {<<"gproc">>,{pkg,<<"gproc">>,<<"0.8.0">>},1}, - {<<"hackney">>,{pkg,<<"hackney">>,<<"1.15.2">>},1}, - {<<"how_are_you">>, - {git,"https://github.com/rbkmoney/how_are_you.git", - {ref,"29f9d3d7c35f7a2d586c8571f572838df5ec91dd"}}, + {git,"https://github.com/valitydev/genlib.git", + {ref,"82c5ff3866e3019eb347c7f1d8f1f847bed28c10"}}, 0}, - {<<"idna">>,{pkg,<<"idna">>,<<"6.0.0">>},2}, - {<<"jsx">>,{pkg,<<"jsx">>,<<"3.0.0">>},1}, + {<<"gproc">>,{pkg,<<"gproc">>,<<"0.9.0">>},1}, + {<<"hackney">>,{pkg,<<"hackney">>,<<"1.18.0">>},1}, + {<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},2}, + {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},1}, {<<"limiter_proto">>, - {git,"git@github.com:rbkmoney/limiter-proto.git", + {git,"https://github.com/valitydev/limiter-proto.git", {ref,"2e2cdab859222648e389dc74867b5273e73583e9"}}, 0}, {<<"machinery">>, - {git,"https://github.com/rbkmoney/machinery.git", + {git,"https://github.com/valitydev/machinery.git", {ref,"db7c94b9913451e9558afa19f2fe77bf48d391da"}}, 0}, {<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},2}, @@ -47,72 +37,59 @@ {ref,"d814d6948d4ff13f6f41d12c6613f59c805750b2"}}, 1}, {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.2.0">>},2}, - {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.0">>},3}, - {<<"prometheus">>,{pkg,<<"prometheus">>,<<"4.6.0">>},0}, - {<<"prometheus_cowboy">>,{pkg,<<"prometheus_cowboy">>,<<"0.1.8">>},0}, - {<<"prometheus_httpd">>,{pkg,<<"prometheus_httpd">>,<<"2.1.11">>},1}, - {<<"ranch">>,{pkg,<<"ranch">>,<<"1.7.1">>},2}, + {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.1">>},2}, + {<<"ranch">>,{pkg,<<"ranch">>,<<"1.8.0">>},2}, {<<"scoper">>, - {git,"https://github.com/rbkmoney/scoper.git", - {ref,"89a973bf3cedc5a48c9fd89d719d25e79fe10027"}}, + {git,"https://github.com/valitydev/scoper.git", + {ref,"7f3183df279bc8181efe58dafd9cae164f495e6f"}}, 0}, {<<"snowflake">>, - {git,"https://github.com/rbkmoney/snowflake.git", - {ref,"7f379ad5e389e1c96389a8d60bae8117965d6a6d"}}, + {git,"https://github.com/valitydev/snowflake.git", + {ref,"de159486ef40cec67074afe71882bdc7f7deab72"}}, 1}, - {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.5">>},2}, + {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.6">>},2}, {<<"thrift">>, - {git,"https://github.com/rbkmoney/thrift_erlang.git", - {ref,"846a0819d9b6d09d0c31f160e33a78dbad2067b4"}}, + {git,"https://github.com/valitydev/thrift_erlang.git", + {ref,"c280ff266ae1c1906fb0dcee8320bb8d8a4a3c75"}}, 1}, - {<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.4.1">>},3}, + {<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},2}, {<<"woody">>, - {git,"https://github.com/rbkmoney/woody_erlang.git", - {ref,"58f56b462429ab1fee65e1bdb34b73512406ba00"}}, + {git,"https://github.com/valitydev/woody_erlang.git", + {ref,"3ddacb9296691aa8ddad05498d1fd34b078eda75"}}, 0}, {<<"xrates_proto">>, - {git,"git@github.com:rbkmoney/xrates-proto.git", + {git,"https://github.com/valitydev/xrates-proto.git", {ref,"66906cd0a8ee9a00fb447f3c3e5b09d3c6fab942"}}, 0}]}. [ {pkg_hash,[ - {<<"accept">>, <<"B33B127ABCA7CC948BBE6CAA4C263369ABF1347CFA9D8E699C6D214660F10CD1">>}, - {<<"bear">>, <<"16264309AE5D005D03718A5C82641FCC259C9E8F09ADEB6FD79CA4271168656F">>}, - {<<"cache">>, <<"3C11DBF4CD8FCD5787C95A5FB2A04038E3729CFCA0386016EEA8C953AB48A5AB">>}, - {<<"certifi">>, <<"867CE347F7C7D78563450A18A6A28A8090331E77FA02380B4A21962A65D36EE5">>}, - {<<"cowboy">>, <<"F3DC62E35797ECD9AC1B50DB74611193C29815401E53BAC9A5C0577BD7BC667D">>}, - {<<"cowlib">>, <<"61A6C7C50CF07FDD24B2F45B89500BB93B6686579B069A89F88CB211E1125C78">>}, - {<<"gproc">>, <<"CEA02C578589C61E5341FCE149EA36CCEF236CC2ECAC8691FBA408E7EA77EC2F">>}, - {<<"hackney">>, <<"07E33C794F8F8964EE86CEBEC1A8ED88DB5070E52E904B8F12209773C1036085">>}, - {<<"idna">>, <<"689C46CBCDF3524C44D5F3DDE8001F364CD7608A99556D8FBD8239A5798D4C10">>}, - {<<"jsx">>, <<"20A170ABD4335FC6DB24D5FAD1E5D677C55DADF83D1B20A8A33B5FE159892A39">>}, + {<<"cache">>, <<"B23A5FE7095445A88412A6E614C933377E0137B44FFED77C9B3FEF1A731A20B2">>}, + {<<"certifi">>, <<"D4FB0A6BB20B7C9C3643E22507E42F356AC090A1DCEA9AB99E27E0376D695EBA">>}, + {<<"cowboy">>, <<"865DD8B6607E14CF03282E10E934023A1BD8BE6F6BACF921A7E2A96D800CD452">>}, + {<<"cowlib">>, <<"0B9FF9C346629256C42EBE1EEB769A83C6CB771A6EE5960BD110AB0B9B872063">>}, + {<<"gproc">>, <<"853CCB7805E9ADA25D227A157BA966F7B34508F386A3E7E21992B1B484230699">>}, + {<<"hackney">>, <<"C4443D960BB9FBA6D01161D01CD81173089686717D9490E5D3606644C48D121F">>}, + {<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>}, + {<<"jsx">>, <<"D12516BAA0BB23A59BB35DCCAF02A1BD08243FCBB9EFE24F2D9D056CCFF71268">>}, {<<"metrics">>, <<"25F094DEA2CDA98213CECC3AEFF09E940299D950904393B2A29D191C346A8486">>}, {<<"mimerl">>, <<"67E2D3F571088D5CFD3E550C383094B47159F3EEE8FFA08E64106CDF5E981BE3">>}, - {<<"parse_trans">>, <<"09765507A3C7590A784615CFD421D101AEC25098D50B89D7AA1D66646BC571C1">>}, - {<<"prometheus">>, <<"20510F381DB1CCAB818B4CF2FAC5FA6AB5CC91BC364A154399901C001465F46F">>}, - {<<"prometheus_cowboy">>, <<"CFCE0BC7B668C5096639084FCD873826E6220EA714BF60A716F5BD080EF2A99C">>}, - {<<"prometheus_httpd">>, <<"F616ED9B85B536B195D94104063025A91F904A4CFC20255363F49A197D96C896">>}, - {<<"ranch">>, <<"6B1FAB51B49196860B733A49C07604465A47BDB78AA10C1C16A3D199F7F8C881">>}, - {<<"ssl_verify_fun">>, <<"6EAF7AD16CB568BB01753DBBD7A95FF8B91C7979482B95F38443FE2C8852A79B">>}, - {<<"unicode_util_compat">>, <<"D869E4C68901DD9531385BB0C8C40444EBF624E60B6962D95952775CAC5E90CD">>}]}, + {<<"parse_trans">>, <<"16328AB840CC09919BD10DAB29E431DA3AF9E9E7E7E6F0089DD5A2D2820011D8">>}, + {<<"ranch">>, <<"8C7A100A139FD57F17327B6413E4167AC559FBC04CA7448E9BE9057311597A1D">>}, + {<<"ssl_verify_fun">>, <<"CF344F5692C82D2CD7554F5EC8FD961548D4FD09E7D22F5B62482E5AEAEBD4B0">>}, + {<<"unicode_util_compat">>, <<"BC84380C9AB48177092F43AC89E4DFA2C6D62B40B8BD132B1059ECC7232F9A78">>}]}, {pkg_hash_ext,[ - {<<"accept">>, <<"11B18C220BCC2EAB63B5470C038EF10EB6783BCB1FCDB11AA4137DEFA5AC1BB8">>}, - {<<"bear">>, <<"534217DCE6A719D59E54FB0EB7A367900DBFC5F85757E8C1F94269DF383F6D9B">>}, - {<<"cache">>, <<"3E7D6706DE5DF76C4D71C895B4BE62B01C3DE6EDB63197035E465C3BCE63F19B">>}, - {<<"certifi">>, <<"805ABD97539CAF89EC6D4732C91E62BA9DA0CDA51AC462380BBD28EE697A8C42">>}, - {<<"cowboy">>, <<"4643E4FBA74AC96D4D152C75803DE6FAD0B3FA5DF354C71AFDD6CBEEB15FAC8A">>}, - {<<"cowlib">>, <<"E4175DC240A70D996156160891E1C62238EDE1729E45740BDD38064DAD476170">>}, - {<<"gproc">>, <<"580ADAFA56463B75263EF5A5DF4C86AF321F68694E7786CB057FD805D1E2A7DE">>}, - {<<"hackney">>, <<"E0100F8EF7D1124222C11AD362C857D3DF7CB5F4204054F9F0F4A728666591FC">>}, - {<<"idna">>, <<"4BDD305EB64E18B0273864920695CB18D7A2021F31A11B9C5FBCD9A253F936E2">>}, - {<<"jsx">>, <<"37BECA0435F5CA8A2F45F76A46211E76418FBEF80C36F0361C249FC75059DC6D">>}, + {<<"cache">>, <<"44516CE6FA03594D3A2AF025DD3A87BFE711000EB730219E1DDEFC816E0AA2F4">>}, + {<<"certifi">>, <<"6AC7EFC1C6F8600B08D625292D4BBF584E14847CE1B6B5C44D983D273E1097EA">>}, + {<<"cowboy">>, <<"2C729F934B4E1AA149AFF882F57C6372C15399A20D54F65C8D67BEF583021BDE">>}, + {<<"cowlib">>, <<"2B3E9DA0B21C4565751A6D4901C20D1B4CC25CBB7FD50D91D2AB6DD287BC86A9">>}, + {<<"gproc">>, <<"587E8AF698CCD3504CF4BA8D90F893EDE2B0F58CABB8A916E2BF9321DE3CF10B">>}, + {<<"hackney">>, <<"9AFCDA620704D720DB8C6A3123E9848D09C87586DC1C10479C42627B905B5C5E">>}, + {<<"idna">>, <<"92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA">>}, + {<<"jsx">>, <<"0C5CC8FDC11B53CC25CF65AC6705AD39E54ECC56D1C22E4ADB8F5A53FB9427F3">>}, {<<"metrics">>, <<"69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16">>}, {<<"mimerl">>, <<"F278585650AA581986264638EBF698F8BB19DF297F66AD91B18910DFC6E19323">>}, - {<<"parse_trans">>, <<"17EF63ABDE837AD30680EA7F857DD9E7CED9476CDD7B0394432AF4BFC241B960">>}, - {<<"prometheus">>, <<"4905FD2992F8038ECCD7AA0CD22F40637ED618C0BED1F75C05AACEC15B7545DE">>}, - {<<"prometheus_cowboy">>, <<"BA286BECA9302618418892D37BCD5DC669A6CC001F4EB6D6AF85FF81F3F4F34C">>}, - {<<"prometheus_httpd">>, <<"0BBE831452CFDF9588538EB2F570B26F30C348ADAE5E95A7D87F35A5910BCF92">>}, - {<<"ranch">>, <<"451D8527787DF716D99DC36162FCA05934915DB0B6141BBDAC2EA8D3C7AFC7D7">>}, - {<<"ssl_verify_fun">>, <<"13104D7897E38ED7F044C4DE953A6C28597D1C952075EB2E328BC6D6F2BFC496">>}, - {<<"unicode_util_compat">>, <<"1D1848C40487CDB0B30E8ED975E34E025860C02E419CB615D255849F3427439D">>}]} + {<<"parse_trans">>, <<"07CD9577885F56362D414E8C4C4E6BDF10D43A8767ABB92D24CBE8B24C54888B">>}, + {<<"ranch">>, <<"49FBCFD3682FAB1F5D109351B61257676DA1A2FDBE295904176D5E521A2DDFE5">>}, + {<<"ssl_verify_fun">>, <<"BDB0D2471F453C88FF3908E7686F86F9BE327D065CC1EC16FA4540197EA04680">>}, + {<<"unicode_util_compat">>, <<"25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521">>}]} ]. diff --git a/test/machinegun/config.yaml b/test/machinegun/config.yaml index 319ce7f8..f039c80b 100644 --- a/test/machinegun/config.yaml +++ b/test/machinegun/config.yaml @@ -3,6 +3,9 @@ service_name: machinegun erlang: secret_cookie_file: "/opt/machinegun/etc/cookie" +logging: + out_type: stdout + namespaces: lim/config_v1: processor: From 3b10d1cbf3e664d6d0670518d236b0cf526170f2 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Tue, 19 Apr 2022 18:03:09 +0300 Subject: [PATCH 17/54] TD-275: Ensure limit changes handled idempotently (#3) While also involving minimal number of interactions with accounter in order to improve runtime behaviour. * Add annotated testcases for changed exception behavior * Test (kinda) that multirange limits work --- apps/limiter/src/lim_body.erl | 5 +- apps/limiter/src/lim_config_machine.erl | 8 +- apps/limiter/src/lim_handler.erl | 6 - apps/limiter/src/lim_p_transfer.erl | 112 ++------ apps/limiter/src/lim_range_machine.erl | 113 ++++---- apps/limiter/src/lim_rates.erl | 8 +- apps/limiter/src/lim_turnover_processor.erl | 178 +++++++----- apps/limiter/test/lim_ct_helper.hrl | 13 +- apps/limiter/test/lim_mock.erl | 2 - apps/limiter/test/lim_turnover_SUITE.erl | 288 +++++++++++++------- elvis.config | 1 + 11 files changed, 398 insertions(+), 336 deletions(-) diff --git a/apps/limiter/src/lim_body.erl b/apps/limiter/src/lim_body.erl index 903d448f..d5cd1340 100644 --- a/apps/limiter/src/lim_body.erl +++ b/apps/limiter/src/lim_body.erl @@ -1,8 +1,5 @@ -module(lim_body). --include_lib("limiter_proto/include/lim_base_thrift.hrl"). --include_lib("limiter_proto/include/lim_limiter_thrift.hrl"). - -export([get_body/3]). -export([create_body_from_cash/2]). @@ -17,7 +14,7 @@ -type currency() :: lim_base_thrift:'CurrencySymbolicCode'(). -type config() :: lim_config_machine:config(). -type body_type() :: full | partial. --type get_body_error() :: notfound | lim_rates:convertation_error(). +-type get_body_error() :: notfound | lim_rates:conversion_error(). -export_type([t/0]). -export_type([cash/0]). diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index 2fe204e5..987240dc 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -1,7 +1,6 @@ -module(lim_config_machine). -include_lib("limiter_proto/include/lim_limiter_thrift.hrl"). --include_lib("limiter_proto/include/lim_base_thrift.hrl"). %% Accessors @@ -9,6 +8,7 @@ -export([id/1]). -export([description/1]). -export([body_type/1]). +-export([currency/1]). -export([started_at/1]). -export([shard_size/1]). -export([time_range_type/1]). @@ -183,6 +183,12 @@ description(_) -> body_type(#{body_type := BodyType}) -> BodyType. +-spec currency(config()) -> currency() | undefined. +currency(#{body_type := {cash, Currency}}) -> + Currency; +currency(#{body_type := amount}) -> + undefined. + -spec started_at(config()) -> timestamp(). started_at(#{started_at := Value}) -> Value. diff --git a/apps/limiter/src/lim_handler.erl b/apps/limiter/src/lim_handler.erl index ce83b7e4..0fceda94 100644 --- a/apps/limiter/src/lim_handler.erl +++ b/apps/limiter/src/lim_handler.erl @@ -86,8 +86,6 @@ handle_function_('Rollback', {LimitChange = ?LIMIT_CHANGE(LimitID), Clock, Conte end. -spec handle_get_error(_) -> no_return(). -handle_get_error({_, {limit, notfound}}) -> - woody_error:raise(business, #limiter_LimitNotFound{}); handle_get_error({_, {range, notfound}}) -> woody_error:raise(business, #limiter_LimitNotFound{}); handle_get_error(Error) -> @@ -102,16 +100,12 @@ handle_hold_error(Error) -> -spec handle_commit_error(_) -> no_return(). handle_commit_error({_, {forbidden_operation_amount, Error}}) -> handle_forbidden_operation_amount_error(Error); -handle_commit_error({_, {plan, notfound}}) -> - woody_error:raise(business, #limiter_LimitChangeNotFound{}); handle_commit_error({_, {invalid_request, Errors}}) -> woody_error:raise(business, #limiter_base_InvalidRequest{errors = Errors}); handle_commit_error(Error) -> handle_default_error(Error). -spec handle_rollback_error(_) -> no_return(). -handle_rollback_error({_, {plan, notfound}}) -> - woody_error:raise(business, #limiter_LimitChangeNotFound{}); handle_rollback_error({_, {invalid_request, Errors}}) -> woody_error:raise(business, #limiter_base_InvalidRequest{errors = Errors}); handle_rollback_error(Error) -> diff --git a/apps/limiter/src/lim_p_transfer.erl b/apps/limiter/src/lim_p_transfer.erl index 666465ae..2e1637b6 100644 --- a/apps/limiter/src/lim_p_transfer.erl +++ b/apps/limiter/src/lim_p_transfer.erl @@ -1,94 +1,34 @@ -module(lim_p_transfer). -include_lib("damsel/include/dmsl_accounter_thrift.hrl"). --include_lib("damsel/include/dmsl_base_thrift.hrl"). --export([construct_postings/3]). --export([reverse_postings/1]). --export([assert_partial_posting_amount/2]). +-export([construct_posting/2]). +-export([reverse_posting/1]). --type amount() :: integer(). --type currency() :: binary(). --type account_id() :: lim_accounting:account_id(). -type posting() :: lim_accounting:posting(). -type body() :: lim_body:t(). --type forbidden_operation_amount_error() :: #{ - type := positive | negative, - partial := amount(), - full := amount(), - currency := currency() -}. - --export_type([forbidden_operation_amount_error/0]). - --spec construct_postings(account_id(), account_id(), body()) -> [posting()]. -construct_postings(AccountFrom, AccountTo, {cash, #{amount := Amount, currency := Currency}}) -> - [ - #accounter_Posting{ - from_id = AccountFrom, - to_id = AccountTo, - amount = Amount, - currency_sym_code = Currency, - description = <<>> - } - ]; -construct_postings(AccountFrom, AccountTo, {amount, Amount}) -> - [ - #accounter_Posting{ - from_id = AccountFrom, - to_id = AccountTo, - amount = Amount, - currency_sym_code = lim_accounting:get_default_currency(), - description = <<>> - } - ]. - --spec reverse_postings([posting()]) -> [posting()]. -reverse_postings(Postings) -> - [ - Posting#accounter_Posting{ - from_id = AccountTo, - to_id = AccountFrom - } - || Posting = #accounter_Posting{from_id = AccountFrom, to_id = AccountTo} <- Postings - ]. - --spec assert_partial_posting_amount([posting()], [posting()]) -> ok | {error, forbidden_operation_amount_error()}. -assert_partial_posting_amount( - [#accounter_Posting{amount = Partial, currency_sym_code = Currency} | _], - [#accounter_Posting{amount = Full, currency_sym_code = Currency} | _] -) -> - compare_amount(Partial, Full, Currency); -assert_partial_posting_amount( - [#accounter_Posting{amount = Partial, currency_sym_code = PartialCurrency} | _], - [#accounter_Posting{amount = Full, currency_sym_code = FullCurrency} | _] -) -> - erlang:error({invalid_partial_cash, {Partial, PartialCurrency}, {Full, FullCurrency}}). - -compare_amount(Partial, Full, Currency) when Full > 0 -> - case Partial =< Full of - true -> - ok; - false -> - {error, - {forbidden_operation_amount, #{ - type => positive, - partial => Partial, - full => Full, - currency => Currency - }}} - end; -compare_amount(Partial, Full, Currency) when Full < 0 -> - case Partial >= Full of - true -> - ok; - false -> - {error, - {forbidden_operation_amount, #{ - type => negative, - partial => Partial, - full => Full, - currency => Currency - }}} - end. +-spec construct_posting(lim_range_machine:time_range_ext(), body()) -> posting(). +construct_posting(#{account_id_from := From, account_id_to := To}, {cash, #{amount := Amount, currency := Currency}}) -> + #accounter_Posting{ + from_id = From, + to_id = To, + amount = Amount, + currency_sym_code = Currency, + description = <<>> + }; +construct_posting(#{account_id_from := From, account_id_to := To}, {amount, Amount}) -> + #accounter_Posting{ + from_id = From, + to_id = To, + amount = Amount, + currency_sym_code = lim_accounting:get_default_currency(), + description = <<>> + }. + +-spec reverse_posting(posting()) -> posting(). +reverse_posting(Posting = #accounter_Posting{from_id = AccountFrom, to_id = AccountTo}) -> + Posting#accounter_Posting{ + from_id = AccountTo, + to_id = AccountFrom + }. diff --git a/apps/limiter/src/lim_range_machine.erl b/apps/limiter/src/lim_range_machine.erl index eac9093d..0660f3cc 100644 --- a/apps/limiter/src/lim_range_machine.erl +++ b/apps/limiter/src/lim_range_machine.erl @@ -11,11 +11,9 @@ %% API -export([get/2]). --export([ensure_exist/2]). +-export([ensure_exists/3]). -export([get_range/2]). -export([get_range_balance/3]). --export([ensure_range_exist/3]). --export([ensure_range_exist_in_state/3]). %% Machinery callbacks @@ -38,13 +36,13 @@ -type woody_context() :: woody_context:ctx(). -type lim_context() :: lim_context:t(). -type timestamp() :: lim_config_machine:timestamp(). --type lim_id() :: lim_config_machine:lim_id(). +-type id() :: binary(). -type time_range_type() :: lim_config_machine:time_range_type(). -type time_range() :: lim_config_machine:time_range(). -type currency() :: lim_config_machine:currency(). -type limit_range_state() :: #{ - id := lim_id(), + id := id(), type := time_range_type(), created_at := timestamp(), currency => currency(), @@ -59,7 +57,7 @@ | {time_range_created, time_range_ext()}. -type limit_range() :: #{ - id := lim_id(), + id := id(), type := time_range_type(), created_at := timestamp(), currency => currency() @@ -73,7 +71,7 @@ }. -type create_params() :: #{ - id := lim_id(), + id := id(), type := time_range_type(), created_at := timestamp(), currency => currency() @@ -82,16 +80,18 @@ -type range_call() :: {add_range, time_range()}. +-export_type([time_range_ext/0]). + -export_type([timestamped_event/1]). -export_type([event/0]). -define(NS, 'lim/range_v1'). --import(lim_pipeline, [do/1, unwrap/1, unwrap/2]). +-import(lim_pipeline, [do/1, unwrap/1]). %% Accessors --spec id(limit_range_state()) -> lim_id(). +-spec id(limit_range_state()) -> id(). id(State) -> maps:get(id, State). @@ -117,61 +117,47 @@ currency(_State) -> %%% API --spec get(lim_id(), lim_context()) -> {ok, limit_range_state()} | {error, notfound}. +-spec get(id(), lim_context()) -> {ok, limit_range_state()} | {error, notfound}. get(ID, LimitContext) -> {ok, WoodyCtx} = lim_context:woody_context(LimitContext), get_state(ID, WoodyCtx). --spec ensure_exist(create_params(), lim_context()) -> {ok, limit_range_state()}. -ensure_exist(Params = #{id := ID}, LimitContext) -> +-spec ensure_exists(create_params(), time_range(), lim_context()) -> {ok, time_range_ext()}. +ensure_exists(Params = #{id := ID, currency := Currency}, TimeRange, LimitContext) -> {ok, WoodyCtx} = lim_context:woody_context(LimitContext), case get_state(ID, WoodyCtx) of {ok, State} -> - {ok, State}; + ensure_range_exist_in_state(TimeRange, State, WoodyCtx); + {error, notfound} -> + _ = start(ID, Params, [new_time_range_ext(TimeRange, Currency, WoodyCtx)], WoodyCtx), + {ok, State} = get_state(ID, WoodyCtx), + get_range(TimeRange, State) + end. + +-spec ensure_range_exist_in_state(time_range(), limit_range_state(), woody_context()) -> {ok, time_range_ext()}. +ensure_range_exist_in_state(TimeRange, State, WoodyCtx) -> + case find_time_range(TimeRange, ranges(State)) of {error, notfound} -> - _ = start(ID, Params, WoodyCtx), - case get_state(ID, WoodyCtx) of - {ok, State} -> - {ok, State}; - {error, notfound} -> - erlang:error({cant_get_after_start, ID}) - end + call(id(State), {add_range, TimeRange}, WoodyCtx); + {ok, Range} -> + {ok, Range} end. -spec get_range(time_range(), limit_range_state()) -> {ok, time_range_ext()} | {error, notfound}. get_range(TimeRange, State) -> find_time_range(TimeRange, ranges(State)). --spec get_range_balance(time_range(), limit_range_state(), lim_context()) -> +-spec get_range_balance(id(), time_range(), lim_context()) -> {ok, lim_accounting:balance()} - | {error, {range, notfound}}. -get_range_balance(TimeRange, State, LimitContext) -> + | {error, notfound}. +get_range_balance(ID, TimeRange, LimitContext) -> do(fun() -> - #{account_id_to := AccountID} = unwrap(range, find_time_range(TimeRange, ranges(State))), + State = unwrap(get(ID, LimitContext)), + #{account_id_to := AccountID} = unwrap(get_range(TimeRange, State)), {ok, Balance} = lim_accounting:get_balance(AccountID, LimitContext), Balance end). --spec ensure_range_exist(lim_id(), time_range(), lim_context()) -> - {ok, time_range_ext()} - | {error, {limit_range, notfound}}. -ensure_range_exist(ID, TimeRange, LimitContext) -> - do(fun() -> - {ok, WoodyCtx} = lim_context:woody_context(LimitContext), - State = unwrap(limit_range, get_state(ID, WoodyCtx)), - unwrap(ensure_range_exist_in_state(TimeRange, State, LimitContext)) - end). - --spec ensure_range_exist_in_state(time_range(), limit_range_state(), lim_context()) -> {ok, time_range_ext()}. -ensure_range_exist_in_state(TimeRange, State, LimitContext) -> - {ok, WoodyCtx} = lim_context:woody_context(LimitContext), - case find_time_range(TimeRange, ranges(State)) of - {error, notfound} -> - call(id(State), {add_range, TimeRange}, WoodyCtx); - {ok, Range} -> - {ok, Range} - end. - %%% Machinery callbacks -spec init(args([event()]), machine(), handler_args(), handler_opts()) -> result(). @@ -182,21 +168,14 @@ init(Events, _Machine, _HandlerArgs, _HandlerOpts) -> -spec process_call(args(range_call()), machine(), handler_args(), handler_opts()) -> {response(time_range_ext()), result()} | no_return(). -process_call({add_range, TimeRange0}, Machine, _HandlerArgs, #{woody_ctx := WoodyCtx}) -> +process_call({add_range, TimeRange}, Machine, _HandlerArgs, #{woody_ctx := WoodyCtx}) -> State = collapse(Machine), - case find_time_range(TimeRange0, ranges(State)) of + case find_time_range(TimeRange, ranges(State)) of {error, notfound} -> - Currency = currency(State), - {ok, LimitContext} = lim_context:create(WoodyCtx), - {ok, AccountIDFrom} = lim_accounting:create_account(Currency, LimitContext), - {ok, AccountIDTo} = lim_accounting:create_account(Currency, LimitContext), - TimeRange1 = TimeRange0#{ - account_id_from => AccountIDFrom, - account_id_to => AccountIDTo - }, - {TimeRange1, #{events => emit_events([{time_range_created, TimeRange1}])}}; - {ok, Range} -> - {Range, #{}} + TimeRangeExt = new_time_range_ext(TimeRange, currency(State), WoodyCtx), + {TimeRangeExt, #{events => emit_events([{time_range_created, TimeRangeExt}])}}; + {ok, TimeRangeExt} -> + {ok, TimeRangeExt} end. -spec process_timeout(machine(), handler_args(), handler_opts()) -> no_return(). @@ -216,17 +195,27 @@ find_time_range(#{lower := Lower}, [Head = #{lower := Lower} | _Rest]) -> find_time_range(TimeRange, [_Head | Rest]) -> find_time_range(TimeRange, Rest). +new_time_range_ext(TimeRange, Currency, WoodyCtx) -> + {ok, LimitContext} = lim_context:create(WoodyCtx), + {ok, AccountIDFrom} = lim_accounting:create_account(Currency, LimitContext), + {ok, AccountIDTo} = lim_accounting:create_account(Currency, LimitContext), + TimeRange#{ + account_id_from => AccountIDFrom, + account_id_to => AccountIDTo + }. + %% --spec start(lim_id(), create_params(), woody_context()) -> ok | {error, exists}. -start(ID, Params, WoodyCtx) -> - machinery:start(?NS, ID, [{created, Params}], get_backend(WoodyCtx)). +-spec start(id(), create_params(), [time_range_ext()], woody_context()) -> ok | {error, exists}. +start(ID, Params, TimeRanges, WoodyCtx) -> + TimeRangeEvents = [{time_range_created, TR} || TR <- TimeRanges], + machinery:start(?NS, ID, [{created, Params} | TimeRangeEvents], get_backend(WoodyCtx)). --spec call(lim_id(), range_call(), woody_context()) -> {ok, response(_)} | {error, notfound}. +-spec call(id(), range_call(), woody_context()) -> {ok, response(_)} | {error, notfound}. call(ID, Msg, WoodyCtx) -> machinery:call(?NS, ID, Msg, get_backend(WoodyCtx)). --spec get_state(lim_id(), woody_context()) -> {ok, limit_range_state()} | {error, notfound}. +-spec get_state(id(), woody_context()) -> {ok, limit_range_state()} | {error, notfound}. get_state(ID, WoodyCtx) -> case machinery:get(?NS, ID, get_backend(WoodyCtx)) of {ok, Machine} -> diff --git a/apps/limiter/src/lim_rates.erl b/apps/limiter/src/lim_rates.erl index 2dc1adda..143f5ce6 100644 --- a/apps/limiter/src/lim_rates.erl +++ b/apps/limiter/src/lim_rates.erl @@ -1,8 +1,6 @@ -module(lim_rates). -include_lib("xrates_proto/include/xrates_rate_thrift.hrl"). --include_lib("limiter_proto/include/lim_base_thrift.hrl"). --include_lib("limiter_proto/include/lim_limiter_thrift.hrl"). -export([get_converted_amount/3]). @@ -11,9 +9,9 @@ -type limit_context() :: lim_context:t(). -type config() :: lim_config_machine:config(). --type convertation_error() :: quote_not_found | currency_not_found. +-type conversion_error() :: quote_not_found | currency_not_found. --export_type([convertation_error/0]). +-export_type([conversion_error/0]). -define(APP, limiter). -define(DEFAULT_FACTOR, 1.1). @@ -21,7 +19,7 @@ -spec get_converted_amount({amount(), currency()}, config(), limit_context()) -> {ok, amount()} - | {error, convertation_error()}. + | {error, conversion_error()}. get_converted_amount(Cash = {_Amount, Currency}, Config, LimitContext) -> Factor = get_exchange_factor(Currency), case diff --git a/apps/limiter/src/lim_turnover_processor.erl b/apps/limiter/src/lim_turnover_processor.erl index 5d86c5e7..92451abf 100644 --- a/apps/limiter/src/lim_turnover_processor.erl +++ b/apps/limiter/src/lim_turnover_processor.erl @@ -1,8 +1,6 @@ -module(lim_turnover_processor). --include_lib("limiter_proto/include/lim_base_thrift.hrl"). -include_lib("limiter_proto/include/lim_limiter_thrift.hrl"). --include_lib("damsel/include/dmsl_accounter_thrift.hrl"). -behaviour(lim_config_machine). @@ -24,7 +22,7 @@ full := amount() }. --type get_limit_error() :: {limit | range, notfound}. +-type get_limit_error() :: {range, notfound}. -type hold_error() :: lim_body:get_body_error() @@ -32,11 +30,12 @@ -type commit_error() :: {forbidden_operation_amount, forbidden_operation_amount_error()} - | {plan, notfound} - | {full | partial, lim_body:get_body_error()} + | lim_body:get_body_error() | lim_accounting:invalid_request_error(). --type rollback_error() :: {plan, notfound} | lim_accounting:invalid_request_error(). +-type rollback_error() :: + lim_body:get_body_error() + | lim_accounting:invalid_request_error(). -export_type([get_limit_error/0]). -export_type([hold_error/0]). @@ -48,12 +47,9 @@ -spec get_limit(lim_id(), config(), lim_context()) -> {ok, limit()} | {error, get_limit_error()}. get_limit(LimitID, Config, LimitContext) -> do(fun() -> - {ok, Timestamp} = lim_context:get_from_context(payment_processing, created_at, LimitContext), - LimitRangeID = construct_range_id(LimitID, Timestamp, Config, LimitContext), - LimitRange = unwrap(limit, lim_range_machine:get(LimitRangeID, LimitContext)), - TimeRange = lim_config_machine:calculate_time_range(Timestamp, Config), + {LimitRangeID, TimeRange} = compute_limit_time_range_location(LimitID, Config, LimitContext), #{max_available_amount := Amount} = - unwrap(lim_range_machine:get_range_balance(TimeRange, LimitRange, LimitContext)), + unwrap(range, lim_range_machine:get_range_balance(LimitRangeID, TimeRange, LimitContext)), #limiter_Limit{ id = LimitRangeID, amount = Amount, @@ -63,53 +59,66 @@ get_limit(LimitID, Config, LimitContext) -> end). -spec hold(lim_change(), config(), lim_context()) -> ok | {error, hold_error()}. -hold(LimitChange = #limiter_LimitChange{id = LimitID}, Config = #{body_type := BodyType}, LimitContext) -> +hold(LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) -> do(fun() -> - {ok, Timestamp} = lim_context:get_from_context(payment_processing, created_at, LimitContext), - {ok, Body} = lim_body:get_body(full, Config, LimitContext), - LimitRangeID = construct_range_id(LimitID, Timestamp, Config, LimitContext), - Currency = - case BodyType of - {cash, CashCurrency} -> CashCurrency; - amount -> undefined - end, - CreateParams = genlib_map:compact(#{ - id => LimitRangeID, - type => lim_config_machine:time_range_type(Config), - created_at => Timestamp, - currency => Currency - }), - {ok, LimitRangeState} = lim_range_machine:ensure_exist(CreateParams, LimitContext), - TimeRange = lim_config_machine:calculate_time_range(Timestamp, Config), - {ok, #{account_id_from := AccountIDFrom, account_id_to := AccountIDTo}} = - lim_range_machine:ensure_range_exist_in_state(TimeRange, LimitRangeState, LimitContext), - Postings = lim_p_transfer:construct_postings(AccountIDFrom, AccountIDTo, Body), - Postings1 = apply_op_behaviour(Postings, LimitContext, Config), - lim_accounting:hold(construct_plan_id(LimitChange), {1, Postings1}, LimitContext) + TimeRangeAccount = ensure_limit_time_range(LimitID, Config, LimitContext), + Body = unwrap(lim_body:get_body(full, Config, LimitContext)), + Posting = construct_posting(TimeRangeAccount, Body, Config, LimitContext), + unwrap(lim_accounting:hold(construct_plan_id(LimitChange), {1, [Posting]}, LimitContext)) end). -spec commit(lim_change(), config(), lim_context()) -> ok | {error, commit_error()}. -commit(LimitChange, Config, LimitContext) -> +commit(LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) -> do(fun() -> - case lim_body:get_body(partial, Config, LimitContext) of - {ok, Body} -> - unwrap(partial_commit(Body, LimitChange, Config, LimitContext)); - {error, notfound} -> - PlanID = construct_plan_id(LimitChange), - [Batch] = unwrap(plan, lim_accounting:get_plan(PlanID, LimitContext)), - unwrap(lim_accounting:commit(PlanID, [Batch], LimitContext)) - end + TimeRangeAccount = ensure_limit_time_range(LimitID, Config, LimitContext), + PlanID = construct_plan_id(LimitChange), + Operations = construct_commit_plan(TimeRangeAccount, Config, LimitContext), + ok = lists:foreach( + fun + ({hold, Batch}) -> + % NOTE + % This operation **can** fail with `InvalidRequest` when the plan is already + % committed, yet we knowingly ignore any them. Instead we rely on the fact that + % accounter guarantees us that it commits **only** when submitted plan consists + % of exactly the same set of batches which were held before. + lim_accounting:hold(PlanID, Batch, LimitContext); + ({commit, Batches}) -> + unwrap(lim_accounting:commit(PlanID, Batches, LimitContext)); + ({rollback, Batches}) -> + unwrap(lim_accounting:rollback(PlanID, Batches, LimitContext)) + end, + Operations + ) end). -spec rollback(lim_change(), config(), lim_context()) -> ok | {error, rollback_error()}. -rollback(LimitChange, _Config, LimitContext) -> +rollback(LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) -> do(fun() -> - PlanID = construct_plan_id(LimitChange), - BatchList = unwrap(plan, lim_accounting:get_plan(PlanID, LimitContext)), - unwrap(lim_accounting:rollback(PlanID, BatchList, LimitContext)) + TimeRangeAccount = ensure_limit_time_range(LimitID, Config, LimitContext), + Body = unwrap(lim_body:get_body(full, Config, LimitContext)), + Posting = construct_posting(TimeRangeAccount, Body, Config, LimitContext), + unwrap(lim_accounting:rollback(construct_plan_id(LimitChange), [{1, [Posting]}], LimitContext)) end). +compute_limit_time_range_location(LimitID, Config, LimitContext) -> + {ok, Timestamp} = lim_context:get_from_context(payment_processing, created_at, LimitContext), + LimitRangeID = construct_range_id(LimitID, Timestamp, Config, LimitContext), + TimeRange = lim_config_machine:calculate_time_range(Timestamp, Config), + {LimitRangeID, TimeRange}. + +ensure_limit_time_range(LimitID, Config, LimitContext) -> + {ok, Timestamp} = lim_context:get_from_context(payment_processing, created_at, LimitContext), + {LimitRangeID, TimeRange} = compute_limit_time_range_location(LimitID, Config, LimitContext), + CreateParams = genlib_map:compact(#{ + id => LimitRangeID, + type => lim_config_machine:time_range_type(Config), + created_at => Timestamp, + currency => lim_config_machine:currency(Config) + }), + unwrap(lim_range_machine:ensure_exists(CreateParams, TimeRange, LimitContext)). + construct_plan_id(#limiter_LimitChange{change_id = ChangeID}) -> + % DISCUSS ChangeID. construct_range_id(LimitID, Timestamp, Config, LimitContext) -> @@ -117,39 +126,60 @@ construct_range_id(LimitID, Timestamp, Config, LimitContext) -> ShardID = lim_config_machine:calculate_shard_id(Timestamp, Config), <>. -partial_commit(PartialBody, LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) -> - do(fun() -> - {ok, Timestamp} = lim_context:get_from_context(payment_processing, created_at, LimitContext), - {ok, FullBody} = lim_body:get_body(full, Config, LimitContext), - ok = unwrap(assert_partial_body(PartialBody, FullBody)), - - LimitRangeID = construct_range_id(LimitID, Timestamp, Config, LimitContext), - {ok, LimitRangeState} = lim_range_machine:get( - LimitRangeID, - LimitContext - ), - TimeRange = lim_config_machine:calculate_time_range(Timestamp, Config), - {ok, #{account_id_from := AccountIDFrom, account_id_to := AccountIDTo}} = - lim_range_machine:get_range(TimeRange, LimitRangeState), - PartialPostings0 = lim_p_transfer:construct_postings(AccountIDFrom, AccountIDTo, PartialBody), - FullPostings0 = lim_p_transfer:construct_postings(AccountIDFrom, AccountIDTo, FullBody), - PartialPostings1 = apply_op_behaviour(PartialPostings0, LimitContext, Config), - FullPostings1 = apply_op_behaviour(FullPostings0, LimitContext, Config), - NewBatchList = [{2, lim_p_transfer:reverse_postings(FullPostings1)} | [{3, PartialPostings1}]], - PlanID = construct_plan_id(LimitChange), - unwrap(lim_accounting:plan(PlanID, NewBatchList, LimitContext)), - unwrap(lim_accounting:commit(PlanID, [{1, FullPostings1} | NewBatchList], LimitContext)) - end). +construct_commit_plan(TimeRangeAccount, Config, LimitContext) -> + Body = unwrap(lim_body:get_body(full, Config, LimitContext)), + MaybePartialBody = lim_body:get_body(partial, Config, LimitContext), + construct_commit_postings(TimeRangeAccount, Body, MaybePartialBody, Config, LimitContext). + +construct_commit_postings(TimeRangeAccount, Full, MaybePartialBody, Config, LimitContext) -> + OriginalHoldPosting = construct_posting(TimeRangeAccount, Full, Config, LimitContext), + case determine_commit_intent(MaybePartialBody, Full) of + commit -> + [{commit, [{1, [OriginalHoldPosting]}]}]; + rollback -> + [{rollback, [{1, [OriginalHoldPosting]}]}]; + {commit, Partial} -> + % Partial body is less than full body + ok = unwrap(assert_partial_body(Partial, Full)), + ReverseHoldPosting = lim_p_transfer:reverse_posting(OriginalHoldPosting), + PartialHoldPosting = construct_posting(TimeRangeAccount, Partial, Config, LimitContext), + PartialBatch = [ReverseHoldPosting, PartialHoldPosting], + [ + {hold, {2, PartialBatch}}, + {commit, [ + {1, [OriginalHoldPosting]}, + {2, PartialBatch} + ]} + ] + end. -apply_op_behaviour(Posting, LimitContext, #{op_behaviour := ComputationConfig}) -> +determine_commit_intent({error, notfound}, _FullBody) -> + % No partial body specified + commit; +determine_commit_intent({ok, FullBody}, FullBody) -> + % Partial body is equal to full body + commit; +determine_commit_intent({ok, {amount, 0}}, _FullBody) -> + % Partial body is 0, this is rollback + rollback; +determine_commit_intent({ok, {cash, #{amount := 0}}}, _FullBody) -> + % Partial body is 0, this is rollback + rollback; +determine_commit_intent({ok, Partial}, _FullBody) -> + {commit, Partial}. + +construct_posting(TimeRangeAccount, Body, Config, LimitContext) -> + apply_op_behaviour(lim_p_transfer:construct_posting(TimeRangeAccount, Body), Config, LimitContext). + +apply_op_behaviour(Posting, #{op_behaviour := ComputationConfig}, LimitContext) -> {ok, Operation} = lim_context:get_operation(payment_processing, LimitContext), case maps:get(Operation, ComputationConfig, undefined) of subtraction -> - lim_p_transfer:reverse_postings(Posting); + lim_p_transfer:reverse_posting(Posting); Type when Type =:= undefined orelse Type =:= additional -> Posting end; -apply_op_behaviour(Body, _LimitContext, _Config) -> +apply_op_behaviour(Body, _Config, _LimitContext) -> Body. assert_partial_body( @@ -164,7 +194,7 @@ assert_partial_body( erlang:error({invalid_partial_cash, {Partial, PartialCurrency}, {Full, FullCurrency}}). compare_amount(Partial, Full, Currency) when Full > 0 -> - case Partial =< Full of + case Partial < Full of true -> ok; false -> @@ -178,7 +208,7 @@ compare_amount(Partial, Full, Currency) when Full > 0 -> })}} end; compare_amount(Partial, Full, Currency) when Full < 0 -> - case Partial >= Full of + case Partial > Full of true -> ok; false -> diff --git a/apps/limiter/test/lim_ct_helper.hrl b/apps/limiter/test/lim_ct_helper.hrl index f421210a..10c1f6ba 100644 --- a/apps/limiter/test/lim_ct_helper.hrl +++ b/apps/limiter/test/lim_ct_helper.hrl @@ -8,11 +8,13 @@ currency = #limiter_base_CurrencyRef{symbolic_code = <<"RUB">>} }). +-define(op_invoice_payment(), {invoice_payment, #limiter_context_PaymentProcessingOperationInvoicePayment{}}). + -define(ctx_invoice_payment(Cost, CaptureCost), ?ctx_invoice_payment(undefined, undefined, Cost, CaptureCost)). -define(ctx_invoice_payment(OwnerID, ShopID, Cost, CaptureCost), #limiter_context_LimitContext{ payment_processing = #limiter_context_ContextPaymentProcessing{ - op = {invoice_payment, #limiter_context_PaymentProcessingOperationInvoicePayment{}}, + op = ?op_invoice_payment(), invoice = #limiter_context_Invoice{ owner_id = OwnerID, shop_id = ShopID, @@ -25,6 +27,15 @@ } }). +-define(ctx_invoice_payment(Payment), #limiter_context_LimitContext{ + payment_processing = #limiter_context_ContextPaymentProcessing{ + op = ?op_invoice_payment(), + invoice = #limiter_context_Invoice{ + effective_payment = Payment + } + } +}). + -define(ctx_invoice_payment_refund(OwnerID, ShopID, Cost, CaptureCost, RefundCost), #limiter_context_LimitContext{ payment_processing = #limiter_context_ContextPaymentProcessing{ op = {invoice_payment_refund, #limiter_context_PaymentProcessingOperationInvoicePaymentRefund{}}, diff --git a/apps/limiter/test/lim_mock.erl b/apps/limiter/test/lim_mock.erl index 1b749cb7..6d84817d 100644 --- a/apps/limiter/test/lim_mock.erl +++ b/apps/limiter/test/lim_mock.erl @@ -19,9 +19,7 @@ stop_mocked_service_sup(SupPid) -> exit(SupPid, shutdown). -define(HOST_IP, "::"). --define(HOST_PORT, 8080). -define(HOST_NAME, "localhost"). --define(HOST_URL, ?HOST_NAME ++ ":" ++ integer_to_list(?HOST_PORT)). -spec mock_services(_, _) -> _. mock_services(Services, SupOrConfig) -> diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index 7b9b7c1e..e7557e34 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -25,6 +25,14 @@ -export([rollback_ok/1]). -export([refund_ok/1]). -export([get_config_ok/1]). +-export([commit_inexistent_hold_fails/1]). +-export([partial_commit_inexistent_hold_fails/1]). +-export([commit_multirange_limit_ok/1]). + +-export([commit_processes_idempotently/1]). +-export([full_commit_processes_idempotently/1]). +-export([partial_commit_processes_idempotently/1]). +-export([rollback_processes_idempotently/1]). -type group_name() :: atom(). -type test_case_name() :: atom(). @@ -36,7 +44,8 @@ -spec all() -> [{group, group_name()}]. all() -> [ - {group, default} + {group, default}, + {group, idempotency} ]. -spec groups() -> [{atom(), list(), [test_case_name()]}]. @@ -52,7 +61,16 @@ groups() -> commit_ok, rollback_ok, get_config_ok, - refund_ok + refund_ok, + commit_inexistent_hold_fails, + partial_commit_inexistent_hold_fails, + commit_multirange_limit_ok + ]}, + {idempotency, [parallel], [ + commit_processes_idempotently, + full_commit_processes_idempotently, + partial_commit_processes_idempotently, + rollback_processes_idempotently ]} ]. @@ -85,12 +103,16 @@ init_per_suite(Config) -> -spec end_per_suite(config()) -> _. end_per_suite(Config) -> - _ = [application:stop(App) || App <- proplists:get_value(apps, Config)], - Config. + genlib_app:test_application_stop(?config(apps, Config)). -spec init_per_testcase(test_case_name(), config()) -> config(). -init_per_testcase(_Name, C) -> - [{test_sup, lim_mock:start_mocked_service_sup()} | C]. +init_per_testcase(Name, C) -> + [ + {id, gen_unique_id(Name)}, + {client, lim_client:new()}, + {test_sup, lim_mock:start_mocked_service_sup()} + | C + ]. -spec end_per_testcase(test_case_name(), config()) -> ok. end_per_testcase(_Name, C) -> @@ -99,12 +121,16 @@ end_per_testcase(_Name, C) -> %% +-define(CHANGE_ID, 42). +-define(LIMIT_CHANGE(ID), ?LIMIT_CHANGE(ID, ?CHANGE_ID)). +-define(LIMIT_CHANGE(ID, ChangeID), #limiter_LimitChange{id = ID, change_id = gen_change_id(ID, ChangeID)}). + -spec commit_with_default_exchange(config()) -> _. commit_with_default_exchange(C) -> Rational = #base_Rational{p = 1000000, q = 100}, _ = mock_exchange(Rational, C), - ID = lim_time:to_rfc3339(lim_time:now()), - #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), + _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + ID = ?config(id, C), Context = #limiter_context_LimitContext{ payment_processing = #limiter_context_ContextPaymentProcessing{ op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, @@ -117,21 +143,15 @@ commit_with_default_exchange(C) -> } } }, - Timestamp = lim_time:to_rfc3339(lim_time:now()), - LimitChangeID = <>, - Change = #limiter_LimitChange{ - id = ID, - change_id = LimitChangeID - }, - {ok, {vector, _}} = hold_and_commit(Change, Context, Client), - {ok, #limiter_Limit{amount = 10000}} = lim_client:get(ID, Context, Client). + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), + {ok, #limiter_Limit{amount = 10000}} = lim_client:get(ID, Context, ?config(client, C)). -spec partial_commit_with_exchange(config()) -> _. partial_commit_with_exchange(C) -> Rational = #base_Rational{p = 800000, q = 100}, _ = mock_exchange(Rational, C), - ID = lim_time:to_rfc3339(lim_time:now()), - #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), + _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + ID = ?config(id, C), Context = #limiter_context_LimitContext{ payment_processing = #limiter_context_ContextPaymentProcessing{ op = {invoice_payment, #limiter_context_PaymentProcessingOperationInvoicePayment{}}, @@ -150,21 +170,15 @@ partial_commit_with_exchange(C) -> } } }, - Timestamp = lim_time:to_rfc3339(lim_time:now()), - LimitChangeID = <>, - Change = #limiter_LimitChange{ - id = ID, - change_id = LimitChangeID - }, - {ok, {vector, _}} = hold_and_commit(Change, Context, Client), - {ok, #limiter_Limit{amount = 8400}} = lim_client:get(ID, Context, Client). + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), + {ok, #limiter_Limit{amount = 8400}} = lim_client:get(ID, Context, ?config(client, C)). -spec commit_with_exchange(config()) -> _. commit_with_exchange(C) -> Rational = #base_Rational{p = 1000000, q = 100}, _ = mock_exchange(Rational, C), - ID = lim_time:to_rfc3339(lim_time:now()), - #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), + _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + ID = ?config(id, C), Context = #limiter_context_LimitContext{ payment_processing = #limiter_context_ContextPaymentProcessing{ op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, @@ -177,14 +191,8 @@ commit_with_exchange(C) -> } } }, - Timestamp = lim_time:to_rfc3339(lim_time:now()), - LimitChangeID = <>, - Change = #limiter_LimitChange{ - id = ID, - change_id = LimitChangeID - }, - {ok, {vector, _}} = hold_and_commit(Change, Context, Client), - {ok, #limiter_Limit{amount = 10500}} = lim_client:get(ID, Context, Client). + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), + {ok, #limiter_Limit{amount = 10500}} = lim_client:get(ID, Context, ?config(client, C)). -spec get_rate(config()) -> _. get_rate(C) -> @@ -206,20 +214,19 @@ get_rate(C) -> -spec get_limit_notfound(config()) -> _. get_limit_notfound(C) -> - ID = lim_time:to_rfc3339(lim_time:now()), - #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), + _ = prepare_environment(<<"GlobalMonthTurnover">>, C), Context = #limiter_context_LimitContext{ payment_processing = #limiter_context_ContextPaymentProcessing{ op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, invoice = #limiter_context_Invoice{created_at = <<"2000-01-01T00:00:00Z">>} } }, - {exception, #limiter_LimitNotFound{}} = lim_client:get(ID, Context, Client). + {exception, #limiter_LimitNotFound{}} = lim_client:get(?config(id, C), Context, ?config(client, C)). -spec hold_ok(config()) -> _. hold_ok(C) -> - ID = <<"ID">>, - #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), + _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + ID = ?config(id, C), Context = #limiter_context_LimitContext{ payment_processing = #limiter_context_ContextPaymentProcessing{ op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, @@ -232,19 +239,13 @@ hold_ok(C) -> } } }, - Timestamp = lim_time:to_rfc3339(lim_time:now()), - LimitChangeID = <>, - Change = #limiter_LimitChange{ - id = ID, - change_id = LimitChangeID - }, - {ok, {vector, #limiter_VectorClock{}}} = lim_client:hold(Change, Context, Client), - {ok, #limiter_Limit{}} = lim_client:get(ID, Context, Client). + {ok, {vector, #limiter_VectorClock{}}} = lim_client:hold(?LIMIT_CHANGE(ID), Context, ?config(client, C)), + {ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)). -spec commit_ok(config()) -> _. commit_ok(C) -> - ID = <<"ID">>, - #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), + _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + ID = ?config(id, C), Context = #limiter_context_LimitContext{ payment_processing = #limiter_context_ContextPaymentProcessing{ op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, @@ -257,67 +258,162 @@ commit_ok(C) -> } } }, - Timestamp = lim_time:to_rfc3339(lim_time:now()), - LimitChangeID = <>, - Change = #limiter_LimitChange{ - id = ID, - change_id = LimitChangeID - }, - {ok, {vector, _}} = hold_and_commit(Change, Context, Client), - {ok, #limiter_Limit{}} = lim_client:get(ID, Context, Client). + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), + {ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)). -spec rollback_ok(config()) -> _. rollback_ok(C) -> - ID = <<"ID">>, - #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), + _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + ID = ?config(id, C), Context0 = ?ctx_invoice_payment(?cash(10), ?cash(10)), Context1 = ?ctx_invoice_payment(?cash(10), ?cash(0)), - - Timestamp = lim_time:to_rfc3339(lim_time:now()), - LimitChangeID = <>, - Change = #limiter_LimitChange{ - id = ID, - change_id = LimitChangeID - }, - {ok, {vector, _}} = lim_client:hold(Change, Context0, Client), - {ok, {vector, _}} = lim_client:commit(Change, Context1, Client). + Change = ?LIMIT_CHANGE(ID), + {ok, {vector, _}} = lim_client:hold(Change, Context0, ?config(client, C)), + {ok, {vector, _}} = lim_client:commit(Change, Context1, ?config(client, C)). -spec refund_ok(config()) -> _. refund_ok(C) -> - ID = lim_time:to_rfc3339(lim_time:now()), + ID = ?config(id, C), + Client = ?config(client, C), OwnerID = <<"WWWcool Ltd">>, ShopID = <<"shop">>, - #{client := Client} = _LimitConfig = prepare_environment(ID, <<"ShopDayTurnover">>, C), + _ = prepare_environment(<<"ShopDayTurnover">>, C), Context0 = ?ctx_invoice_payment(OwnerID, ShopID, ?cash(15), ?cash(15)), RefundContext1 = ?ctx_invoice_payment_refund(OwnerID, ShopID, ?cash(10), ?cash(10), ?cash(10)), - Timestamp = lim_time:to_rfc3339(lim_time:now()), - LimitChangeID = <>, + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Payment">>), Context0, Client), + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Refund">>), RefundContext1, Client), + {ok, #limiter_Limit{} = Limit2} = lim_client:get(ID, RefundContext1, Client), + ?assertEqual(Limit2#limiter_Limit.amount, 5). - Change = #limiter_LimitChange{ +-spec get_config_ok(config()) -> _. +get_config_ok(C) -> + _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + {ok, #limiter_config_LimitConfig{}} = lim_client:get_config(?config(id, C), ?config(client, C)). + +-spec commit_inexistent_hold_fails(config()) -> _. +commit_inexistent_hold_fails(C) -> + ID = ?config(id, C), + _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + Context = ?ctx_invoice_payment(?cash(42), undefined), + % NOTE + % We do not expect `LimitChangeNotFound` here because we no longer reconcile with accounter + % before requesting him to hold / commit. + {exception, #limiter_base_InvalidRequest{}} = + lim_client:commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)). + +-spec partial_commit_inexistent_hold_fails(config()) -> _. +partial_commit_inexistent_hold_fails(C) -> + ID = ?config(id, C), + _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + Context = ?ctx_invoice_payment(?cash(42), ?cash(21)), + % NOTE + % We do not expect `LimitChangeNotFound` here because we no longer reconcile with accounter + % before requesting him to hold / commit. + {exception, #limiter_base_InvalidRequest{}} = + lim_client:commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)). + +-spec commit_multirange_limit_ok(config()) -> _. +commit_multirange_limit_ok(C) -> + ID = ?config(id, C), + Client = ?config(client, C), + Params = #limiter_config_LimitConfigParams{ id = ID, - change_id = LimitChangeID + body_type = {cash, #limiter_config_LimitBodyTypeCash{currency = <<"RUB">>}}, + started_at = <<"2000-01-01T00:00:00Z">>, + shard_size = 12, + time_range_type = {calendar, {month, #time_range_TimeRangeTypeCalendarMonth{}}}, + context_type = {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}, + type = {turnover, #limiter_config_LimitTypeTurnover{}}, + scope = {scope_global, #limiter_config_LimitScopeGlobal{}}, + op_behaviour = #limiter_config_OperationLimitBehaviour{} }, - {ok, {vector, _}} = hold_and_commit(Change, Context0, Client), - - Timestamp2 = lim_time:to_rfc3339(lim_time:now()), - LimitChangeID2 = <>, - Change2 = #limiter_LimitChange{ - id = ID, - change_id = LimitChangeID2 + {ok, _LimitConfig} = lim_client:create_config(Params, Client), + % NOTE + % Expecting those 3 changes will be accounted in the same limit range machine. + % We have no way to verify it here though. + PaymentJan = #limiter_context_InvoicePayment{ + created_at = <<"2020-01-01T00:00:00Z">>, + cost = ?cash(42) + }, + {ok, _} = hold_and_commit(?LIMIT_CHANGE(ID, 1), ?ctx_invoice_payment(PaymentJan), Client), + PaymentFeb = #limiter_context_InvoicePayment{ + created_at = <<"2020-02-01T00:00:00Z">>, + cost = ?cash(43) }, + {ok, _} = hold_and_commit(?LIMIT_CHANGE(ID, 2), ?ctx_invoice_payment(PaymentFeb), Client), + PaymentApr = #limiter_context_InvoicePayment{ + created_at = <<"2020-04-01T00:00:00Z">>, + cost = ?cash(44) + }, + {ok, _} = hold_and_commit(?LIMIT_CHANGE(ID, 3), ?ctx_invoice_payment(PaymentApr), Client), + {ok, #limiter_Limit{amount = 42}} = lim_client:get(ID, ?ctx_invoice_payment(PaymentJan), Client), + {ok, #limiter_Limit{amount = 43}} = lim_client:get(ID, ?ctx_invoice_payment(PaymentFeb), Client), + {ok, #limiter_Limit{amount = 44}} = lim_client:get(ID, ?ctx_invoice_payment(PaymentApr), Client). - {ok, {vector, _}} = hold_and_commit(Change2, RefundContext1, Client), - {ok, #limiter_Limit{} = Limit2} = lim_client:get(ID, RefundContext1, Client), - ?assertEqual(Limit2#limiter_Limit.amount, 5). +%% --spec get_config_ok(config()) -> _. -get_config_ok(C) -> - ID = <<"ID">>, - #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), - {ok, #limiter_config_LimitConfig{}} = lim_client:get_config(ID, Client). +-spec commit_processes_idempotently(config()) -> _. +commit_processes_idempotently(C) -> + ID = ?config(id, C), + Client = ?config(client, C), + _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + Context = ?ctx_invoice_payment(?cash(42), undefined), + Change = ?LIMIT_CHANGE(ID), + {ok, _} = lim_client:hold(Change, Context, Client), + {ok, _} = lim_client:hold(Change, Context, Client), + {ok, _} = lim_client:commit(Change, Context, Client), + {ok, Limit = #limiter_Limit{amount = 42}} = lim_client:get(ID, Context, Client), + {ok, _} = lim_client:commit(Change, Context, Client), + {ok, Limit} = lim_client:get(ID, Context, Client). + +-spec full_commit_processes_idempotently(config()) -> _. +full_commit_processes_idempotently(C) -> + ID = ?config(id, C), + Client = ?config(client, C), + _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + Cost = ?cash(42), + Context = ?ctx_invoice_payment(Cost, Cost), + Change = ?LIMIT_CHANGE(ID), + {ok, _} = lim_client:hold(Change, Context, Client), + {ok, _} = lim_client:hold(Change, Context, Client), + {ok, _} = lim_client:commit(Change, Context, Client), + {ok, Limit = #limiter_Limit{amount = 42}} = lim_client:get(ID, Context, Client), + {ok, _} = lim_client:commit(Change, Context, Client), + {ok, Limit} = lim_client:get(ID, Context, Client). + +-spec partial_commit_processes_idempotently(config()) -> _. +partial_commit_processes_idempotently(C) -> + ID = ?config(id, C), + Client = ?config(client, C), + _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + Context = ?ctx_invoice_payment(?cash(42), ?cash(40)), + Change = ?LIMIT_CHANGE(ID), + {ok, _} = lim_client:hold(Change, Context, Client), + {ok, _} = lim_client:hold(Change, Context, Client), + {ok, _} = lim_client:commit(Change, Context, Client), + {ok, Limit = #limiter_Limit{amount = 40}} = lim_client:get(ID, Context, Client), + {ok, _} = lim_client:commit(Change, Context, Client), + {ok, Limit = #limiter_Limit{amount = 40}} = lim_client:get(ID, Context, Client). + +-spec rollback_processes_idempotently(config()) -> _. +rollback_processes_idempotently(C) -> + ID = ?config(id, C), + Client = ?config(client, C), + _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + Context = ?ctx_invoice_payment(?cash(42), ?cash(0)), + Change = ?LIMIT_CHANGE(ID), + {ok, _} = lim_client:hold(Change, Context, Client), + {ok, _} = lim_client:hold(Change, Context, Client), + {ok, _} = lim_client:commit(Change, Context, Client), + {ok, Limit = #limiter_Limit{amount = 0}} = lim_client:get(ID, Context, Client), + {ok, _} = lim_client:commit(Change, Context, Client), + {ok, Limit = #limiter_Limit{amount = 0}} = lim_client:get(ID, Context, Client). %% +gen_change_id(LimitID, ChangeID) -> + genlib:format("~s/~p", [LimitID, ChangeID]). + hold_and_commit(Change, Context, Client) -> {ok, {vector, _}} = lim_client:hold(Change, Context, Client), {ok, {vector, _}} = lim_client:commit(Change, Context, Client). @@ -325,8 +421,8 @@ hold_and_commit(Change, Context, Client) -> mock_exchange(Rational, C) -> lim_mock:mock_services([{xrates, fun('GetConvertedAmount', _) -> {ok, Rational} end}], C). -prepare_environment(ID, LimitName, _C) -> - Client = lim_client:new(), +prepare_environment(LimitName, C) -> + ID = ?config(id, C), Params = #limiter_cfg_LimitCreateParams{ id = ID, name = LimitName, @@ -337,5 +433,7 @@ prepare_environment(ID, LimitName, _C) -> invoice_payment_refund = {subtraction, #limiter_config_Subtraction{}} } }, - {ok, LimitConfig} = lim_client:legacy_create_config(Params, Client), - #{config => LimitConfig, client => Client}. + {ok, _LimitConfig} = lim_client:legacy_create_config(Params, ?config(client, C)). + +gen_unique_id(Prefix) -> + genlib:format("~s/~B", [Prefix, lim_time:now()]). diff --git a/elvis.config b/elvis.config index 22ae88d4..988dab44 100644 --- a/elvis.config +++ b/elvis.config @@ -8,6 +8,7 @@ ruleset => erl_files, rules => [ {elvis_text_style, line_length, #{limit => 120}}, + {elvis_style, god_modules, #{limit => 30}}, {elvis_style, nesting_level, #{level => 3}}, {elvis_style, no_if_expression, disable} ] From b1cd093ffbd4086b8b89c7589d5690c623db67f7 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Fri, 3 Jun 2022 11:41:44 +0300 Subject: [PATCH 18/54] TD-303: Add multiscope support (#7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As of valitydev/limiter-proto@d390910. Co-authored-by: Артем --- apps/limiter/src/lim_config_codec.erl | 30 ++--- apps/limiter/src/lim_config_machine.erl | 108 ++++++++++++++---- .../src/lim_config_machinery_schema.erl | 2 +- apps/limiter/src/lim_configurator.erl | 10 +- apps/limiter/src/lim_context.erl | 2 +- apps/limiter/src/lim_handler.erl | 7 -- apps/limiter/src/lim_turnover_processor.erl | 2 +- apps/limiter/test/lim_configurator_SUITE.erl | 89 +++++++++++---- apps/limiter/test/lim_ct_helper.hrl | 44 ++++++- apps/limiter/test/lim_turnover_SUITE.erl | 51 +++++---- rebar.config | 2 +- rebar.lock | 10 +- 12 files changed, 249 insertions(+), 108 deletions(-) diff --git a/apps/limiter/src/lim_config_codec.erl b/apps/limiter/src/lim_config_codec.erl index 518ab105..14afa57f 100644 --- a/apps/limiter/src/lim_config_codec.erl +++ b/apps/limiter/src/lim_config_codec.erl @@ -103,19 +103,19 @@ marshal_context_type(payment_processing) -> marshal_type(turnover) -> {turnover, #limiter_config_LimitTypeTurnover{}}. -marshal_scope({scope, Type}) -> - {scope, marshal_scope_type(Type)}; -marshal_scope(global) -> - {scope_global, #limiter_config_LimitScopeGlobal{}}. +marshal_scope(Types) -> + {multi, ordsets:from_list(lists:map(fun marshal_scope_type/1, ordsets:to_list(Types)))}. marshal_scope_type(party) -> - {party, #limiter_config_LimitScopeTypeParty{}}; + {party, #limiter_config_LimitScopeEmptyDetails{}}; marshal_scope_type(shop) -> - {shop, #limiter_config_LimitScopeTypeShop{}}; + {shop, #limiter_config_LimitScopeEmptyDetails{}}; marshal_scope_type(wallet) -> - {wallet, #limiter_config_LimitScopeTypeWallet{}}; + {wallet, #limiter_config_LimitScopeEmptyDetails{}}; marshal_scope_type(identity) -> - {identity, #limiter_config_LimitScopeTypeIdentity{}}. + {identity, #limiter_config_LimitScopeEmptyDetails{}}; +marshal_scope_type(payment_tool) -> + {payment_tool, #limiter_config_LimitScopeEmptyDetails{}}. %% @@ -239,10 +239,10 @@ unmarshal_context_type({payment_processing, #limiter_config_LimitContextTypePaym unmarshal_type({turnover, #limiter_config_LimitTypeTurnover{}}) -> turnover. -unmarshal_scope({scope, Type}) -> - {scope, unmarshal_scope_type(Type)}; -unmarshal_scope({scope_global, #limiter_config_LimitScopeGlobal{}}) -> - global. +unmarshal_scope({single, Type}) -> + ordsets:from_list([unmarshal_scope_type(Type)]); +unmarshal_scope({multi, Types}) -> + ordsets:from_list(lists:map(fun unmarshal_scope_type/1, ordsets:to_list(Types))). unmarshal_scope_type({party, _}) -> party; @@ -251,7 +251,9 @@ unmarshal_scope_type({shop, _}) -> unmarshal_scope_type({wallet, _}) -> wallet; unmarshal_scope_type({identity, _}) -> - identity. + identity; +unmarshal_scope_type({payment_tool, _}) -> + payment_tool. %% @@ -274,7 +276,7 @@ marshal_unmarshal_created_test() -> time_range_type => {calendar, day}, context_type => payment_processing, type => turnover, - scope => {scope, party}, + scope => ordsets:from_list([party, shop]), description => <<"description">> }}, Event = {ev, lim_time:machinery_now(), Created}, diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index 987240dc..fff47c2d 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -39,7 +39,8 @@ -type description() :: binary(). -type limit_type() :: turnover. --type limit_scope() :: global | {scope, party | shop | wallet | identity}. +-type limit_scope() :: ordsets:ordset(limit_scope_type()). +-type limit_scope_type() :: party | shop | wallet | identity. -type body_type() :: {cash, currency()} | amount. -type shard_size() :: pos_integer(). -type shard_id() :: binary(). @@ -211,11 +212,11 @@ type(#{type := Value}) -> type(_) -> undefined. --spec scope(config()) -> lim_maybe:maybe(limit_scope()). +-spec scope(config()) -> limit_scope(). scope(#{scope := Value}) -> Value; scope(_) -> - undefined. + ordsets:new(). -spec context_type(config()) -> context_type(). context_type(#{context_type := Value}) -> @@ -448,7 +449,7 @@ calculate_calendar_shard_id(Range, Timestamp, StartedAt, ShardSize) -> {CurrentDatetime, _USec1} = lim_range_codec:parse_timestamp(Timestamp), Units = calculate_time_units(Range, CurrentDatetime, StartDatetime), SignPrefix = mk_sign_prefix(Units), - RangePrefix = mk_prefix(Range), + RangePrefix = mk_unit_prefix(Range), mk_shard_id(<>, Units, ShardSize). calculate_time_units(year, CurrentDatetime, StartDatetime) -> @@ -525,29 +526,65 @@ year({{Year, _, _}, _Time}) -> month({{_Year, Month, _}, _Time}) -> Month. -mk_prefix(day) -> <<"day">>; -mk_prefix(week) -> <<"week">>; -mk_prefix(month) -> <<"month">>; -mk_prefix(year) -> <<"year">>. +mk_unit_prefix(day) -> <<"day">>; +mk_unit_prefix(week) -> <<"week">>; +mk_unit_prefix(month) -> <<"month">>; +mk_unit_prefix(year) -> <<"year">>. mk_sign_prefix(Units) when Units >= 0 -> <<"future">>; mk_sign_prefix(_) -> <<"past">>. -mk_shard_id(Prefix, Units0, ShardSize) -> - Units1 = abs(Units0), - ID = list_to_binary(integer_to_list(Units1 div ShardSize)), +mk_shard_id(Prefix, Units, ShardSize) -> + ID = integer_to_binary(abs(Units) div ShardSize), <>. --spec mk_scope_prefix(config(), lim_context()) -> {ok, prefix()}. -mk_scope_prefix(#{scope := global}, _LimitContext) -> - {ok, <<>>}; -mk_scope_prefix(#{scope := {scope, party}}, LimitContext) -> - {ok, PartyID} = lim_context:get_from_context(payment_processing, owner_id, invoice, LimitContext), - {ok, <<"/", PartyID/binary>>}; -mk_scope_prefix(#{scope := {scope, shop}}, LimitContext) -> - {ok, PartyID} = lim_context:get_from_context(payment_processing, owner_id, invoice, LimitContext), - {ok, ShopID} = lim_context:get_from_context(payment_processing, shop_id, invoice, LimitContext), - {ok, <<"/", PartyID/binary, "/", ShopID/binary>>}. +-spec mk_scope_prefix(config(), lim_context()) -> prefix(). +mk_scope_prefix(Config, LimitContext) -> + mk_scope_prefix_impl(scope(Config), LimitContext). + +-spec mk_scope_prefix_impl(limit_scope(), lim_context()) -> prefix(). +mk_scope_prefix_impl(Scope, LimitContext) -> + Bits = enumerate_context_bits(Scope), + ordsets:fold( + fun(Bit, Acc) -> + {ok, Value} = extract_context_bit(Bit, LimitContext), + append_prefix(Value, Acc) + end, + <<>>, + Bits + ). + +-spec append_prefix(binary(), prefix()) -> prefix(). +append_prefix(Fragment, Acc) -> + <>. + +-type context_bit() :: + {from, lim_context:context_type(), _Name :: atom(), lim_context:context_operation()} + | {order, integer(), context_bit()}. + +-spec enumerate_context_bits(limit_scope()) -> ordsets:ordset(context_bit()). +enumerate_context_bits(Types) -> + ordsets:fold(fun append_context_bits/2, ordsets:new(), Types). + +append_context_bits(party, Bits) -> + ordsets:add_element( + {order, 1, {from, payment_processing, owner_id, invoice}}, + Bits + ); +append_context_bits(shop, Bits) -> + lists:foldl(fun ordsets:add_element/2, Bits, [ + % NOTE + % Shop scope implies party scope. + % Also we need to preserve order between party / shop to ensure backwards compatibility. + {order, 1, {from, payment_processing, owner_id, invoice}}, + {order, 2, {from, payment_processing, shop_id, invoice}} + ]). + +-spec extract_context_bit(context_bit(), lim_context()) -> {ok, binary()}. +extract_context_bit({order, _, Bit}, LimitContext) -> + extract_context_bit(Bit, LimitContext); +extract_context_bit({from, ContextType, ValueName, Op}, LimitContext) -> + lim_context:get_from_context(ContextType, ValueName, Op, LimitContext). %%% Machinery callbacks @@ -747,4 +784,33 @@ check_calculate_year_shard_id_test() -> ?assertEqual(<<"past/year/1">>, calculate_calendar_shard_id(year, <<"2000-01-01T00:00:00Z">>, StartedAt2, 1)), ?assertEqual(<<"future/year/0">>, calculate_calendar_shard_id(year, <<"2001-01-01T00:00:00Z">>, StartedAt2, 1)). +-define(LIMIT_CONTEXT, #{ + context => #{ + payment_processing => #{ + op => invoice, + invoice => #{ + owner_id => <<"OWNER">>, + shop_id => <<"SHLOP">> + } + } + } +}). + +-spec global_scope_empty_prefix_test() -> _. +global_scope_empty_prefix_test() -> + ?assertEqual(<<>>, mk_scope_prefix_impl(ordsets:new(), ?LIMIT_CONTEXT)). + +-spec preserve_scope_prefix_order_test_() -> [_TestGen]. +preserve_scope_prefix_order_test_() -> + [ + ?_assertEqual( + <<"/OWNER/SHLOP">>, + mk_scope_prefix_impl(ordsets:from_list([shop, party]), ?LIMIT_CONTEXT) + ), + ?_assertEqual( + <<"/OWNER/SHLOP">>, + mk_scope_prefix_impl(ordsets:from_list([shop]), ?LIMIT_CONTEXT) + ) + ]. + -endif. diff --git a/apps/limiter/src/lim_config_machinery_schema.erl b/apps/limiter/src/lim_config_machinery_schema.erl index 36e5c9c2..48ea9116 100644 --- a/apps/limiter/src/lim_config_machinery_schema.erl +++ b/apps/limiter/src/lim_config_machinery_schema.erl @@ -102,7 +102,7 @@ marshal_unmarshal_created_test() -> time_range_type => {calendar, day}, context_type => payment_processing, type => turnover, - scope => {scope, party}, + scope => ordsets:from_list([party]), description => <<"description">> }}, Context = #{machine_ref => ID, machine_ns => config}, diff --git a/apps/limiter/src/lim_configurator.erl b/apps/limiter/src/lim_configurator.erl index fd430cdf..9b90b9d7 100644 --- a/apps/limiter/src/lim_configurator.erl +++ b/apps/limiter/src/lim_configurator.erl @@ -89,7 +89,7 @@ mk_limit_config(<<"ShopDayTurnover">>) -> {ok, #{ processor_type => <<"TurnoverProcessor">>, type => turnover, - scope => {scope, shop}, + scope => ordsets:from_list([shop]), shard_size => 7, context_type => payment_processing, time_range_type => {calendar, day} @@ -98,7 +98,7 @@ mk_limit_config(<<"PartyDayTurnover">>) -> {ok, #{ processor_type => <<"TurnoverProcessor">>, type => turnover, - scope => {scope, party}, + scope => ordsets:from_list([party]), shard_size => 7, context_type => payment_processing, time_range_type => {calendar, day} @@ -107,7 +107,7 @@ mk_limit_config(<<"ShopMonthTurnover">>) -> {ok, #{ processor_type => <<"TurnoverProcessor">>, type => turnover, - scope => {scope, shop}, + scope => ordsets:from_list([shop]), shard_size => 12, context_type => payment_processing, time_range_type => {calendar, month} @@ -116,7 +116,7 @@ mk_limit_config(<<"PartyMonthTurnover">>) -> {ok, #{ processor_type => <<"TurnoverProcessor">>, type => turnover, - scope => {scope, party}, + scope => ordsets:from_list([party]), shard_size => 12, context_type => payment_processing, time_range_type => {calendar, month} @@ -125,7 +125,7 @@ mk_limit_config(<<"GlobalMonthTurnover">>) -> {ok, #{ processor_type => <<"TurnoverProcessor">>, type => turnover, - scope => global, + scope => ordsets:new(), shard_size => 12, context_type => payment_processing, time_range_type => {calendar, month} diff --git a/apps/limiter/src/lim_context.erl b/apps/limiter/src/lim_context.erl index b7192e39..ce0b8e60 100644 --- a/apps/limiter/src/lim_context.erl +++ b/apps/limiter/src/lim_context.erl @@ -21,7 +21,7 @@ -type cash() :: lim_body:cash(). -type t() :: #{ - woody_context := woody_context(), + woody_context => woody_context(), context => context(), clock => clock() }. diff --git a/apps/limiter/src/lim_handler.erl b/apps/limiter/src/lim_handler.erl index 0fceda94..119ec31d 100644 --- a/apps/limiter/src/lim_handler.erl +++ b/apps/limiter/src/lim_handler.erl @@ -14,13 +14,6 @@ -define(LIMIT_CHANGE(ID), #limiter_LimitChange{id = ID}). --define(CASH( - Amount, - Currency -), - #limiter_base_Cash{amount = Amount, currency = #limiter_base_CurrencyRef{symbolic_code = Currency}} -). - %% -spec handle_function(woody:func(), woody:args(), woody_context:ctx(), woody:options()) -> {ok, woody:result()}. diff --git a/apps/limiter/src/lim_turnover_processor.erl b/apps/limiter/src/lim_turnover_processor.erl index 92451abf..e8501276 100644 --- a/apps/limiter/src/lim_turnover_processor.erl +++ b/apps/limiter/src/lim_turnover_processor.erl @@ -122,7 +122,7 @@ construct_plan_id(#limiter_LimitChange{change_id = ChangeID}) -> ChangeID. construct_range_id(LimitID, Timestamp, Config, LimitContext) -> - {ok, Prefix} = lim_config_machine:mk_scope_prefix(Config, LimitContext), + Prefix = lim_config_machine:mk_scope_prefix(Config, LimitContext), ShardID = lim_config_machine:calculate_shard_id(Timestamp, Config), <>. diff --git a/apps/limiter/test/lim_configurator_SUITE.erl b/apps/limiter/test/lim_configurator_SUITE.erl index 0bfff38c..cf42ab5c 100644 --- a/apps/limiter/test/lim_configurator_SUITE.erl +++ b/apps/limiter/test/lim_configurator_SUITE.erl @@ -1,9 +1,9 @@ -module(lim_configurator_SUITE). --include_lib("stdlib/include/assert.hrl"). -include_lib("common_test/include/ct.hrl"). - +-include_lib("stdlib/include/assert.hrl"). -include_lib("limiter_proto/include/lim_configurator_thrift.hrl"). +-include("lim_ct_helper.hrl"). -export([all/0]). @@ -15,13 +15,12 @@ -export([legacy_create_config/1]). -export([create_config/1]). +-export([create_config_single_scope/1]). -export([get_config/1]). -type group_name() :: atom(). -type test_case_name() :: atom(). --define(RATE_SOURCE_ID, <<"dummy_source_id">>). - %% tests descriptions -spec all() -> [{group, group_name()}]. @@ -36,6 +35,7 @@ groups() -> {default, [], [ legacy_create_config, create_config, + create_config_single_scope, get_config ]} ]. @@ -64,49 +64,88 @@ end_per_suite(Config) -> _ = [application:stop(App) || App <- proplists:get_value(apps, Config)]. -spec init_per_testcase(test_case_name(), config()) -> config(). -init_per_testcase(_Name, C) -> - C. +init_per_testcase(Name, C) -> + [{limit_id, mk_limit_id(Name)} | C]. -spec end_per_testcase(test_case_name(), config()) -> ok. end_per_testcase(_Name, _C) -> ok. +mk_limit_id(Name) -> + genlib:format("~p/~B", [Name, erlang:system_time(millisecond)]). + %% -spec legacy_create_config(config()) -> _. -legacy_create_config(_C) -> +legacy_create_config(C) -> Client = lim_client:new(), + ID = ?config(limit_id, C), + Description = genlib:unique(), Params = #limiter_cfg_LimitCreateParams{ - id = <<"ID">>, + id = ID, name = <<"GlobalMonthTurnover">>, - description = <<"description">>, + description = Description, started_at = <<"2000-01-01T00:00:00Z">>, - body_type = {cash, #limiter_config_LimitBodyTypeCash{currency = <<"RUB">>}} + body_type = ?body_type_cash() }, - {ok, #limiter_config_LimitConfig{}} = lim_client:legacy_create_config(Params, Client). + ?assertMatch( + {ok, #limiter_config_LimitConfig{ + id = ID, + description = Description + }}, + lim_client:legacy_create_config(Params, Client) + ). -spec create_config(config()) -> _. -create_config(_C) -> +create_config(C) -> Client = lim_client:new(), + ID = ?config(limit_id, C), + Description = genlib:unique(), Params = #limiter_config_LimitConfigParams{ - id = <<"ID">>, - description = <<"description">>, + id = ?config(limit_id, C), + description = Description, started_at = <<"2000-01-01T00:00:00Z">>, - body_type = {cash, #limiter_config_LimitBodyTypeCash{currency = <<"RUB">>}}, + body_type = ?body_type_cash(<<"RUB">>), shard_size = 4, - time_range_type = {calendar, {week, #time_range_TimeRangeTypeCalendarWeek{}}}, - type = {turnover, #limiter_config_LimitTypeTurnover{}}, - scope = {scope, {shop, #limiter_config_LimitScopeTypeShop{}}}, - op_behaviour = #limiter_config_OperationLimitBehaviour{ - invoice_payment_refund = {addition, #limiter_config_Addition{}} - }, - context_type = {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}} + time_range_type = ?time_range_week(), + type = ?lim_type_turnover(), + scope = ?scope([ + ?scope_shop(), + ?scope_party() + ]), + op_behaviour = ?op_behaviour(), + context_type = ?ctx_type_payproc() + }, + ?assertMatch( + {ok, #limiter_config_LimitConfig{ + id = ID, + description = Description + }}, + lim_client:create_config(Params, Client) + ). + +-spec create_config_single_scope(config()) -> _. +create_config_single_scope(C) -> + Client = lim_client:new(), + Params = #limiter_config_LimitConfigParams{ + id = ?config(limit_id, C), + started_at = <<"2000-01-01T00:00:00Z">>, + body_type = ?body_type_cash(), + time_range_type = ?time_range_week(), + shard_size = 1, + type = ?lim_type_turnover(), + scope = {single, ?scope_party()}, + context_type = ?ctx_type_payproc(), + op_behaviour = ?op_behaviour() }, - {ok, #limiter_config_LimitConfig{}} = lim_client:create_config(Params, Client). + {ok, #limiter_config_LimitConfig{ + scope = Scope + }} = lim_client:create_config(Params, Client), + ?assertEqual(?scope([?scope_party()]), Scope). -spec get_config(config()) -> _. get_config(C) -> - ID = <<"ID">>, + ID = ?config(limit_id, C), #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), {ok, #limiter_config_LimitConfig{id = ID}} = lim_client:get_config(ID, Client). @@ -119,7 +158,7 @@ prepare_environment(ID, LimitName, _C) -> name = LimitName, description = <<"description">>, started_at = <<"2000-01-01T00:00:00Z">>, - body_type = {cash, #limiter_config_LimitBodyTypeCash{currency = <<"RUB">>}} + body_type = ?body_type_cash() }, {ok, LimitConfig} = lim_client:legacy_create_config(Params, Client), #{config => LimitConfig, client => Client}. diff --git a/apps/limiter/test/lim_ct_helper.hrl b/apps/limiter/test/lim_ct_helper.hrl index 10c1f6ba..ecf44f0b 100644 --- a/apps/limiter/test/lim_ct_helper.hrl +++ b/apps/limiter/test/lim_ct_helper.hrl @@ -3,11 +3,51 @@ -include_lib("limiter_proto/include/lim_configurator_thrift.hrl"). --define(cash(Amount), #limiter_base_Cash{ +-define(currency, <<"RUB">>). + +-define(cash(Amount), ?cash(Amount, ?currency)). +-define(cash(Amount, Currency), #limiter_base_Cash{ amount = Amount, - currency = #limiter_base_CurrencyRef{symbolic_code = <<"RUB">>} + currency = #limiter_base_CurrencyRef{symbolic_code = Currency} +}). + +-define(scope(Types), {multi, ordsets:from_list(Types)}). +-define(global(), ?scope([])). + +-define(scope_party(), {party, #limiter_config_LimitScopeEmptyDetails{}}). +-define(scope_shop(), {shop, #limiter_config_LimitScopeEmptyDetails{}}). + +-define(body_type_cash(), ?body_type_cash(?currency)). +-define(body_type_cash(Currency), + {cash, #limiter_config_LimitBodyTypeCash{currency = Currency}} +). + +-define(lim_type_turnover(), + {turnover, #limiter_config_LimitTypeTurnover{}} +). + +-define(time_range_day(), + {calendar, {day, #time_range_TimeRangeTypeCalendarDay{}}} +). +-define(time_range_week(), + {calendar, {week, #time_range_TimeRangeTypeCalendarWeek{}}} +). +-define(time_range_month(), + {calendar, {month, #time_range_TimeRangeTypeCalendarMonth{}}} +). + +-define(op_behaviour(), ?op_behaviour(?op_addition())). +-define(op_behaviour(Refund), #limiter_config_OperationLimitBehaviour{ + invoice_payment_refund = Refund }). +-define(op_addition(), {addition, #limiter_config_Addition{}}). +-define(op_subtraction(), {subtraction, #limiter_config_Subtraction{}}). + +-define(ctx_type_payproc(), + {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}} +). + -define(op_invoice_payment(), {invoice_payment, #limiter_context_PaymentProcessingOperationInvoicePayment{}}). -define(ctx_invoice_payment(Cost, CaptureCost), ?ctx_invoice_payment(undefined, undefined, Cost, CaptureCost)). diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index e7557e34..6d4b20e8 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -129,7 +129,7 @@ end_per_testcase(_Name, C) -> commit_with_default_exchange(C) -> Rational = #base_Rational{p = 1000000, q = 100}, _ = mock_exchange(Rational, C), - _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + _ = configure_limit(?time_range_month(), ?global(), C), ID = ?config(id, C), Context = #limiter_context_LimitContext{ payment_processing = #limiter_context_ContextPaymentProcessing{ @@ -150,7 +150,7 @@ commit_with_default_exchange(C) -> partial_commit_with_exchange(C) -> Rational = #base_Rational{p = 800000, q = 100}, _ = mock_exchange(Rational, C), - _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + _ = configure_limit(?time_range_month(), ?global(), C), ID = ?config(id, C), Context = #limiter_context_LimitContext{ payment_processing = #limiter_context_ContextPaymentProcessing{ @@ -177,7 +177,7 @@ partial_commit_with_exchange(C) -> commit_with_exchange(C) -> Rational = #base_Rational{p = 1000000, q = 100}, _ = mock_exchange(Rational, C), - _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + _ = configure_limit(?time_range_month(), ?global(), C), ID = ?config(id, C), Context = #limiter_context_LimitContext{ payment_processing = #limiter_context_ContextPaymentProcessing{ @@ -214,7 +214,7 @@ get_rate(C) -> -spec get_limit_notfound(config()) -> _. get_limit_notfound(C) -> - _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + _ = configure_limit(?time_range_month(), ?global(), C), Context = #limiter_context_LimitContext{ payment_processing = #limiter_context_ContextPaymentProcessing{ op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, @@ -225,7 +225,7 @@ get_limit_notfound(C) -> -spec hold_ok(config()) -> _. hold_ok(C) -> - _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + _ = configure_limit(?time_range_month(), ?global(), C), ID = ?config(id, C), Context = #limiter_context_LimitContext{ payment_processing = #limiter_context_ContextPaymentProcessing{ @@ -244,7 +244,7 @@ hold_ok(C) -> -spec commit_ok(config()) -> _. commit_ok(C) -> - _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + _ = configure_limit(?time_range_month(), ?global(), C), ID = ?config(id, C), Context = #limiter_context_LimitContext{ payment_processing = #limiter_context_ContextPaymentProcessing{ @@ -263,7 +263,7 @@ commit_ok(C) -> -spec rollback_ok(config()) -> _. rollback_ok(C) -> - _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + _ = configure_limit(?time_range_week(), ?global(), C), ID = ?config(id, C), Context0 = ?ctx_invoice_payment(?cash(10), ?cash(10)), Context1 = ?ctx_invoice_payment(?cash(10), ?cash(0)), @@ -277,7 +277,7 @@ refund_ok(C) -> Client = ?config(client, C), OwnerID = <<"WWWcool Ltd">>, ShopID = <<"shop">>, - _ = prepare_environment(<<"ShopDayTurnover">>, C), + _ = configure_limit(?time_range_day(), ?scope([?scope_party(), ?scope_shop()]), C), Context0 = ?ctx_invoice_payment(OwnerID, ShopID, ?cash(15), ?cash(15)), RefundContext1 = ?ctx_invoice_payment_refund(OwnerID, ShopID, ?cash(10), ?cash(10), ?cash(10)), {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Payment">>), Context0, Client), @@ -287,13 +287,13 @@ refund_ok(C) -> -spec get_config_ok(config()) -> _. get_config_ok(C) -> - _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + _ = configure_limit(?time_range_week(), ?global(), C), {ok, #limiter_config_LimitConfig{}} = lim_client:get_config(?config(id, C), ?config(client, C)). -spec commit_inexistent_hold_fails(config()) -> _. commit_inexistent_hold_fails(C) -> ID = ?config(id, C), - _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + _ = configure_limit(?time_range_week(), ?global(), C), Context = ?ctx_invoice_payment(?cash(42), undefined), % NOTE % We do not expect `LimitChangeNotFound` here because we no longer reconcile with accounter @@ -304,7 +304,7 @@ commit_inexistent_hold_fails(C) -> -spec partial_commit_inexistent_hold_fails(config()) -> _. partial_commit_inexistent_hold_fails(C) -> ID = ?config(id, C), - _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + _ = configure_limit(?time_range_week(), ?global(), C), Context = ?ctx_invoice_payment(?cash(42), ?cash(21)), % NOTE % We do not expect `LimitChangeNotFound` here because we no longer reconcile with accounter @@ -324,7 +324,7 @@ commit_multirange_limit_ok(C) -> time_range_type = {calendar, {month, #time_range_TimeRangeTypeCalendarMonth{}}}, context_type = {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}, type = {turnover, #limiter_config_LimitTypeTurnover{}}, - scope = {scope_global, #limiter_config_LimitScopeGlobal{}}, + scope = ?scope([]), op_behaviour = #limiter_config_OperationLimitBehaviour{} }, {ok, _LimitConfig} = lim_client:create_config(Params, Client), @@ -356,7 +356,7 @@ commit_multirange_limit_ok(C) -> commit_processes_idempotently(C) -> ID = ?config(id, C), Client = ?config(client, C), - _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + _ = configure_limit(?time_range_week(), ?global(), C), Context = ?ctx_invoice_payment(?cash(42), undefined), Change = ?LIMIT_CHANGE(ID), {ok, _} = lim_client:hold(Change, Context, Client), @@ -370,7 +370,7 @@ commit_processes_idempotently(C) -> full_commit_processes_idempotently(C) -> ID = ?config(id, C), Client = ?config(client, C), - _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + _ = configure_limit(?time_range_week(), ?global(), C), Cost = ?cash(42), Context = ?ctx_invoice_payment(Cost, Cost), Change = ?LIMIT_CHANGE(ID), @@ -385,7 +385,7 @@ full_commit_processes_idempotently(C) -> partial_commit_processes_idempotently(C) -> ID = ?config(id, C), Client = ?config(client, C), - _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + _ = configure_limit(?time_range_week(), ?global(), C), Context = ?ctx_invoice_payment(?cash(42), ?cash(40)), Change = ?LIMIT_CHANGE(ID), {ok, _} = lim_client:hold(Change, Context, Client), @@ -399,7 +399,7 @@ partial_commit_processes_idempotently(C) -> rollback_processes_idempotently(C) -> ID = ?config(id, C), Client = ?config(client, C), - _ = prepare_environment(<<"GlobalMonthTurnover">>, C), + _ = configure_limit(?time_range_week(), ?global(), C), Context = ?ctx_invoice_payment(?cash(42), ?cash(0)), Change = ?LIMIT_CHANGE(ID), {ok, _} = lim_client:hold(Change, Context, Client), @@ -421,19 +421,20 @@ hold_and_commit(Change, Context, Client) -> mock_exchange(Rational, C) -> lim_mock:mock_services([{xrates, fun('GetConvertedAmount', _) -> {ok, Rational} end}], C). -prepare_environment(LimitName, C) -> +configure_limit(TimeRange, Scope, C) -> ID = ?config(id, C), - Params = #limiter_cfg_LimitCreateParams{ + Params = #limiter_config_LimitConfigParams{ id = ID, - name = LimitName, - description = <<"description">>, started_at = <<"2000-01-01T00:00:00Z">>, - body_type = {cash, #limiter_config_LimitBodyTypeCash{currency = <<"RUB">>}}, - op_behaviour = #limiter_config_OperationLimitBehaviour{ - invoice_payment_refund = {subtraction, #limiter_config_Subtraction{}} - } + body_type = ?body_type_cash(<<"RUB">>), + time_range_type = TimeRange, + shard_size = 1, + type = ?lim_type_turnover(), + scope = Scope, + context_type = ?ctx_type_payproc(), + op_behaviour = ?op_behaviour(?op_subtraction()) }, - {ok, _LimitConfig} = lim_client:legacy_create_config(Params, ?config(client, C)). + {ok, _LimitConfig} = lim_client:create_config(Params, ?config(client, C)). gen_unique_id(Prefix) -> genlib:format("~s/~B", [Prefix, lim_time:now()]). diff --git a/rebar.config b/rebar.config index 7cdc517e..3bdee951 100644 --- a/rebar.config +++ b/rebar.config @@ -29,7 +29,7 @@ {damsel, {git, "https://github.com/valitydev/damsel.git", {branch, "master"}}}, {limiter_proto, {git, "https://github.com/valitydev/limiter-proto.git", {branch, "master"}}}, {xrates_proto, {git, "https://github.com/valitydev/xrates-proto.git", {branch, "master"}}}, - {machinery, {git, "https://github.com/valitydev/machinery.git", {branch, "master"}}}, + {machinery, {git, "https://github.com/valitydev/machinery-erlang.git", {branch, "master"}}}, {erl_health, {git, "https://github.com/valitydev/erlang-health.git", {branch, "master"}}}, {genlib, {git, "https://github.com/valitydev/genlib.git", {branch, "master"}}}, {scoper, {git, "https://github.com/valitydev/scoper.git", {branch, "master"}}}, diff --git a/rebar.lock b/rebar.lock index 60f4c74d..12c5305d 100644 --- a/rebar.lock +++ b/rebar.lock @@ -25,16 +25,16 @@ {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},1}, {<<"limiter_proto">>, {git,"https://github.com/valitydev/limiter-proto.git", - {ref,"2e2cdab859222648e389dc74867b5273e73583e9"}}, + {ref,"d390910cd246f2356f10c2db410ecf93e55eff4d"}}, 0}, {<<"machinery">>, - {git,"https://github.com/valitydev/machinery.git", - {ref,"db7c94b9913451e9558afa19f2fe77bf48d391da"}}, + {git,"https://github.com/valitydev/machinery-erlang.git", + {ref,"ff4cfefb616250f6905c25e79f74a7a30eb1aae5"}}, 0}, {<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},2}, {<<"mg_proto">>, - {git,"https://github.com/rbkmoney/machinegun_proto.git", - {ref,"d814d6948d4ff13f6f41d12c6613f59c805750b2"}}, + {git,"https://github.com/valitydev/machinegun-proto.git", + {ref,"b43d6fd0939ee4029ec8873dbd16f3c5fbe4a95c"}}, 1}, {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.2.0">>},2}, {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.1">>},2}, From 0c6b2afd6ba06487b7906c6eec103d47a67fcadf Mon Sep 17 00:00:00 2001 From: Vality Bot Date: Thu, 9 Jun 2022 14:21:17 +0300 Subject: [PATCH 19/54] =?UTF-8?q?=F0=9F=94=84=20Synced=20file(s)=20with=20?= =?UTF-8?q?valitydev/configurations=20(#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🔄 Created local '.github/workflows/basic-linters.yml' from remote 'workflows/base/basic-linters.yml' * 🔄 Synced local 'LICENSE' with remote 'LICENSE' * 🔄 Created local '.github/settings.yml' from remote '.github/settings.yml' --- .github/settings.yml | 2 ++ .github/workflows/basic-linters.yml | 10 ++++++++++ LICENSE | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 .github/settings.yml create mode 100644 .github/workflows/basic-linters.yml diff --git a/.github/settings.yml b/.github/settings.yml new file mode 100644 index 00000000..9267e7d0 --- /dev/null +++ b/.github/settings.yml @@ -0,0 +1,2 @@ +# These settings are synced to GitHub by https://probot.github.io/apps/settings/ +_extends: .github diff --git a/.github/workflows/basic-linters.yml b/.github/workflows/basic-linters.yml new file mode 100644 index 00000000..6114f14f --- /dev/null +++ b/.github/workflows/basic-linters.yml @@ -0,0 +1,10 @@ +name: Vality basic linters + +on: + pull_request: + branches: + - "*" + +jobs: + lint: + uses: valitydev/base-workflows/.github/workflows/basic-linters.yml@v1 diff --git a/LICENSE b/LICENSE index 2bb9ad24..d9a10c0d 100644 --- a/LICENSE +++ b/LICENSE @@ -173,4 +173,4 @@ incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - END OF TERMS AND CONDITIONS \ No newline at end of file + END OF TERMS AND CONDITIONS From d36b97f44a2cd4ce35e54dc82308cb050356671c Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Fri, 10 Jun 2022 12:56:13 +0300 Subject: [PATCH 20/54] TD-304: Handle `amount` limit body type (#8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Bump to valitydev/limiter-proto@6723e86. * Introduce _noncurrency_ concept instead of default currency which is kinda misleading. * Rename `lim_p_transfer` → `lim_posting`. * (Minor API break) Respond w/ 0 balance on uninitialized limit ranges. --- apps/limiter/src/lim_accounting.erl | 13 +- apps/limiter/src/lim_body.erl | 58 +++--- apps/limiter/src/lim_config_codec.erl | 113 ++++++++--- apps/limiter/src/lim_config_machine.erl | 30 +-- .../src/lim_config_machinery_schema.erl | 3 +- apps/limiter/src/lim_configurator.erl | 22 +-- apps/limiter/src/lim_context.erl | 10 +- apps/limiter/src/lim_handler.erl | 4 +- apps/limiter/src/lim_p_transfer.erl | 34 ---- apps/limiter/src/lim_posting.erl | 32 ++++ apps/limiter/src/lim_range_machine.erl | 22 +-- apps/limiter/src/lim_rates.erl | 46 ++--- apps/limiter/src/lim_turnover_metric.erl | 61 ++++++ apps/limiter/src/lim_turnover_processor.erl | 138 ++++++-------- apps/limiter/test/lim_configurator_SUITE.erl | 10 +- apps/limiter/test/lim_ct_helper.hrl | 23 ++- apps/limiter/test/lim_turnover_SUITE.erl | 175 ++++++++++++------ elvis.config | 3 +- rebar.lock | 2 +- 19 files changed, 467 insertions(+), 332 deletions(-) delete mode 100644 apps/limiter/src/lim_p_transfer.erl create mode 100644 apps/limiter/src/lim_posting.erl create mode 100644 apps/limiter/src/lim_turnover_metric.erl diff --git a/apps/limiter/src/lim_accounting.erl b/apps/limiter/src/lim_accounting.erl index 3fe0130e..0ef575a8 100644 --- a/apps/limiter/src/lim_accounting.erl +++ b/apps/limiter/src/lim_accounting.erl @@ -9,9 +9,10 @@ -export([rollback/3]). -export([get_plan/2]). -export([get_balance/2]). --export([get_default_currency/0]). -export([create_account/2]). +-export([noncurrency/0]). + -type currency() :: dmsl_domain_thrift:'CurrencySymbolicCode'(). -type amount() :: dmsl_domain_thrift:'Amount'(). -type plan_id() :: dmsl_accounter_thrift:'PlanID'(). @@ -40,7 +41,7 @@ -export_type([batch_id/0]). -export_type([invalid_request_error/0]). --define(DEFAULT_CURRENCY, <<"RUB">>). +-define(NONCURRENCY, <<>>). -spec plan(plan_id(), [batch()], lim_context()) -> ok | {error, invalid_request_error()}. plan(_PlanID, [], _LimitContext) -> @@ -133,9 +134,9 @@ construct_balance( currency => Currency }. --spec get_default_currency() -> currency(). -get_default_currency() -> - ?DEFAULT_CURRENCY. +-spec noncurrency() -> currency(). +noncurrency() -> + ?NONCURRENCY. -spec create_account(currency(), lim_context()) -> {ok, account_id()}. create_account(CurrencyCode, LimitContext) -> @@ -157,7 +158,7 @@ construct_prototype(CurrencyCode, Description) -> %% call_accounter(Function, Args, LimitContext) -> - {ok, WoodyContext} = lim_context:woody_context(LimitContext), + WoodyContext = lim_context:woody_context(LimitContext), lim_client_woody:call(accounter, Function, Args, WoodyContext). convert_exception(#'InvalidRequest'{errors = Errors}) -> diff --git a/apps/limiter/src/lim_body.erl b/apps/limiter/src/lim_body.erl index d5cd1340..4c500dc9 100644 --- a/apps/limiter/src/lim_body.erl +++ b/apps/limiter/src/lim_body.erl @@ -1,9 +1,6 @@ -module(lim_body). --export([get_body/3]). --export([create_body_from_cash/2]). - --type t() :: {amount, amount()} | {cash, cash()}. +-export([get/3]). -type amount() :: integer(). -type cash() :: #{ @@ -14,32 +11,25 @@ -type currency() :: lim_base_thrift:'CurrencySymbolicCode'(). -type config() :: lim_config_machine:config(). -type body_type() :: full | partial. --type get_body_error() :: notfound | lim_rates:conversion_error(). --export_type([t/0]). +-export_type([amount/0]). +-export_type([currency/0]). -export_type([cash/0]). --export_type([get_body_error/0]). --spec get_body(body_type(), config(), lim_context:t()) -> {ok, t()} | {error, get_body_error()}. -get_body(BodyType, Config = #{body_type := {cash, ConfigCurrency}}, LimitContext) -> - ContextType = lim_config_machine:context_type(Config), - {ok, Operation} = lim_context:get_operation(ContextType, LimitContext), - case get_body_for_operation(BodyType, Operation, Config, LimitContext) of - {ok, {cash, #{currency := ConfigCurrency}}} = Result -> - Result; - {ok, {cash, #{amount := Amount, currency := Currency}}} -> - case lim_rates:get_converted_amount({Amount, Currency}, Config, LimitContext) of - {ok, ConvertedAmount} -> - {ok, create_body_from_cash(ConvertedAmount, ConfigCurrency)}; - Error -> - Error - end; - Error -> - Error - end. +-import(lim_pipeline, [do/1, unwrap/1]). + +-spec get(body_type(), config(), lim_context:t()) -> + {ok, cash()} | {error, notfound}. +get(BodyType, Config, LimitContext) -> + do(fun() -> + ContextType = lim_config_machine:context_type(Config), + {ok, Operation} = lim_context:get_operation(ContextType, LimitContext), + Body = unwrap(get_body_for_operation(BodyType, Operation, Config, LimitContext)), + apply_op_behaviour(Body, Config, LimitContext) + end). -spec get_body_for_operation(body_type(), lim_context:context_operation(), config(), lim_context:t()) -> - {ok, t()} | {error, notfound}. + {ok, cash()} | {error, notfound}. get_body_for_operation(full, invoice = Operation, Config, LimitContext) -> ContextType = lim_config_machine:context_type(Config), lim_context:get_from_context(ContextType, cost, Operation, LimitContext); @@ -73,6 +63,18 @@ get_body_for_operation(partial, invoice_payment_refund = Operation, Config, Limi get_body_for_operation(partial, invoice_payment_chargeback, _Config, _LimitContext) -> {error, notfound}. --spec create_body_from_cash(amount(), currency()) -> t(). -create_body_from_cash(Amount, Currency) -> - {cash, #{amount => Amount, currency => Currency}}. +apply_op_behaviour(Body, #{op_behaviour := ComputationConfig}, LimitContext) -> + {ok, Operation} = lim_context:get_operation(payment_processing, LimitContext), + case maps:get(Operation, ComputationConfig, undefined) of + addition -> + Body; + subtraction -> + invert_body(Body); + undefined -> + Body + end; +apply_op_behaviour(Body, _Config, _LimitContext) -> + Body. + +invert_body(Cash = #{amount := Amount}) -> + Cash#{amount := -Amount}. diff --git a/apps/limiter/src/lim_config_codec.erl b/apps/limiter/src/lim_config_codec.erl index 14afa57f..b53e60de 100644 --- a/apps/limiter/src/lim_config_codec.erl +++ b/apps/limiter/src/lim_config_codec.erl @@ -5,7 +5,6 @@ -export([marshal/2]). -export([unmarshal/2]). -export([marshal_config/1]). --export([unmarshal_body_type/1]). -export([unmarshal_op_behaviour/1]). -export([unmarshal_params/1]). -export([maybe_apply/2]). @@ -20,12 +19,18 @@ -type decoded_value() :: decoded_value(any()). -type decoded_value(T) :: T. --spec maybe_apply(any(), function()) -> any(). +-spec maybe_apply(T, fun((T) -> U)) -> U | undefined. maybe_apply(undefined, _) -> undefined; maybe_apply(Value, Fun) -> Fun(Value). +-spec maybe_apply(T, fun((T) -> U), Default) -> U | Default. +maybe_apply(undefined, _, Default) -> + Default; +maybe_apply(Value, Fun, _Default) -> + Fun(Value). + %% API -spec marshal(type_name(), decoded_value()) -> encoded_value(). @@ -56,7 +61,6 @@ marshal_config(Config) -> id = lim_config_machine:id(Config), processor_type = lim_config_machine:processor_type(Config), description = lim_config_machine:description(Config), - body_type = marshal_body_type(lim_config_machine:body_type(Config)), created_at = lim_config_machine:created_at(Config), started_at = lim_config_machine:started_at(Config), shard_size = lim_config_machine:shard_size(Config), @@ -78,11 +82,6 @@ marshal_behaviour(subtraction) -> marshal_behaviour(addition) -> {addition, #limiter_config_Addition{}}. -marshal_body_type(amount) -> - {amount, #limiter_config_LimitBodyTypeAmount{}}; -marshal_body_type({cash, Currency}) -> - {cash, #limiter_config_LimitBodyTypeCash{currency = Currency}}. - marshal_time_range_type({calendar, CalendarType}) -> {calendar, marshal_calendar_time_range_type(CalendarType)}; marshal_time_range_type({interval, Amount}) -> @@ -100,8 +99,15 @@ marshal_calendar_time_range_type(year) -> marshal_context_type(payment_processing) -> {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}. -marshal_type(turnover) -> - {turnover, #limiter_config_LimitTypeTurnover{}}. +marshal_type({turnover, Metric}) -> + {turnover, #limiter_config_LimitTypeTurnover{ + metric = marshal_turnover_metric(Metric) + }}. + +marshal_turnover_metric(number) -> + {number, #limiter_config_LimitTurnoverNumber{}}; +marshal_turnover_metric({amount, Currency}) -> + {amount, #limiter_config_LimitTurnoverAmount{currency = Currency}}. marshal_scope(Types) -> {multi, ordsets:from_list(lists:map(fun marshal_scope_type/1, ordsets:to_list(Types)))}. @@ -145,7 +151,6 @@ unmarshal_timestamp(Timestamp) when is_binary(Timestamp) -> unmarshal_params(#limiter_config_LimitConfigParams{ id = ID, description = Description, - body_type = BodyType, started_at = StartedAt, shard_size = ShardSize, time_range_type = TimeRangeType, @@ -156,7 +161,6 @@ unmarshal_params(#limiter_config_LimitConfigParams{ }) -> genlib_map:compact(#{ id => ID, - body_type => unmarshal_body_type(BodyType), started_at => StartedAt, shard_size => ShardSize, time_range_type => unmarshal_time_range_type(TimeRangeType), @@ -174,31 +178,43 @@ unmarshal_config(#limiter_config_LimitConfig{ id = ID, processor_type = ProcessorType, description = Description, - body_type = BodyType, created_at = CreatedAt, started_at = StartedAt, shard_size = ShardSize, time_range_type = TimeRangeType, context_type = ContextType, - type = Type, + type = TypeIn, scope = Scope, - op_behaviour = OpBehaviour + op_behaviour = OpBehaviour, + body_type_deprecated = BodyTypeIn }) -> + Type = maybe_apply(TypeIn, fun unmarshal_type/1), + BodyType = maybe_apply(BodyTypeIn, fun unmarshal_body_type_deprecated/1), genlib_map:compact(#{ id => ID, processor_type => ProcessorType, created_at => lim_time:from_rfc3339(CreatedAt), - body_type => unmarshal_body_type(BodyType), started_at => StartedAt, shard_size => ShardSize, time_range_type => unmarshal_time_range_type(TimeRangeType), context_type => unmarshal_context_type(ContextType), - type => maybe_apply(Type, fun unmarshal_type/1), + type => derive_type(Type, BodyType), scope => maybe_apply(Scope, fun unmarshal_scope/1), description => Description, op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1) }). +derive_type(Type, undefined) -> + % NOTE + % Current protocol disallows configuring (deprecated) body type, thus we trust limit type. + Type; +derive_type({turnover, _}, {cash, Currency}) -> + % NOTE + % Treating limits with configured (deprecated) body type as turnover limits with amount metric. + {turnover, {amount, Currency}}; +derive_type(undefined, {cash, Currency}) -> + {turnover, {amount, Currency}}. + -spec unmarshal_op_behaviour(encoded_value()) -> decoded_value(). unmarshal_op_behaviour(OpBehaviour) -> #limiter_config_OperationLimitBehaviour{ @@ -213,10 +229,8 @@ unmarshal_behaviour({subtraction, #limiter_config_Subtraction{}}) -> unmarshal_behaviour({addition, #limiter_config_Addition{}}) -> addition. --spec unmarshal_body_type(encoded_value()) -> decoded_value(). -unmarshal_body_type({amount, #limiter_config_LimitBodyTypeAmount{}}) -> - amount; -unmarshal_body_type({cash, #limiter_config_LimitBodyTypeCash{currency = Currency}}) -> +-spec unmarshal_body_type_deprecated(encoded_value()) -> decoded_value(). +unmarshal_body_type_deprecated({cash, #limiter_config_LimitBodyTypeCash{currency = Currency}}) -> {cash, Currency}. unmarshal_time_range_type({calendar, CalendarType}) -> @@ -236,8 +250,13 @@ unmarshal_calendar_time_range_type({year, _}) -> unmarshal_context_type({payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}) -> payment_processing. -unmarshal_type({turnover, #limiter_config_LimitTypeTurnover{}}) -> - turnover. +unmarshal_type({turnover, #limiter_config_LimitTypeTurnover{metric = Metric}}) -> + {turnover, maybe_apply(Metric, fun unmarshal_turnover_metric/1, number)}. + +unmarshal_turnover_metric({number, _}) -> + number; +unmarshal_turnover_metric({amount, #limiter_config_LimitTurnoverAmount{currency = Currency}}) -> + {amount, Currency}. unmarshal_scope({single, Type}) -> ordsets:from_list([unmarshal_scope_type(Type)]); @@ -263,23 +282,65 @@ unmarshal_scope_type({payment_tool, _}) -> -spec test() -> _. -spec marshal_unmarshal_created_test() -> _. - marshal_unmarshal_created_test() -> Created = {created, #{ id => <<"id">>, processor_type => <<"type">>, created_at => lim_time:now(), - body_type => {cash, <<"RUB">>}, started_at => <<"2000-01-01T00:00:00Z">>, shard_size => 7, time_range_type => {calendar, day}, context_type => payment_processing, - type => turnover, + type => {turnover, number}, scope => ordsets:from_list([party, shop]), description => <<"description">> }}, Event = {ev, lim_time:machinery_now(), Created}, ?assertEqual(Event, unmarshal(timestamped_change, marshal(timestamped_change, Event))). +-spec unmarshal_created_w_deprecated_body_type_test_() -> [_TestGen]. +unmarshal_created_w_deprecated_body_type_test_() -> + Now = lim_time:now(), + Config = #limiter_config_LimitConfig{ + id = <<"ID">>, + processor_type = <<"TurnoverProcessor">>, + created_at = lim_time:to_rfc3339(Now), + started_at = <<"2000-01-01T00:00:00Z">>, + shard_size = 42, + time_range_type = {calendar, {day, #time_range_TimeRangeTypeCalendarDay{}}}, + context_type = {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}, + body_type_deprecated = {cash, #limiter_config_LimitBodyTypeCash{currency = <<"☭☭☭"/utf8>>}} + }, + [ + ?_assertMatch( + {created, #{ + id := <<"ID">>, + created_at := Now, + type := {turnover, {amount, <<"☭☭☭"/utf8>>}} + }}, + unmarshal_change( + {created, #limiter_config_CreatedChange{ + limit_config = Config#limiter_config_LimitConfig{ + type = undefined + } + }} + ) + ), + ?_assertMatch( + {created, #{ + id := <<"ID">>, + created_at := Now, + type := {turnover, {amount, <<"☭☭☭"/utf8>>}} + }}, + unmarshal_change( + {created, #limiter_config_CreatedChange{ + limit_config = Config#limiter_config_LimitConfig{ + type = {turnover, #limiter_config_LimitTypeTurnover{}} + } + }} + ) + ) + ]. + -endif. diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index fff47c2d..48a8ccfb 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -7,8 +7,6 @@ -export([created_at/1]). -export([id/1]). -export([description/1]). --export([body_type/1]). --export([currency/1]). -export([started_at/1]). -export([shard_size/1]). -export([time_range_type/1]). @@ -38,10 +36,9 @@ -type processor() :: lim_router:processor(). -type description() :: binary(). --type limit_type() :: turnover. +-type limit_type() :: {turnover, lim_turnover_metric:t()}. -type limit_scope() :: ordsets:ordset(limit_scope_type()). -type limit_scope_type() :: party | shop | wallet | identity. --type body_type() :: {cash, currency()} | amount. -type shard_size() :: pos_integer(). -type shard_id() :: binary(). -type prefix() :: binary(). @@ -57,12 +54,11 @@ id := lim_id(), processor_type := processor_type(), created_at := lim_time:timestamp_ms(), - body_type := body_type(), started_at := timestamp(), shard_size := shard_size(), time_range_type := time_range_type(), context_type := context_type(), - type => limit_type(), + type := limit_type(), scope => limit_scope(), description => description(), op_behaviour => op_behaviour() @@ -70,7 +66,6 @@ -type create_params() :: #{ processor_type := processor_type(), - body_type := body_type(), started_at := timestamp(), shard_size := shard_size(), time_range_type := time_range_type(), @@ -88,16 +83,13 @@ -type lim_change() :: lim_limiter_thrift:'LimitChange'(). -type limit() :: lim_limiter_thrift:'Limit'(). -type timestamp() :: lim_base_thrift:'Timestamp'(). --type currency() :: lim_base_thrift:'CurrencySymbolicCode'(). -export_type([config/0]). --export_type([body_type/0]). -export_type([limit_type/0]). -export_type([limit_scope/0]). -export_type([time_range_type/0]). -export_type([time_range/0]). -export_type([create_params/0]). --export_type([currency/0]). -export_type([lim_id/0]). -export_type([lim_change/0]). -export_type([limit/0]). @@ -180,16 +172,6 @@ description(#{description := ID}) -> description(_) -> undefined. --spec body_type(config()) -> body_type(). -body_type(#{body_type := BodyType}) -> - BodyType. - --spec currency(config()) -> currency() | undefined. -currency(#{body_type := {cash, Currency}}) -> - Currency; -currency(#{body_type := amount}) -> - undefined. - -spec started_at(config()) -> timestamp(). started_at(#{started_at := Value}) -> Value. @@ -206,11 +188,11 @@ time_range_type(#{time_range_type := Value}) -> processor_type(#{processor_type := Value}) -> Value. --spec type(config()) -> lim_maybe:maybe(limit_type()). +-spec type(config()) -> limit_type(). type(#{type := Value}) -> Value; type(_) -> - undefined. + {turnover, number}. -spec scope(config()) -> limit_scope(). scope(#{scope := Value}) -> @@ -232,7 +214,7 @@ op_behaviour(_) -> -spec start(lim_id(), create_params(), lim_context()) -> {ok, config()}. start(ID, Params, LimitContext) -> - {ok, WoodyCtx} = lim_context:woody_context(LimitContext), + WoodyCtx = lim_context:woody_context(LimitContext), Config = genlib_map:compact(Params#{id => ID, created_at => lim_time:now()}), case machinery:start(?NS, ID, [{created, Config}], get_backend(WoodyCtx)) of ok -> @@ -245,7 +227,7 @@ start(ID, Params, LimitContext) -> -spec get(lim_id(), lim_context()) -> {ok, config()} | {error, notfound}. get(ID, LimitContext) -> do(fun() -> - {ok, WoodyCtx} = lim_context:woody_context(LimitContext), + WoodyCtx = lim_context:woody_context(LimitContext), Machine = unwrap(machinery:get(?NS, ID, get_backend(WoodyCtx))), collapse(Machine) end). diff --git a/apps/limiter/src/lim_config_machinery_schema.erl b/apps/limiter/src/lim_config_machinery_schema.erl index 48ea9116..b18ec3ad 100644 --- a/apps/limiter/src/lim_config_machinery_schema.erl +++ b/apps/limiter/src/lim_config_machinery_schema.erl @@ -96,12 +96,11 @@ marshal_unmarshal_created_test() -> id => ID, processor_type => <<"type">>, created_at => lim_time:now(), - body_type => {cash, <<"RUB">>}, started_at => <<"2000-01-01T00:00:00Z">>, shard_size => 7, time_range_type => {calendar, day}, context_type => payment_processing, - type => turnover, + type => {turnover, {amount, <<"RUB">>}}, scope => ordsets:from_list([party]), description => <<"description">> }}, diff --git a/apps/limiter/src/lim_configurator.erl b/apps/limiter/src/lim_configurator.erl index 9b90b9d7..93c65027 100644 --- a/apps/limiter/src/lim_configurator.erl +++ b/apps/limiter/src/lim_configurator.erl @@ -8,21 +8,19 @@ -export([handle_function/4]). -%% - --type lim_context() :: lim_context:t(). +-define(DEFAULT_CURRENCY, <<"RUB">>). %% -spec handle_function(woody:func(), woody:args(), woody_context:ctx(), woody:options()) -> {ok, woody:result()}. handle_function(Fn, Args, WoodyCtx, Opts) -> - {ok, LimitContext} = lim_context:create(WoodyCtx), + LimitContext = lim_context:create(WoodyCtx), scoper:scope( configurator, fun() -> handle_function_(Fn, Args, LimitContext, Opts) end ). --spec handle_function_(woody:func(), woody:args(), lim_context(), woody:options()) -> {ok, woody:result()}. +-spec handle_function_(woody:func(), woody:args(), lim_context:t(), woody:options()) -> {ok, woody:result()}. handle_function_( 'CreateLegacy', {#limiter_cfg_LimitCreateParams{ @@ -30,7 +28,6 @@ handle_function_( name = Name, description = Description, started_at = StartedAt, - body_type = BodyType, op_behaviour = OpBehaviour }}, LimitContext, @@ -43,7 +40,6 @@ handle_function_( genlib_map:compact(Config#{ description => Description, started_at => StartedAt, - body_type => lim_config_codec:unmarshal_body_type(BodyType), op_behaviour => lim_config_codec:maybe_apply( OpBehaviour, fun lim_config_codec:unmarshal_op_behaviour/1 @@ -77,7 +73,7 @@ handle_function_('Get', {LimitID}, LimitContext, _Opts) -> woody_error:raise(business, #limiter_cfg_LimitConfigNotFound{}) end. -map_type(turnover) -> +map_type({turnover, _}) -> <<"TurnoverProcessor">>; map_type(_) -> woody_error:raise( @@ -88,7 +84,7 @@ map_type(_) -> mk_limit_config(<<"ShopDayTurnover">>) -> {ok, #{ processor_type => <<"TurnoverProcessor">>, - type => turnover, + type => {turnover, {amount, ?DEFAULT_CURRENCY}}, scope => ordsets:from_list([shop]), shard_size => 7, context_type => payment_processing, @@ -97,7 +93,7 @@ mk_limit_config(<<"ShopDayTurnover">>) -> mk_limit_config(<<"PartyDayTurnover">>) -> {ok, #{ processor_type => <<"TurnoverProcessor">>, - type => turnover, + type => {turnover, {amount, ?DEFAULT_CURRENCY}}, scope => ordsets:from_list([party]), shard_size => 7, context_type => payment_processing, @@ -106,7 +102,7 @@ mk_limit_config(<<"PartyDayTurnover">>) -> mk_limit_config(<<"ShopMonthTurnover">>) -> {ok, #{ processor_type => <<"TurnoverProcessor">>, - type => turnover, + type => {turnover, {amount, ?DEFAULT_CURRENCY}}, scope => ordsets:from_list([shop]), shard_size => 12, context_type => payment_processing, @@ -115,7 +111,7 @@ mk_limit_config(<<"ShopMonthTurnover">>) -> mk_limit_config(<<"PartyMonthTurnover">>) -> {ok, #{ processor_type => <<"TurnoverProcessor">>, - type => turnover, + type => {turnover, {amount, ?DEFAULT_CURRENCY}}, scope => ordsets:from_list([party]), shard_size => 12, context_type => payment_processing, @@ -124,7 +120,7 @@ mk_limit_config(<<"PartyMonthTurnover">>) -> mk_limit_config(<<"GlobalMonthTurnover">>) -> {ok, #{ processor_type => <<"TurnoverProcessor">>, - type => turnover, + type => {turnover, {amount, ?DEFAULT_CURRENCY}}, scope => ordsets:new(), shard_size => 12, context_type => payment_processing, diff --git a/apps/limiter/src/lim_context.erl b/apps/limiter/src/lim_context.erl index ce0b8e60..90e2bafb 100644 --- a/apps/limiter/src/lim_context.erl +++ b/apps/limiter/src/lim_context.erl @@ -96,13 +96,13 @@ -export_type([context_type/0]). -export_type([context_operation/0]). --spec create(woody_context()) -> {ok, t()}. +-spec create(woody_context()) -> t(). create(WoodyContext) -> - {ok, #{woody_context => WoodyContext}}. + #{woody_context => WoodyContext}. --spec woody_context(t()) -> {ok, woody_context()}. +-spec woody_context(t()) -> woody_context(). woody_context(Context) -> - {ok, maps:get(woody_context, Context)}. + maps:get(woody_context, Context). -spec clock(t()) -> {ok, clock()} | {error, notfound}. clock(#{clock := Clock}) -> @@ -294,7 +294,7 @@ unmarshal_payment_processing_invoice_payment_chargeback(#limiter_context_Invoice }). unmarshal_cash(#limiter_base_Cash{amount = Amount, currency = #limiter_base_CurrencyRef{symbolic_code = Currency}}) -> - lim_body:create_body_from_cash(Amount, Currency). + #{amount => Amount, currency => Currency}. unmarshal_string(Value) -> Value. diff --git a/apps/limiter/src/lim_handler.erl b/apps/limiter/src/lim_handler.erl index 119ec31d..8a6b4f19 100644 --- a/apps/limiter/src/lim_handler.erl +++ b/apps/limiter/src/lim_handler.erl @@ -18,7 +18,7 @@ -spec handle_function(woody:func(), woody:args(), woody_context:ctx(), woody:options()) -> {ok, woody:result()}. handle_function(Fn, Args, WoodyCtx, Opts) -> - {ok, LimitContext} = lim_context:create(WoodyCtx), + LimitContext = lim_context:create(WoodyCtx), scoper:scope( limiter, fun() -> handle_function_(Fn, Args, LimitContext, Opts) end @@ -79,8 +79,6 @@ handle_function_('Rollback', {LimitChange = ?LIMIT_CHANGE(LimitID), Clock, Conte end. -spec handle_get_error(_) -> no_return(). -handle_get_error({_, {range, notfound}}) -> - woody_error:raise(business, #limiter_LimitNotFound{}); handle_get_error(Error) -> handle_default_error(Error). diff --git a/apps/limiter/src/lim_p_transfer.erl b/apps/limiter/src/lim_p_transfer.erl deleted file mode 100644 index 2e1637b6..00000000 --- a/apps/limiter/src/lim_p_transfer.erl +++ /dev/null @@ -1,34 +0,0 @@ --module(lim_p_transfer). - --include_lib("damsel/include/dmsl_accounter_thrift.hrl"). - --export([construct_posting/2]). --export([reverse_posting/1]). - --type posting() :: lim_accounting:posting(). --type body() :: lim_body:t(). - --spec construct_posting(lim_range_machine:time_range_ext(), body()) -> posting(). -construct_posting(#{account_id_from := From, account_id_to := To}, {cash, #{amount := Amount, currency := Currency}}) -> - #accounter_Posting{ - from_id = From, - to_id = To, - amount = Amount, - currency_sym_code = Currency, - description = <<>> - }; -construct_posting(#{account_id_from := From, account_id_to := To}, {amount, Amount}) -> - #accounter_Posting{ - from_id = From, - to_id = To, - amount = Amount, - currency_sym_code = lim_accounting:get_default_currency(), - description = <<>> - }. - --spec reverse_posting(posting()) -> posting(). -reverse_posting(Posting = #accounter_Posting{from_id = AccountFrom, to_id = AccountTo}) -> - Posting#accounter_Posting{ - from_id = AccountTo, - to_id = AccountFrom - }. diff --git a/apps/limiter/src/lim_posting.erl b/apps/limiter/src/lim_posting.erl new file mode 100644 index 00000000..966d20c6 --- /dev/null +++ b/apps/limiter/src/lim_posting.erl @@ -0,0 +1,32 @@ +-module(lim_posting). + +-include_lib("damsel/include/dmsl_accounter_thrift.hrl"). + +-export([new/3]). +-export([reverse/1]). + +-type posting() :: lim_accounting:posting(). + +-spec new(lim_range_machine:time_range_ext(), lim_body:amount(), lim_body:currency()) -> + posting(). +new(#{account_id_from := From, account_id_to := To}, Amount, Currency) -> + reverse_negative_posting(#accounter_Posting{ + from_id = From, + to_id = To, + amount = Amount, + currency_sym_code = Currency, + description = <<>> + }). + +reverse_negative_posting(Posting = #accounter_Posting{amount = Amount}) when Amount < 0 -> + PostingReversed = reverse(Posting), + PostingReversed#accounter_Posting{amount = -Amount}; +reverse_negative_posting(Posting) -> + Posting. + +-spec reverse(posting()) -> posting(). +reverse(Posting = #accounter_Posting{from_id = AccountFrom, to_id = AccountTo}) -> + Posting#accounter_Posting{ + from_id = AccountTo, + to_id = AccountFrom + }. diff --git a/apps/limiter/src/lim_range_machine.erl b/apps/limiter/src/lim_range_machine.erl index 0660f3cc..a37a1ca2 100644 --- a/apps/limiter/src/lim_range_machine.erl +++ b/apps/limiter/src/lim_range_machine.erl @@ -39,7 +39,7 @@ -type id() :: binary(). -type time_range_type() :: lim_config_machine:time_range_type(). -type time_range() :: lim_config_machine:time_range(). --type currency() :: lim_config_machine:currency(). +-type currency() :: lim_body:currency(). -type limit_range_state() :: #{ id := id(), @@ -109,27 +109,27 @@ ranges(#{ranges := Ranges}) -> ranges(_State) -> []. --spec currency(limit_range_state()) -> currency(). +-spec currency(limit_range_state() | create_params()) -> currency(). currency(#{currency := Currency}) -> Currency; currency(_State) -> - lim_accounting:get_default_currency(). + lim_accounting:noncurrency(). %%% API -spec get(id(), lim_context()) -> {ok, limit_range_state()} | {error, notfound}. get(ID, LimitContext) -> - {ok, WoodyCtx} = lim_context:woody_context(LimitContext), - get_state(ID, WoodyCtx). + get_state(ID, lim_context:woody_context(LimitContext)). -spec ensure_exists(create_params(), time_range(), lim_context()) -> {ok, time_range_ext()}. -ensure_exists(Params = #{id := ID, currency := Currency}, TimeRange, LimitContext) -> - {ok, WoodyCtx} = lim_context:woody_context(LimitContext), +ensure_exists(Params = #{id := ID}, TimeRange, LimitContext) -> + WoodyCtx = lim_context:woody_context(LimitContext), case get_state(ID, WoodyCtx) of {ok, State} -> ensure_range_exist_in_state(TimeRange, State, WoodyCtx); {error, notfound} -> - _ = start(ID, Params, [new_time_range_ext(TimeRange, Currency, WoodyCtx)], WoodyCtx), + TimeRangeExt = new_time_range_ext(TimeRange, currency(Params), WoodyCtx), + _ = start(ID, Params, [TimeRangeExt], WoodyCtx), {ok, State} = get_state(ID, WoodyCtx), get_range(TimeRange, State) end. @@ -162,9 +162,7 @@ get_range_balance(ID, TimeRange, LimitContext) -> -spec init(args([event()]), machine(), handler_args(), handler_opts()) -> result(). init(Events, _Machine, _HandlerArgs, _HandlerOpts) -> - #{ - events => emit_events(Events) - }. + #{events => emit_events(Events)}. -spec process_call(args(range_call()), machine(), handler_args(), handler_opts()) -> {response(time_range_ext()), result()} | no_return(). @@ -196,7 +194,7 @@ find_time_range(TimeRange, [_Head | Rest]) -> find_time_range(TimeRange, Rest). new_time_range_ext(TimeRange, Currency, WoodyCtx) -> - {ok, LimitContext} = lim_context:create(WoodyCtx), + LimitContext = lim_context:create(WoodyCtx), {ok, AccountIDFrom} = lim_accounting:create_account(Currency, LimitContext), {ok, AccountIDTo} = lim_accounting:create_account(Currency, LimitContext), TimeRange#{ diff --git a/apps/limiter/src/lim_rates.erl b/apps/limiter/src/lim_rates.erl index 143f5ce6..f7eb00db 100644 --- a/apps/limiter/src/lim_rates.erl +++ b/apps/limiter/src/lim_rates.erl @@ -2,10 +2,8 @@ -include_lib("xrates_proto/include/xrates_rate_thrift.hrl"). --export([get_converted_amount/3]). +-export([convert/4]). --type amount() :: dmsl_domain_thrift:'Amount'(). --type currency() :: dmsl_domain_thrift:'CurrencySymbolicCode'(). -type limit_context() :: lim_context:t(). -type config() :: lim_config_machine:config(). @@ -17,37 +15,32 @@ -define(DEFAULT_FACTOR, 1.1). -define(DEFAULT_FACTOR_NAME, <<"DEFAULT">>). --spec get_converted_amount({amount(), currency()}, config(), limit_context()) -> - {ok, amount()} +-spec convert(lim_body:cash(), lim_body:currency(), config(), limit_context()) -> + {ok, lim_body:cash()} | {error, conversion_error()}. -get_converted_amount(Cash = {_Amount, Currency}, Config, LimitContext) -> - Factor = get_exchange_factor(Currency), - case - call_rates( - 'GetConvertedAmount', - {<<"CBR">>, construct_conversion_request(Cash, Config, LimitContext)}, - LimitContext - ) - of +convert(#{amount := Amount, currency := Currency}, DestinationCurrency, Config, LimitContext) -> + ContextType = lim_config_machine:context_type(Config), + {ok, Timestamp} = lim_context:get_from_context(ContextType, created_at, LimitContext), + Request = #rate_ConversionRequest{ + source = Currency, + destination = DestinationCurrency, + amount = Amount, + datetime = Timestamp + }, + case call_rates('GetConvertedAmount', {<<"CBR">>, Request}, LimitContext) of {ok, #base_Rational{p = P, q = Q}} -> Rational = genlib_rational:new(P, Q), - {ok, genlib_rational:round(genlib_rational:mul(Rational, Factor))}; + Factor = get_exchange_factor(Currency), + {ok, #{ + amount => genlib_rational:round(genlib_rational:mul(Rational, Factor)), + currency => DestinationCurrency + }}; {exception, #rate_QuoteNotFound{}} -> {error, quote_not_found}; {exception, #rate_CurrencyNotFound{}} -> {error, currency_not_found} end. -construct_conversion_request({Amount, Currency}, Config = #{body_type := {cash, DestinationCurrency}}, LimitContext) -> - ContextType = lim_config_machine:context_type(Config), - {ok, Timestamp} = lim_context:get_from_context(ContextType, created_at, LimitContext), - #rate_ConversionRequest{ - source = Currency, - destination = DestinationCurrency, - amount = Amount, - datetime = Timestamp - }. - get_exchange_factor(Currency) -> Factors = genlib_app:env(?APP, exchange_factors, #{}), case maps:get(Currency, Factors, undefined) of @@ -65,5 +58,4 @@ get_exchange_factor(Currency) -> %% call_rates(Function, Args, LimitContext) -> - {ok, WoodyContext} = lim_context:woody_context(LimitContext), - lim_client_woody:call(xrates, Function, Args, WoodyContext). + lim_client_woody:call(xrates, Function, Args, lim_context:woody_context(LimitContext)). diff --git a/apps/limiter/src/lim_turnover_metric.erl b/apps/limiter/src/lim_turnover_metric.erl new file mode 100644 index 00000000..6582bdaa --- /dev/null +++ b/apps/limiter/src/lim_turnover_metric.erl @@ -0,0 +1,61 @@ +-module(lim_turnover_metric). + +-export([compute/4]). + +-type amount() :: lim_body:amount(). +-type currency() :: lim_base_thrift:'CurrencySymbolicCode'(). +-type stage() :: hold | commit. +-type t() :: number | {amount, currency()}. + +-export_type([t/0]). + +%% + +-spec compute(t(), stage(), lim_config_machine:config(), lim_context:t()) -> + {ok, amount()} | {error, lim_rates:conversion_error()}. +compute(number, hold, Config, LimitContext) -> + #{amount := Amount} = get_body(Config, LimitContext), + {ok, sign(Amount)}; +compute(number, commit, Config, LimitContext) -> + case get_commit_body(Config, LimitContext) of + #{amount := Amount} when Amount /= 0 -> + {ok, sign(Amount)}; + #{amount := 0} -> + % Zero amount operation currently means "rollback" in the protocol. + {ok, 0} + end; +compute({amount, Currency}, hold, Config, LimitContext) -> + Body = get_body(Config, LimitContext), + denominate(Body, Currency, Config, LimitContext); +compute({amount, Currency}, commit, Config, LimitContext) -> + Body = get_commit_body(Config, LimitContext), + denominate(Body, Currency, Config, LimitContext). + +get_body(Config, LimitContext) -> + {ok, Body} = lim_body:get(full, Config, LimitContext), + Body. + +get_commit_body(Config, LimitContext) -> + case lim_body:get(partial, Config, LimitContext) of + {ok, Body} -> + Body; + {error, notfound} -> + get_body(Config, LimitContext) + end. + +%% + +denominate(#{amount := Amount, currency := Currency}, Currency, _Config, _LimitContext) -> + {ok, Amount}; +denominate(Body = #{}, DestinationCurrency, Config, LimitContext) -> + case lim_rates:convert(Body, DestinationCurrency, Config, LimitContext) of + {ok, #{amount := AmountConverted}} -> + {ok, AmountConverted}; + {error, _} = Error -> + Error + end. + +sign(Amount) when Amount > 0 -> + +1; +sign(Amount) when Amount < 0 -> + -1. diff --git a/apps/limiter/src/lim_turnover_processor.erl b/apps/limiter/src/lim_turnover_processor.erl index e8501276..203d8f11 100644 --- a/apps/limiter/src/lim_turnover_processor.erl +++ b/apps/limiter/src/lim_turnover_processor.erl @@ -25,16 +25,16 @@ -type get_limit_error() :: {range, notfound}. -type hold_error() :: - lim_body:get_body_error() + lim_rates:conversion_error() | lim_accounting:invalid_request_error(). -type commit_error() :: {forbidden_operation_amount, forbidden_operation_amount_error()} - | lim_body:get_body_error() + | lim_rates:conversion_error() | lim_accounting:invalid_request_error(). -type rollback_error() :: - lim_body:get_body_error() + lim_rates:conversion_error() | lim_accounting:invalid_request_error(). -export_type([get_limit_error/0]). @@ -42,14 +42,13 @@ -export_type([commit_error/0]). -export_type([rollback_error/0]). --import(lim_pipeline, [do/1, unwrap/1, unwrap/2]). +-import(lim_pipeline, [do/1, unwrap/1]). -spec get_limit(lim_id(), config(), lim_context()) -> {ok, limit()} | {error, get_limit_error()}. get_limit(LimitID, Config, LimitContext) -> do(fun() -> {LimitRangeID, TimeRange} = compute_limit_time_range_location(LimitID, Config, LimitContext), - #{max_available_amount := Amount} = - unwrap(range, lim_range_machine:get_range_balance(LimitRangeID, TimeRange, LimitContext)), + Amount = find_range_balance_amount(LimitRangeID, TimeRange, LimitContext), #limiter_Limit{ id = LimitRangeID, amount = Amount, @@ -58,12 +57,20 @@ get_limit(LimitID, Config, LimitContext) -> } end). +find_range_balance_amount(LimitRangeID, TimeRange, LimitContext) -> + case lim_range_machine:get_range_balance(LimitRangeID, TimeRange, LimitContext) of + {ok, #{max_available_amount := Amount}} -> + Amount; + {error, notfound} -> + 0 + end. + -spec hold(lim_change(), config(), lim_context()) -> ok | {error, hold_error()}. hold(LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) -> do(fun() -> TimeRangeAccount = ensure_limit_time_range(LimitID, Config, LimitContext), - Body = unwrap(lim_body:get_body(full, Config, LimitContext)), - Posting = construct_posting(TimeRangeAccount, Body, Config, LimitContext), + Metric = unwrap(compute_metric(hold, Config, LimitContext)), + Posting = lim_posting:new(TimeRangeAccount, Metric, currency(Config)), unwrap(lim_accounting:hold(construct_plan_id(LimitChange), {1, [Posting]}, LimitContext)) end). @@ -95,8 +102,8 @@ commit(LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) - rollback(LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) -> do(fun() -> TimeRangeAccount = ensure_limit_time_range(LimitID, Config, LimitContext), - Body = unwrap(lim_body:get_body(full, Config, LimitContext)), - Posting = construct_posting(TimeRangeAccount, Body, Config, LimitContext), + Metric = unwrap(compute_metric(hold, Config, LimitContext)), + Posting = lim_posting:new(TimeRangeAccount, Metric, currency(Config)), unwrap(lim_accounting:rollback(construct_plan_id(LimitChange), [{1, [Posting]}], LimitContext)) end). @@ -113,7 +120,7 @@ ensure_limit_time_range(LimitID, Config, LimitContext) -> id => LimitRangeID, type => lim_config_machine:time_range_type(Config), created_at => Timestamp, - currency => lim_config_machine:currency(Config) + currency => currency(Config) }), unwrap(lim_range_machine:ensure_exists(CreateParams, TimeRange, LimitContext)). @@ -127,22 +134,24 @@ construct_range_id(LimitID, Timestamp, Config, LimitContext) -> <>. construct_commit_plan(TimeRangeAccount, Config, LimitContext) -> - Body = unwrap(lim_body:get_body(full, Config, LimitContext)), - MaybePartialBody = lim_body:get_body(partial, Config, LimitContext), - construct_commit_postings(TimeRangeAccount, Body, MaybePartialBody, Config, LimitContext). - -construct_commit_postings(TimeRangeAccount, Full, MaybePartialBody, Config, LimitContext) -> - OriginalHoldPosting = construct_posting(TimeRangeAccount, Full, Config, LimitContext), - case determine_commit_intent(MaybePartialBody, Full) of - commit -> + MetricHold = unwrap(compute_metric(hold, Config, LimitContext)), + MetricCommit = unwrap(compute_metric(commit, Config, LimitContext)), + construct_commit_postings(TimeRangeAccount, MetricHold, MetricCommit, Config). + +construct_commit_postings(TimeRangeAccount, MetricHold, MetricCommit, Config) -> + OriginalHoldPosting = lim_posting:new(TimeRangeAccount, MetricHold, currency(Config)), + case MetricCommit of + MetricHold -> + % Commit-time metric is equal to hold-time metric [{commit, [{1, [OriginalHoldPosting]}]}]; - rollback -> + 0 -> + % Commit-time metric is 0, this is rollback [{rollback, [{1, [OriginalHoldPosting]}]}]; - {commit, Partial} -> + _MetricPartial -> % Partial body is less than full body - ok = unwrap(assert_partial_body(Partial, Full)), - ReverseHoldPosting = lim_p_transfer:reverse_posting(OriginalHoldPosting), - PartialHoldPosting = construct_posting(TimeRangeAccount, Partial, Config, LimitContext), + ok = unwrap(validate_metric(MetricCommit, MetricHold)), + ReverseHoldPosting = lim_posting:reverse(OriginalHoldPosting), + PartialHoldPosting = lim_posting:new(TimeRangeAccount, MetricCommit, currency(Config)), PartialBatch = [ReverseHoldPosting, PartialHoldPosting], [ {hold, {2, PartialBatch}}, @@ -153,71 +162,36 @@ construct_commit_postings(TimeRangeAccount, Full, MaybePartialBody, Config, Limi ] end. -determine_commit_intent({error, notfound}, _FullBody) -> - % No partial body specified - commit; -determine_commit_intent({ok, FullBody}, FullBody) -> - % Partial body is equal to full body - commit; -determine_commit_intent({ok, {amount, 0}}, _FullBody) -> - % Partial body is 0, this is rollback - rollback; -determine_commit_intent({ok, {cash, #{amount := 0}}}, _FullBody) -> - % Partial body is 0, this is rollback - rollback; -determine_commit_intent({ok, Partial}, _FullBody) -> - {commit, Partial}. - -construct_posting(TimeRangeAccount, Body, Config, LimitContext) -> - apply_op_behaviour(lim_p_transfer:construct_posting(TimeRangeAccount, Body), Config, LimitContext). - -apply_op_behaviour(Posting, #{op_behaviour := ComputationConfig}, LimitContext) -> - {ok, Operation} = lim_context:get_operation(payment_processing, LimitContext), - case maps:get(Operation, ComputationConfig, undefined) of - subtraction -> - lim_p_transfer:reverse_posting(Posting); - Type when Type =:= undefined orelse Type =:= additional -> - Posting - end; -apply_op_behaviour(Body, _Config, _LimitContext) -> - Body. - -assert_partial_body( - {cash, #{amount := Partial, currency := Currency}}, - {cash, #{amount := Full, currency := Currency}} -) -> - compare_amount(Partial, Full, Currency); -assert_partial_body( - {cash, #{amount := Partial, currency := PartialCurrency}}, - {cash, #{amount := Full, currency := FullCurrency}} -) -> - erlang:error({invalid_partial_cash, {Partial, PartialCurrency}, {Full, FullCurrency}}). - -compare_amount(Partial, Full, Currency) when Full > 0 -> - case Partial < Full of +compute_metric(Stage, Config, LimitContext) -> + {turnover, Metric} = lim_config_machine:type(Config), + lim_turnover_metric:compute(Metric, Stage, Config, LimitContext). + +currency(#{type := {turnover, {amount, Currency}}}) -> + Currency; +currency(#{type := {turnover, number}}) -> + lim_accounting:noncurrency(). + +validate_metric(MetricCommit, MetricHold) when MetricHold > 0 -> + case MetricCommit < MetricHold of true -> ok; false -> {error, - {forbidden_operation_amount, - genlib_map:compact(#{ - type => positive, - partial => Partial, - full => Full, - currency => Currency - })}} + {forbidden_operation_amount, #{ + type => positive, + partial => MetricCommit, + full => MetricHold + }}} end; -compare_amount(Partial, Full, Currency) when Full < 0 -> - case Partial > Full of +validate_metric(MetricCommit, MetricHold) when MetricHold < 0 -> + case MetricCommit > MetricHold of true -> ok; false -> {error, - {forbidden_operation_amount, - genlib_map:compact(#{ - type => negative, - partial => Partial, - full => Full, - currency => Currency - })}} + {forbidden_operation_amount, #{ + type => negative, + partial => MetricCommit, + full => MetricHold + }}} end. diff --git a/apps/limiter/test/lim_configurator_SUITE.erl b/apps/limiter/test/lim_configurator_SUITE.erl index cf42ab5c..5571c4d8 100644 --- a/apps/limiter/test/lim_configurator_SUITE.erl +++ b/apps/limiter/test/lim_configurator_SUITE.erl @@ -85,8 +85,7 @@ legacy_create_config(C) -> id = ID, name = <<"GlobalMonthTurnover">>, description = Description, - started_at = <<"2000-01-01T00:00:00Z">>, - body_type = ?body_type_cash() + started_at = <<"2000-01-01T00:00:00Z">> }, ?assertMatch( {ok, #limiter_config_LimitConfig{ @@ -105,10 +104,9 @@ create_config(C) -> id = ?config(limit_id, C), description = Description, started_at = <<"2000-01-01T00:00:00Z">>, - body_type = ?body_type_cash(<<"RUB">>), shard_size = 4, time_range_type = ?time_range_week(), - type = ?lim_type_turnover(), + type = ?lim_type_turnover(?turnover_metric_amount()), scope = ?scope([ ?scope_shop(), ?scope_party() @@ -130,7 +128,6 @@ create_config_single_scope(C) -> Params = #limiter_config_LimitConfigParams{ id = ?config(limit_id, C), started_at = <<"2000-01-01T00:00:00Z">>, - body_type = ?body_type_cash(), time_range_type = ?time_range_week(), shard_size = 1, type = ?lim_type_turnover(), @@ -157,8 +154,7 @@ prepare_environment(ID, LimitName, _C) -> id = ID, name = LimitName, description = <<"description">>, - started_at = <<"2000-01-01T00:00:00Z">>, - body_type = ?body_type_cash() + started_at = <<"2000-01-01T00:00:00Z">> }, {ok, LimitConfig} = lim_client:legacy_create_config(Params, Client), #{config => LimitConfig, client => Client}. diff --git a/apps/limiter/test/lim_ct_helper.hrl b/apps/limiter/test/lim_ct_helper.hrl index ecf44f0b..70f0e00d 100644 --- a/apps/limiter/test/lim_ct_helper.hrl +++ b/apps/limiter/test/lim_ct_helper.hrl @@ -17,13 +17,15 @@ -define(scope_party(), {party, #limiter_config_LimitScopeEmptyDetails{}}). -define(scope_shop(), {shop, #limiter_config_LimitScopeEmptyDetails{}}). --define(body_type_cash(), ?body_type_cash(?currency)). --define(body_type_cash(Currency), - {cash, #limiter_config_LimitBodyTypeCash{currency = Currency}} +-define(lim_type_turnover(), ?lim_type_turnover(?turnover_metric_number())). +-define(lim_type_turnover(Metric), + {turnover, #limiter_config_LimitTypeTurnover{metric = Metric}} ). --define(lim_type_turnover(), - {turnover, #limiter_config_LimitTypeTurnover{}} +-define(turnover_metric_number(), {number, #limiter_config_LimitTurnoverNumber{}}). +-define(turnover_metric_amount(), ?turnover_metric_amount(?currency)). +-define(turnover_metric_amount(Currency), + {amount, #limiter_config_LimitTurnoverAmount{currency = Currency}} ). -define(time_range_day(), @@ -48,8 +50,19 @@ {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}} ). +-define(op_invoice(), {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}). -define(op_invoice_payment(), {invoice_payment, #limiter_context_PaymentProcessingOperationInvoicePayment{}}). +-define(ctx_invoice(Cost), #limiter_context_LimitContext{ + payment_processing = #limiter_context_ContextPaymentProcessing{ + op = ?op_invoice(), + invoice = #limiter_context_Invoice{ + created_at = <<"2000-01-01T00:00:00Z">>, + cost = Cost + } + } +}). + -define(ctx_invoice_payment(Cost, CaptureCost), ?ctx_invoice_payment(undefined, undefined, Cost, CaptureCost)). -define(ctx_invoice_payment(OwnerID, ShopID, Cost, CaptureCost), #limiter_context_LimitContext{ diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index 6d4b20e8..abb2dcc9 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -19,6 +19,7 @@ -export([partial_commit_with_exchange/1]). -export([commit_with_exchange/1]). -export([get_rate/1]). +-export([get_limit_ok/1]). -export([get_limit_notfound/1]). -export([hold_ok/1]). -export([commit_ok/1]). @@ -34,6 +35,11 @@ -export([partial_commit_processes_idempotently/1]). -export([rollback_processes_idempotently/1]). +-export([commit_number_ok/1]). +-export([rollback_number_ok/1]). +-export([commit_refund_keep_number_unchanged/1]). +-export([partial_commit_number_counts_as_single_op/1]). + -type group_name() :: atom(). -type test_case_name() :: atom(). @@ -45,6 +51,7 @@ all() -> [ {group, default}, + {group, cashless}, {group, idempotency} ]. @@ -56,6 +63,7 @@ groups() -> partial_commit_with_exchange, commit_with_exchange, get_rate, + get_limit_ok, get_limit_notfound, hold_ok, commit_ok, @@ -66,6 +74,12 @@ groups() -> partial_commit_inexistent_hold_fails, commit_multirange_limit_ok ]}, + {cashless, [parallel], [ + commit_number_ok, + rollback_number_ok, + commit_refund_keep_number_unchanged, + partial_commit_number_counts_as_single_op + ]}, {idempotency, [parallel], [ commit_processes_idempotently, full_commit_processes_idempotently, @@ -129,8 +143,7 @@ end_per_testcase(_Name, C) -> commit_with_default_exchange(C) -> Rational = #base_Rational{p = 1000000, q = 100}, _ = mock_exchange(Rational, C), - _ = configure_limit(?time_range_month(), ?global(), C), - ID = ?config(id, C), + ID = configure_limit(?time_range_month(), ?global(), C), Context = #limiter_context_LimitContext{ payment_processing = #limiter_context_ContextPaymentProcessing{ op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, @@ -150,8 +163,7 @@ commit_with_default_exchange(C) -> partial_commit_with_exchange(C) -> Rational = #base_Rational{p = 800000, q = 100}, _ = mock_exchange(Rational, C), - _ = configure_limit(?time_range_month(), ?global(), C), - ID = ?config(id, C), + ID = configure_limit(?time_range_month(), ?global(), C), Context = #limiter_context_LimitContext{ payment_processing = #limiter_context_ContextPaymentProcessing{ op = {invoice_payment, #limiter_context_PaymentProcessingOperationInvoicePayment{}}, @@ -177,8 +189,7 @@ partial_commit_with_exchange(C) -> commit_with_exchange(C) -> Rational = #base_Rational{p = 1000000, q = 100}, _ = mock_exchange(Rational, C), - _ = configure_limit(?time_range_month(), ?global(), C), - ID = ?config(id, C), + ID = configure_limit(?time_range_month(), ?global(), C), Context = #limiter_context_LimitContext{ payment_processing = #limiter_context_ContextPaymentProcessing{ op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, @@ -212,40 +223,33 @@ get_rate(C) -> WoodyContext ). +-spec get_limit_ok(config()) -> _. +get_limit_ok(C) -> + ID = configure_limit(?time_range_month(), ?global(), C), + Context = ?ctx_invoice(_Cost = undefined), + ?assertMatch( + {ok, #limiter_Limit{amount = 0}}, + lim_client:get(ID, Context, ?config(client, C)) + ). + -spec get_limit_notfound(config()) -> _. get_limit_notfound(C) -> - _ = configure_limit(?time_range_month(), ?global(), C), - Context = #limiter_context_LimitContext{ - payment_processing = #limiter_context_ContextPaymentProcessing{ - op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, - invoice = #limiter_context_Invoice{created_at = <<"2000-01-01T00:00:00Z">>} - } - }, - {exception, #limiter_LimitNotFound{}} = lim_client:get(?config(id, C), Context, ?config(client, C)). + Context = ?ctx_invoice(_Cost = undefined), + ?assertEqual( + {exception, #limiter_LimitNotFound{}}, + lim_client:get(<<"NOSUCHLIMITID">>, Context, ?config(client, C)) + ). -spec hold_ok(config()) -> _. hold_ok(C) -> - _ = configure_limit(?time_range_month(), ?global(), C), - ID = ?config(id, C), - Context = #limiter_context_LimitContext{ - payment_processing = #limiter_context_ContextPaymentProcessing{ - op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, - invoice = #limiter_context_Invoice{ - created_at = <<"2000-01-01T00:00:00Z">>, - cost = #limiter_base_Cash{ - amount = 10, - currency = #limiter_base_CurrencyRef{symbolic_code = <<"RUB">>} - } - } - } - }, + ID = configure_limit(?time_range_month(), ?global(), C), + Context = ?ctx_invoice(?cash(10)), {ok, {vector, #limiter_VectorClock{}}} = lim_client:hold(?LIMIT_CHANGE(ID), Context, ?config(client, C)), {ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)). -spec commit_ok(config()) -> _. commit_ok(C) -> - _ = configure_limit(?time_range_month(), ?global(), C), - ID = ?config(id, C), + ID = configure_limit(?time_range_month(), ?global(), C), Context = #limiter_context_LimitContext{ payment_processing = #limiter_context_ContextPaymentProcessing{ op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, @@ -263,8 +267,7 @@ commit_ok(C) -> -spec rollback_ok(config()) -> _. rollback_ok(C) -> - _ = configure_limit(?time_range_week(), ?global(), C), - ID = ?config(id, C), + ID = configure_limit(?time_range_week(), ?global(), C), Context0 = ?ctx_invoice_payment(?cash(10), ?cash(10)), Context1 = ?ctx_invoice_payment(?cash(10), ?cash(0)), Change = ?LIMIT_CHANGE(ID), @@ -273,11 +276,10 @@ rollback_ok(C) -> -spec refund_ok(config()) -> _. refund_ok(C) -> - ID = ?config(id, C), Client = ?config(client, C), OwnerID = <<"WWWcool Ltd">>, ShopID = <<"shop">>, - _ = configure_limit(?time_range_day(), ?scope([?scope_party(), ?scope_shop()]), C), + ID = configure_limit(?time_range_day(), ?scope([?scope_party(), ?scope_shop()]), C), Context0 = ?ctx_invoice_payment(OwnerID, ShopID, ?cash(15), ?cash(15)), RefundContext1 = ?ctx_invoice_payment_refund(OwnerID, ShopID, ?cash(10), ?cash(10), ?cash(10)), {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Payment">>), Context0, Client), @@ -287,13 +289,12 @@ refund_ok(C) -> -spec get_config_ok(config()) -> _. get_config_ok(C) -> - _ = configure_limit(?time_range_week(), ?global(), C), - {ok, #limiter_config_LimitConfig{}} = lim_client:get_config(?config(id, C), ?config(client, C)). + ID = configure_limit(?time_range_week(), ?global(), C), + {ok, #limiter_config_LimitConfig{}} = lim_client:get_config(ID, ?config(client, C)). -spec commit_inexistent_hold_fails(config()) -> _. commit_inexistent_hold_fails(C) -> - ID = ?config(id, C), - _ = configure_limit(?time_range_week(), ?global(), C), + ID = configure_limit(?time_range_week(), ?global(), C), Context = ?ctx_invoice_payment(?cash(42), undefined), % NOTE % We do not expect `LimitChangeNotFound` here because we no longer reconcile with accounter @@ -303,8 +304,7 @@ commit_inexistent_hold_fails(C) -> -spec partial_commit_inexistent_hold_fails(config()) -> _. partial_commit_inexistent_hold_fails(C) -> - ID = ?config(id, C), - _ = configure_limit(?time_range_week(), ?global(), C), + ID = configure_limit(?time_range_week(), ?global(), C), Context = ?ctx_invoice_payment(?cash(42), ?cash(21)), % NOTE % We do not expect `LimitChangeNotFound` here because we no longer reconcile with accounter @@ -318,12 +318,11 @@ commit_multirange_limit_ok(C) -> Client = ?config(client, C), Params = #limiter_config_LimitConfigParams{ id = ID, - body_type = {cash, #limiter_config_LimitBodyTypeCash{currency = <<"RUB">>}}, started_at = <<"2000-01-01T00:00:00Z">>, shard_size = 12, - time_range_type = {calendar, {month, #time_range_TimeRangeTypeCalendarMonth{}}}, - context_type = {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}, - type = {turnover, #limiter_config_LimitTypeTurnover{}}, + time_range_type = ?time_range_month(), + context_type = ?ctx_type_payproc(), + type = ?lim_type_turnover(?turnover_metric_amount(<<"RUB">>)), scope = ?scope([]), op_behaviour = #limiter_config_OperationLimitBehaviour{} }, @@ -354,9 +353,8 @@ commit_multirange_limit_ok(C) -> -spec commit_processes_idempotently(config()) -> _. commit_processes_idempotently(C) -> - ID = ?config(id, C), Client = ?config(client, C), - _ = configure_limit(?time_range_week(), ?global(), C), + ID = configure_limit(?time_range_week(), ?global(), C), Context = ?ctx_invoice_payment(?cash(42), undefined), Change = ?LIMIT_CHANGE(ID), {ok, _} = lim_client:hold(Change, Context, Client), @@ -368,9 +366,8 @@ commit_processes_idempotently(C) -> -spec full_commit_processes_idempotently(config()) -> _. full_commit_processes_idempotently(C) -> - ID = ?config(id, C), Client = ?config(client, C), - _ = configure_limit(?time_range_week(), ?global(), C), + ID = configure_limit(?time_range_week(), ?global(), C), Cost = ?cash(42), Context = ?ctx_invoice_payment(Cost, Cost), Change = ?LIMIT_CHANGE(ID), @@ -383,9 +380,8 @@ full_commit_processes_idempotently(C) -> -spec partial_commit_processes_idempotently(config()) -> _. partial_commit_processes_idempotently(C) -> - ID = ?config(id, C), Client = ?config(client, C), - _ = configure_limit(?time_range_week(), ?global(), C), + ID = configure_limit(?time_range_week(), ?global(), C), Context = ?ctx_invoice_payment(?cash(42), ?cash(40)), Change = ?LIMIT_CHANGE(ID), {ok, _} = lim_client:hold(Change, Context, Client), @@ -397,9 +393,8 @@ partial_commit_processes_idempotently(C) -> -spec rollback_processes_idempotently(config()) -> _. rollback_processes_idempotently(C) -> - ID = ?config(id, C), Client = ?config(client, C), - _ = configure_limit(?time_range_week(), ?global(), C), + ID = configure_limit(?time_range_week(), ?global(), C), Context = ?ctx_invoice_payment(?cash(42), ?cash(0)), Change = ?LIMIT_CHANGE(ID), {ok, _} = lim_client:hold(Change, Context, Client), @@ -411,30 +406,98 @@ rollback_processes_idempotently(C) -> %% +-spec commit_number_ok(config()) -> _. +commit_number_ok(C) -> + Client = ?config(client, C), + ID = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C), + Context = ?ctx_invoice_payment(?cash(10), ?cash(10)), + {ok, LimitState0} = lim_client:get(ID, Context, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID), Context, Client), + {ok, LimitState1} = lim_client:get(ID, Context, Client), + ?assertEqual( + LimitState1#limiter_Limit.amount, + LimitState0#limiter_Limit.amount + 1 + ). + +-spec rollback_number_ok(config()) -> _. +rollback_number_ok(C) -> + Client = ?config(client, C), + ID = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C), + Context = ?ctx_invoice_payment(?cash(10), ?cash(10)), + ContextRollback = ?ctx_invoice_payment(?cash(10), ?cash(0)), + {ok, LimitState0} = lim_client:get(ID, Context, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID), Context, ContextRollback, Client), + {ok, LimitState1} = lim_client:get(ID, Context, Client), + ?assertEqual( + LimitState1#limiter_Limit.amount, + LimitState0#limiter_Limit.amount + ). + +-spec commit_refund_keep_number_unchanged(config()) -> _. +commit_refund_keep_number_unchanged(C) -> + Client = ?config(client, C), + ID = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C), + Cost = ?cash(10), + CaptureCost = ?cash(8), + RefundCost = ?cash(5), + PaymentContext = ?ctx_invoice_payment(<<"OWNER">>, <<"SHOP">>, Cost, CaptureCost), + RefundContext = ?ctx_invoice_payment_refund(<<"OWNER">>, <<"SHOP">>, Cost, CaptureCost, RefundCost), + {ok, LimitState0} = lim_client:get(ID, PaymentContext, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 1), PaymentContext, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 2), RefundContext, Client), + {ok, LimitState1} = lim_client:get(ID, PaymentContext, Client), + ?assertEqual( + % Expected to be the same because refund decreases counter given limit config + LimitState1#limiter_Limit.amount, + LimitState0#limiter_Limit.amount + ). + +-spec partial_commit_number_counts_as_single_op(config()) -> _. +partial_commit_number_counts_as_single_op(C) -> + Client = ?config(client, C), + ID = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C), + Context = ?ctx_invoice_payment(?cash(10), ?cash(10)), + ContextPartial = ?ctx_invoice_payment(?cash(10), ?cash(5)), + {ok, LimitState0} = lim_client:get(ID, Context, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID), Context, ContextPartial, Client), + {ok, LimitState1} = lim_client:get(ID, Context, Client), + ?assertEqual( + LimitState1#limiter_Limit.amount, + LimitState0#limiter_Limit.amount + 1 + ). + +%% + gen_change_id(LimitID, ChangeID) -> genlib:format("~s/~p", [LimitID, ChangeID]). hold_and_commit(Change, Context, Client) -> + hold_and_commit(Change, Context, Context, Client). + +hold_and_commit(Change, Context, ContextCommit, Client) -> {ok, {vector, _}} = lim_client:hold(Change, Context, Client), - {ok, {vector, _}} = lim_client:commit(Change, Context, Client). + {ok, {vector, _}} = lim_client:commit(Change, ContextCommit, Client). mock_exchange(Rational, C) -> lim_mock:mock_services([{xrates, fun('GetConvertedAmount', _) -> {ok, Rational} end}], C). configure_limit(TimeRange, Scope, C) -> + configure_limit(TimeRange, Scope, ?turnover_metric_amount(<<"RUB">>), C). + +configure_limit(TimeRange, Scope, Metric, C) -> ID = ?config(id, C), Params = #limiter_config_LimitConfigParams{ id = ID, started_at = <<"2000-01-01T00:00:00Z">>, - body_type = ?body_type_cash(<<"RUB">>), time_range_type = TimeRange, shard_size = 1, - type = ?lim_type_turnover(), + type = ?lim_type_turnover(Metric), scope = Scope, context_type = ?ctx_type_payproc(), op_behaviour = ?op_behaviour(?op_subtraction()) }, - {ok, _LimitConfig} = lim_client:create_config(Params, ?config(client, C)). + {ok, _LimitConfig} = lim_client:create_config(Params, ?config(client, C)), + ID. gen_unique_id(Prefix) -> genlib:format("~s/~B", [Prefix, lim_time:now()]). diff --git a/elvis.config b/elvis.config index 988dab44..d3de979b 100644 --- a/elvis.config +++ b/elvis.config @@ -25,7 +25,8 @@ % readability. {elvis_style, used_ignored_variable, disable}, % Tests are usually more comprehensible when a bit more verbose. - {elvis_style, dont_repeat_yourself, #{min_complexity => 20}} + {elvis_style, dont_repeat_yourself, #{min_complexity => 20}}, + {elvis_style, god_modules, #{limit => 50}} ] }, #{ diff --git a/rebar.lock b/rebar.lock index 12c5305d..55865372 100644 --- a/rebar.lock +++ b/rebar.lock @@ -25,7 +25,7 @@ {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},1}, {<<"limiter_proto">>, {git,"https://github.com/valitydev/limiter-proto.git", - {ref,"d390910cd246f2356f10c2db410ecf93e55eff4d"}}, + {ref,"6723e862157a7f78194a64271899c2ef1581e177"}}, 0}, {<<"machinery">>, {git,"https://github.com/valitydev/machinery-erlang.git", From 2dff7b518d283834cf4d9f38a0cc45bf8c0a09a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D0=B5=D0=BC?= Date: Mon, 20 Jun 2022 15:53:54 +0300 Subject: [PATCH 21/54] TD-305: Add new payproc context and payment tool scope implementetion (#9) * updated limiter proto * added payproc context * fixed linter * bumped proto * added base tests * added payment tool tests * added minor test * fixed dialyzer * fixed --- apps/limiter/src/lim_body.erl | 2 +- apps/limiter/src/lim_config_codec.erl | 14 +- apps/limiter/src/lim_config_machine.erl | 20 +- apps/limiter/src/lim_configurator.erl | 8 +- apps/limiter/src/lim_context.erl | 170 ++++----------- apps/limiter/src/lim_handler.erl | 6 +- apps/limiter/src/lim_limiter_context.erl | 129 +++++++++++ apps/limiter/src/lim_payproc_context.erl | 213 +++++++++++++++++++ apps/limiter/src/lim_range_codec.erl | 16 +- apps/limiter/src/lim_turnover_metric.erl | 2 +- apps/limiter/test/lim_configurator_SUITE.erl | 4 +- apps/limiter/test/lim_ct_helper.hrl | 138 +++++++++++- apps/limiter/test/lim_turnover_SUITE.erl | 118 ++++++++-- rebar.config | 2 +- rebar.lock | 2 +- 15 files changed, 654 insertions(+), 190 deletions(-) create mode 100644 apps/limiter/src/lim_limiter_context.erl create mode 100644 apps/limiter/src/lim_payproc_context.erl diff --git a/apps/limiter/src/lim_body.erl b/apps/limiter/src/lim_body.erl index 4c500dc9..c2be6f4a 100644 --- a/apps/limiter/src/lim_body.erl +++ b/apps/limiter/src/lim_body.erl @@ -8,7 +8,7 @@ currency := currency() }. --type currency() :: lim_base_thrift:'CurrencySymbolicCode'(). +-type currency() :: lim_domain_thrift:'CurrencySymbolicCode'(). -type config() :: lim_config_machine:config(). -type body_type() :: full | partial. diff --git a/apps/limiter/src/lim_config_codec.erl b/apps/limiter/src/lim_config_codec.erl index b53e60de..13b25fa7 100644 --- a/apps/limiter/src/lim_config_codec.erl +++ b/apps/limiter/src/lim_config_codec.erl @@ -85,16 +85,16 @@ marshal_behaviour(addition) -> marshal_time_range_type({calendar, CalendarType}) -> {calendar, marshal_calendar_time_range_type(CalendarType)}; marshal_time_range_type({interval, Amount}) -> - {interval, #time_range_TimeRangeTypeInterval{amount = Amount}}. + {interval, #limiter_time_range_TimeRangeTypeInterval{amount = Amount}}. marshal_calendar_time_range_type(day) -> - {day, #time_range_TimeRangeTypeCalendarDay{}}; + {day, #limiter_time_range_TimeRangeTypeCalendarDay{}}; marshal_calendar_time_range_type(week) -> - {week, #time_range_TimeRangeTypeCalendarWeek{}}; + {week, #limiter_time_range_TimeRangeTypeCalendarWeek{}}; marshal_calendar_time_range_type(month) -> - {month, #time_range_TimeRangeTypeCalendarMonth{}}; + {month, #limiter_time_range_TimeRangeTypeCalendarMonth{}}; marshal_calendar_time_range_type(year) -> - {year, #time_range_TimeRangeTypeCalendarYear{}}. + {year, #limiter_time_range_TimeRangeTypeCalendarYear{}}. marshal_context_type(payment_processing) -> {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}. @@ -235,7 +235,7 @@ unmarshal_body_type_deprecated({cash, #limiter_config_LimitBodyTypeCash{currency unmarshal_time_range_type({calendar, CalendarType}) -> {calendar, unmarshal_calendar_time_range_type(CalendarType)}; -unmarshal_time_range_type({interval, #time_range_TimeRangeTypeInterval{amount = Amount}}) -> +unmarshal_time_range_type({interval, #limiter_time_range_TimeRangeTypeInterval{amount = Amount}}) -> {interval, Amount}. unmarshal_calendar_time_range_type({day, _}) -> @@ -308,7 +308,7 @@ unmarshal_created_w_deprecated_body_type_test_() -> created_at = lim_time:to_rfc3339(Now), started_at = <<"2000-01-01T00:00:00Z">>, shard_size = 42, - time_range_type = {calendar, {day, #time_range_TimeRangeTypeCalendarDay{}}}, + time_range_type = {calendar, {day, #limiter_time_range_TimeRangeTypeCalendarDay{}}}, context_type = {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}, body_type_deprecated = {cash, #limiter_config_LimitBodyTypeCash{currency = <<"☭☭☭"/utf8>>}} }, diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index 48a8ccfb..81b30867 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -38,7 +38,7 @@ -type limit_type() :: {turnover, lim_turnover_metric:t()}. -type limit_scope() :: ordsets:ordset(limit_scope_type()). --type limit_scope_type() :: party | shop | wallet | identity. +-type limit_scope_type() :: party | shop | wallet | identity | payment_tool. -type shard_size() :: pos_integer(). -type shard_id() :: binary(). -type prefix() :: binary(). @@ -560,11 +560,27 @@ append_context_bits(shop, Bits) -> % Also we need to preserve order between party / shop to ensure backwards compatibility. {order, 1, {from, payment_processing, owner_id, invoice}}, {order, 2, {from, payment_processing, shop_id, invoice}} - ]). + ]); +append_context_bits(payment_tool, Bits) -> + ordsets:add_element( + {from, payment_processing, payer, invoice_payment}, + Bits + ). -spec extract_context_bit(context_bit(), lim_context()) -> {ok, binary()}. extract_context_bit({order, _, Bit}, LimitContext) -> extract_context_bit(Bit, LimitContext); +extract_context_bit({from, payment_processing, payer, Op}, LimitContext) -> + {ok, {_, PayerData}} = lim_context:get_from_context(payment_processing, payer, Op, LimitContext), + #{payment_tool := {PaymentToolType, PaymentToolData}} = PayerData, + case PaymentToolType of + bank_card -> + Token = maps:get(token, PaymentToolData), + ExpData = maps:get(exp_date, PaymentToolData, <<>>), + {ok, <>}; + _ -> + {error, {unsupported_payment_tool_type, PaymentToolType}} + end; extract_context_bit({from, ContextType, ValueName, Op}, LimitContext) -> lim_context:get_from_context(ContextType, ValueName, Op, LimitContext). diff --git a/apps/limiter/src/lim_configurator.erl b/apps/limiter/src/lim_configurator.erl index 93c65027..b5b09cda 100644 --- a/apps/limiter/src/lim_configurator.erl +++ b/apps/limiter/src/lim_configurator.erl @@ -23,7 +23,7 @@ handle_function(Fn, Args, WoodyCtx, Opts) -> -spec handle_function_(woody:func(), woody:args(), lim_context:t(), woody:options()) -> {ok, woody:result()}. handle_function_( 'CreateLegacy', - {#limiter_cfg_LimitCreateParams{ + {#limiter_configurator_LimitCreateParams{ id = ID, name = Name, description = Description, @@ -51,7 +51,7 @@ handle_function_( {error, {name, notfound}} -> woody_error:raise( business, - #limiter_cfg_LimitConfigNameNotFound{} + #limiter_configurator_LimitConfigNameNotFound{} ) end; handle_function_('Create', {Params}, LimitContext, _Opts) -> @@ -70,7 +70,7 @@ handle_function_('Get', {LimitID}, LimitContext, _Opts) -> {ok, LimitConfig} -> {ok, lim_config_codec:marshal_config(LimitConfig)}; {error, notfound} -> - woody_error:raise(business, #limiter_cfg_LimitConfigNotFound{}) + woody_error:raise(business, #limiter_configurator_LimitConfigNotFound{}) end. map_type({turnover, _}) -> @@ -78,7 +78,7 @@ map_type({turnover, _}) -> map_type(_) -> woody_error:raise( business, - #limiter_base_InvalidRequest{errors = [<<"Config type not found.">>]} + #'InvalidRequest'{errors = [<<"Config type not found.">>]} ). mk_limit_config(<<"ShopDayTurnover">>) -> diff --git a/apps/limiter/src/lim_context.erl b/apps/limiter/src/lim_context.erl index 90e2bafb..f2bca88c 100644 --- a/apps/limiter/src/lim_context.erl +++ b/apps/limiter/src/lim_context.erl @@ -18,6 +18,8 @@ -type thrift_context() :: lim_limiter_thrift:'LimitContext'(). -type clock() :: lim_limiter_thrift:'Clock'(). -type id() :: binary(). +-type token() :: binary(). +-type exp_date() :: binary(). -type cash() :: lim_body:cash(). -type t() :: #{ @@ -52,8 +54,8 @@ shop_id => id(), cost => cash(), created_at => timestamp(), - effective_adjustment => payment_processing_adjustment(), - effective_payment => payment_processing_payment() + adjustment => payment_processing_adjustment(), + payment => payment_processing_payment() }. -type payment_processing_adjustment() :: #{ @@ -68,10 +70,26 @@ capture_cost => cash(), created_at => timestamp(), flow => instant | hold, - payer => payment_resource | customer | recurrent, - effective_adjustment => payment_processing_payment_adjustment(), - effective_refund => payment_processing_payment_refund(), - effective_chargeback => payment_processing_payment_chargeback() + payer => payment_processing_payer(), + adjustment => payment_processing_payment_adjustment(), + refund => payment_processing_payment_refund(), + chargeback => payment_processing_payment_chargeback() +}. + +-type payment_processing_payer() :: + {payment_resource, payment_processing_payer_data()} + | {customer, payment_processing_payer_data()} + | {recurrent, payment_processing_payer_data()}. + +-type payment_processing_payer_data() :: #{ + payment_tool => payment_processing_payment_tool() +}. + +-type payment_processing_payment_tool() :: {bank_card, payment_processing_bank_card()}. + +-type payment_processing_bank_card() :: #{ + token => token(), + exp_date => exp_date() }. -type payment_processing_payment_adjustment() :: #{ @@ -93,6 +111,7 @@ }. -export_type([t/0]). +-export_type([context/0]). -export_type([context_type/0]). -export_type([context_operation/0]). @@ -156,23 +175,23 @@ get_from_context(_, _ValueName, _Op, _LimContext) -> get_payment_processing_operation_context(invoice, #{invoice := Invoice}) -> {ok, Invoice}; -get_payment_processing_operation_context(invoice_adjustment, #{invoice := #{effective_adjustment := Adjustment}}) -> +get_payment_processing_operation_context(invoice_adjustment, #{invoice := #{adjustment := Adjustment}}) -> {ok, Adjustment}; -get_payment_processing_operation_context(invoice_payment, #{invoice := #{effective_payment := Payment}}) -> +get_payment_processing_operation_context(invoice_payment, #{invoice := #{payment := Payment}}) -> {ok, Payment}; get_payment_processing_operation_context( invoice_payment_adjustment, - #{invoice := #{effective_payment := #{effective_adjustment := Adjustment}}} + #{invoice := #{payment := #{adjustment := Adjustment}}} ) -> {ok, Adjustment}; get_payment_processing_operation_context( invoice_payment_refund, - #{invoice := #{effective_payment := #{effective_refund := Refund}}} + #{invoice := #{payment := #{refund := Refund}}} ) -> {ok, Refund}; get_payment_processing_operation_context( invoice_payment_chargeback, - #{invoice := #{effective_payment := #{effective_chargeback := Chargeback}}} + #{invoice := #{payment := #{chargeback := Chargeback}}} ) -> {ok, Chargeback}; get_payment_processing_operation_context(_, _) -> @@ -180,126 +199,13 @@ get_payment_processing_operation_context(_, _) -> %% -unmarshal_context(#limiter_context_LimitContext{payment_processing = PaymentProcessing}) -> - #{payment_processing => unmarshal_payment_processing_context(PaymentProcessing)}; +unmarshal_context(#limiter_context_LimitContext{limiter_payment_processing = PaymentProcessing}) when + PaymentProcessing =/= undefined +-> + #{payment_processing => lim_limiter_context:unmarshal(PaymentProcessing)}; +unmarshal_context(#limiter_context_LimitContext{payment_processing = PaymentProcessing}) when + PaymentProcessing =/= undefined +-> + #{payment_processing => lim_payproc_context:unmarshal(PaymentProcessing)}; unmarshal_context(_) -> #{}. - -unmarshal_payment_processing_context(#limiter_context_ContextPaymentProcessing{ - op = {Operation, _}, - invoice = Invoice -}) -> - genlib_map:compact(#{ - op => Operation, - invoice => maybe_unmarshal(Invoice, fun unmarshal_payment_processing_invoice/1) - }). - -unmarshal_payment_processing_invoice(#limiter_context_Invoice{ - id = ID, - owner_id = OwnerID, - shop_id = ShopID, - cost = Cost, - created_at = CreatedAt, - effective_payment = EffectivePayment, - effective_adjustment = EffectiveAdjustment -}) -> - genlib_map:compact(#{ - id => maybe_unmarshal(ID, fun unmarshal_string/1), - owner_id => maybe_unmarshal(OwnerID, fun unmarshal_string/1), - shop_id => maybe_unmarshal(ShopID, fun unmarshal_string/1), - cost => maybe_unmarshal(Cost, fun unmarshal_cash/1), - created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1), - effective_adjustment => maybe_unmarshal( - EffectiveAdjustment, - fun unmarshal_payment_processing_invoice_adjustment/1 - ), - effective_payment => maybe_unmarshal(EffectivePayment, fun unmarshal_payment_processing_invoice_payment/1) - }). - -unmarshal_payment_processing_invoice_adjustment(#limiter_context_InvoiceAdjustment{id = ID}) -> - genlib_map:compact(#{ - id => maybe_unmarshal(ID, fun unmarshal_string/1) - }). - -unmarshal_payment_processing_invoice_payment(#limiter_context_InvoicePayment{ - id = ID, - owner_id = OwnerID, - shop_id = ShopID, - cost = Cost, - capture_cost = CaptureCost, - created_at = CreatedAt, - flow = Flow, - payer = Payer, - effective_adjustment = EffectiveAdjustment, - effective_refund = EffectiveRefund, - effective_chargeback = EffectiveChargeback -}) -> - genlib_map:compact(#{ - id => maybe_unmarshal(ID, fun unmarshal_string/1), - owner_id => maybe_unmarshal(OwnerID, fun unmarshal_string/1), - shop_id => maybe_unmarshal(ShopID, fun unmarshal_string/1), - cost => maybe_unmarshal(Cost, fun unmarshal_cash/1), - capture_cost => maybe_unmarshal(CaptureCost, fun unmarshal_cash/1), - created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1), - flow => maybe_unmarshal(Flow, fun unmarshal_payment_processing_invoice_payment_flow/1), - payer => maybe_unmarshal(Payer, fun unmarshal_payment_processing_invoice_payment_payer/1), - effective_adjustment => maybe_unmarshal( - EffectiveAdjustment, - fun unmarshal_payment_processing_invoice_payment_adjustment/1 - ), - effective_refund => maybe_unmarshal(EffectiveRefund, fun unmarshal_payment_processing_invoice_payment_refund/1), - effective_chargeback => maybe_unmarshal( - EffectiveChargeback, - fun unmarshal_payment_processing_invoice_payment_chargeback/1 - ) - }). - -unmarshal_payment_processing_invoice_payment_flow({Flow, _}) -> - Flow. - -unmarshal_payment_processing_invoice_payment_payer({Payer, _}) -> - Payer. - -unmarshal_payment_processing_invoice_payment_adjustment(#limiter_context_InvoicePaymentAdjustment{ - id = ID, - created_at = CreatedAt -}) -> - genlib_map:compact(#{ - id => maybe_unmarshal(ID, fun unmarshal_string/1), - created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1) - }). - -unmarshal_payment_processing_invoice_payment_refund(#limiter_context_InvoicePaymentRefund{ - id = ID, - cost = Cost, - created_at = CreatedAt -}) -> - genlib_map:compact(#{ - id => maybe_unmarshal(ID, fun unmarshal_string/1), - cost => maybe_unmarshal(Cost, fun unmarshal_cash/1), - created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1) - }). - -unmarshal_payment_processing_invoice_payment_chargeback(#limiter_context_InvoicePaymentChargeback{ - id = ID, - levy = Levy, - body = Body, - created_at = CreatedAt -}) -> - genlib_map:compact(#{ - id => maybe_unmarshal(ID, fun unmarshal_string/1), - levy => maybe_unmarshal(Levy, fun unmarshal_cash/1), - body => maybe_unmarshal(Body, fun unmarshal_cash/1), - created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1) - }). - -unmarshal_cash(#limiter_base_Cash{amount = Amount, currency = #limiter_base_CurrencyRef{symbolic_code = Currency}}) -> - #{amount => Amount, currency => Currency}. - -unmarshal_string(Value) -> - Value. - -maybe_unmarshal(undefined, _) -> - undefined; -maybe_unmarshal(Value, UnmarshalFun) -> - UnmarshalFun(Value). diff --git a/apps/limiter/src/lim_handler.erl b/apps/limiter/src/lim_handler.erl index 8a6b4f19..6e0d7b96 100644 --- a/apps/limiter/src/lim_handler.erl +++ b/apps/limiter/src/lim_handler.erl @@ -84,7 +84,7 @@ handle_get_error(Error) -> -spec handle_hold_error(_) -> no_return(). handle_hold_error({_, {invalid_request, Errors}}) -> - woody_error:raise(business, #limiter_base_InvalidRequest{errors = Errors}); + woody_error:raise(business, #'InvalidRequest'{errors = Errors}); handle_hold_error(Error) -> handle_default_error(Error). @@ -92,13 +92,13 @@ handle_hold_error(Error) -> handle_commit_error({_, {forbidden_operation_amount, Error}}) -> handle_forbidden_operation_amount_error(Error); handle_commit_error({_, {invalid_request, Errors}}) -> - woody_error:raise(business, #limiter_base_InvalidRequest{errors = Errors}); + woody_error:raise(business, #'InvalidRequest'{errors = Errors}); handle_commit_error(Error) -> handle_default_error(Error). -spec handle_rollback_error(_) -> no_return(). handle_rollback_error({_, {invalid_request, Errors}}) -> - woody_error:raise(business, #limiter_base_InvalidRequest{errors = Errors}); + woody_error:raise(business, #'InvalidRequest'{errors = Errors}); handle_rollback_error(Error) -> handle_default_error(Error). diff --git a/apps/limiter/src/lim_limiter_context.erl b/apps/limiter/src/lim_limiter_context.erl new file mode 100644 index 00000000..9ffbcb8c --- /dev/null +++ b/apps/limiter/src/lim_limiter_context.erl @@ -0,0 +1,129 @@ +-module(lim_limiter_context). + +-include_lib("limiter_proto/include/lim_limiter_context_thrift.hrl"). + +-export([unmarshal/1]). + +-type thrift_context() :: lim_limiter_context_thrift:'ContextPaymentProcessing'(). + +%% + +-spec unmarshal(thrift_context()) -> lim_context:context(). +unmarshal(#limiter_context_ContextPaymentProcessing{ + op = {Operation, _}, + invoice = Invoice +}) -> + genlib_map:compact(#{ + op => Operation, + invoice => maybe_unmarshal(Invoice, fun unmarshal_payment_processing_invoice/1) + }). + +unmarshal_payment_processing_invoice(#limiter_context_Invoice{ + id = ID, + owner_id = OwnerID, + shop_id = ShopID, + cost = Cost, + created_at = CreatedAt, + effective_payment = Payment, + effective_adjustment = Adjustment +}) -> + genlib_map:compact(#{ + id => maybe_unmarshal(ID, fun unmarshal_string/1), + owner_id => maybe_unmarshal(OwnerID, fun unmarshal_string/1), + shop_id => maybe_unmarshal(ShopID, fun unmarshal_string/1), + cost => maybe_unmarshal(Cost, fun unmarshal_cash/1), + created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1), + adjustment => maybe_unmarshal( + Adjustment, + fun unmarshal_payment_processing_invoice_adjustment/1 + ), + payment => maybe_unmarshal(Payment, fun unmarshal_payment_processing_invoice_payment/1) + }). + +unmarshal_payment_processing_invoice_adjustment(#limiter_context_InvoiceAdjustment{id = ID}) -> + genlib_map:compact(#{ + id => maybe_unmarshal(ID, fun unmarshal_string/1) + }). + +unmarshal_payment_processing_invoice_payment(#limiter_context_InvoicePayment{ + id = ID, + owner_id = OwnerID, + shop_id = ShopID, + cost = Cost, + capture_cost = CaptureCost, + created_at = CreatedAt, + flow = Flow, + payer = Payer, + effective_adjustment = Adjustment, + effective_refund = EffectiveRefund, + effective_chargeback = EffectiveChargeback +}) -> + genlib_map:compact(#{ + id => maybe_unmarshal(ID, fun unmarshal_string/1), + owner_id => maybe_unmarshal(OwnerID, fun unmarshal_string/1), + shop_id => maybe_unmarshal(ShopID, fun unmarshal_string/1), + cost => maybe_unmarshal(Cost, fun unmarshal_cash/1), + capture_cost => maybe_unmarshal(CaptureCost, fun unmarshal_cash/1), + created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1), + flow => maybe_unmarshal(Flow, fun unmarshal_payment_processing_invoice_payment_flow/1), + payer => maybe_unmarshal(Payer, fun unmarshal_payment_processing_invoice_payment_payer/1), + adjustment => maybe_unmarshal( + Adjustment, + fun unmarshal_payment_processing_invoice_payment_adjustment/1 + ), + refund => maybe_unmarshal(EffectiveRefund, fun unmarshal_payment_processing_invoice_payment_refund/1), + chargeback => maybe_unmarshal( + EffectiveChargeback, + fun unmarshal_payment_processing_invoice_payment_chargeback/1 + ) + }). + +unmarshal_payment_processing_invoice_payment_flow({Flow, _}) -> + Flow. + +unmarshal_payment_processing_invoice_payment_payer({Payer, _}) -> + {Payer, #{}}. + +unmarshal_payment_processing_invoice_payment_adjustment(#limiter_context_InvoicePaymentAdjustment{ + id = ID, + created_at = CreatedAt +}) -> + genlib_map:compact(#{ + id => maybe_unmarshal(ID, fun unmarshal_string/1), + created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1) + }). + +unmarshal_payment_processing_invoice_payment_refund(#limiter_context_InvoicePaymentRefund{ + id = ID, + cost = Cost, + created_at = CreatedAt +}) -> + genlib_map:compact(#{ + id => maybe_unmarshal(ID, fun unmarshal_string/1), + cost => maybe_unmarshal(Cost, fun unmarshal_cash/1), + created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1) + }). + +unmarshal_payment_processing_invoice_payment_chargeback(#limiter_context_InvoicePaymentChargeback{ + id = ID, + levy = Levy, + body = Body, + created_at = CreatedAt +}) -> + genlib_map:compact(#{ + id => maybe_unmarshal(ID, fun unmarshal_string/1), + levy => maybe_unmarshal(Levy, fun unmarshal_cash/1), + body => maybe_unmarshal(Body, fun unmarshal_cash/1), + created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1) + }). + +unmarshal_cash(#domain_Cash{amount = Amount, currency = #domain_CurrencyRef{symbolic_code = Currency}}) -> + #{amount => Amount, currency => Currency}. + +unmarshal_string(Value) -> + Value. + +maybe_unmarshal(undefined, _) -> + undefined; +maybe_unmarshal(Value, UnmarshalFun) -> + UnmarshalFun(Value). diff --git a/apps/limiter/src/lim_payproc_context.erl b/apps/limiter/src/lim_payproc_context.erl new file mode 100644 index 00000000..18cb2573 --- /dev/null +++ b/apps/limiter/src/lim_payproc_context.erl @@ -0,0 +1,213 @@ +-module(lim_payproc_context). + +-include_lib("limiter_proto/include/lim_limiter_payproc_context_thrift.hrl"). + +-export([unmarshal/1]). + +-type thrift_context() :: lim_limiter_payproc_context_thrift:'Context'(). + +%% + +-spec unmarshal(thrift_context()) -> lim_context:context(). +unmarshal(#limiter_context_payproc_Context{ + op = {Operation, _}, + invoice = Invoice +}) -> + genlib_map:compact(#{ + op => Operation, + invoice => maybe_unmarshal(Invoice, fun unmarshal_payment_processing_invoice/1) + }). + +unmarshal_payment_processing_invoice(#limiter_context_payproc_Invoice{ + invoice = #domain_Invoice{ + id = ID, + owner_id = OwnerID, + shop_id = ShopID, + cost = Cost, + created_at = CreatedAt + }, + payment = Payment, + adjustment = Adjustment +}) -> + genlib_map:compact(#{ + id => maybe_unmarshal(ID, fun unmarshal_string/1), + owner_id => maybe_unmarshal(OwnerID, fun unmarshal_string/1), + shop_id => maybe_unmarshal(ShopID, fun unmarshal_string/1), + cost => maybe_unmarshal(Cost, fun unmarshal_cash/1), + created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1), + adjustment => maybe_unmarshal( + Adjustment, + fun unmarshal_payment_processing_invoice_adjustment/1 + ), + payment => maybe_unmarshal(Payment, fun unmarshal_payment_processing_invoice_payment/1) + }). + +unmarshal_payment_processing_invoice_adjustment(#domain_InvoiceAdjustment{id = ID}) -> + #{id => unmarshal_string(ID)}. + +unmarshal_payment_processing_invoice_payment(#limiter_context_payproc_InvoicePayment{ + payment = #domain_InvoicePayment{ + id = ID, + owner_id = OwnerID, + shop_id = ShopID, + cost = Cost, + created_at = CreatedAt, + flow = Flow, + status = Status, + payer = Payer + }, + adjustment = Adjustment, + refund = EffectiveRefund, + chargeback = EffectiveChargeback +}) -> + genlib_map:compact(#{ + id => maybe_unmarshal(ID, fun unmarshal_string/1), + owner_id => maybe_unmarshal(OwnerID, fun unmarshal_string/1), + shop_id => maybe_unmarshal(ShopID, fun unmarshal_string/1), + cost => maybe_unmarshal(Cost, fun unmarshal_cash/1), + capture_cost => get_capture_cost_from_status(Status), + created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1), + flow => maybe_unmarshal(Flow, fun unmarshal_payment_processing_invoice_payment_flow/1), + payer => maybe_unmarshal(Payer, fun unmarshal_payment_processing_invoice_payment_payer/1), + adjustment => maybe_unmarshal( + Adjustment, + fun unmarshal_payment_processing_invoice_payment_adjustment/1 + ), + refund => maybe_unmarshal(EffectiveRefund, fun unmarshal_payment_processing_invoice_payment_refund/1), + chargeback => maybe_unmarshal( + EffectiveChargeback, + fun unmarshal_payment_processing_invoice_payment_chargeback/1 + ) + }). + +get_capture_cost_from_status({captured, #domain_InvoicePaymentCaptured{cost = Cost}}) -> + maybe_unmarshal(Cost, fun unmarshal_cash/1); +get_capture_cost_from_status(_) -> + undefined. + +unmarshal_payment_processing_invoice_payment_flow({Flow, _}) -> + Flow. + +unmarshal_payment_processing_invoice_payment_payer({Payer, Data}) -> + {Payer, unmarshal_payment_processing_invoice_payment_payer_data(Data)}. + +unmarshal_payment_processing_invoice_payment_payer_data(#domain_PaymentResourcePayer{ + resource = #domain_DisposablePaymentResource{payment_tool = PaymentTool} +}) -> + genlib_map:compact(#{ + payment_tool => maybe_unmarshal(PaymentTool, fun unmarshal_payment_processing_payment_tool/1) + }); +unmarshal_payment_processing_invoice_payment_payer_data(#domain_CustomerPayer{payment_tool = PaymentTool}) -> + genlib_map:compact(#{ + payment_tool => maybe_unmarshal(PaymentTool, fun unmarshal_payment_processing_payment_tool/1) + }); +unmarshal_payment_processing_invoice_payment_payer_data(#domain_RecurrentPayer{payment_tool = PaymentTool}) -> + genlib_map:compact(#{ + payment_tool => maybe_unmarshal(PaymentTool, fun unmarshal_payment_processing_payment_tool/1) + }). + +unmarshal_payment_processing_payment_tool({bank_card, #domain_BankCard{token = Token, exp_date = ExpDate}}) -> + {bank_card, #{ + token => Token, + exp_date => maybe_unmarshal(ExpDate, fun unmarshal_payment_processing_bank_card_exp_data/1) + }}; +unmarshal_payment_processing_payment_tool(_) -> + undefined. + +unmarshal_payment_processing_bank_card_exp_data(#domain_BankCardExpDate{month = Month, year = Year}) -> + BinaryMonth = integer_to_binary(Month), + BinaryYear = integer_to_binary(Year), + <>. + +unmarshal_payment_processing_invoice_payment_adjustment(#domain_InvoicePaymentAdjustment{ + id = ID, + created_at = CreatedAt +}) -> + genlib_map:compact(#{ + id => maybe_unmarshal(ID, fun unmarshal_string/1), + created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1) + }). + +unmarshal_payment_processing_invoice_payment_refund(#domain_InvoicePaymentRefund{ + id = ID, + cash = Cost, + created_at = CreatedAt +}) -> + genlib_map:compact(#{ + id => maybe_unmarshal(ID, fun unmarshal_string/1), + cost => maybe_unmarshal(Cost, fun unmarshal_cash/1), + created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1) + }). + +unmarshal_payment_processing_invoice_payment_chargeback(#domain_InvoicePaymentChargeback{ + id = ID, + levy = Levy, + body = Body, + created_at = CreatedAt +}) -> + genlib_map:compact(#{ + id => maybe_unmarshal(ID, fun unmarshal_string/1), + levy => maybe_unmarshal(Levy, fun unmarshal_cash/1), + body => maybe_unmarshal(Body, fun unmarshal_cash/1), + created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1) + }). + +unmarshal_cash(#domain_Cash{amount = Amount, currency = #domain_CurrencyRef{symbolic_code = Currency}}) -> + #{amount => Amount, currency => Currency}. + +unmarshal_string(Value) -> + Value. + +maybe_unmarshal(undefined, _) -> + undefined; +maybe_unmarshal(Value, UnmarshalFun) -> + UnmarshalFun(Value). + +-ifdef(TEST). +-include_lib("eunit/include/eunit.hrl"). + +-spec test() -> _. + +-spec marshal_unmarshal_created_test() -> _. +marshal_unmarshal_created_test() -> + ExpDate = #domain_BankCardExpDate{month = 2, year = 2022}, + PaymentTool = + {bank_card, #domain_BankCard{ + token = <<"Token">>, + bin = <<"bin">>, + exp_date = ExpDate, + last_digits = <<"1234">> + }}, + Data0 = #domain_PaymentResourcePayer{ + resource = #domain_DisposablePaymentResource{payment_tool = PaymentTool}, + contact_info = #domain_ContactInfo{} + }, + #{payment_tool := _} = unmarshal_payment_processing_invoice_payment_payer_data(Data0), + Data1 = #domain_CustomerPayer{ + customer_id = <<"customer_id">>, + customer_binding_id = <<"customer_binding_id">>, + rec_payment_tool_id = <<"rec_payment_tool_id">>, + payment_tool = PaymentTool, + contact_info = #domain_ContactInfo{} + }, + #{payment_tool := _} = unmarshal_payment_processing_invoice_payment_payer_data(Data1), + Data2 = #domain_RecurrentPayer{ + payment_tool = PaymentTool, + recurrent_parent = #domain_RecurrentParentPayment{ + invoice_id = <<"invoice_id">>, + payment_id = <<"payment_id">> + }, + contact_info = #domain_ContactInfo{} + }, + #{payment_tool := _} = unmarshal_payment_processing_invoice_payment_payer_data(Data2), + Data3 = #domain_RecurrentPayer{ + payment_tool = {payment_terminal, #domain_PaymentTerminal{}}, + recurrent_parent = #domain_RecurrentParentPayment{ + invoice_id = <<"invoice_id">>, + payment_id = <<"payment_id">> + }, + contact_info = #domain_ContactInfo{} + }, + #{} = unmarshal_payment_processing_invoice_payment_payer_data(Data3). + +-endif. diff --git a/apps/limiter/src/lim_range_codec.erl b/apps/limiter/src/lim_range_codec.erl index 7767f69a..8e854bd8 100644 --- a/apps/limiter/src/lim_range_codec.erl +++ b/apps/limiter/src/lim_range_codec.erl @@ -48,7 +48,7 @@ marshal(time_range, #{ account_id_from := AccountIDFrom, account_id_to := AccountIDTo }) -> - #time_range_TimeRange{ + #limiter_time_range_TimeRange{ upper = Upper, lower = Lower, account_id_from = AccountIDFrom, @@ -57,15 +57,15 @@ marshal(time_range, #{ marshal(time_range_type, {calendar, SubType}) -> {calendar, marshal(time_range_sub_type, SubType)}; marshal(time_range_type, {interval, Interval}) -> - {interval, #time_range_TimeRangeTypeInterval{amount = Interval}}; + {interval, #limiter_time_range_TimeRangeTypeInterval{amount = Interval}}; marshal(time_range_sub_type, year) -> - {year, #time_range_TimeRangeTypeCalendarYear{}}; + {year, #limiter_time_range_TimeRangeTypeCalendarYear{}}; marshal(time_range_sub_type, month) -> - {month, #time_range_TimeRangeTypeCalendarMonth{}}; + {month, #limiter_time_range_TimeRangeTypeCalendarMonth{}}; marshal(time_range_sub_type, week) -> - {week, #time_range_TimeRangeTypeCalendarWeek{}}; + {week, #limiter_time_range_TimeRangeTypeCalendarWeek{}}; marshal(time_range_sub_type, day) -> - {day, #time_range_TimeRangeTypeCalendarDay{}}; + {day, #limiter_time_range_TimeRangeTypeCalendarDay{}}; marshal(timestamp, {DateTime, USec}) -> DateTimeinSeconds = genlib_time:daytime_to_unixtime(DateTime), {TimeinUnit, Unit} = @@ -99,7 +99,7 @@ unmarshal(range, #limiter_range_LimitRange{ created_at => CreatedAt, currency => Currency }); -unmarshal(time_range, #time_range_TimeRange{ +unmarshal(time_range, #limiter_time_range_TimeRange{ upper = Upper, lower = Lower, account_id_from = AccountIDFrom, @@ -113,7 +113,7 @@ unmarshal(time_range, #time_range_TimeRange{ }; unmarshal(time_range_type, {calendar, SubType}) -> {calendar, unmarshal(time_range_sub_type, SubType)}; -unmarshal(time_range_type, {interval, #time_range_TimeRangeTypeInterval{amount = Interval}}) -> +unmarshal(time_range_type, {interval, #limiter_time_range_TimeRangeTypeInterval{amount = Interval}}) -> {interval, Interval}; unmarshal(time_range_sub_type, {year, _}) -> year; diff --git a/apps/limiter/src/lim_turnover_metric.erl b/apps/limiter/src/lim_turnover_metric.erl index 6582bdaa..cc99c6fd 100644 --- a/apps/limiter/src/lim_turnover_metric.erl +++ b/apps/limiter/src/lim_turnover_metric.erl @@ -3,7 +3,7 @@ -export([compute/4]). -type amount() :: lim_body:amount(). --type currency() :: lim_base_thrift:'CurrencySymbolicCode'(). +-type currency() :: lim_domain_thrift:'CurrencySymbolicCode'(). -type stage() :: hold | commit. -type t() :: number | {amount, currency()}. diff --git a/apps/limiter/test/lim_configurator_SUITE.erl b/apps/limiter/test/lim_configurator_SUITE.erl index 5571c4d8..61e8f64b 100644 --- a/apps/limiter/test/lim_configurator_SUITE.erl +++ b/apps/limiter/test/lim_configurator_SUITE.erl @@ -81,7 +81,7 @@ legacy_create_config(C) -> Client = lim_client:new(), ID = ?config(limit_id, C), Description = genlib:unique(), - Params = #limiter_cfg_LimitCreateParams{ + Params = #limiter_configurator_LimitCreateParams{ id = ID, name = <<"GlobalMonthTurnover">>, description = Description, @@ -150,7 +150,7 @@ get_config(C) -> prepare_environment(ID, LimitName, _C) -> Client = lim_client:new(), - Params = #limiter_cfg_LimitCreateParams{ + Params = #limiter_configurator_LimitCreateParams{ id = ID, name = LimitName, description = <<"description">>, diff --git a/apps/limiter/test/lim_ct_helper.hrl b/apps/limiter/test/lim_ct_helper.hrl index 70f0e00d..57bbc3e2 100644 --- a/apps/limiter/test/lim_ct_helper.hrl +++ b/apps/limiter/test/lim_ct_helper.hrl @@ -4,11 +4,13 @@ -include_lib("limiter_proto/include/lim_configurator_thrift.hrl"). -define(currency, <<"RUB">>). +-define(string, <<"STRING">>). +-define(timestamp, <<"2000-01-01T00:00:00Z">>). -define(cash(Amount), ?cash(Amount, ?currency)). --define(cash(Amount, Currency), #limiter_base_Cash{ +-define(cash(Amount, Currency), #domain_Cash{ amount = Amount, - currency = #limiter_base_CurrencyRef{symbolic_code = Currency} + currency = #domain_CurrencyRef{symbolic_code = Currency} }). -define(scope(Types), {multi, ordsets:from_list(Types)}). @@ -16,6 +18,7 @@ -define(scope_party(), {party, #limiter_config_LimitScopeEmptyDetails{}}). -define(scope_shop(), {shop, #limiter_config_LimitScopeEmptyDetails{}}). +-define(scope_payment_tool(), {payment_tool, #limiter_config_LimitScopeEmptyDetails{}}). -define(lim_type_turnover(), ?lim_type_turnover(?turnover_metric_number())). -define(lim_type_turnover(Metric), @@ -29,13 +32,16 @@ ). -define(time_range_day(), - {calendar, {day, #time_range_TimeRangeTypeCalendarDay{}}} + {calendar, {day, #limiter_time_range_TimeRangeTypeCalendarDay{}}} ). -define(time_range_week(), - {calendar, {week, #time_range_TimeRangeTypeCalendarWeek{}}} + {calendar, {week, #limiter_time_range_TimeRangeTypeCalendarWeek{}}} ). -define(time_range_month(), - {calendar, {month, #time_range_TimeRangeTypeCalendarMonth{}}} + {calendar, {month, #limiter_time_range_TimeRangeTypeCalendarMonth{}}} +). +-define(time_range_year(), + {calendar, {year, #limiter_time_range_TimeRangeTypeCalendarYear{}}} ). -define(op_behaviour(), ?op_behaviour(?op_addition())). @@ -54,7 +60,7 @@ -define(op_invoice_payment(), {invoice_payment, #limiter_context_PaymentProcessingOperationInvoicePayment{}}). -define(ctx_invoice(Cost), #limiter_context_LimitContext{ - payment_processing = #limiter_context_ContextPaymentProcessing{ + limiter_payment_processing = #limiter_context_ContextPaymentProcessing{ op = ?op_invoice(), invoice = #limiter_context_Invoice{ created_at = <<"2000-01-01T00:00:00Z">>, @@ -66,7 +72,7 @@ -define(ctx_invoice_payment(Cost, CaptureCost), ?ctx_invoice_payment(undefined, undefined, Cost, CaptureCost)). -define(ctx_invoice_payment(OwnerID, ShopID, Cost, CaptureCost), #limiter_context_LimitContext{ - payment_processing = #limiter_context_ContextPaymentProcessing{ + limiter_payment_processing = #limiter_context_ContextPaymentProcessing{ op = ?op_invoice_payment(), invoice = #limiter_context_Invoice{ owner_id = OwnerID, @@ -74,14 +80,19 @@ effective_payment = #limiter_context_InvoicePayment{ created_at = <<"2000-01-01T00:00:00Z">>, cost = Cost, - capture_cost = CaptureCost - } + capture_cost = CaptureCost, + flow = {hold, #limiter_context_InvoicePaymentFlowHold{}}, + payer = {payment_resource, #limiter_context_PaymentResourcePayer{}}, + effective_adjustment = #limiter_context_InvoicePaymentAdjustment{}, + effective_chargeback = #limiter_context_InvoicePaymentChargeback{} + }, + effective_adjustment = #limiter_context_InvoiceAdjustment{} } } }). -define(ctx_invoice_payment(Payment), #limiter_context_LimitContext{ - payment_processing = #limiter_context_ContextPaymentProcessing{ + limiter_payment_processing = #limiter_context_ContextPaymentProcessing{ op = ?op_invoice_payment(), invoice = #limiter_context_Invoice{ effective_payment = Payment @@ -90,7 +101,7 @@ }). -define(ctx_invoice_payment_refund(OwnerID, ShopID, Cost, CaptureCost, RefundCost), #limiter_context_LimitContext{ - payment_processing = #limiter_context_ContextPaymentProcessing{ + limiter_payment_processing = #limiter_context_ContextPaymentProcessing{ op = {invoice_payment_refund, #limiter_context_PaymentProcessingOperationInvoicePaymentRefund{}}, invoice = #limiter_context_Invoice{ owner_id = OwnerID, @@ -108,4 +119,109 @@ } }). +%% Payproc + +-define(payproc_op_invoice, {invoice, #limiter_context_payproc_OperationInvoice{}}). +-define(payproc_op_invoice_payment, {invoice_payment, #limiter_context_payproc_OperationInvoicePayment{}}). + +-define(payproc_bank_card(), + ?payproc_bank_card(?string, 2, 2022) +). + +-define(payproc_bank_card(Token, Month, Year), #domain_BankCard{ + token = Token, + bin = ?string, + last_digits = ?string, + exp_date = #domain_BankCardExpDate{month = Month, year = Year} +}). + +-define(payproc_invoice(OwnerID, ShopID, Cost), #domain_Invoice{ + id = ?string, + owner_id = OwnerID, + shop_id = ShopID, + created_at = ?timestamp, + status = {unpaid, #domain_InvoiceUnpaid{}}, + details = #domain_InvoiceDetails{product = ?string}, + due = ?timestamp, + cost = Cost +}). + +-define(payproc_invoice_payment(Cost, CaptureCost), + ?payproc_invoice_payment(Cost, CaptureCost, {bank_card, ?payproc_bank_card()}) +). + +-define(payproc_invoice_payment(Cost, CaptureCost, PaymentTool), #domain_InvoicePayment{ + id = ?string, + created_at = ?timestamp, + status = {captured, #domain_InvoicePaymentCaptured{cost = CaptureCost}}, + cost = Cost, + domain_revision = 1, + flow = {instant, #domain_InvoicePaymentFlowInstant{}}, + payer = + {payment_resource, #domain_PaymentResourcePayer{ + resource = #domain_DisposablePaymentResource{ + payment_tool = PaymentTool + }, + contact_info = #domain_ContactInfo{} + }} +}). + +-define(payproc_ctx_invoice(Cost), #limiter_context_LimitContext{ + payment_processing = #limiter_context_payproc_Context{ + op = ?payproc_op_invoice, + invoice = #limiter_context_payproc_Invoice{ + invoice = ?payproc_invoice(?string, ?string, Cost) + } + } +}). + +-define(payproc_ctx_invoice_payment(Cost, CaptureCost), + ?payproc_ctx_invoice_payment(?string, ?string, Cost, CaptureCost) +). + +-define(payproc_ctx_invoice_payment(OwnerID, ShopID, Cost, CaptureCost), #limiter_context_LimitContext{ + payment_processing = #limiter_context_payproc_Context{ + op = ?payproc_op_invoice_payment, + invoice = #limiter_context_payproc_Invoice{ + invoice = ?payproc_invoice(OwnerID, ShopID, Cost), + payment = #limiter_context_payproc_InvoicePayment{ + payment = ?payproc_invoice_payment(Cost, CaptureCost) + } + } + } +}). + +-define(payproc_ctx_invoice_payment(Payment), #limiter_context_LimitContext{ + payment_processing = #limiter_context_payproc_Context{ + op = ?payproc_op_invoice_payment, + invoice = #limiter_context_payproc_Invoice{ + invoice = ?payproc_invoice(?string, ?string, ?cash(10)), + payment = #limiter_context_payproc_InvoicePayment{ + payment = Payment + } + } + } +}). + +-define(payproc_ctx_invoice_payment_refund(OwnerID, ShopID, Cost, CaptureCost, RefundCost), + #limiter_context_LimitContext{ + payment_processing = #limiter_context_payproc_Context{ + op = {invoice_payment_refund, #limiter_context_payproc_OperationInvoicePaymentRefund{}}, + invoice = #limiter_context_payproc_Invoice{ + invoice = ?payproc_invoice(OwnerID, ShopID, Cost), + payment = #limiter_context_payproc_InvoicePayment{ + payment = ?payproc_invoice_payment(Cost, CaptureCost), + refund = #domain_InvoicePaymentRefund{ + id = ?string, + status = {succeeded, #domain_InvoicePaymentRefundSucceeded{}}, + created_at = ?timestamp, + domain_revision = 1, + cash = RefundCost + } + } + } + } + } +). + -endif. diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index abb2dcc9..1d07ff15 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -40,6 +40,12 @@ -export([commit_refund_keep_number_unchanged/1]). -export([partial_commit_number_counts_as_single_op/1]). +-export([payproc_hold_ok/1]). +-export([payproc_commit_ok/1]). +-export([payproc_rollback_ok/1]). +-export([payproc_refund_ok/1]). +-export([payproc_commit_with_payment_tool_scope_ok/1]). + -type group_name() :: atom(). -type test_case_name() :: atom(). @@ -72,7 +78,12 @@ groups() -> refund_ok, commit_inexistent_hold_fails, partial_commit_inexistent_hold_fails, - commit_multirange_limit_ok + commit_multirange_limit_ok, + payproc_hold_ok, + payproc_commit_ok, + payproc_rollback_ok, + payproc_refund_ok, + payproc_commit_with_payment_tool_scope_ok ]}, {cashless, [parallel], [ commit_number_ok, @@ -145,13 +156,13 @@ commit_with_default_exchange(C) -> _ = mock_exchange(Rational, C), ID = configure_limit(?time_range_month(), ?global(), C), Context = #limiter_context_LimitContext{ - payment_processing = #limiter_context_ContextPaymentProcessing{ + limiter_payment_processing = #limiter_context_ContextPaymentProcessing{ op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, invoice = #limiter_context_Invoice{ created_at = <<"2000-01-01T00:00:00Z">>, - cost = #limiter_base_Cash{ + cost = #domain_Cash{ amount = 10000, - currency = #limiter_base_CurrencyRef{symbolic_code = <<"SOME_CURRENCY">>} + currency = #domain_CurrencyRef{symbolic_code = <<"SOME_CURRENCY">>} } } } @@ -165,18 +176,18 @@ partial_commit_with_exchange(C) -> _ = mock_exchange(Rational, C), ID = configure_limit(?time_range_month(), ?global(), C), Context = #limiter_context_LimitContext{ - payment_processing = #limiter_context_ContextPaymentProcessing{ + limiter_payment_processing = #limiter_context_ContextPaymentProcessing{ op = {invoice_payment, #limiter_context_PaymentProcessingOperationInvoicePayment{}}, invoice = #limiter_context_Invoice{ effective_payment = #limiter_context_InvoicePayment{ created_at = <<"2000-01-01T00:00:00Z">>, - cost = #limiter_base_Cash{ + cost = #domain_Cash{ amount = 10000, - currency = #limiter_base_CurrencyRef{symbolic_code = <<"USD">>} + currency = #domain_CurrencyRef{symbolic_code = <<"USD">>} }, - capture_cost = #limiter_base_Cash{ + capture_cost = #domain_Cash{ amount = 8000, - currency = #limiter_base_CurrencyRef{symbolic_code = <<"USD">>} + currency = #domain_CurrencyRef{symbolic_code = <<"USD">>} } } } @@ -191,13 +202,13 @@ commit_with_exchange(C) -> _ = mock_exchange(Rational, C), ID = configure_limit(?time_range_month(), ?global(), C), Context = #limiter_context_LimitContext{ - payment_processing = #limiter_context_ContextPaymentProcessing{ + limiter_payment_processing = #limiter_context_ContextPaymentProcessing{ op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, invoice = #limiter_context_Invoice{ created_at = <<"2000-01-01T00:00:00Z">>, - cost = #limiter_base_Cash{ + cost = #domain_Cash{ amount = 10000, - currency = #limiter_base_CurrencyRef{symbolic_code = <<"USD">>} + currency = #domain_CurrencyRef{symbolic_code = <<"USD">>} } } } @@ -251,13 +262,13 @@ hold_ok(C) -> commit_ok(C) -> ID = configure_limit(?time_range_month(), ?global(), C), Context = #limiter_context_LimitContext{ - payment_processing = #limiter_context_ContextPaymentProcessing{ + limiter_payment_processing = #limiter_context_ContextPaymentProcessing{ op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, invoice = #limiter_context_Invoice{ created_at = <<"2000-01-01T00:00:00Z">>, - cost = #limiter_base_Cash{ + cost = #domain_Cash{ amount = 10, - currency = #limiter_base_CurrencyRef{symbolic_code = <<"RUB">>} + currency = #domain_CurrencyRef{symbolic_code = <<"RUB">>} } } } @@ -299,7 +310,7 @@ commit_inexistent_hold_fails(C) -> % NOTE % We do not expect `LimitChangeNotFound` here because we no longer reconcile with accounter % before requesting him to hold / commit. - {exception, #limiter_base_InvalidRequest{}} = + {exception, #'InvalidRequest'{}} = lim_client:commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)). -spec partial_commit_inexistent_hold_fails(config()) -> _. @@ -309,7 +320,7 @@ partial_commit_inexistent_hold_fails(C) -> % NOTE % We do not expect `LimitChangeNotFound` here because we no longer reconcile with accounter % before requesting him to hold / commit. - {exception, #limiter_base_InvalidRequest{}} = + {exception, #'InvalidRequest'{}} = lim_client:commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)). -spec commit_multirange_limit_ok(config()) -> _. @@ -468,6 +479,79 @@ partial_commit_number_counts_as_single_op(C) -> %% +-spec payproc_hold_ok(config()) -> _. +payproc_hold_ok(C) -> + ID = configure_limit(?time_range_month(), ?global(), C), + Context = ?payproc_ctx_invoice(?cash(10)), + {ok, {vector, #limiter_VectorClock{}}} = lim_client:hold(?LIMIT_CHANGE(ID), Context, ?config(client, C)), + {ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)). + +-spec payproc_commit_ok(config()) -> _. +payproc_commit_ok(C) -> + ID = configure_limit(?time_range_year(), ?global(), C), + Context = ?payproc_ctx_invoice(?cash(10)), + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), + {ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)). + +-spec payproc_rollback_ok(config()) -> _. +payproc_rollback_ok(C) -> + ID = configure_limit(?time_range_week(), ?global(), C), + Context0 = ?payproc_ctx_invoice_payment(?cash(10), ?cash(10)), + Context1 = ?payproc_ctx_invoice_payment(?cash(10), ?cash(0)), + Change = ?LIMIT_CHANGE(ID), + {ok, {vector, _}} = lim_client:hold(Change, Context0, ?config(client, C)), + {ok, {vector, _}} = lim_client:commit(Change, Context1, ?config(client, C)). + +-spec payproc_refund_ok(config()) -> _. +payproc_refund_ok(C) -> + Client = ?config(client, C), + OwnerID = <<"WWWcool Ltd">>, + ShopID = <<"shop">>, + ID = configure_limit(?time_range_day(), ?scope([?scope_party(), ?scope_shop()]), C), + Context0 = ?payproc_ctx_invoice_payment(OwnerID, ShopID, ?cash(15), ?cash(15)), + RefundContext1 = ?payproc_ctx_invoice_payment_refund(OwnerID, ShopID, ?cash(10), ?cash(10), ?cash(10)), + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Payment">>), Context0, Client), + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Refund">>), RefundContext1, Client), + {ok, #limiter_Limit{} = Limit2} = lim_client:get(ID, RefundContext1, Client), + ?assertEqual(Limit2#limiter_Limit.amount, 5). + +-spec payproc_commit_with_payment_tool_scope_ok(config()) -> _. +payproc_commit_with_payment_tool_scope_ok(C) -> + Client = ?config(client, C), + ID = configure_limit(?time_range_week(), ?scope([?scope_payment_tool()]), ?turnover_metric_number(), C), + Context0 = ?payproc_ctx_invoice_payment( + ?payproc_invoice_payment( + ?cash(10), + ?cash(10), + {bank_card, ?payproc_bank_card()} + ) + ), + Context1 = ?payproc_ctx_invoice_payment( + ?payproc_invoice_payment( + ?cash(10), + ?cash(10), + {bank_card, ?payproc_bank_card(<<"OtherToken">>, 2, 2022)} + ) + ), + Context2 = ?payproc_ctx_invoice_payment( + ?payproc_invoice_payment( + ?cash(10), + ?cash(10), + {bank_card, ?payproc_bank_card(?string, 3, 2022)} + ) + ), + {ok, LimitState0} = lim_client:get(ID, Context0, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 1), Context0, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 2), Context1, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 3), Context2, Client), + {ok, LimitState1} = lim_client:get(ID, Context0, Client), + ?assertEqual( + LimitState1#limiter_Limit.amount, + LimitState0#limiter_Limit.amount + 1 + ). + +%% + gen_change_id(LimitID, ChangeID) -> genlib:format("~s/~p", [LimitID, ChangeID]). diff --git a/rebar.config b/rebar.config index 3bdee951..80ddc112 100644 --- a/rebar.config +++ b/rebar.config @@ -50,7 +50,7 @@ % mandatory unmatched_returns, error_handling, - race_conditions, + % race_conditions, unknown ]}, {plt_apps, all_deps} diff --git a/rebar.lock b/rebar.lock index 55865372..a8ad132b 100644 --- a/rebar.lock +++ b/rebar.lock @@ -25,7 +25,7 @@ {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},1}, {<<"limiter_proto">>, {git,"https://github.com/valitydev/limiter-proto.git", - {ref,"6723e862157a7f78194a64271899c2ef1581e177"}}, + {ref,"ac9705389211682263c0a983ae76f663d4857ec9"}}, 0}, {<<"machinery">>, {git,"https://github.com/valitydev/machinery-erlang.git", From 618a75171235a68ee0d4629892c17a4a0b2d14d8 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Wed, 6 Jul 2022 18:32:32 +0300 Subject: [PATCH 22/54] TD-329: Add withdrawal context support (#12) * Bump to valitydev/machinery-erlang@62c3243 * Bump to valitydev/machinegun-proto@347c5c4 * Drop top-level `damsel` dependency * Drop `get_rate` testcase as meaningless * Drop testcases which became duplicate * Support new context type @ config * Delegate context accessors to specific modules * Support rollbacks @ valitydev/limiter-proto@6158184 --- .env | 4 +- apps/limiter/src/lim_accounting.erl | 2 +- apps/limiter/src/lim_body.erl | 49 +- apps/limiter/src/lim_config_codec.erl | 93 ++-- apps/limiter/src/lim_config_machine.erl | 95 ++-- .../src/lim_config_machinery_schema.erl | 6 +- apps/limiter/src/lim_configurator.erl | 11 +- apps/limiter/src/lim_context.erl | 198 ++------- apps/limiter/src/lim_handler.erl | 14 +- apps/limiter/src/lim_limiter_context.erl | 129 ------ apps/limiter/src/lim_payproc_context.erl | 420 ++++++++++-------- apps/limiter/src/lim_payproc_utils.erl | 48 ++ apps/limiter/src/lim_range_codec.erl | 37 +- .../src/lim_range_machinery_schema.erl | 8 +- apps/limiter/src/lim_rates.erl | 2 +- apps/limiter/src/lim_turnover_metric.erl | 4 +- apps/limiter/src/lim_turnover_processor.erl | 11 +- apps/limiter/src/lim_wthdproc_context.erl | 73 +++ apps/limiter/src/limiter.app.src | 1 - apps/limiter/src/limiter.erl | 4 +- apps/limiter/test/lim_client.erl | 23 +- apps/limiter/test/lim_configurator_SUITE.erl | 29 +- apps/limiter/test/lim_ct_helper.hrl | 246 +++++----- apps/limiter/test/lim_turnover_SUITE.erl | 337 ++++++-------- rebar.config | 1 - rebar.lock | 10 +- 26 files changed, 835 insertions(+), 1020 deletions(-) delete mode 100644 apps/limiter/src/lim_limiter_context.erl create mode 100644 apps/limiter/src/lim_payproc_utils.erl create mode 100644 apps/limiter/src/lim_wthdproc_context.erl diff --git a/.env b/.env index aa650996..19340136 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ SERVICE_NAME=limiter -OTP_VERSION=24.2.0 +OTP_VERSION=24.3.4 REBAR_VERSION=3.18 -THRIFT_VERSION=0.14.2.2 +THRIFT_VERSION=0.14.2.3 diff --git a/apps/limiter/src/lim_accounting.erl b/apps/limiter/src/lim_accounting.erl index 0ef575a8..39366c9d 100644 --- a/apps/limiter/src/lim_accounting.erl +++ b/apps/limiter/src/lim_accounting.erl @@ -161,7 +161,7 @@ call_accounter(Function, Args, LimitContext) -> WoodyContext = lim_context:woody_context(LimitContext), lim_client_woody:call(accounter, Function, Args, WoodyContext). -convert_exception(#'InvalidRequest'{errors = Errors}) -> +convert_exception(#base_InvalidRequest{errors = Errors}) -> Errors; convert_exception(#accounter_InvalidPostingParams{wrong_postings = Errors}) -> maps:fold(fun(_, Error, Acc) -> [Error | Acc] end, [], Errors). diff --git a/apps/limiter/src/lim_body.erl b/apps/limiter/src/lim_body.erl index c2be6f4a..9da6c453 100644 --- a/apps/limiter/src/lim_body.erl +++ b/apps/limiter/src/lim_body.erl @@ -8,7 +8,7 @@ currency := currency() }. --type currency() :: lim_domain_thrift:'CurrencySymbolicCode'(). +-type currency() :: dmsl_domain_thrift:'CurrencySymbolicCode'(). -type config() :: lim_config_machine:config(). -type body_type() :: full | partial. @@ -24,47 +24,18 @@ get(BodyType, Config, LimitContext) -> do(fun() -> ContextType = lim_config_machine:context_type(Config), {ok, Operation} = lim_context:get_operation(ContextType, LimitContext), - Body = unwrap(get_body_for_operation(BodyType, Operation, Config, LimitContext)), - apply_op_behaviour(Body, Config, LimitContext) + Body = unwrap(get_body_for_operation(BodyType, ContextType, LimitContext)), + apply_op_behaviour(Operation, Body, Config) end). --spec get_body_for_operation(body_type(), lim_context:context_operation(), config(), lim_context:t()) -> +-spec get_body_for_operation(body_type(), lim_context:context_type(), lim_context:t()) -> {ok, cash()} | {error, notfound}. -get_body_for_operation(full, invoice = Operation, Config, LimitContext) -> - ContextType = lim_config_machine:context_type(Config), - lim_context:get_from_context(ContextType, cost, Operation, LimitContext); -get_body_for_operation(full, invoice_adjustment, Config, LimitContext) -> - ContextType = lim_config_machine:context_type(Config), - lim_context:get_from_context(ContextType, cost, invoice, LimitContext); -get_body_for_operation(full, invoice_payment = Operation, Config, LimitContext) -> - ContextType = lim_config_machine:context_type(Config), - lim_context:get_from_context(ContextType, cost, Operation, LimitContext); -get_body_for_operation(full, invoice_payment_adjustment, Config, LimitContext) -> - ContextType = lim_config_machine:context_type(Config), - lim_context:get_from_context(ContextType, cost, invoice_payment, LimitContext); -get_body_for_operation(full, invoice_payment_refund, Config, LimitContext) -> - ContextType = lim_config_machine:context_type(Config), - lim_context:get_from_context(ContextType, cost, invoice_payment, LimitContext); -get_body_for_operation(full, invoice_payment_chargeback = Operation, Config, LimitContext) -> - ContextType = lim_config_machine:context_type(Config), - lim_context:get_from_context(ContextType, body, Operation, LimitContext); -get_body_for_operation(partial, invoice, _Config, _LimitContext) -> - {error, notfound}; -get_body_for_operation(partial, invoice_adjustment, _Config, _LimitContext) -> - {error, notfound}; -get_body_for_operation(partial, invoice_payment = Operation, Config, LimitContext) -> - ContextType = lim_config_machine:context_type(Config), - lim_context:get_from_context(ContextType, capture_cost, Operation, LimitContext); -get_body_for_operation(partial, invoice_payment_adjustment, _Config, _LimitContext) -> - {error, notfound}; -get_body_for_operation(partial, invoice_payment_refund = Operation, Config, LimitContext) -> - ContextType = lim_config_machine:context_type(Config), - lim_context:get_from_context(ContextType, cost, Operation, LimitContext); -get_body_for_operation(partial, invoice_payment_chargeback, _Config, _LimitContext) -> - {error, notfound}. +get_body_for_operation(full, ContextType, LimitContext) -> + lim_context:get_value(ContextType, cost, LimitContext); +get_body_for_operation(partial, ContextType, LimitContext) -> + lim_context:get_value(ContextType, capture_cost, LimitContext). -apply_op_behaviour(Body, #{op_behaviour := ComputationConfig}, LimitContext) -> - {ok, Operation} = lim_context:get_operation(payment_processing, LimitContext), +apply_op_behaviour(Operation, Body, #{op_behaviour := ComputationConfig}) -> case maps:get(Operation, ComputationConfig, undefined) of addition -> Body; @@ -73,7 +44,7 @@ apply_op_behaviour(Body, #{op_behaviour := ComputationConfig}, LimitContext) -> undefined -> Body end; -apply_op_behaviour(Body, _Config, _LimitContext) -> +apply_op_behaviour(_Operation, Body, _Config) -> Body. invert_body(Cash = #{amount := Amount}) -> diff --git a/apps/limiter/src/lim_config_codec.erl b/apps/limiter/src/lim_config_codec.erl index 13b25fa7..52e3452b 100644 --- a/apps/limiter/src/lim_config_codec.erl +++ b/apps/limiter/src/lim_config_codec.erl @@ -1,6 +1,7 @@ -module(lim_config_codec). --include_lib("limiter_proto/include/lim_limiter_config_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_config_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_timerange_thrift.hrl"). -export([marshal/2]). -export([unmarshal/2]). @@ -35,7 +36,7 @@ maybe_apply(Value, Fun, _Default) -> -spec marshal(type_name(), decoded_value()) -> encoded_value(). marshal(timestamped_change, {ev, Timestamp, Change}) -> - #limiter_config_TimestampedChange{ + #config_TimestampedChange{ change = marshal_change(Change), occured_at = marshal_timestamp(Timestamp) }. @@ -53,11 +54,11 @@ marshal_timestamp({DateTime, USec}) -> genlib_rfc3339:format_relaxed(TimeinUnit, Unit). marshal_change({created, Config}) -> - {created, #limiter_config_CreatedChange{limit_config = marshal_config(Config)}}. + {created, #config_CreatedChange{limit_config = marshal_config(Config)}}. -spec marshal_config(decoded_value()) -> encoded_value(). marshal_config(Config) -> - #limiter_config_LimitConfig{ + #config_LimitConfig{ id = lim_config_machine:id(Config), processor_type = lim_config_machine:processor_type(Config), description = lim_config_machine:description(Config), @@ -73,62 +74,64 @@ marshal_config(Config) -> marshal_op_behaviour(OpBehaviour) -> PaymentRefund = maps:get(invoice_payment_refund, OpBehaviour, undefined), - #limiter_config_OperationLimitBehaviour{ + #config_OperationLimitBehaviour{ invoice_payment_refund = maybe_apply(PaymentRefund, fun marshal_behaviour/1) }. marshal_behaviour(subtraction) -> - {subtraction, #limiter_config_Subtraction{}}; + {subtraction, #config_Subtraction{}}; marshal_behaviour(addition) -> - {addition, #limiter_config_Addition{}}. + {addition, #config_Addition{}}. marshal_time_range_type({calendar, CalendarType}) -> {calendar, marshal_calendar_time_range_type(CalendarType)}; marshal_time_range_type({interval, Amount}) -> - {interval, #limiter_time_range_TimeRangeTypeInterval{amount = Amount}}. + {interval, #timerange_TimeRangeTypeInterval{amount = Amount}}. marshal_calendar_time_range_type(day) -> - {day, #limiter_time_range_TimeRangeTypeCalendarDay{}}; + {day, #timerange_TimeRangeTypeCalendarDay{}}; marshal_calendar_time_range_type(week) -> - {week, #limiter_time_range_TimeRangeTypeCalendarWeek{}}; + {week, #timerange_TimeRangeTypeCalendarWeek{}}; marshal_calendar_time_range_type(month) -> - {month, #limiter_time_range_TimeRangeTypeCalendarMonth{}}; + {month, #timerange_TimeRangeTypeCalendarMonth{}}; marshal_calendar_time_range_type(year) -> - {year, #limiter_time_range_TimeRangeTypeCalendarYear{}}. + {year, #timerange_TimeRangeTypeCalendarYear{}}. marshal_context_type(payment_processing) -> - {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}. + {payment_processing, #config_LimitContextTypePaymentProcessing{}}; +marshal_context_type(withdrawal_processing) -> + {withdrawal_processing, #config_LimitContextTypeWithdrawalProcessing{}}. marshal_type({turnover, Metric}) -> - {turnover, #limiter_config_LimitTypeTurnover{ + {turnover, #config_LimitTypeTurnover{ metric = marshal_turnover_metric(Metric) }}. marshal_turnover_metric(number) -> - {number, #limiter_config_LimitTurnoverNumber{}}; + {number, #config_LimitTurnoverNumber{}}; marshal_turnover_metric({amount, Currency}) -> - {amount, #limiter_config_LimitTurnoverAmount{currency = Currency}}. + {amount, #config_LimitTurnoverAmount{currency = Currency}}. marshal_scope(Types) -> {multi, ordsets:from_list(lists:map(fun marshal_scope_type/1, ordsets:to_list(Types)))}. marshal_scope_type(party) -> - {party, #limiter_config_LimitScopeEmptyDetails{}}; + {party, #config_LimitScopeEmptyDetails{}}; marshal_scope_type(shop) -> - {shop, #limiter_config_LimitScopeEmptyDetails{}}; + {shop, #config_LimitScopeEmptyDetails{}}; marshal_scope_type(wallet) -> - {wallet, #limiter_config_LimitScopeEmptyDetails{}}; + {wallet, #config_LimitScopeEmptyDetails{}}; marshal_scope_type(identity) -> - {identity, #limiter_config_LimitScopeEmptyDetails{}}; + {identity, #config_LimitScopeEmptyDetails{}}; marshal_scope_type(payment_tool) -> - {payment_tool, #limiter_config_LimitScopeEmptyDetails{}}. + {payment_tool, #config_LimitScopeEmptyDetails{}}. %% -spec unmarshal(type_name(), encoded_value()) -> decoded_value(). unmarshal(timestamped_change, TimestampedChange) -> - Timestamp = unmarshal_timestamp(TimestampedChange#limiter_config_TimestampedChange.occured_at), - Change = unmarshal_change(TimestampedChange#limiter_config_TimestampedChange.change), + Timestamp = unmarshal_timestamp(TimestampedChange#config_TimestampedChange.occured_at), + Change = unmarshal_change(TimestampedChange#config_TimestampedChange.change), {ev, Timestamp, Change}. unmarshal_timestamp(Timestamp) when is_binary(Timestamp) -> @@ -148,7 +151,7 @@ unmarshal_timestamp(Timestamp) when is_binary(Timestamp) -> end. -spec unmarshal_params(encoded_value()) -> decoded_value(). -unmarshal_params(#limiter_config_LimitConfigParams{ +unmarshal_params(#config_LimitConfigParams{ id = ID, description = Description, started_at = StartedAt, @@ -171,10 +174,10 @@ unmarshal_params(#limiter_config_LimitConfigParams{ op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1) }). -unmarshal_change({created, #limiter_config_CreatedChange{limit_config = Config}}) -> +unmarshal_change({created, #config_CreatedChange{limit_config = Config}}) -> {created, unmarshal_config(Config)}. -unmarshal_config(#limiter_config_LimitConfig{ +unmarshal_config(#config_LimitConfig{ id = ID, processor_type = ProcessorType, description = Description, @@ -217,25 +220,25 @@ derive_type(undefined, {cash, Currency}) -> -spec unmarshal_op_behaviour(encoded_value()) -> decoded_value(). unmarshal_op_behaviour(OpBehaviour) -> - #limiter_config_OperationLimitBehaviour{ + #config_OperationLimitBehaviour{ invoice_payment_refund = Refund } = OpBehaviour, genlib_map:compact(#{ invoice_payment_refund => maybe_apply(Refund, fun unmarshal_behaviour/1) }). -unmarshal_behaviour({subtraction, #limiter_config_Subtraction{}}) -> +unmarshal_behaviour({subtraction, #config_Subtraction{}}) -> subtraction; -unmarshal_behaviour({addition, #limiter_config_Addition{}}) -> +unmarshal_behaviour({addition, #config_Addition{}}) -> addition. -spec unmarshal_body_type_deprecated(encoded_value()) -> decoded_value(). -unmarshal_body_type_deprecated({cash, #limiter_config_LimitBodyTypeCash{currency = Currency}}) -> +unmarshal_body_type_deprecated({cash, #config_LimitBodyTypeCash{currency = Currency}}) -> {cash, Currency}. unmarshal_time_range_type({calendar, CalendarType}) -> {calendar, unmarshal_calendar_time_range_type(CalendarType)}; -unmarshal_time_range_type({interval, #limiter_time_range_TimeRangeTypeInterval{amount = Amount}}) -> +unmarshal_time_range_type({interval, #timerange_TimeRangeTypeInterval{amount = Amount}}) -> {interval, Amount}. unmarshal_calendar_time_range_type({day, _}) -> @@ -247,15 +250,17 @@ unmarshal_calendar_time_range_type({month, _}) -> unmarshal_calendar_time_range_type({year, _}) -> year. -unmarshal_context_type({payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}) -> - payment_processing. +unmarshal_context_type({payment_processing, #config_LimitContextTypePaymentProcessing{}}) -> + payment_processing; +unmarshal_context_type({withdrawal_processing, #config_LimitContextTypeWithdrawalProcessing{}}) -> + withdrawal_processing. -unmarshal_type({turnover, #limiter_config_LimitTypeTurnover{metric = Metric}}) -> +unmarshal_type({turnover, #config_LimitTypeTurnover{metric = Metric}}) -> {turnover, maybe_apply(Metric, fun unmarshal_turnover_metric/1, number)}. unmarshal_turnover_metric({number, _}) -> number; -unmarshal_turnover_metric({amount, #limiter_config_LimitTurnoverAmount{currency = Currency}}) -> +unmarshal_turnover_metric({amount, #config_LimitTurnoverAmount{currency = Currency}}) -> {amount, Currency}. unmarshal_scope({single, Type}) -> @@ -302,15 +307,15 @@ marshal_unmarshal_created_test() -> -spec unmarshal_created_w_deprecated_body_type_test_() -> [_TestGen]. unmarshal_created_w_deprecated_body_type_test_() -> Now = lim_time:now(), - Config = #limiter_config_LimitConfig{ + Config = #config_LimitConfig{ id = <<"ID">>, processor_type = <<"TurnoverProcessor">>, created_at = lim_time:to_rfc3339(Now), started_at = <<"2000-01-01T00:00:00Z">>, shard_size = 42, - time_range_type = {calendar, {day, #limiter_time_range_TimeRangeTypeCalendarDay{}}}, - context_type = {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}, - body_type_deprecated = {cash, #limiter_config_LimitBodyTypeCash{currency = <<"☭☭☭"/utf8>>}} + time_range_type = {calendar, {day, #timerange_TimeRangeTypeCalendarDay{}}}, + context_type = {payment_processing, #config_LimitContextTypePaymentProcessing{}}, + body_type_deprecated = {cash, #config_LimitBodyTypeCash{currency = <<"☭☭☭"/utf8>>}} }, [ ?_assertMatch( @@ -320,8 +325,8 @@ unmarshal_created_w_deprecated_body_type_test_() -> type := {turnover, {amount, <<"☭☭☭"/utf8>>}} }}, unmarshal_change( - {created, #limiter_config_CreatedChange{ - limit_config = Config#limiter_config_LimitConfig{ + {created, #config_CreatedChange{ + limit_config = Config#config_LimitConfig{ type = undefined } }} @@ -334,9 +339,9 @@ unmarshal_created_w_deprecated_body_type_test_() -> type := {turnover, {amount, <<"☭☭☭"/utf8>>}} }}, unmarshal_change( - {created, #limiter_config_CreatedChange{ - limit_config = Config#limiter_config_LimitConfig{ - type = {turnover, #limiter_config_LimitTypeTurnover{}} + {created, #config_CreatedChange{ + limit_config = Config#config_LimitConfig{ + type = {turnover, #config_LimitTypeTurnover{}} } }} ) diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index 81b30867..5a567483 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -1,6 +1,6 @@ -module(lim_config_machine). --include_lib("limiter_proto/include/lim_limiter_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_limiter_thrift.hrl"). %% Accessors @@ -79,10 +79,10 @@ -type op_behaviour() :: #{operation_type() := addition | subtraction}. -type operation_type() :: invoice_payment_refund. --type lim_id() :: lim_limiter_thrift:'LimitID'(). --type lim_change() :: lim_limiter_thrift:'LimitChange'(). --type limit() :: lim_limiter_thrift:'Limit'(). --type timestamp() :: lim_base_thrift:'Timestamp'(). +-type lim_id() :: limproto_limiter_thrift:'LimitID'(). +-type lim_change() :: limproto_limiter_thrift:'LimitChange'(). +-type limit() :: limproto_limiter_thrift:'Limit'(). +-type timestamp() :: dmsl_base_thrift:'Timestamp'(). -export_type([config/0]). -export_type([limit_type/0]). @@ -522,14 +522,14 @@ mk_shard_id(Prefix, Units, ShardSize) -> -spec mk_scope_prefix(config(), lim_context()) -> prefix(). mk_scope_prefix(Config, LimitContext) -> - mk_scope_prefix_impl(scope(Config), LimitContext). + mk_scope_prefix_impl(scope(Config), context_type(Config), LimitContext). --spec mk_scope_prefix_impl(limit_scope(), lim_context()) -> prefix(). -mk_scope_prefix_impl(Scope, LimitContext) -> +-spec mk_scope_prefix_impl(limit_scope(), context_type(), lim_context()) -> prefix(). +mk_scope_prefix_impl(Scope, ContextType, LimitContext) -> Bits = enumerate_context_bits(Scope), ordsets:fold( fun(Bit, Acc) -> - {ok, Value} = extract_context_bit(Bit, LimitContext), + {ok, Value} = extract_context_bit(Bit, ContextType, LimitContext), append_prefix(Value, Acc) end, <<>>, @@ -541,7 +541,7 @@ append_prefix(Fragment, Acc) -> <>. -type context_bit() :: - {from, lim_context:context_type(), _Name :: atom(), lim_context:context_operation()} + {from, _ValueName :: atom()} | {order, integer(), context_bit()}. -spec enumerate_context_bits(limit_scope()) -> ordsets:ordset(context_bit()). @@ -550,7 +550,7 @@ enumerate_context_bits(Types) -> append_context_bits(party, Bits) -> ordsets:add_element( - {order, 1, {from, payment_processing, owner_id, invoice}}, + {order, 1, {from, owner_id}}, Bits ); append_context_bits(shop, Bits) -> @@ -558,31 +558,33 @@ append_context_bits(shop, Bits) -> % NOTE % Shop scope implies party scope. % Also we need to preserve order between party / shop to ensure backwards compatibility. - {order, 1, {from, payment_processing, owner_id, invoice}}, - {order, 2, {from, payment_processing, shop_id, invoice}} + {order, 1, {from, owner_id}}, + {order, 2, {from, shop_id}} ]); append_context_bits(payment_tool, Bits) -> ordsets:add_element( - {from, payment_processing, payer, invoice_payment}, + {from, payment_tool}, Bits ). --spec extract_context_bit(context_bit(), lim_context()) -> {ok, binary()}. -extract_context_bit({order, _, Bit}, LimitContext) -> - extract_context_bit(Bit, LimitContext); -extract_context_bit({from, payment_processing, payer, Op}, LimitContext) -> - {ok, {_, PayerData}} = lim_context:get_from_context(payment_processing, payer, Op, LimitContext), - #{payment_tool := {PaymentToolType, PaymentToolData}} = PayerData, - case PaymentToolType of - bank_card -> - Token = maps:get(token, PaymentToolData), - ExpData = maps:get(exp_date, PaymentToolData, <<>>), - {ok, <>}; - _ -> - {error, {unsupported_payment_tool_type, PaymentToolType}} +-spec extract_context_bit(context_bit(), context_type(), lim_context()) -> {ok, binary()}. +extract_context_bit({order, _, Bit}, ContextType, LimitContext) -> + extract_context_bit(Bit, ContextType, LimitContext); +extract_context_bit({from, payment_tool}, ContextType, LimitContext) -> + {ok, PaymentTool} = lim_context:get_value(ContextType, payment_tool, LimitContext), + case PaymentTool of + {bank_card, #{token := Token, exp_date := {Month, Year}}} -> + {ok, mk_scope_component([Token, Month, Year])}; + {bank_card, #{token := Token, exp_date := undefined}} -> + {ok, mk_scope_component([Token, <<"undefined">>])}; + {digital_wallet, #{id := ID, service := Service}} -> + {ok, mk_scope_component([<<"DW">>, Service, ID])} end; -extract_context_bit({from, ContextType, ValueName, Op}, LimitContext) -> - lim_context:get_from_context(ContextType, ValueName, Op, LimitContext). +extract_context_bit({from, ValueName}, ContextType, LimitContext) -> + lim_context:get_value(ContextType, ValueName, LimitContext). + +mk_scope_component(Fragments) -> + lim_string:join($/, Fragments). %%% Machinery callbacks @@ -640,6 +642,9 @@ apply_event_({created, Config}, undefined) -> -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). +-include_lib("limiter_proto/include/limproto_context_payproc_thrift.hrl"). +-include_lib("damsel/include/dmsl_domain_thrift.hrl"). + -spec test() -> _. -spec check_sign_prefix_test() -> _. @@ -782,32 +787,42 @@ check_calculate_year_shard_id_test() -> ?assertEqual(<<"past/year/1">>, calculate_calendar_shard_id(year, <<"2000-01-01T00:00:00Z">>, StartedAt2, 1)), ?assertEqual(<<"future/year/0">>, calculate_calendar_shard_id(year, <<"2001-01-01T00:00:00Z">>, StartedAt2, 1)). --define(LIMIT_CONTEXT, #{ - context => #{ - payment_processing => #{ - op => invoice, - invoice => #{ - owner_id => <<"OWNER">>, - shop_id => <<"SHLOP">> - } +-define(PAYPROC_CTX_INVOICE(Invoice), #limiter_LimitContext{ + payment_processing = #context_payproc_Context{ + op = {invoice, #context_payproc_OperationInvoice{}}, + invoice = #context_payproc_Invoice{ + invoice = Invoice } } }). +-define(INVOICE(OwnerID, ShopID), #domain_Invoice{ + id = <<"ID">>, + owner_id = OwnerID, + shop_id = ShopID, + created_at = <<"2000-02-02T12:12:12Z">>, + status = {unpaid, #domain_InvoiceUnpaid{}}, + details = #domain_InvoiceDetails{product = <<>>}, + due = <<"2222-02-02T12:12:12Z">>, + cost = #domain_Cash{amount = 42, currency = #domain_CurrencyRef{symbolic_code = <<"CNY">>}} +}). + -spec global_scope_empty_prefix_test() -> _. global_scope_empty_prefix_test() -> - ?assertEqual(<<>>, mk_scope_prefix_impl(ordsets:new(), ?LIMIT_CONTEXT)). + Context = #{context => ?PAYPROC_CTX_INVOICE(?INVOICE(<<"OWNER">>, <<"SHOP">>))}, + ?assertEqual(<<>>, mk_scope_prefix_impl(ordsets:new(), payment_processing, Context)). -spec preserve_scope_prefix_order_test_() -> [_TestGen]. preserve_scope_prefix_order_test_() -> + Context = #{context => ?PAYPROC_CTX_INVOICE(?INVOICE(<<"OWNER">>, <<"SHLOP">>))}, [ ?_assertEqual( <<"/OWNER/SHLOP">>, - mk_scope_prefix_impl(ordsets:from_list([shop, party]), ?LIMIT_CONTEXT) + mk_scope_prefix_impl(ordsets:from_list([shop, party]), payment_processing, Context) ), ?_assertEqual( <<"/OWNER/SHLOP">>, - mk_scope_prefix_impl(ordsets:from_list([shop]), ?LIMIT_CONTEXT) + mk_scope_prefix_impl(ordsets:from_list([shop]), payment_processing, Context) ) ]. diff --git a/apps/limiter/src/lim_config_machinery_schema.erl b/apps/limiter/src/lim_config_machinery_schema.erl index b18ec3ad..0ef3c9fb 100644 --- a/apps/limiter/src/lim_config_machinery_schema.erl +++ b/apps/limiter/src/lim_config_machinery_schema.erl @@ -70,13 +70,13 @@ unmarshal(T, V, C) when -spec marshal_event(machinery_mg_schema:version(), event(), context()) -> {machinery_msgpack:t(), context()}. marshal_event(1, TimestampedChange, Context) -> ThriftChange = lim_config_codec:marshal(timestamped_change, TimestampedChange), - Type = {struct, struct, {lim_limiter_config_thrift, 'TimestampedChange'}}, + Type = {struct, struct, {limproto_config_thrift, 'TimestampedChange'}}, {{bin, lim_proto_utils:serialize(Type, ThriftChange)}, Context}. -spec unmarshal_event(machinery_mg_schema:version(), machinery_msgpack:t(), context()) -> {event(), context()}. unmarshal_event(1, EncodedChange, Context) -> {bin, EncodedThriftChange} = EncodedChange, - Type = {struct, struct, {lim_limiter_config_thrift, 'TimestampedChange'}}, + Type = {struct, struct, {limproto_config_thrift, 'TimestampedChange'}}, ThriftChange = lim_proto_utils:deserialize(Type, EncodedThriftChange), {lim_config_codec:unmarshal(timestamped_change, ThriftChange), Context}. @@ -104,7 +104,7 @@ marshal_unmarshal_created_test() -> scope => ordsets:from_list([party]), description => <<"description">> }}, - Context = #{machine_ref => ID, machine_ns => config}, + Context = #{machine_id => ID, machine_ns => config}, Event = {ev, lim_time:machinery_now(), Created}, {Marshaled, _} = marshal_event(1, Event, Context), {Unmarshaled, _} = unmarshal_event(1, Marshaled, Context), diff --git a/apps/limiter/src/lim_configurator.erl b/apps/limiter/src/lim_configurator.erl index b5b09cda..f23c862c 100644 --- a/apps/limiter/src/lim_configurator.erl +++ b/apps/limiter/src/lim_configurator.erl @@ -1,6 +1,7 @@ -module(lim_configurator). --include_lib("limiter_proto/include/lim_configurator_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_configurator_thrift.hrl"). +-include_lib("damsel/include/dmsl_base_thrift.hrl"). %% Woody handler @@ -23,7 +24,7 @@ handle_function(Fn, Args, WoodyCtx, Opts) -> -spec handle_function_(woody:func(), woody:args(), lim_context:t(), woody:options()) -> {ok, woody:result()}. handle_function_( 'CreateLegacy', - {#limiter_configurator_LimitCreateParams{ + {#configurator_LimitCreateParams{ id = ID, name = Name, description = Description, @@ -51,7 +52,7 @@ handle_function_( {error, {name, notfound}} -> woody_error:raise( business, - #limiter_configurator_LimitConfigNameNotFound{} + #configurator_LimitConfigNameNotFound{} ) end; handle_function_('Create', {Params}, LimitContext, _Opts) -> @@ -70,7 +71,7 @@ handle_function_('Get', {LimitID}, LimitContext, _Opts) -> {ok, LimitConfig} -> {ok, lim_config_codec:marshal_config(LimitConfig)}; {error, notfound} -> - woody_error:raise(business, #limiter_configurator_LimitConfigNotFound{}) + woody_error:raise(business, #configurator_LimitConfigNotFound{}) end. map_type({turnover, _}) -> @@ -78,7 +79,7 @@ map_type({turnover, _}) -> map_type(_) -> woody_error:raise( business, - #'InvalidRequest'{errors = [<<"Config type not found.">>]} + #base_InvalidRequest{errors = [<<"Config type not found.">>]} ). mk_limit_config(<<"ShopDayTurnover">>) -> diff --git a/apps/limiter/src/lim_context.erl b/apps/limiter/src/lim_context.erl index f2bca88c..de267afa 100644 --- a/apps/limiter/src/lim_context.erl +++ b/apps/limiter/src/lim_context.erl @@ -1,12 +1,11 @@ -module(lim_context). --include_lib("limiter_proto/include/lim_limiter_context_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_limiter_thrift.hrl"). -export([create/1]). -export([woody_context/1]). -export([get_operation/2]). --export([get_from_context/3]). --export([get_from_context/4]). +-export([get_value/3]). -export([set_context/2]). -export([set_clock/2]). @@ -14,107 +13,26 @@ -export([clock/1]). -type woody_context() :: woody_context:ctx(). --type timestamp() :: binary(). --type thrift_context() :: lim_limiter_thrift:'LimitContext'(). --type clock() :: lim_limiter_thrift:'Clock'(). --type id() :: binary(). --type token() :: binary(). --type exp_date() :: binary(). --type cash() :: lim_body:cash(). +-type limit_context() :: limproto_limiter_thrift:'LimitContext'(). +-type clock() :: limproto_limiter_thrift:'Clock'(). -type t() :: #{ woody_context => woody_context(), - context => context(), + context => limit_context(), clock => clock() }. --type context_type() :: payment_processing. --type context_operation() :: payment_processing_operation(). - --type context() :: #{ - payment_processing => payment_processing_context() -}. - --type payment_processing_context() :: #{ - op := payment_processing_operation(), - invoice => payment_processing_invoice() -}. - --type payment_processing_operation() :: - invoice - | invoice_adjustment - | invoice_payment - | invoice_payment_adjustment - | invoice_payment_refund - | invoice_payment_chargeback. - --type payment_processing_invoice() :: #{ - id => id(), - owner_id => id(), - shop_id => id(), - cost => cash(), - created_at => timestamp(), - adjustment => payment_processing_adjustment(), - payment => payment_processing_payment() -}. - --type payment_processing_adjustment() :: #{ - id => id() -}. - --type payment_processing_payment() :: #{ - id => id(), - owner_id => id(), - shop_id => id(), - cost => cash(), - capture_cost => cash(), - created_at => timestamp(), - flow => instant | hold, - payer => payment_processing_payer(), - adjustment => payment_processing_payment_adjustment(), - refund => payment_processing_payment_refund(), - chargeback => payment_processing_payment_chargeback() -}. - --type payment_processing_payer() :: - {payment_resource, payment_processing_payer_data()} - | {customer, payment_processing_payer_data()} - | {recurrent, payment_processing_payer_data()}. - --type payment_processing_payer_data() :: #{ - payment_tool => payment_processing_payment_tool() -}. - --type payment_processing_payment_tool() :: {bank_card, payment_processing_bank_card()}. - --type payment_processing_bank_card() :: #{ - token => token(), - exp_date => exp_date() -}. - --type payment_processing_payment_adjustment() :: #{ - id => id(), - created_at => timestamp() -}. - --type payment_processing_payment_refund() :: #{ - id => id(), - cost => cash(), - created_at => timestamp() -}. - --type payment_processing_payment_chargeback() :: #{ - id => id(), - levy => cash(), - body => cash(), - created_at => timestamp() -}. +-type context_type() :: payment_processing | withdrawal_processing. +-type context_inner() :: lim_payproc_context:context() | lim_wthdproc_context:context(). +-type context_operation() :: lim_payproc_context:operation() | lim_wthdproc_context:operation(). -export_type([t/0]). --export_type([context/0]). -export_type([context_type/0]). -export_type([context_operation/0]). +-callback get_operation(context_inner()) -> {ok, context_operation()} | {error, notfound}. +-callback get_value(_Name :: atom(), context_inner()) -> {ok, term()} | {error, notfound | {unsupported, _}}. + -spec create(woody_context()) -> t(). create(WoodyContext) -> #{woody_context => WoodyContext}. @@ -129,83 +47,33 @@ clock(#{clock := Clock}) -> clock(_) -> {error, notfound}. --spec set_context(thrift_context(), t()) -> t(). +-spec set_context(limit_context(), t()) -> t(). set_context(Context, LimContext) -> - LimContext#{context => unmarshal_context(Context)}. + LimContext#{context => Context}. -spec set_clock(clock(), t()) -> t(). set_clock(Clock, LimContext) -> LimContext#{clock => Clock}. --spec get_operation(context_type(), t()) -> {ok, atom()} | {error, notfound}. -get_operation(Type, #{context := Context}) -> - case maps:get(Type, Context, undefined) of - undefined -> - {error, notfound}; - #{op := Operation} -> - {ok, Operation} - end. - --spec get_from_context(context_type(), atom(), t()) -> {ok, term()} | {error, notfound}. -get_from_context(payment_processing, ValueName, LimContext = #{context := Context}) -> - case maps:get(payment_processing, Context, undefined) of - undefined -> - {error, notfound}; - #{op := Operation} -> - get_from_context(payment_processing, ValueName, Operation, LimContext) - end; -get_from_context(_, _ValueName, _LimContext) -> - {error, notfound}. - --spec get_from_context(context_type(), atom(), context_operation(), t()) -> {ok, term()} | {error, notfound}. -get_from_context(payment_processing, ValueName, Op, #{context := #{payment_processing := Context}}) -> - case get_payment_processing_operation_context(Op, Context) of - {ok, OperationContext} -> - case maps:get(ValueName, OperationContext, undefined) of - undefined -> - {error, notfound}; - Value -> - {ok, Value} - end; - Error -> - Error - end; -get_from_context(_, _ValueName, _Op, _LimContext) -> - {error, notfound}. - -get_payment_processing_operation_context(invoice, #{invoice := Invoice}) -> - {ok, Invoice}; -get_payment_processing_operation_context(invoice_adjustment, #{invoice := #{adjustment := Adjustment}}) -> - {ok, Adjustment}; -get_payment_processing_operation_context(invoice_payment, #{invoice := #{payment := Payment}}) -> - {ok, Payment}; -get_payment_processing_operation_context( - invoice_payment_adjustment, - #{invoice := #{payment := #{adjustment := Adjustment}}} +-spec get_operation(context_type(), t()) -> + {ok, context_operation()} | {error, notfound}. +get_operation(Type, Context) -> + {Mod, OperationContext} = get_operation_context(Type, Context), + Mod:get_operation(OperationContext). + +-spec get_value(context_type(), atom(), t()) -> + {ok, term()} | {error, notfound | {unsupported, _}}. +get_value(Type, ValueName, Context) -> + {Mod, OperationContext} = get_operation_context(Type, Context), + Mod:get_value(ValueName, OperationContext). + +get_operation_context( + payment_processing, + #{context := #limiter_LimitContext{payment_processing = PayprocContext}} ) -> - {ok, Adjustment}; -get_payment_processing_operation_context( - invoice_payment_refund, - #{invoice := #{payment := #{refund := Refund}}} + {lim_payproc_context, PayprocContext}; +get_operation_context( + withdrawal_processing, + #{context := #limiter_LimitContext{withdrawal_processing = WithdrawalContext}} ) -> - {ok, Refund}; -get_payment_processing_operation_context( - invoice_payment_chargeback, - #{invoice := #{payment := #{chargeback := Chargeback}}} -) -> - {ok, Chargeback}; -get_payment_processing_operation_context(_, _) -> - {error, notfound}. - -%% - -unmarshal_context(#limiter_context_LimitContext{limiter_payment_processing = PaymentProcessing}) when - PaymentProcessing =/= undefined --> - #{payment_processing => lim_limiter_context:unmarshal(PaymentProcessing)}; -unmarshal_context(#limiter_context_LimitContext{payment_processing = PaymentProcessing}) when - PaymentProcessing =/= undefined --> - #{payment_processing => lim_payproc_context:unmarshal(PaymentProcessing)}; -unmarshal_context(_) -> - #{}. + {lim_wthdproc_context, WithdrawalContext}. diff --git a/apps/limiter/src/lim_handler.erl b/apps/limiter/src/lim_handler.erl index 6e0d7b96..11e26d37 100644 --- a/apps/limiter/src/lim_handler.erl +++ b/apps/limiter/src/lim_handler.erl @@ -1,6 +1,8 @@ -module(lim_handler). --include_lib("limiter_proto/include/lim_limiter_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_limiter_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_base_thrift.hrl"). +-include_lib("damsel/include/dmsl_base_thrift.hrl"). %% Woody handler @@ -84,7 +86,7 @@ handle_get_error(Error) -> -spec handle_hold_error(_) -> no_return(). handle_hold_error({_, {invalid_request, Errors}}) -> - woody_error:raise(business, #'InvalidRequest'{errors = Errors}); + woody_error:raise(business, #base_InvalidRequest{errors = Errors}); handle_hold_error(Error) -> handle_default_error(Error). @@ -92,13 +94,13 @@ handle_hold_error(Error) -> handle_commit_error({_, {forbidden_operation_amount, Error}}) -> handle_forbidden_operation_amount_error(Error); handle_commit_error({_, {invalid_request, Errors}}) -> - woody_error:raise(business, #'InvalidRequest'{errors = Errors}); + woody_error:raise(business, #base_InvalidRequest{errors = Errors}); handle_commit_error(Error) -> handle_default_error(Error). -spec handle_rollback_error(_) -> no_return(). handle_rollback_error({_, {invalid_request, Errors}}) -> - woody_error:raise(business, #'InvalidRequest'{errors = Errors}); + woody_error:raise(business, #base_InvalidRequest{errors = Errors}); handle_rollback_error(Error) -> handle_default_error(Error). @@ -122,7 +124,7 @@ handle_forbidden_operation_amount_error(#{ positive -> woody_error:raise(business, #limiter_ForbiddenOperationAmount{ amount = Partial, - allowed_range = #limiter_base_AmountRange{ + allowed_range = #base_AmountRange{ upper = {inclusive, Full}, lower = {inclusive, 0} } @@ -130,7 +132,7 @@ handle_forbidden_operation_amount_error(#{ negative -> woody_error:raise(business, #limiter_ForbiddenOperationAmount{ amount = Partial, - allowed_range = #limiter_base_AmountRange{ + allowed_range = #base_AmountRange{ upper = {inclusive, 0}, lower = {inclusive, Full} } diff --git a/apps/limiter/src/lim_limiter_context.erl b/apps/limiter/src/lim_limiter_context.erl deleted file mode 100644 index 9ffbcb8c..00000000 --- a/apps/limiter/src/lim_limiter_context.erl +++ /dev/null @@ -1,129 +0,0 @@ --module(lim_limiter_context). - --include_lib("limiter_proto/include/lim_limiter_context_thrift.hrl"). - --export([unmarshal/1]). - --type thrift_context() :: lim_limiter_context_thrift:'ContextPaymentProcessing'(). - -%% - --spec unmarshal(thrift_context()) -> lim_context:context(). -unmarshal(#limiter_context_ContextPaymentProcessing{ - op = {Operation, _}, - invoice = Invoice -}) -> - genlib_map:compact(#{ - op => Operation, - invoice => maybe_unmarshal(Invoice, fun unmarshal_payment_processing_invoice/1) - }). - -unmarshal_payment_processing_invoice(#limiter_context_Invoice{ - id = ID, - owner_id = OwnerID, - shop_id = ShopID, - cost = Cost, - created_at = CreatedAt, - effective_payment = Payment, - effective_adjustment = Adjustment -}) -> - genlib_map:compact(#{ - id => maybe_unmarshal(ID, fun unmarshal_string/1), - owner_id => maybe_unmarshal(OwnerID, fun unmarshal_string/1), - shop_id => maybe_unmarshal(ShopID, fun unmarshal_string/1), - cost => maybe_unmarshal(Cost, fun unmarshal_cash/1), - created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1), - adjustment => maybe_unmarshal( - Adjustment, - fun unmarshal_payment_processing_invoice_adjustment/1 - ), - payment => maybe_unmarshal(Payment, fun unmarshal_payment_processing_invoice_payment/1) - }). - -unmarshal_payment_processing_invoice_adjustment(#limiter_context_InvoiceAdjustment{id = ID}) -> - genlib_map:compact(#{ - id => maybe_unmarshal(ID, fun unmarshal_string/1) - }). - -unmarshal_payment_processing_invoice_payment(#limiter_context_InvoicePayment{ - id = ID, - owner_id = OwnerID, - shop_id = ShopID, - cost = Cost, - capture_cost = CaptureCost, - created_at = CreatedAt, - flow = Flow, - payer = Payer, - effective_adjustment = Adjustment, - effective_refund = EffectiveRefund, - effective_chargeback = EffectiveChargeback -}) -> - genlib_map:compact(#{ - id => maybe_unmarshal(ID, fun unmarshal_string/1), - owner_id => maybe_unmarshal(OwnerID, fun unmarshal_string/1), - shop_id => maybe_unmarshal(ShopID, fun unmarshal_string/1), - cost => maybe_unmarshal(Cost, fun unmarshal_cash/1), - capture_cost => maybe_unmarshal(CaptureCost, fun unmarshal_cash/1), - created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1), - flow => maybe_unmarshal(Flow, fun unmarshal_payment_processing_invoice_payment_flow/1), - payer => maybe_unmarshal(Payer, fun unmarshal_payment_processing_invoice_payment_payer/1), - adjustment => maybe_unmarshal( - Adjustment, - fun unmarshal_payment_processing_invoice_payment_adjustment/1 - ), - refund => maybe_unmarshal(EffectiveRefund, fun unmarshal_payment_processing_invoice_payment_refund/1), - chargeback => maybe_unmarshal( - EffectiveChargeback, - fun unmarshal_payment_processing_invoice_payment_chargeback/1 - ) - }). - -unmarshal_payment_processing_invoice_payment_flow({Flow, _}) -> - Flow. - -unmarshal_payment_processing_invoice_payment_payer({Payer, _}) -> - {Payer, #{}}. - -unmarshal_payment_processing_invoice_payment_adjustment(#limiter_context_InvoicePaymentAdjustment{ - id = ID, - created_at = CreatedAt -}) -> - genlib_map:compact(#{ - id => maybe_unmarshal(ID, fun unmarshal_string/1), - created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1) - }). - -unmarshal_payment_processing_invoice_payment_refund(#limiter_context_InvoicePaymentRefund{ - id = ID, - cost = Cost, - created_at = CreatedAt -}) -> - genlib_map:compact(#{ - id => maybe_unmarshal(ID, fun unmarshal_string/1), - cost => maybe_unmarshal(Cost, fun unmarshal_cash/1), - created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1) - }). - -unmarshal_payment_processing_invoice_payment_chargeback(#limiter_context_InvoicePaymentChargeback{ - id = ID, - levy = Levy, - body = Body, - created_at = CreatedAt -}) -> - genlib_map:compact(#{ - id => maybe_unmarshal(ID, fun unmarshal_string/1), - levy => maybe_unmarshal(Levy, fun unmarshal_cash/1), - body => maybe_unmarshal(Body, fun unmarshal_cash/1), - created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1) - }). - -unmarshal_cash(#domain_Cash{amount = Amount, currency = #domain_CurrencyRef{symbolic_code = Currency}}) -> - #{amount => Amount, currency => Currency}. - -unmarshal_string(Value) -> - Value. - -maybe_unmarshal(undefined, _) -> - undefined; -maybe_unmarshal(Value, UnmarshalFun) -> - UnmarshalFun(Value). diff --git a/apps/limiter/src/lim_payproc_context.erl b/apps/limiter/src/lim_payproc_context.erl index 18cb2573..07664658 100644 --- a/apps/limiter/src/lim_payproc_context.erl +++ b/apps/limiter/src/lim_payproc_context.erl @@ -1,213 +1,247 @@ -module(lim_payproc_context). --include_lib("limiter_proto/include/lim_limiter_payproc_context_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_context_payproc_thrift.hrl"). +-include_lib("damsel/include/dmsl_domain_thrift.hrl"). --export([unmarshal/1]). +-behaviour(lim_context). +-export([get_operation/1]). +-export([get_value/2]). --type thrift_context() :: lim_limiter_payproc_context_thrift:'Context'(). +-type context() :: limproto_context_payproc_thrift:'Context'(). + +-type operation() :: + invoice + | invoice_adjustment + | invoice_payment + | invoice_payment_adjustment + | invoice_payment_refund + | invoice_payment_chargeback. + +-export_type([operation/0]). +-export_type([context/0]). %% --spec unmarshal(thrift_context()) -> lim_context:context(). -unmarshal(#limiter_context_payproc_Context{ - op = {Operation, _}, - invoice = Invoice -}) -> - genlib_map:compact(#{ - op => Operation, - invoice => maybe_unmarshal(Invoice, fun unmarshal_payment_processing_invoice/1) - }). - -unmarshal_payment_processing_invoice(#limiter_context_payproc_Invoice{ - invoice = #domain_Invoice{ - id = ID, - owner_id = OwnerID, - shop_id = ShopID, - cost = Cost, - created_at = CreatedAt - }, - payment = Payment, - adjustment = Adjustment -}) -> - genlib_map:compact(#{ - id => maybe_unmarshal(ID, fun unmarshal_string/1), - owner_id => maybe_unmarshal(OwnerID, fun unmarshal_string/1), - shop_id => maybe_unmarshal(ShopID, fun unmarshal_string/1), - cost => maybe_unmarshal(Cost, fun unmarshal_cash/1), - created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1), - adjustment => maybe_unmarshal( - Adjustment, - fun unmarshal_payment_processing_invoice_adjustment/1 - ), - payment => maybe_unmarshal(Payment, fun unmarshal_payment_processing_invoice_payment/1) - }). - -unmarshal_payment_processing_invoice_adjustment(#domain_InvoiceAdjustment{id = ID}) -> - #{id => unmarshal_string(ID)}. - -unmarshal_payment_processing_invoice_payment(#limiter_context_payproc_InvoicePayment{ - payment = #domain_InvoicePayment{ - id = ID, - owner_id = OwnerID, - shop_id = ShopID, - cost = Cost, - created_at = CreatedAt, - flow = Flow, - status = Status, - payer = Payer - }, - adjustment = Adjustment, - refund = EffectiveRefund, - chargeback = EffectiveChargeback -}) -> - genlib_map:compact(#{ - id => maybe_unmarshal(ID, fun unmarshal_string/1), - owner_id => maybe_unmarshal(OwnerID, fun unmarshal_string/1), - shop_id => maybe_unmarshal(ShopID, fun unmarshal_string/1), - cost => maybe_unmarshal(Cost, fun unmarshal_cash/1), - capture_cost => get_capture_cost_from_status(Status), - created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1), - flow => maybe_unmarshal(Flow, fun unmarshal_payment_processing_invoice_payment_flow/1), - payer => maybe_unmarshal(Payer, fun unmarshal_payment_processing_invoice_payment_payer/1), - adjustment => maybe_unmarshal( - Adjustment, - fun unmarshal_payment_processing_invoice_payment_adjustment/1 - ), - refund => maybe_unmarshal(EffectiveRefund, fun unmarshal_payment_processing_invoice_payment_refund/1), - chargeback => maybe_unmarshal( - EffectiveChargeback, - fun unmarshal_payment_processing_invoice_payment_chargeback/1 - ) - }). - -get_capture_cost_from_status({captured, #domain_InvoicePaymentCaptured{cost = Cost}}) -> - maybe_unmarshal(Cost, fun unmarshal_cash/1); -get_capture_cost_from_status(_) -> - undefined. - -unmarshal_payment_processing_invoice_payment_flow({Flow, _}) -> - Flow. - -unmarshal_payment_processing_invoice_payment_payer({Payer, Data}) -> - {Payer, unmarshal_payment_processing_invoice_payment_payer_data(Data)}. - -unmarshal_payment_processing_invoice_payment_payer_data(#domain_PaymentResourcePayer{ - resource = #domain_DisposablePaymentResource{payment_tool = PaymentTool} -}) -> - genlib_map:compact(#{ - payment_tool => maybe_unmarshal(PaymentTool, fun unmarshal_payment_processing_payment_tool/1) - }); -unmarshal_payment_processing_invoice_payment_payer_data(#domain_CustomerPayer{payment_tool = PaymentTool}) -> - genlib_map:compact(#{ - payment_tool => maybe_unmarshal(PaymentTool, fun unmarshal_payment_processing_payment_tool/1) - }); -unmarshal_payment_processing_invoice_payment_payer_data(#domain_RecurrentPayer{payment_tool = PaymentTool}) -> - genlib_map:compact(#{ - payment_tool => maybe_unmarshal(PaymentTool, fun unmarshal_payment_processing_payment_tool/1) - }). - -unmarshal_payment_processing_payment_tool({bank_card, #domain_BankCard{token = Token, exp_date = ExpDate}}) -> - {bank_card, #{ - token => Token, - exp_date => maybe_unmarshal(ExpDate, fun unmarshal_payment_processing_bank_card_exp_data/1) - }}; -unmarshal_payment_processing_payment_tool(_) -> - undefined. - -unmarshal_payment_processing_bank_card_exp_data(#domain_BankCardExpDate{month = Month, year = Year}) -> - BinaryMonth = integer_to_binary(Month), - BinaryYear = integer_to_binary(Year), - <>. - -unmarshal_payment_processing_invoice_payment_adjustment(#domain_InvoicePaymentAdjustment{ - id = ID, - created_at = CreatedAt -}) -> - genlib_map:compact(#{ - id => maybe_unmarshal(ID, fun unmarshal_string/1), - created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1) - }). - -unmarshal_payment_processing_invoice_payment_refund(#domain_InvoicePaymentRefund{ - id = ID, - cash = Cost, - created_at = CreatedAt -}) -> - genlib_map:compact(#{ - id => maybe_unmarshal(ID, fun unmarshal_string/1), - cost => maybe_unmarshal(Cost, fun unmarshal_cash/1), - created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1) - }). - -unmarshal_payment_processing_invoice_payment_chargeback(#domain_InvoicePaymentChargeback{ - id = ID, - levy = Levy, - body = Body, - created_at = CreatedAt -}) -> - genlib_map:compact(#{ - id => maybe_unmarshal(ID, fun unmarshal_string/1), - levy => maybe_unmarshal(Levy, fun unmarshal_cash/1), - body => maybe_unmarshal(Body, fun unmarshal_cash/1), - created_at => maybe_unmarshal(CreatedAt, fun unmarshal_string/1) - }). - -unmarshal_cash(#domain_Cash{amount = Amount, currency = #domain_CurrencyRef{symbolic_code = Currency}}) -> - #{amount => Amount, currency => Currency}. - -unmarshal_string(Value) -> - Value. - -maybe_unmarshal(undefined, _) -> - undefined; -maybe_unmarshal(Value, UnmarshalFun) -> - UnmarshalFun(Value). +-spec get_operation(context()) -> {ok, operation()} | {error, notfound}. +get_operation(#context_payproc_Context{op = {Operation, _}}) -> + {ok, Operation}; +get_operation(#context_payproc_Context{op = undefined}) -> + {error, notfound}. + +-spec get_value(atom(), context()) -> {ok, term()} | {error, notfound | {unsupported, _}}. +get_value(ValueName, Context) -> + case get_operation(Context) of + {ok, Operation} -> + get_value(ValueName, Operation, Context); + {error, _} = Error -> + Error + end. + +get_value(owner_id, _Operation, Context) -> + get_owner_id(Context); +get_value(shop_id, _Operation, Context) -> + get_shop_id(Context); +get_value(created_at, Operation, Context) -> + get_created_at(Operation, Context); +get_value(cost, Operation, Context) -> + get_cost(Operation, Context); +get_value(capture_cost, Operation, Context) -> + get_capture_cost(Operation, Context); +get_value(payment_tool, Operation, Context) -> + get_payment_tool(Operation, Context); +get_value(ValueName, _Operation, _Context) -> + {error, {unsupported, ValueName}}. + +%% + +-define(INVOICE(V), #context_payproc_Context{ + invoice = #context_payproc_Invoice{ + invoice = V = #domain_Invoice{} + } +}). + +-define(INVOICE_ADJUSTMENT(V), #context_payproc_Context{ + invoice = #context_payproc_Invoice{ + adjustment = V = #domain_InvoiceAdjustment{} + } +}). + +-define(INVOICE_PAYMENT(V), #context_payproc_Context{ + invoice = #context_payproc_Invoice{ + payment = #context_payproc_InvoicePayment{payment = V = #domain_InvoicePayment{}} + } +}). + +-define(INVOICE_PAYMENT_ADJUSTMENT(V), #context_payproc_Context{ + invoice = #context_payproc_Invoice{ + payment = #context_payproc_InvoicePayment{adjustment = V = #domain_InvoicePaymentAdjustment{}} + } +}). + +-define(INVOICE_PAYMENT_REFUND(V), #context_payproc_Context{ + invoice = #context_payproc_Invoice{ + payment = #context_payproc_InvoicePayment{refund = V = #domain_InvoicePaymentRefund{}} + } +}). + +-define(INVOICE_PAYMENT_CHARGEBACK(V), #context_payproc_Context{ + invoice = #context_payproc_Invoice{ + payment = #context_payproc_InvoicePayment{chargeback = V = #domain_InvoicePaymentChargeback{}} + } +}). + +get_owner_id(?INVOICE(Invoice)) -> + {ok, Invoice#domain_Invoice.owner_id}; +get_owner_id(_) -> + {error, notfound}. + +get_shop_id(?INVOICE(Invoice)) -> + {ok, Invoice#domain_Invoice.shop_id}; +get_shop_id(_) -> + {error, notfound}. + +get_created_at(invoice, ?INVOICE(Invoice)) -> + {ok, Invoice#domain_Invoice.created_at}; +get_created_at(invoice_adjustment, ?INVOICE_ADJUSTMENT(Adjustment)) -> + {ok, Adjustment#domain_InvoiceAdjustment.created_at}; +get_created_at(invoice_payment, ?INVOICE_PAYMENT(Payment)) -> + {ok, Payment#domain_InvoicePayment.created_at}; +get_created_at(invoice_payment_adjustment, ?INVOICE_PAYMENT_ADJUSTMENT(Adjustment)) -> + {ok, Adjustment#domain_InvoicePaymentAdjustment.created_at}; +get_created_at(invoice_payment_refund, ?INVOICE_PAYMENT_REFUND(Refund)) -> + {ok, Refund#domain_InvoicePaymentRefund.created_at}; +get_created_at(invoice_payment_chargeback, ?INVOICE_PAYMENT_CHARGEBACK(Chargeback)) -> + {ok, Chargeback#domain_InvoicePaymentChargeback.created_at}; +get_created_at(_, _CtxInvoice) -> + {error, notfound}. + +get_cost(invoice, ?INVOICE(Invoice)) -> + lim_payproc_utils:cash(Invoice#domain_Invoice.cost); +get_cost(invoice_payment, ?INVOICE_PAYMENT(Payment)) -> + lim_payproc_utils:cash(Payment#domain_InvoicePayment.cost); +get_cost(invoice_payment_refund, ?INVOICE_PAYMENT_REFUND(Refund)) -> + lim_payproc_utils:cash(Refund#domain_InvoicePaymentRefund.cash); +get_cost(invoice_payment_chargeback, ?INVOICE_PAYMENT_CHARGEBACK(Chargeback)) -> + lim_payproc_utils:cash(Chargeback#domain_InvoicePaymentChargeback.body); +get_cost(_, _CtxInvoice) -> + {error, notfound}. + +get_capture_cost(invoice_payment, ?INVOICE_PAYMENT(Payment)) -> + get_capture_cost(Payment#domain_InvoicePayment.status); +get_capture_cost(_, _CtxInvoice) -> + {error, notfound}. + +get_capture_cost({captured, #domain_InvoicePaymentCaptured{cost = Cost}}) when Cost /= undefined -> + lim_payproc_utils:cash(Cost); +get_capture_cost({_Status, _}) -> + {error, notfound}. + +get_payment_tool(Operation, ?INVOICE_PAYMENT(Payment)) when + Operation == invoice_payment; + Operation == invoice_payment_adjustment; + Operation == invoice_payment_refund; + Operation == invoice_payment_chargeback +-> + {_Type, Payer} = Payment#domain_InvoicePayment.payer, + get_payer_payment_tool(Payer); +get_payment_tool(_, _CtxInvoice) -> + {error, notfound}. + +get_payer_payment_tool(#domain_PaymentResourcePayer{resource = #domain_DisposablePaymentResource{payment_tool = PT}}) -> + lim_payproc_utils:payment_tool(PT); +get_payer_payment_tool(#domain_CustomerPayer{payment_tool = PT}) -> + lim_payproc_utils:payment_tool(PT); +get_payer_payment_tool(#domain_RecurrentPayer{payment_tool = PT}) -> + lim_payproc_utils:payment_tool(PT). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). -spec test() -> _. --spec marshal_unmarshal_created_test() -> _. -marshal_unmarshal_created_test() -> - ExpDate = #domain_BankCardExpDate{month = 2, year = 2022}, +-define(PAYMENT_W_PAYER(Payer), #domain_InvoicePayment{ + id = <<"ID">>, + created_at = <<"2000-02-02T12:12:12Z">>, + status = {pending, #domain_InvoicePaymentPending{}}, + cost = #domain_Cash{ + amount = 42, + currency = #domain_CurrencyRef{symbolic_code = <<"CNY">>} + }, + domain_revision = 42, + flow = {instant, #domain_InvoicePaymentFlowInstant{}}, + payer = Payer +}). + +-define(CONTEXT_PAYMENT(Payment), #context_payproc_Context{ + op = {invoice_payment, #context_payproc_OperationInvoicePayment{}}, + invoice = #context_payproc_Invoice{ + payment = #context_payproc_InvoicePayment{ + payment = Payment + } + } +}). + +-spec get_payment_tool_test_() -> [_TestGen]. +get_payment_tool_test_() -> PaymentTool = {bank_card, #domain_BankCard{ token = <<"Token">>, - bin = <<"bin">>, - exp_date = ExpDate, + bin = <<"654321">>, + exp_date = #domain_BankCardExpDate{month = 2, year = 2022}, last_digits = <<"1234">> }}, - Data0 = #domain_PaymentResourcePayer{ - resource = #domain_DisposablePaymentResource{payment_tool = PaymentTool}, - contact_info = #domain_ContactInfo{} - }, - #{payment_tool := _} = unmarshal_payment_processing_invoice_payment_payer_data(Data0), - Data1 = #domain_CustomerPayer{ - customer_id = <<"customer_id">>, - customer_binding_id = <<"customer_binding_id">>, - rec_payment_tool_id = <<"rec_payment_tool_id">>, - payment_tool = PaymentTool, - contact_info = #domain_ContactInfo{} - }, - #{payment_tool := _} = unmarshal_payment_processing_invoice_payment_payer_data(Data1), - Data2 = #domain_RecurrentPayer{ - payment_tool = PaymentTool, - recurrent_parent = #domain_RecurrentParentPayment{ - invoice_id = <<"invoice_id">>, - payment_id = <<"payment_id">> - }, - contact_info = #domain_ContactInfo{} - }, - #{payment_tool := _} = unmarshal_payment_processing_invoice_payment_payer_data(Data2), - Data3 = #domain_RecurrentPayer{ - payment_tool = {payment_terminal, #domain_PaymentTerminal{}}, - recurrent_parent = #domain_RecurrentParentPayment{ - invoice_id = <<"invoice_id">>, - payment_id = <<"payment_id">> - }, - contact_info = #domain_ContactInfo{} - }, - #{} = unmarshal_payment_processing_invoice_payment_payer_data(Data3). + PaymentResourcePayer = + {payment_resource, #domain_PaymentResourcePayer{ + resource = #domain_DisposablePaymentResource{payment_tool = PaymentTool}, + contact_info = #domain_ContactInfo{} + }}, + CustomerPayer = + {customer, #domain_CustomerPayer{ + customer_id = <<"customer_id">>, + customer_binding_id = <<"customer_binding_id">>, + rec_payment_tool_id = <<"rec_payment_tool_id">>, + payment_tool = PaymentTool, + contact_info = #domain_ContactInfo{} + }}, + RecurrentPayer = + {recurrent, #domain_RecurrentPayer{ + payment_tool = PaymentTool, + recurrent_parent = #domain_RecurrentParentPayment{ + invoice_id = <<"invoice_id">>, + payment_id = <<"payment_id">> + }, + contact_info = #domain_ContactInfo{} + }}, + ExpectedValue = {bank_card, #{token => <<"Token">>, exp_date => {2, 2022}}}, + [ + ?_assertEqual( + {ok, ExpectedValue}, + get_value(payment_tool, ?CONTEXT_PAYMENT(?PAYMENT_W_PAYER(PaymentResourcePayer))) + ), + ?_assertEqual( + {ok, ExpectedValue}, + get_value(payment_tool, ?CONTEXT_PAYMENT(?PAYMENT_W_PAYER(CustomerPayer))) + ), + ?_assertEqual( + {ok, ExpectedValue}, + get_value(payment_tool, ?CONTEXT_PAYMENT(?PAYMENT_W_PAYER(RecurrentPayer))) + ) + ]. + +-spec get_payment_tool_unsupported_test_() -> _TestGen. +get_payment_tool_unsupported_test_() -> + Payer = + {recurrent, #domain_RecurrentPayer{ + payment_tool = {payment_terminal, #domain_PaymentTerminal{}}, + recurrent_parent = #domain_RecurrentParentPayment{ + invoice_id = <<"invoice_id">>, + payment_id = <<"payment_id">> + }, + contact_info = #domain_ContactInfo{} + }}, + ?_assertEqual( + {error, {unsupported, {payment_tool, payment_terminal}}}, + get_value(payment_tool, ?CONTEXT_PAYMENT(?PAYMENT_W_PAYER(Payer))) + ). -endif. diff --git a/apps/limiter/src/lim_payproc_utils.erl b/apps/limiter/src/lim_payproc_utils.erl new file mode 100644 index 00000000..7b62a575 --- /dev/null +++ b/apps/limiter/src/lim_payproc_utils.erl @@ -0,0 +1,48 @@ +-module(lim_payproc_utils). + +-include_lib("damsel/include/dmsl_domain_thrift.hrl"). + +-export([cash/1]). +-export([payment_tool/1]). + +-type cash() :: + lim_body:cash(). + +-type payment_tool() :: + {bank_card, #{ + token := binary(), + exp_date := {1..12, 2000..9999} | undefined + }} + | {digital_wallet, #{ + id := binary(), + service := binary() + }}. + +%% + +-spec cash(dmsl_domain_thrift:'Cash'()) -> + {ok, cash()}. +cash(#domain_Cash{amount = Amount, currency = #domain_CurrencyRef{symbolic_code = Currency}}) -> + {ok, #{amount => Amount, currency => Currency}}. + +-spec payment_tool(dmsl_domain_thrift:'PaymentTool'()) -> + {ok, payment_tool()} | {error, {unsupported, _}}. +payment_tool({bank_card, BC}) -> + {ok, + {bank_card, #{ + token => BC#domain_BankCard.token, + exp_date => get_bank_card_expdate(BC#domain_BankCard.exp_date) + }}}; +payment_tool({digital_wallet, DW}) -> + {ok, + {digital_wallet, #{ + id => DW#domain_DigitalWallet.id, + service => DW#domain_DigitalWallet.payment_service#domain_PaymentServiceRef.id + }}}; +payment_tool({Type, _}) -> + {error, {unsupported, {payment_tool, Type}}}. + +get_bank_card_expdate(#domain_BankCardExpDate{month = Month, year = Year}) -> + {Month, Year}; +get_bank_card_expdate(undefined) -> + undefined. diff --git a/apps/limiter/src/lim_range_codec.erl b/apps/limiter/src/lim_range_codec.erl index 8e854bd8..d6b24245 100644 --- a/apps/limiter/src/lim_range_codec.erl +++ b/apps/limiter/src/lim_range_codec.erl @@ -1,6 +1,7 @@ -module(lim_range_codec). --include_lib("limiter_proto/include/lim_limiter_range_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_range_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_timerange_thrift.hrl"). -export([marshal/2]). -export([unmarshal/2]). @@ -20,14 +21,14 @@ -spec marshal(type_name(), decoded_value()) -> encoded_value(). marshal(timestamped_change, {ev, Timestamp, Change}) -> - #limiter_range_TimestampedChange{ + #range_TimestampedChange{ change = marshal(change, Change), occured_at = marshal(timestamp, Timestamp) }; marshal(change, {created, Range}) -> - {created, #limiter_range_CreatedChange{limit_range = marshal(range, Range)}}; + {created, #range_CreatedChange{limit_range = marshal(range, Range)}}; marshal(change, {time_range_created, TimeRange}) -> - {time_range_created, #limiter_range_TimeRangeCreatedChange{time_range = marshal(time_range, TimeRange)}}; + {time_range_created, #range_TimeRangeCreatedChange{time_range = marshal(time_range, TimeRange)}}; marshal( range, Range = #{ @@ -36,7 +37,7 @@ marshal( created_at := CreatedAt } ) -> - #limiter_range_LimitRange{ + #range_LimitRange{ id = ID, type = marshal(time_range_type, Type), created_at = CreatedAt, @@ -48,7 +49,7 @@ marshal(time_range, #{ account_id_from := AccountIDFrom, account_id_to := AccountIDTo }) -> - #limiter_time_range_TimeRange{ + #timerange_TimeRange{ upper = Upper, lower = Lower, account_id_from = AccountIDFrom, @@ -57,15 +58,15 @@ marshal(time_range, #{ marshal(time_range_type, {calendar, SubType}) -> {calendar, marshal(time_range_sub_type, SubType)}; marshal(time_range_type, {interval, Interval}) -> - {interval, #limiter_time_range_TimeRangeTypeInterval{amount = Interval}}; + {interval, #timerange_TimeRangeTypeInterval{amount = Interval}}; marshal(time_range_sub_type, year) -> - {year, #limiter_time_range_TimeRangeTypeCalendarYear{}}; + {year, #timerange_TimeRangeTypeCalendarYear{}}; marshal(time_range_sub_type, month) -> - {month, #limiter_time_range_TimeRangeTypeCalendarMonth{}}; + {month, #timerange_TimeRangeTypeCalendarMonth{}}; marshal(time_range_sub_type, week) -> - {week, #limiter_time_range_TimeRangeTypeCalendarWeek{}}; + {week, #timerange_TimeRangeTypeCalendarWeek{}}; marshal(time_range_sub_type, day) -> - {day, #limiter_time_range_TimeRangeTypeCalendarDay{}}; + {day, #timerange_TimeRangeTypeCalendarDay{}}; marshal(timestamp, {DateTime, USec}) -> DateTimeinSeconds = genlib_time:daytime_to_unixtime(DateTime), {TimeinUnit, Unit} = @@ -80,14 +81,14 @@ marshal(timestamp, {DateTime, USec}) -> -spec unmarshal(type_name(), encoded_value()) -> decoded_value(). unmarshal(timestamped_change, TimestampedChange) -> - Timestamp = unmarshal(timestamp, TimestampedChange#limiter_range_TimestampedChange.occured_at), - Change = unmarshal(change, TimestampedChange#limiter_range_TimestampedChange.change), + Timestamp = unmarshal(timestamp, TimestampedChange#range_TimestampedChange.occured_at), + Change = unmarshal(change, TimestampedChange#range_TimestampedChange.change), {ev, Timestamp, Change}; -unmarshal(change, {created, #limiter_range_CreatedChange{limit_range = Range}}) -> +unmarshal(change, {created, #range_CreatedChange{limit_range = Range}}) -> {created, unmarshal(range, Range)}; -unmarshal(change, {time_range_created, #limiter_range_TimeRangeCreatedChange{time_range = Range}}) -> +unmarshal(change, {time_range_created, #range_TimeRangeCreatedChange{time_range = Range}}) -> {time_range_created, unmarshal(time_range, Range)}; -unmarshal(range, #limiter_range_LimitRange{ +unmarshal(range, #range_LimitRange{ id = ID, type = Type, created_at = CreatedAt, @@ -99,7 +100,7 @@ unmarshal(range, #limiter_range_LimitRange{ created_at => CreatedAt, currency => Currency }); -unmarshal(time_range, #limiter_time_range_TimeRange{ +unmarshal(time_range, #timerange_TimeRange{ upper = Upper, lower = Lower, account_id_from = AccountIDFrom, @@ -113,7 +114,7 @@ unmarshal(time_range, #limiter_time_range_TimeRange{ }; unmarshal(time_range_type, {calendar, SubType}) -> {calendar, unmarshal(time_range_sub_type, SubType)}; -unmarshal(time_range_type, {interval, #limiter_time_range_TimeRangeTypeInterval{amount = Interval}}) -> +unmarshal(time_range_type, {interval, #timerange_TimeRangeTypeInterval{amount = Interval}}) -> {interval, Interval}; unmarshal(time_range_sub_type, {year, _}) -> year; diff --git a/apps/limiter/src/lim_range_machinery_schema.erl b/apps/limiter/src/lim_range_machinery_schema.erl index db8d84d5..71b0413a 100644 --- a/apps/limiter/src/lim_range_machinery_schema.erl +++ b/apps/limiter/src/lim_range_machinery_schema.erl @@ -70,13 +70,13 @@ unmarshal(T, V, C) when -spec marshal_event(machinery_mg_schema:version(), event(), context()) -> {machinery_msgpack:t(), context()}. marshal_event(1, TimestampedChange, Context) -> ThriftChange = lim_range_codec:marshal(timestamped_change, TimestampedChange), - Type = {struct, struct, {lim_limiter_range_thrift, 'TimestampedChange'}}, + Type = {struct, struct, {limproto_range_thrift, 'TimestampedChange'}}, {{bin, lim_proto_utils:serialize(Type, ThriftChange)}, Context}. -spec unmarshal_event(machinery_mg_schema:version(), machinery_msgpack:t(), context()) -> {event(), context()}. unmarshal_event(1, EncodedChange, Context) -> {bin, EncodedThriftChange} = EncodedChange, - Type = {struct, struct, {lim_limiter_range_thrift, 'TimestampedChange'}}, + Type = {struct, struct, {limproto_range_thrift, 'TimestampedChange'}}, ThriftChange = lim_proto_utils:deserialize(Type, EncodedThriftChange), {lim_range_codec:unmarshal(timestamped_change, ThriftChange), Context}. @@ -98,7 +98,7 @@ marshal_unmarshal_created_test() -> created_at => <<"2000-01-01T00:00:00Z">>, currency => <<"USD">> }}, - Context = #{machine_ref => ID, machine_ns => limrange}, + Context = #{machine_id => ID, machine_ns => limrange}, Event = {ev, lim_time:machinery_now(), Created}, {Marshaled, _} = marshal_event(1, Event, Context), {Unmarshaled, _} = unmarshal_event(1, Marshaled, Context), @@ -113,7 +113,7 @@ marshal_unmarshal_time_range_created_test() -> upper => <<"2000-01-01T00:00:00Z">>, lower => <<"2000-01-01T00:00:00Z">> }}, - Context = #{machine_ref => <<"id">>, machine_ns => limrange}, + Context = #{machine_id => <<"id">>, machine_ns => limrange}, Event = {ev, lim_time:machinery_now(), TimeRangeCreated}, {Marshaled, _} = marshal_event(1, Event, Context), {Unmarshaled, _} = unmarshal_event(1, Marshaled, Context), diff --git a/apps/limiter/src/lim_rates.erl b/apps/limiter/src/lim_rates.erl index f7eb00db..5526f8bb 100644 --- a/apps/limiter/src/lim_rates.erl +++ b/apps/limiter/src/lim_rates.erl @@ -20,7 +20,7 @@ | {error, conversion_error()}. convert(#{amount := Amount, currency := Currency}, DestinationCurrency, Config, LimitContext) -> ContextType = lim_config_machine:context_type(Config), - {ok, Timestamp} = lim_context:get_from_context(ContextType, created_at, LimitContext), + {ok, Timestamp} = lim_context:get_value(ContextType, created_at, LimitContext), Request = #rate_ConversionRequest{ source = Currency, destination = DestinationCurrency, diff --git a/apps/limiter/src/lim_turnover_metric.erl b/apps/limiter/src/lim_turnover_metric.erl index cc99c6fd..8ce2aa71 100644 --- a/apps/limiter/src/lim_turnover_metric.erl +++ b/apps/limiter/src/lim_turnover_metric.erl @@ -3,7 +3,7 @@ -export([compute/4]). -type amount() :: lim_body:amount(). --type currency() :: lim_domain_thrift:'CurrencySymbolicCode'(). +-type currency() :: dmsl_domain_thrift:'CurrencySymbolicCode'(). -type stage() :: hold | commit. -type t() :: number | {amount, currency()}. @@ -39,7 +39,7 @@ get_commit_body(Config, LimitContext) -> case lim_body:get(partial, Config, LimitContext) of {ok, Body} -> Body; - {error, notfound} -> + {error, _} -> get_body(Config, LimitContext) end. diff --git a/apps/limiter/src/lim_turnover_processor.erl b/apps/limiter/src/lim_turnover_processor.erl index 203d8f11..1cdfa549 100644 --- a/apps/limiter/src/lim_turnover_processor.erl +++ b/apps/limiter/src/lim_turnover_processor.erl @@ -1,6 +1,6 @@ -module(lim_turnover_processor). --include_lib("limiter_proto/include/lim_limiter_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_limiter_thrift.hrl"). -behaviour(lim_config_machine). @@ -108,13 +108,13 @@ rollback(LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) end). compute_limit_time_range_location(LimitID, Config, LimitContext) -> - {ok, Timestamp} = lim_context:get_from_context(payment_processing, created_at, LimitContext), + Timestamp = get_timestamp(Config, LimitContext), LimitRangeID = construct_range_id(LimitID, Timestamp, Config, LimitContext), TimeRange = lim_config_machine:calculate_time_range(Timestamp, Config), {LimitRangeID, TimeRange}. ensure_limit_time_range(LimitID, Config, LimitContext) -> - {ok, Timestamp} = lim_context:get_from_context(payment_processing, created_at, LimitContext), + Timestamp = get_timestamp(Config, LimitContext), {LimitRangeID, TimeRange} = compute_limit_time_range_location(LimitID, Config, LimitContext), CreateParams = genlib_map:compact(#{ id => LimitRangeID, @@ -124,6 +124,11 @@ ensure_limit_time_range(LimitID, Config, LimitContext) -> }), unwrap(lim_range_machine:ensure_exists(CreateParams, TimeRange, LimitContext)). +get_timestamp(Config, LimitContext) -> + ContextType = lim_config_machine:context_type(Config), + {ok, Timestamp} = lim_context:get_value(ContextType, created_at, LimitContext), + Timestamp. + construct_plan_id(#limiter_LimitChange{change_id = ChangeID}) -> % DISCUSS ChangeID. diff --git a/apps/limiter/src/lim_wthdproc_context.erl b/apps/limiter/src/lim_wthdproc_context.erl new file mode 100644 index 00000000..123716f2 --- /dev/null +++ b/apps/limiter/src/lim_wthdproc_context.erl @@ -0,0 +1,73 @@ +-module(lim_wthdproc_context). + +-include_lib("limiter_proto/include/limproto_context_withdrawal_thrift.hrl"). +-include_lib("damsel/include/dmsl_wthd_domain_thrift.hrl"). + +-behaviour(lim_context). +-export([get_operation/1]). +-export([get_value/2]). + +-type context() :: limproto_context_withdrawal_thrift:'Context'(). + +-type operation() :: + withdrawal. + +-export_type([operation/0]). +-export_type([context/0]). + +%% + +-spec get_operation(context()) -> {ok, operation()} | {error, notfound}. +get_operation(#context_withdrawal_Context{op = {Operation, _}}) -> + {ok, Operation}; +get_operation(#context_withdrawal_Context{op = undefined}) -> + {error, notfound}. + +-spec get_value(atom(), context()) -> {ok, term()} | {error, notfound | {unsupported, _}}. +get_value(ValueName, Context) -> + case get_operation(Context) of + {ok, Operation} -> + get_value(ValueName, Operation, Context); + {error, _} = Error -> + Error + end. + +get_value(owner_id, _Operation, Context) -> + get_owner_id(Context); +get_value(created_at, Operation, Context) -> + get_created_at(Operation, Context); +get_value(cost, Operation, Context) -> + get_cost(Operation, Context); +get_value(payment_tool, Operation, Context) -> + get_payment_tool(Operation, Context); +get_value(ValueName, _Operation, _Context) -> + {error, {unsupported, ValueName}}. + +-define(WITHDRAWAL(V), #context_withdrawal_Context{ + withdrawal = #context_withdrawal_Withdrawal{ + withdrawal = V = #wthd_domain_Withdrawal{} + } +}). + +get_owner_id(?WITHDRAWAL(Wthd)) -> + Identity = Wthd#wthd_domain_Withdrawal.sender, + {ok, Identity#wthd_domain_Identity.owner_id}; +get_owner_id(_CtxWithdrawal) -> + {error, notfound}. + +get_created_at(withdrawal, ?WITHDRAWAL(Wthd)) -> + {ok, Wthd#wthd_domain_Withdrawal.created_at}; +get_created_at(_, _CtxWithdrawal) -> + {error, notfound}. + +get_cost(withdrawal, ?WITHDRAWAL(Wthd)) -> + Body = Wthd#wthd_domain_Withdrawal.body, + lim_payproc_utils:cash(Body); +get_cost(_, _CtxWithdrawal) -> + {error, notfound}. + +get_payment_tool(withdrawal, ?WITHDRAWAL(Wthd)) -> + Destination = Wthd#wthd_domain_Withdrawal.destination, + lim_payproc_utils:payment_tool(Destination); +get_payment_tool(_, _CtxWithdrawal) -> + {error, notfound}. diff --git a/apps/limiter/src/limiter.app.src b/apps/limiter/src/limiter.app.src index 0e0bd831..a7410261 100644 --- a/apps/limiter/src/limiter.app.src +++ b/apps/limiter/src/limiter.app.src @@ -5,7 +5,6 @@ {applications, [ kernel, stdlib, - damsel, limiter_proto, xrates_proto, machinery, diff --git a/apps/limiter/src/limiter.erl b/apps/limiter/src/limiter.erl index aaf6c4eb..6bf855b7 100644 --- a/apps/limiter/src/limiter.erl +++ b/apps/limiter/src/limiter.erl @@ -91,11 +91,11 @@ get_handler_specs(ServiceOpts) -> [ { maps:get(path, LimiterService, <<"/v1/limiter">>), - {{lim_limiter_thrift, 'Limiter'}, lim_handler} + {{limproto_limiter_thrift, 'Limiter'}, lim_handler} }, { maps:get(path, ConfiguratorService, <<"/v1/configurator">>), - {{lim_configurator_thrift, 'Configurator'}, lim_configurator} + {{limproto_configurator_thrift, 'Configurator'}, lim_configurator} } ]. diff --git a/apps/limiter/test/lim_client.erl b/apps/limiter/test/lim_client.erl index 5a14e139..1de2da45 100644 --- a/apps/limiter/test/lim_client.erl +++ b/apps/limiter/test/lim_client.erl @@ -1,11 +1,12 @@ -module(lim_client). --include_lib("limiter_proto/include/lim_limiter_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_limiter_thrift.hrl"). -export([new/0]). -export([get/3]). -export([hold/3]). -export([commit/3]). +-export([rollback/3]). -export([legacy_create_config/2]). -export([create_config/2]). @@ -13,12 +14,12 @@ -type client() :: woody_context:ctx(). --type limit_id() :: lim_limiter_thrift:'LimitID'(). --type limit_change() :: lim_limiter_thrift:'LimitChange'(). --type limit_context() :: lim_limiter_thrift:'LimitContext'(). --type clock() :: lim_limiter_thrift:'Clock'(). --type legacy_create_params() :: lim_configurator_thrift:'LimitCreateParams'(). --type limit_config_params() :: lim_limiter_config_thrift:'LimitConfigParams'(). +-type limit_id() :: limproto_limiter_thrift:'LimitID'(). +-type limit_change() :: limproto_limiter_thrift:'LimitChange'(). +-type limit_context() :: limproto_limiter_thrift:'LimitContext'(). +-type clock() :: limproto_limiter_thrift:'Clock'(). +-type legacy_create_params() :: limproto_configurator_thrift:'LimitCreateParams'(). +-type limit_config_params() :: limproto_config_thrift:'LimitConfigParams'(). %%% API @@ -38,6 +39,10 @@ hold(LimitChange, Context, Client) -> commit(LimitChange, Context, Client) -> call('Commit', {LimitChange, clock(), Context}, Client). +-spec rollback(limit_change(), limit_context(), client()) -> woody:result() | no_return(). +rollback(LimitChange, Context, Client) -> + call('Rollback', {LimitChange, clock(), Context}, Client). + %% -spec legacy_create_config(legacy_create_params(), client()) -> woody:result() | no_return(). @@ -56,7 +61,7 @@ get_config(LimitConfigID, Client) -> -spec call(atom(), tuple(), client()) -> woody:result() | no_return(). call(Function, Args, Client) -> - Call = {{lim_limiter_thrift, 'Limiter'}, Function, Args}, + Call = {{limproto_limiter_thrift, 'Limiter'}, Function, Args}, Opts = #{ url => <<"http://limiter:8022/v1/limiter">>, event_handler => scoper_woody_event_handler, @@ -68,7 +73,7 @@ call(Function, Args, Client) -> -spec call_configurator(atom(), tuple(), client()) -> woody:result() | no_return(). call_configurator(Function, Args, Client) -> - Call = {{lim_configurator_thrift, 'Configurator'}, Function, Args}, + Call = {{limproto_configurator_thrift, 'Configurator'}, Function, Args}, Opts = #{ url => <<"http://limiter:8022/v1/configurator">>, event_handler => scoper_woody_event_handler, diff --git a/apps/limiter/test/lim_configurator_SUITE.erl b/apps/limiter/test/lim_configurator_SUITE.erl index 61e8f64b..cb98e490 100644 --- a/apps/limiter/test/lim_configurator_SUITE.erl +++ b/apps/limiter/test/lim_configurator_SUITE.erl @@ -2,7 +2,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("stdlib/include/assert.hrl"). --include_lib("limiter_proto/include/lim_configurator_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_configurator_thrift.hrl"). -include("lim_ct_helper.hrl"). -export([all/0]). @@ -17,6 +17,7 @@ -export([create_config/1]). -export([create_config_single_scope/1]). -export([get_config/1]). +-export([get_inexistent_config/1]). -type group_name() :: atom(). -type test_case_name() :: atom(). @@ -36,7 +37,8 @@ groups() -> legacy_create_config, create_config, create_config_single_scope, - get_config + get_config, + get_inexistent_config ]} ]. @@ -81,14 +83,14 @@ legacy_create_config(C) -> Client = lim_client:new(), ID = ?config(limit_id, C), Description = genlib:unique(), - Params = #limiter_configurator_LimitCreateParams{ + Params = #configurator_LimitCreateParams{ id = ID, name = <<"GlobalMonthTurnover">>, description = Description, started_at = <<"2000-01-01T00:00:00Z">> }, ?assertMatch( - {ok, #limiter_config_LimitConfig{ + {ok, #config_LimitConfig{ id = ID, description = Description }}, @@ -100,7 +102,7 @@ create_config(C) -> Client = lim_client:new(), ID = ?config(limit_id, C), Description = genlib:unique(), - Params = #limiter_config_LimitConfigParams{ + Params = #config_LimitConfigParams{ id = ?config(limit_id, C), description = Description, started_at = <<"2000-01-01T00:00:00Z">>, @@ -115,7 +117,7 @@ create_config(C) -> context_type = ?ctx_type_payproc() }, ?assertMatch( - {ok, #limiter_config_LimitConfig{ + {ok, #config_LimitConfig{ id = ID, description = Description }}, @@ -125,7 +127,7 @@ create_config(C) -> -spec create_config_single_scope(config()) -> _. create_config_single_scope(C) -> Client = lim_client:new(), - Params = #limiter_config_LimitConfigParams{ + Params = #config_LimitConfigParams{ id = ?config(limit_id, C), started_at = <<"2000-01-01T00:00:00Z">>, time_range_type = ?time_range_week(), @@ -135,7 +137,7 @@ create_config_single_scope(C) -> context_type = ?ctx_type_payproc(), op_behaviour = ?op_behaviour() }, - {ok, #limiter_config_LimitConfig{ + {ok, #config_LimitConfig{ scope = Scope }} = lim_client:create_config(Params, Client), ?assertEqual(?scope([?scope_party()]), Scope). @@ -144,13 +146,20 @@ create_config_single_scope(C) -> get_config(C) -> ID = ?config(limit_id, C), #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), - {ok, #limiter_config_LimitConfig{id = ID}} = lim_client:get_config(ID, Client). + {ok, #config_LimitConfig{id = ID}} = lim_client:get_config(ID, Client). + +-spec get_inexistent_config(config()) -> _. +get_inexistent_config(_C) -> + ?assertEqual( + {exception, #configurator_LimitConfigNotFound{}}, + lim_client:get_config(<<"NOSUCHCONFIG">>, lim_client:new()) + ). %% prepare_environment(ID, LimitName, _C) -> Client = lim_client:new(), - Params = #limiter_configurator_LimitCreateParams{ + Params = #configurator_LimitCreateParams{ id = ID, name = LimitName, description = <<"description">>, diff --git a/apps/limiter/test/lim_ct_helper.hrl b/apps/limiter/test/lim_ct_helper.hrl index 57bbc3e2..eaa5d8c2 100644 --- a/apps/limiter/test/lim_ct_helper.hrl +++ b/apps/limiter/test/lim_ct_helper.hrl @@ -1,7 +1,13 @@ -ifndef(__limiter_ct_helper__). -define(__limiter_ct_helper__, 42). --include_lib("limiter_proto/include/lim_configurator_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_config_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_timerange_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_limiter_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_context_payproc_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_context_withdrawal_thrift.hrl"). +-include_lib("damsel/include/dmsl_domain_thrift.hrl"). +-include_lib("damsel/include/dmsl_wthd_domain_thrift.hrl"). -define(currency, <<"RUB">>). -define(string, <<"STRING">>). @@ -16,126 +22,83 @@ -define(scope(Types), {multi, ordsets:from_list(Types)}). -define(global(), ?scope([])). --define(scope_party(), {party, #limiter_config_LimitScopeEmptyDetails{}}). --define(scope_shop(), {shop, #limiter_config_LimitScopeEmptyDetails{}}). --define(scope_payment_tool(), {payment_tool, #limiter_config_LimitScopeEmptyDetails{}}). +-define(scope_party(), {party, #config_LimitScopeEmptyDetails{}}). +-define(scope_shop(), {shop, #config_LimitScopeEmptyDetails{}}). +-define(scope_payment_tool(), {payment_tool, #config_LimitScopeEmptyDetails{}}). -define(lim_type_turnover(), ?lim_type_turnover(?turnover_metric_number())). -define(lim_type_turnover(Metric), - {turnover, #limiter_config_LimitTypeTurnover{metric = Metric}} + {turnover, #config_LimitTypeTurnover{metric = Metric}} ). --define(turnover_metric_number(), {number, #limiter_config_LimitTurnoverNumber{}}). +-define(turnover_metric_number(), {number, #config_LimitTurnoverNumber{}}). -define(turnover_metric_amount(), ?turnover_metric_amount(?currency)). -define(turnover_metric_amount(Currency), - {amount, #limiter_config_LimitTurnoverAmount{currency = Currency}} + {amount, #config_LimitTurnoverAmount{currency = Currency}} ). -define(time_range_day(), - {calendar, {day, #limiter_time_range_TimeRangeTypeCalendarDay{}}} + {calendar, {day, #timerange_TimeRangeTypeCalendarDay{}}} ). -define(time_range_week(), - {calendar, {week, #limiter_time_range_TimeRangeTypeCalendarWeek{}}} + {calendar, {week, #timerange_TimeRangeTypeCalendarWeek{}}} ). -define(time_range_month(), - {calendar, {month, #limiter_time_range_TimeRangeTypeCalendarMonth{}}} + {calendar, {month, #timerange_TimeRangeTypeCalendarMonth{}}} ). -define(time_range_year(), - {calendar, {year, #limiter_time_range_TimeRangeTypeCalendarYear{}}} + {calendar, {year, #timerange_TimeRangeTypeCalendarYear{}}} ). -define(op_behaviour(), ?op_behaviour(?op_addition())). --define(op_behaviour(Refund), #limiter_config_OperationLimitBehaviour{ +-define(op_behaviour(Refund), #config_OperationLimitBehaviour{ invoice_payment_refund = Refund }). --define(op_addition(), {addition, #limiter_config_Addition{}}). --define(op_subtraction(), {subtraction, #limiter_config_Subtraction{}}). +-define(op_addition(), {addition, #config_Addition{}}). +-define(op_subtraction(), {subtraction, #config_Subtraction{}}). -define(ctx_type_payproc(), - {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}} + {payment_processing, #config_LimitContextTypePaymentProcessing{}} ). --define(op_invoice(), {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}). --define(op_invoice_payment(), {invoice_payment, #limiter_context_PaymentProcessingOperationInvoicePayment{}}). - --define(ctx_invoice(Cost), #limiter_context_LimitContext{ - limiter_payment_processing = #limiter_context_ContextPaymentProcessing{ - op = ?op_invoice(), - invoice = #limiter_context_Invoice{ - created_at = <<"2000-01-01T00:00:00Z">>, - cost = Cost - } - } -}). - --define(ctx_invoice_payment(Cost, CaptureCost), ?ctx_invoice_payment(undefined, undefined, Cost, CaptureCost)). - --define(ctx_invoice_payment(OwnerID, ShopID, Cost, CaptureCost), #limiter_context_LimitContext{ - limiter_payment_processing = #limiter_context_ContextPaymentProcessing{ - op = ?op_invoice_payment(), - invoice = #limiter_context_Invoice{ - owner_id = OwnerID, - shop_id = ShopID, - effective_payment = #limiter_context_InvoicePayment{ - created_at = <<"2000-01-01T00:00:00Z">>, - cost = Cost, - capture_cost = CaptureCost, - flow = {hold, #limiter_context_InvoicePaymentFlowHold{}}, - payer = {payment_resource, #limiter_context_PaymentResourcePayer{}}, - effective_adjustment = #limiter_context_InvoicePaymentAdjustment{}, - effective_chargeback = #limiter_context_InvoicePaymentChargeback{} - }, - effective_adjustment = #limiter_context_InvoiceAdjustment{} - } - } -}). +-define(ctx_type_wthdproc(), + {withdrawal_processing, #config_LimitContextTypeWithdrawalProcessing{}} +). --define(ctx_invoice_payment(Payment), #limiter_context_LimitContext{ - limiter_payment_processing = #limiter_context_ContextPaymentProcessing{ - op = ?op_invoice_payment(), - invoice = #limiter_context_Invoice{ - effective_payment = Payment - } - } -}). +%% Payproc --define(ctx_invoice_payment_refund(OwnerID, ShopID, Cost, CaptureCost, RefundCost), #limiter_context_LimitContext{ - limiter_payment_processing = #limiter_context_ContextPaymentProcessing{ - op = {invoice_payment_refund, #limiter_context_PaymentProcessingOperationInvoicePaymentRefund{}}, - invoice = #limiter_context_Invoice{ - owner_id = OwnerID, - shop_id = ShopID, - effective_payment = #limiter_context_InvoicePayment{ - created_at = <<"2000-01-01T00:00:00Z">>, - cost = Cost, - capture_cost = CaptureCost, - effective_refund = #limiter_context_InvoicePaymentRefund{ - cost = RefundCost, - created_at = <<"2000-01-01T00:00:00Z">> - } - } - } - } -}). +-define(op_invoice, {invoice, #context_payproc_OperationInvoice{}}). +-define(op_payment, {invoice_payment, #context_payproc_OperationInvoicePayment{}}). +-define(op_refund, {invoice_payment_refund, #context_payproc_OperationInvoicePaymentRefund{}}). -%% Payproc +-define(bank_card(), ?bank_card(?string, 2, 2022)). --define(payproc_op_invoice, {invoice, #limiter_context_payproc_OperationInvoice{}}). --define(payproc_op_invoice_payment, {invoice_payment, #limiter_context_payproc_OperationInvoicePayment{}}). +-define(bank_card(Token), + {bank_card, #domain_BankCard{ + token = Token, + bin = ?string, + last_digits = ?string + }} +). --define(payproc_bank_card(), - ?payproc_bank_card(?string, 2, 2022) +-define(bank_card(Token, Month, Year), + {bank_card, #domain_BankCard{ + token = Token, + bin = ?string, + last_digits = ?string, + exp_date = #domain_BankCardExpDate{month = Month, year = Year} + }} ). --define(payproc_bank_card(Token, Month, Year), #domain_BankCard{ - token = Token, - bin = ?string, - last_digits = ?string, - exp_date = #domain_BankCardExpDate{month = Month, year = Year} -}). +-define(digital_wallet(ID, Service), + {digital_wallet, #domain_DigitalWallet{ + id = ID, + payment_service = #domain_PaymentServiceRef{id = Service} + }} +). --define(payproc_invoice(OwnerID, ShopID, Cost), #domain_Invoice{ +-define(invoice(OwnerID, ShopID, Cost), #domain_Invoice{ id = ?string, owner_id = OwnerID, shop_id = ShopID, @@ -146,13 +109,17 @@ cost = Cost }). --define(payproc_invoice_payment(Cost, CaptureCost), - ?payproc_invoice_payment(Cost, CaptureCost, {bank_card, ?payproc_bank_card()}) +-define(invoice_payment(Cost, CaptureCost), + ?invoice_payment(Cost, CaptureCost, ?bank_card()) +). + +-define(invoice_payment(Cost, CaptureCost, PaymentTool), + ?invoice_payment(Cost, CaptureCost, PaymentTool, ?timestamp) ). --define(payproc_invoice_payment(Cost, CaptureCost, PaymentTool), #domain_InvoicePayment{ +-define(invoice_payment(Cost, CaptureCost, PaymentTool, CreatedAt), #domain_InvoicePayment{ id = ?string, - created_at = ?timestamp, + created_at = CreatedAt, status = {captured, #domain_InvoicePaymentCaptured{cost = CaptureCost}}, cost = Cost, domain_revision = 1, @@ -166,62 +133,87 @@ }} }). --define(payproc_ctx_invoice(Cost), #limiter_context_LimitContext{ - payment_processing = #limiter_context_payproc_Context{ - op = ?payproc_op_invoice, - invoice = #limiter_context_payproc_Invoice{ - invoice = ?payproc_invoice(?string, ?string, Cost) +-define(payproc_ctx_invoice(Cost), #limiter_LimitContext{ + payment_processing = #context_payproc_Context{ + op = ?op_invoice, + invoice = #context_payproc_Invoice{ + invoice = ?invoice(?string, ?string, Cost) } } }). --define(payproc_ctx_invoice_payment(Cost, CaptureCost), - ?payproc_ctx_invoice_payment(?string, ?string, Cost, CaptureCost) +-define(payproc_ctx_payment(Cost, CaptureCost), + ?payproc_ctx_payment(?string, ?string, Cost, CaptureCost) ). --define(payproc_ctx_invoice_payment(OwnerID, ShopID, Cost, CaptureCost), #limiter_context_LimitContext{ - payment_processing = #limiter_context_payproc_Context{ - op = ?payproc_op_invoice_payment, - invoice = #limiter_context_payproc_Invoice{ - invoice = ?payproc_invoice(OwnerID, ShopID, Cost), - payment = #limiter_context_payproc_InvoicePayment{ - payment = ?payproc_invoice_payment(Cost, CaptureCost) +-define(payproc_ctx_payment(OwnerID, ShopID, Cost, CaptureCost), #limiter_LimitContext{ + payment_processing = #context_payproc_Context{ + op = ?op_payment, + invoice = #context_payproc_Invoice{ + invoice = ?invoice(OwnerID, ShopID, Cost), + payment = #context_payproc_InvoicePayment{ + payment = ?invoice_payment(Cost, CaptureCost) } } } }). --define(payproc_ctx_invoice_payment(Payment), #limiter_context_LimitContext{ - payment_processing = #limiter_context_payproc_Context{ - op = ?payproc_op_invoice_payment, - invoice = #limiter_context_payproc_Invoice{ - invoice = ?payproc_invoice(?string, ?string, ?cash(10)), - payment = #limiter_context_payproc_InvoicePayment{ +-define(payproc_ctx_payment(Payment), #limiter_LimitContext{ + payment_processing = #context_payproc_Context{ + op = ?op_payment, + invoice = #context_payproc_Invoice{ + invoice = ?invoice(?string, ?string, ?cash(10)), + payment = #context_payproc_InvoicePayment{ payment = Payment } } } }). --define(payproc_ctx_invoice_payment_refund(OwnerID, ShopID, Cost, CaptureCost, RefundCost), - #limiter_context_LimitContext{ - payment_processing = #limiter_context_payproc_Context{ - op = {invoice_payment_refund, #limiter_context_payproc_OperationInvoicePaymentRefund{}}, - invoice = #limiter_context_payproc_Invoice{ - invoice = ?payproc_invoice(OwnerID, ShopID, Cost), - payment = #limiter_context_payproc_InvoicePayment{ - payment = ?payproc_invoice_payment(Cost, CaptureCost), - refund = #domain_InvoicePaymentRefund{ - id = ?string, - status = {succeeded, #domain_InvoicePaymentRefundSucceeded{}}, - created_at = ?timestamp, - domain_revision = 1, - cash = RefundCost - } +-define(payproc_ctx_refund(OwnerID, ShopID, Cost, CaptureCost, RefundCost), #limiter_LimitContext{ + payment_processing = #context_payproc_Context{ + op = ?op_refund, + invoice = #context_payproc_Invoice{ + invoice = ?invoice(OwnerID, ShopID, Cost), + payment = #context_payproc_InvoicePayment{ + payment = ?invoice_payment(Cost, CaptureCost), + refund = #domain_InvoicePaymentRefund{ + id = ?string, + status = {succeeded, #domain_InvoicePaymentRefundSucceeded{}}, + created_at = ?timestamp, + domain_revision = 1, + cash = RefundCost } } } } -). +}). + +%% Wthdproc + +-define(identity(OwnerID), #wthd_domain_Identity{ + id = OwnerID, + owner_id = OwnerID +}). + +-define(withdrawal(Body), ?withdrawal(Body, ?bank_card(), ?string)). + +-define(withdrawal(Body, Destination, OwnerID), #wthd_domain_Withdrawal{ + body = Body, + created_at = ?timestamp, + destination = Destination, + sender = ?identity(OwnerID) +}). + +-define(op_withdrawal, {withdrawal, #context_withdrawal_OperationWithdrawal{}}). + +-define(wthdproc_ctx_withdrawal(Cost), #limiter_LimitContext{ + withdrawal_processing = #context_withdrawal_Context{ + op = ?op_withdrawal, + withdrawal = #context_withdrawal_Withdrawal{ + withdrawal = ?withdrawal(Cost) + } + } +}). -endif. diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index 1d07ff15..b527a703 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -2,10 +2,8 @@ -include_lib("stdlib/include/assert.hrl"). -include_lib("common_test/include/ct.hrl"). --include_lib("lim_ct_helper.hrl"). - --include_lib("limiter_proto/include/lim_configurator_thrift.hrl"). --include_lib("xrates_proto/include/xrates_rate_thrift.hrl"). +-include_lib("damsel/include/dmsl_base_thrift.hrl"). +-include("lim_ct_helper.hrl"). -export([all/0]). @@ -18,17 +16,18 @@ -export([commit_with_default_exchange/1]). -export([partial_commit_with_exchange/1]). -export([commit_with_exchange/1]). --export([get_rate/1]). -export([get_limit_ok/1]). -export([get_limit_notfound/1]). -export([hold_ok/1]). -export([commit_ok/1]). -export([rollback_ok/1]). +-export([partial_zero_commit_rollbacks/1]). -export([refund_ok/1]). -export([get_config_ok/1]). -export([commit_inexistent_hold_fails/1]). -export([partial_commit_inexistent_hold_fails/1]). -export([commit_multirange_limit_ok/1]). +-export([commit_with_payment_tool_scope_ok/1]). -export([commit_processes_idempotently/1]). -export([full_commit_processes_idempotently/1]). @@ -40,23 +39,16 @@ -export([commit_refund_keep_number_unchanged/1]). -export([partial_commit_number_counts_as_single_op/1]). --export([payproc_hold_ok/1]). --export([payproc_commit_ok/1]). --export([payproc_rollback_ok/1]). --export([payproc_refund_ok/1]). --export([payproc_commit_with_payment_tool_scope_ok/1]). - -type group_name() :: atom(). -type test_case_name() :: atom(). --define(RATE_SOURCE_ID, <<"dummy_source_id">>). - %% tests descriptions -spec all() -> [{group, group_name()}]. all() -> [ {group, default}, + {group, withdrawals}, {group, cashless}, {group, idempotency} ]. @@ -68,22 +60,24 @@ groups() -> commit_with_default_exchange, partial_commit_with_exchange, commit_with_exchange, - get_rate, get_limit_ok, get_limit_notfound, hold_ok, commit_ok, rollback_ok, + partial_zero_commit_rollbacks, get_config_ok, refund_ok, commit_inexistent_hold_fails, partial_commit_inexistent_hold_fails, commit_multirange_limit_ok, - payproc_hold_ok, - payproc_commit_ok, - payproc_rollback_ok, - payproc_refund_ok, - payproc_commit_with_payment_tool_scope_ok + commit_with_payment_tool_scope_ok + ]}, + {withdrawals, [parallel], [ + get_limit_ok, + hold_ok, + commit_ok, + rollback_ok ]}, {cashless, [parallel], [ commit_number_ok, @@ -155,18 +149,8 @@ commit_with_default_exchange(C) -> Rational = #base_Rational{p = 1000000, q = 100}, _ = mock_exchange(Rational, C), ID = configure_limit(?time_range_month(), ?global(), C), - Context = #limiter_context_LimitContext{ - limiter_payment_processing = #limiter_context_ContextPaymentProcessing{ - op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, - invoice = #limiter_context_Invoice{ - created_at = <<"2000-01-01T00:00:00Z">>, - cost = #domain_Cash{ - amount = 10000, - currency = #domain_CurrencyRef{symbolic_code = <<"SOME_CURRENCY">>} - } - } - } - }, + Cost = ?cash(10000, <<"SOME_CURRENCY">>), + Context = ?payproc_ctx_invoice(Cost), {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), {ok, #limiter_Limit{amount = 10000}} = lim_client:get(ID, Context, ?config(client, C)). @@ -175,24 +159,9 @@ partial_commit_with_exchange(C) -> Rational = #base_Rational{p = 800000, q = 100}, _ = mock_exchange(Rational, C), ID = configure_limit(?time_range_month(), ?global(), C), - Context = #limiter_context_LimitContext{ - limiter_payment_processing = #limiter_context_ContextPaymentProcessing{ - op = {invoice_payment, #limiter_context_PaymentProcessingOperationInvoicePayment{}}, - invoice = #limiter_context_Invoice{ - effective_payment = #limiter_context_InvoicePayment{ - created_at = <<"2000-01-01T00:00:00Z">>, - cost = #domain_Cash{ - amount = 10000, - currency = #domain_CurrencyRef{symbolic_code = <<"USD">>} - }, - capture_cost = #domain_Cash{ - amount = 8000, - currency = #domain_CurrencyRef{symbolic_code = <<"USD">>} - } - } - } - } - }, + Cost = ?cash(1000, <<"USD">>), + CaptureCost = ?cash(800, <<"USD">>), + Context = ?payproc_ctx_payment(Cost, CaptureCost), {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), {ok, #limiter_Limit{amount = 8400}} = lim_client:get(ID, Context, ?config(client, C)). @@ -201,43 +170,19 @@ commit_with_exchange(C) -> Rational = #base_Rational{p = 1000000, q = 100}, _ = mock_exchange(Rational, C), ID = configure_limit(?time_range_month(), ?global(), C), - Context = #limiter_context_LimitContext{ - limiter_payment_processing = #limiter_context_ContextPaymentProcessing{ - op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, - invoice = #limiter_context_Invoice{ - created_at = <<"2000-01-01T00:00:00Z">>, - cost = #domain_Cash{ - amount = 10000, - currency = #domain_CurrencyRef{symbolic_code = <<"USD">>} - } - } - } - }, + Cost = ?cash(10000, <<"USD">>), + Context = ?payproc_ctx_invoice(Cost), {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), {ok, #limiter_Limit{amount = 10500}} = lim_client:get(ID, Context, ?config(client, C)). --spec get_rate(config()) -> _. -get_rate(C) -> - Rational = #base_Rational{p = 10, q = 10}, - _ = mock_exchange(Rational, C), - Request = #rate_ConversionRequest{ - source = <<"RUB">>, - destination = <<"USD">>, - amount = 100, - datetime = <<"Timestamp">> - }, - WoodyContext = woody_context:new(), - {ok, Rational} = lim_client_woody:call( - xrates, - 'GetConvertedAmount', - {?RATE_SOURCE_ID, Request}, - WoodyContext - ). - -spec get_limit_ok(config()) -> _. get_limit_ok(C) -> ID = configure_limit(?time_range_month(), ?global(), C), - Context = ?ctx_invoice(_Cost = undefined), + Context = + case get_group_name(C) of + default -> ?payproc_ctx_invoice(?cash(0)); + withdrawals -> ?wthdproc_ctx_withdrawal(?cash(0)) + end, ?assertMatch( {ok, #limiter_Limit{amount = 0}}, lim_client:get(ID, Context, ?config(client, C)) @@ -245,7 +190,7 @@ get_limit_ok(C) -> -spec get_limit_notfound(config()) -> _. get_limit_notfound(C) -> - Context = ?ctx_invoice(_Cost = undefined), + Context = ?payproc_ctx_invoice(?cash(0)), ?assertEqual( {exception, #limiter_LimitNotFound{}}, lim_client:get(<<"NOSUCHLIMITID">>, Context, ?config(client, C)) @@ -254,36 +199,50 @@ get_limit_notfound(C) -> -spec hold_ok(config()) -> _. hold_ok(C) -> ID = configure_limit(?time_range_month(), ?global(), C), - Context = ?ctx_invoice(?cash(10)), + Context = + case get_group_name(C) of + default -> ?payproc_ctx_invoice(?cash(10)); + withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10)) + end, {ok, {vector, #limiter_VectorClock{}}} = lim_client:hold(?LIMIT_CHANGE(ID), Context, ?config(client, C)), {ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)). -spec commit_ok(config()) -> _. commit_ok(C) -> ID = configure_limit(?time_range_month(), ?global(), C), - Context = #limiter_context_LimitContext{ - limiter_payment_processing = #limiter_context_ContextPaymentProcessing{ - op = {invoice, #limiter_context_PaymentProcessingOperationInvoice{}}, - invoice = #limiter_context_Invoice{ - created_at = <<"2000-01-01T00:00:00Z">>, - cost = #domain_Cash{ - amount = 10, - currency = #domain_CurrencyRef{symbolic_code = <<"RUB">>} - } - } - } - }, + Context = + case get_group_name(C) of + default -> ?payproc_ctx_invoice(?cash(10, <<"RUB">>)); + withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)) + end, {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), {ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)). -spec rollback_ok(config()) -> _. rollback_ok(C) -> ID = configure_limit(?time_range_week(), ?global(), C), - Context0 = ?ctx_invoice_payment(?cash(10), ?cash(10)), - Context1 = ?ctx_invoice_payment(?cash(10), ?cash(0)), + Context = + case get_group_name(C) of + default -> ?payproc_ctx_invoice(?cash(10, <<"RUB">>)); + withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)) + end, + Change = ?LIMIT_CHANGE(ID), + {ok, {vector, _}} = lim_client:hold(Change, Context, ?config(client, C)), + {ok, {vector, _}} = lim_client:rollback(Change, Context, ?config(client, C)). + +-spec partial_zero_commit_rollbacks(config()) -> _. +partial_zero_commit_rollbacks(C) -> + ID = configure_limit(?time_range_week(), ?global(), C), + Context0 = ?payproc_ctx_payment(?cash(10), ?cash(10)), + Context1 = ?payproc_ctx_payment(?cash(10), ?cash(0)), Change = ?LIMIT_CHANGE(ID), {ok, {vector, _}} = lim_client:hold(Change, Context0, ?config(client, C)), - {ok, {vector, _}} = lim_client:commit(Change, Context1, ?config(client, C)). + {ok, {vector, _}} = lim_client:commit(Change, Context1, ?config(client, C)), + % NOTE + % Successful rollback here means that partial commit with zero is handled exactly + % like rollback, thus subsequent rollback succeeds idempotently. This is a backwards + % compatibility measure. + {ok, {vector, _}} = lim_client:rollback(Change, Context0, ?config(client, C)). -spec refund_ok(config()) -> _. refund_ok(C) -> @@ -291,8 +250,8 @@ refund_ok(C) -> OwnerID = <<"WWWcool Ltd">>, ShopID = <<"shop">>, ID = configure_limit(?time_range_day(), ?scope([?scope_party(), ?scope_shop()]), C), - Context0 = ?ctx_invoice_payment(OwnerID, ShopID, ?cash(15), ?cash(15)), - RefundContext1 = ?ctx_invoice_payment_refund(OwnerID, ShopID, ?cash(10), ?cash(10), ?cash(10)), + Context0 = ?payproc_ctx_payment(OwnerID, ShopID, ?cash(15), ?cash(15)), + RefundContext1 = ?payproc_ctx_refund(OwnerID, ShopID, ?cash(10), ?cash(10), ?cash(10)), {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Payment">>), Context0, Client), {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Refund">>), RefundContext1, Client), {ok, #limiter_Limit{} = Limit2} = lim_client:get(ID, RefundContext1, Client), @@ -301,33 +260,33 @@ refund_ok(C) -> -spec get_config_ok(config()) -> _. get_config_ok(C) -> ID = configure_limit(?time_range_week(), ?global(), C), - {ok, #limiter_config_LimitConfig{}} = lim_client:get_config(ID, ?config(client, C)). + {ok, #config_LimitConfig{}} = lim_client:get_config(ID, ?config(client, C)). -spec commit_inexistent_hold_fails(config()) -> _. commit_inexistent_hold_fails(C) -> ID = configure_limit(?time_range_week(), ?global(), C), - Context = ?ctx_invoice_payment(?cash(42), undefined), + Context = ?payproc_ctx_payment(?cash(42), undefined), % NOTE % We do not expect `LimitChangeNotFound` here because we no longer reconcile with accounter % before requesting him to hold / commit. - {exception, #'InvalidRequest'{}} = + {exception, #base_InvalidRequest{}} = lim_client:commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)). -spec partial_commit_inexistent_hold_fails(config()) -> _. partial_commit_inexistent_hold_fails(C) -> ID = configure_limit(?time_range_week(), ?global(), C), - Context = ?ctx_invoice_payment(?cash(42), ?cash(21)), + Context = ?payproc_ctx_payment(?cash(42), ?cash(21)), % NOTE % We do not expect `LimitChangeNotFound` here because we no longer reconcile with accounter % before requesting him to hold / commit. - {exception, #'InvalidRequest'{}} = + {exception, #base_InvalidRequest{}} = lim_client:commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)). -spec commit_multirange_limit_ok(config()) -> _. commit_multirange_limit_ok(C) -> ID = ?config(id, C), Client = ?config(client, C), - Params = #limiter_config_LimitConfigParams{ + Params = #config_LimitConfigParams{ id = ID, started_at = <<"2000-01-01T00:00:00Z">>, shard_size = 12, @@ -335,30 +294,52 @@ commit_multirange_limit_ok(C) -> context_type = ?ctx_type_payproc(), type = ?lim_type_turnover(?turnover_metric_amount(<<"RUB">>)), scope = ?scope([]), - op_behaviour = #limiter_config_OperationLimitBehaviour{} + op_behaviour = #config_OperationLimitBehaviour{} }, {ok, _LimitConfig} = lim_client:create_config(Params, Client), % NOTE % Expecting those 3 changes will be accounted in the same limit range machine. % We have no way to verify it here though. - PaymentJan = #limiter_context_InvoicePayment{ - created_at = <<"2020-01-01T00:00:00Z">>, - cost = ?cash(42) - }, - {ok, _} = hold_and_commit(?LIMIT_CHANGE(ID, 1), ?ctx_invoice_payment(PaymentJan), Client), - PaymentFeb = #limiter_context_InvoicePayment{ - created_at = <<"2020-02-01T00:00:00Z">>, - cost = ?cash(43) - }, - {ok, _} = hold_and_commit(?LIMIT_CHANGE(ID, 2), ?ctx_invoice_payment(PaymentFeb), Client), - PaymentApr = #limiter_context_InvoicePayment{ - created_at = <<"2020-04-01T00:00:00Z">>, - cost = ?cash(44) - }, - {ok, _} = hold_and_commit(?LIMIT_CHANGE(ID, 3), ?ctx_invoice_payment(PaymentApr), Client), - {ok, #limiter_Limit{amount = 42}} = lim_client:get(ID, ?ctx_invoice_payment(PaymentJan), Client), - {ok, #limiter_Limit{amount = 43}} = lim_client:get(ID, ?ctx_invoice_payment(PaymentFeb), Client), - {ok, #limiter_Limit{amount = 44}} = lim_client:get(ID, ?ctx_invoice_payment(PaymentApr), Client). + PaymentJan = ?invoice_payment(?cash(42), ?cash(42), ?bank_card(), <<"2020-01-01T00:00:00Z">>), + {ok, _} = hold_and_commit(?LIMIT_CHANGE(ID, 1), ?payproc_ctx_payment(PaymentJan), Client), + PaymentFeb = ?invoice_payment(?cash(43), ?cash(43), ?bank_card(), <<"2020-02-01T00:00:00Z">>), + {ok, _} = hold_and_commit(?LIMIT_CHANGE(ID, 2), ?payproc_ctx_payment(PaymentFeb), Client), + PaymentApr = ?invoice_payment(?cash(44), ?cash(44), ?bank_card(), <<"2020-04-01T00:00:00Z">>), + {ok, _} = hold_and_commit(?LIMIT_CHANGE(ID, 3), ?payproc_ctx_payment(PaymentApr), Client), + {ok, #limiter_Limit{amount = 42}} = lim_client:get(ID, ?payproc_ctx_payment(PaymentJan), Client), + {ok, #limiter_Limit{amount = 43}} = lim_client:get(ID, ?payproc_ctx_payment(PaymentFeb), Client), + {ok, #limiter_Limit{amount = 44}} = lim_client:get(ID, ?payproc_ctx_payment(PaymentApr), Client). + +-spec commit_with_payment_tool_scope_ok(config()) -> _. +commit_with_payment_tool_scope_ok(C) -> + Client = ?config(client, C), + ID = configure_limit(?time_range_week(), ?scope([?scope_payment_tool()]), ?turnover_metric_number(), C), + Context1 = ?payproc_ctx_payment( + ?invoice_payment(?cash(10), ?cash(10), ?bank_card(<<"Token">>, 2, 2022)) + ), + Context2 = ?payproc_ctx_payment( + ?invoice_payment(?cash(10), ?cash(10), ?bank_card(<<"OtherToken">>, 2, 2022)) + ), + Context3 = ?payproc_ctx_payment( + ?invoice_payment(?cash(10), ?cash(10), ?bank_card(?string, 3, 2022)) + ), + Context4 = ?payproc_ctx_payment( + ?invoice_payment(?cash(10), ?cash(10), ?bank_card(?string)) + ), + Context5 = ?payproc_ctx_payment( + ?invoice_payment(?cash(10), ?cash(10), ?digital_wallet(<<"ID42">>, <<"Pepal">>)) + ), + {ok, LimitState0} = lim_client:get(ID, Context1, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 1), Context1, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 2), Context2, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 3), Context3, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 4), Context4, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 5), Context5, Client), + {ok, LimitState1} = lim_client:get(ID, Context1, Client), + ?assertEqual( + LimitState1#limiter_Limit.amount, + LimitState0#limiter_Limit.amount + 1 + ). %% @@ -366,7 +347,7 @@ commit_multirange_limit_ok(C) -> commit_processes_idempotently(C) -> Client = ?config(client, C), ID = configure_limit(?time_range_week(), ?global(), C), - Context = ?ctx_invoice_payment(?cash(42), undefined), + Context = ?payproc_ctx_payment(?cash(42), undefined), Change = ?LIMIT_CHANGE(ID), {ok, _} = lim_client:hold(Change, Context, Client), {ok, _} = lim_client:hold(Change, Context, Client), @@ -380,7 +361,7 @@ full_commit_processes_idempotently(C) -> Client = ?config(client, C), ID = configure_limit(?time_range_week(), ?global(), C), Cost = ?cash(42), - Context = ?ctx_invoice_payment(Cost, Cost), + Context = ?payproc_ctx_payment(Cost, Cost), Change = ?LIMIT_CHANGE(ID), {ok, _} = lim_client:hold(Change, Context, Client), {ok, _} = lim_client:hold(Change, Context, Client), @@ -393,7 +374,7 @@ full_commit_processes_idempotently(C) -> partial_commit_processes_idempotently(C) -> Client = ?config(client, C), ID = configure_limit(?time_range_week(), ?global(), C), - Context = ?ctx_invoice_payment(?cash(42), ?cash(40)), + Context = ?payproc_ctx_payment(?cash(42), ?cash(40)), Change = ?LIMIT_CHANGE(ID), {ok, _} = lim_client:hold(Change, Context, Client), {ok, _} = lim_client:hold(Change, Context, Client), @@ -406,7 +387,7 @@ partial_commit_processes_idempotently(C) -> rollback_processes_idempotently(C) -> Client = ?config(client, C), ID = configure_limit(?time_range_week(), ?global(), C), - Context = ?ctx_invoice_payment(?cash(42), ?cash(0)), + Context = ?payproc_ctx_payment(?cash(42), ?cash(0)), Change = ?LIMIT_CHANGE(ID), {ok, _} = lim_client:hold(Change, Context, Client), {ok, _} = lim_client:hold(Change, Context, Client), @@ -421,7 +402,7 @@ rollback_processes_idempotently(C) -> commit_number_ok(C) -> Client = ?config(client, C), ID = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C), - Context = ?ctx_invoice_payment(?cash(10), ?cash(10)), + Context = ?payproc_ctx_payment(?cash(10), ?cash(10)), {ok, LimitState0} = lim_client:get(ID, Context, Client), _ = hold_and_commit(?LIMIT_CHANGE(ID), Context, Client), {ok, LimitState1} = lim_client:get(ID, Context, Client), @@ -434,8 +415,8 @@ commit_number_ok(C) -> rollback_number_ok(C) -> Client = ?config(client, C), ID = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C), - Context = ?ctx_invoice_payment(?cash(10), ?cash(10)), - ContextRollback = ?ctx_invoice_payment(?cash(10), ?cash(0)), + Context = ?payproc_ctx_payment(?cash(10), ?cash(10)), + ContextRollback = ?payproc_ctx_payment(?cash(10), ?cash(0)), {ok, LimitState0} = lim_client:get(ID, Context, Client), _ = hold_and_commit(?LIMIT_CHANGE(ID), Context, ContextRollback, Client), {ok, LimitState1} = lim_client:get(ID, Context, Client), @@ -451,8 +432,8 @@ commit_refund_keep_number_unchanged(C) -> Cost = ?cash(10), CaptureCost = ?cash(8), RefundCost = ?cash(5), - PaymentContext = ?ctx_invoice_payment(<<"OWNER">>, <<"SHOP">>, Cost, CaptureCost), - RefundContext = ?ctx_invoice_payment_refund(<<"OWNER">>, <<"SHOP">>, Cost, CaptureCost, RefundCost), + PaymentContext = ?payproc_ctx_payment(<<"OWNER">>, <<"SHOP">>, Cost, CaptureCost), + RefundContext = ?payproc_ctx_refund(<<"OWNER">>, <<"SHOP">>, Cost, CaptureCost, RefundCost), {ok, LimitState0} = lim_client:get(ID, PaymentContext, Client), _ = hold_and_commit(?LIMIT_CHANGE(ID, 1), PaymentContext, Client), _ = hold_and_commit(?LIMIT_CHANGE(ID, 2), RefundContext, Client), @@ -467,8 +448,8 @@ commit_refund_keep_number_unchanged(C) -> partial_commit_number_counts_as_single_op(C) -> Client = ?config(client, C), ID = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C), - Context = ?ctx_invoice_payment(?cash(10), ?cash(10)), - ContextPartial = ?ctx_invoice_payment(?cash(10), ?cash(5)), + Context = ?payproc_ctx_payment(?cash(10), ?cash(10)), + ContextPartial = ?payproc_ctx_payment(?cash(10), ?cash(5)), {ok, LimitState0} = lim_client:get(ID, Context, Client), _ = hold_and_commit(?LIMIT_CHANGE(ID), Context, ContextPartial, Client), {ok, LimitState1} = lim_client:get(ID, Context, Client), @@ -479,79 +460,6 @@ partial_commit_number_counts_as_single_op(C) -> %% --spec payproc_hold_ok(config()) -> _. -payproc_hold_ok(C) -> - ID = configure_limit(?time_range_month(), ?global(), C), - Context = ?payproc_ctx_invoice(?cash(10)), - {ok, {vector, #limiter_VectorClock{}}} = lim_client:hold(?LIMIT_CHANGE(ID), Context, ?config(client, C)), - {ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)). - --spec payproc_commit_ok(config()) -> _. -payproc_commit_ok(C) -> - ID = configure_limit(?time_range_year(), ?global(), C), - Context = ?payproc_ctx_invoice(?cash(10)), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), - {ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)). - --spec payproc_rollback_ok(config()) -> _. -payproc_rollback_ok(C) -> - ID = configure_limit(?time_range_week(), ?global(), C), - Context0 = ?payproc_ctx_invoice_payment(?cash(10), ?cash(10)), - Context1 = ?payproc_ctx_invoice_payment(?cash(10), ?cash(0)), - Change = ?LIMIT_CHANGE(ID), - {ok, {vector, _}} = lim_client:hold(Change, Context0, ?config(client, C)), - {ok, {vector, _}} = lim_client:commit(Change, Context1, ?config(client, C)). - --spec payproc_refund_ok(config()) -> _. -payproc_refund_ok(C) -> - Client = ?config(client, C), - OwnerID = <<"WWWcool Ltd">>, - ShopID = <<"shop">>, - ID = configure_limit(?time_range_day(), ?scope([?scope_party(), ?scope_shop()]), C), - Context0 = ?payproc_ctx_invoice_payment(OwnerID, ShopID, ?cash(15), ?cash(15)), - RefundContext1 = ?payproc_ctx_invoice_payment_refund(OwnerID, ShopID, ?cash(10), ?cash(10), ?cash(10)), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Payment">>), Context0, Client), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Refund">>), RefundContext1, Client), - {ok, #limiter_Limit{} = Limit2} = lim_client:get(ID, RefundContext1, Client), - ?assertEqual(Limit2#limiter_Limit.amount, 5). - --spec payproc_commit_with_payment_tool_scope_ok(config()) -> _. -payproc_commit_with_payment_tool_scope_ok(C) -> - Client = ?config(client, C), - ID = configure_limit(?time_range_week(), ?scope([?scope_payment_tool()]), ?turnover_metric_number(), C), - Context0 = ?payproc_ctx_invoice_payment( - ?payproc_invoice_payment( - ?cash(10), - ?cash(10), - {bank_card, ?payproc_bank_card()} - ) - ), - Context1 = ?payproc_ctx_invoice_payment( - ?payproc_invoice_payment( - ?cash(10), - ?cash(10), - {bank_card, ?payproc_bank_card(<<"OtherToken">>, 2, 2022)} - ) - ), - Context2 = ?payproc_ctx_invoice_payment( - ?payproc_invoice_payment( - ?cash(10), - ?cash(10), - {bank_card, ?payproc_bank_card(?string, 3, 2022)} - ) - ), - {ok, LimitState0} = lim_client:get(ID, Context0, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 1), Context0, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 2), Context1, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 3), Context2, Client), - {ok, LimitState1} = lim_client:get(ID, Context0, Client), - ?assertEqual( - LimitState1#limiter_Limit.amount, - LimitState0#limiter_Limit.amount + 1 - ). - -%% - gen_change_id(LimitID, ChangeID) -> genlib:format("~s/~p", [LimitID, ChangeID]). @@ -570,14 +478,19 @@ configure_limit(TimeRange, Scope, C) -> configure_limit(TimeRange, Scope, Metric, C) -> ID = ?config(id, C), - Params = #limiter_config_LimitConfigParams{ + ContextType = + case get_group_name(C) of + withdrawals -> ?ctx_type_wthdproc(); + _Default -> ?ctx_type_payproc() + end, + Params = #config_LimitConfigParams{ id = ID, started_at = <<"2000-01-01T00:00:00Z">>, time_range_type = TimeRange, shard_size = 1, type = ?lim_type_turnover(Metric), scope = Scope, - context_type = ?ctx_type_payproc(), + context_type = ContextType, op_behaviour = ?op_behaviour(?op_subtraction()) }, {ok, _LimitConfig} = lim_client:create_config(Params, ?config(client, C)), @@ -585,3 +498,7 @@ configure_limit(TimeRange, Scope, Metric, C) -> gen_unique_id(Prefix) -> genlib:format("~s/~B", [Prefix, lim_time:now()]). + +get_group_name(C) -> + GroupProps = ?config(tc_group_properties, C), + proplists:get_value(name, GroupProps). diff --git a/rebar.config b/rebar.config index 80ddc112..58099616 100644 --- a/rebar.config +++ b/rebar.config @@ -26,7 +26,6 @@ %% Common project dependencies. {deps, [ - {damsel, {git, "https://github.com/valitydev/damsel.git", {branch, "master"}}}, {limiter_proto, {git, "https://github.com/valitydev/limiter-proto.git", {branch, "master"}}}, {xrates_proto, {git, "https://github.com/valitydev/xrates-proto.git", {branch, "master"}}}, {machinery, {git, "https://github.com/valitydev/machinery-erlang.git", {branch, "master"}}}, diff --git a/rebar.lock b/rebar.lock index a8ad132b..3de7d897 100644 --- a/rebar.lock +++ b/rebar.lock @@ -9,8 +9,8 @@ {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.11.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"1d60b20f2136938c43902dd38a80ae70f03e4a14"}}, - 0}, + {ref,"33c5665571042440ccec109735481d8c13704ec2"}}, + 1}, {<<"erl_health">>, {git,"https://github.com/valitydev/erlang-health.git", {ref,"5958e2f35cd4d09f40685762b82b82f89b4d9333"}}, @@ -25,16 +25,16 @@ {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},1}, {<<"limiter_proto">>, {git,"https://github.com/valitydev/limiter-proto.git", - {ref,"ac9705389211682263c0a983ae76f663d4857ec9"}}, + {ref,"61581846b4d41de3a9e561c79f558e74450ab950"}}, 0}, {<<"machinery">>, {git,"https://github.com/valitydev/machinery-erlang.git", - {ref,"ff4cfefb616250f6905c25e79f74a7a30eb1aae5"}}, + {ref,"62c32434c80a462956ad9d50f9bce47836580d77"}}, 0}, {<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},2}, {<<"mg_proto">>, {git,"https://github.com/valitydev/machinegun-proto.git", - {ref,"b43d6fd0939ee4029ec8873dbd16f3c5fbe4a95c"}}, + {ref,"347c5c44c8dcca24a50e0509c0df5401f863e790"}}, 1}, {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.2.0">>},2}, {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.1">>},2}, From 40e4b22ce744b48efac25145c87ded34e7168815 Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Tue, 12 Jul 2022 11:08:26 +0300 Subject: [PATCH 23/54] Reuse valitydev/action-deploy-docker@v2 (#13) --- .github/workflows/build-image.yml | 42 ++++--------------------------- 1 file changed, 5 insertions(+), 37 deletions(-) diff --git a/.github/workflows/build-image.yml b/.github/workflows/build-image.yml index 6f39e7c0..ff53b0e7 100644 --- a/.github/workflows/build-image.yml +++ b/.github/workflows/build-image.yml @@ -1,4 +1,4 @@ -name: Build Docker image +name: Build and publish Docker image on: push: @@ -12,42 +12,10 @@ env: REGISTRY: ghcr.io jobs: - build: + build-push: runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Setup Buildx - uses: docker/setup-buildx-action@v1 - - # https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions#setting-an-environment-variable - - name: Update environment variables - run: grep -v '^#' .env >> $GITHUB_ENV - - - name: Log in to the Container registry - uses: docker/login-action@v1.12.0 - with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Construct tags / labels for an image - id: meta - uses: docker/metadata-action@v3.6.2 - with: - images: | - ${{ env.REGISTRY }}/${{ github.repository }} - tags: | - type=sha - - - name: Build and push Docker image - uses: docker/build-push-action@v2.9.0 + - uses: valitydev/action-deploy-docker@v2 with: - push: ${{ github.event_name == 'push' }} - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - build-args: | - SERVICE_NAME=${{ env.SERVICE_NAME }} - OTP_VERSION=${{ env.OTP_VERSION }} - THRIFT_VERSION=${{ env.THRIFT_VERSION }} + registry-username: ${{ github.actor }} + registry-access-token: ${{ secrets.GITHUB_TOKEN }} From cd37acdcea16165ffb7e403e0af8e9e3f3ff22dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D0=B5=D0=BC?= Date: Fri, 5 Aug 2022 22:29:03 +0300 Subject: [PATCH 24/54] added party scope test (#14) --- apps/limiter/test/lim_turnover_SUITE.erl | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index b527a703..978a3661 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -39,6 +39,8 @@ -export([commit_refund_keep_number_unchanged/1]). -export([partial_commit_number_counts_as_single_op/1]). +-export([party_limit_commit_ok/1]). + -type group_name() :: atom(). -type test_case_name() :: atom(). @@ -71,13 +73,15 @@ groups() -> commit_inexistent_hold_fails, partial_commit_inexistent_hold_fails, commit_multirange_limit_ok, - commit_with_payment_tool_scope_ok + commit_with_payment_tool_scope_ok, + party_limit_commit_ok ]}, {withdrawals, [parallel], [ get_limit_ok, hold_ok, commit_ok, - rollback_ok + rollback_ok, + party_limit_commit_ok ]}, {cashless, [parallel], [ commit_number_ok, @@ -460,6 +464,19 @@ partial_commit_number_counts_as_single_op(C) -> %% +-spec party_limit_commit_ok(config()) -> _. +party_limit_commit_ok(C) -> + ID = configure_limit(?time_range_month(), ?scope([?scope_party()]), C), + Context = + case get_group_name(C) of + default -> ?payproc_ctx_invoice(?cash(10, <<"RUB">>)); + withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)) + end, + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), + {ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)). + +%% + gen_change_id(LimitID, ChangeID) -> genlib:format("~s/~p", [LimitID, ChangeID]). From edbdccb67dbfae9d760174766e9e6f4ee556d9f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D0=B5=D0=BC?= Date: Sun, 2 Oct 2022 19:37:04 +0400 Subject: [PATCH 25/54] TD-407: Add provider, terminal, wallet, identity, email scopes (#15) * added new scopes * added identity and wallet scope tests * switched to proto master * fixed * added multu scope test * added prefix * added commands * finished with new scope ordering system * added requested changes * applied patch --- apps/limiter/src/lim_config_codec.erl | 16 +- apps/limiter/src/lim_config_machine.erl | 188 +++++++++++++++---- apps/limiter/src/lim_context_utils.erl | 22 +++ apps/limiter/src/lim_payproc_context.erl | 52 +++++ apps/limiter/src/lim_wthdproc_context.erl | 43 +++++ apps/limiter/test/lim_configurator_SUITE.erl | 20 ++ apps/limiter/test/lim_ct_helper.hrl | 27 ++- apps/limiter/test/lim_turnover_SUITE.erl | 92 ++++++++- rebar.lock | 2 +- 9 files changed, 416 insertions(+), 46 deletions(-) create mode 100644 apps/limiter/src/lim_context_utils.erl diff --git a/apps/limiter/src/lim_config_codec.erl b/apps/limiter/src/lim_config_codec.erl index 52e3452b..1cd609ad 100644 --- a/apps/limiter/src/lim_config_codec.erl +++ b/apps/limiter/src/lim_config_codec.erl @@ -124,7 +124,13 @@ marshal_scope_type(wallet) -> marshal_scope_type(identity) -> {identity, #config_LimitScopeEmptyDetails{}}; marshal_scope_type(payment_tool) -> - {payment_tool, #config_LimitScopeEmptyDetails{}}. + {payment_tool, #config_LimitScopeEmptyDetails{}}; +marshal_scope_type(provider) -> + {provider, #config_LimitScopeEmptyDetails{}}; +marshal_scope_type(terminal) -> + {terminal, #config_LimitScopeEmptyDetails{}}; +marshal_scope_type(payer_contact_email) -> + {payer_contact_email, #config_LimitScopeEmptyDetails{}}. %% @@ -277,7 +283,13 @@ unmarshal_scope_type({wallet, _}) -> unmarshal_scope_type({identity, _}) -> identity; unmarshal_scope_type({payment_tool, _}) -> - payment_tool. + payment_tool; +unmarshal_scope_type({provider, _}) -> + provider; +unmarshal_scope_type({terminal, _}) -> + terminal; +unmarshal_scope_type({payer_contact_email, _}) -> + payer_contact_email. %% diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index 5a567483..dc8a27eb 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -38,7 +38,7 @@ -type limit_type() :: {turnover, lim_turnover_metric:t()}. -type limit_scope() :: ordsets:ordset(limit_scope_type()). --type limit_scope_type() :: party | shop | wallet | identity | payment_tool. +-type limit_scope_type() :: party | shop | wallet | identity | payment_tool | provider | terminal | payer_contact_email. -type shard_size() :: pos_integer(). -type shard_id() :: binary(). -type prefix() :: binary(). @@ -527,7 +527,7 @@ mk_scope_prefix(Config, LimitContext) -> -spec mk_scope_prefix_impl(limit_scope(), context_type(), lim_context()) -> prefix(). mk_scope_prefix_impl(Scope, ContextType, LimitContext) -> Bits = enumerate_context_bits(Scope), - ordsets:fold( + lists:foldl( fun(Bit, Acc) -> {ok, Value} = extract_context_bit(Bit, ContextType, LimitContext), append_prefix(Value, Acc) @@ -542,34 +542,55 @@ append_prefix(Fragment, Acc) -> -type context_bit() :: {from, _ValueName :: atom()} - | {order, integer(), context_bit()}. + | {prefix, prefix()}. --spec enumerate_context_bits(limit_scope()) -> ordsets:ordset(context_bit()). +-spec enumerate_context_bits(limit_scope()) -> [context_bit()]. enumerate_context_bits(Types) -> - ordsets:fold(fun append_context_bits/2, ordsets:new(), Types). - -append_context_bits(party, Bits) -> - ordsets:add_element( - {order, 1, {from, owner_id}}, - Bits - ); -append_context_bits(shop, Bits) -> - lists:foldl(fun ordsets:add_element/2, Bits, [ - % NOTE - % Shop scope implies party scope. - % Also we need to preserve order between party / shop to ensure backwards compatibility. - {order, 1, {from, owner_id}}, - {order, 2, {from, shop_id}} - ]); -append_context_bits(payment_tool, Bits) -> - ordsets:add_element( - {from, payment_tool}, - Bits - ). + TypesOrder = [party, shop, identity, wallet, payment_tool, provider, terminal, payer_contact_email], + SortedTypes = lists:filter(fun(T) -> ordsets:is_element(T, Types) end, TypesOrder), + SquashedTypes = squash_scope_types(SortedTypes), + lists:flatmap(fun get_context_bits/1, SquashedTypes). + +squash_scope_types([party, shop | Rest]) -> + % NOTE + % Shop scope implies party scope. + [shop | squash_scope_types(Rest)]; +squash_scope_types([identity, wallet | Rest]) -> + % NOTE + % Wallet scope implies identity scope. + [wallet | squash_scope_types(Rest)]; +squash_scope_types([provider, terminal | Rest]) -> + % NOTE + % Provider scope implies identity scope. + [terminal | squash_scope_types(Rest)]; +squash_scope_types([Type | Rest]) -> + [Type | squash_scope_types(Rest)]; +squash_scope_types([]) -> + []. + +-spec get_context_bits(limit_scope_type()) -> [context_bit()]. +get_context_bits(party) -> + [{from, owner_id}]; +get_context_bits(shop) -> + % NOTE + % We need to preserve order between party / shop to ensure backwards compatibility. + [{from, owner_id}, {from, shop_id}]; +get_context_bits(payment_tool) -> + [{from, payment_tool}]; +get_context_bits(identity) -> + [{prefix, <<"identity">>}, {from, identity_id}]; +get_context_bits(wallet) -> + [{prefix, <<"wallet">>}, {from, identity_id}, {from, wallet_id}]; +get_context_bits(provider) -> + [{prefix, <<"provider">>}, {from, provider_id}]; +get_context_bits(terminal) -> + [{prefix, <<"terminal">>}, {from, provider_id}, {from, terminal_id}]; +get_context_bits(payer_contact_email) -> + [{prefix, <<"payer_contact_email">>}, {from, payer_contact_email}]. -spec extract_context_bit(context_bit(), context_type(), lim_context()) -> {ok, binary()}. -extract_context_bit({order, _, Bit}, ContextType, LimitContext) -> - extract_context_bit(Bit, ContextType, LimitContext); +extract_context_bit({prefix, Prefix}, _ContextType, _LimitContext) -> + {ok, Prefix}; extract_context_bit({from, payment_tool}, ContextType, LimitContext) -> {ok, PaymentTool} = lim_context:get_value(ContextType, payment_tool, LimitContext), case PaymentTool of @@ -643,7 +664,10 @@ apply_event_({created, Config}, undefined) -> -include_lib("eunit/include/eunit.hrl"). -include_lib("limiter_proto/include/limproto_context_payproc_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_context_withdrawal_thrift.hrl"). -include_lib("damsel/include/dmsl_domain_thrift.hrl"). +-include_lib("damsel/include/dmsl_wthd_domain_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_base_thrift.hrl"). -spec test() -> _. @@ -787,15 +811,27 @@ check_calculate_year_shard_id_test() -> ?assertEqual(<<"past/year/1">>, calculate_calendar_shard_id(year, <<"2000-01-01T00:00:00Z">>, StartedAt2, 1)), ?assertEqual(<<"future/year/0">>, calculate_calendar_shard_id(year, <<"2001-01-01T00:00:00Z">>, StartedAt2, 1)). --define(PAYPROC_CTX_INVOICE(Invoice), #limiter_LimitContext{ +-define(PAYPROC_CTX_INVOICE(Invoice, Payment, Route), #limiter_LimitContext{ payment_processing = #context_payproc_Context{ - op = {invoice, #context_payproc_OperationInvoice{}}, + op = {invoice_payment, #context_payproc_OperationInvoicePayment{}}, invoice = #context_payproc_Invoice{ - invoice = Invoice + invoice = Invoice, + payment = #context_payproc_InvoicePayment{ + payment = Payment, + route = Route + } } } }). +-define(PAYPROC_CTX_INVOICE(Invoice), + ?PAYPROC_CTX_INVOICE( + Invoice, + ?PAYMENT(?PAYER(?PAYMENT_TOOL)), + ?ROUTE(22, 2) + ) +). + -define(INVOICE(OwnerID, ShopID), #domain_Invoice{ id = <<"ID">>, owner_id = OwnerID, @@ -807,6 +843,55 @@ check_calculate_year_shard_id_test() -> cost = #domain_Cash{amount = 42, currency = #domain_CurrencyRef{symbolic_code = <<"CNY">>}} }). +-define(PAYMENT(Payer), #domain_InvoicePayment{ + id = <<"ID">>, + created_at = <<"2000-02-02T12:12:12Z">>, + status = {pending, #domain_InvoicePaymentPending{}}, + cost = #domain_Cash{amount = 42, currency = #domain_CurrencyRef{symbolic_code = <<"CNY">>}}, + domain_revision = 1, + flow = {instant, #domain_InvoicePaymentFlowInstant{}}, + payer = Payer +}). + +-define(PAYER(PaymentTool), + {payment_resource, #domain_PaymentResourcePayer{ + resource = #domain_DisposablePaymentResource{payment_tool = PaymentTool}, + contact_info = #domain_ContactInfo{email = <<"email">>} + }} +). + +-define(PAYMENT_TOOL, + {bank_card, #domain_BankCard{ + token = <<"token">>, + bin = <<"****">>, + last_digits = <<"last_digits">>, + exp_date = #domain_BankCardExpDate{month = 2, year = 2022} + }} +). + +-define(WITHDRAWAL_CTX(Withdrawal, WalletID, Route), #limiter_LimitContext{ + withdrawal_processing = #context_withdrawal_Context{ + op = {withdrawal, #context_withdrawal_OperationWithdrawal{}}, + withdrawal = #context_withdrawal_Withdrawal{ + withdrawal = Withdrawal, + wallet_id = WalletID, + route = Route + } + } +}). + +-define(WITHDRAWAL(OwnerID, IdentityID, PaymentTool), #wthd_domain_Withdrawal{ + destination = PaymentTool, + sender = #wthd_domain_Identity{id = IdentityID, owner_id = OwnerID}, + created_at = <<"2000-02-02T12:12:12Z">>, + body = #domain_Cash{amount = 42, currency = #domain_CurrencyRef{symbolic_code = <<"CNY">>}} +}). + +-define(ROUTE(ProviderID, TerminalID), #base_Route{ + provider = #domain_ProviderRef{id = ProviderID}, + terminal = #domain_TerminalRef{id = TerminalID} +}). + -spec global_scope_empty_prefix_test() -> _. global_scope_empty_prefix_test() -> Context = #{context => ?PAYPROC_CTX_INVOICE(?INVOICE(<<"OWNER">>, <<"SHOP">>))}, @@ -814,16 +899,55 @@ global_scope_empty_prefix_test() -> -spec preserve_scope_prefix_order_test_() -> [_TestGen]. preserve_scope_prefix_order_test_() -> - Context = #{context => ?PAYPROC_CTX_INVOICE(?INVOICE(<<"OWNER">>, <<"SHLOP">>))}, + Context = #{context => ?PAYPROC_CTX_INVOICE(?INVOICE(<<"OWNER">>, <<"SHOP">>))}, [ ?_assertEqual( - <<"/OWNER/SHLOP">>, + <<"/OWNER/SHOP">>, mk_scope_prefix_impl(ordsets:from_list([shop, party]), payment_processing, Context) ), ?_assertEqual( - <<"/OWNER/SHLOP">>, + <<"/OWNER/SHOP">>, + mk_scope_prefix_impl(ordsets:from_list([party, shop]), payment_processing, Context) + ), + ?_assertEqual( + <<"/OWNER/SHOP">>, mk_scope_prefix_impl(ordsets:from_list([shop]), payment_processing, Context) ) ]. +-spec prefix_content_test_() -> [_TestGen]. +prefix_content_test_() -> + Context = #{ + context => ?PAYPROC_CTX_INVOICE( + ?INVOICE(<<"OWNER">>, <<"SHOP">>), + ?PAYMENT(?PAYER(?PAYMENT_TOOL)), + ?ROUTE(22, 2) + ) + }, + WithdrawalContext = #{ + context => ?WITHDRAWAL_CTX( + ?WITHDRAWAL(<<"OWNER">>, <<"IDENTITY">>, ?PAYMENT_TOOL), + <<"WALLET">>, + ?ROUTE(22, 2) + ) + }, + [ + ?_assertEqual( + <<"/terminal/22/2">>, + mk_scope_prefix_impl(ordsets:from_list([terminal, provider]), payment_processing, Context) + ), + ?_assertEqual( + <<"/terminal/22/2">>, + mk_scope_prefix_impl(ordsets:from_list([provider, terminal]), payment_processing, Context) + ), + ?_assertEqual( + <<"/OWNER/wallet/IDENTITY/WALLET">>, + mk_scope_prefix_impl(ordsets:from_list([wallet, identity, party]), withdrawal_processing, WithdrawalContext) + ), + ?_assertEqual( + <<"/token/2/2022/payer_contact_email/email">>, + mk_scope_prefix_impl(ordsets:from_list([payer_contact_email, payment_tool]), payment_processing, Context) + ) + ]. + -endif. diff --git a/apps/limiter/src/lim_context_utils.erl b/apps/limiter/src/lim_context_utils.erl new file mode 100644 index 00000000..7edb084a --- /dev/null +++ b/apps/limiter/src/lim_context_utils.erl @@ -0,0 +1,22 @@ +-module(lim_context_utils). + +-include_lib("limiter_proto/include/limproto_base_thrift.hrl"). +-include_lib("damsel/include/dmsl_domain_thrift.hrl"). + +-export([route_provider_id/1]). +-export([route_terminal_id/1]). + +-type provider_id() :: binary(). +-type terminal_id() :: binary(). + +%% + +-spec route_provider_id(limproto_base_thrift:'Route'()) -> + {ok, provider_id()}. +route_provider_id(#base_Route{provider = #domain_ProviderRef{id = ID}}) -> + {ok, genlib:to_binary(ID)}. + +-spec route_terminal_id(limproto_base_thrift:'Route'()) -> + {ok, terminal_id()}. +route_terminal_id(#base_Route{terminal = #domain_TerminalRef{id = ID}}) -> + {ok, genlib:to_binary(ID)}. diff --git a/apps/limiter/src/lim_payproc_context.erl b/apps/limiter/src/lim_payproc_context.erl index 07664658..7458ab92 100644 --- a/apps/limiter/src/lim_payproc_context.erl +++ b/apps/limiter/src/lim_payproc_context.erl @@ -1,6 +1,7 @@ -module(lim_payproc_context). -include_lib("limiter_proto/include/limproto_context_payproc_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_base_thrift.hrl"). -include_lib("damsel/include/dmsl_domain_thrift.hrl"). -behaviour(lim_context). @@ -49,6 +50,12 @@ get_value(capture_cost, Operation, Context) -> get_capture_cost(Operation, Context); get_value(payment_tool, Operation, Context) -> get_payment_tool(Operation, Context); +get_value(provider_id, Operation, Context) -> + get_provider_id(Operation, Context); +get_value(terminal_id, Operation, Context) -> + get_terminal_id(Operation, Context); +get_value(payer_contact_email, Operation, Context) -> + get_payer_contact_email(Operation, Context); get_value(ValueName, _Operation, _Context) -> {error, {unsupported, ValueName}}. @@ -90,6 +97,12 @@ get_value(ValueName, _Operation, _Context) -> } }). +-define(INVOICE_PAYMENT_ROUTE(V), #context_payproc_Context{ + invoice = #context_payproc_Invoice{ + payment = #context_payproc_InvoicePayment{route = V = #base_Route{}} + } +}). + get_owner_id(?INVOICE(Invoice)) -> {ok, Invoice#domain_Invoice.owner_id}; get_owner_id(_) -> @@ -154,6 +167,45 @@ get_payer_payment_tool(#domain_CustomerPayer{payment_tool = PT}) -> get_payer_payment_tool(#domain_RecurrentPayer{payment_tool = PT}) -> lim_payproc_utils:payment_tool(PT). +get_provider_id(Operation, ?INVOICE_PAYMENT_ROUTE(Route)) when + Operation == invoice_payment; + Operation == invoice_payment_adjustment; + Operation == invoice_payment_refund; + Operation == invoice_payment_chargeback +-> + lim_context_utils:route_provider_id(Route); +get_provider_id(_, _CtxInvoice) -> + {error, notfound}. + +get_terminal_id(Operation, ?INVOICE_PAYMENT_ROUTE(Route)) when + Operation == invoice_payment; + Operation == invoice_payment_adjustment; + Operation == invoice_payment_refund; + Operation == invoice_payment_chargeback +-> + lim_context_utils:route_terminal_id(Route); +get_terminal_id(_, _CtxInvoice) -> + {error, notfound}. + +get_payer_contact_email(Operation, ?INVOICE_PAYMENT(Payment)) when + Operation == invoice_payment; + Operation == invoice_payment_adjustment; + Operation == invoice_payment_refund; + Operation == invoice_payment_chargeback +-> + {_Type, Payer} = Payment#domain_InvoicePayment.payer, + CI = get_payer_contact_info(Payer), + {ok, string:lowercase(CI#domain_ContactInfo.email)}; +get_payer_contact_email(_, _CtxInvoice) -> + {error, notfound}. + +get_payer_contact_info(#domain_PaymentResourcePayer{contact_info = CI}) -> + CI; +get_payer_contact_info(#domain_CustomerPayer{contact_info = CI}) -> + CI; +get_payer_contact_info(#domain_RecurrentPayer{contact_info = CI}) -> + CI. + -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). diff --git a/apps/limiter/src/lim_wthdproc_context.erl b/apps/limiter/src/lim_wthdproc_context.erl index 123716f2..2b325441 100644 --- a/apps/limiter/src/lim_wthdproc_context.erl +++ b/apps/limiter/src/lim_wthdproc_context.erl @@ -1,7 +1,9 @@ -module(lim_wthdproc_context). -include_lib("limiter_proto/include/limproto_context_withdrawal_thrift.hrl"). +-include_lib("limiter_proto/include/limproto_base_thrift.hrl"). -include_lib("damsel/include/dmsl_wthd_domain_thrift.hrl"). +-include_lib("damsel/include/dmsl_domain_thrift.hrl"). -behaviour(lim_context). -export([get_operation/1]). @@ -40,6 +42,14 @@ get_value(cost, Operation, Context) -> get_cost(Operation, Context); get_value(payment_tool, Operation, Context) -> get_payment_tool(Operation, Context); +get_value(identity_id, Operation, Context) -> + get_identity_id(Operation, Context); +get_value(wallet_id, Operation, Context) -> + get_wallet_id(Operation, Context); +get_value(provider_id, Operation, Context) -> + get_provider_id(Operation, Context); +get_value(terminal_id, Operation, Context) -> + get_terminal_id(Operation, Context); get_value(ValueName, _Operation, _Context) -> {error, {unsupported, ValueName}}. @@ -49,6 +59,18 @@ get_value(ValueName, _Operation, _Context) -> } }). +-define(WALLET_ID(V), #context_withdrawal_Context{ + withdrawal = #context_withdrawal_Withdrawal{ + wallet_id = V + } +}). + +-define(ROUTE(V), #context_withdrawal_Context{ + withdrawal = #context_withdrawal_Withdrawal{ + route = V = #base_Route{} + } +}). + get_owner_id(?WITHDRAWAL(Wthd)) -> Identity = Wthd#wthd_domain_Withdrawal.sender, {ok, Identity#wthd_domain_Identity.owner_id}; @@ -71,3 +93,24 @@ get_payment_tool(withdrawal, ?WITHDRAWAL(Wthd)) -> lim_payproc_utils:payment_tool(Destination); get_payment_tool(_, _CtxWithdrawal) -> {error, notfound}. + +get_identity_id(withdrawal, ?WITHDRAWAL(Wthd)) -> + Identity = Wthd#wthd_domain_Withdrawal.sender, + {ok, Identity#wthd_domain_Identity.id}; +get_identity_id(_, _CtxWithdrawal) -> + {error, notfound}. + +get_wallet_id(withdrawal, ?WALLET_ID(WalletID)) -> + {ok, WalletID}; +get_wallet_id(_, _CtxWithdrawal) -> + {error, notfound}. + +get_provider_id(withdrawal, ?ROUTE(Route)) -> + lim_context_utils:route_provider_id(Route); +get_provider_id(_, _CtxWithdrawal) -> + {error, notfound}. + +get_terminal_id(withdrawal, ?ROUTE(Route)) -> + lim_context_utils:route_terminal_id(Route); +get_terminal_id(_, _CtxWithdrawal) -> + {error, notfound}. diff --git a/apps/limiter/test/lim_configurator_SUITE.erl b/apps/limiter/test/lim_configurator_SUITE.erl index cb98e490..525d2b59 100644 --- a/apps/limiter/test/lim_configurator_SUITE.erl +++ b/apps/limiter/test/lim_configurator_SUITE.erl @@ -16,6 +16,7 @@ -export([legacy_create_config/1]). -export([create_config/1]). -export([create_config_single_scope/1]). +-export([create_config_multi_scope/1]). -export([get_config/1]). -export([get_inexistent_config/1]). @@ -37,6 +38,7 @@ groups() -> legacy_create_config, create_config, create_config_single_scope, + create_config_multi_scope, get_config, get_inexistent_config ]} @@ -142,6 +144,24 @@ create_config_single_scope(C) -> }} = lim_client:create_config(Params, Client), ?assertEqual(?scope([?scope_party()]), Scope). +-spec create_config_multi_scope(config()) -> _. +create_config_multi_scope(C) -> + Client = lim_client:new(), + Params = #config_LimitConfigParams{ + id = ?config(limit_id, C), + started_at = <<"2000-01-01T00:00:00Z">>, + time_range_type = ?time_range_week(), + shard_size = 1, + type = ?lim_type_turnover(), + scope = {multi, ordsets:from_list([?scope_terminal(), ?scope_payment_tool()])}, + context_type = ?ctx_type_payproc(), + op_behaviour = ?op_behaviour() + }, + {ok, #config_LimitConfig{ + scope = Scope + }} = lim_client:create_config(Params, Client), + ?assertEqual(?scope([?scope_terminal(), ?scope_payment_tool()]), Scope). + -spec get_config(config()) -> _. get_config(C) -> ID = ?config(limit_id, C), diff --git a/apps/limiter/test/lim_ct_helper.hrl b/apps/limiter/test/lim_ct_helper.hrl index eaa5d8c2..47eed89f 100644 --- a/apps/limiter/test/lim_ct_helper.hrl +++ b/apps/limiter/test/lim_ct_helper.hrl @@ -1,6 +1,7 @@ -ifndef(__limiter_ct_helper__). -define(__limiter_ct_helper__, 42). +-include_lib("limiter_proto/include/limproto_base_thrift.hrl"). -include_lib("limiter_proto/include/limproto_config_thrift.hrl"). -include_lib("limiter_proto/include/limproto_timerange_thrift.hrl"). -include_lib("limiter_proto/include/limproto_limiter_thrift.hrl"). @@ -11,6 +12,7 @@ -define(currency, <<"RUB">>). -define(string, <<"STRING">>). +-define(integer, 999). -define(timestamp, <<"2000-01-01T00:00:00Z">>). -define(cash(Amount), ?cash(Amount, ?currency)). @@ -19,12 +21,23 @@ currency = #domain_CurrencyRef{symbolic_code = Currency} }). +-define(route(), ?route(?integer, ?integer)). +-define(route(ProviderID, TerminalID), #base_Route{ + provider = #domain_ProviderRef{id = ProviderID}, + terminal = #domain_TerminalRef{id = TerminalID} +}). + -define(scope(Types), {multi, ordsets:from_list(Types)}). -define(global(), ?scope([])). -define(scope_party(), {party, #config_LimitScopeEmptyDetails{}}). -define(scope_shop(), {shop, #config_LimitScopeEmptyDetails{}}). -define(scope_payment_tool(), {payment_tool, #config_LimitScopeEmptyDetails{}}). +-define(scope_provider(), {provider, #config_LimitScopeEmptyDetails{}}). +-define(scope_terminal(), {terminal, #config_LimitScopeEmptyDetails{}}). +-define(scope_payer_contact_email(), {payer_contact_email, #config_LimitScopeEmptyDetails{}}). +-define(scope_identity(), {identity, #config_LimitScopeEmptyDetails{}}). +-define(scope_wallet(), {wallet, #config_LimitScopeEmptyDetails{}}). -define(lim_type_turnover(), ?lim_type_turnover(?turnover_metric_number())). -define(lim_type_turnover(Metric), @@ -129,7 +142,9 @@ resource = #domain_DisposablePaymentResource{ payment_tool = PaymentTool }, - contact_info = #domain_ContactInfo{} + contact_info = #domain_ContactInfo{ + email = ?string + } }} }). @@ -152,7 +167,8 @@ invoice = #context_payproc_Invoice{ invoice = ?invoice(OwnerID, ShopID, Cost), payment = #context_payproc_InvoicePayment{ - payment = ?invoice_payment(Cost, CaptureCost) + payment = ?invoice_payment(Cost, CaptureCost), + route = ?route() } } } @@ -164,7 +180,8 @@ invoice = #context_payproc_Invoice{ invoice = ?invoice(?string, ?string, ?cash(10)), payment = #context_payproc_InvoicePayment{ - payment = Payment + payment = Payment, + route = ?route() } } } @@ -211,7 +228,9 @@ withdrawal_processing = #context_withdrawal_Context{ op = ?op_withdrawal, withdrawal = #context_withdrawal_Withdrawal{ - withdrawal = ?withdrawal(Cost) + withdrawal = ?withdrawal(Cost), + route = ?route(), + wallet_id = ?string } } }). diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index 978a3661..5c58b183 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -39,7 +39,14 @@ -export([commit_refund_keep_number_unchanged/1]). -export([partial_commit_number_counts_as_single_op/1]). --export([party_limit_commit_ok/1]). +-export([commit_with_party_scope_ok/1]). +-export([commit_with_provider_scope_ok/1]). +-export([commit_with_terminal_scope_ok/1]). +-export([commit_with_email_scope_ok/1]). + +-export([commit_with_identity_scope_ok/1]). +-export([commit_with_wallet_scope_ok/1]). +-export([commit_with_multi_scope_ok/1]). -type group_name() :: atom(). -type test_case_name() :: atom(). @@ -74,14 +81,22 @@ groups() -> partial_commit_inexistent_hold_fails, commit_multirange_limit_ok, commit_with_payment_tool_scope_ok, - party_limit_commit_ok + commit_with_party_scope_ok, + commit_with_provider_scope_ok, + commit_with_terminal_scope_ok, + commit_with_email_scope_ok, + commit_with_multi_scope_ok ]}, {withdrawals, [parallel], [ get_limit_ok, hold_ok, commit_ok, rollback_ok, - party_limit_commit_ok + commit_with_party_scope_ok, + commit_with_provider_scope_ok, + commit_with_terminal_scope_ok, + commit_with_identity_scope_ok, + commit_with_wallet_scope_ok ]}, {cashless, [parallel], [ commit_number_ok, @@ -464,17 +479,80 @@ partial_commit_number_counts_as_single_op(C) -> %% --spec party_limit_commit_ok(config()) -> _. -party_limit_commit_ok(C) -> - ID = configure_limit(?time_range_month(), ?scope([?scope_party()]), C), +-spec commit_with_party_scope_ok(config()) -> _. +commit_with_party_scope_ok(C) -> + _ = commit_with_some_scope(?scope([?scope_party()]), C). + +-spec commit_with_provider_scope_ok(config()) -> _. +commit_with_provider_scope_ok(C) -> + _ = commit_with_some_scope(?scope([?scope_provider()]), C). + +-spec commit_with_terminal_scope_ok(config()) -> _. +commit_with_terminal_scope_ok(C) -> + _ = commit_with_some_scope(?scope([?scope_terminal()]), C). + +commit_with_some_scope(Scope, C) -> + ID = configure_limit(?time_range_month(), Scope, C), Context = case get_group_name(C) of - default -> ?payproc_ctx_invoice(?cash(10, <<"RUB">>)); + default -> ?payproc_ctx_payment(?cash(10, <<"RUB">>), ?cash(10, <<"RUB">>)); withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)) end, {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), {ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)). +-spec commit_with_email_scope_ok(config()) -> _. +commit_with_email_scope_ok(C) -> + ID = configure_limit(?time_range_month(), ?scope([?scope_payer_contact_email()]), C), + Context = ?payproc_ctx_payment(?cash(10, <<"RUB">>), ?cash(10, <<"RUB">>)), + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), + {ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)). + +-spec commit_with_identity_scope_ok(config()) -> _. +commit_with_identity_scope_ok(C) -> + ID = configure_limit(?time_range_month(), ?scope([?scope_identity()]), C), + Context = ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)), + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), + {ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)). + +-spec commit_with_wallet_scope_ok(config()) -> _. +commit_with_wallet_scope_ok(C) -> + ID = configure_limit(?time_range_month(), ?scope([?scope_wallet()]), C), + Context = ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)), + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), + {ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)). + +-spec commit_with_multi_scope_ok(config()) -> _. +commit_with_multi_scope_ok(C) -> + Client = ?config(client, C), + ID = configure_limit(?time_range_week(), ?scope([?scope_provider(), ?scope_payment_tool()]), C), + Context1 = ?payproc_ctx_payment( + ?invoice_payment(?cash(10), ?cash(10), ?bank_card(<<"Token">>, 2, 2022)) + ), + Context2 = ?payproc_ctx_payment( + ?invoice_payment(?cash(10), ?cash(10), ?bank_card(<<"OtherToken">>, 2, 2022)) + ), + Context3 = ?payproc_ctx_payment( + ?invoice_payment(?cash(10), ?cash(10), ?bank_card(?string, 3, 2022)) + ), + Context4 = ?payproc_ctx_payment( + ?invoice_payment(?cash(10), ?cash(10), ?bank_card(?string)) + ), + Context5 = ?payproc_ctx_payment( + ?invoice_payment(?cash(10), ?cash(10), ?digital_wallet(<<"ID42">>, <<"Pepal">>)) + ), + {ok, LimitState0} = lim_client:get(ID, Context1, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 1), Context1, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 2), Context2, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 3), Context3, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 4), Context4, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 5), Context5, Client), + {ok, LimitState1} = lim_client:get(ID, Context1, Client), + ?assertEqual( + LimitState1#limiter_Limit.amount, + LimitState0#limiter_Limit.amount + 10 + ). + %% gen_change_id(LimitID, ChangeID) -> diff --git a/rebar.lock b/rebar.lock index 3de7d897..676e7188 100644 --- a/rebar.lock +++ b/rebar.lock @@ -25,7 +25,7 @@ {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},1}, {<<"limiter_proto">>, {git,"https://github.com/valitydev/limiter-proto.git", - {ref,"61581846b4d41de3a9e561c79f558e74450ab950"}}, + {ref,"31de59b17ad20e426b158ace6097e35330926bea"}}, 0}, {<<"machinery">>, {git,"https://github.com/valitydev/machinery-erlang.git", From daf099b4715471b40a03cd915957848629a2e917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D0=B5=D0=BC?= Date: Fri, 21 Oct 2022 14:52:07 +0400 Subject: [PATCH 26/54] TD-427: Add plan id wrap with hash (#16) * added plan id wrap with hash * removed used mock * fixed * encoded --- apps/limiter/src/lim_accounting.erl | 18 ++++++++++++++---- apps/limiter/test/lim_turnover_SUITE.erl | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/apps/limiter/src/lim_accounting.erl b/apps/limiter/src/lim_accounting.erl index 39366c9d..397d7eeb 100644 --- a/apps/limiter/src/lim_accounting.erl +++ b/apps/limiter/src/lim_accounting.erl @@ -57,19 +57,19 @@ plan(PlanID, Batches, LimitContext) -> -spec hold(plan_id(), batch(), lim_context()) -> ok | {error, invalid_request_error()}. hold(PlanID, Batch, LimitContext) -> - do('Hold', construct_plan_change(PlanID, Batch), LimitContext). + do('Hold', construct_plan_change(wrap_plan_id(PlanID), Batch), LimitContext). -spec commit(plan_id(), [batch()], lim_context()) -> ok | {error, invalid_request_error()}. commit(PlanID, Batches, LimitContext) -> - do('CommitPlan', construct_plan(PlanID, Batches), LimitContext). + do('CommitPlan', construct_plan(wrap_plan_id(PlanID), Batches), LimitContext). -spec rollback(plan_id(), [batch()], lim_context()) -> ok | {error, invalid_request_error()}. rollback(PlanID, Batches, LimitContext) -> - do('RollbackPlan', construct_plan(PlanID, Batches), LimitContext). + do('RollbackPlan', construct_plan(wrap_plan_id(PlanID), Batches), LimitContext). -spec get_plan(plan_id(), lim_context()) -> {ok, [batch()]} | {error, notfound}. get_plan(PlanID, LimitContext) -> - case call_accounter('GetPlan', {PlanID}, LimitContext) of + case call_accounter('GetPlan', {wrap_plan_id(PlanID)}, LimitContext) of {ok, #accounter_PostingPlan{batch_list = BatchList}} -> {ok, decode_batch_list(BatchList)}; {exception, #accounter_PlanNotFound{}} -> @@ -157,6 +157,16 @@ construct_prototype(CurrencyCode, Description) -> %% +wrap_plan_id(PlanID) -> + %% Accounter requires max 64 byte plan id + case byte_size(PlanID) < 64 of + true -> + %% For backward compatibility + PlanID; + false -> + base64:encode(crypto:hash(sha384, PlanID)) + end. + call_accounter(Function, Args, LimitContext) -> WoodyContext = lim_context:woody_context(LimitContext), lim_client_woody:call(accounter, Function, Args, WoodyContext). diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index 5c58b183..85893bc4 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -13,6 +13,7 @@ -export([init_per_testcase/2]). -export([end_per_testcase/2]). +-export([commit_with_long_change_id/1]). -export([commit_with_default_exchange/1]). -export([partial_commit_with_exchange/1]). -export([commit_with_exchange/1]). @@ -66,6 +67,7 @@ all() -> groups() -> [ {default, [], [ + commit_with_long_change_id, commit_with_default_exchange, partial_commit_with_exchange, commit_with_exchange, @@ -163,6 +165,19 @@ end_per_testcase(_Name, C) -> -define(LIMIT_CHANGE(ID), ?LIMIT_CHANGE(ID, ?CHANGE_ID)). -define(LIMIT_CHANGE(ID, ChangeID), #limiter_LimitChange{id = ID, change_id = gen_change_id(ID, ChangeID)}). +-spec commit_with_long_change_id(config()) -> _. +commit_with_long_change_id(C) -> + ID = configure_limit(?time_range_month(), ?global(), C), + Context = ?payproc_ctx_invoice(?cash(10, <<"RUB">>)), + LongBinary = + << + "LongBinaryLongBinaryLongBinaryLongBinaryLongBinaryLong\n" + " BinaryLongBinaryLongBinaryLongBinaryLongBinaryLongBinary" + >>, + ChangeID = <>, + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ChangeID), Context, ?config(client, C)), + {ok, #limiter_Limit{amount = 10}} = lim_client:get(ID, Context, ?config(client, C)). + -spec commit_with_default_exchange(config()) -> _. commit_with_default_exchange(C) -> Rational = #base_Rational{p = 1000000, q = 100}, From 5c61eaff524dc88d48bdc0b193473e122d16eb3e Mon Sep 17 00:00:00 2001 From: ndiezel0 Date: Wed, 7 Dec 2022 21:36:15 +0500 Subject: [PATCH 27/54] Temporarily disable currency conversion (#17) * Temporarily disable currency conversion * Add details * Fix test and spec * Fix --- .github/workflows/erlang-checks.yml | 2 +- apps/limiter/src/lim_turnover_metric.erl | 24 ++++++++++++++++-------- apps/limiter/test/lim_turnover_SUITE.erl | 7 ++++--- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/.github/workflows/erlang-checks.yml b/.github/workflows/erlang-checks.yml index 4bb7abf8..5d855baf 100644 --- a/.github/workflows/erlang-checks.yml +++ b/.github/workflows/erlang-checks.yml @@ -30,7 +30,7 @@ jobs: run: name: Run checks needs: setup - uses: valitydev/erlang-workflows/.github/workflows/erlang-parallel-build.yml@v1.0.1 + uses: valitydev/erlang-workflows/.github/workflows/erlang-parallel-build.yml@v1.0.10 with: otp-version: ${{ needs.setup.outputs.otp-version }} rebar-version: ${{ needs.setup.outputs.rebar-version }} diff --git a/apps/limiter/src/lim_turnover_metric.erl b/apps/limiter/src/lim_turnover_metric.erl index 8ce2aa71..88ee5fe4 100644 --- a/apps/limiter/src/lim_turnover_metric.erl +++ b/apps/limiter/src/lim_turnover_metric.erl @@ -12,7 +12,7 @@ %% -spec compute(t(), stage(), lim_config_machine:config(), lim_context:t()) -> - {ok, amount()} | {error, lim_rates:conversion_error()}. + {ok, amount()} | {error, lim_accounting:invalid_request_error()}. compute(number, hold, Config, LimitContext) -> #{amount := Amount} = get_body(Config, LimitContext), {ok, sign(Amount)}; @@ -47,13 +47,21 @@ get_commit_body(Config, LimitContext) -> denominate(#{amount := Amount, currency := Currency}, Currency, _Config, _LimitContext) -> {ok, Amount}; -denominate(Body = #{}, DestinationCurrency, Config, LimitContext) -> - case lim_rates:convert(Body, DestinationCurrency, Config, LimitContext) of - {ok, #{amount := AmountConverted}} -> - {ok, AmountConverted}; - {error, _} = Error -> - Error - end. +denominate(#{currency := Currency}, DestinationCurrency, _Config, _LimitContext) -> + {error, + {invalid_request, + io_lib:format( + "Invalid operation currency: ~p, expected limit currency: ~p", + [Currency, DestinationCurrency] + )}}. +%% NOTE conversion disabled temporarily +%%denominate(Body = #{}, DestinationCurrency, Config, LimitContext) -> +%% case lim_rates:convert(Body, DestinationCurrency, Config, LimitContext) of +%% {ok, #{amount := AmountConverted}} -> +%% {ok, AmountConverted}; +%% {error, _} = Error -> +%% Error +%% end. sign(Amount) when Amount > 0 -> +1; diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index 85893bc4..cad662f5 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -68,9 +68,10 @@ groups() -> [ {default, [], [ commit_with_long_change_id, - commit_with_default_exchange, - partial_commit_with_exchange, - commit_with_exchange, + % NOTE disabled to stop exchange + %% commit_with_default_exchange, + %% partial_commit_with_exchange, + %% commit_with_exchange, get_limit_ok, get_limit_notfound, hold_ok, From 7c416e253303457466b648f3d960a693ca1dc723 Mon Sep 17 00:00:00 2001 From: ndiezel0 Date: Wed, 7 Dec 2022 22:13:08 +0500 Subject: [PATCH 28/54] Fix InvalidRequest (#18) * Fix InvalidRequest * Fix * Format --- apps/limiter/src/lim_turnover_metric.erl | 11 +++++++---- apps/limiter/test/lim_turnover_SUITE.erl | 7 ++++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/apps/limiter/src/lim_turnover_metric.erl b/apps/limiter/src/lim_turnover_metric.erl index 88ee5fe4..cf96bdfc 100644 --- a/apps/limiter/src/lim_turnover_metric.erl +++ b/apps/limiter/src/lim_turnover_metric.erl @@ -7,12 +7,14 @@ -type stage() :: hold | commit. -type t() :: number | {amount, currency()}. +-type invalid_request_error() :: {invalid_request, list(binary())}. + -export_type([t/0]). %% -spec compute(t(), stage(), lim_config_machine:config(), lim_context:t()) -> - {ok, amount()} | {error, lim_accounting:invalid_request_error()}. + {ok, amount()} | {error, invalid_request_error()}. compute(number, hold, Config, LimitContext) -> #{amount := Amount} = get_body(Config, LimitContext), {ok, sign(Amount)}; @@ -49,11 +51,12 @@ denominate(#{amount := Amount, currency := Currency}, Currency, _Config, _LimitC {ok, Amount}; denominate(#{currency := Currency}, DestinationCurrency, _Config, _LimitContext) -> {error, - {invalid_request, - io_lib:format( + {invalid_request, [ + genlib:format( "Invalid operation currency: ~p, expected limit currency: ~p", [Currency, DestinationCurrency] - )}}. + ) + ]}}. %% NOTE conversion disabled temporarily %%denominate(Body = #{}, DestinationCurrency, Config, LimitContext) -> %% case lim_rates:convert(Body, DestinationCurrency, Config, LimitContext) of diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index cad662f5..b0d22b5e 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -71,7 +71,7 @@ groups() -> % NOTE disabled to stop exchange %% commit_with_default_exchange, %% partial_commit_with_exchange, - %% commit_with_exchange, + commit_with_exchange, get_limit_ok, get_limit_notfound, hold_ok, @@ -207,8 +207,9 @@ commit_with_exchange(C) -> ID = configure_limit(?time_range_month(), ?global(), C), Cost = ?cash(10000, <<"USD">>), Context = ?payproc_ctx_invoice(Cost), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), - {ok, #limiter_Limit{amount = 10500}} = lim_client:get(ID, Context, ?config(client, C)). + {exception, #base_InvalidRequest{}} = lim_client:hold(?LIMIT_CHANGE(ID), Context, ?config(client, C)). +%% {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), +%% {ok, #limiter_Limit{amount = 10500}} = lim_client:get(ID, Context, ?config(client, C)). -spec get_limit_ok(config()) -> _. get_limit_ok(C) -> From d7febd557c4401fff7a79337393e8073ba65a20c Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Mon, 20 Feb 2023 11:29:50 +0300 Subject: [PATCH 29/54] TD-511: Adds app configuration flag to disable currency conversion (#19) * Adds app configuration flag to disable currency conversion * Revert turnover test suite * Adds testcase for disabled currency conversion * Changes currency_conversion default to disabled * Simplify currency conversion testcases setup --- apps/limiter/src/lim_turnover_metric.erl | 26 +++++++++++++++--------- apps/limiter/test/lim_turnover_SUITE.erl | 26 ++++++++++++++++++------ config/sys.config | 3 +++ 3 files changed, 39 insertions(+), 16 deletions(-) diff --git a/apps/limiter/src/lim_turnover_metric.erl b/apps/limiter/src/lim_turnover_metric.erl index cf96bdfc..d5a931f3 100644 --- a/apps/limiter/src/lim_turnover_metric.erl +++ b/apps/limiter/src/lim_turnover_metric.erl @@ -14,7 +14,7 @@ %% -spec compute(t(), stage(), lim_config_machine:config(), lim_context:t()) -> - {ok, amount()} | {error, invalid_request_error()}. + {ok, amount()} | {error, lim_rates:conversion_error()} | {error, invalid_request_error()}. compute(number, hold, Config, LimitContext) -> #{amount := Amount} = get_body(Config, LimitContext), {ok, sign(Amount)}; @@ -49,7 +49,13 @@ get_commit_body(Config, LimitContext) -> denominate(#{amount := Amount, currency := Currency}, Currency, _Config, _LimitContext) -> {ok, Amount}; -denominate(#{currency := Currency}, DestinationCurrency, _Config, _LimitContext) -> +denominate(Body = #{currency := Currency}, DestinationCurrency, Config, LimitContext) -> + case genlib_app:env(limiter, currency_conversion, disabled) of + disabled -> invalid_request_currencies_mismatch(Currency, DestinationCurrency); + enabled -> convert_currency(Body, DestinationCurrency, Config, LimitContext) + end. + +invalid_request_currencies_mismatch(Currency, DestinationCurrency) -> {error, {invalid_request, [ genlib:format( @@ -57,14 +63,14 @@ denominate(#{currency := Currency}, DestinationCurrency, _Config, _LimitContext) [Currency, DestinationCurrency] ) ]}}. -%% NOTE conversion disabled temporarily -%%denominate(Body = #{}, DestinationCurrency, Config, LimitContext) -> -%% case lim_rates:convert(Body, DestinationCurrency, Config, LimitContext) of -%% {ok, #{amount := AmountConverted}} -> -%% {ok, AmountConverted}; -%% {error, _} = Error -> -%% Error -%% end. + +convert_currency(Body, DestinationCurrency, Config, LimitContext) -> + case lim_rates:convert(Body, DestinationCurrency, Config, LimitContext) of + {ok, #{amount := AmountConverted}} -> + {ok, AmountConverted}; + {error, _} = Error -> + Error + end. sign(Amount) when Amount > 0 -> +1; diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index b0d22b5e..b343cc8b 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -17,6 +17,7 @@ -export([commit_with_default_exchange/1]). -export([partial_commit_with_exchange/1]). -export([commit_with_exchange/1]). +-export([commit_with_disabled_exchange/1]). -export([get_limit_ok/1]). -export([get_limit_notfound/1]). -export([hold_ok/1]). @@ -68,10 +69,10 @@ groups() -> [ {default, [], [ commit_with_long_change_id, - % NOTE disabled to stop exchange - %% commit_with_default_exchange, - %% partial_commit_with_exchange, + commit_with_default_exchange, + partial_commit_with_exchange, commit_with_exchange, + commit_with_disabled_exchange, get_limit_ok, get_limit_notfound, hold_ok, @@ -181,6 +182,7 @@ commit_with_long_change_id(C) -> -spec commit_with_default_exchange(config()) -> _. commit_with_default_exchange(C) -> + ok = application:set_env(limiter, currency_conversion, enabled), Rational = #base_Rational{p = 1000000, q = 100}, _ = mock_exchange(Rational, C), ID = configure_limit(?time_range_month(), ?global(), C), @@ -191,6 +193,7 @@ commit_with_default_exchange(C) -> -spec partial_commit_with_exchange(config()) -> _. partial_commit_with_exchange(C) -> + ok = application:set_env(limiter, currency_conversion, enabled), Rational = #base_Rational{p = 800000, q = 100}, _ = mock_exchange(Rational, C), ID = configure_limit(?time_range_month(), ?global(), C), @@ -202,14 +205,25 @@ partial_commit_with_exchange(C) -> -spec commit_with_exchange(config()) -> _. commit_with_exchange(C) -> + ok = application:set_env(limiter, currency_conversion, enabled), Rational = #base_Rational{p = 1000000, q = 100}, _ = mock_exchange(Rational, C), ID = configure_limit(?time_range_month(), ?global(), C), Cost = ?cash(10000, <<"USD">>), Context = ?payproc_ctx_invoice(Cost), - {exception, #base_InvalidRequest{}} = lim_client:hold(?LIMIT_CHANGE(ID), Context, ?config(client, C)). -%% {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), -%% {ok, #limiter_Limit{amount = 10500}} = lim_client:get(ID, Context, ?config(client, C)). + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), + {ok, #limiter_Limit{amount = 10500}} = lim_client:get(ID, Context, ?config(client, C)). + +-spec commit_with_disabled_exchange(config()) -> _. +commit_with_disabled_exchange(C) -> + ok = application:set_env(limiter, currency_conversion, disabled), + Rational = #base_Rational{p = 1000000, q = 100}, + _ = mock_exchange(Rational, C), + ID = configure_limit(?time_range_month(), ?global(), C), + Cost = ?cash(10000, <<"USD">>), + Context = ?payproc_ctx_invoice(Cost), + {exception, #base_InvalidRequest{}} = + lim_client:hold(?LIMIT_CHANGE(ID), Context, ?config(client, C)). -spec get_limit_ok(config()) -> _. get_limit_ok(C) -> diff --git a/config/sys.config b/config/sys.config index 9751edcd..39828890 100644 --- a/config/sys.config +++ b/config/sys.config @@ -22,6 +22,9 @@ } }}, + % Enables currency conversion for turnover metric (default = disabled) + {currency_conversion, disabled}, + {exchange_factors, #{ <<"DEFAULT">> => {1, 1}, <<"USD">> => {105, 100}, From 3eff7ddf1ef4b74642ce314ffea83d9da6ad0771 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Wed, 1 Mar 2023 18:33:36 +0300 Subject: [PATCH 30/54] VEN-9: Adds support for versioned limit config object (#20) * Adds explicit damsel dependency * Bump dominant docker compose image (sha-e0afa44) * Adds dominant-sourced config unmarshalling * Adds operation `Limiter:GetVersioned/4` * Refactor test suite and add testcase group with dominant-sourced config object use --- apps/limiter/src/lim_config_codec.erl | 92 ++++- apps/limiter/src/lim_config_machine.erl | 42 +- apps/limiter/src/lim_handler.erl | 5 +- apps/limiter/src/limiter.app.src | 3 +- apps/limiter/test/lim_client.erl | 11 +- apps/limiter/test/lim_configurator_SUITE.erl | 40 +- apps/limiter/test/lim_turnover_SUITE.erl | 407 +++++++++++++------ compose.yml | 14 + config/sys.config | 23 ++ rebar.config | 4 +- rebar.lock | 12 +- test/machinegun/config.yaml | 4 + 12 files changed, 491 insertions(+), 166 deletions(-) diff --git a/apps/limiter/src/lim_config_codec.erl b/apps/limiter/src/lim_config_codec.erl index 1cd609ad..2c60b6f7 100644 --- a/apps/limiter/src/lim_config_codec.erl +++ b/apps/limiter/src/lim_config_codec.erl @@ -2,6 +2,8 @@ -include_lib("limiter_proto/include/limproto_config_thrift.hrl"). -include_lib("limiter_proto/include/limproto_timerange_thrift.hrl"). +-include_lib("damsel/include/dmsl_domain_thrift.hrl"). +-include_lib("damsel/include/dmsl_limiter_config_thrift.hrl"). -export([marshal/2]). -export([unmarshal/2]). @@ -138,7 +140,35 @@ marshal_scope_type(payer_contact_email) -> unmarshal(timestamped_change, TimestampedChange) -> Timestamp = unmarshal_timestamp(TimestampedChange#config_TimestampedChange.occured_at), Change = unmarshal_change(TimestampedChange#config_TimestampedChange.change), - {ev, Timestamp, Change}. + {ev, Timestamp, Change}; +unmarshal('LimitConfigObject', #domain_LimitConfigObject{ + ref = #domain_LimitConfigRef{id = ID}, + data = #limiter_config_LimitConfig{ + processor_type = ProcessorType, + created_at = CreatedAt, + started_at = StartedAt, + shard_size = ShardSize, + time_range_type = TimeRangeType, + context_type = ContextType, + type = Type, + scopes = Scopes, + description = Description, + op_behaviour = OpBehaviour + } +}) -> + genlib_map:compact(#{ + id => ID, + processor_type => ProcessorType, + created_at => lim_time:from_rfc3339(CreatedAt), + started_at => StartedAt, + shard_size => ShardSize, + time_range_type => unmarshal_time_range_type(TimeRangeType), + context_type => unmarshal_context_type(ContextType), + type => maybe_apply(Type, fun unmarshal_type/1), + scope => maybe_apply(Scopes, fun unmarshal_scope/1), + description => Description, + op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1) + }). unmarshal_timestamp(Timestamp) when is_binary(Timestamp) -> try @@ -225,16 +255,22 @@ derive_type(undefined, {cash, Currency}) -> {turnover, {amount, Currency}}. -spec unmarshal_op_behaviour(encoded_value()) -> decoded_value(). -unmarshal_op_behaviour(OpBehaviour) -> - #config_OperationLimitBehaviour{ - invoice_payment_refund = Refund - } = OpBehaviour, +unmarshal_op_behaviour(#limiter_config_OperationLimitBehaviour{invoice_payment_refund = Refund}) -> + do_unmarshal_op_behaviour_refund(Refund); +unmarshal_op_behaviour(#config_OperationLimitBehaviour{invoice_payment_refund = Refund}) -> + do_unmarshal_op_behaviour_refund(Refund). + +do_unmarshal_op_behaviour_refund(Refund) -> genlib_map:compact(#{ invoice_payment_refund => maybe_apply(Refund, fun unmarshal_behaviour/1) }). +unmarshal_behaviour({subtraction, #limiter_config_Subtraction{}}) -> + subtraction; unmarshal_behaviour({subtraction, #config_Subtraction{}}) -> subtraction; +unmarshal_behaviour({addition, #limiter_config_Addition{}}) -> + addition; unmarshal_behaviour({addition, #config_Addition{}}) -> addition. @@ -244,6 +280,8 @@ unmarshal_body_type_deprecated({cash, #config_LimitBodyTypeCash{currency = Curre unmarshal_time_range_type({calendar, CalendarType}) -> {calendar, unmarshal_calendar_time_range_type(CalendarType)}; +unmarshal_time_range_type({interval, #limiter_config_TimeRangeTypeInterval{amount = Amount}}) -> + {interval, Amount}; unmarshal_time_range_type({interval, #timerange_TimeRangeTypeInterval{amount = Amount}}) -> {interval, Amount}. @@ -256,22 +294,32 @@ unmarshal_calendar_time_range_type({month, _}) -> unmarshal_calendar_time_range_type({year, _}) -> year. +unmarshal_context_type({payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}) -> + payment_processing; unmarshal_context_type({payment_processing, #config_LimitContextTypePaymentProcessing{}}) -> payment_processing; +unmarshal_context_type({withdrawal_processing, #limiter_config_LimitContextTypeWithdrawalProcessing{}}) -> + withdrawal_processing; unmarshal_context_type({withdrawal_processing, #config_LimitContextTypeWithdrawalProcessing{}}) -> withdrawal_processing. +unmarshal_type({turnover, #limiter_config_LimitTypeTurnover{metric = Metric}}) -> + {turnover, maybe_apply(Metric, fun unmarshal_turnover_metric/1, number)}; unmarshal_type({turnover, #config_LimitTypeTurnover{metric = Metric}}) -> {turnover, maybe_apply(Metric, fun unmarshal_turnover_metric/1, number)}. unmarshal_turnover_metric({number, _}) -> number; +unmarshal_turnover_metric({amount, #limiter_config_LimitTurnoverAmount{currency = Currency}}) -> + {amount, Currency}; unmarshal_turnover_metric({amount, #config_LimitTurnoverAmount{currency = Currency}}) -> {amount, Currency}. unmarshal_scope({single, Type}) -> ordsets:from_list([unmarshal_scope_type(Type)]); unmarshal_scope({multi, Types}) -> + ordsets:from_list(lists:map(fun unmarshal_scope_type/1, ordsets:to_list(Types))); +unmarshal_scope(Types) when is_list(Types) -> ordsets:from_list(lists:map(fun unmarshal_scope_type/1, ordsets:to_list(Types))). unmarshal_scope_type({party, _}) -> @@ -360,4 +408,38 @@ unmarshal_created_w_deprecated_body_type_test_() -> ) ]. +-spec unmarshal_config_object_test() -> _. +unmarshal_config_object_test() -> + CreatedAt = lim_time:now(), + Config = #{ + id => <<"id">>, + processor_type => <<"type">>, + created_at => CreatedAt, + started_at => <<"2000-01-01T00:00:00Z">>, + shard_size => 7, + time_range_type => {calendar, day}, + context_type => payment_processing, + type => {turnover, number}, + scope => ordsets:from_list([party, shop]), + description => <<"description">> + }, + Object = #domain_LimitConfigObject{ + ref = #domain_LimitConfigRef{id = <<"id">>}, + data = #limiter_config_LimitConfig{ + processor_type = <<"type">>, + created_at = lim_time:to_rfc3339(CreatedAt), + started_at = <<"2000-01-01T00:00:00Z">>, + shard_size = 7, + time_range_type = {calendar, {day, #limiter_config_TimeRangeTypeCalendarDay{}}}, + context_type = {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}, + type = + {turnover, #limiter_config_LimitTypeTurnover{metric = {number, #limiter_config_LimitTurnoverNumber{}}}}, + scopes = ordsets:from_list([ + {'party', #limiter_config_LimitScopeEmptyDetails{}}, {'shop', #limiter_config_LimitScopeEmptyDetails{}} + ]), + description = <<"description">> + } + }, + ?assertEqual(Config, unmarshal('LimitConfigObject', Object)). + -endif. diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index dc8a27eb..d6bc9755 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -1,6 +1,8 @@ -module(lim_config_machine). -include_lib("limiter_proto/include/limproto_limiter_thrift.hrl"). +-include_lib("damsel/include/dmsl_domain_thrift.hrl"). +-include_lib("damsel/include/dmsl_domain_conf_thrift.hrl"). %% Accessors @@ -21,7 +23,7 @@ -export([start/3]). -export([get/2]). --export([get_limit/2]). +-export([get_limit/3]). -export([hold/2]). -export([commit/2]). -export([rollback/2]). @@ -80,6 +82,7 @@ -type operation_type() :: invoice_payment_refund. -type lim_id() :: limproto_limiter_thrift:'LimitID'(). +-type lim_version() :: dmsl_domain_thrift:'DataRevision'() | undefined. -type lim_change() :: limproto_limiter_thrift:'LimitChange'(). -type limit() :: limproto_limiter_thrift:'Limit'(). -type timestamp() :: dmsl_base_thrift:'Timestamp'(). @@ -232,41 +235,56 @@ get(ID, LimitContext) -> collapse(Machine) end). --spec get_limit(lim_id(), lim_context()) -> {ok, limit()} | {error, config_error() | {processor(), get_limit_error()}}. -get_limit(ID, LimitContext) -> +-spec get_limit(lim_id(), lim_version(), lim_context()) -> + {ok, limit()} | {error, config_error() | {processor(), get_limit_error()}}. +get_limit(ID, Version, LimitContext) -> do(fun() -> - {Handler, Config} = unwrap(get_handler(ID, LimitContext)), + {Handler, Config} = unwrap(get_handler(ID, Version, LimitContext)), unwrap(Handler, Handler:get_limit(ID, Config, LimitContext)) end). -spec hold(lim_change(), lim_context()) -> ok | {error, config_error() | {processor(), hold_error()}}. -hold(LimitChange = #limiter_LimitChange{id = ID}, LimitContext) -> +hold(LimitChange = #limiter_LimitChange{id = ID, version = Version}, LimitContext) -> do(fun() -> - {Handler, Config} = unwrap(get_handler(ID, LimitContext)), + {Handler, Config} = unwrap(get_handler(ID, Version, LimitContext)), unwrap(Handler, Handler:hold(LimitChange, Config, LimitContext)) end). -spec commit(lim_change(), lim_context()) -> ok | {error, config_error() | {processor(), commit_error()}}. -commit(LimitChange = #limiter_LimitChange{id = ID}, LimitContext) -> +commit(LimitChange = #limiter_LimitChange{id = ID, version = Version}, LimitContext) -> do(fun() -> - {Handler, Config} = unwrap(get_handler(ID, LimitContext)), + {Handler, Config} = unwrap(get_handler(ID, Version, LimitContext)), unwrap(Handler, Handler:commit(LimitChange, Config, LimitContext)) end). -spec rollback(lim_change(), lim_context()) -> ok | {error, config_error() | {processor(), rollback_error()}}. -rollback(LimitChange = #limiter_LimitChange{id = ID}, LimitContext) -> +rollback(LimitChange = #limiter_LimitChange{id = ID, version = Version}, LimitContext) -> do(fun() -> - {Handler, Config} = unwrap(get_handler(ID, LimitContext)), + {Handler, Config} = unwrap(get_handler(ID, Version, LimitContext)), unwrap(Handler, Handler:rollback(LimitChange, Config, LimitContext)) end). -get_handler(ID, LimitContext) -> +get_handler(ID, Version, LimitContext) -> do(fun() -> - Config = #{processor_type := ProcessorType} = unwrap(config, get(ID, LimitContext)), + Config = #{processor_type := ProcessorType} = unwrap(config, get_config(ID, Version, LimitContext)), {ok, Handler} = lim_router:get_handler(ProcessorType), {Handler, Config} end). +-spec get_config(lim_id(), lim_version(), lim_context()) -> {ok, config()} | {error, notfound}. +get_config(ID, undefined, LimitContext) -> + get(ID, LimitContext); +get_config(ID, Version, #{woody_context := WoodyContext}) -> + LimitConfigRef = {limit_config, #domain_LimitConfigRef{id = ID}}, + try + Object = dmt_client:checkout_versioned_object(Version, LimitConfigRef, #{woody_context => WoodyContext}), + #domain_conf_VersionedObject{object = {limit_config, ConfigObject}} = Object, + {ok, lim_config_codec:unmarshal('LimitConfigObject', ConfigObject)} + catch + throw:#domain_conf_ObjectNotFound{} -> + {error, notfound} + end. + -spec calculate_time_range(timestamp(), config()) -> time_range(). calculate_time_range(Timestamp, Config) -> StartedAt = started_at(Config), diff --git a/apps/limiter/src/lim_handler.erl b/apps/limiter/src/lim_handler.erl index 11e26d37..d48db86d 100644 --- a/apps/limiter/src/lim_handler.erl +++ b/apps/limiter/src/lim_handler.erl @@ -27,11 +27,14 @@ handle_function(Fn, Args, WoodyCtx, Opts) -> ). -spec handle_function_(woody:func(), woody:args(), lim_context(), woody:options()) -> {ok, woody:result()}. -handle_function_('Get', {LimitID, Clock, Context}, LimitContext, _Opts) -> +handle_function_('Get', {LimitID, Clock, Context}, LimitContext, Opts) -> + handle_function_('GetVersioned', {LimitID, undefined, Clock, Context}, LimitContext, Opts); +handle_function_('GetVersioned', {LimitID, Version, Clock, Context}, LimitContext, _Opts) -> scoper:add_meta(#{limit_id => LimitID}), case lim_config_machine:get_limit( LimitID, + Version, lim_context:set_context(Context, lim_context:set_clock(Clock, LimitContext)) ) of diff --git a/apps/limiter/src/limiter.app.src b/apps/limiter/src/limiter.app.src index a7410261..968afd6f 100644 --- a/apps/limiter/src/limiter.app.src +++ b/apps/limiter/src/limiter.app.src @@ -10,7 +10,8 @@ machinery, woody, scoper, % should be before any scoper event handler usage - erl_health + erl_health, + dmt_client ]}, {mod, {limiter, []}}, {env, []} diff --git a/apps/limiter/test/lim_client.erl b/apps/limiter/test/lim_client.erl index 1de2da45..82c323f0 100644 --- a/apps/limiter/test/lim_client.erl +++ b/apps/limiter/test/lim_client.erl @@ -3,7 +3,7 @@ -include_lib("limiter_proto/include/limproto_limiter_thrift.hrl"). -export([new/0]). --export([get/3]). +-export([get/4]). -export([hold/3]). -export([commit/3]). -export([rollback/3]). @@ -15,6 +15,7 @@ -type client() :: woody_context:ctx(). -type limit_id() :: limproto_limiter_thrift:'LimitID'(). +-type limit_version() :: limproto_limiter_thrift:'Version'() | undefined. -type limit_change() :: limproto_limiter_thrift:'LimitChange'(). -type limit_context() :: limproto_limiter_thrift:'LimitContext'(). -type clock() :: limproto_limiter_thrift:'Clock'(). @@ -27,9 +28,11 @@ new() -> woody_context:new(). --spec get(limit_id(), limit_context(), client()) -> woody:result() | no_return(). -get(LimitID, Context, Client) -> - call('Get', {LimitID, clock(), Context}, Client). +-spec get(limit_id(), limit_version(), limit_context(), client()) -> woody:result() | no_return(). +get(LimitID, undefined, Context, Client) -> + call('Get', {LimitID, clock(), Context}, Client); +get(LimitID, Version, Context, Client) -> + call('GetVersioned', {LimitID, Version, clock(), Context}, Client). -spec hold(limit_change(), limit_context(), client()) -> woody:result() | no_return(). hold(LimitChange, Context, Client) -> diff --git a/apps/limiter/test/lim_configurator_SUITE.erl b/apps/limiter/test/lim_configurator_SUITE.erl index 525d2b59..f385eff2 100644 --- a/apps/limiter/test/lim_configurator_SUITE.erl +++ b/apps/limiter/test/lim_configurator_SUITE.erl @@ -51,16 +51,38 @@ init_per_suite(Config) -> % dbg:tracer(), dbg:p(all, c), % dbg:tpl({machinery, '_', '_'}, x), Apps = - genlib_app:start_application_with(limiter, [ - {service_clients, #{ - accounter => #{ - url => <<"http://shumway:8022/accounter">> - }, - automaton => #{ - url => <<"http://machinegun:8022/v1/automaton">> - } + genlib_app:start_application_with(dmt_client, [ + % milliseconds + {cache_update_interval, 5000}, + {max_cache_size, #{ + elements => 20, + % 50Mb + memory => 52428800 + }}, + {woody_event_handlers, [ + {scoper_woody_event_handler, #{ + event_handler_opts => #{ + formatter_opts => #{ + max_length => 1000 + } + } + }} + ]}, + {service_urls, #{ + 'Repository' => <<"http://dominant:8022/v1/domain/repository">>, + 'RepositoryClient' => <<"http://dominant:8022/v1/domain/repository_client">> }} - ]), + ]) ++ + genlib_app:start_application_with(limiter, [ + {service_clients, #{ + accounter => #{ + url => <<"http://shumway:8022/accounter">> + }, + automaton => #{ + url => <<"http://machinegun:8022/v1/automaton">> + } + }} + ]), [{apps, Apps}] ++ Config. -spec end_per_suite(config()) -> _. diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index b343cc8b..abd1a6e2 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -3,6 +3,7 @@ -include_lib("stdlib/include/assert.hrl"). -include_lib("common_test/include/ct.hrl"). -include_lib("damsel/include/dmsl_base_thrift.hrl"). +-include_lib("damsel/include/dmsl_limiter_config_thrift.hrl"). -include("lim_ct_helper.hrl"). -export([all/0]). @@ -10,6 +11,8 @@ -export([groups/0]). -export([init_per_suite/1]). -export([end_per_suite/1]). +-export([init_per_group/2]). +-export([end_per_group/2]). -export([init_per_testcase/2]). -export([end_per_testcase/2]). @@ -59,6 +62,7 @@ all() -> [ {group, default}, + {group, default_with_dominant}, {group, withdrawals}, {group, cashless}, {group, idempotency} @@ -91,6 +95,29 @@ groups() -> commit_with_email_scope_ok, commit_with_multi_scope_ok ]}, + %% Repeats `default` group exept for `get_config_ok` and `commit_multirange_limit_ok` + {default_with_dominant, [], [ + commit_with_long_change_id, + commit_with_default_exchange, + partial_commit_with_exchange, + commit_with_exchange, + commit_with_disabled_exchange, + get_limit_ok, + get_limit_notfound, + hold_ok, + commit_ok, + rollback_ok, + partial_zero_commit_rollbacks, + refund_ok, + commit_inexistent_hold_fails, + partial_commit_inexistent_hold_fails, + commit_with_payment_tool_scope_ok, + commit_with_party_scope_ok, + commit_with_provider_scope_ok, + commit_with_terminal_scope_ok, + commit_with_email_scope_ok, + commit_with_multi_scope_ok + ]}, {withdrawals, [parallel], [ get_limit_ok, hold_ok, @@ -123,30 +150,65 @@ init_per_suite(Config) -> % dbg:tracer(), dbg:p(all, c), % dbg:tpl({lim_handler, '_', '_'}, x), Apps = - genlib_app:start_application_with(limiter, [ - {service_clients, #{ - accounter => #{ - url => <<"http://shumway:8022/accounter">> - }, - automaton => #{ - url => <<"http://machinegun:8022/v1/automaton">> - }, - xrates => #{ - url => <<"http://xrates:8022/xrates">> - } + genlib_app:start_application_with(dmt_client, [ + % milliseconds + {cache_update_interval, 5000}, + {max_cache_size, #{ + elements => 20, + % 50Mb + memory => 52428800 }}, - {exchange_factors, #{ - <<"DEFAULT">> => {1, 1}, - <<"USD">> => {105, 100}, - <<"EUR">> => {12, 10} + {woody_event_handlers, [ + {scoper_woody_event_handler, #{ + event_handler_opts => #{ + formatter_opts => #{ + max_length => 1000 + } + } + }} + ]}, + {service_urls, #{ + 'Repository' => <<"http://dominant:8022/v1/domain/repository">>, + 'RepositoryClient' => <<"http://dominant:8022/v1/domain/repository_client">> }} - ]), + ]) ++ + genlib_app:start_application_with(limiter, [ + {service_clients, #{ + accounter => #{ + url => <<"http://shumway:8022/accounter">> + }, + automaton => #{ + url => <<"http://machinegun:8022/v1/automaton">> + }, + xrates => #{ + url => <<"http://xrates:8022/xrates">> + } + }}, + {exchange_factors, #{ + <<"DEFAULT">> => {1, 1}, + <<"USD">> => {105, 100}, + <<"EUR">> => {12, 10} + }} + ]), [{apps, Apps}] ++ Config. -spec end_per_suite(config()) -> _. end_per_suite(Config) -> genlib_app:test_application_stop(?config(apps, Config)). +-spec init_per_group(test_case_name(), config()) -> config(). +init_per_group(default_with_dominant, C) -> + set_limit_config_source(repository, C); +init_per_group(_Name, C) -> + set_limit_config_source(legacy, C). + +set_limit_config_source(ConfigSource, C) -> + [{limit_config_source, ConfigSource} | C]. + +-spec end_per_group(test_case_name(), config()) -> ok. +end_per_group(_Name, _C) -> + ok. + -spec init_per_testcase(test_case_name(), config()) -> config(). init_per_testcase(Name, C) -> [ @@ -164,12 +226,16 @@ end_per_testcase(_Name, C) -> %% -define(CHANGE_ID, 42). --define(LIMIT_CHANGE(ID), ?LIMIT_CHANGE(ID, ?CHANGE_ID)). --define(LIMIT_CHANGE(ID, ChangeID), #limiter_LimitChange{id = ID, change_id = gen_change_id(ID, ChangeID)}). +-define(LIMIT_CHANGE(ID, ChangeID), ?LIMIT_CHANGE(ID, ChangeID, undefined)). +-define(LIMIT_CHANGE(ID, ChangeID, Version), #limiter_LimitChange{ + id = ID, + change_id = gen_change_id(ID, ChangeID), + version = Version +}). -spec commit_with_long_change_id(config()) -> _. commit_with_long_change_id(C) -> - ID = configure_limit(?time_range_month(), ?global(), C), + {ID, Version} = configure_limit(?time_range_month(), ?global(), C), Context = ?payproc_ctx_invoice(?cash(10, <<"RUB">>)), LongBinary = << @@ -177,115 +243,122 @@ commit_with_long_change_id(C) -> " BinaryLongBinaryLongBinaryLongBinaryLongBinaryLongBinary" >>, ChangeID = <>, - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ChangeID), Context, ?config(client, C)), - {ok, #limiter_Limit{amount = 10}} = lim_client:get(ID, Context, ?config(client, C)). + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ChangeID, Version), Context, ?config(client, C)), + {ok, #limiter_Limit{amount = 10}} = lim_client:get(ID, Version, Context, ?config(client, C)). -spec commit_with_default_exchange(config()) -> _. commit_with_default_exchange(C) -> ok = application:set_env(limiter, currency_conversion, enabled), Rational = #base_Rational{p = 1000000, q = 100}, _ = mock_exchange(Rational, C), - ID = configure_limit(?time_range_month(), ?global(), C), + {ID, Version} = configure_limit(?time_range_month(), ?global(), C), Cost = ?cash(10000, <<"SOME_CURRENCY">>), Context = ?payproc_ctx_invoice(Cost), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), - {ok, #limiter_Limit{amount = 10000}} = lim_client:get(ID, Context, ?config(client, C)). + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), + {ok, #limiter_Limit{amount = 10000}} = lim_client:get(ID, Version, Context, ?config(client, C)). -spec partial_commit_with_exchange(config()) -> _. partial_commit_with_exchange(C) -> ok = application:set_env(limiter, currency_conversion, enabled), Rational = #base_Rational{p = 800000, q = 100}, _ = mock_exchange(Rational, C), - ID = configure_limit(?time_range_month(), ?global(), C), + {ID, Version} = configure_limit(?time_range_month(), ?global(), C), Cost = ?cash(1000, <<"USD">>), CaptureCost = ?cash(800, <<"USD">>), Context = ?payproc_ctx_payment(Cost, CaptureCost), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), - {ok, #limiter_Limit{amount = 8400}} = lim_client:get(ID, Context, ?config(client, C)). + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), + {ok, #limiter_Limit{amount = 8400}} = lim_client:get(ID, Version, Context, ?config(client, C)). -spec commit_with_exchange(config()) -> _. commit_with_exchange(C) -> ok = application:set_env(limiter, currency_conversion, enabled), Rational = #base_Rational{p = 1000000, q = 100}, _ = mock_exchange(Rational, C), - ID = configure_limit(?time_range_month(), ?global(), C), + {ID, Version} = configure_limit(?time_range_month(), ?global(), C), Cost = ?cash(10000, <<"USD">>), Context = ?payproc_ctx_invoice(Cost), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), - {ok, #limiter_Limit{amount = 10500}} = lim_client:get(ID, Context, ?config(client, C)). + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), + {ok, #limiter_Limit{amount = 10500}} = lim_client:get(ID, Version, Context, ?config(client, C)). -spec commit_with_disabled_exchange(config()) -> _. commit_with_disabled_exchange(C) -> ok = application:set_env(limiter, currency_conversion, disabled), Rational = #base_Rational{p = 1000000, q = 100}, _ = mock_exchange(Rational, C), - ID = configure_limit(?time_range_month(), ?global(), C), + {ID, Version} = configure_limit(?time_range_month(), ?global(), C), Cost = ?cash(10000, <<"USD">>), Context = ?payproc_ctx_invoice(Cost), {exception, #base_InvalidRequest{}} = - lim_client:hold(?LIMIT_CHANGE(ID), Context, ?config(client, C)). + lim_client:hold(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)). -spec get_limit_ok(config()) -> _. get_limit_ok(C) -> - ID = configure_limit(?time_range_month(), ?global(), C), + {ID, Version} = configure_limit(?time_range_month(), ?global(), C), Context = case get_group_name(C) of - default -> ?payproc_ctx_invoice(?cash(0)); - withdrawals -> ?wthdproc_ctx_withdrawal(?cash(0)) + withdrawals -> ?wthdproc_ctx_withdrawal(?cash(0)); + _Default -> ?payproc_ctx_invoice(?cash(0)) end, ?assertMatch( {ok, #limiter_Limit{amount = 0}}, - lim_client:get(ID, Context, ?config(client, C)) + lim_client:get(ID, Version, Context, ?config(client, C)) ). -spec get_limit_notfound(config()) -> _. get_limit_notfound(C) -> + Version = + case proplists:get_value(limit_config_source, C, legacy) of + legacy -> undefined; + repository -> 0 + end, Context = ?payproc_ctx_invoice(?cash(0)), ?assertEqual( {exception, #limiter_LimitNotFound{}}, - lim_client:get(<<"NOSUCHLIMITID">>, Context, ?config(client, C)) + lim_client:get(<<"NOSUCHLIMITID">>, Version, Context, ?config(client, C)) ). -spec hold_ok(config()) -> _. hold_ok(C) -> - ID = configure_limit(?time_range_month(), ?global(), C), + {ID, Version} = configure_limit(?time_range_month(), ?global(), C), Context = case get_group_name(C) of - default -> ?payproc_ctx_invoice(?cash(10)); - withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10)) + withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10)); + _Default -> ?payproc_ctx_invoice(?cash(10)) end, - {ok, {vector, #limiter_VectorClock{}}} = lim_client:hold(?LIMIT_CHANGE(ID), Context, ?config(client, C)), - {ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)). + {ok, {vector, #limiter_VectorClock{}}} = lim_client:hold( + ?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C) + ), + {ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)). -spec commit_ok(config()) -> _. commit_ok(C) -> - ID = configure_limit(?time_range_month(), ?global(), C), + {ID, Version} = configure_limit(?time_range_month(), ?global(), C), Context = case get_group_name(C) of - default -> ?payproc_ctx_invoice(?cash(10, <<"RUB">>)); - withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)) + withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)); + _Default -> ?payproc_ctx_invoice(?cash(10, <<"RUB">>)) end, - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), - {ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)). + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), + {ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)). -spec rollback_ok(config()) -> _. rollback_ok(C) -> - ID = configure_limit(?time_range_week(), ?global(), C), + {ID, Version} = configure_limit(?time_range_week(), ?global(), C), Context = case get_group_name(C) of - default -> ?payproc_ctx_invoice(?cash(10, <<"RUB">>)); - withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)) + withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)); + _Default -> ?payproc_ctx_invoice(?cash(10, <<"RUB">>)) end, - Change = ?LIMIT_CHANGE(ID), + Change = ?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), {ok, {vector, _}} = lim_client:hold(Change, Context, ?config(client, C)), {ok, {vector, _}} = lim_client:rollback(Change, Context, ?config(client, C)). -spec partial_zero_commit_rollbacks(config()) -> _. partial_zero_commit_rollbacks(C) -> - ID = configure_limit(?time_range_week(), ?global(), C), + {ID, Version} = configure_limit(?time_range_week(), ?global(), C), Context0 = ?payproc_ctx_payment(?cash(10), ?cash(10)), Context1 = ?payproc_ctx_payment(?cash(10), ?cash(0)), - Change = ?LIMIT_CHANGE(ID), + Change = ?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), {ok, {vector, _}} = lim_client:hold(Change, Context0, ?config(client, C)), {ok, {vector, _}} = lim_client:commit(Change, Context1, ?config(client, C)), % NOTE @@ -299,38 +372,38 @@ refund_ok(C) -> Client = ?config(client, C), OwnerID = <<"WWWcool Ltd">>, ShopID = <<"shop">>, - ID = configure_limit(?time_range_day(), ?scope([?scope_party(), ?scope_shop()]), C), + {ID, Version} = configure_limit(?time_range_day(), ?scope([?scope_party(), ?scope_shop()]), C), Context0 = ?payproc_ctx_payment(OwnerID, ShopID, ?cash(15), ?cash(15)), RefundContext1 = ?payproc_ctx_refund(OwnerID, ShopID, ?cash(10), ?cash(10), ?cash(10)), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Payment">>), Context0, Client), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Refund">>), RefundContext1, Client), - {ok, #limiter_Limit{} = Limit2} = lim_client:get(ID, RefundContext1, Client), + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Payment">>, Version), Context0, Client), + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Refund">>, Version), RefundContext1, Client), + {ok, #limiter_Limit{} = Limit2} = lim_client:get(ID, Version, RefundContext1, Client), ?assertEqual(Limit2#limiter_Limit.amount, 5). -spec get_config_ok(config()) -> _. get_config_ok(C) -> - ID = configure_limit(?time_range_week(), ?global(), C), + {ID, _Version} = configure_limit(?time_range_week(), ?global(), C), {ok, #config_LimitConfig{}} = lim_client:get_config(ID, ?config(client, C)). -spec commit_inexistent_hold_fails(config()) -> _. commit_inexistent_hold_fails(C) -> - ID = configure_limit(?time_range_week(), ?global(), C), + {ID, Version} = configure_limit(?time_range_week(), ?global(), C), Context = ?payproc_ctx_payment(?cash(42), undefined), % NOTE % We do not expect `LimitChangeNotFound` here because we no longer reconcile with accounter % before requesting him to hold / commit. {exception, #base_InvalidRequest{}} = - lim_client:commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)). + lim_client:commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)). -spec partial_commit_inexistent_hold_fails(config()) -> _. partial_commit_inexistent_hold_fails(C) -> - ID = configure_limit(?time_range_week(), ?global(), C), + {ID, Version} = configure_limit(?time_range_week(), ?global(), C), Context = ?payproc_ctx_payment(?cash(42), ?cash(21)), % NOTE % We do not expect `LimitChangeNotFound` here because we no longer reconcile with accounter % before requesting him to hold / commit. {exception, #base_InvalidRequest{}} = - lim_client:commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)). + lim_client:commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)). -spec commit_multirange_limit_ok(config()) -> _. commit_multirange_limit_ok(C) -> @@ -356,14 +429,14 @@ commit_multirange_limit_ok(C) -> {ok, _} = hold_and_commit(?LIMIT_CHANGE(ID, 2), ?payproc_ctx_payment(PaymentFeb), Client), PaymentApr = ?invoice_payment(?cash(44), ?cash(44), ?bank_card(), <<"2020-04-01T00:00:00Z">>), {ok, _} = hold_and_commit(?LIMIT_CHANGE(ID, 3), ?payproc_ctx_payment(PaymentApr), Client), - {ok, #limiter_Limit{amount = 42}} = lim_client:get(ID, ?payproc_ctx_payment(PaymentJan), Client), - {ok, #limiter_Limit{amount = 43}} = lim_client:get(ID, ?payproc_ctx_payment(PaymentFeb), Client), - {ok, #limiter_Limit{amount = 44}} = lim_client:get(ID, ?payproc_ctx_payment(PaymentApr), Client). + {ok, #limiter_Limit{amount = 42}} = lim_client:get(ID, undefined, ?payproc_ctx_payment(PaymentJan), Client), + {ok, #limiter_Limit{amount = 43}} = lim_client:get(ID, undefined, ?payproc_ctx_payment(PaymentFeb), Client), + {ok, #limiter_Limit{amount = 44}} = lim_client:get(ID, undefined, ?payproc_ctx_payment(PaymentApr), Client). -spec commit_with_payment_tool_scope_ok(config()) -> _. commit_with_payment_tool_scope_ok(C) -> Client = ?config(client, C), - ID = configure_limit(?time_range_week(), ?scope([?scope_payment_tool()]), ?turnover_metric_number(), C), + {ID, Version} = configure_limit(?time_range_week(), ?scope([?scope_payment_tool()]), ?turnover_metric_number(), C), Context1 = ?payproc_ctx_payment( ?invoice_payment(?cash(10), ?cash(10), ?bank_card(<<"Token">>, 2, 2022)) ), @@ -379,13 +452,13 @@ commit_with_payment_tool_scope_ok(C) -> Context5 = ?payproc_ctx_payment( ?invoice_payment(?cash(10), ?cash(10), ?digital_wallet(<<"ID42">>, <<"Pepal">>)) ), - {ok, LimitState0} = lim_client:get(ID, Context1, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 1), Context1, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 2), Context2, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 3), Context3, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 4), Context4, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 5), Context5, Client), - {ok, LimitState1} = lim_client:get(ID, Context1, Client), + {ok, LimitState0} = lim_client:get(ID, Version, Context1, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 1, Version), Context1, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 2, Version), Context2, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 3, Version), Context3, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 4, Version), Context4, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 5, Version), Context5, Client), + {ok, LimitState1} = lim_client:get(ID, Version, Context1, Client), ?assertEqual( LimitState1#limiter_Limit.amount, LimitState0#limiter_Limit.amount + 1 @@ -396,66 +469,66 @@ commit_with_payment_tool_scope_ok(C) -> -spec commit_processes_idempotently(config()) -> _. commit_processes_idempotently(C) -> Client = ?config(client, C), - ID = configure_limit(?time_range_week(), ?global(), C), + {ID, Version} = configure_limit(?time_range_week(), ?global(), C), Context = ?payproc_ctx_payment(?cash(42), undefined), - Change = ?LIMIT_CHANGE(ID), + Change = ?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), {ok, _} = lim_client:hold(Change, Context, Client), {ok, _} = lim_client:hold(Change, Context, Client), {ok, _} = lim_client:commit(Change, Context, Client), - {ok, Limit = #limiter_Limit{amount = 42}} = lim_client:get(ID, Context, Client), + {ok, Limit = #limiter_Limit{amount = 42}} = lim_client:get(ID, Version, Context, Client), {ok, _} = lim_client:commit(Change, Context, Client), - {ok, Limit} = lim_client:get(ID, Context, Client). + {ok, Limit} = lim_client:get(ID, Version, Context, Client). -spec full_commit_processes_idempotently(config()) -> _. full_commit_processes_idempotently(C) -> Client = ?config(client, C), - ID = configure_limit(?time_range_week(), ?global(), C), + {ID, Version} = configure_limit(?time_range_week(), ?global(), C), Cost = ?cash(42), Context = ?payproc_ctx_payment(Cost, Cost), - Change = ?LIMIT_CHANGE(ID), + Change = ?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), {ok, _} = lim_client:hold(Change, Context, Client), {ok, _} = lim_client:hold(Change, Context, Client), {ok, _} = lim_client:commit(Change, Context, Client), - {ok, Limit = #limiter_Limit{amount = 42}} = lim_client:get(ID, Context, Client), + {ok, Limit = #limiter_Limit{amount = 42}} = lim_client:get(ID, Version, Context, Client), {ok, _} = lim_client:commit(Change, Context, Client), - {ok, Limit} = lim_client:get(ID, Context, Client). + {ok, Limit} = lim_client:get(ID, Version, Context, Client). -spec partial_commit_processes_idempotently(config()) -> _. partial_commit_processes_idempotently(C) -> Client = ?config(client, C), - ID = configure_limit(?time_range_week(), ?global(), C), + {ID, Version} = configure_limit(?time_range_week(), ?global(), C), Context = ?payproc_ctx_payment(?cash(42), ?cash(40)), - Change = ?LIMIT_CHANGE(ID), + Change = ?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), {ok, _} = lim_client:hold(Change, Context, Client), {ok, _} = lim_client:hold(Change, Context, Client), {ok, _} = lim_client:commit(Change, Context, Client), - {ok, Limit = #limiter_Limit{amount = 40}} = lim_client:get(ID, Context, Client), + {ok, Limit = #limiter_Limit{amount = 40}} = lim_client:get(ID, Version, Context, Client), {ok, _} = lim_client:commit(Change, Context, Client), - {ok, Limit = #limiter_Limit{amount = 40}} = lim_client:get(ID, Context, Client). + {ok, Limit = #limiter_Limit{amount = 40}} = lim_client:get(ID, Version, Context, Client). -spec rollback_processes_idempotently(config()) -> _. rollback_processes_idempotently(C) -> Client = ?config(client, C), - ID = configure_limit(?time_range_week(), ?global(), C), + {ID, Version} = configure_limit(?time_range_week(), ?global(), C), Context = ?payproc_ctx_payment(?cash(42), ?cash(0)), - Change = ?LIMIT_CHANGE(ID), + Change = ?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), {ok, _} = lim_client:hold(Change, Context, Client), {ok, _} = lim_client:hold(Change, Context, Client), {ok, _} = lim_client:commit(Change, Context, Client), - {ok, Limit = #limiter_Limit{amount = 0}} = lim_client:get(ID, Context, Client), + {ok, Limit = #limiter_Limit{amount = 0}} = lim_client:get(ID, Version, Context, Client), {ok, _} = lim_client:commit(Change, Context, Client), - {ok, Limit = #limiter_Limit{amount = 0}} = lim_client:get(ID, Context, Client). + {ok, Limit = #limiter_Limit{amount = 0}} = lim_client:get(ID, Version, Context, Client). %% -spec commit_number_ok(config()) -> _. commit_number_ok(C) -> Client = ?config(client, C), - ID = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C), + {ID, Version} = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C), Context = ?payproc_ctx_payment(?cash(10), ?cash(10)), - {ok, LimitState0} = lim_client:get(ID, Context, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID), Context, Client), - {ok, LimitState1} = lim_client:get(ID, Context, Client), + {ok, LimitState0} = lim_client:get(ID, Version, Context, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, Client), + {ok, LimitState1} = lim_client:get(ID, Version, Context, Client), ?assertEqual( LimitState1#limiter_Limit.amount, LimitState0#limiter_Limit.amount + 1 @@ -464,12 +537,12 @@ commit_number_ok(C) -> -spec rollback_number_ok(config()) -> _. rollback_number_ok(C) -> Client = ?config(client, C), - ID = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C), + {ID, Version} = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C), Context = ?payproc_ctx_payment(?cash(10), ?cash(10)), ContextRollback = ?payproc_ctx_payment(?cash(10), ?cash(0)), - {ok, LimitState0} = lim_client:get(ID, Context, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID), Context, ContextRollback, Client), - {ok, LimitState1} = lim_client:get(ID, Context, Client), + {ok, LimitState0} = lim_client:get(ID, Version, Context, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ContextRollback, Client), + {ok, LimitState1} = lim_client:get(ID, Version, Context, Client), ?assertEqual( LimitState1#limiter_Limit.amount, LimitState0#limiter_Limit.amount @@ -478,16 +551,16 @@ rollback_number_ok(C) -> -spec commit_refund_keep_number_unchanged(config()) -> _. commit_refund_keep_number_unchanged(C) -> Client = ?config(client, C), - ID = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C), + {ID, Version} = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C), Cost = ?cash(10), CaptureCost = ?cash(8), RefundCost = ?cash(5), PaymentContext = ?payproc_ctx_payment(<<"OWNER">>, <<"SHOP">>, Cost, CaptureCost), RefundContext = ?payproc_ctx_refund(<<"OWNER">>, <<"SHOP">>, Cost, CaptureCost, RefundCost), - {ok, LimitState0} = lim_client:get(ID, PaymentContext, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 1), PaymentContext, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 2), RefundContext, Client), - {ok, LimitState1} = lim_client:get(ID, PaymentContext, Client), + {ok, LimitState0} = lim_client:get(ID, Version, PaymentContext, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 1, Version), PaymentContext, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 2, Version), RefundContext, Client), + {ok, LimitState1} = lim_client:get(ID, Version, PaymentContext, Client), ?assertEqual( % Expected to be the same because refund decreases counter given limit config LimitState1#limiter_Limit.amount, @@ -497,12 +570,12 @@ commit_refund_keep_number_unchanged(C) -> -spec partial_commit_number_counts_as_single_op(config()) -> _. partial_commit_number_counts_as_single_op(C) -> Client = ?config(client, C), - ID = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C), + {ID, Version} = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C), Context = ?payproc_ctx_payment(?cash(10), ?cash(10)), ContextPartial = ?payproc_ctx_payment(?cash(10), ?cash(5)), - {ok, LimitState0} = lim_client:get(ID, Context, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID), Context, ContextPartial, Client), - {ok, LimitState1} = lim_client:get(ID, Context, Client), + {ok, LimitState0} = lim_client:get(ID, Version, Context, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ContextPartial, Client), + {ok, LimitState1} = lim_client:get(ID, Version, Context, Client), ?assertEqual( LimitState1#limiter_Limit.amount, LimitState0#limiter_Limit.amount + 1 @@ -523,40 +596,40 @@ commit_with_terminal_scope_ok(C) -> _ = commit_with_some_scope(?scope([?scope_terminal()]), C). commit_with_some_scope(Scope, C) -> - ID = configure_limit(?time_range_month(), Scope, C), + {ID, Version} = configure_limit(?time_range_month(), Scope, C), Context = case get_group_name(C) of - default -> ?payproc_ctx_payment(?cash(10, <<"RUB">>), ?cash(10, <<"RUB">>)); - withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)) + withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)); + _Default -> ?payproc_ctx_payment(?cash(10, <<"RUB">>), ?cash(10, <<"RUB">>)) end, - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), - {ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)). + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), + {ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)). -spec commit_with_email_scope_ok(config()) -> _. commit_with_email_scope_ok(C) -> - ID = configure_limit(?time_range_month(), ?scope([?scope_payer_contact_email()]), C), + {ID, Version} = configure_limit(?time_range_month(), ?scope([?scope_payer_contact_email()]), C), Context = ?payproc_ctx_payment(?cash(10, <<"RUB">>), ?cash(10, <<"RUB">>)), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), - {ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)). + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), + {ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)). -spec commit_with_identity_scope_ok(config()) -> _. commit_with_identity_scope_ok(C) -> - ID = configure_limit(?time_range_month(), ?scope([?scope_identity()]), C), + {ID, Version} = configure_limit(?time_range_month(), ?scope([?scope_identity()]), C), Context = ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), - {ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)). + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), + {ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)). -spec commit_with_wallet_scope_ok(config()) -> _. commit_with_wallet_scope_ok(C) -> - ID = configure_limit(?time_range_month(), ?scope([?scope_wallet()]), C), + {ID, Version} = configure_limit(?time_range_month(), ?scope([?scope_wallet()]), C), Context = ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID), Context, ?config(client, C)), - {ok, #limiter_Limit{}} = lim_client:get(ID, Context, ?config(client, C)). + {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), + {ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)). -spec commit_with_multi_scope_ok(config()) -> _. commit_with_multi_scope_ok(C) -> Client = ?config(client, C), - ID = configure_limit(?time_range_week(), ?scope([?scope_provider(), ?scope_payment_tool()]), C), + {ID, Version} = configure_limit(?time_range_week(), ?scope([?scope_provider(), ?scope_payment_tool()]), C), Context1 = ?payproc_ctx_payment( ?invoice_payment(?cash(10), ?cash(10), ?bank_card(<<"Token">>, 2, 2022)) ), @@ -572,13 +645,13 @@ commit_with_multi_scope_ok(C) -> Context5 = ?payproc_ctx_payment( ?invoice_payment(?cash(10), ?cash(10), ?digital_wallet(<<"ID42">>, <<"Pepal">>)) ), - {ok, LimitState0} = lim_client:get(ID, Context1, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 1), Context1, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 2), Context2, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 3), Context3, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 4), Context4, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 5), Context5, Client), - {ok, LimitState1} = lim_client:get(ID, Context1, Client), + {ok, LimitState0} = lim_client:get(ID, Version, Context1, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 1, Version), Context1, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 2, Version), Context2, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 3, Version), Context3, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 4, Version), Context4, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, 5, Version), Context5, Client), + {ok, LimitState1} = lim_client:get(ID, Version, Context1, Client), ?assertEqual( LimitState1#limiter_Limit.amount, LimitState0#limiter_Limit.amount + 10 @@ -609,7 +682,7 @@ configure_limit(TimeRange, Scope, Metric, C) -> withdrawals -> ?ctx_type_wthdproc(); _Default -> ?ctx_type_payproc() end, - Params = #config_LimitConfigParams{ + CreateParams = #config_LimitConfigParams{ id = ID, started_at = <<"2000-01-01T00:00:00Z">>, time_range_type = TimeRange, @@ -619,8 +692,16 @@ configure_limit(TimeRange, Scope, Metric, C) -> context_type = ContextType, op_behaviour = ?op_behaviour(?op_subtraction()) }, - {ok, _LimitConfig} = lim_client:create_config(Params, ?config(client, C)), - ID. + ConfigSource = proplists:get_value(limit_config_source, C, legacy), + put_config_into_repository(ConfigSource, CreateParams, ?config(client, C)). + +put_config_into_repository(legacy, CreateParams = #config_LimitConfigParams{id = ID}, Client) -> + {ok, _LimitConfig} = lim_client:create_config(CreateParams, Client), + {ID, undefined}; +put_config_into_repository(repository, CreateParams = #config_LimitConfigParams{id = ID}, _Client) -> + LimitConfigObject = mk_limit_config_object(CreateParams), + Version = dmt_client:insert({limit_config, LimitConfigObject}), + {ID, Version}. gen_unique_id(Prefix) -> genlib:format("~s/~B", [Prefix, lim_time:now()]). @@ -628,3 +709,67 @@ gen_unique_id(Prefix) -> get_group_name(C) -> GroupProps = ?config(tc_group_properties, C), proplists:get_value(name, GroupProps). + +mk_limit_config_object(#config_LimitConfigParams{ + id = ID, + started_at = StartedAt, + time_range_type = TimeRange, + shard_size = ShardSize, + type = Type, + scope = Scope, + context_type = ContextType, + op_behaviour = OpBehaviour +}) -> + #domain_LimitConfigObject{ + ref = #domain_LimitConfigRef{id = ID}, + data = #limiter_config_LimitConfig{ + processor_type = <<"TurnoverProcessor">>, + created_at = StartedAt, + started_at = StartedAt, + shard_size = ShardSize, + time_range_type = translate_time_range_type(TimeRange), + context_type = translate_tuple_record(ContextType, "config", "limiter_config"), + type = maybe_apply(Type, fun translate_type/1), + scopes = maybe_apply(Scope, fun translate_scope/1), + description = <<"Description">>, + op_behaviour = maybe_apply(OpBehaviour, fun mk_op_behaviour/1) + } + }. + +mk_op_behaviour(#config_OperationLimitBehaviour{invoice_payment_refund = PaymentRefund}) -> + #limiter_config_OperationLimitBehaviour{ + invoice_payment_refund = maybe_apply(PaymentRefund, fun(Item) -> + translate_tuple_record(Item, "config", "limiter_config") + end) + }. + +%% Interval type is never used in this test suite. To appease dialyzer this clause is commented out. +% translate_time_range_type({interval, #timerange_TimeRangeTypeInterval{amount = Amount}}) -> +% {interval, #limiter_config_TimeRangeTypeInterval{amount = Amount}}; +translate_time_range_type({calendar, CalendarType}) -> + {calendar, translate_tuple_record(CalendarType, "timerange", "limiter_config")}. + +translate_type({turnover, #config_LimitTypeTurnover{metric = Metric}}) -> + {turnover, #limiter_config_LimitTypeTurnover{metric = translate_tuple_record(Metric, "config", "limiter_config")}}. + +translate_scope({single, ScopeType}) -> + ordsets:from_list([translate_scope_type(ScopeType)]); +translate_scope({multi, ScopeTypes}) -> + ordsets:from_list(lists:map(fun translate_scope_type/1, ordsets:to_list(ScopeTypes))). + +translate_scope_type({Scope, #config_LimitScopeEmptyDetails{}}) -> + {Scope, #limiter_config_LimitScopeEmptyDetails{}}. + +maybe_apply(undefined, _) -> + undefined; +maybe_apply(Value, Fun) -> + Fun(Value). + +translate_tuple_record({Type, Record}, OldRecordPrefix, NewRecordPrefix) -> + {Type, change_record_name_prefix(Record, OldRecordPrefix, NewRecordPrefix)}. + +change_record_name_prefix(Record, OldPrefix, NewPrefix) -> + RecordName = atom_to_list(element(1, Record)), + [OldPrefix, TypeName] = string:split(RecordName, "_", trailing), + NewRecordName = list_to_existing_atom(NewPrefix ++ "_" ++ TypeName), + setelement(1, Record, NewRecordName). diff --git a/compose.yml b/compose.yml index e82f70af..c5219062 100644 --- a/compose.yml +++ b/compose.yml @@ -15,11 +15,25 @@ services: working_dir: $PWD command: /sbin/init depends_on: + dominant: + condition: service_healthy machinegun: condition: service_healthy shumway: condition: service_healthy + dominant: + image: ghcr.io/valitydev/dominant:sha-e0afa44 + command: /opt/dominant/bin/dominant foreground + depends_on: + machinegun: + condition: service_healthy + healthcheck: + test: "/opt/dominant/bin/dominant ping" + interval: 10s + timeout: 5s + retries: 10 + machinegun: image: ghcr.io/valitydev/machinegun:sha-7e785cd volumes: diff --git a/config/sys.config b/config/sys.config index 39828890..4829140d 100644 --- a/config/sys.config +++ b/config/sys.config @@ -73,6 +73,29 @@ }} ]}, + {dmt_client, [ + % milliseconds + {cache_update_interval, 5000}, + {max_cache_size, #{ + elements => 20, + % 50Mb + memory => 52428800 + }}, + {woody_event_handlers, [ + {scoper_woody_event_handler, #{ + event_handler_opts => #{ + formatter_opts => #{ + max_length => 1000 + } + } + }} + ]}, + {service_urls, #{ + 'Repository' => <<"http://dominant:8022/v1/domain/repository">>, + 'RepositoryClient' => <<"http://dominant:8022/v1/domain/repository_client">> + }} + ]}, + {kernel, [ {logger_sasl_compatible, false}, {logger_level, debug}, diff --git a/rebar.config b/rebar.config index 58099616..6df9915e 100644 --- a/rebar.config +++ b/rebar.config @@ -26,13 +26,15 @@ %% Common project dependencies. {deps, [ + {damsel, {git, "https://github.com/valitydev/damsel.git", {branch, "master"}}}, {limiter_proto, {git, "https://github.com/valitydev/limiter-proto.git", {branch, "master"}}}, {xrates_proto, {git, "https://github.com/valitydev/xrates-proto.git", {branch, "master"}}}, {machinery, {git, "https://github.com/valitydev/machinery-erlang.git", {branch, "master"}}}, {erl_health, {git, "https://github.com/valitydev/erlang-health.git", {branch, "master"}}}, {genlib, {git, "https://github.com/valitydev/genlib.git", {branch, "master"}}}, {scoper, {git, "https://github.com/valitydev/scoper.git", {branch, "master"}}}, - {woody, {git, "https://github.com/valitydev/woody_erlang.git", {branch, "master"}}} + {woody, {git, "https://github.com/valitydev/woody_erlang.git", {branch, "master"}}}, + {dmt_client, {git, "https://github.com/valitydev/dmt_client.git", {branch, "master"}}} ]}. %% XRef checks diff --git a/rebar.lock b/rebar.lock index 676e7188..92dc3a09 100644 --- a/rebar.lock +++ b/rebar.lock @@ -9,7 +9,15 @@ {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.11.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"33c5665571042440ccec109735481d8c13704ec2"}}, + {ref,"9859ca2843ad1617a0bf8549c125a7e94d1d54b7"}}, + 0}, + {<<"dmt_client">>, + {git,"https://github.com/valitydev/dmt_client.git", + {ref,"19a8ded17c05140f663c7b8b30450d9a1da4f53e"}}, + 0}, + {<<"dmt_core">>, + {git,"https://github.com/valitydev/dmt-core.git", + {ref,"75841332fe0b40a77da0c12ea8d5dbb994da8e82"}}, 1}, {<<"erl_health">>, {git,"https://github.com/valitydev/erlang-health.git", @@ -25,7 +33,7 @@ {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},1}, {<<"limiter_proto">>, {git,"https://github.com/valitydev/limiter-proto.git", - {ref,"31de59b17ad20e426b158ace6097e35330926bea"}}, + {ref,"9b76200a957c0e91bcdf6f16dfbab90d38a3f173"}}, 0}, {<<"machinery">>, {git,"https://github.com/valitydev/machinery-erlang.git", diff --git a/test/machinegun/config.yaml b/test/machinegun/config.yaml index f039c80b..8f5f3975 100644 --- a/test/machinegun/config.yaml +++ b/test/machinegun/config.yaml @@ -15,6 +15,10 @@ namespaces: processor: url: http://limiter:8022/v1/stateproc/lim/range_v1 pool_size: 500 + domain-config: + processor: + url: http://dominant:8022/v1/stateproc + pool_size: 300 storage: type: memory From 5af0cc663a2446314d6882944d8e0c35639d3f17 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Thu, 6 Apr 2023 11:00:38 +0300 Subject: [PATCH 31/54] TD-550: Adds new exceptions support for hold operation (#21) * Adds new exceptions support for hold operation: `InvalidOperationCurrency`, `OperationContextNotSupported` and `PaymentToolNotSupported`. * Adds testcases for new exceptions --- apps/limiter/src/lim_body.erl | 2 +- apps/limiter/src/lim_config_machine.erl | 57 +++++++++++---------- apps/limiter/src/lim_context.erl | 41 +++++++++++---- apps/limiter/src/lim_handler.erl | 13 +++++ apps/limiter/src/lim_rates.erl | 10 ++-- apps/limiter/src/lim_turnover_metric.erl | 17 +++--- apps/limiter/src/lim_turnover_processor.erl | 56 +++++++++++--------- apps/limiter/src/lim_wthdproc_context.erl | 1 - apps/limiter/test/lim_turnover_SUITE.erl | 46 ++++++++++++++--- rebar.lock | 4 +- 10 files changed, 161 insertions(+), 86 deletions(-) diff --git a/apps/limiter/src/lim_body.erl b/apps/limiter/src/lim_body.erl index 9da6c453..c76a1ff0 100644 --- a/apps/limiter/src/lim_body.erl +++ b/apps/limiter/src/lim_body.erl @@ -23,7 +23,7 @@ get(BodyType, Config, LimitContext) -> do(fun() -> ContextType = lim_config_machine:context_type(Config), - {ok, Operation} = lim_context:get_operation(ContextType, LimitContext), + Operation = unwrap(lim_context:get_operation(ContextType, LimitContext)), Body = unwrap(get_body_for_operation(BodyType, ContextType, LimitContext)), apply_op_behaviour(Operation, Body, Config) end). diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index d6bc9755..0b4a6685 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -538,21 +538,24 @@ mk_shard_id(Prefix, Units, ShardSize) -> ID = integer_to_binary(abs(Units) div ShardSize), <>. --spec mk_scope_prefix(config(), lim_context()) -> prefix(). +-spec mk_scope_prefix(config(), lim_context()) -> {ok, prefix()} | {error, lim_context:context_error()}. mk_scope_prefix(Config, LimitContext) -> mk_scope_prefix_impl(scope(Config), context_type(Config), LimitContext). --spec mk_scope_prefix_impl(limit_scope(), context_type(), lim_context()) -> prefix(). +-spec mk_scope_prefix_impl(limit_scope(), context_type(), lim_context()) -> + {ok, prefix()} | {error, lim_context:context_error()}. mk_scope_prefix_impl(Scope, ContextType, LimitContext) -> - Bits = enumerate_context_bits(Scope), - lists:foldl( - fun(Bit, Acc) -> - {ok, Value} = extract_context_bit(Bit, ContextType, LimitContext), - append_prefix(Value, Acc) - end, - <<>>, - Bits - ). + do(fun() -> + Bits = enumerate_context_bits(Scope), + lists:foldl( + fun(Bit, Acc) -> + Value = unwrap(extract_context_bit(Bit, ContextType, LimitContext)), + append_prefix(Value, Acc) + end, + <<>>, + Bits + ) + end). -spec append_prefix(binary(), prefix()) -> prefix(). append_prefix(Fragment, Acc) -> @@ -606,18 +609,20 @@ get_context_bits(terminal) -> get_context_bits(payer_contact_email) -> [{prefix, <<"payer_contact_email">>}, {from, payer_contact_email}]. --spec extract_context_bit(context_bit(), context_type(), lim_context()) -> {ok, binary()}. +-spec extract_context_bit(context_bit(), context_type(), lim_context()) -> + {ok, binary()} | {error, lim_context:context_error()}. extract_context_bit({prefix, Prefix}, _ContextType, _LimitContext) -> {ok, Prefix}; extract_context_bit({from, payment_tool}, ContextType, LimitContext) -> - {ok, PaymentTool} = lim_context:get_value(ContextType, payment_tool, LimitContext), - case PaymentTool of - {bank_card, #{token := Token, exp_date := {Month, Year}}} -> + case lim_context:get_value(ContextType, payment_tool, LimitContext) of + {ok, {bank_card, #{token := Token, exp_date := {Month, Year}}}} -> {ok, mk_scope_component([Token, Month, Year])}; - {bank_card, #{token := Token, exp_date := undefined}} -> + {ok, {bank_card, #{token := Token, exp_date := undefined}}} -> {ok, mk_scope_component([Token, <<"undefined">>])}; - {digital_wallet, #{id := ID, service := Service}} -> - {ok, mk_scope_component([<<"DW">>, Service, ID])} + {ok, {digital_wallet, #{id := ID, service := Service}}} -> + {ok, mk_scope_component([<<"DW">>, Service, ID])}; + {error, _} = Error -> + Error end; extract_context_bit({from, ValueName}, ContextType, LimitContext) -> lim_context:get_value(ContextType, ValueName, LimitContext). @@ -913,22 +918,22 @@ check_calculate_year_shard_id_test() -> -spec global_scope_empty_prefix_test() -> _. global_scope_empty_prefix_test() -> Context = #{context => ?PAYPROC_CTX_INVOICE(?INVOICE(<<"OWNER">>, <<"SHOP">>))}, - ?assertEqual(<<>>, mk_scope_prefix_impl(ordsets:new(), payment_processing, Context)). + ?assertEqual({ok, <<>>}, mk_scope_prefix_impl(ordsets:new(), payment_processing, Context)). -spec preserve_scope_prefix_order_test_() -> [_TestGen]. preserve_scope_prefix_order_test_() -> Context = #{context => ?PAYPROC_CTX_INVOICE(?INVOICE(<<"OWNER">>, <<"SHOP">>))}, [ ?_assertEqual( - <<"/OWNER/SHOP">>, + {ok, <<"/OWNER/SHOP">>}, mk_scope_prefix_impl(ordsets:from_list([shop, party]), payment_processing, Context) ), ?_assertEqual( - <<"/OWNER/SHOP">>, + {ok, <<"/OWNER/SHOP">>}, mk_scope_prefix_impl(ordsets:from_list([party, shop]), payment_processing, Context) ), ?_assertEqual( - <<"/OWNER/SHOP">>, + {ok, <<"/OWNER/SHOP">>}, mk_scope_prefix_impl(ordsets:from_list([shop]), payment_processing, Context) ) ]. @@ -951,19 +956,19 @@ prefix_content_test_() -> }, [ ?_assertEqual( - <<"/terminal/22/2">>, + {ok, <<"/terminal/22/2">>}, mk_scope_prefix_impl(ordsets:from_list([terminal, provider]), payment_processing, Context) ), ?_assertEqual( - <<"/terminal/22/2">>, + {ok, <<"/terminal/22/2">>}, mk_scope_prefix_impl(ordsets:from_list([provider, terminal]), payment_processing, Context) ), ?_assertEqual( - <<"/OWNER/wallet/IDENTITY/WALLET">>, + {ok, <<"/OWNER/wallet/IDENTITY/WALLET">>}, mk_scope_prefix_impl(ordsets:from_list([wallet, identity, party]), withdrawal_processing, WithdrawalContext) ), ?_assertEqual( - <<"/token/2/2022/payer_contact_email/email">>, + {ok, <<"/token/2/2022/payer_contact_email/email">>}, mk_scope_prefix_impl(ordsets:from_list([payer_contact_email, payment_tool]), payment_processing, Context) ) ]. diff --git a/apps/limiter/src/lim_context.erl b/apps/limiter/src/lim_context.erl index de267afa..8fd0271a 100644 --- a/apps/limiter/src/lim_context.erl +++ b/apps/limiter/src/lim_context.erl @@ -1,6 +1,7 @@ -module(lim_context). -include_lib("limiter_proto/include/limproto_limiter_thrift.hrl"). +-include_lib("damsel/include/dmsl_limiter_config_thrift.hrl"). -export([create/1]). -export([woody_context/1]). @@ -26,12 +27,20 @@ -type context_inner() :: lim_payproc_context:context() | lim_wthdproc_context:context(). -type context_operation() :: lim_payproc_context:operation() | lim_wthdproc_context:operation(). +-type unsupported_error(T) :: {unsupported, T}. +-type operation_context_not_supported_error() :: + {operation_context_not_supported, limproto_limiter_thrift:'LimitContextType'()}. +-type context_error() :: notfound | unsupported_error(_) | operation_context_not_supported_error(). + -export_type([t/0]). -export_type([context_type/0]). -export_type([context_operation/0]). +-export_type([unsupported_error/1]). +-export_type([operation_context_not_supported_error/0]). +-export_type([context_error/0]). -callback get_operation(context_inner()) -> {ok, context_operation()} | {error, notfound}. --callback get_value(_Name :: atom(), context_inner()) -> {ok, term()} | {error, notfound | {unsupported, _}}. +-callback get_value(_Name :: atom(), context_inner()) -> {ok, term()} | {error, notfound | unsupported_error(_)}. -spec create(woody_context()) -> t(). create(WoodyContext) -> @@ -56,24 +65,34 @@ set_clock(Clock, LimContext) -> LimContext#{clock => Clock}. -spec get_operation(context_type(), t()) -> - {ok, context_operation()} | {error, notfound}. + {ok, context_operation()} | {error, notfound | operation_context_not_supported_error()}. get_operation(Type, Context) -> - {Mod, OperationContext} = get_operation_context(Type, Context), - Mod:get_operation(OperationContext). + case get_operation_context(Type, Context) of + {error, _} = Error -> Error; + {ok, Mod, OperationContext} -> Mod:get_operation(OperationContext) + end. --spec get_value(context_type(), atom(), t()) -> - {ok, term()} | {error, notfound | {unsupported, _}}. +-spec get_value(context_type(), atom(), t()) -> {ok, term()} | {error, context_error()}. get_value(Type, ValueName, Context) -> - {Mod, OperationContext} = get_operation_context(Type, Context), - Mod:get_value(ValueName, OperationContext). - + case get_operation_context(Type, Context) of + {error, _} = Error -> Error; + {ok, Mod, OperationContext} -> Mod:get_value(ValueName, OperationContext) + end. + +get_operation_context(payment_processing, #{context := #limiter_LimitContext{payment_processing = undefined}}) -> + {error, + {operation_context_not_supported, + {withdrawal_processing, #limiter_config_LimitContextTypeWithdrawalProcessing{}}}}; get_operation_context( payment_processing, #{context := #limiter_LimitContext{payment_processing = PayprocContext}} ) -> - {lim_payproc_context, PayprocContext}; + {ok, lim_payproc_context, PayprocContext}; +get_operation_context(withdrawal_processing, #{context := #limiter_LimitContext{withdrawal_processing = undefined}}) -> + {error, + {operation_context_not_supported, {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}}}; get_operation_context( withdrawal_processing, #{context := #limiter_LimitContext{withdrawal_processing = WithdrawalContext}} ) -> - {lim_wthdproc_context, WithdrawalContext}. + {ok, lim_wthdproc_context, WithdrawalContext}. diff --git a/apps/limiter/src/lim_handler.erl b/apps/limiter/src/lim_handler.erl index d48db86d..2161dfbf 100644 --- a/apps/limiter/src/lim_handler.erl +++ b/apps/limiter/src/lim_handler.erl @@ -90,6 +90,19 @@ handle_get_error(Error) -> -spec handle_hold_error(_) -> no_return(). handle_hold_error({_, {invalid_request, Errors}}) -> woody_error:raise(business, #base_InvalidRequest{errors = Errors}); +handle_hold_error({_, {invalid_operation_currency, {Currency, ExpectedCurrency}}}) -> + woody_error:raise(business, #limiter_InvalidOperationCurrency{ + currency = Currency, + expected_currency = ExpectedCurrency + }); +handle_hold_error({_, {operation_context_not_supported, ContextType}}) -> + woody_error:raise(business, #limiter_OperationContextNotSupported{ + context_type = ContextType + }); +handle_hold_error({_, {unsupported, {payment_tool, Type}}}) -> + woody_error:raise(business, #limiter_PaymentToolNotSupported{ + payment_tool = atom_to_binary(Type) + }); handle_hold_error(Error) -> handle_default_error(Error). diff --git a/apps/limiter/src/lim_rates.erl b/apps/limiter/src/lim_rates.erl index 5526f8bb..bc02f631 100644 --- a/apps/limiter/src/lim_rates.erl +++ b/apps/limiter/src/lim_rates.erl @@ -16,11 +16,15 @@ -define(DEFAULT_FACTOR_NAME, <<"DEFAULT">>). -spec convert(lim_body:cash(), lim_body:currency(), config(), limit_context()) -> - {ok, lim_body:cash()} - | {error, conversion_error()}. + {ok, lim_body:cash()} | {error, conversion_error() | lim_context:context_error()}. convert(#{amount := Amount, currency := Currency}, DestinationCurrency, Config, LimitContext) -> ContextType = lim_config_machine:context_type(Config), - {ok, Timestamp} = lim_context:get_value(ContextType, created_at, LimitContext), + case lim_context:get_value(ContextType, created_at, LimitContext) of + {ok, Timestamp} -> do_convert(Timestamp, Amount, Currency, DestinationCurrency, LimitContext); + {error, _} = Error -> Error + end. + +do_convert(Timestamp, Amount, Currency, DestinationCurrency, LimitContext) -> Request = #rate_ConversionRequest{ source = Currency, destination = DestinationCurrency, diff --git a/apps/limiter/src/lim_turnover_metric.erl b/apps/limiter/src/lim_turnover_metric.erl index d5a931f3..f27a3093 100644 --- a/apps/limiter/src/lim_turnover_metric.erl +++ b/apps/limiter/src/lim_turnover_metric.erl @@ -7,14 +7,15 @@ -type stage() :: hold | commit. -type t() :: number | {amount, currency()}. --type invalid_request_error() :: {invalid_request, list(binary())}. +-type invalid_operation_currency_error() :: {invalid_operation_currency, {currency(), currency()}}. -export_type([t/0]). +-export_type([invalid_operation_currency_error/0]). %% -spec compute(t(), stage(), lim_config_machine:config(), lim_context:t()) -> - {ok, amount()} | {error, lim_rates:conversion_error()} | {error, invalid_request_error()}. + {ok, amount()} | {error, lim_rates:conversion_error()} | {error, invalid_operation_currency_error()}. compute(number, hold, Config, LimitContext) -> #{amount := Amount} = get_body(Config, LimitContext), {ok, sign(Amount)}; @@ -51,18 +52,12 @@ denominate(#{amount := Amount, currency := Currency}, Currency, _Config, _LimitC {ok, Amount}; denominate(Body = #{currency := Currency}, DestinationCurrency, Config, LimitContext) -> case genlib_app:env(limiter, currency_conversion, disabled) of - disabled -> invalid_request_currencies_mismatch(Currency, DestinationCurrency); + disabled -> currencies_mismatch_error(Currency, DestinationCurrency); enabled -> convert_currency(Body, DestinationCurrency, Config, LimitContext) end. -invalid_request_currencies_mismatch(Currency, DestinationCurrency) -> - {error, - {invalid_request, [ - genlib:format( - "Invalid operation currency: ~p, expected limit currency: ~p", - [Currency, DestinationCurrency] - ) - ]}}. +currencies_mismatch_error(Currency, ExpectedCurrency) -> + {error, {invalid_operation_currency, {Currency, ExpectedCurrency}}}. convert_currency(Body, DestinationCurrency, Config, LimitContext) -> case lim_rates:convert(Body, DestinationCurrency, Config, LimitContext) of diff --git a/apps/limiter/src/lim_turnover_processor.erl b/apps/limiter/src/lim_turnover_processor.erl index 1cdfa549..02abc76c 100644 --- a/apps/limiter/src/lim_turnover_processor.erl +++ b/apps/limiter/src/lim_turnover_processor.erl @@ -26,7 +26,10 @@ -type hold_error() :: lim_rates:conversion_error() - | lim_accounting:invalid_request_error(). + | lim_accounting:invalid_request_error() + | lim_turnover_metric:invalid_operation_currency_error() + | lim_context:operation_context_not_supported_error() + | lim_context:unsupported_error({payment_tool, atom()}). -type commit_error() :: {forbidden_operation_amount, forbidden_operation_amount_error()} @@ -47,7 +50,7 @@ -spec get_limit(lim_id(), config(), lim_context()) -> {ok, limit()} | {error, get_limit_error()}. get_limit(LimitID, Config, LimitContext) -> do(fun() -> - {LimitRangeID, TimeRange} = compute_limit_time_range_location(LimitID, Config, LimitContext), + {LimitRangeID, TimeRange} = unwrap(compute_limit_time_range_location(LimitID, Config, LimitContext)), Amount = find_range_balance_amount(LimitRangeID, TimeRange, LimitContext), #limiter_Limit{ id = LimitRangeID, @@ -68,7 +71,7 @@ find_range_balance_amount(LimitRangeID, TimeRange, LimitContext) -> -spec hold(lim_change(), config(), lim_context()) -> ok | {error, hold_error()}. hold(LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) -> do(fun() -> - TimeRangeAccount = ensure_limit_time_range(LimitID, Config, LimitContext), + TimeRangeAccount = unwrap(ensure_limit_time_range(LimitID, Config, LimitContext)), Metric = unwrap(compute_metric(hold, Config, LimitContext)), Posting = lim_posting:new(TimeRangeAccount, Metric, currency(Config)), unwrap(lim_accounting:hold(construct_plan_id(LimitChange), {1, [Posting]}, LimitContext)) @@ -77,7 +80,7 @@ hold(LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) -> -spec commit(lim_change(), config(), lim_context()) -> ok | {error, commit_error()}. commit(LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) -> do(fun() -> - TimeRangeAccount = ensure_limit_time_range(LimitID, Config, LimitContext), + TimeRangeAccount = unwrap(ensure_limit_time_range(LimitID, Config, LimitContext)), PlanID = construct_plan_id(LimitChange), Operations = construct_commit_plan(TimeRangeAccount, Config, LimitContext), ok = lists:foreach( @@ -101,42 +104,49 @@ commit(LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) - -spec rollback(lim_change(), config(), lim_context()) -> ok | {error, rollback_error()}. rollback(LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) -> do(fun() -> - TimeRangeAccount = ensure_limit_time_range(LimitID, Config, LimitContext), + TimeRangeAccount = unwrap(ensure_limit_time_range(LimitID, Config, LimitContext)), Metric = unwrap(compute_metric(hold, Config, LimitContext)), Posting = lim_posting:new(TimeRangeAccount, Metric, currency(Config)), unwrap(lim_accounting:rollback(construct_plan_id(LimitChange), [{1, [Posting]}], LimitContext)) end). compute_limit_time_range_location(LimitID, Config, LimitContext) -> - Timestamp = get_timestamp(Config, LimitContext), - LimitRangeID = construct_range_id(LimitID, Timestamp, Config, LimitContext), - TimeRange = lim_config_machine:calculate_time_range(Timestamp, Config), - {LimitRangeID, TimeRange}. + do(fun() -> + Timestamp = unwrap(get_timestamp(Config, LimitContext)), + LimitRangeID = unwrap(construct_range_id(LimitID, Timestamp, Config, LimitContext)), + TimeRange = lim_config_machine:calculate_time_range(Timestamp, Config), + {LimitRangeID, TimeRange} + end). ensure_limit_time_range(LimitID, Config, LimitContext) -> - Timestamp = get_timestamp(Config, LimitContext), - {LimitRangeID, TimeRange} = compute_limit_time_range_location(LimitID, Config, LimitContext), - CreateParams = genlib_map:compact(#{ - id => LimitRangeID, - type => lim_config_machine:time_range_type(Config), - created_at => Timestamp, - currency => currency(Config) - }), - unwrap(lim_range_machine:ensure_exists(CreateParams, TimeRange, LimitContext)). + do(fun() -> + Timestamp = unwrap(get_timestamp(Config, LimitContext)), + {LimitRangeID, TimeRange} = unwrap(compute_limit_time_range_location(LimitID, Config, LimitContext)), + CreateParams = genlib_map:compact(#{ + id => LimitRangeID, + type => lim_config_machine:time_range_type(Config), + created_at => Timestamp, + currency => currency(Config) + }), + unwrap(lim_range_machine:ensure_exists(CreateParams, TimeRange, LimitContext)) + end). get_timestamp(Config, LimitContext) -> ContextType = lim_config_machine:context_type(Config), - {ok, Timestamp} = lim_context:get_value(ContextType, created_at, LimitContext), - Timestamp. + lim_context:get_value(ContextType, created_at, LimitContext). construct_plan_id(#limiter_LimitChange{change_id = ChangeID}) -> % DISCUSS ChangeID. construct_range_id(LimitID, Timestamp, Config, LimitContext) -> - Prefix = lim_config_machine:mk_scope_prefix(Config, LimitContext), - ShardID = lim_config_machine:calculate_shard_id(Timestamp, Config), - <>. + case lim_config_machine:mk_scope_prefix(Config, LimitContext) of + {ok, Prefix} -> + ShardID = lim_config_machine:calculate_shard_id(Timestamp, Config), + {ok, <>}; + {error, _} = Error -> + Error + end. construct_commit_plan(TimeRangeAccount, Config, LimitContext) -> MetricHold = unwrap(compute_metric(hold, Config, LimitContext)), diff --git a/apps/limiter/src/lim_wthdproc_context.erl b/apps/limiter/src/lim_wthdproc_context.erl index 2b325441..c3a6b4ce 100644 --- a/apps/limiter/src/lim_wthdproc_context.erl +++ b/apps/limiter/src/lim_wthdproc_context.erl @@ -3,7 +3,6 @@ -include_lib("limiter_proto/include/limproto_context_withdrawal_thrift.hrl"). -include_lib("limiter_proto/include/limproto_base_thrift.hrl"). -include_lib("damsel/include/dmsl_wthd_domain_thrift.hrl"). --include_lib("damsel/include/dmsl_domain_thrift.hrl"). -behaviour(lim_context). -export([get_operation/1]). diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index abd1a6e2..47a3042a 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -20,7 +20,9 @@ -export([commit_with_default_exchange/1]). -export([partial_commit_with_exchange/1]). -export([commit_with_exchange/1]). --export([commit_with_disabled_exchange/1]). +-export([hold_with_disabled_exchange/1]). +-export([hold_with_wrong_operation_context/1]). +-export([hold_with_wrong_payment_tool/1]). -export([get_limit_ok/1]). -export([get_limit_notfound/1]). -export([hold_ok/1]). @@ -76,7 +78,9 @@ groups() -> commit_with_default_exchange, partial_commit_with_exchange, commit_with_exchange, - commit_with_disabled_exchange, + hold_with_disabled_exchange, + hold_with_wrong_operation_context, + hold_with_wrong_payment_tool, get_limit_ok, get_limit_notfound, hold_ok, @@ -101,7 +105,9 @@ groups() -> commit_with_default_exchange, partial_commit_with_exchange, commit_with_exchange, - commit_with_disabled_exchange, + hold_with_disabled_exchange, + hold_with_wrong_operation_context, + hold_with_wrong_payment_tool, get_limit_ok, get_limit_notfound, hold_ok, @@ -280,15 +286,39 @@ commit_with_exchange(C) -> {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), {ok, #limiter_Limit{amount = 10500}} = lim_client:get(ID, Version, Context, ?config(client, C)). --spec commit_with_disabled_exchange(config()) -> _. -commit_with_disabled_exchange(C) -> +-spec hold_with_disabled_exchange(config()) -> _. +hold_with_disabled_exchange(C) -> ok = application:set_env(limiter, currency_conversion, disabled), Rational = #base_Rational{p = 1000000, q = 100}, _ = mock_exchange(Rational, C), - {ID, Version} = configure_limit(?time_range_month(), ?global(), C), - Cost = ?cash(10000, <<"USD">>), + ConfiguredCurrency = <<"RUB">>, + {ID, Version} = configure_limit(?time_range_month(), ?global(), ?turnover_metric_amount(ConfiguredCurrency), C), + Currency = <<"USD">>, + Cost = ?cash(10000, Currency), Context = ?payproc_ctx_invoice(Cost), - {exception, #base_InvalidRequest{}} = + {exception, #limiter_InvalidOperationCurrency{currency = Currency, expected_currency = ConfiguredCurrency}} = + lim_client:hold(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)). + +-spec hold_with_wrong_operation_context(config()) -> _. +hold_with_wrong_operation_context(C) -> + Rational = #base_Rational{p = 1000000, q = 100}, + _ = mock_exchange(Rational, C), + {ID, Version} = configure_limit(?time_range_month(), ?global(), C), + Cost = ?cash(10000), + Context = ?wthdproc_ctx_withdrawal(Cost), + {exception, #limiter_OperationContextNotSupported{ + context_type = {withdrawal_processing, #limiter_config_LimitContextTypeWithdrawalProcessing{}} + }} = + lim_client:hold(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)). + +-spec hold_with_wrong_payment_tool(config()) -> _. +hold_with_wrong_payment_tool(C) -> + Rational = #base_Rational{p = 1000000, q = 100}, + _ = mock_exchange(Rational, C), + {ID, Version} = configure_limit(?time_range_week(), ?scope([?scope_payment_tool()]), ?turnover_metric_number(), C), + NotSupportedPaymentTool = {crypto_currency, #domain_CryptoCurrencyRef{id = <<"wow;so-cryptic;much-hidden">>}}, + Context = ?payproc_ctx_payment(?invoice_payment(?cash(10000), ?cash(10000), NotSupportedPaymentTool)), + {exception, #limiter_PaymentToolNotSupported{payment_tool = <<"crypto_currency">>}} = lim_client:hold(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)). -spec get_limit_ok(config()) -> _. diff --git a/rebar.lock b/rebar.lock index 92dc3a09..8b625abd 100644 --- a/rebar.lock +++ b/rebar.lock @@ -9,7 +9,7 @@ {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.11.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"9859ca2843ad1617a0bf8549c125a7e94d1d54b7"}}, + {ref,"698c7d275fbe6a8f06f209638ede093f3134fc9b"}}, 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git", @@ -33,7 +33,7 @@ {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},1}, {<<"limiter_proto">>, {git,"https://github.com/valitydev/limiter-proto.git", - {ref,"9b76200a957c0e91bcdf6f16dfbab90d38a3f173"}}, + {ref,"e46f732ae357ae302b9b9e4cd8e95725bfd060ee"}}, 0}, {<<"machinery">>, {git,"https://github.com/valitydev/machinery-erlang.git", From 2b8723b0d18dc39b3b92d1d3b23509b6ef51b629 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Fri, 14 Apr 2023 10:21:26 +0300 Subject: [PATCH 32/54] TD-550: Adds business exceptions for rollback (#22) * Bumps limiter-proto --- apps/limiter/src/lim_handler.erl | 14 +++++--- apps/limiter/test/lim_turnover_SUITE.erl | 44 ++++++++++++++++++++++++ rebar.lock | 2 +- 3 files changed, 54 insertions(+), 6 deletions(-) diff --git a/apps/limiter/src/lim_handler.erl b/apps/limiter/src/lim_handler.erl index 2161dfbf..c1cbcb0d 100644 --- a/apps/limiter/src/lim_handler.erl +++ b/apps/limiter/src/lim_handler.erl @@ -90,20 +90,24 @@ handle_get_error(Error) -> -spec handle_hold_error(_) -> no_return(). handle_hold_error({_, {invalid_request, Errors}}) -> woody_error:raise(business, #base_InvalidRequest{errors = Errors}); -handle_hold_error({_, {invalid_operation_currency, {Currency, ExpectedCurrency}}}) -> +handle_hold_error(Error) -> + handle_business_error(Error). + +-spec handle_business_error(_) -> no_return(). +handle_business_error({_, {invalid_operation_currency, {Currency, ExpectedCurrency}}}) -> woody_error:raise(business, #limiter_InvalidOperationCurrency{ currency = Currency, expected_currency = ExpectedCurrency }); -handle_hold_error({_, {operation_context_not_supported, ContextType}}) -> +handle_business_error({_, {operation_context_not_supported, ContextType}}) -> woody_error:raise(business, #limiter_OperationContextNotSupported{ context_type = ContextType }); -handle_hold_error({_, {unsupported, {payment_tool, Type}}}) -> +handle_business_error({_, {unsupported, {payment_tool, Type}}}) -> woody_error:raise(business, #limiter_PaymentToolNotSupported{ payment_tool = atom_to_binary(Type) }); -handle_hold_error(Error) -> +handle_business_error(Error) -> handle_default_error(Error). -spec handle_commit_error(_) -> no_return(). @@ -118,7 +122,7 @@ handle_commit_error(Error) -> handle_rollback_error({_, {invalid_request, Errors}}) -> woody_error:raise(business, #base_InvalidRequest{errors = Errors}); handle_rollback_error(Error) -> - handle_default_error(Error). + handle_business_error(Error). -spec handle_default_error(_) -> no_return(). handle_default_error({config, notfound}) -> diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index 47a3042a..1d19cd2c 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -21,8 +21,11 @@ -export([partial_commit_with_exchange/1]). -export([commit_with_exchange/1]). -export([hold_with_disabled_exchange/1]). +-export([rollback_with_wrong_currency/1]). -export([hold_with_wrong_operation_context/1]). +-export([rollback_with_wrong_operation_context/1]). -export([hold_with_wrong_payment_tool/1]). +-export([rollback_with_wrong_payment_tool/1]). -export([get_limit_ok/1]). -export([get_limit_notfound/1]). -export([hold_ok/1]). @@ -79,8 +82,11 @@ groups() -> partial_commit_with_exchange, commit_with_exchange, hold_with_disabled_exchange, + rollback_with_wrong_currency, hold_with_wrong_operation_context, + rollback_with_wrong_operation_context, hold_with_wrong_payment_tool, + rollback_with_wrong_payment_tool, get_limit_ok, get_limit_notfound, hold_ok, @@ -106,8 +112,11 @@ groups() -> partial_commit_with_exchange, commit_with_exchange, hold_with_disabled_exchange, + rollback_with_wrong_currency, hold_with_wrong_operation_context, + rollback_with_wrong_operation_context, hold_with_wrong_payment_tool, + rollback_with_wrong_payment_tool, get_limit_ok, get_limit_notfound, hold_ok, @@ -299,6 +308,19 @@ hold_with_disabled_exchange(C) -> {exception, #limiter_InvalidOperationCurrency{currency = Currency, expected_currency = ConfiguredCurrency}} = lim_client:hold(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)). +-spec rollback_with_wrong_currency(config()) -> _. +rollback_with_wrong_currency(C) -> + ok = application:set_env(limiter, currency_conversion, disabled), + Rational = #base_Rational{p = 1000000, q = 100}, + _ = mock_exchange(Rational, C), + ConfiguredCurrency = <<"RUB">>, + {ID, Version} = configure_limit(?time_range_month(), ?global(), ?turnover_metric_amount(ConfiguredCurrency), C), + Currency = <<"USD">>, + Cost = ?cash(10000, Currency), + Context = ?payproc_ctx_invoice(Cost), + {exception, #limiter_InvalidOperationCurrency{currency = Currency, expected_currency = ConfiguredCurrency}} = + lim_client:rollback(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)). + -spec hold_with_wrong_operation_context(config()) -> _. hold_with_wrong_operation_context(C) -> Rational = #base_Rational{p = 1000000, q = 100}, @@ -311,6 +333,18 @@ hold_with_wrong_operation_context(C) -> }} = lim_client:hold(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)). +-spec rollback_with_wrong_operation_context(config()) -> _. +rollback_with_wrong_operation_context(C) -> + Rational = #base_Rational{p = 1000000, q = 100}, + _ = mock_exchange(Rational, C), + {ID, Version} = configure_limit(?time_range_month(), ?global(), C), + Cost = ?cash(10000), + Context = ?wthdproc_ctx_withdrawal(Cost), + {exception, #limiter_OperationContextNotSupported{ + context_type = {withdrawal_processing, #limiter_config_LimitContextTypeWithdrawalProcessing{}} + }} = + lim_client:rollback(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)). + -spec hold_with_wrong_payment_tool(config()) -> _. hold_with_wrong_payment_tool(C) -> Rational = #base_Rational{p = 1000000, q = 100}, @@ -321,6 +355,16 @@ hold_with_wrong_payment_tool(C) -> {exception, #limiter_PaymentToolNotSupported{payment_tool = <<"crypto_currency">>}} = lim_client:hold(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)). +-spec rollback_with_wrong_payment_tool(config()) -> _. +rollback_with_wrong_payment_tool(C) -> + Rational = #base_Rational{p = 1000000, q = 100}, + _ = mock_exchange(Rational, C), + {ID, Version} = configure_limit(?time_range_week(), ?scope([?scope_payment_tool()]), ?turnover_metric_number(), C), + NotSupportedPaymentTool = {crypto_currency, #domain_CryptoCurrencyRef{id = <<"wow;so-cryptic;much-hidden">>}}, + Context = ?payproc_ctx_payment(?invoice_payment(?cash(10000), ?cash(10000), NotSupportedPaymentTool)), + {exception, #limiter_PaymentToolNotSupported{payment_tool = <<"crypto_currency">>}} = + lim_client:rollback(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)). + -spec get_limit_ok(config()) -> _. get_limit_ok(C) -> {ID, Version} = configure_limit(?time_range_month(), ?global(), C), diff --git a/rebar.lock b/rebar.lock index 8b625abd..f40a1377 100644 --- a/rebar.lock +++ b/rebar.lock @@ -33,7 +33,7 @@ {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},1}, {<<"limiter_proto">>, {git,"https://github.com/valitydev/limiter-proto.git", - {ref,"e46f732ae357ae302b9b9e4cd8e95725bfd060ee"}}, + {ref,"bbd2c0dce044dd5b4e424fc8e38a0023a1685a22"}}, 0}, {<<"machinery">>, {git,"https://github.com/valitydev/machinery-erlang.git", From 32484dbd2f8ee20539e9f6246885a86b031166dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D0=B5=D0=BC?= Date: Sun, 21 May 2023 14:08:40 +0300 Subject: [PATCH 33/54] bumped to valitydev/damsel@52dbefd (#23) --- rebar.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebar.lock b/rebar.lock index f40a1377..fbc5d2fb 100644 --- a/rebar.lock +++ b/rebar.lock @@ -9,7 +9,7 @@ {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.11.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"698c7d275fbe6a8f06f209638ede093f3134fc9b"}}, + {ref,"52dbefd54fd68d0c04f8ded5734b27635e939c60"}}, 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git", From d5fc565b1a658f251e498304b7effbcb366d6041 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Wed, 5 Jul 2023 13:51:17 +0300 Subject: [PATCH 34/54] OPS-268: Setups user in `Dockerfile` (#24) --- Dockerfile | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Dockerfile b/Dockerfile index ec0732d9..64717a78 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,6 +23,8 @@ RUN rebar3 compile && \ FROM docker.io/library/erlang:${OTP_VERSION}-slim ARG SERVICE_NAME +ARG USER_UID=1001 +ARG USER_GID=$USER_UID # Set env ENV CHARSET=UTF-8 @@ -36,6 +38,12 @@ COPY --from=builder /build/_build/prod/rel/${SERVICE_NAME} /opt/${SERVICE_NAME} RUN echo "#!/bin/sh" >> /entrypoint.sh && \ echo "exec /opt/${SERVICE_NAME}/bin/${SERVICE_NAME} foreground" >> /entrypoint.sh && \ chmod +x /entrypoint.sh + +# Setup user +RUN groupadd --gid ${USER_GID} ${SERVICE_NAME} && \ + useradd --uid ${USER_UID} --gid ${USER_GID} -M ${SERVICE_NAME} +USER ${SERVICE_NAME} + ENTRYPOINT [] CMD ["/entrypoint.sh"] From 6c3bfe319e5330c8f6925b2590423deb3639b056 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Thu, 13 Jul 2023 15:28:32 +0300 Subject: [PATCH 35/54] OPS-268: Adds default logger permissions (#25) --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index 64717a78..4f386cb2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -41,6 +41,8 @@ RUN echo "#!/bin/sh" >> /entrypoint.sh && \ # Setup user RUN groupadd --gid ${USER_GID} ${SERVICE_NAME} && \ + mkdir /var/log/${SERVICE_NAME} && \ + chown ${USER_UID}:${USER_GID} /var/log/${SERVICE_NAME} && \ useradd --uid ${USER_UID} --gid ${USER_GID} -M ${SERVICE_NAME} USER ${SERVICE_NAME} From e9e5e630d1f854abbf54bef703c218cd53752f56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D0=B5=D0=BC?= Date: Mon, 4 Sep 2023 15:33:15 +0300 Subject: [PATCH 36/54] OPS-356: Remove inv adj (#26) * removed * bumped to valitydev/damsel@c32f50f and valitydev/limiter-proto@e045813 --- apps/limiter/src/lim_payproc_context.erl | 9 --------- rebar.lock | 4 ++-- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/apps/limiter/src/lim_payproc_context.erl b/apps/limiter/src/lim_payproc_context.erl index 7458ab92..cf2e78df 100644 --- a/apps/limiter/src/lim_payproc_context.erl +++ b/apps/limiter/src/lim_payproc_context.erl @@ -12,7 +12,6 @@ -type operation() :: invoice - | invoice_adjustment | invoice_payment | invoice_payment_adjustment | invoice_payment_refund @@ -67,12 +66,6 @@ get_value(ValueName, _Operation, _Context) -> } }). --define(INVOICE_ADJUSTMENT(V), #context_payproc_Context{ - invoice = #context_payproc_Invoice{ - adjustment = V = #domain_InvoiceAdjustment{} - } -}). - -define(INVOICE_PAYMENT(V), #context_payproc_Context{ invoice = #context_payproc_Invoice{ payment = #context_payproc_InvoicePayment{payment = V = #domain_InvoicePayment{}} @@ -115,8 +108,6 @@ get_shop_id(_) -> get_created_at(invoice, ?INVOICE(Invoice)) -> {ok, Invoice#domain_Invoice.created_at}; -get_created_at(invoice_adjustment, ?INVOICE_ADJUSTMENT(Adjustment)) -> - {ok, Adjustment#domain_InvoiceAdjustment.created_at}; get_created_at(invoice_payment, ?INVOICE_PAYMENT(Payment)) -> {ok, Payment#domain_InvoicePayment.created_at}; get_created_at(invoice_payment_adjustment, ?INVOICE_PAYMENT_ADJUSTMENT(Adjustment)) -> diff --git a/rebar.lock b/rebar.lock index fbc5d2fb..d1d47393 100644 --- a/rebar.lock +++ b/rebar.lock @@ -9,7 +9,7 @@ {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.11.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"52dbefd54fd68d0c04f8ded5734b27635e939c60"}}, + {ref,"c32f50f875bd1c58957e507420a23f7d2fac3ecb"}}, 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git", @@ -33,7 +33,7 @@ {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},1}, {<<"limiter_proto">>, {git,"https://github.com/valitydev/limiter-proto.git", - {ref,"bbd2c0dce044dd5b4e424fc8e38a0023a1685a22"}}, + {ref,"e045813d32e67432e5592d582e59e45df05da647"}}, 0}, {<<"machinery">>, {git,"https://github.com/valitydev/machinery-erlang.git", From 920d6ac31e53bc13651a53b0f407fc3d2be9a802 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Tue, 24 Oct 2023 14:50:19 +0300 Subject: [PATCH 37/54] TD-686: Adopts opentelemetry API (#27) --- Makefile | 7 ++-- apps/limiter/src/limiter.app.src | 5 ++- compose.tracing.yaml | 21 ++++++++++++ compose.yml => compose.yaml | 6 ++-- rebar.config | 8 +++-- rebar.lock | 56 ++++++++++++++++++++++++++------ test/machinegun/config.yaml | 13 ++++++-- 7 files changed, 93 insertions(+), 23 deletions(-) create mode 100644 compose.tracing.yaml rename compose.yml => compose.yaml (91%) diff --git a/Makefile b/Makefile index f3fc564f..25c966b0 100644 --- a/Makefile +++ b/Makefile @@ -11,12 +11,12 @@ DOTENV := $(shell grep -v '^\#' .env) # Development images -DEV_IMAGE_TAG = limiter-dev +DEV_IMAGE_TAG = $(TEST_CONTAINER_NAME)-dev DEV_IMAGE_ID = $(file < .image.dev) DOCKER ?= docker DOCKERCOMPOSE ?= docker-compose -DOCKERCOMPOSE_W_ENV = DEV_IMAGE_TAG=$(DEV_IMAGE_TAG) $(DOCKERCOMPOSE) +DOCKERCOMPOSE_W_ENV = DEV_IMAGE_TAG=$(DEV_IMAGE_TAG) $(DOCKERCOMPOSE) -f compose.yaml -f compose.tracing.yaml REBAR ?= rebar3 TEST_CONTAINER_NAME ?= testrunner @@ -28,7 +28,7 @@ dev-image: .image.dev .image.dev: Dockerfile.dev .env env $(DOTENV) $(DOCKERCOMPOSE_W_ENV) build $(TEST_CONTAINER_NAME) - $(DOCKER) image ls -q -f "reference=$(DEV_IMAGE_TAG)" | head -n1 > $@ + $(DOCKER) image ls -q -f "reference=$(DEV_IMAGE_ID)" | head -n1 > $@ clean-dev-image: ifneq ($(DEV_IMAGE_ID),) @@ -50,7 +50,6 @@ wc-shell: dev-image wc-%: dev-image $(DOCKER_RUN) $(DEV_IMAGE_TAG) make $* -# TODO docker compose down doesn't work yet wdeps-shell: dev-image $(DOCKERCOMPOSE_RUN) $(TEST_CONTAINER_NAME) su; \ $(DOCKERCOMPOSE_W_ENV) down diff --git a/apps/limiter/src/limiter.app.src b/apps/limiter/src/limiter.app.src index 968afd6f..fa46fefb 100644 --- a/apps/limiter/src/limiter.app.src +++ b/apps/limiter/src/limiter.app.src @@ -11,7 +11,10 @@ woody, scoper, % should be before any scoper event handler usage erl_health, - dmt_client + dmt_client, + opentelemetry_api, + opentelemetry_exporter, + opentelemetry ]}, {mod, {limiter, []}}, {env, []} diff --git a/compose.tracing.yaml b/compose.tracing.yaml new file mode 100644 index 00000000..f712b3c5 --- /dev/null +++ b/compose.tracing.yaml @@ -0,0 +1,21 @@ +services: + testrunner: + depends_on: + jaeger: + condition: service_healthy + + jaeger: + image: jaegertracing/all-in-one:1.47 + environment: + - COLLECTOR_OTLP_ENABLED=true + healthcheck: + test: "/go/bin/all-in-one-linux status" + interval: 2s + timeout: 1s + retries: 20 + ports: + - 4317:4317 # OTLP gRPC receiver + - 4318:4318 # OTLP http receiver + - 5778:5778 + - 14250:14250 + - 16686:16686 diff --git a/compose.yml b/compose.yaml similarity index 91% rename from compose.yml rename to compose.yaml index c5219062..a08a671c 100644 --- a/compose.yml +++ b/compose.yaml @@ -23,7 +23,7 @@ services: condition: service_healthy dominant: - image: ghcr.io/valitydev/dominant:sha-e0afa44 + image: ghcr.io/valitydev/dominant:sha-2150eea command: /opt/dominant/bin/dominant foreground depends_on: machinegun: @@ -35,10 +35,10 @@ services: retries: 10 machinegun: - image: ghcr.io/valitydev/machinegun:sha-7e785cd + image: ghcr.io/valitydev/machinegun:sha-5c0db56 + command: /opt/machinegun/bin/machinegun foreground volumes: - ./test/machinegun/config.yaml:/opt/machinegun/etc/config.yaml - - ./test/log/machinegun:/var/log/machinegun - ./test/machinegun/cookie:/opt/machinegun/etc/cookie healthcheck: test: "/opt/machinegun/bin/machinegun ping" diff --git a/rebar.config b/rebar.config index 6df9915e..4fbecdb9 100644 --- a/rebar.config +++ b/rebar.config @@ -34,7 +34,10 @@ {genlib, {git, "https://github.com/valitydev/genlib.git", {branch, "master"}}}, {scoper, {git, "https://github.com/valitydev/scoper.git", {branch, "master"}}}, {woody, {git, "https://github.com/valitydev/woody_erlang.git", {branch, "master"}}}, - {dmt_client, {git, "https://github.com/valitydev/dmt_client.git", {branch, "master"}}} + {dmt_client, {git, "https://github.com/valitydev/dmt_client.git", {branch, "master"}}}, + {opentelemetry_api, "1.2.1"}, + {opentelemetry, "1.3.0"}, + {opentelemetry_exporter, "1.3.0"} ]}. %% XRef checks @@ -68,7 +71,7 @@ {prometheus, "4.8.1"}, {prometheus_cowboy, "0.1.8"}, {logger_logstash_formatter, - {git, "https://github.com/valitydev/logger_logstash_formatter.git", {ref, "2c7b716"}}}, + {git, "https://github.com/valitydev/logger_logstash_formatter.git", {ref, "08a66a6"}}}, {iosetopts, {git, "https://github.com/valitydev/iosetopts.git", {ref, "edb445c"}}} ]}, {relx, [ @@ -77,6 +80,7 @@ {recon, load}, {runtime_tools, load}, {tools, load}, + {opentelemetry, temporary}, {logger_logstash_formatter, load}, prometheus, prometheus_cowboy, diff --git a/rebar.lock b/rebar.lock index d1d47393..e0db19f7 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,19 +1,22 @@ {"1.2.0", -[{<<"cache">>,{pkg,<<"cache">>,<<"2.3.3">>},1}, +[{<<"acceptor_pool">>,{pkg,<<"acceptor_pool">>,<<"1.0.0">>},2}, + {<<"cache">>,{pkg,<<"cache">>,<<"2.3.3">>},1}, {<<"certifi">>,{pkg,<<"certifi">>,<<"2.8.0">>},2}, {<<"cg_mon">>, {git,"https://github.com/rbkmoney/cg_mon.git", {ref,"5a87a37694e42b6592d3b4164ae54e0e87e24e18"}}, 1}, + {<<"chatterbox">>,{pkg,<<"ts_chatterbox">>,<<"0.13.0">>},2}, {<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.9.0">>},1}, {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.11.0">>},2}, + {<<"ctx">>,{pkg,<<"ctx">>,<<"0.6.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"c32f50f875bd1c58957e507420a23f7d2fac3ecb"}}, + {ref,"f718741970470474efcd32800daf885cb8d75584"}}, 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git", - {ref,"19a8ded17c05140f663c7b8b30450d9a1da4f53e"}}, + {ref,"b8bc0281dbf1e55a1a67ef6da861e0353ff14913"}}, 0}, {<<"dmt_core">>, {git,"https://github.com/valitydev/dmt-core.git", @@ -21,14 +24,16 @@ 1}, {<<"erl_health">>, {git,"https://github.com/valitydev/erlang-health.git", - {ref,"5958e2f35cd4d09f40685762b82b82f89b4d9333"}}, + {ref,"7ffbc855bdbe79e23efad1803b0b185c9ea8d2f1"}}, 0}, {<<"genlib">>, {git,"https://github.com/valitydev/genlib.git", - {ref,"82c5ff3866e3019eb347c7f1d8f1f847bed28c10"}}, + {ref,"f6074551d6586998e91a97ea20acb47241254ff3"}}, 0}, {<<"gproc">>,{pkg,<<"gproc">>,<<"0.9.0">>},1}, + {<<"grpcbox">>,{pkg,<<"grpcbox">>,<<"0.16.0">>},1}, {<<"hackney">>,{pkg,<<"hackney">>,<<"1.18.0">>},1}, + {<<"hpack">>,{pkg,<<"hpack_erl">>,<<"0.2.3">>},3}, {<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},2}, {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},1}, {<<"limiter_proto">>, @@ -45,25 +50,36 @@ {ref,"347c5c44c8dcca24a50e0509c0df5401f863e790"}}, 1}, {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.2.0">>},2}, + {<<"opentelemetry">>,{pkg,<<"opentelemetry">>,<<"1.3.0">>},0}, + {<<"opentelemetry_api">>,{pkg,<<"opentelemetry_api">>,<<"1.2.1">>},0}, + {<<"opentelemetry_exporter">>, + {pkg,<<"opentelemetry_exporter">>,<<"1.3.0">>}, + 0}, + {<<"opentelemetry_semantic_conventions">>, + {pkg,<<"opentelemetry_semantic_conventions">>,<<"0.2.0">>}, + 1}, {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.1">>},2}, {<<"ranch">>,{pkg,<<"ranch">>,<<"1.8.0">>},2}, {<<"scoper">>, {git,"https://github.com/valitydev/scoper.git", - {ref,"7f3183df279bc8181efe58dafd9cae164f495e6f"}}, + {ref,"41a14a558667316998af9f49149ee087ffa8bef2"}}, 0}, {<<"snowflake">>, {git,"https://github.com/valitydev/snowflake.git", {ref,"de159486ef40cec67074afe71882bdc7f7deab72"}}, 1}, - {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.6">>},2}, + {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.7">>},2}, {<<"thrift">>, {git,"https://github.com/valitydev/thrift_erlang.git", {ref,"c280ff266ae1c1906fb0dcee8320bb8d8a4a3c75"}}, 1}, + {<<"tls_certificate_check">>, + {pkg,<<"tls_certificate_check">>,<<"1.19.0">>}, + 1}, {<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},2}, {<<"woody">>, {git,"https://github.com/valitydev/woody_erlang.git", - {ref,"3ddacb9296691aa8ddad05498d1fd34b078eda75"}}, + {ref,"5d46291a6bfcee0bae2a9346a7d927603a909249"}}, 0}, {<<"xrates_proto">>, {git,"https://github.com/valitydev/xrates-proto.git", @@ -71,33 +87,53 @@ 0}]}. [ {pkg_hash,[ + {<<"acceptor_pool">>, <<"43C20D2ACAE35F0C2BCD64F9D2BDE267E459F0F3FD23DAB26485BF518C281B21">>}, {<<"cache">>, <<"B23A5FE7095445A88412A6E614C933377E0137B44FFED77C9B3FEF1A731A20B2">>}, {<<"certifi">>, <<"D4FB0A6BB20B7C9C3643E22507E42F356AC090A1DCEA9AB99E27E0376D695EBA">>}, + {<<"chatterbox">>, <<"6F059D97BCAA758B8EA6FFFE2B3B81362BD06B639D3EA2BB088335511D691EBF">>}, {<<"cowboy">>, <<"865DD8B6607E14CF03282E10E934023A1BD8BE6F6BACF921A7E2A96D800CD452">>}, {<<"cowlib">>, <<"0B9FF9C346629256C42EBE1EEB769A83C6CB771A6EE5960BD110AB0B9B872063">>}, + {<<"ctx">>, <<"8FF88B70E6400C4DF90142E7F130625B82086077A45364A78D208ED3ED53C7FE">>}, {<<"gproc">>, <<"853CCB7805E9ADA25D227A157BA966F7B34508F386A3E7E21992B1B484230699">>}, + {<<"grpcbox">>, <<"B83F37C62D6EECA347B77F9B1EC7E9F62231690CDFEB3A31BE07CD4002BA9C82">>}, {<<"hackney">>, <<"C4443D960BB9FBA6D01161D01CD81173089686717D9490E5D3606644C48D121F">>}, + {<<"hpack">>, <<"17670F83FF984AE6CD74B1C456EDDE906D27FF013740EE4D9EFAA4F1BF999633">>}, {<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>}, {<<"jsx">>, <<"D12516BAA0BB23A59BB35DCCAF02A1BD08243FCBB9EFE24F2D9D056CCFF71268">>}, {<<"metrics">>, <<"25F094DEA2CDA98213CECC3AEFF09E940299D950904393B2A29D191C346A8486">>}, {<<"mimerl">>, <<"67E2D3F571088D5CFD3E550C383094B47159F3EEE8FFA08E64106CDF5E981BE3">>}, + {<<"opentelemetry">>, <<"988AC3C26ACAC9720A1D4FB8D9DC52E95B45ECFEC2D5B5583276A09E8936BC5E">>}, + {<<"opentelemetry_api">>, <<"7B69ED4F40025C005DE0B74FCE8C0549625D59CB4DF12D15C32FE6DC5076FF42">>}, + {<<"opentelemetry_exporter">>, <<"1D8809C0D4F4ACF986405F7700ED11992BCBDB6A4915DD11921E80777FFA7167">>}, + {<<"opentelemetry_semantic_conventions">>, <<"B67FE459C2938FCAB341CB0951C44860C62347C005ACE1B50F8402576F241435">>}, {<<"parse_trans">>, <<"16328AB840CC09919BD10DAB29E431DA3AF9E9E7E7E6F0089DD5A2D2820011D8">>}, {<<"ranch">>, <<"8C7A100A139FD57F17327B6413E4167AC559FBC04CA7448E9BE9057311597A1D">>}, - {<<"ssl_verify_fun">>, <<"CF344F5692C82D2CD7554F5EC8FD961548D4FD09E7D22F5B62482E5AEAEBD4B0">>}, + {<<"ssl_verify_fun">>, <<"354C321CF377240C7B8716899E182CE4890C5938111A1296ADD3EC74CF1715DF">>}, + {<<"tls_certificate_check">>, <<"C76C4C5D79EE79A2B11C84F910C825D6F024A78427C854F515748E9BD025E987">>}, {<<"unicode_util_compat">>, <<"BC84380C9AB48177092F43AC89E4DFA2C6D62B40B8BD132B1059ECC7232F9A78">>}]}, {pkg_hash_ext,[ + {<<"acceptor_pool">>, <<"0CBCD83FDC8B9AD2EEE2067EF8B91A14858A5883CB7CD800E6FCD5803E158788">>}, {<<"cache">>, <<"44516CE6FA03594D3A2AF025DD3A87BFE711000EB730219E1DDEFC816E0AA2F4">>}, {<<"certifi">>, <<"6AC7EFC1C6F8600B08D625292D4BBF584E14847CE1B6B5C44D983D273E1097EA">>}, + {<<"chatterbox">>, <<"B93D19104D86AF0B3F2566C4CBA2A57D2E06D103728246BA1AC6C3C0FF010AA7">>}, {<<"cowboy">>, <<"2C729F934B4E1AA149AFF882F57C6372C15399A20D54F65C8D67BEF583021BDE">>}, {<<"cowlib">>, <<"2B3E9DA0B21C4565751A6D4901C20D1B4CC25CBB7FD50D91D2AB6DD287BC86A9">>}, + {<<"ctx">>, <<"A14ED2D1B67723DBEBBE423B28D7615EB0BDCBA6FF28F2D1F1B0A7E1D4AA5FC2">>}, {<<"gproc">>, <<"587E8AF698CCD3504CF4BA8D90F893EDE2B0F58CABB8A916E2BF9321DE3CF10B">>}, + {<<"grpcbox">>, <<"294DF743AE20A7E030889F00644001370A4F7CE0121F3BBDAF13CF3169C62913">>}, {<<"hackney">>, <<"9AFCDA620704D720DB8C6A3123E9848D09C87586DC1C10479C42627B905B5C5E">>}, + {<<"hpack">>, <<"06F580167C4B8B8A6429040DF36CC93BBA6D571FAEAEC1B28816523379CBB23A">>}, {<<"idna">>, <<"92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA">>}, {<<"jsx">>, <<"0C5CC8FDC11B53CC25CF65AC6705AD39E54ECC56D1C22E4ADB8F5A53FB9427F3">>}, {<<"metrics">>, <<"69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16">>}, {<<"mimerl">>, <<"F278585650AA581986264638EBF698F8BB19DF297F66AD91B18910DFC6E19323">>}, + {<<"opentelemetry">>, <<"8E09EDC26AAD11161509D7ECAD854A3285D88580F93B63B0B1CF0BAC332BFCC0">>}, + {<<"opentelemetry_api">>, <<"6D7A27B7CAD2AD69A09CABF6670514CAFCEC717C8441BEB5C96322BAC3D05350">>}, + {<<"opentelemetry_exporter">>, <<"2B40007F509D38361744882FD060A8841AF772AB83BB542AA5350908B303AD65">>}, + {<<"opentelemetry_semantic_conventions">>, <<"D61FA1F5639EE8668D74B527E6806E0503EFC55A42DB7B5F39939D84C07D6895">>}, {<<"parse_trans">>, <<"07CD9577885F56362D414E8C4C4E6BDF10D43A8767ABB92D24CBE8B24C54888B">>}, {<<"ranch">>, <<"49FBCFD3682FAB1F5D109351B61257676DA1A2FDBE295904176D5E521A2DDFE5">>}, - {<<"ssl_verify_fun">>, <<"BDB0D2471F453C88FF3908E7686F86F9BE327D065CC1EC16FA4540197EA04680">>}, + {<<"ssl_verify_fun">>, <<"FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8">>}, + {<<"tls_certificate_check">>, <<"4083B4A298ADD534C96125337CB01161C358BB32DD870D5A893AAE685FD91D70">>}, {<<"unicode_util_compat">>, <<"25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521">>}]} ]. diff --git a/test/machinegun/config.yaml b/test/machinegun/config.yaml index 8f5f3975..3b93a483 100644 --- a/test/machinegun/config.yaml +++ b/test/machinegun/config.yaml @@ -3,9 +3,6 @@ service_name: machinegun erlang: secret_cookie_file: "/opt/machinegun/etc/cookie" -logging: - out_type: stdout - namespaces: lim/config_v1: processor: @@ -22,3 +19,13 @@ namespaces: storage: type: memory + +logging: + out_type: stdout + level: info + +opentelemetry: + service_name: machinegun + exporter: + protocol: http/protobuf + endpoint: http://jaeger:4318 From bcff0b78f6a3ec3b445c34addbe5d15c3beab73d Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Wed, 29 May 2024 12:46:08 +0300 Subject: [PATCH 38/54] CP-12: Adds support for 'currency_conversion' config option (#28) * CP-12: Adds support for 'currency_conversin' config option * Fixes eunit tests * Updates testcases in suites * Fixes struct type in marshaling and temporarily disables codecov report upload * Bumps damsel and limiter-proto * Removes global 'currency_conversion' flag --- .github/workflows/erlang-checks.yml | 1 + apps/limiter/src/lim_client_woody.erl | 2 +- apps/limiter/src/lim_config_codec.erl | 33 ++++++++--- apps/limiter/src/lim_config_machine.erl | 19 +++++- .../src/lim_config_machinery_schema.erl | 3 +- apps/limiter/src/lim_range_machine.erl | 5 ++ apps/limiter/src/lim_turnover_metric.erl | 6 +- apps/limiter/src/lim_utils.erl | 2 +- apps/limiter/src/limiter.erl | 2 +- apps/limiter/test/lim_client.erl | 4 +- apps/limiter/test/lim_configurator_SUITE.erl | 6 +- apps/limiter/test/lim_ct_helper.hrl | 2 + apps/limiter/test/lim_mock.erl | 2 +- apps/limiter/test/lim_turnover_SUITE.erl | 59 +++++++------------ config/sys.config | 3 - rebar.lock | 25 ++++---- 16 files changed, 99 insertions(+), 75 deletions(-) diff --git a/.github/workflows/erlang-checks.yml b/.github/workflows/erlang-checks.yml index 5d855baf..dc53054d 100644 --- a/.github/workflows/erlang-checks.yml +++ b/.github/workflows/erlang-checks.yml @@ -37,3 +37,4 @@ jobs: use-thrift: true thrift-version: ${{ needs.setup.outputs.thrift-version }} run-ct-with-compose: true + upload-coverage: false diff --git a/apps/limiter/src/lim_client_woody.erl b/apps/limiter/src/lim_client_woody.erl index c877427a..9838c623 100644 --- a/apps/limiter/src/lim_client_woody.erl +++ b/apps/limiter/src/lim_client_woody.erl @@ -12,7 +12,7 @@ -spec call(service_name(), woody:func(), woody:args(), woody_context:ctx()) -> woody:result(). call(ServiceName, Function, Args, Context) -> - EventHandler = scoper_woody_event_handler, + EventHandler = {scoper_woody_event_handler, #{}}, call(ServiceName, Function, Args, Context, EventHandler). -spec call(service_name(), woody:func(), woody:args(), woody_context:ctx(), woody:ev_handler()) -> woody:result(). diff --git a/apps/limiter/src/lim_config_codec.erl b/apps/limiter/src/lim_config_codec.erl index 2c60b6f7..9440e1b5 100644 --- a/apps/limiter/src/lim_config_codec.erl +++ b/apps/limiter/src/lim_config_codec.erl @@ -71,9 +71,15 @@ marshal_config(Config) -> context_type = marshal_context_type(lim_config_machine:context_type(Config)), type = maybe_apply(lim_config_machine:type(Config), fun marshal_type/1), scope = maybe_apply(lim_config_machine:scope(Config), fun marshal_scope/1), - op_behaviour = maybe_apply(lim_config_machine:op_behaviour(Config), fun marshal_op_behaviour/1) + op_behaviour = maybe_apply(lim_config_machine:op_behaviour(Config), fun marshal_op_behaviour/1), + currency_conversion = marshal_currency_conversion(lim_config_machine:currency_conversion(Config)) }. +marshal_currency_conversion(true) -> + #config_CurrencyConversion{}; +marshal_currency_conversion(_) -> + undefined. + marshal_op_behaviour(OpBehaviour) -> PaymentRefund = maps:get(invoice_payment_refund, OpBehaviour, undefined), #config_OperationLimitBehaviour{ @@ -153,7 +159,8 @@ unmarshal('LimitConfigObject', #domain_LimitConfigObject{ type = Type, scopes = Scopes, description = Description, - op_behaviour = OpBehaviour + op_behaviour = OpBehaviour, + currency_conversion = CurrencyConversion } }) -> genlib_map:compact(#{ @@ -167,7 +174,8 @@ unmarshal('LimitConfigObject', #domain_LimitConfigObject{ type => maybe_apply(Type, fun unmarshal_type/1), scope => maybe_apply(Scopes, fun unmarshal_scope/1), description => Description, - op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1) + op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1), + currency_conversion => CurrencyConversion =/= undefined }). unmarshal_timestamp(Timestamp) when is_binary(Timestamp) -> @@ -196,7 +204,8 @@ unmarshal_params(#config_LimitConfigParams{ context_type = ContextType, type = Type, scope = Scope, - op_behaviour = OpBehaviour + op_behaviour = OpBehaviour, + currency_conversion = CurrencyConversion }) -> genlib_map:compact(#{ id => ID, @@ -207,7 +216,8 @@ unmarshal_params(#config_LimitConfigParams{ type => maybe_apply(Type, fun unmarshal_type/1), scope => maybe_apply(Scope, fun unmarshal_scope/1), description => Description, - op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1) + op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1), + currency_conversion => CurrencyConversion =/= undefined }). unmarshal_change({created, #config_CreatedChange{limit_config = Config}}) -> @@ -225,6 +235,7 @@ unmarshal_config(#config_LimitConfig{ type = TypeIn, scope = Scope, op_behaviour = OpBehaviour, + currency_conversion = CurrencyConversion, body_type_deprecated = BodyTypeIn }) -> Type = maybe_apply(TypeIn, fun unmarshal_type/1), @@ -240,7 +251,8 @@ unmarshal_config(#config_LimitConfig{ type => derive_type(Type, BodyType), scope => maybe_apply(Scope, fun unmarshal_scope/1), description => Description, - op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1) + op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1), + currency_conversion => CurrencyConversion =/= undefined }). derive_type(Type, undefined) -> @@ -359,7 +371,8 @@ marshal_unmarshal_created_test() -> context_type => payment_processing, type => {turnover, number}, scope => ordsets:from_list([party, shop]), - description => <<"description">> + description => <<"description">>, + currency_conversion => false }}, Event = {ev, lim_time:machinery_now(), Created}, ?assertEqual(Event, unmarshal(timestamped_change, marshal(timestamped_change, Event))). @@ -421,7 +434,8 @@ unmarshal_config_object_test() -> context_type => payment_processing, type => {turnover, number}, scope => ordsets:from_list([party, shop]), - description => <<"description">> + description => <<"description">>, + currency_conversion => true }, Object = #domain_LimitConfigObject{ ref = #domain_LimitConfigRef{id = <<"id">>}, @@ -437,7 +451,8 @@ unmarshal_config_object_test() -> scopes = ordsets:from_list([ {'party', #limiter_config_LimitScopeEmptyDetails{}}, {'shop', #limiter_config_LimitScopeEmptyDetails{}} ]), - description = <<"description">> + description = <<"description">>, + currency_conversion = #limiter_config_CurrencyConversion{} } }, ?assertEqual(Config, unmarshal('LimitConfigObject', Object)). diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index 0b4a6685..bd425440 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -17,6 +17,7 @@ -export([scope/1]). -export([context_type/1]). -export([op_behaviour/1]). +-export([currency_conversion/1]). %% API @@ -63,7 +64,8 @@ type := limit_type(), scope => limit_scope(), description => description(), - op_behaviour => op_behaviour() + op_behaviour => op_behaviour(), + currency_conversion => currency_conversion() }. -type create_params() :: #{ @@ -75,11 +77,13 @@ type => limit_type(), scope => limit_scope(), description => description(), - op_behaviour => op_behaviour() + op_behaviour => op_behaviour(), + current_function => currency_conversion() }. -type op_behaviour() :: #{operation_type() := addition | subtraction}. -type operation_type() :: invoice_payment_refund. +-type currency_conversion() :: boolean(). -type lim_id() :: limproto_limiter_thrift:'LimitID'(). -type lim_version() :: dmsl_domain_thrift:'DataRevision'() | undefined. @@ -106,6 +110,7 @@ -export([process_call/4]). -export([process_timeout/3]). -export([process_repair/4]). +-export([process_notification/4]). -type timestamped_event(T) :: {ev, machinery:timestamp(), T}. @@ -213,6 +218,12 @@ op_behaviour(#{op_behaviour := Value}) -> op_behaviour(_) -> undefined. +-spec currency_conversion(config()) -> currency_conversion(). +currency_conversion(#{currency_conversion := Value}) -> + Value; +currency_conversion(_) -> + false. + %% -spec start(lim_id(), create_params(), lim_context()) -> {ok, config()}. @@ -650,6 +661,10 @@ process_timeout(_Machine, _HandlerArgs, _HandlerOpts) -> process_repair(_Args, _Machine, _HandlerArgs, _HandlerOpts) -> not_implemented(repair). +-spec process_notification(args(_), machine(), handler_args(), handler_opts()) -> no_return(). +process_notification(_Args, _Machine, _HandlerArgs, _HandlerOpts) -> + not_implemented(notification). + %%% Internal functions emit_events(Events) -> diff --git a/apps/limiter/src/lim_config_machinery_schema.erl b/apps/limiter/src/lim_config_machinery_schema.erl index 0ef3c9fb..9be2d1f6 100644 --- a/apps/limiter/src/lim_config_machinery_schema.erl +++ b/apps/limiter/src/lim_config_machinery_schema.erl @@ -102,7 +102,8 @@ marshal_unmarshal_created_test() -> context_type => payment_processing, type => {turnover, {amount, <<"RUB">>}}, scope => ordsets:from_list([party]), - description => <<"description">> + description => <<"description">>, + currency_conversion => false }}, Context = #{machine_id => ID, machine_ns => config}, Event = {ev, lim_time:machinery_now(), Created}, diff --git a/apps/limiter/src/lim_range_machine.erl b/apps/limiter/src/lim_range_machine.erl index a37a1ca2..332518e4 100644 --- a/apps/limiter/src/lim_range_machine.erl +++ b/apps/limiter/src/lim_range_machine.erl @@ -23,6 +23,7 @@ -export([process_call/4]). -export([process_timeout/3]). -export([process_repair/4]). +-export([process_notification/4]). -type args(T) :: machinery:args(T). -type machine() :: machinery:machine(event(), _). @@ -184,6 +185,10 @@ process_timeout(_Machine, _HandlerArgs, _HandlerOpts) -> process_repair(_Args, _Machine, _HandlerArgs, _HandlerOpts) -> not_implemented(repair). +-spec process_notification(args(_), machine(), handler_args(), handler_opts()) -> no_return(). +process_notification(_Args, _Machine, _HandlerArgs, _HandlerOpts) -> + not_implemented(notification). + %%% Internal functions find_time_range(_TimeRange, []) -> diff --git a/apps/limiter/src/lim_turnover_metric.erl b/apps/limiter/src/lim_turnover_metric.erl index f27a3093..fbb94c70 100644 --- a/apps/limiter/src/lim_turnover_metric.erl +++ b/apps/limiter/src/lim_turnover_metric.erl @@ -51,9 +51,9 @@ get_commit_body(Config, LimitContext) -> denominate(#{amount := Amount, currency := Currency}, Currency, _Config, _LimitContext) -> {ok, Amount}; denominate(Body = #{currency := Currency}, DestinationCurrency, Config, LimitContext) -> - case genlib_app:env(limiter, currency_conversion, disabled) of - disabled -> currencies_mismatch_error(Currency, DestinationCurrency); - enabled -> convert_currency(Body, DestinationCurrency, Config, LimitContext) + case lim_config_machine:currency_conversion(Config) of + false -> currencies_mismatch_error(Currency, DestinationCurrency); + true -> convert_currency(Body, DestinationCurrency, Config, LimitContext) end. currencies_mismatch_error(Currency, ExpectedCurrency) -> diff --git a/apps/limiter/src/lim_utils.erl b/apps/limiter/src/lim_utils.erl index e3da1324..57decb23 100644 --- a/apps/limiter/src/lim_utils.erl +++ b/apps/limiter/src/lim_utils.erl @@ -24,4 +24,4 @@ get_woody_client(Url) -> -spec get_woody_event_handlers() -> woody:ev_handlers(). get_woody_event_handlers() -> - genlib_app:env(limiter, woody_event_handlers, [scoper_woody_event_handler]). + genlib_app:env(limiter, woody_event_handlers, [{scoper_woody_event_handler, #{}}]). diff --git a/apps/limiter/src/limiter.erl b/apps/limiter/src/limiter.erl index 6bf855b7..af207969 100644 --- a/apps/limiter/src/limiter.erl +++ b/apps/limiter/src/limiter.erl @@ -37,7 +37,7 @@ init([]) -> ok = application:set_env(limiter, backends, maps:from_list(Backends)), RouteOptsEnv = genlib_app:env(?MODULE, route_opts, #{}), - EventHandlers = genlib_app:env(?MODULE, woody_event_handlers, [woody_event_handler_default]), + EventHandlers = genlib_app:env(?MODULE, woody_event_handlers, [{woody_event_handler_default, #{}}]), EventHandlerOpts = genlib_app:env(?MODULE, scoper_event_handler_options, #{}), RouteOpts = RouteOptsEnv#{event_handler => {scoper_woody_event_handler, EventHandlerOpts}}, diff --git a/apps/limiter/test/lim_client.erl b/apps/limiter/test/lim_client.erl index 82c323f0..8159ad89 100644 --- a/apps/limiter/test/lim_client.erl +++ b/apps/limiter/test/lim_client.erl @@ -67,7 +67,7 @@ call(Function, Args, Client) -> Call = {{limproto_limiter_thrift, 'Limiter'}, Function, Args}, Opts = #{ url => <<"http://limiter:8022/v1/limiter">>, - event_handler => scoper_woody_event_handler, + event_handler => {scoper_woody_event_handler, #{}}, transport_opts => #{ max_connections => 10000 } @@ -79,7 +79,7 @@ call_configurator(Function, Args, Client) -> Call = {{limproto_configurator_thrift, 'Configurator'}, Function, Args}, Opts = #{ url => <<"http://limiter:8022/v1/configurator">>, - event_handler => scoper_woody_event_handler, + event_handler => {scoper_woody_event_handler, #{}}, transport_opts => #{ max_connections => 10000 } diff --git a/apps/limiter/test/lim_configurator_SUITE.erl b/apps/limiter/test/lim_configurator_SUITE.erl index f385eff2..3f3a0bea 100644 --- a/apps/limiter/test/lim_configurator_SUITE.erl +++ b/apps/limiter/test/lim_configurator_SUITE.erl @@ -138,7 +138,8 @@ create_config(C) -> ?scope_party() ]), op_behaviour = ?op_behaviour(), - context_type = ?ctx_type_payproc() + context_type = ?ctx_type_payproc(), + currency_conversion = ?currency_conversion() }, ?assertMatch( {ok, #config_LimitConfig{ @@ -159,7 +160,8 @@ create_config_single_scope(C) -> type = ?lim_type_turnover(), scope = {single, ?scope_party()}, context_type = ?ctx_type_payproc(), - op_behaviour = ?op_behaviour() + op_behaviour = ?op_behaviour(), + currency_conversion = undefined }, {ok, #config_LimitConfig{ scope = Scope diff --git a/apps/limiter/test/lim_ct_helper.hrl b/apps/limiter/test/lim_ct_helper.hrl index 47eed89f..407a3e85 100644 --- a/apps/limiter/test/lim_ct_helper.hrl +++ b/apps/limiter/test/lim_ct_helper.hrl @@ -68,6 +68,8 @@ invoice_payment_refund = Refund }). +-define(currency_conversion(), #config_CurrencyConversion{}). + -define(op_addition(), {addition, #config_Addition{}}). -define(op_subtraction(), {subtraction, #config_Subtraction{}}). diff --git a/apps/limiter/test/lim_mock.erl b/apps/limiter/test/lim_mock.erl index 6d84817d..f93092f2 100644 --- a/apps/limiter/test/lim_mock.erl +++ b/apps/limiter/test/lim_mock.erl @@ -44,7 +44,7 @@ mock_services_(Services, SupPid) when is_pid(SupPid) -> Options = #{ ip => IP, port => 0, - event_handler => scoper_woody_event_handler, + event_handler => {scoper_woody_event_handler, #{}}, handlers => lists:map(fun mock_service_handler/1, Services), transport_opts => #{num_acceptors => 1} }, diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index 1d19cd2c..459fa273 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -76,7 +76,7 @@ all() -> -spec groups() -> [{atom(), list(), [test_case_name()]}]. groups() -> [ - {default, [], [ + {base, [], [ commit_with_long_change_id, commit_with_default_exchange, partial_commit_with_exchange, @@ -93,11 +93,9 @@ groups() -> commit_ok, rollback_ok, partial_zero_commit_rollbacks, - get_config_ok, refund_ok, commit_inexistent_hold_fails, partial_commit_inexistent_hold_fails, - commit_multirange_limit_ok, commit_with_payment_tool_scope_ok, commit_with_party_scope_ok, commit_with_provider_scope_ok, @@ -105,33 +103,13 @@ groups() -> commit_with_email_scope_ok, commit_with_multi_scope_ok ]}, - %% Repeats `default` group exept for `get_config_ok` and `commit_multirange_limit_ok` + {default, [], [ + {group, base}, + get_config_ok, + commit_multirange_limit_ok + ]}, {default_with_dominant, [], [ - commit_with_long_change_id, - commit_with_default_exchange, - partial_commit_with_exchange, - commit_with_exchange, - hold_with_disabled_exchange, - rollback_with_wrong_currency, - hold_with_wrong_operation_context, - rollback_with_wrong_operation_context, - hold_with_wrong_payment_tool, - rollback_with_wrong_payment_tool, - get_limit_ok, - get_limit_notfound, - hold_ok, - commit_ok, - rollback_ok, - partial_zero_commit_rollbacks, - refund_ok, - commit_inexistent_hold_fails, - partial_commit_inexistent_hold_fails, - commit_with_payment_tool_scope_ok, - commit_with_party_scope_ok, - commit_with_provider_scope_ok, - commit_with_terminal_scope_ok, - commit_with_email_scope_ok, - commit_with_multi_scope_ok + {group, base} ]}, {withdrawals, [parallel], [ get_limit_ok, @@ -263,10 +241,11 @@ commit_with_long_change_id(C) -> -spec commit_with_default_exchange(config()) -> _. commit_with_default_exchange(C) -> - ok = application:set_env(limiter, currency_conversion, enabled), Rational = #base_Rational{p = 1000000, q = 100}, _ = mock_exchange(Rational, C), - {ID, Version} = configure_limit(?time_range_month(), ?global(), C), + {ID, Version} = configure_limit( + ?time_range_month(), ?global(), ?turnover_metric_amount(<<"RUB">>), ?currency_conversion(), C + ), Cost = ?cash(10000, <<"SOME_CURRENCY">>), Context = ?payproc_ctx_invoice(Cost), {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), @@ -274,10 +253,11 @@ commit_with_default_exchange(C) -> -spec partial_commit_with_exchange(config()) -> _. partial_commit_with_exchange(C) -> - ok = application:set_env(limiter, currency_conversion, enabled), Rational = #base_Rational{p = 800000, q = 100}, _ = mock_exchange(Rational, C), - {ID, Version} = configure_limit(?time_range_month(), ?global(), C), + {ID, Version} = configure_limit( + ?time_range_month(), ?global(), ?turnover_metric_amount(<<"RUB">>), ?currency_conversion(), C + ), Cost = ?cash(1000, <<"USD">>), CaptureCost = ?cash(800, <<"USD">>), Context = ?payproc_ctx_payment(Cost, CaptureCost), @@ -286,10 +266,11 @@ partial_commit_with_exchange(C) -> -spec commit_with_exchange(config()) -> _. commit_with_exchange(C) -> - ok = application:set_env(limiter, currency_conversion, enabled), Rational = #base_Rational{p = 1000000, q = 100}, _ = mock_exchange(Rational, C), - {ID, Version} = configure_limit(?time_range_month(), ?global(), C), + {ID, Version} = configure_limit( + ?time_range_month(), ?global(), ?turnover_metric_amount(<<"RUB">>), ?currency_conversion(), C + ), Cost = ?cash(10000, <<"USD">>), Context = ?payproc_ctx_invoice(Cost), {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), @@ -297,7 +278,6 @@ commit_with_exchange(C) -> -spec hold_with_disabled_exchange(config()) -> _. hold_with_disabled_exchange(C) -> - ok = application:set_env(limiter, currency_conversion, disabled), Rational = #base_Rational{p = 1000000, q = 100}, _ = mock_exchange(Rational, C), ConfiguredCurrency = <<"RUB">>, @@ -310,7 +290,6 @@ hold_with_disabled_exchange(C) -> -spec rollback_with_wrong_currency(config()) -> _. rollback_with_wrong_currency(C) -> - ok = application:set_env(limiter, currency_conversion, disabled), Rational = #base_Rational{p = 1000000, q = 100}, _ = mock_exchange(Rational, C), ConfiguredCurrency = <<"RUB">>, @@ -750,6 +729,9 @@ configure_limit(TimeRange, Scope, C) -> configure_limit(TimeRange, Scope, ?turnover_metric_amount(<<"RUB">>), C). configure_limit(TimeRange, Scope, Metric, C) -> + configure_limit(TimeRange, Scope, Metric, undefined, C). + +configure_limit(TimeRange, Scope, Metric, CurrencyConversion, C) -> ID = ?config(id, C), ContextType = case get_group_name(C) of @@ -764,7 +746,8 @@ configure_limit(TimeRange, Scope, Metric, C) -> type = ?lim_type_turnover(Metric), scope = Scope, context_type = ContextType, - op_behaviour = ?op_behaviour(?op_subtraction()) + op_behaviour = ?op_behaviour(?op_subtraction()), + currency_conversion = CurrencyConversion }, ConfigSource = proplists:get_value(limit_config_source, C, legacy), put_config_into_repository(ConfigSource, CreateParams, ?config(client, C)). diff --git a/config/sys.config b/config/sys.config index 4829140d..95d8179e 100644 --- a/config/sys.config +++ b/config/sys.config @@ -22,9 +22,6 @@ } }}, - % Enables currency conversion for turnover metric (default = disabled) - {currency_conversion, disabled}, - {exchange_factors, #{ <<"DEFAULT">> => {1, 1}, <<"USD">> => {105, 100}, diff --git a/rebar.lock b/rebar.lock index e0db19f7..dcf2a4ea 100644 --- a/rebar.lock +++ b/rebar.lock @@ -12,7 +12,7 @@ {<<"ctx">>,{pkg,<<"ctx">>,<<"0.6.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"f718741970470474efcd32800daf885cb8d75584"}}, + {ref,"02005deb9deb6470d2a17bb61e6b51098512f066"}}, 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git", @@ -24,7 +24,7 @@ 1}, {<<"erl_health">>, {git,"https://github.com/valitydev/erlang-health.git", - {ref,"7ffbc855bdbe79e23efad1803b0b185c9ea8d2f1"}}, + {ref,"49716470d0e8dab5e37db55d52dea78001735a3d"}}, 0}, {<<"genlib">>, {git,"https://github.com/valitydev/genlib.git", @@ -38,18 +38,18 @@ {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},1}, {<<"limiter_proto">>, {git,"https://github.com/valitydev/limiter-proto.git", - {ref,"e045813d32e67432e5592d582e59e45df05da647"}}, + {ref,"1432a9871fc31b74c2af28b9367cb3ed6e08d498"}}, 0}, {<<"machinery">>, {git,"https://github.com/valitydev/machinery-erlang.git", - {ref,"62c32434c80a462956ad9d50f9bce47836580d77"}}, + {ref,"d62ceffbdb266bb4748bed34198e3575bba2dc73"}}, 0}, {<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},2}, {<<"mg_proto">>, - {git,"https://github.com/valitydev/machinegun-proto.git", - {ref,"347c5c44c8dcca24a50e0509c0df5401f863e790"}}, + {git,"https://github.com/valitydev/machinegun-proto", + {ref,"3decc8f8b13c9cd1701deab47781aacddd7dbc92"}}, 1}, - {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.2.0">>},2}, + {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.3.0">>},2}, {<<"opentelemetry">>,{pkg,<<"opentelemetry">>,<<"1.3.0">>},0}, {<<"opentelemetry_api">>,{pkg,<<"opentelemetry_api">>,<<"1.2.1">>},0}, {<<"opentelemetry_exporter">>, @@ -59,10 +59,11 @@ {pkg,<<"opentelemetry_semantic_conventions">>,<<"0.2.0">>}, 1}, {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.1">>},2}, + {<<"prometheus">>,{pkg,<<"prometheus">>,<<"4.6.0">>},1}, {<<"ranch">>,{pkg,<<"ranch">>,<<"1.8.0">>},2}, {<<"scoper">>, {git,"https://github.com/valitydev/scoper.git", - {ref,"41a14a558667316998af9f49149ee087ffa8bef2"}}, + {ref,"55a2a32ee25e22fa35f583a18eaf38b2b743429b"}}, 0}, {<<"snowflake">>, {git,"https://github.com/valitydev/snowflake.git", @@ -79,7 +80,7 @@ {<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},2}, {<<"woody">>, {git,"https://github.com/valitydev/woody_erlang.git", - {ref,"5d46291a6bfcee0bae2a9346a7d927603a909249"}}, + {ref,"81219ba5408e1c67f5eaed3c7e566ede42da88d4"}}, 0}, {<<"xrates_proto">>, {git,"https://github.com/valitydev/xrates-proto.git", @@ -101,12 +102,13 @@ {<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>}, {<<"jsx">>, <<"D12516BAA0BB23A59BB35DCCAF02A1BD08243FCBB9EFE24F2D9D056CCFF71268">>}, {<<"metrics">>, <<"25F094DEA2CDA98213CECC3AEFF09E940299D950904393B2A29D191C346A8486">>}, - {<<"mimerl">>, <<"67E2D3F571088D5CFD3E550C383094B47159F3EEE8FFA08E64106CDF5E981BE3">>}, + {<<"mimerl">>, <<"D0CD9FC04B9061F82490F6581E0128379830E78535E017F7780F37FEA7545726">>}, {<<"opentelemetry">>, <<"988AC3C26ACAC9720A1D4FB8D9DC52E95B45ECFEC2D5B5583276A09E8936BC5E">>}, {<<"opentelemetry_api">>, <<"7B69ED4F40025C005DE0B74FCE8C0549625D59CB4DF12D15C32FE6DC5076FF42">>}, {<<"opentelemetry_exporter">>, <<"1D8809C0D4F4ACF986405F7700ED11992BCBDB6A4915DD11921E80777FFA7167">>}, {<<"opentelemetry_semantic_conventions">>, <<"B67FE459C2938FCAB341CB0951C44860C62347C005ACE1B50F8402576F241435">>}, {<<"parse_trans">>, <<"16328AB840CC09919BD10DAB29E431DA3AF9E9E7E7E6F0089DD5A2D2820011D8">>}, + {<<"prometheus">>, <<"20510F381DB1CCAB818B4CF2FAC5FA6AB5CC91BC364A154399901C001465F46F">>}, {<<"ranch">>, <<"8C7A100A139FD57F17327B6413E4167AC559FBC04CA7448E9BE9057311597A1D">>}, {<<"ssl_verify_fun">>, <<"354C321CF377240C7B8716899E182CE4890C5938111A1296ADD3EC74CF1715DF">>}, {<<"tls_certificate_check">>, <<"C76C4C5D79EE79A2B11C84F910C825D6F024A78427C854F515748E9BD025E987">>}, @@ -126,12 +128,13 @@ {<<"idna">>, <<"92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA">>}, {<<"jsx">>, <<"0C5CC8FDC11B53CC25CF65AC6705AD39E54ECC56D1C22E4ADB8F5A53FB9427F3">>}, {<<"metrics">>, <<"69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16">>}, - {<<"mimerl">>, <<"F278585650AA581986264638EBF698F8BB19DF297F66AD91B18910DFC6E19323">>}, + {<<"mimerl">>, <<"A1E15A50D1887217DE95F0B9B0793E32853F7C258A5CD227650889B38839FE9D">>}, {<<"opentelemetry">>, <<"8E09EDC26AAD11161509D7ECAD854A3285D88580F93B63B0B1CF0BAC332BFCC0">>}, {<<"opentelemetry_api">>, <<"6D7A27B7CAD2AD69A09CABF6670514CAFCEC717C8441BEB5C96322BAC3D05350">>}, {<<"opentelemetry_exporter">>, <<"2B40007F509D38361744882FD060A8841AF772AB83BB542AA5350908B303AD65">>}, {<<"opentelemetry_semantic_conventions">>, <<"D61FA1F5639EE8668D74B527E6806E0503EFC55A42DB7B5F39939D84C07D6895">>}, {<<"parse_trans">>, <<"07CD9577885F56362D414E8C4C4E6BDF10D43A8767ABB92D24CBE8B24C54888B">>}, + {<<"prometheus">>, <<"4905FD2992F8038ECCD7AA0CD22F40637ED618C0BED1F75C05AACEC15B7545DE">>}, {<<"ranch">>, <<"49FBCFD3682FAB1F5D109351B61257676DA1A2FDBE295904176D5E521A2DDFE5">>}, {<<"ssl_verify_fun">>, <<"FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8">>}, {<<"tls_certificate_check">>, <<"4083B4A298ADD534C96125337CB01161C358BB32DD870D5A893AAE685FD91D70">>}, From 9c1ee9fb00b351103210aed17a81a44568764550 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Tue, 4 Jun 2024 12:27:55 +0300 Subject: [PATCH 39/54] CP-4: Adds support for 'sender' and 'receiver' scopes for withdrawal context (#29) * CP-4: Adds support for 'sender' and 'receiver' scopes for withdrawal context * Adds tests for sender and receiver scopes * Bumps damsel and limiter_proto --- apps/limiter/src/lim_config_codec.erl | 12 +++++- apps/limiter/src/lim_config_machine.erl | 9 +++- apps/limiter/src/lim_payproc_context.erl | 10 +++++ apps/limiter/src/lim_wthdproc_context.erl | 26 ++++++++++++ apps/limiter/test/lim_ct_helper.hrl | 25 +++++++++++- apps/limiter/test/lim_turnover_SUITE.erl | 50 +++++++++++++++++++++-- rebar.lock | 4 +- 7 files changed, 125 insertions(+), 11 deletions(-) diff --git a/apps/limiter/src/lim_config_codec.erl b/apps/limiter/src/lim_config_codec.erl index 9440e1b5..77fe8556 100644 --- a/apps/limiter/src/lim_config_codec.erl +++ b/apps/limiter/src/lim_config_codec.erl @@ -138,7 +138,11 @@ marshal_scope_type(provider) -> marshal_scope_type(terminal) -> {terminal, #config_LimitScopeEmptyDetails{}}; marshal_scope_type(payer_contact_email) -> - {payer_contact_email, #config_LimitScopeEmptyDetails{}}. + {payer_contact_email, #config_LimitScopeEmptyDetails{}}; +marshal_scope_type(sender) -> + {sender, #config_LimitScopeEmptyDetails{}}; +marshal_scope_type(receiver) -> + {receiver, #config_LimitScopeEmptyDetails{}}. %% @@ -349,7 +353,11 @@ unmarshal_scope_type({provider, _}) -> unmarshal_scope_type({terminal, _}) -> terminal; unmarshal_scope_type({payer_contact_email, _}) -> - payer_contact_email. + payer_contact_email; +unmarshal_scope_type({sender, _}) -> + sender; +unmarshal_scope_type({receiver, _}) -> + receiver. %% diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index bd425440..ca9b562d 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -578,7 +578,8 @@ append_prefix(Fragment, Acc) -> -spec enumerate_context_bits(limit_scope()) -> [context_bit()]. enumerate_context_bits(Types) -> - TypesOrder = [party, shop, identity, wallet, payment_tool, provider, terminal, payer_contact_email], + TypesOrder = + [party, shop, identity, wallet, payment_tool, provider, terminal, payer_contact_email, sender, receiver], SortedTypes = lists:filter(fun(T) -> ordsets:is_element(T, Types) end, TypesOrder), SquashedTypes = squash_scope_types(SortedTypes), lists:flatmap(fun get_context_bits/1, SquashedTypes). @@ -618,7 +619,11 @@ get_context_bits(provider) -> get_context_bits(terminal) -> [{prefix, <<"terminal">>}, {from, provider_id}, {from, terminal_id}]; get_context_bits(payer_contact_email) -> - [{prefix, <<"payer_contact_email">>}, {from, payer_contact_email}]. + [{prefix, <<"payer_contact_email">>}, {from, payer_contact_email}]; +get_context_bits(sender) -> + [{prefix, <<"sender">>}, {from, sender}]; +get_context_bits(receiver) -> + [{prefix, <<"receiver">>}, {from, receiver}]. -spec extract_context_bit(context_bit(), context_type(), lim_context()) -> {ok, binary()} | {error, lim_context:context_error()}. diff --git a/apps/limiter/src/lim_payproc_context.erl b/apps/limiter/src/lim_payproc_context.erl index cf2e78df..90d68b5c 100644 --- a/apps/limiter/src/lim_payproc_context.erl +++ b/apps/limiter/src/lim_payproc_context.erl @@ -55,6 +55,10 @@ get_value(terminal_id, Operation, Context) -> get_terminal_id(Operation, Context); get_value(payer_contact_email, Operation, Context) -> get_payer_contact_email(Operation, Context); +get_value(sender, Operation, Context) -> + get_destination_sender(Operation, Context); +get_value(receiver, Operation, Context) -> + get_destination_receiver(Operation, Context); get_value(ValueName, _Operation, _Context) -> {error, {unsupported, ValueName}}. @@ -197,6 +201,12 @@ get_payer_contact_info(#domain_CustomerPayer{contact_info = CI}) -> get_payer_contact_info(#domain_RecurrentPayer{contact_info = CI}) -> CI. +get_destination_sender(_, _CtxWithdrawal) -> + {error, notfound}. + +get_destination_receiver(_, _CtxWithdrawal) -> + {error, notfound}. + -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). diff --git a/apps/limiter/src/lim_wthdproc_context.erl b/apps/limiter/src/lim_wthdproc_context.erl index c3a6b4ce..0a12e1d8 100644 --- a/apps/limiter/src/lim_wthdproc_context.erl +++ b/apps/limiter/src/lim_wthdproc_context.erl @@ -49,6 +49,10 @@ get_value(provider_id, Operation, Context) -> get_provider_id(Operation, Context); get_value(terminal_id, Operation, Context) -> get_terminal_id(Operation, Context); +get_value(sender, Operation, Context) -> + get_destination_sender(Operation, Context); +get_value(receiver, Operation, Context) -> + get_destination_receiver(Operation, Context); get_value(ValueName, _Operation, _Context) -> {error, {unsupported, ValueName}}. @@ -70,6 +74,14 @@ get_value(ValueName, _Operation, _Context) -> } }). +-define(AUTH_DATA(V), #context_withdrawal_Context{ + withdrawal = #context_withdrawal_Withdrawal{ + withdrawal = #wthd_domain_Withdrawal{auth_data = V} + } +}). + +-define(SENDER_RECEIVER(V), ?AUTH_DATA({sender_receiver, V})). + get_owner_id(?WITHDRAWAL(Wthd)) -> Identity = Wthd#wthd_domain_Withdrawal.sender, {ok, Identity#wthd_domain_Identity.owner_id}; @@ -113,3 +125,17 @@ get_terminal_id(withdrawal, ?ROUTE(Route)) -> lim_context_utils:route_terminal_id(Route); get_terminal_id(_, _CtxWithdrawal) -> {error, notfound}. + +get_destination_sender(withdrawal, ?SENDER_RECEIVER(#wthd_domain_SenderReceiverAuthData{sender = Token})) -> + {ok, hash_token(Token)}; +get_destination_sender(_, _CtxWithdrawal) -> + {error, notfound}. + +get_destination_receiver(withdrawal, ?SENDER_RECEIVER(#wthd_domain_SenderReceiverAuthData{receiver = Token})) -> + {ok, hash_token(Token)}; +get_destination_receiver(_, _CtxWithdrawal) -> + {error, notfound}. + +hash_token(Token) -> + <> = crypto:hash(sha, Token), + genlib_format:format_int_base(I, 61). diff --git a/apps/limiter/test/lim_ct_helper.hrl b/apps/limiter/test/lim_ct_helper.hrl index 407a3e85..9ac52cdc 100644 --- a/apps/limiter/test/lim_ct_helper.hrl +++ b/apps/limiter/test/lim_ct_helper.hrl @@ -12,6 +12,7 @@ -define(currency, <<"RUB">>). -define(string, <<"STRING">>). +-define(token, <<"TOKEN">>). -define(integer, 999). -define(timestamp, <<"2000-01-01T00:00:00Z">>). @@ -38,6 +39,8 @@ -define(scope_payer_contact_email(), {payer_contact_email, #config_LimitScopeEmptyDetails{}}). -define(scope_identity(), {identity, #config_LimitScopeEmptyDetails{}}). -define(scope_wallet(), {wallet, #config_LimitScopeEmptyDetails{}}). +-define(scope_sender(), {sender, #config_LimitScopeEmptyDetails{}}). +-define(scope_receiver(), {receiver, #config_LimitScopeEmptyDetails{}}). -define(lim_type_turnover(), ?lim_type_turnover(?turnover_metric_number())). -define(lim_type_turnover(Metric), @@ -215,13 +218,20 @@ owner_id = OwnerID }). +-define(auth_data(Sender, Receiver), + {sender_receiver, #wthd_domain_SenderReceiverAuthData{sender = Sender, receiver = Receiver}} +). + -define(withdrawal(Body), ?withdrawal(Body, ?bank_card(), ?string)). --define(withdrawal(Body, Destination, OwnerID), #wthd_domain_Withdrawal{ +-define(withdrawal(Body, Destination, OwnerID), ?withdrawal(Body, Destination, OwnerID, undefined)). + +-define(withdrawal(Body, Destination, OwnerID, AuthData), #wthd_domain_Withdrawal{ body = Body, created_at = ?timestamp, destination = Destination, - sender = ?identity(OwnerID) + sender = ?identity(OwnerID), + auth_data = AuthData }). -define(op_withdrawal, {withdrawal, #context_withdrawal_OperationWithdrawal{}}). @@ -237,4 +247,15 @@ } }). +-define(wthdproc_ctx_withdrawal_w_auth_data(Cost, Sender, Receiver), #limiter_LimitContext{ + withdrawal_processing = #context_withdrawal_Context{ + op = ?op_withdrawal, + withdrawal = #context_withdrawal_Withdrawal{ + withdrawal = ?withdrawal(Cost, ?bank_card(), ?string, ?auth_data(Sender, Receiver)), + route = ?route(), + wallet_id = ?string + } + } +}). + -endif. diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index 459fa273..f39b3caa 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -57,6 +57,11 @@ -export([commit_with_identity_scope_ok/1]). -export([commit_with_wallet_scope_ok/1]). -export([commit_with_multi_scope_ok/1]). +-export([hold_with_sender_notfound/1]). +-export([hold_with_receiver_notfound/1]). +-export([commit_with_sender_scope_ok/1]). +-export([commit_with_receiver_scope_ok/1]). +-export([commit_with_sender_receiver_scope_ok/1]). -type group_name() :: atom(). -type test_case_name() :: atom(). @@ -101,7 +106,9 @@ groups() -> commit_with_provider_scope_ok, commit_with_terminal_scope_ok, commit_with_email_scope_ok, - commit_with_multi_scope_ok + commit_with_multi_scope_ok, + hold_with_sender_notfound, + hold_with_receiver_notfound ]}, {default, [], [ {group, base}, @@ -120,7 +127,10 @@ groups() -> commit_with_provider_scope_ok, commit_with_terminal_scope_ok, commit_with_identity_scope_ok, - commit_with_wallet_scope_ok + commit_with_wallet_scope_ok, + commit_with_sender_scope_ok, + commit_with_receiver_scope_ok, + commit_with_sender_receiver_scope_ok ]}, {cashless, [parallel], [ commit_number_ok, @@ -652,7 +662,7 @@ commit_with_some_scope(Scope, C) -> {ID, Version} = configure_limit(?time_range_month(), Scope, C), Context = case get_group_name(C) of - withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)); + withdrawals -> ?wthdproc_ctx_withdrawal_w_auth_data(?cash(10, <<"RUB">>), ?token, ?token); _Default -> ?payproc_ctx_payment(?cash(10, <<"RUB">>), ?cash(10, <<"RUB">>)) end, {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), @@ -710,6 +720,40 @@ commit_with_multi_scope_ok(C) -> LimitState0#limiter_Limit.amount + 10 ). +-spec hold_with_sender_notfound(config()) -> _. +hold_with_sender_notfound(C) -> + hold_with_scope_notfound([?scope_sender()], C). + +-spec hold_with_receiver_notfound(config()) -> _. +hold_with_receiver_notfound(C) -> + hold_with_scope_notfound([?scope_receiver()], C). + +hold_with_scope_notfound(Scopes, C) -> + Context = + case get_group_name(C) of + withdrawals -> ?wthdproc_ctx_withdrawal(?cash(0)); + _Default -> ?payproc_ctx_invoice(?cash(0)) + end, + {ID, Version} = configure_limit(?time_range_month(), ?scope(Scopes), C), + ?assertException( + error, + {woody_error, + {external, result_unexpected, <<"error:{unknown_error,{lim_turnover_processor,notfound}}", _/binary>>}}, + lim_client:hold(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)) + ). + +-spec commit_with_sender_scope_ok(config()) -> _. +commit_with_sender_scope_ok(C) -> + _ = commit_with_some_scope(?scope([?scope_sender()]), C). + +-spec commit_with_receiver_scope_ok(config()) -> _. +commit_with_receiver_scope_ok(C) -> + _ = commit_with_some_scope(?scope([?scope_receiver()]), C). + +-spec commit_with_sender_receiver_scope_ok(config()) -> _. +commit_with_sender_receiver_scope_ok(C) -> + _ = commit_with_some_scope(?scope([?scope_sender(), ?scope_receiver()]), C). + %% gen_change_id(LimitID, ChangeID) -> diff --git a/rebar.lock b/rebar.lock index dcf2a4ea..d69be6a3 100644 --- a/rebar.lock +++ b/rebar.lock @@ -12,7 +12,7 @@ {<<"ctx">>,{pkg,<<"ctx">>,<<"0.6.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"02005deb9deb6470d2a17bb61e6b51098512f066"}}, + {ref,"500c0a2aab6dc90db0c0b7ea4cd9757fd86368d0"}}, 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git", @@ -38,7 +38,7 @@ {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},1}, {<<"limiter_proto">>, {git,"https://github.com/valitydev/limiter-proto.git", - {ref,"1432a9871fc31b74c2af28b9367cb3ed6e08d498"}}, + {ref,"10328404f1cea68586962ed7fce0405b18d62b28"}}, 0}, {<<"machinery">>, {git,"https://github.com/valitydev/machinery-erlang.git", From 0fc3b48a18f3a3a8a545803fa8962025a55f640c Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Tue, 29 Oct 2024 17:54:11 +0300 Subject: [PATCH 40/54] EMP-74: Adds scope support for withdrawal's destination field, e.g. phone number (#31) * Adds scope support for withdrawal's destination phone number * Implements destination_field limit scope * Adds hold and commit testcases for destination_field scope * Moves field path into hashed value * Adds destination_field support for domain object * Bumps stable deps --- apps/limiter/src/lim_config_codec.erl | 16 +++++- apps/limiter/src/lim_config_machine.erl | 65 ++++++++++++++++++++-- apps/limiter/src/lim_context_utils.erl | 28 ++++++++++ apps/limiter/src/lim_payproc_utils.erl | 20 +++++++ apps/limiter/src/lim_wthdproc_context.erl | 8 +-- apps/limiter/test/lim_ct_helper.hrl | 43 +++++++++------ apps/limiter/test/lim_turnover_SUITE.erl | 66 +++++++++++++++++++++-- rebar.lock | 4 +- 8 files changed, 219 insertions(+), 31 deletions(-) diff --git a/apps/limiter/src/lim_config_codec.erl b/apps/limiter/src/lim_config_codec.erl index 77fe8556..911591bc 100644 --- a/apps/limiter/src/lim_config_codec.erl +++ b/apps/limiter/src/lim_config_codec.erl @@ -139,6 +139,8 @@ marshal_scope_type(terminal) -> {terminal, #config_LimitScopeEmptyDetails{}}; marshal_scope_type(payer_contact_email) -> {payer_contact_email, #config_LimitScopeEmptyDetails{}}; +marshal_scope_type({destination_field, FieldPath}) -> + {destination_field, #config_LimitScopeDestinationFieldDetails{field_path = FieldPath}}; marshal_scope_type(sender) -> {sender, #config_LimitScopeEmptyDetails{}}; marshal_scope_type(receiver) -> @@ -354,6 +356,12 @@ unmarshal_scope_type({terminal, _}) -> terminal; unmarshal_scope_type({payer_contact_email, _}) -> payer_contact_email; +unmarshal_scope_type({destination_field, #config_LimitScopeDestinationFieldDetails{field_path = FieldPath}}) -> + %% Limiter proto variant clause + {destination_field, FieldPath}; +unmarshal_scope_type({destination_field, #limiter_config_LimitScopeDestinationFieldDetails{field_path = FieldPath}}) -> + %% Domain config variant clause + {destination_field, FieldPath}; unmarshal_scope_type({sender, _}) -> sender; unmarshal_scope_type({receiver, _}) -> @@ -441,7 +449,7 @@ unmarshal_config_object_test() -> time_range_type => {calendar, day}, context_type => payment_processing, type => {turnover, number}, - scope => ordsets:from_list([party, shop]), + scope => ordsets:from_list([party, shop, {destination_field, [<<"path">>, <<"to">>, <<"field">>]}]), description => <<"description">>, currency_conversion => true }, @@ -457,7 +465,11 @@ unmarshal_config_object_test() -> type = {turnover, #limiter_config_LimitTypeTurnover{metric = {number, #limiter_config_LimitTurnoverNumber{}}}}, scopes = ordsets:from_list([ - {'party', #limiter_config_LimitScopeEmptyDetails{}}, {'shop', #limiter_config_LimitScopeEmptyDetails{}} + {'party', #limiter_config_LimitScopeEmptyDetails{}}, + {'shop', #limiter_config_LimitScopeEmptyDetails{}}, + {'destination_field', #limiter_config_LimitScopeDestinationFieldDetails{ + field_path = [<<"path">>, <<"to">>, <<"field">>] + }} ]), description = <<"description">>, currency_conversion = #limiter_config_CurrencyConversion{} diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index ca9b562d..69da326c 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -41,7 +41,16 @@ -type limit_type() :: {turnover, lim_turnover_metric:t()}. -type limit_scope() :: ordsets:ordset(limit_scope_type()). --type limit_scope_type() :: party | shop | wallet | identity | payment_tool | provider | terminal | payer_contact_email. +-type limit_scope_type() :: + party + | shop + | wallet + | identity + | payment_tool + | provider + | terminal + | payer_contact_email + | {destination_field, [Field :: binary()]}. -type shard_size() :: pos_integer(). -type shard_id() :: binary(). -type prefix() :: binary(). @@ -579,8 +588,34 @@ append_prefix(Fragment, Acc) -> -spec enumerate_context_bits(limit_scope()) -> [context_bit()]. enumerate_context_bits(Types) -> TypesOrder = - [party, shop, identity, wallet, payment_tool, provider, terminal, payer_contact_email, sender, receiver], - SortedTypes = lists:filter(fun(T) -> ordsets:is_element(T, Types) end, TypesOrder), + [ + party, + shop, + identity, + wallet, + payment_tool, + provider, + terminal, + payer_contact_email, + %% Scope 'destination_field' differs from other scope + %% types by having an attribute and being represented as a + %% tuple '{destination_field, [Field :: binary()]}'. + destination_field, + sender, + receiver + ], + SortedTypes = lists:filtermap( + fun + (destination_field) -> + case lists:keyfind(destination_field, 1, Types) of + {destination_field, _} = Found -> {true, Found}; + _ -> false + end; + (T) -> + ordsets:is_element(T, Types) + end, + TypesOrder + ), SquashedTypes = squash_scope_types(SortedTypes), lists:flatmap(fun get_context_bits/1, SquashedTypes). @@ -620,6 +655,8 @@ get_context_bits(terminal) -> [{prefix, <<"terminal">>}, {from, provider_id}, {from, terminal_id}]; get_context_bits(payer_contact_email) -> [{prefix, <<"payer_contact_email">>}, {from, payer_contact_email}]; +get_context_bits({destination_field, FieldPath}) -> + [{prefix, <<"destination">>}, {from, {destination_field, FieldPath}}]; get_context_bits(sender) -> [{prefix, <<"sender">>}, {from, sender}]; get_context_bits(receiver) -> @@ -629,6 +666,15 @@ get_context_bits(receiver) -> {ok, binary()} | {error, lim_context:context_error()}. extract_context_bit({prefix, Prefix}, _ContextType, _LimitContext) -> {ok, Prefix}; +extract_context_bit({from, {destination_field, FieldPath}}, ContextType, LimitContext) -> + do(fun() -> + #{type := Type, data := Data} = unwrap(get_generic_payment_tool_resource(ContextType, LimitContext)), + DecodedData = unwrap(lim_context_utils:decode_content(Type, Data)), + FieldValue = unwrap(lim_context_utils:get_field_by_path(FieldPath, DecodedData)), + PrefixedValue = lim_string:join($., FieldPath ++ [FieldValue]), + HashedValue = lim_context_utils:base61_hash(PrefixedValue), + mk_scope_component([HashedValue]) + end); extract_context_bit({from, payment_tool}, ContextType, LimitContext) -> case lim_context:get_value(ContextType, payment_tool, LimitContext) of {ok, {bank_card, #{token := Token, exp_date := {Month, Year}}}} -> @@ -637,6 +683,9 @@ extract_context_bit({from, payment_tool}, ContextType, LimitContext) -> {ok, mk_scope_component([Token, <<"undefined">>])}; {ok, {digital_wallet, #{id := ID, service := Service}}} -> {ok, mk_scope_component([<<"DW">>, Service, ID])}; + %% Generic payment tool is supposed to be not supported + {ok, {generic = Type, _}} -> + {error, {unsupported, {payment_tool, Type}}}; {error, _} = Error -> Error end; @@ -646,6 +695,16 @@ extract_context_bit({from, ValueName}, ContextType, LimitContext) -> mk_scope_component(Fragments) -> lim_string:join($/, Fragments). +get_generic_payment_tool_resource(ContextType, LimitContext) -> + case lim_context:get_value(ContextType, payment_tool, LimitContext) of + {ok, {generic, #{data := ResourceData}}} -> + {ok, ResourceData}; + {ok, {ToolType, _}} -> + {error, {unsupported, ToolType}}; + {error, _} = Error -> + Error + end. + %%% Machinery callbacks -spec init(args([event()]), machine(), handler_args(), handler_opts()) -> result(). diff --git a/apps/limiter/src/lim_context_utils.erl b/apps/limiter/src/lim_context_utils.erl index 7edb084a..a07363d5 100644 --- a/apps/limiter/src/lim_context_utils.erl +++ b/apps/limiter/src/lim_context_utils.erl @@ -5,6 +5,9 @@ -export([route_provider_id/1]). -export([route_terminal_id/1]). +-export([base61_hash/1]). +-export([decode_content/2]). +-export([get_field_by_path/2]). -type provider_id() :: binary(). -type terminal_id() :: binary(). @@ -20,3 +23,28 @@ route_provider_id(#base_Route{provider = #domain_ProviderRef{id = ID}}) -> {ok, terminal_id()}. route_terminal_id(#base_Route{terminal = #domain_TerminalRef{id = ID}}) -> {ok, genlib:to_binary(ID)}. + +-spec base61_hash(iolist() | binary()) -> binary(). +base61_hash(IOList) -> + <> = crypto:hash(sha, IOList), + genlib_format:format_int_base(I, 61). + +-spec decode_content(Type, binary()) -> + {ok, map()} | {error, {unsupported, Type}} +when + Type :: binary(). +decode_content(<<"application/schema-instance+json; schema=", _/binary>>, Data) -> + {ok, jsx:decode(Data)}; +decode_content(<<"application/json">>, Data) -> + {ok, jsx:decode(Data)}; +decode_content(Type, _Data) -> + {error, {unsupported, Type}}. + +-spec get_field_by_path([binary()], map()) -> {ok, map() | binary() | number()} | {error, notfound}. +get_field_by_path([], Data) -> + {ok, Data}; +get_field_by_path([Key | Path], Data) -> + case maps:get(Key, Data, undefined) of + undefined -> {error, notfound}; + Value -> get_field_by_path(Path, Value) + end. diff --git a/apps/limiter/src/lim_payproc_utils.erl b/apps/limiter/src/lim_payproc_utils.erl index 7b62a575..8034d13c 100644 --- a/apps/limiter/src/lim_payproc_utils.erl +++ b/apps/limiter/src/lim_payproc_utils.erl @@ -1,6 +1,7 @@ -module(lim_payproc_utils). -include_lib("damsel/include/dmsl_domain_thrift.hrl"). +-include_lib("damsel/include/dmsl_base_thrift.hrl"). -export([cash/1]). -export([payment_tool/1]). @@ -16,6 +17,13 @@ | {digital_wallet, #{ id := binary(), service := binary() + }} + | {generic, #{ + service := binary(), + data := #{ + type := binary(), + data := binary() + } }}. %% @@ -39,6 +47,18 @@ payment_tool({digital_wallet, DW}) -> id => DW#domain_DigitalWallet.id, service => DW#domain_DigitalWallet.payment_service#domain_PaymentServiceRef.id }}}; +payment_tool({generic, G}) -> + %% TODO Move to codec into marshal/unmarshal clauses + Content = G#domain_GenericPaymentTool.data, + {ok, + {generic, #{ + service => G#domain_GenericPaymentTool.payment_service#domain_PaymentServiceRef.id, + data => #{ + %% TODO Content decoding + type => Content#base_Content.type, + data => Content#base_Content.data + } + }}}; payment_tool({Type, _}) -> {error, {unsupported, {payment_tool, Type}}}. diff --git a/apps/limiter/src/lim_wthdproc_context.erl b/apps/limiter/src/lim_wthdproc_context.erl index 0a12e1d8..f02414b4 100644 --- a/apps/limiter/src/lim_wthdproc_context.erl +++ b/apps/limiter/src/lim_wthdproc_context.erl @@ -127,15 +127,11 @@ get_terminal_id(_, _CtxWithdrawal) -> {error, notfound}. get_destination_sender(withdrawal, ?SENDER_RECEIVER(#wthd_domain_SenderReceiverAuthData{sender = Token})) -> - {ok, hash_token(Token)}; + {ok, lim_context_utils:base61_hash(Token)}; get_destination_sender(_, _CtxWithdrawal) -> {error, notfound}. get_destination_receiver(withdrawal, ?SENDER_RECEIVER(#wthd_domain_SenderReceiverAuthData{receiver = Token})) -> - {ok, hash_token(Token)}; + {ok, lim_context_utils:base61_hash(Token)}; get_destination_receiver(_, _CtxWithdrawal) -> {error, notfound}. - -hash_token(Token) -> - <> = crypto:hash(sha, Token), - genlib_format:format_int_base(I, 61). diff --git a/apps/limiter/test/lim_ct_helper.hrl b/apps/limiter/test/lim_ct_helper.hrl index 9ac52cdc..d0e54b5a 100644 --- a/apps/limiter/test/lim_ct_helper.hrl +++ b/apps/limiter/test/lim_ct_helper.hrl @@ -7,6 +7,7 @@ -include_lib("limiter_proto/include/limproto_limiter_thrift.hrl"). -include_lib("limiter_proto/include/limproto_context_payproc_thrift.hrl"). -include_lib("limiter_proto/include/limproto_context_withdrawal_thrift.hrl"). +-include_lib("damsel/include/dmsl_base_thrift.hrl"). -include_lib("damsel/include/dmsl_domain_thrift.hrl"). -include_lib("damsel/include/dmsl_wthd_domain_thrift.hrl"). @@ -41,6 +42,9 @@ -define(scope_wallet(), {wallet, #config_LimitScopeEmptyDetails{}}). -define(scope_sender(), {sender, #config_LimitScopeEmptyDetails{}}). -define(scope_receiver(), {receiver, #config_LimitScopeEmptyDetails{}}). +-define(scope_destination_field(FieldPath), + {destination_field, #config_LimitScopeDestinationFieldDetails{field_path = FieldPath}} +). -define(lim_type_turnover(), ?lim_type_turnover(?turnover_metric_number())). -define(lim_type_turnover(Metric), @@ -116,6 +120,15 @@ }} ). +-define(generic_pt(), + {generic, #domain_GenericPaymentTool{ + payment_service = #domain_PaymentServiceRef{id = <<"ID42">>}, + data = #base_Content{ + type = <<"application/json">>, data = <<"{\"opaque\":{\"payload\":{\"data\":\"value\"}}}">> + } + }} +). + -define(invoice(OwnerID, ShopID, Cost), #domain_Invoice{ id = ?string, owner_id = OwnerID, @@ -153,15 +166,20 @@ }} }). --define(payproc_ctx_invoice(Cost), #limiter_LimitContext{ +-define(payproc_ctx(Op, Invoice, InvoicePayment), #limiter_LimitContext{ payment_processing = #context_payproc_Context{ - op = ?op_invoice, + op = Op, invoice = #context_payproc_Invoice{ - invoice = ?invoice(?string, ?string, Cost) + invoice = Invoice, + payment = InvoicePayment } } }). +-define(payproc_ctx(Invoice, InvoicePayment), ?payproc_ctx(?op_invoice, Invoice, InvoicePayment)). + +-define(payproc_ctx_invoice(Cost), ?payproc_ctx(?invoice(?string, ?string, Cost), undefined)). + -define(payproc_ctx_payment(Cost, CaptureCost), ?payproc_ctx_payment(?string, ?string, Cost, CaptureCost) ). @@ -236,26 +254,21 @@ -define(op_withdrawal, {withdrawal, #context_withdrawal_OperationWithdrawal{}}). --define(wthdproc_ctx_withdrawal(Cost), #limiter_LimitContext{ +-define(wthdproc_ctx(Withdrawal), #limiter_LimitContext{ withdrawal_processing = #context_withdrawal_Context{ op = ?op_withdrawal, withdrawal = #context_withdrawal_Withdrawal{ - withdrawal = ?withdrawal(Cost), + withdrawal = Withdrawal, route = ?route(), wallet_id = ?string } } }). --define(wthdproc_ctx_withdrawal_w_auth_data(Cost, Sender, Receiver), #limiter_LimitContext{ - withdrawal_processing = #context_withdrawal_Context{ - op = ?op_withdrawal, - withdrawal = #context_withdrawal_Withdrawal{ - withdrawal = ?withdrawal(Cost, ?bank_card(), ?string, ?auth_data(Sender, Receiver)), - route = ?route(), - wallet_id = ?string - } - } -}). +-define(wthdproc_ctx_withdrawal(Cost), ?wthdproc_ctx(?withdrawal(Cost))). + +-define(wthdproc_ctx_withdrawal_w_auth_data(Cost, Sender, Receiver), + ?wthdproc_ctx(?withdrawal(Cost, ?bank_card(), ?string, ?auth_data(Sender, Receiver))) +). -endif. diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index f39b3caa..98d85791 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -59,9 +59,12 @@ -export([commit_with_multi_scope_ok/1]). -export([hold_with_sender_notfound/1]). -export([hold_with_receiver_notfound/1]). +-export([hold_with_destination_field_not_found/1]). +-export([hold_with_destination_field_not_supported/1]). -export([commit_with_sender_scope_ok/1]). -export([commit_with_receiver_scope_ok/1]). -export([commit_with_sender_receiver_scope_ok/1]). +-export([commit_with_destination_field_scope_ok/1]). -type group_name() :: atom(). -type test_case_name() :: atom(). @@ -108,7 +111,9 @@ groups() -> commit_with_email_scope_ok, commit_with_multi_scope_ok, hold_with_sender_notfound, - hold_with_receiver_notfound + hold_with_receiver_notfound, + hold_with_destination_field_not_found, + hold_with_destination_field_not_supported ]}, {default, [], [ {group, base}, @@ -130,7 +135,9 @@ groups() -> commit_with_wallet_scope_ok, commit_with_sender_scope_ok, commit_with_receiver_scope_ok, - commit_with_sender_receiver_scope_ok + commit_with_sender_receiver_scope_ok, + commit_with_destination_field_scope_ok, + hold_with_destination_field_not_supported ]}, {cashless, [parallel], [ commit_number_ok, @@ -659,12 +666,15 @@ commit_with_terminal_scope_ok(C) -> _ = commit_with_some_scope(?scope([?scope_terminal()]), C). commit_with_some_scope(Scope, C) -> - {ID, Version} = configure_limit(?time_range_month(), Scope, C), Context = case get_group_name(C) of withdrawals -> ?wthdproc_ctx_withdrawal_w_auth_data(?cash(10, <<"RUB">>), ?token, ?token); _Default -> ?payproc_ctx_payment(?cash(10, <<"RUB">>), ?cash(10, <<"RUB">>)) end, + commit_with_some_scope(Scope, Context, C). + +commit_with_some_scope(Scope, Context, C) -> + {ID, Version} = configure_limit(?time_range_month(), Scope, C), {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), {ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)). @@ -728,12 +738,30 @@ hold_with_sender_notfound(C) -> hold_with_receiver_notfound(C) -> hold_with_scope_notfound([?scope_receiver()], C). +-spec hold_with_destination_field_not_found(config()) -> _. +hold_with_destination_field_not_found(C) -> + Scopes = [?scope_destination_field([<<"not">>, <<"existing">>, <<"field">>])], + Context = + case get_group_name(C) of + withdrawals -> ?wthdproc_ctx(?withdrawal(?cash(0), ?generic_pt(), ?string)); + _Default -> ?payproc_ctx(?invoice(?string, ?string, ?cash(0)), undefined) + end, + hold_with_scope_notfound(Scopes, Context, C). + +-spec hold_with_destination_field_not_supported(config()) -> _. +hold_with_destination_field_not_supported(C) -> + Scopes = [?scope_destination_field([<<"opaque">>, <<"payload">>, <<"data">>])], + hold_with_scope_unsupported(Scopes, C). + hold_with_scope_notfound(Scopes, C) -> Context = case get_group_name(C) of withdrawals -> ?wthdproc_ctx_withdrawal(?cash(0)); _Default -> ?payproc_ctx_invoice(?cash(0)) end, + hold_with_scope_notfound(Scopes, Context, C). + +hold_with_scope_notfound(Scopes, Context, C) -> {ID, Version} = configure_limit(?time_range_month(), ?scope(Scopes), C), ?assertException( error, @@ -742,6 +770,28 @@ hold_with_scope_notfound(Scopes, C) -> lim_client:hold(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)) ). +hold_with_scope_unsupported(Scopes, C) -> + {ID, Version} = configure_limit(?time_range_month(), ?scope(Scopes), C), + Context = + case get_group_name(C) of + withdrawals -> + ?wthdproc_ctx(?withdrawal(?cash(10, <<"RUB">>), ?bank_card(), ?string)); + _Default -> + ?payproc_ctx( + ?op_payment, ?invoice(?string, ?string, ?cash(10, <<"RUB">>)), #context_payproc_InvoicePayment{ + payment = ?invoice_payment(?cash(10, <<"RUB">>), ?cash(10, <<"RUB">>)), + route = ?route() + } + ) + end, + ?assertException( + error, + {woody_error, + {external, result_unexpected, + <<"error:{unknown_error,{lim_turnover_processor,{unsupported,bank_card}}}", _/binary>>}}, + lim_client:hold(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)) + ). + -spec commit_with_sender_scope_ok(config()) -> _. commit_with_sender_scope_ok(C) -> _ = commit_with_some_scope(?scope([?scope_sender()]), C). @@ -754,6 +804,16 @@ commit_with_receiver_scope_ok(C) -> commit_with_sender_receiver_scope_ok(C) -> _ = commit_with_some_scope(?scope([?scope_sender(), ?scope_receiver()]), C). +-spec commit_with_destination_field_scope_ok(config()) -> _. +commit_with_destination_field_scope_ok(C) -> + Scopes = [?scope_destination_field([<<"opaque">>, <<"payload">>, <<"data">>])], + Context = + case get_group_name(C) of + withdrawals -> ?wthdproc_ctx(?withdrawal(?cash(10, <<"RUB">>), ?generic_pt(), ?string)); + _Default -> ?payproc_ctx_payment(?cash(10, <<"RUB">>), ?cash(10, <<"RUB">>)) + end, + _ = commit_with_some_scope(?scope(Scopes), Context, C). + %% gen_change_id(LimitID, ChangeID) -> diff --git a/rebar.lock b/rebar.lock index d69be6a3..c7a212a4 100644 --- a/rebar.lock +++ b/rebar.lock @@ -12,7 +12,7 @@ {<<"ctx">>,{pkg,<<"ctx">>,<<"0.6.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"500c0a2aab6dc90db0c0b7ea4cd9757fd86368d0"}}, + {ref,"7ed2112a6503abe9f65142e43dca6675e939d164"}}, 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git", @@ -38,7 +38,7 @@ {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},1}, {<<"limiter_proto">>, {git,"https://github.com/valitydev/limiter-proto.git", - {ref,"10328404f1cea68586962ed7fce0405b18d62b28"}}, + {ref,"2483f600d6f00193a7e8493bcefd6654d14eaa6a"}}, 0}, {<<"machinery">>, {git,"https://github.com/valitydev/machinery-erlang.git", From 553e18d59b618fd1730dd55d5e860618ddb5203c Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Thu, 31 Oct 2024 13:47:43 +0300 Subject: [PATCH 41/54] Adds support for prometheus woody metrics (#32) * Adds support for prometheus woody metrics * Bumps CI wf * Ignore accidental god-module `lim_turnover_SUITE` --- .github/workflows/erlang-checks.yml | 4 ++-- apps/limiter/src/limiter.app.src | 6 ++++-- apps/limiter/src/limiter.erl | 5 +++++ elvis.config | 8 +++++++- rebar.config | 15 ++++++++++----- rebar.lock | 26 +++++++++++++++++++------- 6 files changed, 47 insertions(+), 17 deletions(-) diff --git a/.github/workflows/erlang-checks.yml b/.github/workflows/erlang-checks.yml index dc53054d..c842aff1 100644 --- a/.github/workflows/erlang-checks.yml +++ b/.github/workflows/erlang-checks.yml @@ -18,7 +18,7 @@ jobs: thrift-version: ${{ steps.thrift-version.outputs.version }} steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 - run: grep -v '^#' .env >> $GITHUB_ENV - id: otp-version run: echo "::set-output name=version::$OTP_VERSION" @@ -30,7 +30,7 @@ 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@v1.0.15 with: otp-version: ${{ needs.setup.outputs.otp-version }} rebar-version: ${{ needs.setup.outputs.rebar-version }} diff --git a/apps/limiter/src/limiter.app.src b/apps/limiter/src/limiter.app.src index fa46fefb..2624ad9e 100644 --- a/apps/limiter/src/limiter.app.src +++ b/apps/limiter/src/limiter.app.src @@ -5,11 +5,13 @@ {applications, [ kernel, stdlib, + prometheus, + prometheus_cowboy, + scoper, % should be before any scoper event handler usage + woody, limiter_proto, xrates_proto, machinery, - woody, - scoper, % should be before any scoper event handler usage erl_health, dmt_client, opentelemetry_api, diff --git a/apps/limiter/src/limiter.erl b/apps/limiter/src/limiter.erl index af207969..1eedaa6c 100644 --- a/apps/limiter/src/limiter.erl +++ b/apps/limiter/src/limiter.erl @@ -17,6 +17,7 @@ -spec start(normal, any()) -> {ok, pid()} | {error, any()}. start(_StartType, _StartArgs) -> + ok = setup_metrics(), supervisor:start_link({local, ?MODULE}, ?MODULE, []). -spec stop(any()) -> ok. @@ -152,3 +153,7 @@ get_service_client(ServiceID) -> Url -> lim_utils:get_woody_client(Url) end. + +setup_metrics() -> + ok = woody_ranch_prometheus_collector:setup(), + ok = woody_hackney_prometheus_collector:setup(). diff --git a/elvis.config b/elvis.config index d3de979b..cbebd321 100644 --- a/elvis.config +++ b/elvis.config @@ -26,7 +26,13 @@ {elvis_style, used_ignored_variable, disable}, % Tests are usually more comprehensible when a bit more verbose. {elvis_style, dont_repeat_yourself, #{min_complexity => 20}}, - {elvis_style, god_modules, #{limit => 50}} + {elvis_style, god_modules, #{ + limit => 50, + ignore => [ + % NOTE Maybe refactor testsuite by moving helpers to according ct-modules + lim_turnover_SUITE + ] + }} ] }, #{ diff --git a/rebar.config b/rebar.config index 4fbecdb9..03bd0662 100644 --- a/rebar.config +++ b/rebar.config @@ -26,6 +26,8 @@ %% Common project dependencies. {deps, [ + {prometheus, "4.8.1"}, + {prometheus_cowboy, "0.1.8"}, {damsel, {git, "https://github.com/valitydev/damsel.git", {branch, "master"}}}, {limiter_proto, {git, "https://github.com/valitydev/limiter-proto.git", {branch, "master"}}}, {xrates_proto, {git, "https://github.com/valitydev/xrates-proto.git", {branch, "master"}}}, @@ -65,11 +67,6 @@ {deps, [ % for introspection on production {recon, "2.5.2"}, - % Because of a dependency conflict, prometheus libs are only included in the prod profile for now - % https://github.com/project-fifo/rebar3_lint/issues/42 - % https://github.com/valitydev/hellgate/pull/2/commits/884724c1799703cee4d1033850fe32c17f986d9e - {prometheus, "4.8.1"}, - {prometheus_cowboy, "0.1.8"}, {logger_logstash_formatter, {git, "https://github.com/valitydev/logger_logstash_formatter.git", {ref, "08a66a6"}}}, {iosetopts, {git, "https://github.com/valitydev/iosetopts.git", {ref, "edb445c"}}} @@ -122,3 +119,11 @@ "ct.coverdata" ]} ]}. + +%% NOTE +%% It is needed to use rebar3 lint plugin +{overrides, [ + {del, accept, [{plugins, [{rebar3_archive_plugin, "0.0.2"}]}]}, + {del, prometheus_cowboy, [{plugins, [{rebar3_archive_plugin, "0.0.1"}]}]}, + {del, prometheus_httpd, [{plugins, [{rebar3_archive_plugin, "0.0.1"}]}]} +]}. diff --git a/rebar.lock b/rebar.lock index c7a212a4..827cd4e1 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,5 +1,6 @@ {"1.2.0", -[{<<"acceptor_pool">>,{pkg,<<"acceptor_pool">>,<<"1.0.0">>},2}, +[{<<"accept">>,{pkg,<<"accept">>,<<"0.3.5">>},2}, + {<<"acceptor_pool">>,{pkg,<<"acceptor_pool">>,<<"1.0.0">>},2}, {<<"cache">>,{pkg,<<"cache">>,<<"2.3.3">>},1}, {<<"certifi">>,{pkg,<<"certifi">>,<<"2.8.0">>},2}, {<<"cg_mon">>, @@ -16,11 +17,11 @@ 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git", - {ref,"b8bc0281dbf1e55a1a67ef6da861e0353ff14913"}}, + {ref,"d8a4f490d49c038d96f1cbc2a279164c6f4039f9"}}, 0}, {<<"dmt_core">>, {git,"https://github.com/valitydev/dmt-core.git", - {ref,"75841332fe0b40a77da0c12ea8d5dbb994da8e82"}}, + {ref,"19d8f57198f2cbe5b64aa4a923ba32774e505503"}}, 1}, {<<"erl_health">>, {git,"https://github.com/valitydev/erlang-health.git", @@ -59,7 +60,10 @@ {pkg,<<"opentelemetry_semantic_conventions">>,<<"0.2.0">>}, 1}, {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.1">>},2}, - {<<"prometheus">>,{pkg,<<"prometheus">>,<<"4.6.0">>},1}, + {<<"prometheus">>,{pkg,<<"prometheus">>,<<"4.8.1">>},0}, + {<<"prometheus_cowboy">>,{pkg,<<"prometheus_cowboy">>,<<"0.1.8">>},0}, + {<<"prometheus_httpd">>,{pkg,<<"prometheus_httpd">>,<<"2.1.11">>},1}, + {<<"quantile_estimator">>,{pkg,<<"quantile_estimator">>,<<"0.2.1">>},1}, {<<"ranch">>,{pkg,<<"ranch">>,<<"1.8.0">>},2}, {<<"scoper">>, {git,"https://github.com/valitydev/scoper.git", @@ -80,7 +84,7 @@ {<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},2}, {<<"woody">>, {git,"https://github.com/valitydev/woody_erlang.git", - {ref,"81219ba5408e1c67f5eaed3c7e566ede42da88d4"}}, + {ref,"072825ee7179825a4078feb0649df71303c74157"}}, 0}, {<<"xrates_proto">>, {git,"https://github.com/valitydev/xrates-proto.git", @@ -88,6 +92,7 @@ 0}]}. [ {pkg_hash,[ + {<<"accept">>, <<"B33B127ABCA7CC948BBE6CAA4C263369ABF1347CFA9D8E699C6D214660F10CD1">>}, {<<"acceptor_pool">>, <<"43C20D2ACAE35F0C2BCD64F9D2BDE267E459F0F3FD23DAB26485BF518C281B21">>}, {<<"cache">>, <<"B23A5FE7095445A88412A6E614C933377E0137B44FFED77C9B3FEF1A731A20B2">>}, {<<"certifi">>, <<"D4FB0A6BB20B7C9C3643E22507E42F356AC090A1DCEA9AB99E27E0376D695EBA">>}, @@ -108,12 +113,16 @@ {<<"opentelemetry_exporter">>, <<"1D8809C0D4F4ACF986405F7700ED11992BCBDB6A4915DD11921E80777FFA7167">>}, {<<"opentelemetry_semantic_conventions">>, <<"B67FE459C2938FCAB341CB0951C44860C62347C005ACE1B50F8402576F241435">>}, {<<"parse_trans">>, <<"16328AB840CC09919BD10DAB29E431DA3AF9E9E7E7E6F0089DD5A2D2820011D8">>}, - {<<"prometheus">>, <<"20510F381DB1CCAB818B4CF2FAC5FA6AB5CC91BC364A154399901C001465F46F">>}, + {<<"prometheus">>, <<"FA76B152555273739C14B06F09F485CF6D5D301FE4E9D31B7FF803D26025D7A0">>}, + {<<"prometheus_cowboy">>, <<"CFCE0BC7B668C5096639084FCD873826E6220EA714BF60A716F5BD080EF2A99C">>}, + {<<"prometheus_httpd">>, <<"F616ED9B85B536B195D94104063025A91F904A4CFC20255363F49A197D96C896">>}, + {<<"quantile_estimator">>, <<"EF50A361F11B5F26B5F16D0696E46A9E4661756492C981F7B2229EF42FF1CD15">>}, {<<"ranch">>, <<"8C7A100A139FD57F17327B6413E4167AC559FBC04CA7448E9BE9057311597A1D">>}, {<<"ssl_verify_fun">>, <<"354C321CF377240C7B8716899E182CE4890C5938111A1296ADD3EC74CF1715DF">>}, {<<"tls_certificate_check">>, <<"C76C4C5D79EE79A2B11C84F910C825D6F024A78427C854F515748E9BD025E987">>}, {<<"unicode_util_compat">>, <<"BC84380C9AB48177092F43AC89E4DFA2C6D62B40B8BD132B1059ECC7232F9A78">>}]}, {pkg_hash_ext,[ + {<<"accept">>, <<"11B18C220BCC2EAB63B5470C038EF10EB6783BCB1FCDB11AA4137DEFA5AC1BB8">>}, {<<"acceptor_pool">>, <<"0CBCD83FDC8B9AD2EEE2067EF8B91A14858A5883CB7CD800E6FCD5803E158788">>}, {<<"cache">>, <<"44516CE6FA03594D3A2AF025DD3A87BFE711000EB730219E1DDEFC816E0AA2F4">>}, {<<"certifi">>, <<"6AC7EFC1C6F8600B08D625292D4BBF584E14847CE1B6B5C44D983D273E1097EA">>}, @@ -134,7 +143,10 @@ {<<"opentelemetry_exporter">>, <<"2B40007F509D38361744882FD060A8841AF772AB83BB542AA5350908B303AD65">>}, {<<"opentelemetry_semantic_conventions">>, <<"D61FA1F5639EE8668D74B527E6806E0503EFC55A42DB7B5F39939D84C07D6895">>}, {<<"parse_trans">>, <<"07CD9577885F56362D414E8C4C4E6BDF10D43A8767ABB92D24CBE8B24C54888B">>}, - {<<"prometheus">>, <<"4905FD2992F8038ECCD7AA0CD22F40637ED618C0BED1F75C05AACEC15B7545DE">>}, + {<<"prometheus">>, <<"6EDFBE928D271C7F657A6F2C46258738086584BD6CAE4A000B8B9A6009BA23A5">>}, + {<<"prometheus_cowboy">>, <<"BA286BECA9302618418892D37BCD5DC669A6CC001F4EB6D6AF85FF81F3F4F34C">>}, + {<<"prometheus_httpd">>, <<"0BBE831452CFDF9588538EB2F570B26F30C348ADAE5E95A7D87F35A5910BCF92">>}, + {<<"quantile_estimator">>, <<"282A8A323CA2A845C9E6F787D166348F776C1D4A41EDE63046D72D422E3DA946">>}, {<<"ranch">>, <<"49FBCFD3682FAB1F5D109351B61257676DA1A2FDBE295904176D5E521A2DDFE5">>}, {<<"ssl_verify_fun">>, <<"FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8">>}, {<<"tls_certificate_check">>, <<"4083B4A298ADD534C96125337CB01161C358BB32DD870D5A893AAE685FD91D70">>}, From 2646b7172e90e22bbe2253a89646edab65eb2978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D0=B5=D0=BC?= Date: Mon, 25 Nov 2024 12:30:35 +0300 Subject: [PATCH 42/54] TD-958: New backend (#30) * added liminator * added support of new liminator backend * fixed elvis * wip * finished * bumped limiter proto * fixed * added scopes to limit cfg to test change ctx * Revert "added scopes to limit cfg to test change ctx" This reverts commit 12203c66dc466ffe4eba04dbbd1e895fae49dfd8. * Revert "Revert "added scopes to limit cfg to test change ctx"" This reverts commit 76963bb77a1bd0caf87a8851313cea73f9ea700d. * fixed * fixed * fixed fmt --- .github/workflows/erlang-checks.yml | 1 + Makefile | 2 +- apps/limiter/src/lim_client_woody.erl | 2 + apps/limiter/src/lim_config_machine.erl | 133 ++++++++- apps/limiter/src/lim_context.erl | 12 + apps/limiter/src/lim_handler.erl | 85 ++++++ apps/limiter/src/lim_liminator.erl | 92 +++++++ apps/limiter/src/lim_payproc_context.erl | 29 ++ apps/limiter/src/lim_turnover_metric.erl | 1 + apps/limiter/src/lim_turnover_processor.erl | 68 +++-- apps/limiter/src/lim_wthdproc_context.erl | 30 +++ apps/limiter/src/limiter.app.src | 1 + apps/limiter/test/lim_client.erl | 27 ++ apps/limiter/test/lim_configurator_SUITE.erl | 3 + apps/limiter/test/lim_turnover_SUITE.erl | 270 ++++++++++++++++++- compose.yaml | 35 ++- elvis.config | 11 +- rebar.config | 1 + rebar.lock | 6 +- 19 files changed, 763 insertions(+), 46 deletions(-) create mode 100644 apps/limiter/src/lim_liminator.erl diff --git a/.github/workflows/erlang-checks.yml b/.github/workflows/erlang-checks.yml index c842aff1..436c8239 100644 --- a/.github/workflows/erlang-checks.yml +++ b/.github/workflows/erlang-checks.yml @@ -38,3 +38,4 @@ jobs: thrift-version: ${{ needs.setup.outputs.thrift-version }} run-ct-with-compose: true upload-coverage: false + cache-version: v1 diff --git a/Makefile b/Makefile index 25c966b0..0ffe029f 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ DEV_IMAGE_TAG = $(TEST_CONTAINER_NAME)-dev DEV_IMAGE_ID = $(file < .image.dev) DOCKER ?= docker -DOCKERCOMPOSE ?= docker-compose +DOCKERCOMPOSE ?= docker compose DOCKERCOMPOSE_W_ENV = DEV_IMAGE_TAG=$(DEV_IMAGE_TAG) $(DOCKERCOMPOSE) -f compose.yaml -f compose.tracing.yaml REBAR ?= rebar3 TEST_CONTAINER_NAME ?= testrunner diff --git a/apps/limiter/src/lim_client_woody.erl b/apps/limiter/src/lim_client_woody.erl index 9838c623..e91f0816 100644 --- a/apps/limiter/src/lim_client_woody.erl +++ b/apps/limiter/src/lim_client_woody.erl @@ -39,6 +39,8 @@ get_service_client_url(ServiceName) -> -spec get_service_modname(service_name()) -> woody:service(). get_service_modname(xrates) -> {xrates_rate_thrift, 'Rates'}; +get_service_modname(liminator) -> + {liminator_liminator_thrift, 'LiminatorService'}; get_service_modname(accounter) -> {dmsl_accounter_thrift, 'Accounter'}. diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index 69da326c..788dd302 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -29,6 +29,12 @@ -export([commit/2]). -export([rollback/2]). +-export([get_values/2]). +-export([get_batch/3]). +-export([hold_batch/3]). +-export([commit_batch/3]). +-export([rollback_batch/3]). + -export([calculate_shard_id/2]). -export([calculate_time_range/2]). -export([mk_scope_prefix/2]). @@ -99,6 +105,8 @@ -type lim_change() :: limproto_limiter_thrift:'LimitChange'(). -type limit() :: limproto_limiter_thrift:'Limit'(). -type timestamp() :: dmsl_base_thrift:'Timestamp'(). +-type operation_id() :: limproto_limiter_thrift:'OperationID'(). +-type lim_changes() :: [lim_change()]. -export_type([config/0]). -export_type([limit_type/0]). @@ -140,8 +148,16 @@ %% Handler behaviour +-callback make_change( + Stage :: lim_turnover_metric:stage(), + LimitChange :: lim_change(), + Config :: config(), + LimitContext :: lim_context() +) -> {ok, lim_liminator:limit_change()} | {error, make_change_error()}. + -callback get_limit( ID :: lim_id(), + Version :: lim_version(), Config :: config(), LimitContext :: lim_context() ) -> {ok, limit()} | {error, get_limit_error()}. @@ -164,6 +180,7 @@ LimitContext :: lim_context() ) -> ok | {error, rollback_error()}. +-type make_change_error() :: lim_turnover_processor:make_change_error(). -type get_limit_error() :: lim_turnover_processor:get_limit_error(). -type hold_error() :: lim_turnover_processor:hold_error(). -type commit_error() :: lim_turnover_processor:commit_error(). @@ -260,7 +277,7 @@ get(ID, LimitContext) -> get_limit(ID, Version, LimitContext) -> do(fun() -> {Handler, Config} = unwrap(get_handler(ID, Version, LimitContext)), - unwrap(Handler, Handler:get_limit(ID, Config, LimitContext)) + unwrap(Handler, Handler:get_limit(ID, Version, Config, LimitContext)) end). -spec hold(lim_change(), lim_context()) -> ok | {error, config_error() | {processor(), hold_error()}}. @@ -284,6 +301,64 @@ rollback(LimitChange = #limiter_LimitChange{id = ID, version = Version}, LimitCo unwrap(Handler, Handler:rollback(LimitChange, Config, LimitContext)) end). +-spec get_values(lim_changes(), lim_context()) -> + {ok, [lim_liminator:limit_response()]} | {error, config_error() | {processor(), get_limit_error()}}. +get_values(LimitChanges, LimitContext) -> + do(fun() -> + Changes = unwrap(collect_changes(hold, LimitChanges, LimitContext)), + Names = lists:map(fun lim_liminator:get_name/1, Changes), + unwrap(lim_liminator:get_values(Names, LimitContext)) + end). + +-spec get_batch(operation_id(), lim_changes(), lim_context()) -> + {ok, [lim_liminator:limit_response()]} | {error, config_error() | {processor(), get_limit_error()}}. +get_batch(OperationID, LimitChanges, LimitContext) -> + do(fun() -> + unwrap( + OperationID, + lim_liminator:get(OperationID, unwrap(collect_changes(hold, LimitChanges, LimitContext)), LimitContext) + ) + end). + +-spec hold_batch(operation_id(), lim_changes(), lim_context()) -> + {ok, [lim_liminator:limit_response()]} | {error, config_error() | {processor(), hold_error()}}. +hold_batch(OperationID, LimitChanges, LimitContext) -> + do(fun() -> + unwrap( + OperationID, + lim_liminator:hold(OperationID, unwrap(collect_changes(hold, LimitChanges, LimitContext)), LimitContext) + ) + end). + +-spec commit_batch(operation_id(), lim_changes(), lim_context()) -> + ok | {error, config_error() | {processor(), hold_error()}}. +commit_batch(OperationID, LimitChanges, LimitContext) -> + do(fun() -> + unwrap( + OperationID, + lim_liminator:commit(OperationID, unwrap(collect_changes(commit, LimitChanges, LimitContext)), LimitContext) + ) + end). + +-spec rollback_batch(operation_id(), lim_changes(), lim_context()) -> + ok | {error, config_error() | {processor(), hold_error()}}. +rollback_batch(OperationID, LimitChanges, LimitContext) -> + do(fun() -> + unwrap( + OperationID, + lim_liminator:rollback(OperationID, unwrap(collect_changes(hold, LimitChanges, LimitContext)), LimitContext) + ) + end). + +collect_changes(_Stage, [], _LimitContext) -> + {ok, []}; +collect_changes(Stage, [LimitChange = #limiter_LimitChange{id = ID, version = Version} | Other], LimitContext) -> + do(fun() -> + {Handler, Config} = unwrap(get_handler(ID, Version, LimitContext)), + Change = unwrap(Handler, Handler:make_change(Stage, LimitChange, Config, LimitContext)), + [Change | unwrap(collect_changes(Stage, Other, LimitContext))] + end). + get_handler(ID, Version, LimitContext) -> do(fun() -> Config = #{processor_type := ProcessorType} = unwrap(config, get_config(ID, Version, LimitContext)), @@ -558,25 +633,34 @@ mk_shard_id(Prefix, Units, ShardSize) -> ID = integer_to_binary(abs(Units) div ShardSize), <>. --spec mk_scope_prefix(config(), lim_context()) -> {ok, prefix()} | {error, lim_context:context_error()}. +-spec mk_scope_prefix(config(), lim_context()) -> + {ok, {prefix(), lim_context:change_context()}} | {error, lim_context:context_error()}. mk_scope_prefix(Config, LimitContext) -> mk_scope_prefix_impl(scope(Config), context_type(Config), LimitContext). -spec mk_scope_prefix_impl(limit_scope(), context_type(), lim_context()) -> - {ok, prefix()} | {error, lim_context:context_error()}. + {ok, {prefix(), lim_context:change_context()}} | {error, lim_context:context_error()}. mk_scope_prefix_impl(Scope, ContextType, LimitContext) -> do(fun() -> Bits = enumerate_context_bits(Scope), lists:foldl( - fun(Bit, Acc) -> + fun(Bit, {AccPrefix, Map}) -> + BinaryBit = encode_bit(Bit), Value = unwrap(extract_context_bit(Bit, ContextType, LimitContext)), - append_prefix(Value, Acc) + {append_prefix(Value, AccPrefix), Map#{<<"Scope.", BinaryBit/binary>> => Value}} end, - <<>>, + {<<>>, #{}}, Bits ) end). +encode_bit({prefix, _Prefix}) -> + genlib:to_binary(prefix); +encode_bit({from, {destination_field, FieldPath}}) -> + lim_string:join($., [<<"destination_field">>] ++ FieldPath); +encode_bit({from, BitType}) -> + genlib:to_binary(BitType). + -spec append_prefix(binary(), prefix()) -> prefix(). append_prefix(Fragment, Acc) -> <>. @@ -997,22 +1081,22 @@ check_calculate_year_shard_id_test() -> -spec global_scope_empty_prefix_test() -> _. global_scope_empty_prefix_test() -> Context = #{context => ?PAYPROC_CTX_INVOICE(?INVOICE(<<"OWNER">>, <<"SHOP">>))}, - ?assertEqual({ok, <<>>}, mk_scope_prefix_impl(ordsets:new(), payment_processing, Context)). + ?assertEqual({ok, {<<>>, #{}}}, mk_scope_prefix_impl(ordsets:new(), payment_processing, Context)). -spec preserve_scope_prefix_order_test_() -> [_TestGen]. preserve_scope_prefix_order_test_() -> Context = #{context => ?PAYPROC_CTX_INVOICE(?INVOICE(<<"OWNER">>, <<"SHOP">>))}, [ ?_assertEqual( - {ok, <<"/OWNER/SHOP">>}, + {ok, {<<"/OWNER/SHOP">>, #{<<"Scope.owner_id">> => <<"OWNER">>, <<"Scope.shop_id">> => <<"SHOP">>}}}, mk_scope_prefix_impl(ordsets:from_list([shop, party]), payment_processing, Context) ), ?_assertEqual( - {ok, <<"/OWNER/SHOP">>}, + {ok, {<<"/OWNER/SHOP">>, #{<<"Scope.owner_id">> => <<"OWNER">>, <<"Scope.shop_id">> => <<"SHOP">>}}}, mk_scope_prefix_impl(ordsets:from_list([party, shop]), payment_processing, Context) ), ?_assertEqual( - {ok, <<"/OWNER/SHOP">>}, + {ok, {<<"/OWNER/SHOP">>, #{<<"Scope.owner_id">> => <<"OWNER">>, <<"Scope.shop_id">> => <<"SHOP">>}}}, mk_scope_prefix_impl(ordsets:from_list([shop]), payment_processing, Context) ) ]. @@ -1035,19 +1119,40 @@ prefix_content_test_() -> }, [ ?_assertEqual( - {ok, <<"/terminal/22/2">>}, + {ok, + {<<"/terminal/22/2">>, #{ + <<"Scope.prefix">> => <<"terminal">>, + <<"Scope.provider_id">> => <<"22">>, + <<"Scope.terminal_id">> => <<"2">> + }}}, mk_scope_prefix_impl(ordsets:from_list([terminal, provider]), payment_processing, Context) ), ?_assertEqual( - {ok, <<"/terminal/22/2">>}, + {ok, + {<<"/terminal/22/2">>, #{ + <<"Scope.prefix">> => <<"terminal">>, + <<"Scope.provider_id">> => <<"22">>, + <<"Scope.terminal_id">> => <<"2">> + }}}, mk_scope_prefix_impl(ordsets:from_list([provider, terminal]), payment_processing, Context) ), ?_assertEqual( - {ok, <<"/OWNER/wallet/IDENTITY/WALLET">>}, + {ok, + {<<"/OWNER/wallet/IDENTITY/WALLET">>, #{ + <<"Scope.identity_id">> => <<"IDENTITY">>, + <<"Scope.owner_id">> => <<"OWNER">>, + <<"Scope.prefix">> => <<"wallet">>, + <<"Scope.wallet_id">> => <<"WALLET">> + }}}, mk_scope_prefix_impl(ordsets:from_list([wallet, identity, party]), withdrawal_processing, WithdrawalContext) ), ?_assertEqual( - {ok, <<"/token/2/2022/payer_contact_email/email">>}, + {ok, + {<<"/token/2/2022/payer_contact_email/email">>, #{ + <<"Scope.payer_contact_email">> => <<"email">>, + <<"Scope.payment_tool">> => <<"token/2/2022">>, + <<"Scope.prefix">> => <<"payer_contact_email">> + }}}, mk_scope_prefix_impl(ordsets:from_list([payer_contact_email, payment_tool]), payment_processing, Context) ) ]. diff --git a/apps/limiter/src/lim_context.erl b/apps/limiter/src/lim_context.erl index 8fd0271a..230cd026 100644 --- a/apps/limiter/src/lim_context.erl +++ b/apps/limiter/src/lim_context.erl @@ -6,6 +6,7 @@ -export([create/1]). -export([woody_context/1]). -export([get_operation/2]). +-export([make_change_context/2]). -export([get_value/3]). -export([set_context/2]). @@ -24,6 +25,7 @@ }. -type context_type() :: payment_processing | withdrawal_processing. +-type change_context() :: #{binary() => binary()}. -type context_inner() :: lim_payproc_context:context() | lim_wthdproc_context:context(). -type context_operation() :: lim_payproc_context:operation() | lim_wthdproc_context:operation(). @@ -34,12 +36,14 @@ -export_type([t/0]). -export_type([context_type/0]). +-export_type([change_context/0]). -export_type([context_operation/0]). -export_type([unsupported_error/1]). -export_type([operation_context_not_supported_error/0]). -export_type([context_error/0]). -callback get_operation(context_inner()) -> {ok, context_operation()} | {error, notfound}. +-callback make_change_context(context_inner()) -> {ok, change_context()}. -callback get_value(_Name :: atom(), context_inner()) -> {ok, term()} | {error, notfound | unsupported_error(_)}. -spec create(woody_context()) -> t(). @@ -72,6 +76,14 @@ get_operation(Type, Context) -> {ok, Mod, OperationContext} -> Mod:get_operation(OperationContext) end. +-spec make_change_context(context_type(), t()) -> + {ok, change_context()} | {error, operation_context_not_supported_error()}. +make_change_context(Type, Context) -> + case get_operation_context(Type, Context) of + {error, _} = Error -> Error; + {ok, Mod, OperationContext} -> Mod:make_change_context(OperationContext) + end. + -spec get_value(context_type(), atom(), t()) -> {ok, term()} | {error, context_error()}. get_value(Type, ValueName, Context) -> case get_operation_context(Type, Context) of diff --git a/apps/limiter/src/lim_handler.erl b/apps/limiter/src/lim_handler.erl index c1cbcb0d..d09d4897 100644 --- a/apps/limiter/src/lim_handler.erl +++ b/apps/limiter/src/lim_handler.erl @@ -3,6 +3,7 @@ -include_lib("limiter_proto/include/limproto_limiter_thrift.hrl"). -include_lib("limiter_proto/include/limproto_base_thrift.hrl"). -include_lib("damsel/include/dmsl_base_thrift.hrl"). +-include_lib("liminator_proto/include/liminator_liminator_thrift.hrl"). %% Woody handler @@ -15,6 +16,7 @@ -type lim_context() :: lim_context:t(). -define(LIMIT_CHANGE(ID), #limiter_LimitChange{id = ID}). +-define(LIMIT_REQUEST(ID, Changes), #limiter_LimitRequest{operation_id = ID, limit_changes = Changes}). %% @@ -81,8 +83,91 @@ handle_function_('Rollback', {LimitChange = ?LIMIT_CHANGE(LimitID), Clock, Conte {ok, {vector, #limiter_VectorClock{state = <<>>}}}; {error, Error} -> handle_rollback_error(Error) + end; +handle_function_('GetValues', {?LIMIT_REQUEST(OperationID, Changes), Context}, LimitContext, _Opts) -> + scoper:add_meta(#{operation_id => OperationID}), + case + lim_config_machine:get_values( + Changes, + lim_context:set_context(Context, LimitContext) + ) + of + {ok, Responses} -> + {ok, convert_responses(Responses)}; + {error, Error} -> + handle_get_error(Error) + end; +handle_function_('GetBatch', {?LIMIT_REQUEST(OperationID, Changes), Context}, LimitContext, _Opts) -> + scoper:add_meta(#{operation_id => OperationID}), + case + lim_config_machine:get_batch( + OperationID, + Changes, + lim_context:set_context(Context, LimitContext) + ) + of + {ok, Responses} -> + {ok, convert_responses(Responses)}; + {error, Error} -> + handle_get_error(Error) + end; +handle_function_('HoldBatch', {?LIMIT_REQUEST(OperationID, Changes), Context}, LimitContext, _Opts) -> + scoper:add_meta(#{operation_id => OperationID}), + case + lim_config_machine:hold_batch( + OperationID, + Changes, + lim_context:set_context(Context, LimitContext) + ) + of + {ok, Responses} -> + {ok, convert_responses(Responses)}; + {error, Error} -> + handle_hold_error(Error) + end; +handle_function_('CommitBatch', {?LIMIT_REQUEST(OperationID, Changes), Context}, LimitContext, _Opts) -> + scoper:add_meta(#{operation_id => OperationID}), + case + lim_config_machine:commit_batch( + OperationID, + Changes, + lim_context:set_context(Context, LimitContext) + ) + of + ok -> + {ok, ok}; + {error, Error} -> + handle_commit_error(Error) + end; +handle_function_('RollbackBatch', {?LIMIT_REQUEST(OperationID, Changes), Context}, LimitContext, _Opts) -> + scoper:add_meta(#{operation_id => OperationID}), + case + lim_config_machine:rollback_batch( + OperationID, + Changes, + lim_context:set_context(Context, LimitContext) + ) + of + ok -> + {ok, ok}; + {error, Error} -> + handle_rollback_error(Error) end. +convert_responses([]) -> + []; +convert_responses([Response | Other]) -> + [convert_response(Response) | convert_responses(Other)]. + +convert_response(#liminator_LimitResponse{ + limit_id = LimitID, + total_value = Value +}) -> + #limiter_Limit{ + id = LimitID, + amount = Value + }. + -spec handle_get_error(_) -> no_return(). handle_get_error(Error) -> handle_default_error(Error). diff --git a/apps/limiter/src/lim_liminator.erl b/apps/limiter/src/lim_liminator.erl new file mode 100644 index 00000000..39706583 --- /dev/null +++ b/apps/limiter/src/lim_liminator.erl @@ -0,0 +1,92 @@ +-module(lim_liminator). + +-include_lib("liminator_proto/include/liminator_liminator_thrift.hrl"). + +-export([get_name/1]). +-export([construct_change/4]). + +-export([get_values/2]). +-export([get/3]). +-export([hold/3]). +-export([commit/3]). +-export([rollback/3]). + +-type operation_id() :: liminator_liminator_thrift:'OperationId'(). +-type limit_change() :: liminator_liminator_thrift:'LimitChange'(). +-type limit_name() :: liminator_liminator_thrift:'LimitName'(). +-type limit_id() :: liminator_liminator_thrift:'LimitId'(). +-type change_context() :: liminator_liminator_thrift:'Context'(). +-type limit_response() :: liminator_liminator_thrift:'LimitResponse'(). +-type amount() :: liminator_liminator_thrift:'Value'(). +-type lim_context() :: lim_context:t(). + +-type invalid_request_error() :: {invalid_request, list(binary())}. + +-export_type([amount/0]). +-export_type([invalid_request_error/0]). +-export_type([limit_response/0]). +-export_type([limit_change/0]). +-export_type([change_context/0]). + +-spec construct_change(limit_id(), limit_name(), amount(), change_context()) -> limit_change(). +construct_change(ID, Name, Value, ChangeContext) -> + #liminator_LimitChange{ + limit_id = ID, + limit_name = Name, + value = Value, + context = ChangeContext + }. + +-spec get_name(limit_change()) -> limit_name(). +get_name(#liminator_LimitChange{limit_name = Name}) -> + Name. + +-spec get_values([limit_name()], lim_context()) -> + {ok, [limit_response()]} | {error, invalid_request_error()}. +get_values(Names, LimitContext) -> + do('GetLastLimitsValues', Names, LimitContext). + +-spec get(operation_id(), [limit_change()], lim_context()) -> + {ok, [limit_response()]} | {error, invalid_request_error()}. +get(OperationID, Changes, LimitContext) -> + do('Get', #liminator_LimitRequest{operation_id = OperationID, limit_changes = Changes}, LimitContext). + +-spec hold(operation_id(), [limit_change()], lim_context()) -> + {ok, [limit_response()]} | {error, invalid_request_error()}. +hold(OperationID, Changes, LimitContext) -> + do('Hold', #liminator_LimitRequest{operation_id = OperationID, limit_changes = Changes}, LimitContext). + +-spec commit(operation_id(), [limit_change()], lim_context()) -> {ok, ok} | {error, invalid_request_error()}. +commit(OperationID, Changes, LimitContext) -> + do('Commit', #liminator_LimitRequest{operation_id = OperationID, limit_changes = Changes}, LimitContext). + +-spec rollback(operation_id(), [limit_change()], lim_context()) -> {ok, ok} | {error, invalid_request_error()}. +rollback(OperationID, Changes, LimitContext) -> + do('Rollback', #liminator_LimitRequest{operation_id = OperationID, limit_changes = Changes}, LimitContext). + +do(Op, Arg, LimitContext) -> + case call(Op, {Arg}, LimitContext) of + {ok, Result} -> + {ok, Result}; + {exception, Exception} -> + {error, {invalid_request, convert_exception(Exception)}} + end. + +%% +-spec call(woody:func(), woody:args(), lim_context()) -> woody:result(). +call(Function, Args, LimitContext) -> + WoodyContext = lim_context:woody_context(LimitContext), + lim_client_woody:call(liminator, Function, Args, WoodyContext). + +convert_exception(#liminator_OperationNotFound{}) -> + [<<"OperationNotFound">>]; +convert_exception(#liminator_LimitNotFound{}) -> + [<<"LimitNotFound">>]; +convert_exception(#liminator_OperationAlreadyInFinalState{}) -> + [<<"OperationAlreadyInFinalState">>]; +convert_exception(#liminator_DuplicateOperation{}) -> + [<<"DuplicateOperation">>]; +convert_exception(#liminator_DuplicateLimitName{}) -> + [<<"DuplicateLimitName">>]; +convert_exception(#liminator_LimitsValuesReadingException{}) -> + [<<"LimitsValuesReadingException">>]. diff --git a/apps/limiter/src/lim_payproc_context.erl b/apps/limiter/src/lim_payproc_context.erl index 90d68b5c..4d501595 100644 --- a/apps/limiter/src/lim_payproc_context.erl +++ b/apps/limiter/src/lim_payproc_context.erl @@ -6,6 +6,7 @@ -behaviour(lim_context). -export([get_operation/1]). +-export([make_change_context/1]). -export([get_value/2]). -type context() :: limproto_context_payproc_thrift:'Context'(). @@ -28,6 +29,21 @@ get_operation(#context_payproc_Context{op = {Operation, _}}) -> get_operation(#context_payproc_Context{op = undefined}) -> {error, notfound}. +-spec make_change_context(context()) -> {ok, lim_context:change_context()}. +make_change_context(#context_payproc_Context{op = undefined}) -> + {ok, #{}}; +make_change_context( + Context = #context_payproc_Context{ + op = {Operation, _} + } +) -> + {ok, + genlib_map:compact(#{ + <<"Context.op">> => genlib:to_binary(Operation), + <<"Context.owner_id">> => try_get_value(owner_id, Context, undefined), + <<"Context.shop_id">> => try_get_value(shop_id, Context, undefined) + })}. + -spec get_value(atom(), context()) -> {ok, term()} | {error, notfound | {unsupported, _}}. get_value(ValueName, Context) -> case get_operation(Context) of @@ -37,6 +53,19 @@ get_value(ValueName, Context) -> Error end. +try_get_value(ValueName, Context, Default) -> + case get_operation(Context) of + {ok, Operation} -> + case get_value(ValueName, Operation, Context) of + {ok, Value} -> + Value; + {error, _} -> + Default + end; + {error, _} -> + Default + end. + get_value(owner_id, _Operation, Context) -> get_owner_id(Context); get_value(shop_id, _Operation, Context) -> diff --git a/apps/limiter/src/lim_turnover_metric.erl b/apps/limiter/src/lim_turnover_metric.erl index fbb94c70..c2c94d15 100644 --- a/apps/limiter/src/lim_turnover_metric.erl +++ b/apps/limiter/src/lim_turnover_metric.erl @@ -10,6 +10,7 @@ -type invalid_operation_currency_error() :: {invalid_operation_currency, {currency(), currency()}}. -export_type([t/0]). +-export_type([stage/0]). -export_type([invalid_operation_currency_error/0]). %% diff --git a/apps/limiter/src/lim_turnover_processor.erl b/apps/limiter/src/lim_turnover_processor.erl index 02abc76c..d048dd53 100644 --- a/apps/limiter/src/lim_turnover_processor.erl +++ b/apps/limiter/src/lim_turnover_processor.erl @@ -4,7 +4,9 @@ -behaviour(lim_config_machine). --export([get_limit/3]). +-export([make_change/4]). + +-export([get_limit/4]). -export([hold/3]). -export([commit/3]). -export([rollback/3]). @@ -14,6 +16,7 @@ -type lim_change() :: lim_config_machine:lim_change(). -type limit() :: lim_config_machine:limit(). -type config() :: lim_config_machine:config(). +-type lim_version() :: dmsl_domain_thrift:'DataRevision'() | undefined. -type amount() :: integer(). -type forbidden_operation_amount_error() :: #{ @@ -23,6 +26,10 @@ }. -type get_limit_error() :: {range, notfound}. +-type make_change_error() :: + lim_rates:conversion_error() + | lim_context:operation_context_not_supported_error() + | lim_turnover_metric:invalid_operation_currency_error(). -type hold_error() :: lim_rates:conversion_error() @@ -40,6 +47,7 @@ lim_rates:conversion_error() | lim_accounting:invalid_request_error(). +-export_type([make_change_error/0]). -export_type([get_limit_error/0]). -export_type([hold_error/0]). -export_type([commit_error/0]). @@ -47,10 +55,20 @@ -import(lim_pipeline, [do/1, unwrap/1]). --spec get_limit(lim_id(), config(), lim_context()) -> {ok, limit()} | {error, get_limit_error()}. -get_limit(LimitID, Config, LimitContext) -> +-spec make_change(lim_turnover_metric:stage(), lim_change(), config(), lim_context()) -> + {ok, lim_liminator:limit_change()} | {error, make_change_error()}. +make_change(Stage, #limiter_LimitChange{id = LimitID, version = Version}, Config, LimitContext) -> + do(fun() -> + {LimitRangeID, ScopeChangeContext} = unwrap(compute_limit_range_id(LimitID, Version, Config, LimitContext)), + ChangeContext = unwrap(lim_context:make_change_context(lim_config_machine:context_type(Config), LimitContext)), + Metric = unwrap(compute_metric(Stage, Config, LimitContext)), + lim_liminator:construct_change(LimitID, LimitRangeID, Metric, maps:merge(ChangeContext, ScopeChangeContext)) + end). + +-spec get_limit(lim_id(), lim_version(), config(), lim_context()) -> {ok, limit()} | {error, get_limit_error()}. +get_limit(LimitID, Version, Config, LimitContext) -> do(fun() -> - {LimitRangeID, TimeRange} = unwrap(compute_limit_time_range_location(LimitID, Config, LimitContext)), + {LimitRangeID, TimeRange} = unwrap(compute_limit_time_range_location(LimitID, Version, Config, LimitContext)), Amount = find_range_balance_amount(LimitRangeID, TimeRange, LimitContext), #limiter_Limit{ id = LimitRangeID, @@ -69,18 +87,18 @@ find_range_balance_amount(LimitRangeID, TimeRange, LimitContext) -> end. -spec hold(lim_change(), config(), lim_context()) -> ok | {error, hold_error()}. -hold(LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) -> +hold(LimitChange = #limiter_LimitChange{id = LimitID, version = Version}, Config, LimitContext) -> do(fun() -> - TimeRangeAccount = unwrap(ensure_limit_time_range(LimitID, Config, LimitContext)), + TimeRangeAccount = unwrap(ensure_limit_time_range(LimitID, Version, Config, LimitContext)), Metric = unwrap(compute_metric(hold, Config, LimitContext)), Posting = lim_posting:new(TimeRangeAccount, Metric, currency(Config)), unwrap(lim_accounting:hold(construct_plan_id(LimitChange), {1, [Posting]}, LimitContext)) end). -spec commit(lim_change(), config(), lim_context()) -> ok | {error, commit_error()}. -commit(LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) -> +commit(LimitChange = #limiter_LimitChange{id = LimitID, version = Version}, Config, LimitContext) -> do(fun() -> - TimeRangeAccount = unwrap(ensure_limit_time_range(LimitID, Config, LimitContext)), + TimeRangeAccount = unwrap(ensure_limit_time_range(LimitID, Version, Config, LimitContext)), PlanID = construct_plan_id(LimitChange), Operations = construct_commit_plan(TimeRangeAccount, Config, LimitContext), ok = lists:foreach( @@ -102,26 +120,32 @@ commit(LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) - end). -spec rollback(lim_change(), config(), lim_context()) -> ok | {error, rollback_error()}. -rollback(LimitChange = #limiter_LimitChange{id = LimitID}, Config, LimitContext) -> +rollback(LimitChange = #limiter_LimitChange{id = LimitID, version = Version}, Config, LimitContext) -> do(fun() -> - TimeRangeAccount = unwrap(ensure_limit_time_range(LimitID, Config, LimitContext)), + TimeRangeAccount = unwrap(ensure_limit_time_range(LimitID, Version, Config, LimitContext)), Metric = unwrap(compute_metric(hold, Config, LimitContext)), Posting = lim_posting:new(TimeRangeAccount, Metric, currency(Config)), unwrap(lim_accounting:rollback(construct_plan_id(LimitChange), [{1, [Posting]}], LimitContext)) end). -compute_limit_time_range_location(LimitID, Config, LimitContext) -> +compute_limit_range_id(LimitID, Version, Config, LimitContext) -> do(fun() -> Timestamp = unwrap(get_timestamp(Config, LimitContext)), - LimitRangeID = unwrap(construct_range_id(LimitID, Timestamp, Config, LimitContext)), + unwrap(construct_range_id(Timestamp, LimitID, Version, Config, LimitContext)) + end). + +compute_limit_time_range_location(LimitID, Version, Config, LimitContext) -> + do(fun() -> + Timestamp = unwrap(get_timestamp(Config, LimitContext)), + {LimitRangeID, _} = unwrap(compute_limit_range_id(LimitID, Version, Config, LimitContext)), TimeRange = lim_config_machine:calculate_time_range(Timestamp, Config), {LimitRangeID, TimeRange} end). -ensure_limit_time_range(LimitID, Config, LimitContext) -> +ensure_limit_time_range(LimitID, Version, Config, LimitContext) -> do(fun() -> Timestamp = unwrap(get_timestamp(Config, LimitContext)), - {LimitRangeID, TimeRange} = unwrap(compute_limit_time_range_location(LimitID, Config, LimitContext)), + {LimitRangeID, TimeRange} = unwrap(compute_limit_time_range_location(LimitID, Version, Config, LimitContext)), CreateParams = genlib_map:compact(#{ id => LimitRangeID, type => lim_config_machine:time_range_type(Config), @@ -136,14 +160,22 @@ get_timestamp(Config, LimitContext) -> lim_context:get_value(ContextType, created_at, LimitContext). construct_plan_id(#limiter_LimitChange{change_id = ChangeID}) -> - % DISCUSS ChangeID. -construct_range_id(LimitID, Timestamp, Config, LimitContext) -> +construct_range_id(Timestamp, LimitID, undefined, Config, LimitContext) -> + case lim_config_machine:mk_scope_prefix(Config, LimitContext) of + {ok, {Prefix, ChangeContext}} -> + ShardID = lim_config_machine:calculate_shard_id(Timestamp, Config), + {ok, {<>, ChangeContext}}; + {error, _} = Error -> + Error + end; +construct_range_id(Timestamp, LimitID, Version, Config, LimitContext) -> + BinaryVersion = genlib:to_binary(Version), case lim_config_machine:mk_scope_prefix(Config, LimitContext) of - {ok, Prefix} -> + {ok, {Prefix, ChangeContext}} -> ShardID = lim_config_machine:calculate_shard_id(Timestamp, Config), - {ok, <>}; + {ok, {<>, ChangeContext}}; {error, _} = Error -> Error end. diff --git a/apps/limiter/src/lim_wthdproc_context.erl b/apps/limiter/src/lim_wthdproc_context.erl index f02414b4..f44a1e18 100644 --- a/apps/limiter/src/lim_wthdproc_context.erl +++ b/apps/limiter/src/lim_wthdproc_context.erl @@ -6,6 +6,7 @@ -behaviour(lim_context). -export([get_operation/1]). +-export([make_change_context/1]). -export([get_value/2]). -type context() :: limproto_context_withdrawal_thrift:'Context'(). @@ -24,6 +25,22 @@ get_operation(#context_withdrawal_Context{op = {Operation, _}}) -> get_operation(#context_withdrawal_Context{op = undefined}) -> {error, notfound}. +-spec make_change_context(context()) -> {ok, lim_context:change_context()}. +make_change_context(#context_withdrawal_Context{op = undefined}) -> + {ok, #{}}; +make_change_context( + Context = #context_withdrawal_Context{ + op = {Operation, _} + } +) -> + {ok, + genlib_map:compact(#{ + <<"Context.op">> => genlib:to_binary(Operation), + <<"Context.owner_id">> => try_get_value(owner_id, Context, undefined), + <<"Context.identity_id">> => try_get_value(identity_id, Context, undefined), + <<"Context.wallet_id">> => try_get_value(wallet_id, Context, undefined) + })}. + -spec get_value(atom(), context()) -> {ok, term()} | {error, notfound | {unsupported, _}}. get_value(ValueName, Context) -> case get_operation(Context) of @@ -33,6 +50,19 @@ get_value(ValueName, Context) -> Error end. +try_get_value(ValueName, Context, Default) -> + case get_operation(Context) of + {ok, Operation} -> + case get_value(ValueName, Operation, Context) of + {ok, Value} -> + Value; + {error, _} -> + Default + end; + {error, _} -> + Default + end. + get_value(owner_id, _Operation, Context) -> get_owner_id(Context); get_value(created_at, Operation, Context) -> diff --git a/apps/limiter/src/limiter.app.src b/apps/limiter/src/limiter.app.src index 2624ad9e..9355c8ce 100644 --- a/apps/limiter/src/limiter.app.src +++ b/apps/limiter/src/limiter.app.src @@ -10,6 +10,7 @@ scoper, % should be before any scoper event handler usage woody, limiter_proto, + liminator_proto, xrates_proto, machinery, erl_health, diff --git a/apps/limiter/test/lim_client.erl b/apps/limiter/test/lim_client.erl index 8159ad89..68a7d2a7 100644 --- a/apps/limiter/test/lim_client.erl +++ b/apps/limiter/test/lim_client.erl @@ -8,6 +8,12 @@ -export([commit/3]). -export([rollback/3]). +-export([get_values/3]). +-export([get_batch/3]). +-export([hold_batch/3]). +-export([commit_batch/3]). +-export([rollback_batch/3]). + -export([legacy_create_config/2]). -export([create_config/2]). -export([get_config/2]). @@ -17,6 +23,7 @@ -type limit_id() :: limproto_limiter_thrift:'LimitID'(). -type limit_version() :: limproto_limiter_thrift:'Version'() | undefined. -type limit_change() :: limproto_limiter_thrift:'LimitChange'(). +-type limit_request() :: limproto_limiter_thrift:'LimitRequest'(). -type limit_context() :: limproto_limiter_thrift:'LimitContext'(). -type clock() :: limproto_limiter_thrift:'Clock'(). -type legacy_create_params() :: limproto_configurator_thrift:'LimitCreateParams'(). @@ -46,6 +53,26 @@ commit(LimitChange, Context, Client) -> rollback(LimitChange, Context, Client) -> call('Rollback', {LimitChange, clock(), Context}, Client). +-spec get_values(limit_request(), limit_context(), client()) -> woody:result() | no_return(). +get_values(LimitRequest, Context, Client) -> + call('GetValues', {LimitRequest, Context}, Client). + +-spec get_batch(limit_request(), limit_context(), client()) -> woody:result() | no_return(). +get_batch(LimitRequest, Context, Client) -> + call('GetBatch', {LimitRequest, Context}, Client). + +-spec hold_batch(limit_request(), limit_context(), client()) -> woody:result() | no_return(). +hold_batch(LimitRequest, Context, Client) -> + call('HoldBatch', {LimitRequest, Context}, Client). + +-spec commit_batch(limit_request(), limit_context(), client()) -> woody:result() | no_return(). +commit_batch(LimitRequest, Context, Client) -> + call('CommitBatch', {LimitRequest, Context}, Client). + +-spec rollback_batch(limit_request(), limit_context(), client()) -> woody:result() | no_return(). +rollback_batch(LimitRequest, Context, Client) -> + call('RollbackBatch', {LimitRequest, Context}, Client). + %% -spec legacy_create_config(legacy_create_params(), client()) -> woody:result() | no_return(). diff --git a/apps/limiter/test/lim_configurator_SUITE.erl b/apps/limiter/test/lim_configurator_SUITE.erl index 3f3a0bea..6323b448 100644 --- a/apps/limiter/test/lim_configurator_SUITE.erl +++ b/apps/limiter/test/lim_configurator_SUITE.erl @@ -75,6 +75,9 @@ init_per_suite(Config) -> ]) ++ genlib_app:start_application_with(limiter, [ {service_clients, #{ + liminator => #{ + url => <<"http://liminator:8022/liminator/v1">> + }, accounter => #{ url => <<"http://shumway:8022/accounter">> }, diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index 98d85791..3ec2b58a 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -66,6 +66,19 @@ -export([commit_with_sender_receiver_scope_ok/1]). -export([commit_with_destination_field_scope_ok/1]). +-export([batch_hold_ok/1]). +-export([batch_commit_ok/1]). +-export([batch_rollback_ok/1]). +-export([two_batch_hold_ok/1]). +-export([two_batch_commit_ok/1]). +-export([two_batch_rollback_ok/1]). +-export([retry_batch_hold_ok/1]). +-export([batch_commit_less_ok/1]). +-export([batch_commit_more_ok/1]). +-export([batch_commit_negative_ok/1]). +-export([batch_commit_negative_less_ok/1]). +-export([batch_commit_negative_more_ok/1]). + -type group_name() :: atom(). -type test_case_name() :: atom(). @@ -121,7 +134,19 @@ groups() -> commit_multirange_limit_ok ]}, {default_with_dominant, [], [ - {group, base} + {group, base}, + batch_hold_ok, + batch_commit_ok, + batch_rollback_ok, + two_batch_hold_ok, + two_batch_commit_ok, + two_batch_rollback_ok, + retry_batch_hold_ok, + batch_commit_less_ok, + batch_commit_more_ok, + batch_commit_negative_ok, + batch_commit_negative_less_ok, + batch_commit_negative_more_ok ]}, {withdrawals, [parallel], [ get_limit_ok, @@ -184,6 +209,9 @@ init_per_suite(Config) -> ]) ++ genlib_app:start_application_with(limiter, [ {service_clients, #{ + liminator => #{ + url => <<"http://liminator:8022/liminator/v1">> + }, accounter => #{ url => <<"http://shumway:8022/accounter">> }, @@ -242,6 +270,8 @@ end_per_testcase(_Name, C) -> change_id = gen_change_id(ID, ChangeID), version = Version }). +-define(LIMIT_REQUEST(ID, Changes), #limiter_LimitRequest{operation_id = ID, limit_changes = Changes}). +-define(LIMIT(Amount), #limiter_Limit{amount = Amount}). -spec commit_with_long_change_id(config()) -> _. commit_with_long_change_id(C) -> @@ -816,6 +846,240 @@ commit_with_destination_field_scope_ok(C) -> %% +construct_request(C) -> + ID = ?config(id, C), + {ID0, Version0} = configure_limit( + ?time_range_month(), + ?scope([?scope_provider(), ?scope_payment_tool()]), + ?turnover_metric_amount(<<"RUB">>), + undefined, + genlib:format("~s/~B", [ID, 0]), + C + ), + {ID1, Version1} = configure_limit( + ?time_range_month(), + ?scope([?scope_provider(), ?scope_payment_tool()]), + ?turnover_metric_amount(<<"RUB">>), + undefined, + genlib:format("~s/~B", [ID, 1]), + C + ), + {ID2, Version2} = configure_limit( + ?time_range_month(), + ?scope([?scope_provider(), ?scope_payment_tool()]), + ?turnover_metric_amount(<<"RUB">>), + undefined, + genlib:format("~s/~B", [ID, 2]), + C + ), + ?LIMIT_REQUEST(ID, [ + ?LIMIT_CHANGE(ID0, 0, Version0), + ?LIMIT_CHANGE(ID1, 0, Version1), + ?LIMIT_CHANGE(ID2, 0, Version2) + ]). + +-spec batch_hold_ok(config()) -> _. +batch_hold_ok(C) -> + Context = + case get_group_name(C) of + withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10)); + _Default -> ?payproc_ctx_payment(?cash(10), ?cash(10)) + end, + Request = construct_request(C), + ok = hold_and_assert_batch(10, Request, Context, C). + +-spec batch_commit_ok(config()) -> _. +batch_commit_ok(C) -> + Context = + case get_group_name(C) of + withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10)); + _Default -> ?payproc_ctx_payment(?cash(10), ?cash(10)) + end, + Request = construct_request(C), + ok = hold_and_assert_batch(10, Request, Context, C), + {ok, ok} = lim_client:commit_batch(Request, Context, ?config(client, C)), + ok = assert_values(10, Request, Context, C). + +-spec batch_rollback_ok(config()) -> _. +batch_rollback_ok(C) -> + Context = + case get_group_name(C) of + withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10)); + _Default -> ?payproc_ctx_payment(?cash(10), ?cash(10)) + end, + Request = construct_request(C), + ok = hold_and_assert_batch(10, Request, Context, C), + {ok, ok} = lim_client:rollback_batch(Request, Context, ?config(client, C)), + ok = assert_values(0, Request, Context, C). + +-spec two_batch_hold_ok(config()) -> _. +two_batch_hold_ok(C) -> + Context = + case get_group_name(C) of + withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10)); + _Default -> ?payproc_ctx_payment(?cash(10), ?cash(10)) + end, + ?LIMIT_REQUEST(RequestID, Changes) = Request0 = construct_request(C), + Request1 = ?LIMIT_REQUEST(genlib:format("~s/~B", [RequestID, 1000]), Changes), + ok = hold_and_assert_batch(10, Request0, Context, C), + ok = hold_and_assert_batch(20, Request1, Context, C). + +-spec two_batch_commit_ok(config()) -> _. +two_batch_commit_ok(C) -> + Context = + case get_group_name(C) of + withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10)); + _Default -> ?payproc_ctx_payment(?cash(10), ?cash(10)) + end, + ?LIMIT_REQUEST(RequestID, Changes) = Request0 = construct_request(C), + Request1 = ?LIMIT_REQUEST(genlib:format("~s/~B", [RequestID, 1000]), Changes), + ok = hold_and_assert_batch(10, Request0, Context, C), + {ok, ok} = lim_client:commit_batch(Request0, Context, ?config(client, C)), + ok = hold_and_assert_batch(20, Request1, Context, C), + {ok, ok} = lim_client:commit_batch(Request1, Context, ?config(client, C)), + ok = assert_values(20, Request1, Context, C). + +-spec two_batch_rollback_ok(config()) -> _. +two_batch_rollback_ok(C) -> + Context = + case get_group_name(C) of + withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10)); + _Default -> ?payproc_ctx_payment(?cash(10), ?cash(10)) + end, + ?LIMIT_REQUEST(RequestID, Changes) = Request0 = construct_request(C), + Request1 = ?LIMIT_REQUEST(genlib:format("~s/~B", [RequestID, 1000]), Changes), + ok = hold_and_assert_batch(10, Request0, Context, C), + ok = hold_and_assert_batch(20, Request1, Context, C), + {ok, ok} = lim_client:rollback_batch(Request0, Context, ?config(client, C)), + ok = assert_values(10, Request1, Context, C), + {ok, ok} = lim_client:rollback_batch(Request1, Context, ?config(client, C)), + ok = assert_values(0, Request1, Context, C). + +-spec retry_batch_hold_ok(config()) -> _. +retry_batch_hold_ok(C) -> + Context = + case get_group_name(C) of + withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10)); + _Default -> ?payproc_ctx_payment(?cash(10), ?cash(10)) + end, + ?LIMIT_REQUEST(RequestID, Changes) = Request0 = construct_request(C), + Request1 = ?LIMIT_REQUEST(genlib:format("~s/~B", [RequestID, 1000]), Changes), + Request2 = ?LIMIT_REQUEST(genlib:format("~s/~B", [RequestID, 2000]), Changes), + ok = hold_and_assert_batch(10, Request0, Context, C), + ok = assert_batch(10, Request0, Context, C), + ok = hold_and_assert_batch(20, Request1, Context, C), + ok = assert_batch(10, Request0, Context, C), + ok = assert_batch(20, Request1, Context, C), + ok = hold_and_assert_batch(30, Request2, Context, C), + ok = assert_batch(10, Request0, Context, C), + ok = assert_batch(20, Request1, Context, C), + ok = assert_batch(30, Request2, Context, C), + {ok, ok} = lim_client:commit_batch(Request2, Context, ?config(client, C)), + ok = assert_values(30, Request1, Context, C), + ok = assert_batch(10, Request0, Context, C), + ok = assert_batch(20, Request1, Context, C), + {ok, ok} = lim_client:rollback_batch(Request1, Context, ?config(client, C)), + ok = assert_values(20, Request1, Context, C), + ok = assert_batch(10, Request0, Context, C), + {ok, ok} = lim_client:commit_batch(Request0, Context, ?config(client, C)), + ok = assert_values(20, Request1, Context, C). + +-spec batch_commit_less_ok(config()) -> _. +batch_commit_less_ok(C) -> + Cost = ?cash(1000, <<"RUB">>), + CaptureCost = ?cash(800, <<"RUB">>), + Context = + case get_group_name(C) of + withdrawals -> ?wthdproc_ctx_withdrawal(Cost); + _Default -> ?payproc_ctx_payment(Cost, CaptureCost) + end, + Request = construct_request(C), + ok = hold_and_assert_batch(1000, Request, Context, C), + {ok, ok} = lim_client:commit_batch(Request, Context, ?config(client, C)), + ok = assert_values(800, Request, Context, C). + +-spec batch_commit_more_ok(config()) -> _. +batch_commit_more_ok(C) -> + Cost = ?cash(1000, <<"RUB">>), + CaptureCost = ?cash(1200, <<"RUB">>), + Context = + case get_group_name(C) of + withdrawals -> ?wthdproc_ctx_withdrawal(Cost); + _Default -> ?payproc_ctx_payment(Cost, CaptureCost) + end, + Request = construct_request(C), + ok = hold_and_assert_batch(1000, Request, Context, C), + {exception, #base_InvalidRequest{errors = [<<"OperationNotFound">>]}} = lim_client:commit_batch( + Request, Context, ?config(client, C) + ). + +-spec batch_commit_negative_ok(config()) -> _. +batch_commit_negative_ok(C) -> + Cost = ?cash(-1000, <<"RUB">>), + CaptureCost = ?cash(-1000, <<"RUB">>), + Context = + case get_group_name(C) of + withdrawals -> ?wthdproc_ctx_withdrawal(Cost); + _Default -> ?payproc_ctx_payment(Cost, CaptureCost) + end, + Request = construct_request(C), + ok = hold_and_assert_batch(-1000, Request, Context, C), + {ok, ok} = lim_client:commit_batch(Request, Context, ?config(client, C)), + ok = assert_values(-1000, Request, Context, C). + +-spec batch_commit_negative_less_ok(config()) -> _. +batch_commit_negative_less_ok(C) -> + Cost = ?cash(-1000, <<"RUB">>), + CaptureCost = ?cash(-800, <<"RUB">>), + Context = + case get_group_name(C) of + withdrawals -> ?wthdproc_ctx_withdrawal(Cost); + _Default -> ?payproc_ctx_payment(Cost, CaptureCost) + end, + Request = construct_request(C), + ok = hold_and_assert_batch(-1000, Request, Context, C), + {ok, ok} = lim_client:commit_batch(Request, Context, ?config(client, C)), + ok = assert_values(-800, Request, Context, C). + +-spec batch_commit_negative_more_ok(config()) -> _. +batch_commit_negative_more_ok(C) -> + Cost = ?cash(-1000, <<"RUB">>), + CaptureCost = ?cash(-1200, <<"RUB">>), + Context = + case get_group_name(C) of + withdrawals -> ?wthdproc_ctx_withdrawal(Cost); + _Default -> ?payproc_ctx_payment(Cost, CaptureCost) + end, + Request = construct_request(C), + ok = hold_and_assert_batch(-1000, Request, Context, C), + {exception, #base_InvalidRequest{errors = [<<"OperationNotFound">>]}} = lim_client:commit_batch( + Request, Context, ?config(client, C) + ). + +%% + +hold_and_assert_batch(Value, Request0, Context, C) -> + {ok, [LimitState0 | [LimitState1 | [LimitState2]]]} = lim_client:hold_batch(Request0, Context, ?config(client, C)), + ?assertEqual(Value, LimitState0#limiter_Limit.amount), + ?assertEqual(Value, LimitState1#limiter_Limit.amount), + ?assertEqual(Value, LimitState2#limiter_Limit.amount), + {ok, [LimitState0 | [LimitState1 | [LimitState2]]]} = lim_client:get_values(Request0, Context, ?config(client, C)), + ok. + +assert_batch(BatchValue, Request0, Context, C) -> + {ok, [LimitState0 | [LimitState1 | [LimitState2]]]} = lim_client:get_batch(Request0, Context, ?config(client, C)), + ?assertEqual(BatchValue, LimitState0#limiter_Limit.amount), + ?assertEqual(BatchValue, LimitState1#limiter_Limit.amount), + ?assertEqual(BatchValue, LimitState2#limiter_Limit.amount), + ok. + +assert_values(Value, Request0, Context, C) -> + {ok, [LimitState0 | [LimitState1 | [LimitState2]]]} = lim_client:get_values(Request0, Context, ?config(client, C)), + ?assertEqual(Value, LimitState0#limiter_Limit.amount), + ?assertEqual(Value, LimitState1#limiter_Limit.amount), + ?assertEqual(Value, LimitState2#limiter_Limit.amount), + ok. + gen_change_id(LimitID, ChangeID) -> genlib:format("~s/~p", [LimitID, ChangeID]). @@ -836,7 +1100,9 @@ configure_limit(TimeRange, Scope, Metric, C) -> configure_limit(TimeRange, Scope, Metric, undefined, C). configure_limit(TimeRange, Scope, Metric, CurrencyConversion, C) -> - ID = ?config(id, C), + configure_limit(TimeRange, Scope, Metric, CurrencyConversion, ?config(id, C), C). + +configure_limit(TimeRange, Scope, Metric, CurrencyConversion, ID, C) -> ContextType = case get_group_name(C) of withdrawals -> ?ctx_type_wthdproc(); diff --git a/compose.yaml b/compose.yaml index a08a671c..70feb7b5 100644 --- a/compose.yaml +++ b/compose.yaml @@ -1,4 +1,3 @@ -version: '3' services: testrunner: @@ -21,6 +20,8 @@ services: condition: service_healthy shumway: condition: service_healthy + liminator: + condition: service_healthy dominant: image: ghcr.io/valitydev/dominant:sha-2150eea @@ -67,8 +68,38 @@ services: retries: 20 shumway-db: - image: docker.io/library/postgres:9.6 + image: docker.io/library/postgres:14.3 environment: - POSTGRES_DB=shumway - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres + + liminator: + image: ghcr.io/valitydev/liminator:sha-fc6546f + restart: unless-stopped + entrypoint: + - java + - -Xmx512m + - -jar + - /opt/liminator/liminator.jar + - --spring.datasource.url=jdbc:postgresql://liminator-db:5432/liminator + - --spring.datasource.username=vality + - --spring.datasource.password=postgres + - --spring.flyway.url=jdbc:postgresql://liminator-db:5432/liminator + - --spring.flyway.username=vality + - --spring.flyway.password=postgres + - --service.skipExistedHoldOps=false + depends_on: + - liminator-db + healthcheck: + test: "curl http://localhost:8022/actuator/health" + interval: 5s + timeout: 1s + retries: 20 + + liminator-db: + image: docker.io/library/postgres:14.3 + environment: + - POSTGRES_DB=liminator + - POSTGRES_USER=vality + - POSTGRES_PASSWORD=postgres diff --git a/elvis.config b/elvis.config index cbebd321..b0dc472d 100644 --- a/elvis.config +++ b/elvis.config @@ -8,8 +8,9 @@ ruleset => erl_files, rules => [ {elvis_text_style, line_length, #{limit => 120}}, - {elvis_style, god_modules, #{limit => 30}}, + {elvis_style, god_modules, #{limit => 30, ignore => [lim_config_machine]}}, {elvis_style, nesting_level, #{level => 3}}, + {elvis_style, dont_repeat_yourself, #{min_complexity => 20}}, {elvis_style, no_if_expression, disable} ] }, @@ -26,13 +27,7 @@ {elvis_style, used_ignored_variable, disable}, % Tests are usually more comprehensible when a bit more verbose. {elvis_style, dont_repeat_yourself, #{min_complexity => 20}}, - {elvis_style, god_modules, #{ - limit => 50, - ignore => [ - % NOTE Maybe refactor testsuite by moving helpers to according ct-modules - lim_turnover_SUITE - ] - }} + {elvis_style, god_modules, #{limit => 50, ignore => [lim_turnover_SUITE]}} ] }, #{ diff --git a/rebar.config b/rebar.config index 03bd0662..6168fe5d 100644 --- a/rebar.config +++ b/rebar.config @@ -30,6 +30,7 @@ {prometheus_cowboy, "0.1.8"}, {damsel, {git, "https://github.com/valitydev/damsel.git", {branch, "master"}}}, {limiter_proto, {git, "https://github.com/valitydev/limiter-proto.git", {branch, "master"}}}, + {liminator_proto, {git, "https://github.com/valitydev/liminator-proto.git", {branch, "master"}}}, {xrates_proto, {git, "https://github.com/valitydev/xrates-proto.git", {branch, "master"}}}, {machinery, {git, "https://github.com/valitydev/machinery-erlang.git", {branch, "master"}}}, {erl_health, {git, "https://github.com/valitydev/erlang-health.git", {branch, "master"}}}, diff --git a/rebar.lock b/rebar.lock index 827cd4e1..b1663804 100644 --- a/rebar.lock +++ b/rebar.lock @@ -37,9 +37,13 @@ {<<"hpack">>,{pkg,<<"hpack_erl">>,<<"0.2.3">>},3}, {<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},2}, {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},1}, + {<<"liminator_proto">>, + {git,"https://github.com/valitydev/liminator-proto.git", + {ref,"3870a390b3dd658e2c889b8e9590714e9bc8a0c9"}}, + 0}, {<<"limiter_proto">>, {git,"https://github.com/valitydev/limiter-proto.git", - {ref,"2483f600d6f00193a7e8493bcefd6654d14eaa6a"}}, + {ref,"970f197ce6c527fee5c45237ad2ce4b8820184a1"}}, 0}, {<<"machinery">>, {git,"https://github.com/valitydev/machinery-erlang.git", From 227109420acc861d5bb361ee241fa0a75b78b345 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Mon, 25 Nov 2024 14:07:40 +0300 Subject: [PATCH 43/54] Adds missing liminator client to default `sys.config` (#33) --- config/sys.config | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/sys.config b/config/sys.config index 95d8179e..6ba6a00b 100644 --- a/config/sys.config +++ b/config/sys.config @@ -11,6 +11,9 @@ } }}, {service_clients, #{ + liminator => #{ + url => <<"http://liminator:8022/liminator/v1">> + }, accounter => #{ url => <<"http://shumway:8022/accounter">> }, From a6f63ea574fefd5c979eaf3db7792ca314169d15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D0=B5=D0=BC?= Date: Tue, 10 Dec 2024 15:58:12 +0300 Subject: [PATCH 44/54] EMP-170: Generic cond bump (#34) --- rebar.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rebar.lock b/rebar.lock index b1663804..ec9df342 100644 --- a/rebar.lock +++ b/rebar.lock @@ -13,7 +13,7 @@ {<<"ctx">>,{pkg,<<"ctx">>,<<"0.6.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"7ed2112a6503abe9f65142e43dca6675e939d164"}}, + {ref,"81d1edce2043500e4581867da3f5f4c31e682f44"}}, 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git", From bcf48f3f14a97d5d1048db2797e1650cca716819 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Thu, 6 Feb 2025 14:12:51 +0300 Subject: [PATCH 45/54] TECH-76: Upgrades to Erlang/OTP 27 (#35) * TECH-76: Upgrades to Erlang/OTP 27 * Bumps deps --- .env | 4 +- .github/workflows/erlang-checks.yml | 4 +- apps/limiter/src/lim_body.erl | 2 +- apps/limiter/src/lim_client_woody.erl | 2 +- apps/limiter/src/lim_config_machine.erl | 14 ++--- apps/limiter/src/lim_maybe.erl | 12 ++-- apps/limiter/src/lim_payproc_context.erl | 4 +- apps/limiter/src/lim_posting.erl | 4 +- apps/limiter/src/lim_range_codec.erl | 4 +- apps/limiter/src/lim_range_machine.erl | 8 +-- apps/limiter/src/lim_turnover_metric.erl | 2 +- apps/limiter/src/lim_turnover_processor.erl | 6 +- apps/limiter/src/lim_wthdproc_context.erl | 4 +- apps/limiter/test/lim_turnover_SUITE.erl | 5 +- elvis.config | 11 +++- rebar.config | 14 ++--- rebar.lock | 66 +++++++++++++++------ 17 files changed, 99 insertions(+), 67 deletions(-) diff --git a/.env b/.env index 19340136..c3186585 100644 --- a/.env +++ b/.env @@ -1,4 +1,4 @@ SERVICE_NAME=limiter -OTP_VERSION=24.3.4 -REBAR_VERSION=3.18 +OTP_VERSION=27.1.2 +REBAR_VERSION=3.24 THRIFT_VERSION=0.14.2.3 diff --git a/.github/workflows/erlang-checks.yml b/.github/workflows/erlang-checks.yml index 436c8239..ecb75be3 100644 --- a/.github/workflows/erlang-checks.yml +++ b/.github/workflows/erlang-checks.yml @@ -18,7 +18,7 @@ jobs: thrift-version: ${{ steps.thrift-version.outputs.version }} steps: - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - run: grep -v '^#' .env >> $GITHUB_ENV - id: otp-version run: echo "::set-output name=version::$OTP_VERSION" @@ -30,7 +30,7 @@ jobs: run: name: Run checks needs: setup - uses: valitydev/erlang-workflows/.github/workflows/erlang-parallel-build.yml@v1.0.15 + uses: valitydev/erlang-workflows/.github/workflows/erlang-parallel-build.yml@v1.0.17 with: otp-version: ${{ needs.setup.outputs.otp-version }} rebar-version: ${{ needs.setup.outputs.rebar-version }} diff --git a/apps/limiter/src/lim_body.erl b/apps/limiter/src/lim_body.erl index c76a1ff0..e9223b91 100644 --- a/apps/limiter/src/lim_body.erl +++ b/apps/limiter/src/lim_body.erl @@ -47,5 +47,5 @@ apply_op_behaviour(Operation, Body, #{op_behaviour := ComputationConfig}) -> apply_op_behaviour(_Operation, Body, _Config) -> Body. -invert_body(Cash = #{amount := Amount}) -> +invert_body(#{amount := Amount} = Cash) -> Cash#{amount := -Amount}. diff --git a/apps/limiter/src/lim_client_woody.erl b/apps/limiter/src/lim_client_woody.erl index e91f0816..522db345 100644 --- a/apps/limiter/src/lim_client_woody.erl +++ b/apps/limiter/src/lim_client_woody.erl @@ -32,7 +32,7 @@ get_service_client_config(ServiceName) -> ServiceClients = genlib_app:env(?APP, service_clients, #{}), maps:get(ServiceName, ServiceClients, #{}). --spec get_service_client_url(atom()) -> lim_maybe:maybe(woody:url()). +-spec get_service_client_url(atom()) -> lim_maybe:'maybe'(woody:url()). get_service_client_url(ServiceName) -> maps:get(url, get_service_client_config(ServiceName), undefined). diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index 788dd302..4884c689 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -200,7 +200,7 @@ created_at(#{created_at := CreatedAt}) -> id(#{id := ID}) -> ID. --spec description(config()) -> lim_maybe:maybe(description()). +-spec description(config()) -> lim_maybe:'maybe'(description()). description(#{description := ID}) -> ID; description(_) -> @@ -238,7 +238,7 @@ scope(_) -> context_type(#{context_type := Value}) -> Value. --spec op_behaviour(config()) -> lim_maybe:maybe(op_behaviour()). +-spec op_behaviour(config()) -> lim_maybe:'maybe'(op_behaviour()). op_behaviour(#{op_behaviour := Value}) -> Value; op_behaviour(_) -> @@ -281,21 +281,21 @@ get_limit(ID, Version, LimitContext) -> end). -spec hold(lim_change(), lim_context()) -> ok | {error, config_error() | {processor(), hold_error()}}. -hold(LimitChange = #limiter_LimitChange{id = ID, version = Version}, LimitContext) -> +hold(#limiter_LimitChange{id = ID, version = Version} = LimitChange, LimitContext) -> do(fun() -> {Handler, Config} = unwrap(get_handler(ID, Version, LimitContext)), unwrap(Handler, Handler:hold(LimitChange, Config, LimitContext)) end). -spec commit(lim_change(), lim_context()) -> ok | {error, config_error() | {processor(), commit_error()}}. -commit(LimitChange = #limiter_LimitChange{id = ID, version = Version}, LimitContext) -> +commit(#limiter_LimitChange{id = ID, version = Version} = LimitChange, LimitContext) -> do(fun() -> {Handler, Config} = unwrap(get_handler(ID, Version, LimitContext)), unwrap(Handler, Handler:commit(LimitChange, Config, LimitContext)) end). -spec rollback(lim_change(), lim_context()) -> ok | {error, config_error() | {processor(), rollback_error()}}. -rollback(LimitChange = #limiter_LimitChange{id = ID, version = Version}, LimitContext) -> +rollback(#limiter_LimitChange{id = ID, version = Version} = LimitChange, LimitContext) -> do(fun() -> {Handler, Config} = unwrap(get_handler(ID, Version, LimitContext)), unwrap(Handler, Handler:rollback(LimitChange, Config, LimitContext)) @@ -836,11 +836,11 @@ not_implemented(What) -> %% --spec apply_event(machinery:event(timestamped_event(event())), lim_maybe:maybe(config())) -> config(). +-spec apply_event(machinery:event(timestamped_event(event())), lim_maybe:'maybe'(config())) -> config(). apply_event({_ID, _Ts, {ev, _EvTs, Event}}, Config) -> apply_event_(Event, Config). --spec apply_event_(event(), lim_maybe:maybe(config())) -> config(). +-spec apply_event_(event(), lim_maybe:'maybe'(config())) -> config(). apply_event_({created, Config}, undefined) -> Config. diff --git a/apps/limiter/src/lim_maybe.erl b/apps/limiter/src/lim_maybe.erl index f43cfafb..a14b143d 100644 --- a/apps/limiter/src/lim_maybe.erl +++ b/apps/limiter/src/lim_maybe.erl @@ -4,10 +4,10 @@ -module(lim_maybe). --type maybe(T) :: +-type 'maybe'(T) :: undefined | T. --export_type([maybe/1]). +-export_type(['maybe'/1]). -export([from_result/1]). -export([to_list/1]). @@ -18,13 +18,13 @@ %% --spec from_result({ok, T} | {error, _}) -> maybe(T). +-spec from_result({ok, T} | {error, _}) -> 'maybe'(T). from_result({ok, T}) -> T; from_result({error, _}) -> undefined. --spec to_list(maybe(T)) -> [T]. +-spec to_list('maybe'(T)) -> [T]. to_list(undefined) -> []; to_list(T) -> @@ -40,7 +40,7 @@ apply(Fun, Arg, _Default) when Arg =/= undefined -> apply(_Fun, undefined, Default) -> Default. --spec get_defined([maybe(T)]) -> T. +-spec get_defined(['maybe'(T)]) -> T. get_defined([]) -> erlang:error(badarg); get_defined([Value | _Tail]) when Value =/= undefined -> @@ -48,6 +48,6 @@ get_defined([Value | _Tail]) when Value =/= undefined -> get_defined([undefined | Tail]) -> get_defined(Tail). --spec get_defined(maybe(T), maybe(T)) -> T. +-spec get_defined('maybe'(T), 'maybe'(T)) -> T. get_defined(V1, V2) -> get_defined([V1, V2]). diff --git a/apps/limiter/src/lim_payproc_context.erl b/apps/limiter/src/lim_payproc_context.erl index 4d501595..9132dfd3 100644 --- a/apps/limiter/src/lim_payproc_context.erl +++ b/apps/limiter/src/lim_payproc_context.erl @@ -33,9 +33,9 @@ get_operation(#context_payproc_Context{op = undefined}) -> make_change_context(#context_payproc_Context{op = undefined}) -> {ok, #{}}; make_change_context( - Context = #context_payproc_Context{ + #context_payproc_Context{ op = {Operation, _} - } + } = Context ) -> {ok, genlib_map:compact(#{ diff --git a/apps/limiter/src/lim_posting.erl b/apps/limiter/src/lim_posting.erl index 966d20c6..c27a362e 100644 --- a/apps/limiter/src/lim_posting.erl +++ b/apps/limiter/src/lim_posting.erl @@ -18,14 +18,14 @@ new(#{account_id_from := From, account_id_to := To}, Amount, Currency) -> description = <<>> }). -reverse_negative_posting(Posting = #accounter_Posting{amount = Amount}) when Amount < 0 -> +reverse_negative_posting(#accounter_Posting{amount = Amount} = Posting) when Amount < 0 -> PostingReversed = reverse(Posting), PostingReversed#accounter_Posting{amount = -Amount}; reverse_negative_posting(Posting) -> Posting. -spec reverse(posting()) -> posting(). -reverse(Posting = #accounter_Posting{from_id = AccountFrom, to_id = AccountTo}) -> +reverse(#accounter_Posting{from_id = AccountFrom, to_id = AccountTo} = Posting) -> Posting#accounter_Posting{ from_id = AccountTo, to_id = AccountFrom diff --git a/apps/limiter/src/lim_range_codec.erl b/apps/limiter/src/lim_range_codec.erl index d6b24245..bccfa918 100644 --- a/apps/limiter/src/lim_range_codec.erl +++ b/apps/limiter/src/lim_range_codec.erl @@ -31,11 +31,11 @@ marshal(change, {time_range_created, TimeRange}) -> {time_range_created, #range_TimeRangeCreatedChange{time_range = marshal(time_range, TimeRange)}}; marshal( range, - Range = #{ + #{ id := ID, type := Type, created_at := CreatedAt - } + } = Range ) -> #range_LimitRange{ id = ID, diff --git a/apps/limiter/src/lim_range_machine.erl b/apps/limiter/src/lim_range_machine.erl index 332518e4..7dc8a336 100644 --- a/apps/limiter/src/lim_range_machine.erl +++ b/apps/limiter/src/lim_range_machine.erl @@ -123,7 +123,7 @@ get(ID, LimitContext) -> get_state(ID, lim_context:woody_context(LimitContext)). -spec ensure_exists(create_params(), time_range(), lim_context()) -> {ok, time_range_ext()}. -ensure_exists(Params = #{id := ID}, TimeRange, LimitContext) -> +ensure_exists(#{id := ID} = Params, TimeRange, LimitContext) -> WoodyCtx = lim_context:woody_context(LimitContext), case get_state(ID, WoodyCtx) of {ok, State} -> @@ -246,15 +246,15 @@ not_implemented(What) -> %% --spec apply_event(machinery:event(timestamped_event(event())), lim_maybe:maybe(limit_range_state())) -> +-spec apply_event(machinery:event(timestamped_event(event())), lim_maybe:'maybe'(limit_range_state())) -> limit_range_state(). apply_event({_ID, _Ts, {ev, _EvTs, Event}}, Config) -> apply_event_(Event, Config). --spec apply_event_(event(), lim_maybe:maybe(limit_range_state())) -> limit_range_state(). +-spec apply_event_(event(), lim_maybe:'maybe'(limit_range_state())) -> limit_range_state(). apply_event_({created, LimitRange}, undefined) -> LimitRange; -apply_event_({time_range_created, TimeRange}, LimitRange = #{ranges := Ranges}) -> +apply_event_({time_range_created, TimeRange}, #{ranges := Ranges} = LimitRange) -> LimitRange#{ranges => [TimeRange | Ranges]}; apply_event_({time_range_created, TimeRange}, LimitRange) -> LimitRange#{ranges => [TimeRange]}. diff --git a/apps/limiter/src/lim_turnover_metric.erl b/apps/limiter/src/lim_turnover_metric.erl index c2c94d15..228ef626 100644 --- a/apps/limiter/src/lim_turnover_metric.erl +++ b/apps/limiter/src/lim_turnover_metric.erl @@ -51,7 +51,7 @@ get_commit_body(Config, LimitContext) -> denominate(#{amount := Amount, currency := Currency}, Currency, _Config, _LimitContext) -> {ok, Amount}; -denominate(Body = #{currency := Currency}, DestinationCurrency, Config, LimitContext) -> +denominate(#{currency := Currency} = Body, DestinationCurrency, Config, LimitContext) -> case lim_config_machine:currency_conversion(Config) of false -> currencies_mismatch_error(Currency, DestinationCurrency); true -> convert_currency(Body, DestinationCurrency, Config, LimitContext) diff --git a/apps/limiter/src/lim_turnover_processor.erl b/apps/limiter/src/lim_turnover_processor.erl index d048dd53..6ae694cd 100644 --- a/apps/limiter/src/lim_turnover_processor.erl +++ b/apps/limiter/src/lim_turnover_processor.erl @@ -87,7 +87,7 @@ find_range_balance_amount(LimitRangeID, TimeRange, LimitContext) -> end. -spec hold(lim_change(), config(), lim_context()) -> ok | {error, hold_error()}. -hold(LimitChange = #limiter_LimitChange{id = LimitID, version = Version}, Config, LimitContext) -> +hold(#limiter_LimitChange{id = LimitID, version = Version} = LimitChange, Config, LimitContext) -> do(fun() -> TimeRangeAccount = unwrap(ensure_limit_time_range(LimitID, Version, Config, LimitContext)), Metric = unwrap(compute_metric(hold, Config, LimitContext)), @@ -96,7 +96,7 @@ hold(LimitChange = #limiter_LimitChange{id = LimitID, version = Version}, Config end). -spec commit(lim_change(), config(), lim_context()) -> ok | {error, commit_error()}. -commit(LimitChange = #limiter_LimitChange{id = LimitID, version = Version}, Config, LimitContext) -> +commit(#limiter_LimitChange{id = LimitID, version = Version} = LimitChange, Config, LimitContext) -> do(fun() -> TimeRangeAccount = unwrap(ensure_limit_time_range(LimitID, Version, Config, LimitContext)), PlanID = construct_plan_id(LimitChange), @@ -120,7 +120,7 @@ commit(LimitChange = #limiter_LimitChange{id = LimitID, version = Version}, Conf end). -spec rollback(lim_change(), config(), lim_context()) -> ok | {error, rollback_error()}. -rollback(LimitChange = #limiter_LimitChange{id = LimitID, version = Version}, Config, LimitContext) -> +rollback(#limiter_LimitChange{id = LimitID, version = Version} = LimitChange, Config, LimitContext) -> do(fun() -> TimeRangeAccount = unwrap(ensure_limit_time_range(LimitID, Version, Config, LimitContext)), Metric = unwrap(compute_metric(hold, Config, LimitContext)), diff --git a/apps/limiter/src/lim_wthdproc_context.erl b/apps/limiter/src/lim_wthdproc_context.erl index f44a1e18..595222f3 100644 --- a/apps/limiter/src/lim_wthdproc_context.erl +++ b/apps/limiter/src/lim_wthdproc_context.erl @@ -29,9 +29,9 @@ get_operation(#context_withdrawal_Context{op = undefined}) -> make_change_context(#context_withdrawal_Context{op = undefined}) -> {ok, #{}}; make_change_context( - Context = #context_withdrawal_Context{ + #context_withdrawal_Context{ op = {Operation, _} - } + } = Context ) -> {ok, genlib_map:compact(#{ diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index 3ec2b58a..27ac719d 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -271,7 +271,6 @@ end_per_testcase(_Name, C) -> version = Version }). -define(LIMIT_REQUEST(ID, Changes), #limiter_LimitRequest{operation_id = ID, limit_changes = Changes}). --define(LIMIT(Amount), #limiter_Limit{amount = Amount}). -spec commit_with_long_change_id(config()) -> _. commit_with_long_change_id(C) -> @@ -1122,10 +1121,10 @@ configure_limit(TimeRange, Scope, Metric, CurrencyConversion, ID, C) -> ConfigSource = proplists:get_value(limit_config_source, C, legacy), put_config_into_repository(ConfigSource, CreateParams, ?config(client, C)). -put_config_into_repository(legacy, CreateParams = #config_LimitConfigParams{id = ID}, Client) -> +put_config_into_repository(legacy, #config_LimitConfigParams{id = ID} = CreateParams, Client) -> {ok, _LimitConfig} = lim_client:create_config(CreateParams, Client), {ID, undefined}; -put_config_into_repository(repository, CreateParams = #config_LimitConfigParams{id = ID}, _Client) -> +put_config_into_repository(repository, #config_LimitConfigParams{id = ID} = CreateParams, _Client) -> LimitConfigObject = mk_limit_config_object(CreateParams), Version = dmt_client:insert({limit_config, LimitConfigObject}), {ID, Version}. diff --git a/elvis.config b/elvis.config index b0dc472d..9ab4b44b 100644 --- a/elvis.config +++ b/elvis.config @@ -11,7 +11,10 @@ {elvis_style, god_modules, #{limit => 30, ignore => [lim_config_machine]}}, {elvis_style, nesting_level, #{level => 3}}, {elvis_style, dont_repeat_yourself, #{min_complexity => 20}}, - {elvis_style, no_if_expression, disable} + {elvis_style, no_if_expression, disable}, + {elvis_style, no_throw, disable}, + {elvis_style, no_import, disable}, + {elvis_style, export_used_types, disable} ] }, #{ @@ -27,7 +30,8 @@ {elvis_style, used_ignored_variable, disable}, % Tests are usually more comprehensible when a bit more verbose. {elvis_style, dont_repeat_yourself, #{min_complexity => 20}}, - {elvis_style, god_modules, #{limit => 50, ignore => [lim_turnover_SUITE]}} + {elvis_style, god_modules, #{limit => 50, ignore => [lim_turnover_SUITE]}}, + {elvis_style, export_used_types, disable} ] }, #{ @@ -49,7 +53,8 @@ {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}, + {elvis_project, no_branch_deps, disable} ] }, #{ diff --git a/rebar.config b/rebar.config index 6168fe5d..780fc4d2 100644 --- a/rebar.config +++ b/rebar.config @@ -32,11 +32,11 @@ {limiter_proto, {git, "https://github.com/valitydev/limiter-proto.git", {branch, "master"}}}, {liminator_proto, {git, "https://github.com/valitydev/liminator-proto.git", {branch, "master"}}}, {xrates_proto, {git, "https://github.com/valitydev/xrates-proto.git", {branch, "master"}}}, - {machinery, {git, "https://github.com/valitydev/machinery-erlang.git", {branch, "master"}}}, + {machinery, {git, "https://github.com/valitydev/machinery-erlang.git", {tag, "v1.1.0"}}}, {erl_health, {git, "https://github.com/valitydev/erlang-health.git", {branch, "master"}}}, - {genlib, {git, "https://github.com/valitydev/genlib.git", {branch, "master"}}}, - {scoper, {git, "https://github.com/valitydev/scoper.git", {branch, "master"}}}, - {woody, {git, "https://github.com/valitydev/woody_erlang.git", {branch, "master"}}}, + {genlib, {git, "https://github.com/valitydev/genlib.git", {tag, "v1.1.0"}}}, + {scoper, {git, "https://github.com/valitydev/scoper.git", {tag, "v1.1.0"}}}, + {woody, {git, "https://github.com/valitydev/woody_erlang.git", {tag, "v1.1.0"}}}, {dmt_client, {git, "https://github.com/valitydev/dmt_client.git", {branch, "master"}}}, {opentelemetry_api, "1.2.1"}, {opentelemetry, "1.3.0"}, @@ -101,9 +101,9 @@ ]}. {project_plugins, [ - {rebar3_lint, "1.0.1"}, - {erlfmt, "1.0.0"}, - {covertool, "2.0.4"} + {rebar3_lint, "3.2.6"}, + {erlfmt, "1.5.0"}, + {covertool, "2.0.7"} ]}. %% Linter config. diff --git a/rebar.lock b/rebar.lock index ec9df342..b1520e5a 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,15 +1,21 @@ {"1.2.0", [{<<"accept">>,{pkg,<<"accept">>,<<"0.3.5">>},2}, {<<"acceptor_pool">>,{pkg,<<"acceptor_pool">>,<<"1.0.0">>},2}, + {<<"brod">>,{pkg,<<"brod">>,<<"4.3.2">>},2}, {<<"cache">>,{pkg,<<"cache">>,<<"2.3.3">>},1}, + {<<"canal">>, + {git,"https://github.com/valitydev/canal", + {ref,"621d3821cd0a6036fee75d8e3b2d17167f3268e4"}}, + 3}, {<<"certifi">>,{pkg,<<"certifi">>,<<"2.8.0">>},2}, {<<"cg_mon">>, {git,"https://github.com/rbkmoney/cg_mon.git", {ref,"5a87a37694e42b6592d3b4164ae54e0e87e24e18"}}, 1}, - {<<"chatterbox">>,{pkg,<<"ts_chatterbox">>,<<"0.13.0">>},2}, + {<<"chatterbox">>,{pkg,<<"ts_chatterbox">>,<<"0.15.1">>},2}, {<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.9.0">>},1}, {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.11.0">>},2}, + {<<"crc32cer">>,{pkg,<<"crc32cer">>,<<"0.1.11">>},4}, {<<"ctx">>,{pkg,<<"ctx">>,<<"0.6.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", @@ -23,31 +29,41 @@ {git,"https://github.com/valitydev/dmt-core.git", {ref,"19d8f57198f2cbe5b64aa4a923ba32774e505503"}}, 1}, + {<<"epg_connector">>, + {git,"https://github.com/valitydev/epg_connector.git", + {ref,"35a7480b298ac4318352a03824ce06619b75f9da"}}, + 2}, + {<<"epgsql">>, + {git,"https://github.com/epgsql/epgsql.git", + {ref,"7ba52768cf0ea7d084df24d4275a88eef4db13c2"}}, + 3}, {<<"erl_health">>, {git,"https://github.com/valitydev/erlang-health.git", {ref,"49716470d0e8dab5e37db55d52dea78001735a3d"}}, 0}, {<<"genlib">>, {git,"https://github.com/valitydev/genlib.git", - {ref,"f6074551d6586998e91a97ea20acb47241254ff3"}}, + {ref,"d2324089afbbd9630e85fac554620f1de0b33dfe"}}, 0}, {<<"gproc">>,{pkg,<<"gproc">>,<<"0.9.0">>},1}, - {<<"grpcbox">>,{pkg,<<"grpcbox">>,<<"0.16.0">>},1}, + {<<"grpcbox">>,{pkg,<<"grpcbox">>,<<"0.17.1">>},1}, {<<"hackney">>,{pkg,<<"hackney">>,<<"1.18.0">>},1}, - {<<"hpack">>,{pkg,<<"hpack_erl">>,<<"0.2.3">>},3}, + {<<"hpack">>,{pkg,<<"hpack_erl">>,<<"0.3.0">>},3}, {<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},2}, + {<<"jsone">>,{pkg,<<"jsone">>,<<"1.8.0">>},4}, {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},1}, + {<<"kafka_protocol">>,{pkg,<<"kafka_protocol">>,<<"4.1.10">>},3}, {<<"liminator_proto">>, {git,"https://github.com/valitydev/liminator-proto.git", {ref,"3870a390b3dd658e2c889b8e9590714e9bc8a0c9"}}, 0}, {<<"limiter_proto">>, {git,"https://github.com/valitydev/limiter-proto.git", - {ref,"970f197ce6c527fee5c45237ad2ce4b8820184a1"}}, + {ref,"efeb7d4a05bd13c95fada18514509b34b107fcb9"}}, 0}, {<<"machinery">>, {git,"https://github.com/valitydev/machinery-erlang.git", - {ref,"d62ceffbdb266bb4748bed34198e3575bba2dc73"}}, + {ref,"0ca82988ec310aceab7686c078c2a20fa6209cde"}}, 0}, {<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},2}, {<<"mg_proto">>, @@ -64,6 +80,10 @@ {pkg,<<"opentelemetry_semantic_conventions">>,<<"0.2.0">>}, 1}, {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.1">>},2}, + {<<"progressor">>, + {git,"https://github.com/valitydev/progressor.git", + {ref,"a67d4ddbcc3ddc3471e903d6d7291ca8e194906c"}}, + 1}, {<<"prometheus">>,{pkg,<<"prometheus">>,<<"4.8.1">>},0}, {<<"prometheus_cowboy">>,{pkg,<<"prometheus_cowboy">>,<<"0.1.8">>},0}, {<<"prometheus_httpd">>,{pkg,<<"prometheus_httpd">>,<<"2.1.11">>},1}, @@ -71,7 +91,7 @@ {<<"ranch">>,{pkg,<<"ranch">>,<<"1.8.0">>},2}, {<<"scoper">>, {git,"https://github.com/valitydev/scoper.git", - {ref,"55a2a32ee25e22fa35f583a18eaf38b2b743429b"}}, + {ref,"0e7aa01e9632daa39727edd62d4656ee715b4569"}}, 0}, {<<"snowflake">>, {git,"https://github.com/valitydev/snowflake.git", @@ -80,36 +100,40 @@ {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.7">>},2}, {<<"thrift">>, {git,"https://github.com/valitydev/thrift_erlang.git", - {ref,"c280ff266ae1c1906fb0dcee8320bb8d8a4a3c75"}}, + {ref,"3a60e5dc5bbd709495024f26e100b041c3547fd9"}}, 1}, {<<"tls_certificate_check">>, - {pkg,<<"tls_certificate_check">>,<<"1.19.0">>}, + {pkg,<<"tls_certificate_check">>,<<"1.25.0">>}, 1}, {<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},2}, {<<"woody">>, {git,"https://github.com/valitydev/woody_erlang.git", - {ref,"072825ee7179825a4078feb0649df71303c74157"}}, + {ref,"cc983a9423325ba1d6a509775eb6ff7ace721539"}}, 0}, {<<"xrates_proto">>, {git,"https://github.com/valitydev/xrates-proto.git", - {ref,"66906cd0a8ee9a00fb447f3c3e5b09d3c6fab942"}}, + {ref,"bb96f8c153f92a2065a414ae5261bad13ba16d2d"}}, 0}]}. [ {pkg_hash,[ {<<"accept">>, <<"B33B127ABCA7CC948BBE6CAA4C263369ABF1347CFA9D8E699C6D214660F10CD1">>}, {<<"acceptor_pool">>, <<"43C20D2ACAE35F0C2BCD64F9D2BDE267E459F0F3FD23DAB26485BF518C281B21">>}, + {<<"brod">>, <<"51F4DFF17ED43A806558EBD62CC88E7B35AED336D1BA1F3DE2D010F463D49736">>}, {<<"cache">>, <<"B23A5FE7095445A88412A6E614C933377E0137B44FFED77C9B3FEF1A731A20B2">>}, {<<"certifi">>, <<"D4FB0A6BB20B7C9C3643E22507E42F356AC090A1DCEA9AB99E27E0376D695EBA">>}, - {<<"chatterbox">>, <<"6F059D97BCAA758B8EA6FFFE2B3B81362BD06B639D3EA2BB088335511D691EBF">>}, + {<<"chatterbox">>, <<"5CAC4D15DD7AD61FC3C4415CE4826FC563D4643DEE897A558EC4EA0B1C835C9C">>}, {<<"cowboy">>, <<"865DD8B6607E14CF03282E10E934023A1BD8BE6F6BACF921A7E2A96D800CD452">>}, {<<"cowlib">>, <<"0B9FF9C346629256C42EBE1EEB769A83C6CB771A6EE5960BD110AB0B9B872063">>}, + {<<"crc32cer">>, <<"B550DA6D615FEB72A882D15D020F8F7DEE72DFB2CB1BCDF3B1EE8DC2AFD68CFC">>}, {<<"ctx">>, <<"8FF88B70E6400C4DF90142E7F130625B82086077A45364A78D208ED3ED53C7FE">>}, {<<"gproc">>, <<"853CCB7805E9ADA25D227A157BA966F7B34508F386A3E7E21992B1B484230699">>}, - {<<"grpcbox">>, <<"B83F37C62D6EECA347B77F9B1EC7E9F62231690CDFEB3A31BE07CD4002BA9C82">>}, + {<<"grpcbox">>, <<"6E040AB3EF16FE699FFB513B0EF8E2E896DA7B18931A1EF817143037C454BCCE">>}, {<<"hackney">>, <<"C4443D960BB9FBA6D01161D01CD81173089686717D9490E5D3606644C48D121F">>}, - {<<"hpack">>, <<"17670F83FF984AE6CD74B1C456EDDE906D27FF013740EE4D9EFAA4F1BF999633">>}, + {<<"hpack">>, <<"2461899CC4AB6A0EF8E970C1661C5FC6A52D3C25580BC6DD204F84CE94669926">>}, {<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>}, + {<<"jsone">>, <<"347FF1FA700E182E1F9C5012FA6D737B12C854313B9AE6954CA75D3987D6C06D">>}, {<<"jsx">>, <<"D12516BAA0BB23A59BB35DCCAF02A1BD08243FCBB9EFE24F2D9D056CCFF71268">>}, + {<<"kafka_protocol">>, <<"F917B6C90C8DF0DE2B40A87D6B9AE1CFCE7788E91A65818E90E40CF76111097A">>}, {<<"metrics">>, <<"25F094DEA2CDA98213CECC3AEFF09E940299D950904393B2A29D191C346A8486">>}, {<<"mimerl">>, <<"D0CD9FC04B9061F82490F6581E0128379830E78535E017F7780F37FEA7545726">>}, {<<"opentelemetry">>, <<"988AC3C26ACAC9720A1D4FB8D9DC52E95B45ECFEC2D5B5583276A09E8936BC5E">>}, @@ -123,23 +147,27 @@ {<<"quantile_estimator">>, <<"EF50A361F11B5F26B5F16D0696E46A9E4661756492C981F7B2229EF42FF1CD15">>}, {<<"ranch">>, <<"8C7A100A139FD57F17327B6413E4167AC559FBC04CA7448E9BE9057311597A1D">>}, {<<"ssl_verify_fun">>, <<"354C321CF377240C7B8716899E182CE4890C5938111A1296ADD3EC74CF1715DF">>}, - {<<"tls_certificate_check">>, <<"C76C4C5D79EE79A2B11C84F910C825D6F024A78427C854F515748E9BD025E987">>}, + {<<"tls_certificate_check">>, <<"702B1835FE718A52310509537392ABD067DBE941EBC05FE72409D2B2F8061651">>}, {<<"unicode_util_compat">>, <<"BC84380C9AB48177092F43AC89E4DFA2C6D62B40B8BD132B1059ECC7232F9A78">>}]}, {pkg_hash_ext,[ {<<"accept">>, <<"11B18C220BCC2EAB63B5470C038EF10EB6783BCB1FCDB11AA4137DEFA5AC1BB8">>}, {<<"acceptor_pool">>, <<"0CBCD83FDC8B9AD2EEE2067EF8B91A14858A5883CB7CD800E6FCD5803E158788">>}, + {<<"brod">>, <<"88584FDEBA746AA6729E2A1826416C10899954F68AF93659B3C2F38A2DCAA27C">>}, {<<"cache">>, <<"44516CE6FA03594D3A2AF025DD3A87BFE711000EB730219E1DDEFC816E0AA2F4">>}, {<<"certifi">>, <<"6AC7EFC1C6F8600B08D625292D4BBF584E14847CE1B6B5C44D983D273E1097EA">>}, - {<<"chatterbox">>, <<"B93D19104D86AF0B3F2566C4CBA2A57D2E06D103728246BA1AC6C3C0FF010AA7">>}, + {<<"chatterbox">>, <<"4F75B91451338BC0DA5F52F3480FA6EF6E3A2AEECFC33686D6B3D0A0948F31AA">>}, {<<"cowboy">>, <<"2C729F934B4E1AA149AFF882F57C6372C15399A20D54F65C8D67BEF583021BDE">>}, {<<"cowlib">>, <<"2B3E9DA0B21C4565751A6D4901C20D1B4CC25CBB7FD50D91D2AB6DD287BC86A9">>}, + {<<"crc32cer">>, <<"A39B8F0B1990AC1BF06C3A247FC6A178B740CDFC33C3B53688DC7DD6B1855942">>}, {<<"ctx">>, <<"A14ED2D1B67723DBEBBE423B28D7615EB0BDCBA6FF28F2D1F1B0A7E1D4AA5FC2">>}, {<<"gproc">>, <<"587E8AF698CCD3504CF4BA8D90F893EDE2B0F58CABB8A916E2BF9321DE3CF10B">>}, - {<<"grpcbox">>, <<"294DF743AE20A7E030889F00644001370A4F7CE0121F3BBDAF13CF3169C62913">>}, + {<<"grpcbox">>, <<"4A3B5D7111DAABC569DC9CBD9B202A3237D81C80BF97212FBC676832CB0CEB17">>}, {<<"hackney">>, <<"9AFCDA620704D720DB8C6A3123E9848D09C87586DC1C10479C42627B905B5C5E">>}, - {<<"hpack">>, <<"06F580167C4B8B8A6429040DF36CC93BBA6D571FAEAEC1B28816523379CBB23A">>}, + {<<"hpack">>, <<"D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0">>}, {<<"idna">>, <<"92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA">>}, + {<<"jsone">>, <<"08560B78624A12E0B5E7EC0271EC8CA38EF51F63D84D84843473E14D9B12618C">>}, {<<"jsx">>, <<"0C5CC8FDC11B53CC25CF65AC6705AD39E54ECC56D1C22E4ADB8F5A53FB9427F3">>}, + {<<"kafka_protocol">>, <<"DF680A3706EAD8695F8B306897C0A33E8063C690DA9308DB87B462CFD7029D04">>}, {<<"metrics">>, <<"69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16">>}, {<<"mimerl">>, <<"A1E15A50D1887217DE95F0B9B0793E32853F7C258A5CD227650889B38839FE9D">>}, {<<"opentelemetry">>, <<"8E09EDC26AAD11161509D7ECAD854A3285D88580F93B63B0B1CF0BAC332BFCC0">>}, @@ -153,6 +181,6 @@ {<<"quantile_estimator">>, <<"282A8A323CA2A845C9E6F787D166348F776C1D4A41EDE63046D72D422E3DA946">>}, {<<"ranch">>, <<"49FBCFD3682FAB1F5D109351B61257676DA1A2FDBE295904176D5E521A2DDFE5">>}, {<<"ssl_verify_fun">>, <<"FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8">>}, - {<<"tls_certificate_check">>, <<"4083B4A298ADD534C96125337CB01161C358BB32DD870D5A893AAE685FD91D70">>}, + {<<"tls_certificate_check">>, <<"167343CCF50538CF2FAF61A3F1460E749B3EDF2ECEF55516AF2B5834362ABCB1">>}, {<<"unicode_util_compat">>, <<"25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521">>}]} ]. From 08cdd0788740880c7285877e6c5d74b8cc3c1472 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D0=B5=D0=BC?= Date: Thu, 29 May 2025 16:33:50 +0300 Subject: [PATCH 46/54] Epic: Party here! (#36) * bumped damsel * fixed * bumped again * bumped compose * removed identity * bumped damsel * bumped * bumped dmt --- apps/limiter/src/lim_config_codec.erl | 4 ---- apps/limiter/src/lim_config_machine.erl | 23 +++++++++-------------- apps/limiter/src/lim_wthdproc_context.erl | 12 +----------- apps/limiter/test/lim_ct_helper.hrl | 8 +------- apps/limiter/test/lim_turnover_SUITE.erl | 11 +---------- compose.yaml | 2 +- rebar.config | 4 +--- rebar.lock | 4 ++-- 8 files changed, 16 insertions(+), 52 deletions(-) diff --git a/apps/limiter/src/lim_config_codec.erl b/apps/limiter/src/lim_config_codec.erl index 911591bc..76caa6fb 100644 --- a/apps/limiter/src/lim_config_codec.erl +++ b/apps/limiter/src/lim_config_codec.erl @@ -129,8 +129,6 @@ marshal_scope_type(shop) -> {shop, #config_LimitScopeEmptyDetails{}}; marshal_scope_type(wallet) -> {wallet, #config_LimitScopeEmptyDetails{}}; -marshal_scope_type(identity) -> - {identity, #config_LimitScopeEmptyDetails{}}; marshal_scope_type(payment_tool) -> {payment_tool, #config_LimitScopeEmptyDetails{}}; marshal_scope_type(provider) -> @@ -346,8 +344,6 @@ unmarshal_scope_type({shop, _}) -> shop; unmarshal_scope_type({wallet, _}) -> wallet; -unmarshal_scope_type({identity, _}) -> - identity; unmarshal_scope_type({payment_tool, _}) -> payment_tool; unmarshal_scope_type({provider, _}) -> diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index 4884c689..cfac8c9e 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -51,7 +51,6 @@ party | shop | wallet - | identity | payment_tool | provider | terminal @@ -675,7 +674,6 @@ enumerate_context_bits(Types) -> [ party, shop, - identity, wallet, payment_tool, provider, @@ -707,13 +705,13 @@ squash_scope_types([party, shop | Rest]) -> % NOTE % Shop scope implies party scope. [shop | squash_scope_types(Rest)]; -squash_scope_types([identity, wallet | Rest]) -> +squash_scope_types([party, wallet | Rest]) -> % NOTE - % Wallet scope implies identity scope. + % Wallet scope implies party scope. [wallet | squash_scope_types(Rest)]; squash_scope_types([provider, terminal | Rest]) -> % NOTE - % Provider scope implies identity scope. + % Provider scope implies provider scope. [terminal | squash_scope_types(Rest)]; squash_scope_types([Type | Rest]) -> [Type | squash_scope_types(Rest)]; @@ -729,10 +727,8 @@ get_context_bits(shop) -> [{from, owner_id}, {from, shop_id}]; get_context_bits(payment_tool) -> [{from, payment_tool}]; -get_context_bits(identity) -> - [{prefix, <<"identity">>}, {from, identity_id}]; get_context_bits(wallet) -> - [{prefix, <<"wallet">>}, {from, identity_id}, {from, wallet_id}]; + [{prefix, <<"wallet">>}, {from, owner_id}, {from, wallet_id}]; get_context_bits(provider) -> [{prefix, <<"provider">>}, {from, provider_id}]; get_context_bits(terminal) -> @@ -1066,9 +1062,9 @@ check_calculate_year_shard_id_test() -> } }). --define(WITHDRAWAL(OwnerID, IdentityID, PaymentTool), #wthd_domain_Withdrawal{ +-define(WITHDRAWAL(OwnerID, PaymentTool), #wthd_domain_Withdrawal{ destination = PaymentTool, - sender = #wthd_domain_Identity{id = IdentityID, owner_id = OwnerID}, + sender = OwnerID, created_at = <<"2000-02-02T12:12:12Z">>, body = #domain_Cash{amount = 42, currency = #domain_CurrencyRef{symbolic_code = <<"CNY">>}} }). @@ -1112,7 +1108,7 @@ prefix_content_test_() -> }, WithdrawalContext = #{ context => ?WITHDRAWAL_CTX( - ?WITHDRAWAL(<<"OWNER">>, <<"IDENTITY">>, ?PAYMENT_TOOL), + ?WITHDRAWAL(<<"OWNER">>, ?PAYMENT_TOOL), <<"WALLET">>, ?ROUTE(22, 2) ) @@ -1138,13 +1134,12 @@ prefix_content_test_() -> ), ?_assertEqual( {ok, - {<<"/OWNER/wallet/IDENTITY/WALLET">>, #{ - <<"Scope.identity_id">> => <<"IDENTITY">>, + {<<"/wallet/OWNER/WALLET">>, #{ <<"Scope.owner_id">> => <<"OWNER">>, <<"Scope.prefix">> => <<"wallet">>, <<"Scope.wallet_id">> => <<"WALLET">> }}}, - mk_scope_prefix_impl(ordsets:from_list([wallet, identity, party]), withdrawal_processing, WithdrawalContext) + mk_scope_prefix_impl(ordsets:from_list([wallet, party]), withdrawal_processing, WithdrawalContext) ), ?_assertEqual( {ok, diff --git a/apps/limiter/src/lim_wthdproc_context.erl b/apps/limiter/src/lim_wthdproc_context.erl index 595222f3..f9aad8e3 100644 --- a/apps/limiter/src/lim_wthdproc_context.erl +++ b/apps/limiter/src/lim_wthdproc_context.erl @@ -37,7 +37,6 @@ make_change_context( genlib_map:compact(#{ <<"Context.op">> => genlib:to_binary(Operation), <<"Context.owner_id">> => try_get_value(owner_id, Context, undefined), - <<"Context.identity_id">> => try_get_value(identity_id, Context, undefined), <<"Context.wallet_id">> => try_get_value(wallet_id, Context, undefined) })}. @@ -71,8 +70,6 @@ get_value(cost, Operation, Context) -> get_cost(Operation, Context); get_value(payment_tool, Operation, Context) -> get_payment_tool(Operation, Context); -get_value(identity_id, Operation, Context) -> - get_identity_id(Operation, Context); get_value(wallet_id, Operation, Context) -> get_wallet_id(Operation, Context); get_value(provider_id, Operation, Context) -> @@ -113,8 +110,7 @@ get_value(ValueName, _Operation, _Context) -> -define(SENDER_RECEIVER(V), ?AUTH_DATA({sender_receiver, V})). get_owner_id(?WITHDRAWAL(Wthd)) -> - Identity = Wthd#wthd_domain_Withdrawal.sender, - {ok, Identity#wthd_domain_Identity.owner_id}; + {ok, Wthd#wthd_domain_Withdrawal.sender}; get_owner_id(_CtxWithdrawal) -> {error, notfound}. @@ -135,12 +131,6 @@ get_payment_tool(withdrawal, ?WITHDRAWAL(Wthd)) -> get_payment_tool(_, _CtxWithdrawal) -> {error, notfound}. -get_identity_id(withdrawal, ?WITHDRAWAL(Wthd)) -> - Identity = Wthd#wthd_domain_Withdrawal.sender, - {ok, Identity#wthd_domain_Identity.id}; -get_identity_id(_, _CtxWithdrawal) -> - {error, notfound}. - get_wallet_id(withdrawal, ?WALLET_ID(WalletID)) -> {ok, WalletID}; get_wallet_id(_, _CtxWithdrawal) -> diff --git a/apps/limiter/test/lim_ct_helper.hrl b/apps/limiter/test/lim_ct_helper.hrl index d0e54b5a..46a175e5 100644 --- a/apps/limiter/test/lim_ct_helper.hrl +++ b/apps/limiter/test/lim_ct_helper.hrl @@ -38,7 +38,6 @@ -define(scope_provider(), {provider, #config_LimitScopeEmptyDetails{}}). -define(scope_terminal(), {terminal, #config_LimitScopeEmptyDetails{}}). -define(scope_payer_contact_email(), {payer_contact_email, #config_LimitScopeEmptyDetails{}}). --define(scope_identity(), {identity, #config_LimitScopeEmptyDetails{}}). -define(scope_wallet(), {wallet, #config_LimitScopeEmptyDetails{}}). -define(scope_sender(), {sender, #config_LimitScopeEmptyDetails{}}). -define(scope_receiver(), {receiver, #config_LimitScopeEmptyDetails{}}). @@ -231,11 +230,6 @@ %% Wthdproc --define(identity(OwnerID), #wthd_domain_Identity{ - id = OwnerID, - owner_id = OwnerID -}). - -define(auth_data(Sender, Receiver), {sender_receiver, #wthd_domain_SenderReceiverAuthData{sender = Sender, receiver = Receiver}} ). @@ -248,7 +242,7 @@ body = Body, created_at = ?timestamp, destination = Destination, - sender = ?identity(OwnerID), + sender = OwnerID, auth_data = AuthData }). diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index 27ac719d..eaeeac44 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -54,7 +54,6 @@ -export([commit_with_terminal_scope_ok/1]). -export([commit_with_email_scope_ok/1]). --export([commit_with_identity_scope_ok/1]). -export([commit_with_wallet_scope_ok/1]). -export([commit_with_multi_scope_ok/1]). -export([hold_with_sender_notfound/1]). @@ -156,7 +155,6 @@ groups() -> commit_with_party_scope_ok, commit_with_provider_scope_ok, commit_with_terminal_scope_ok, - commit_with_identity_scope_ok, commit_with_wallet_scope_ok, commit_with_sender_scope_ok, commit_with_receiver_scope_ok, @@ -714,16 +712,9 @@ commit_with_email_scope_ok(C) -> {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), {ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)). --spec commit_with_identity_scope_ok(config()) -> _. -commit_with_identity_scope_ok(C) -> - {ID, Version} = configure_limit(?time_range_month(), ?scope([?scope_identity()]), C), - Context = ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), - {ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)). - -spec commit_with_wallet_scope_ok(config()) -> _. commit_with_wallet_scope_ok(C) -> - {ID, Version} = configure_limit(?time_range_month(), ?scope([?scope_wallet()]), C), + {ID, Version} = configure_limit(?time_range_month(), ?scope([?scope_party(), ?scope_wallet()]), C), Context = ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)), {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), {ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)). diff --git a/compose.yaml b/compose.yaml index 70feb7b5..ab481360 100644 --- a/compose.yaml +++ b/compose.yaml @@ -24,7 +24,7 @@ services: condition: service_healthy dominant: - image: ghcr.io/valitydev/dominant:sha-2150eea + image: ghcr.io/valitydev/dominant:sha-ef03371-epic-party_here command: /opt/dominant/bin/dominant foreground depends_on: machinegun: diff --git a/rebar.config b/rebar.config index 780fc4d2..9d32db7f 100644 --- a/rebar.config +++ b/rebar.config @@ -69,12 +69,10 @@ % for introspection on production {recon, "2.5.2"}, {logger_logstash_formatter, - {git, "https://github.com/valitydev/logger_logstash_formatter.git", {ref, "08a66a6"}}}, - {iosetopts, {git, "https://github.com/valitydev/iosetopts.git", {ref, "edb445c"}}} + {git, "https://github.com/valitydev/logger_logstash_formatter.git", {ref, "08a66a6"}}} ]}, {relx, [ {release, {limiter, "1.0.0"}, [ - iosetopts, {recon, load}, {runtime_tools, load}, {tools, load}, diff --git a/rebar.lock b/rebar.lock index b1520e5a..35c9390b 100644 --- a/rebar.lock +++ b/rebar.lock @@ -19,7 +19,7 @@ {<<"ctx">>,{pkg,<<"ctx">>,<<"0.6.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"81d1edce2043500e4581867da3f5f4c31e682f44"}}, + {ref,"ab44b9db25a76a2c50545fd884e4cdf3d3e3b628"}}, 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git", @@ -59,7 +59,7 @@ 0}, {<<"limiter_proto">>, {git,"https://github.com/valitydev/limiter-proto.git", - {ref,"efeb7d4a05bd13c95fada18514509b34b107fcb9"}}, + {ref,"d4cdf0f6328125996ee705c3da87461f99dde7f4"}}, 0}, {<<"machinery">>, {git,"https://github.com/valitydev/machinery-erlang.git", From 8fd529e3883aa13ff73bc7eec26ee43038ac0532 Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Thu, 5 Jun 2025 17:10:20 +0300 Subject: [PATCH 47/54] Upgrades to dmt v2 #major (#37) * Upgrades to dmt v2 * Removes commit test with idenity scope * Updates dmt hostname * Bumps damsel & dmt-v2 * Bumps dmt v2 --- .github/workflows/tag-action.yml | 18 +++++ apps/limiter/src/lim_config_machine.erl | 8 +- apps/limiter/test/lim_configurator_SUITE.erl | 5 +- apps/limiter/test/lim_turnover_SUITE.erl | 21 ++++- compose.tracing.yaml | 15 ++++ compose.yaml | 65 ++++++++------- config/sys.config | 5 +- rebar.config | 24 ++---- rebar.lock | 80 +++++++++---------- test/dmt/sys.config | 74 +++++++++++++++++ test/machinegun/config.yaml | 10 --- .../create-multiple-postgresql-databases.sh | 25 ++++++ 12 files changed, 241 insertions(+), 109 deletions(-) create mode 100644 .github/workflows/tag-action.yml create mode 100644 test/dmt/sys.config create mode 100644 test/postgres/docker-entrypoint-initdb.d/create-multiple-postgresql-databases.sh diff --git a/.github/workflows/tag-action.yml b/.github/workflows/tag-action.yml new file mode 100644 index 00000000..ec3faf40 --- /dev/null +++ b/.github/workflows/tag-action.yml @@ -0,0 +1,18 @@ +name: Create Tag + +on: + push: + branches: + - master + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + + - uses: valitydev/action-tagger@v2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + with-v: true diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index cfac8c9e..b5a00d98 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -2,7 +2,7 @@ -include_lib("limiter_proto/include/limproto_limiter_thrift.hrl"). -include_lib("damsel/include/dmsl_domain_thrift.hrl"). --include_lib("damsel/include/dmsl_domain_conf_thrift.hrl"). +-include_lib("damsel/include/dmsl_domain_conf_v2_thrift.hrl"). %% Accessors @@ -371,11 +371,11 @@ get_config(ID, undefined, LimitContext) -> get_config(ID, Version, #{woody_context := WoodyContext}) -> LimitConfigRef = {limit_config, #domain_LimitConfigRef{id = ID}}, try - Object = dmt_client:checkout_versioned_object(Version, LimitConfigRef, #{woody_context => WoodyContext}), - #domain_conf_VersionedObject{object = {limit_config, ConfigObject}} = Object, + #domain_conf_v2_VersionedObject{object = {limit_config, ConfigObject}} = + dmt_client:checkout_object(Version, LimitConfigRef, #{woody_context => WoodyContext}), {ok, lim_config_codec:unmarshal('LimitConfigObject', ConfigObject)} catch - throw:#domain_conf_ObjectNotFound{} -> + throw:#domain_conf_v2_ObjectNotFound{} -> {error, notfound} end. diff --git a/apps/limiter/test/lim_configurator_SUITE.erl b/apps/limiter/test/lim_configurator_SUITE.erl index 6323b448..1c4d7c53 100644 --- a/apps/limiter/test/lim_configurator_SUITE.erl +++ b/apps/limiter/test/lim_configurator_SUITE.erl @@ -69,8 +69,9 @@ init_per_suite(Config) -> }} ]}, {service_urls, #{ - 'Repository' => <<"http://dominant:8022/v1/domain/repository">>, - 'RepositoryClient' => <<"http://dominant:8022/v1/domain/repository_client">> + 'AuthorManagement' => <<"http://dmt:8022/v1/domain/author">>, + 'Repository' => <<"http://dmt:8022/v1/domain/repository">>, + 'RepositoryClient' => <<"http://dmt:8022/v1/domain/repository_client">> }} ]) ++ genlib_app:start_application_with(limiter, [ diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index eaeeac44..6a06fd53 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -4,6 +4,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("damsel/include/dmsl_base_thrift.hrl"). -include_lib("damsel/include/dmsl_limiter_config_thrift.hrl"). +-include_lib("damsel/include/dmsl_domain_conf_v2_thrift.hrl"). -include("lim_ct_helper.hrl"). -export([all/0]). @@ -201,8 +202,9 @@ init_per_suite(Config) -> }} ]}, {service_urls, #{ - 'Repository' => <<"http://dominant:8022/v1/domain/repository">>, - 'RepositoryClient' => <<"http://dominant:8022/v1/domain/repository_client">> + 'AuthorManagement' => <<"http://dmt:8022/v1/domain/author">>, + 'Repository' => <<"http://dmt:8022/v1/domain/repository">>, + 'RepositoryClient' => <<"http://dmt:8022/v1/domain/repository_client">> }} ]) ++ genlib_app:start_application_with(limiter, [ @@ -1117,7 +1119,7 @@ put_config_into_repository(legacy, #config_LimitConfigParams{id = ID} = CreatePa {ID, undefined}; put_config_into_repository(repository, #config_LimitConfigParams{id = ID} = CreateParams, _Client) -> LimitConfigObject = mk_limit_config_object(CreateParams), - Version = dmt_client:insert({limit_config, LimitConfigObject}), + Version = dmt_client:insert({limit_config, LimitConfigObject}, ensure_stub_author()), {ID, Version}. gen_unique_id(Prefix) -> @@ -1190,3 +1192,16 @@ change_record_name_prefix(Record, OldPrefix, NewPrefix) -> [OldPrefix, TypeName] = string:split(RecordName, "_", trailing), NewRecordName = list_to_existing_atom(NewPrefix ++ "_" ++ TypeName), setelement(1, Record, NewRecordName). + +ensure_stub_author() -> + %% TODO DISCUSS Stubs and fallback authors + ensure_author(~b"unknown", ~b"unknown@local"). + +ensure_author(Name, Email) -> + try + #domain_conf_v2_Author{id = ID} = dmt_client:get_author_by_email(Email), + ID + catch + throw:#domain_conf_v2_AuthorNotFound{} -> + dmt_client:create_author(Name, Email) + end. diff --git a/compose.tracing.yaml b/compose.tracing.yaml index f712b3c5..2e8e4a70 100644 --- a/compose.tracing.yaml +++ b/compose.tracing.yaml @@ -1,5 +1,20 @@ services: + + dmt: + environment: &otlp_enabled + OTEL_TRACES_EXPORTER: otlp + OTEL_TRACES_SAMPLER: parentbased_always_off + OTEL_EXPORTER_OTLP_PROTOCOL: http_protobuf + OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4318 + + machinegun: + environment: *otlp_enabled + testrunner: + environment: + <<: *otlp_enabled + OTEL_SERVICE_NAME: limiter_testrunner + OTEL_TRACES_SAMPLER: parentbased_always_on depends_on: jaeger: condition: service_healthy diff --git a/compose.yaml b/compose.yaml index ab481360..c6ef112f 100644 --- a/compose.yaml +++ b/compose.yaml @@ -14,7 +14,7 @@ services: working_dir: $PWD command: /sbin/init depends_on: - dominant: + dmt: condition: service_healthy machinegun: condition: service_healthy @@ -23,17 +23,19 @@ services: liminator: condition: service_healthy - dominant: - image: ghcr.io/valitydev/dominant:sha-ef03371-epic-party_here - command: /opt/dominant/bin/dominant foreground + dmt: + image: ghcr.io/valitydev/dominant-v2:sha-109d2ea + command: /opt/dmt/bin/dmt foreground + healthcheck: + test: "/opt/dmt/bin/dmt ping" + interval: 5s + timeout: 3s + retries: 12 depends_on: - machinegun: + db: condition: service_healthy - healthcheck: - test: "/opt/dominant/bin/dominant ping" - interval: 10s - timeout: 5s - retries: 10 + volumes: + - ./test/dmt/sys.config:/opt/dmt/releases/0.1/sys.config machinegun: image: ghcr.io/valitydev/machinegun:sha-5c0db56 @@ -55,25 +57,19 @@ services: - -Xmx512m - -jar - /opt/shumway/shumway.jar - - --spring.datasource.url=jdbc:postgresql://shumway-db:5432/shumway - - --spring.datasource.username=postgres + - --spring.datasource.url=jdbc:postgresql://db:5432/shumway + - --spring.datasource.username=shumway - --spring.datasource.password=postgres - --management.metrics.export.statsd.enabled=false depends_on: - - shumway-db + db: + condition: service_healthy healthcheck: test: "curl http://localhost:8022/actuator/health" interval: 5s timeout: 1s retries: 20 - shumway-db: - image: docker.io/library/postgres:14.3 - environment: - - POSTGRES_DB=shumway - - POSTGRES_USER=postgres - - POSTGRES_PASSWORD=postgres - liminator: image: ghcr.io/valitydev/liminator:sha-fc6546f restart: unless-stopped @@ -82,24 +78,31 @@ services: - -Xmx512m - -jar - /opt/liminator/liminator.jar - - --spring.datasource.url=jdbc:postgresql://liminator-db:5432/liminator - - --spring.datasource.username=vality + - --spring.datasource.url=jdbc:postgresql://db:5432/liminator + - --spring.datasource.username=liminator - --spring.datasource.password=postgres - - --spring.flyway.url=jdbc:postgresql://liminator-db:5432/liminator - - --spring.flyway.username=vality - - --spring.flyway.password=postgres - --service.skipExistedHoldOps=false depends_on: - - liminator-db + db: + condition: service_healthy healthcheck: test: "curl http://localhost:8022/actuator/health" interval: 5s timeout: 1s retries: 20 - liminator-db: - image: docker.io/library/postgres:14.3 + db: + image: postgres:15-bookworm + command: -c 'max_connections=1000' environment: - - POSTGRES_DB=liminator - - POSTGRES_USER=vality - - POSTGRES_PASSWORD=postgres + POSTGRES_MULTIPLE_DATABASES: "dmt,shumway,liminator" + POSTGRES_PASSWORD: "postgres" + volumes: + - ./test/postgres/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d + healthcheck: + test: ["CMD-SHELL", "pg_isready -U hellgate"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + restart: unless-stopped diff --git a/config/sys.config b/config/sys.config index 6ba6a00b..30bf1c03 100644 --- a/config/sys.config +++ b/config/sys.config @@ -91,8 +91,9 @@ }} ]}, {service_urls, #{ - 'Repository' => <<"http://dominant:8022/v1/domain/repository">>, - 'RepositoryClient' => <<"http://dominant:8022/v1/domain/repository_client">> + 'AuthorManagement' => <<"http://dmt:8022/v1/domain/author">>, + 'Repository' => <<"http://dmt:8022/v1/domain/repository">>, + 'RepositoryClient' => <<"http://dmt:8022/v1/domain/repository_client">> }} ]}, diff --git a/rebar.config b/rebar.config index 9d32db7f..01afc2f1 100644 --- a/rebar.config +++ b/rebar.config @@ -26,21 +26,21 @@ %% Common project dependencies. {deps, [ - {prometheus, "4.8.1"}, - {prometheus_cowboy, "0.1.8"}, - {damsel, {git, "https://github.com/valitydev/damsel.git", {branch, "master"}}}, + {prometheus, "4.11.0"}, + {prometheus_cowboy, "0.1.9"}, + {damsel, {git, "https://github.com/valitydev/damsel.git", {tag, "v2.2.0"}}}, {limiter_proto, {git, "https://github.com/valitydev/limiter-proto.git", {branch, "master"}}}, {liminator_proto, {git, "https://github.com/valitydev/liminator-proto.git", {branch, "master"}}}, {xrates_proto, {git, "https://github.com/valitydev/xrates-proto.git", {branch, "master"}}}, - {machinery, {git, "https://github.com/valitydev/machinery-erlang.git", {tag, "v1.1.0"}}}, + {machinery, {git, "https://github.com/valitydev/machinery-erlang.git", {tag, "v1.1.7"}}}, {erl_health, {git, "https://github.com/valitydev/erlang-health.git", {branch, "master"}}}, {genlib, {git, "https://github.com/valitydev/genlib.git", {tag, "v1.1.0"}}}, {scoper, {git, "https://github.com/valitydev/scoper.git", {tag, "v1.1.0"}}}, {woody, {git, "https://github.com/valitydev/woody_erlang.git", {tag, "v1.1.0"}}}, - {dmt_client, {git, "https://github.com/valitydev/dmt_client.git", {branch, "master"}}}, - {opentelemetry_api, "1.2.1"}, - {opentelemetry, "1.3.0"}, - {opentelemetry_exporter, "1.3.0"} + {dmt_client, {git, "https://github.com/valitydev/dmt_client.git", {tag, "v2.0.0"}}}, + {opentelemetry_api, "1.4.0"}, + {opentelemetry, "1.5.0"}, + {opentelemetry_exporter, "1.8.0"} ]}. %% XRef checks @@ -118,11 +118,3 @@ "ct.coverdata" ]} ]}. - -%% NOTE -%% It is needed to use rebar3 lint plugin -{overrides, [ - {del, accept, [{plugins, [{rebar3_archive_plugin, "0.0.2"}]}]}, - {del, prometheus_cowboy, [{plugins, [{rebar3_archive_plugin, "0.0.1"}]}]}, - {del, prometheus_httpd, [{plugins, [{rebar3_archive_plugin, "0.0.1"}]}]} -]}. diff --git a/rebar.lock b/rebar.lock index 35c9390b..87977e6c 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,5 +1,5 @@ {"1.2.0", -[{<<"accept">>,{pkg,<<"accept">>,<<"0.3.5">>},2}, +[{<<"accept">>,{pkg,<<"accept">>,<<"0.3.7">>},2}, {<<"acceptor_pool">>,{pkg,<<"acceptor_pool">>,<<"1.0.0">>},2}, {<<"brod">>,{pkg,<<"brod">>,<<"4.3.2">>},2}, {<<"cache">>,{pkg,<<"cache">>,<<"2.3.3">>},1}, @@ -19,11 +19,11 @@ {<<"ctx">>,{pkg,<<"ctx">>,<<"0.6.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"ab44b9db25a76a2c50545fd884e4cdf3d3e3b628"}}, + {ref,"ba7414811590859d058817b8f22d2e9c22f627f8"}}, 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git", - {ref,"d8a4f490d49c038d96f1cbc2a279164c6f4039f9"}}, + {ref,"fcfb028a041149caeebec8d9cef469c8cdbbc63e"}}, 0}, {<<"dmt_core">>, {git,"https://github.com/valitydev/dmt-core.git", @@ -31,7 +31,7 @@ 1}, {<<"epg_connector">>, {git,"https://github.com/valitydev/epg_connector.git", - {ref,"35a7480b298ac4318352a03824ce06619b75f9da"}}, + {ref,"dd93e27c00d492169e8a7bfc38976b911c6e7d05"}}, 2}, {<<"epgsql">>, {git,"https://github.com/epgsql/epgsql.git", @@ -55,7 +55,7 @@ {<<"kafka_protocol">>,{pkg,<<"kafka_protocol">>,<<"4.1.10">>},3}, {<<"liminator_proto">>, {git,"https://github.com/valitydev/liminator-proto.git", - {ref,"3870a390b3dd658e2c889b8e9590714e9bc8a0c9"}}, + {ref,"d63ded1d138416e4278d7fbe57d7359c9113f7ea"}}, 0}, {<<"limiter_proto">>, {git,"https://github.com/valitydev/limiter-proto.git", @@ -63,32 +63,30 @@ 0}, {<<"machinery">>, {git,"https://github.com/valitydev/machinery-erlang.git", - {ref,"0ca82988ec310aceab7686c078c2a20fa6209cde"}}, + {ref,"74f49ff6c2a161ecad426e1bd0dcef6f508babab"}}, 0}, {<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},2}, {<<"mg_proto">>, {git,"https://github.com/valitydev/machinegun-proto", {ref,"3decc8f8b13c9cd1701deab47781aacddd7dbc92"}}, 1}, - {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.3.0">>},2}, - {<<"opentelemetry">>,{pkg,<<"opentelemetry">>,<<"1.3.0">>},0}, - {<<"opentelemetry_api">>,{pkg,<<"opentelemetry_api">>,<<"1.2.1">>},0}, + {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.4.0">>},2}, + {<<"opentelemetry">>,{pkg,<<"opentelemetry">>,<<"1.5.0">>},0}, + {<<"opentelemetry_api">>,{pkg,<<"opentelemetry_api">>,<<"1.4.0">>},0}, {<<"opentelemetry_exporter">>, - {pkg,<<"opentelemetry_exporter">>,<<"1.3.0">>}, + {pkg,<<"opentelemetry_exporter">>,<<"1.8.0">>}, 0}, - {<<"opentelemetry_semantic_conventions">>, - {pkg,<<"opentelemetry_semantic_conventions">>,<<"0.2.0">>}, - 1}, {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.1">>},2}, {<<"progressor">>, {git,"https://github.com/valitydev/progressor.git", - {ref,"a67d4ddbcc3ddc3471e903d6d7291ca8e194906c"}}, + {ref,"4c44615f712ae8992ff1a654f227def9f44c8aa7"}}, 1}, - {<<"prometheus">>,{pkg,<<"prometheus">>,<<"4.8.1">>},0}, - {<<"prometheus_cowboy">>,{pkg,<<"prometheus_cowboy">>,<<"0.1.8">>},0}, - {<<"prometheus_httpd">>,{pkg,<<"prometheus_httpd">>,<<"2.1.11">>},1}, + {<<"prometheus">>,{pkg,<<"prometheus">>,<<"4.11.0">>},0}, + {<<"prometheus_cowboy">>,{pkg,<<"prometheus_cowboy">>,<<"0.1.9">>},0}, + {<<"prometheus_httpd">>,{pkg,<<"prometheus_httpd">>,<<"2.1.15">>},1}, {<<"quantile_estimator">>,{pkg,<<"quantile_estimator">>,<<"0.2.1">>},1}, {<<"ranch">>,{pkg,<<"ranch">>,<<"1.8.0">>},2}, + {<<"recon">>,{pkg,<<"recon">>,<<"2.5.6">>},2}, {<<"scoper">>, {git,"https://github.com/valitydev/scoper.git", {ref,"0e7aa01e9632daa39727edd62d4656ee715b4569"}}, @@ -103,9 +101,9 @@ {ref,"3a60e5dc5bbd709495024f26e100b041c3547fd9"}}, 1}, {<<"tls_certificate_check">>, - {pkg,<<"tls_certificate_check">>,<<"1.25.0">>}, + {pkg,<<"tls_certificate_check">>,<<"1.28.0">>}, 1}, - {<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.0">>},2}, + {<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.1">>},2}, {<<"woody">>, {git,"https://github.com/valitydev/woody_erlang.git", {ref,"cc983a9423325ba1d6a509775eb6ff7ace721539"}}, @@ -116,7 +114,7 @@ 0}]}. [ {pkg_hash,[ - {<<"accept">>, <<"B33B127ABCA7CC948BBE6CAA4C263369ABF1347CFA9D8E699C6D214660F10CD1">>}, + {<<"accept">>, <<"CD6E34A2D7E28CA38B2D3CB233734CA0C221EFBC1F171F91FEC5F162CC2D18DA">>}, {<<"acceptor_pool">>, <<"43C20D2ACAE35F0C2BCD64F9D2BDE267E459F0F3FD23DAB26485BF518C281B21">>}, {<<"brod">>, <<"51F4DFF17ED43A806558EBD62CC88E7B35AED336D1BA1F3DE2D010F463D49736">>}, {<<"cache">>, <<"B23A5FE7095445A88412A6E614C933377E0137B44FFED77C9B3FEF1A731A20B2">>}, @@ -135,22 +133,22 @@ {<<"jsx">>, <<"D12516BAA0BB23A59BB35DCCAF02A1BD08243FCBB9EFE24F2D9D056CCFF71268">>}, {<<"kafka_protocol">>, <<"F917B6C90C8DF0DE2B40A87D6B9AE1CFCE7788E91A65818E90E40CF76111097A">>}, {<<"metrics">>, <<"25F094DEA2CDA98213CECC3AEFF09E940299D950904393B2A29D191C346A8486">>}, - {<<"mimerl">>, <<"D0CD9FC04B9061F82490F6581E0128379830E78535E017F7780F37FEA7545726">>}, - {<<"opentelemetry">>, <<"988AC3C26ACAC9720A1D4FB8D9DC52E95B45ECFEC2D5B5583276A09E8936BC5E">>}, - {<<"opentelemetry_api">>, <<"7B69ED4F40025C005DE0B74FCE8C0549625D59CB4DF12D15C32FE6DC5076FF42">>}, - {<<"opentelemetry_exporter">>, <<"1D8809C0D4F4ACF986405F7700ED11992BCBDB6A4915DD11921E80777FFA7167">>}, - {<<"opentelemetry_semantic_conventions">>, <<"B67FE459C2938FCAB341CB0951C44860C62347C005ACE1B50F8402576F241435">>}, + {<<"mimerl">>, <<"3882A5CA67FBBE7117BA8947F27643557ADEC38FA2307490C4C4207624CB213B">>}, + {<<"opentelemetry">>, <<"7DDA6551EDFC3050EA4B0B40C0D2570423D6372B97E9C60793263EF62C53C3C2">>}, + {<<"opentelemetry_api">>, <<"63CA1742F92F00059298F478048DFB826F4B20D49534493D6919A0DB39B6DB04">>}, + {<<"opentelemetry_exporter">>, <<"5D546123230771EF4174E37BEDFD77E3374913304CD6EA3CA82A2ADD49CD5D56">>}, {<<"parse_trans">>, <<"16328AB840CC09919BD10DAB29E431DA3AF9E9E7E7E6F0089DD5A2D2820011D8">>}, - {<<"prometheus">>, <<"FA76B152555273739C14B06F09F485CF6D5D301FE4E9D31B7FF803D26025D7A0">>}, - {<<"prometheus_cowboy">>, <<"CFCE0BC7B668C5096639084FCD873826E6220EA714BF60A716F5BD080EF2A99C">>}, - {<<"prometheus_httpd">>, <<"F616ED9B85B536B195D94104063025A91F904A4CFC20255363F49A197D96C896">>}, + {<<"prometheus">>, <<"B95F8DE8530F541BD95951E18E355A840003672E5EDA4788C5FA6183406BA29A">>}, + {<<"prometheus_cowboy">>, <<"D9D5B300516A61ED5AE31391F8EEEEB202230081D32A1813F2D78772B6F274E1">>}, + {<<"prometheus_httpd">>, <<"8F767D819A5D36275EAB9264AFF40D87279151646776069BF69FBDBBD562BD75">>}, {<<"quantile_estimator">>, <<"EF50A361F11B5F26B5F16D0696E46A9E4661756492C981F7B2229EF42FF1CD15">>}, {<<"ranch">>, <<"8C7A100A139FD57F17327B6413E4167AC559FBC04CA7448E9BE9057311597A1D">>}, + {<<"recon">>, <<"9052588E83BFEDFD9B72E1034532AEE2A5369D9D9343B61AEB7FBCE761010741">>}, {<<"ssl_verify_fun">>, <<"354C321CF377240C7B8716899E182CE4890C5938111A1296ADD3EC74CF1715DF">>}, - {<<"tls_certificate_check">>, <<"702B1835FE718A52310509537392ABD067DBE941EBC05FE72409D2B2F8061651">>}, - {<<"unicode_util_compat">>, <<"BC84380C9AB48177092F43AC89E4DFA2C6D62B40B8BD132B1059ECC7232F9A78">>}]}, + {<<"tls_certificate_check">>, <<"C39BF21F67C2D124AE905454FAD00F27E625917E8AB1009146E916E1DF6AB275">>}, + {<<"unicode_util_compat">>, <<"A48703A25C170EEDADCA83B11E88985AF08D35F37C6F664D6DCFB106A97782FC">>}]}, {pkg_hash_ext,[ - {<<"accept">>, <<"11B18C220BCC2EAB63B5470C038EF10EB6783BCB1FCDB11AA4137DEFA5AC1BB8">>}, + {<<"accept">>, <<"CA69388943F5DAD2E7232A5478F16086E3C872F48E32B88B378E1885A59F5649">>}, {<<"acceptor_pool">>, <<"0CBCD83FDC8B9AD2EEE2067EF8B91A14858A5883CB7CD800E6FCD5803E158788">>}, {<<"brod">>, <<"88584FDEBA746AA6729E2A1826416C10899954F68AF93659B3C2F38A2DCAA27C">>}, {<<"cache">>, <<"44516CE6FA03594D3A2AF025DD3A87BFE711000EB730219E1DDEFC816E0AA2F4">>}, @@ -169,18 +167,18 @@ {<<"jsx">>, <<"0C5CC8FDC11B53CC25CF65AC6705AD39E54ECC56D1C22E4ADB8F5A53FB9427F3">>}, {<<"kafka_protocol">>, <<"DF680A3706EAD8695F8B306897C0A33E8063C690DA9308DB87B462CFD7029D04">>}, {<<"metrics">>, <<"69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16">>}, - {<<"mimerl">>, <<"A1E15A50D1887217DE95F0B9B0793E32853F7C258A5CD227650889B38839FE9D">>}, - {<<"opentelemetry">>, <<"8E09EDC26AAD11161509D7ECAD854A3285D88580F93B63B0B1CF0BAC332BFCC0">>}, - {<<"opentelemetry_api">>, <<"6D7A27B7CAD2AD69A09CABF6670514CAFCEC717C8441BEB5C96322BAC3D05350">>}, - {<<"opentelemetry_exporter">>, <<"2B40007F509D38361744882FD060A8841AF772AB83BB542AA5350908B303AD65">>}, - {<<"opentelemetry_semantic_conventions">>, <<"D61FA1F5639EE8668D74B527E6806E0503EFC55A42DB7B5F39939D84C07D6895">>}, + {<<"mimerl">>, <<"13AF15F9F68C65884ECCA3A3891D50A7B57D82152792F3E19D88650AA126B144">>}, + {<<"opentelemetry">>, <<"CDF4F51D17B592FC592B9A75F86A6F808C23044BA7CF7B9534DEBBCC5C23B0EE">>}, + {<<"opentelemetry_api">>, <<"3DFBBFAA2C2ED3121C5C483162836C4F9027DEF469C41578AF5EF32589FCFC58">>}, + {<<"opentelemetry_exporter">>, <<"A1F9F271F8D3B02B81462A6BFEF7075FD8457FDB06ADFF5D2537DF5E2264D9AF">>}, {<<"parse_trans">>, <<"07CD9577885F56362D414E8C4C4E6BDF10D43A8767ABB92D24CBE8B24C54888B">>}, - {<<"prometheus">>, <<"6EDFBE928D271C7F657A6F2C46258738086584BD6CAE4A000B8B9A6009BA23A5">>}, - {<<"prometheus_cowboy">>, <<"BA286BECA9302618418892D37BCD5DC669A6CC001F4EB6D6AF85FF81F3F4F34C">>}, - {<<"prometheus_httpd">>, <<"0BBE831452CFDF9588538EB2F570B26F30C348ADAE5E95A7D87F35A5910BCF92">>}, + {<<"prometheus">>, <<"719862351AABF4DF7079B05DC085D2BBCBE3AC0AC3009E956671B1D5AB88247D">>}, + {<<"prometheus_cowboy">>, <<"5F71C039DEB9E9FF9DD6366BC74C907A463872B85286E619EFF0BDA15111695A">>}, + {<<"prometheus_httpd">>, <<"67736D000745184D5013C58A63E947821AB90CB9320BC2E6AE5D3061C6FFE039">>}, {<<"quantile_estimator">>, <<"282A8A323CA2A845C9E6F787D166348F776C1D4A41EDE63046D72D422E3DA946">>}, {<<"ranch">>, <<"49FBCFD3682FAB1F5D109351B61257676DA1A2FDBE295904176D5E521A2DDFE5">>}, + {<<"recon">>, <<"96C6799792D735CC0F0FD0F86267E9D351E63339CBE03DF9D162010CEFC26BB0">>}, {<<"ssl_verify_fun">>, <<"FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8">>}, - {<<"tls_certificate_check">>, <<"167343CCF50538CF2FAF61A3F1460E749B3EDF2ECEF55516AF2B5834362ABCB1">>}, - {<<"unicode_util_compat">>, <<"25EEE6D67DF61960CF6A794239566599B09E17E668D3700247BC498638152521">>}]} + {<<"tls_certificate_check">>, <<"3AB058C3F9457FFFCA916729587415F0DDC822048A0E5B5E2694918556D92DF1">>}, + {<<"unicode_util_compat">>, <<"B3A917854CE3AE233619744AD1E0102E05673136776FB2FA76234F3E03B23642">>}]} ]. diff --git a/test/dmt/sys.config b/test/dmt/sys.config new file mode 100644 index 00000000..f1c50ed7 --- /dev/null +++ b/test/dmt/sys.config @@ -0,0 +1,74 @@ +[ + {kernel, [ + {log_level, debug}, + {logger, [ + {handler, default, logger_std_h, #{ + level => all, + config => #{ + type => standard_io + } + %% formatter => + %% {logger_logstash_formatter, #{}} + }} + ]} + ]}, + + {dmt, [ + {host, <<"dmt">>}, + {port, 8022}, + {scoper_event_handler_options, #{ + event_handler_opts => #{ + formatter_opts => #{ + max_length => 1000 + } + } + }}, + {services, #{ + repository => #{ + url => <<"http://dmt:8022/v1/domain/repository">> + }, + repository_client => #{ + url => <<"http://dmt:8022/v1/domain/repository_client">> + }, + author => #{ + url => <<"http://dmt:8022/v1/domain/author">> + } + }} + ]}, + + {woody, [ + {acceptors_pool_size, 4} + ]}, + + {epg_connector, [ + {databases, #{ + default_db => #{ + host => "db", + port => 5432, + username => "dmt", + password => "postgres", + database => "dmt" + } + }}, + {pools, #{ + default_pool => #{ + database => default_db, + size => 10 + }, + author_pool => #{ + database => default_db, + size => 10 + } + }} + ]}, + + {scoper, [ + {storage, scoper_storage_logger} + ]}, + + {prometheus, [ + {collectors, [ + default + ]} + ]} +]. diff --git a/test/machinegun/config.yaml b/test/machinegun/config.yaml index 3b93a483..aa4ee45d 100644 --- a/test/machinegun/config.yaml +++ b/test/machinegun/config.yaml @@ -12,10 +12,6 @@ namespaces: processor: url: http://limiter:8022/v1/stateproc/lim/range_v1 pool_size: 500 - domain-config: - processor: - url: http://dominant:8022/v1/stateproc - pool_size: 300 storage: type: memory @@ -23,9 +19,3 @@ storage: logging: out_type: stdout level: info - -opentelemetry: - service_name: machinegun - exporter: - protocol: http/protobuf - endpoint: http://jaeger:4318 diff --git a/test/postgres/docker-entrypoint-initdb.d/create-multiple-postgresql-databases.sh b/test/postgres/docker-entrypoint-initdb.d/create-multiple-postgresql-databases.sh new file mode 100644 index 00000000..83e34fd7 --- /dev/null +++ b/test/postgres/docker-entrypoint-initdb.d/create-multiple-postgresql-databases.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +set -e +set -u + +function create_user_and_database() { + local database=$1 + echo " Creating user and database '$database'" + psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL + CREATE DATABASE $database; + \c $database; + CREATE USER $database; + ALTER USER $database WITH ENCRYPTED PASSWORD '$POSTGRES_PASSWORD'; + GRANT ALL ON SCHEMA public TO $database; + GRANT ALL PRIVILEGES ON DATABASE $database TO $database; +EOSQL +} + +if [ -n "$POSTGRES_MULTIPLE_DATABASES" ]; then + echo "Multiple database creation requested: $POSTGRES_MULTIPLE_DATABASES" + for db in $(echo $POSTGRES_MULTIPLE_DATABASES | tr ',' ' '); do + create_user_and_database $db + done + echo "Multiple databases created" +fi From 9eb96a7ccd97ec1d9e203ea50a7b00b0cb1cdad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D0=B5=D0=BC?= Date: Fri, 1 Aug 2025 13:45:28 +0300 Subject: [PATCH 48/54] BG-327: Bump damsel (#38) --- apps/limiter/src/lim_config_machine.erl | 1 + apps/limiter/src/lim_payproc_context.erl | 16 ---------------- apps/limiter/test/lim_ct_helper.hrl | 1 + compose.yaml | 2 +- rebar.config | 4 ++-- rebar.lock | 8 ++------ 6 files changed, 7 insertions(+), 25 deletions(-) diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index b5a00d98..c7108249 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -1018,6 +1018,7 @@ check_calculate_year_shard_id_test() -> id = <<"ID">>, owner_id = OwnerID, shop_id = ShopID, + domain_revision = 1, created_at = <<"2000-02-02T12:12:12Z">>, status = {unpaid, #domain_InvoiceUnpaid{}}, details = #domain_InvoiceDetails{product = <<>>}, diff --git a/apps/limiter/src/lim_payproc_context.erl b/apps/limiter/src/lim_payproc_context.erl index 9132dfd3..e434d025 100644 --- a/apps/limiter/src/lim_payproc_context.erl +++ b/apps/limiter/src/lim_payproc_context.erl @@ -186,8 +186,6 @@ get_payment_tool(_, _CtxInvoice) -> get_payer_payment_tool(#domain_PaymentResourcePayer{resource = #domain_DisposablePaymentResource{payment_tool = PT}}) -> lim_payproc_utils:payment_tool(PT); -get_payer_payment_tool(#domain_CustomerPayer{payment_tool = PT}) -> - lim_payproc_utils:payment_tool(PT); get_payer_payment_tool(#domain_RecurrentPayer{payment_tool = PT}) -> lim_payproc_utils:payment_tool(PT). @@ -225,8 +223,6 @@ get_payer_contact_email(_, _CtxInvoice) -> get_payer_contact_info(#domain_PaymentResourcePayer{contact_info = CI}) -> CI; -get_payer_contact_info(#domain_CustomerPayer{contact_info = CI}) -> - CI; get_payer_contact_info(#domain_RecurrentPayer{contact_info = CI}) -> CI. @@ -277,14 +273,6 @@ get_payment_tool_test_() -> resource = #domain_DisposablePaymentResource{payment_tool = PaymentTool}, contact_info = #domain_ContactInfo{} }}, - CustomerPayer = - {customer, #domain_CustomerPayer{ - customer_id = <<"customer_id">>, - customer_binding_id = <<"customer_binding_id">>, - rec_payment_tool_id = <<"rec_payment_tool_id">>, - payment_tool = PaymentTool, - contact_info = #domain_ContactInfo{} - }}, RecurrentPayer = {recurrent, #domain_RecurrentPayer{ payment_tool = PaymentTool, @@ -300,10 +288,6 @@ get_payment_tool_test_() -> {ok, ExpectedValue}, get_value(payment_tool, ?CONTEXT_PAYMENT(?PAYMENT_W_PAYER(PaymentResourcePayer))) ), - ?_assertEqual( - {ok, ExpectedValue}, - get_value(payment_tool, ?CONTEXT_PAYMENT(?PAYMENT_W_PAYER(CustomerPayer))) - ), ?_assertEqual( {ok, ExpectedValue}, get_value(payment_tool, ?CONTEXT_PAYMENT(?PAYMENT_W_PAYER(RecurrentPayer))) diff --git a/apps/limiter/test/lim_ct_helper.hrl b/apps/limiter/test/lim_ct_helper.hrl index 46a175e5..bbd7d491 100644 --- a/apps/limiter/test/lim_ct_helper.hrl +++ b/apps/limiter/test/lim_ct_helper.hrl @@ -132,6 +132,7 @@ id = ?string, owner_id = OwnerID, shop_id = ShopID, + domain_revision = 1, created_at = ?timestamp, status = {unpaid, #domain_InvoiceUnpaid{}}, details = #domain_InvoiceDetails{product = ?string}, diff --git a/compose.yaml b/compose.yaml index c6ef112f..cda6fd28 100644 --- a/compose.yaml +++ b/compose.yaml @@ -24,7 +24,7 @@ services: condition: service_healthy dmt: - image: ghcr.io/valitydev/dominant-v2:sha-109d2ea + image: ghcr.io/valitydev/dominant-v2:sha-f55c065 command: /opt/dmt/bin/dmt foreground healthcheck: test: "/opt/dmt/bin/dmt ping" diff --git a/rebar.config b/rebar.config index 01afc2f1..76150c66 100644 --- a/rebar.config +++ b/rebar.config @@ -28,7 +28,7 @@ {deps, [ {prometheus, "4.11.0"}, {prometheus_cowboy, "0.1.9"}, - {damsel, {git, "https://github.com/valitydev/damsel.git", {tag, "v2.2.0"}}}, + {damsel, {git, "https://github.com/valitydev/damsel.git", {tag, "v2.2.11"}}}, {limiter_proto, {git, "https://github.com/valitydev/limiter-proto.git", {branch, "master"}}}, {liminator_proto, {git, "https://github.com/valitydev/liminator-proto.git", {branch, "master"}}}, {xrates_proto, {git, "https://github.com/valitydev/xrates-proto.git", {branch, "master"}}}, @@ -37,7 +37,7 @@ {genlib, {git, "https://github.com/valitydev/genlib.git", {tag, "v1.1.0"}}}, {scoper, {git, "https://github.com/valitydev/scoper.git", {tag, "v1.1.0"}}}, {woody, {git, "https://github.com/valitydev/woody_erlang.git", {tag, "v1.1.0"}}}, - {dmt_client, {git, "https://github.com/valitydev/dmt_client.git", {tag, "v2.0.0"}}}, + {dmt_client, {git, "https://github.com/valitydev/dmt_client.git", {tag, "v2.0.2"}}}, {opentelemetry_api, "1.4.0"}, {opentelemetry, "1.5.0"}, {opentelemetry_exporter, "1.8.0"} diff --git a/rebar.lock b/rebar.lock index 87977e6c..91906d87 100644 --- a/rebar.lock +++ b/rebar.lock @@ -19,16 +19,12 @@ {<<"ctx">>,{pkg,<<"ctx">>,<<"0.6.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"ba7414811590859d058817b8f22d2e9c22f627f8"}}, + {ref,"ff9b01f552f922ce4a16710827aa872325dbe5a9"}}, 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git", - {ref,"fcfb028a041149caeebec8d9cef469c8cdbbc63e"}}, + {ref,"fff521d3d50b48e3c6b628fe4796b3628aedc6b7"}}, 0}, - {<<"dmt_core">>, - {git,"https://github.com/valitydev/dmt-core.git", - {ref,"19d8f57198f2cbe5b64aa4a923ba32774e505503"}}, - 1}, {<<"epg_connector">>, {git,"https://github.com/valitydev/epg_connector.git", {ref,"dd93e27c00d492169e8a7bfc38976b911c6e7d05"}}, From 9ed4e6e748c868dc020f1aff9dc43366dcc40c15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D0=B5=D0=BC?= Date: Wed, 27 Aug 2025 11:37:43 +0300 Subject: [PATCH 49/54] BG-378: Bump (#39) --- apps/limiter/src/lim_config_machine.erl | 6 +++--- apps/limiter/src/lim_payproc_context.erl | 4 ++-- apps/limiter/src/lim_wthdproc_context.erl | 3 ++- apps/limiter/test/lim_ct_helper.hrl | 6 +++--- rebar.config | 4 ++-- rebar.lock | 14 +++++++------- 6 files changed, 19 insertions(+), 18 deletions(-) diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index c7108249..cad612c1 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -1016,8 +1016,8 @@ check_calculate_year_shard_id_test() -> -define(INVOICE(OwnerID, ShopID), #domain_Invoice{ id = <<"ID">>, - owner_id = OwnerID, - shop_id = ShopID, + party_ref = #domain_PartyConfigRef{id = OwnerID}, + shop_ref = #domain_ShopConfigRef{id = ShopID}, domain_revision = 1, created_at = <<"2000-02-02T12:12:12Z">>, status = {unpaid, #domain_InvoiceUnpaid{}}, @@ -1065,7 +1065,7 @@ check_calculate_year_shard_id_test() -> -define(WITHDRAWAL(OwnerID, PaymentTool), #wthd_domain_Withdrawal{ destination = PaymentTool, - sender = OwnerID, + sender = #domain_PartyConfigRef{id = OwnerID}, created_at = <<"2000-02-02T12:12:12Z">>, body = #domain_Cash{amount = 42, currency = #domain_CurrencyRef{symbolic_code = <<"CNY">>}} }). diff --git a/apps/limiter/src/lim_payproc_context.erl b/apps/limiter/src/lim_payproc_context.erl index e434d025..fa9c3553 100644 --- a/apps/limiter/src/lim_payproc_context.erl +++ b/apps/limiter/src/lim_payproc_context.erl @@ -130,12 +130,12 @@ get_value(ValueName, _Operation, _Context) -> }). get_owner_id(?INVOICE(Invoice)) -> - {ok, Invoice#domain_Invoice.owner_id}; + {ok, Invoice#domain_Invoice.party_ref#domain_PartyConfigRef.id}; get_owner_id(_) -> {error, notfound}. get_shop_id(?INVOICE(Invoice)) -> - {ok, Invoice#domain_Invoice.shop_id}; + {ok, Invoice#domain_Invoice.shop_ref#domain_ShopConfigRef.id}; get_shop_id(_) -> {error, notfound}. diff --git a/apps/limiter/src/lim_wthdproc_context.erl b/apps/limiter/src/lim_wthdproc_context.erl index f9aad8e3..f9b44306 100644 --- a/apps/limiter/src/lim_wthdproc_context.erl +++ b/apps/limiter/src/lim_wthdproc_context.erl @@ -3,6 +3,7 @@ -include_lib("limiter_proto/include/limproto_context_withdrawal_thrift.hrl"). -include_lib("limiter_proto/include/limproto_base_thrift.hrl"). -include_lib("damsel/include/dmsl_wthd_domain_thrift.hrl"). +-include_lib("damsel/include/dmsl_domain_thrift.hrl"). -behaviour(lim_context). -export([get_operation/1]). @@ -110,7 +111,7 @@ get_value(ValueName, _Operation, _Context) -> -define(SENDER_RECEIVER(V), ?AUTH_DATA({sender_receiver, V})). get_owner_id(?WITHDRAWAL(Wthd)) -> - {ok, Wthd#wthd_domain_Withdrawal.sender}; + {ok, Wthd#wthd_domain_Withdrawal.sender#domain_PartyConfigRef.id}; get_owner_id(_CtxWithdrawal) -> {error, notfound}. diff --git a/apps/limiter/test/lim_ct_helper.hrl b/apps/limiter/test/lim_ct_helper.hrl index bbd7d491..3f67b607 100644 --- a/apps/limiter/test/lim_ct_helper.hrl +++ b/apps/limiter/test/lim_ct_helper.hrl @@ -130,8 +130,8 @@ -define(invoice(OwnerID, ShopID, Cost), #domain_Invoice{ id = ?string, - owner_id = OwnerID, - shop_id = ShopID, + party_ref = #domain_PartyConfigRef{id = OwnerID}, + shop_ref = #domain_ShopConfigRef{id = ShopID}, domain_revision = 1, created_at = ?timestamp, status = {unpaid, #domain_InvoiceUnpaid{}}, @@ -243,7 +243,7 @@ body = Body, created_at = ?timestamp, destination = Destination, - sender = OwnerID, + sender = #domain_PartyConfigRef{id = OwnerID}, auth_data = AuthData }). diff --git a/rebar.config b/rebar.config index 76150c66..c7aa349f 100644 --- a/rebar.config +++ b/rebar.config @@ -28,11 +28,11 @@ {deps, [ {prometheus, "4.11.0"}, {prometheus_cowboy, "0.1.9"}, - {damsel, {git, "https://github.com/valitydev/damsel.git", {tag, "v2.2.11"}}}, + {damsel, {git, "https://github.com/valitydev/damsel.git", {tag, "v2.2.12"}}}, {limiter_proto, {git, "https://github.com/valitydev/limiter-proto.git", {branch, "master"}}}, {liminator_proto, {git, "https://github.com/valitydev/liminator-proto.git", {branch, "master"}}}, {xrates_proto, {git, "https://github.com/valitydev/xrates-proto.git", {branch, "master"}}}, - {machinery, {git, "https://github.com/valitydev/machinery-erlang.git", {tag, "v1.1.7"}}}, + {machinery, {git, "https://github.com/valitydev/machinery-erlang.git", {tag, "v1.1.10"}}}, {erl_health, {git, "https://github.com/valitydev/erlang-health.git", {branch, "master"}}}, {genlib, {git, "https://github.com/valitydev/genlib.git", {tag, "v1.1.0"}}}, {scoper, {git, "https://github.com/valitydev/scoper.git", {tag, "v1.1.0"}}}, diff --git a/rebar.lock b/rebar.lock index 91906d87..54327672 100644 --- a/rebar.lock +++ b/rebar.lock @@ -5,7 +5,7 @@ {<<"cache">>,{pkg,<<"cache">>,<<"2.3.3">>},1}, {<<"canal">>, {git,"https://github.com/valitydev/canal", - {ref,"621d3821cd0a6036fee75d8e3b2d17167f3268e4"}}, + {ref,"89faedce3b054bcca7cc31ca64d2ead8a9402305"}}, 3}, {<<"certifi">>,{pkg,<<"certifi">>,<<"2.8.0">>},2}, {<<"cg_mon">>, @@ -19,7 +19,7 @@ {<<"ctx">>,{pkg,<<"ctx">>,<<"0.6.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"ff9b01f552f922ce4a16710827aa872325dbe5a9"}}, + {ref,"8cab698cd78125ac47489d0ba81169df376757a4"}}, 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git", @@ -27,11 +27,11 @@ 0}, {<<"epg_connector">>, {git,"https://github.com/valitydev/epg_connector.git", - {ref,"dd93e27c00d492169e8a7bfc38976b911c6e7d05"}}, + {ref,"2e86da8083908d0d35a4eed3e2168c9ba6a8d04a"}}, 2}, {<<"epgsql">>, {git,"https://github.com/epgsql/epgsql.git", - {ref,"7ba52768cf0ea7d084df24d4275a88eef4db13c2"}}, + {ref,"28e9f84c95065a51e92baeb37d2cf1687fc4b9ce"}}, 3}, {<<"erl_health">>, {git,"https://github.com/valitydev/erlang-health.git", @@ -59,12 +59,12 @@ 0}, {<<"machinery">>, {git,"https://github.com/valitydev/machinery-erlang.git", - {ref,"74f49ff6c2a161ecad426e1bd0dcef6f508babab"}}, + {ref,"066f6cecd5d8f011c1b3bf8e45f6ad967c0e733d"}}, 0}, {<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},2}, {<<"mg_proto">>, {git,"https://github.com/valitydev/machinegun-proto", - {ref,"3decc8f8b13c9cd1701deab47781aacddd7dbc92"}}, + {ref,"cc2c27c30d30dc34c0c56fc7c7e96326d6bd6a14"}}, 1}, {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.4.0">>},2}, {<<"opentelemetry">>,{pkg,<<"opentelemetry">>,<<"1.5.0">>},0}, @@ -75,7 +75,7 @@ {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.1">>},2}, {<<"progressor">>, {git,"https://github.com/valitydev/progressor.git", - {ref,"4c44615f712ae8992ff1a654f227def9f44c8aa7"}}, + {ref,"d429410a0f2b42fbcc22b340cc8dc1915b13f119"}}, 1}, {<<"prometheus">>,{pkg,<<"prometheus">>,<<"4.11.0">>},0}, {<<"prometheus_cowboy">>,{pkg,<<"prometheus_cowboy">>,<<"0.1.9">>},0}, From 7b27571b71065af59e4300a4571cea96a48bbd73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D1=80=D1=82=D0=B5=D0=BC?= Date: Wed, 27 Aug 2025 16:11:57 +0300 Subject: [PATCH 50/54] BG-378: Bump dmt client (#40) --- rebar.config | 2 +- rebar.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rebar.config b/rebar.config index c7aa349f..ce0e3df0 100644 --- a/rebar.config +++ b/rebar.config @@ -37,7 +37,7 @@ {genlib, {git, "https://github.com/valitydev/genlib.git", {tag, "v1.1.0"}}}, {scoper, {git, "https://github.com/valitydev/scoper.git", {tag, "v1.1.0"}}}, {woody, {git, "https://github.com/valitydev/woody_erlang.git", {tag, "v1.1.0"}}}, - {dmt_client, {git, "https://github.com/valitydev/dmt_client.git", {tag, "v2.0.2"}}}, + {dmt_client, {git, "https://github.com/valitydev/dmt_client.git", {tag, "v2.0.3"}}}, {opentelemetry_api, "1.4.0"}, {opentelemetry, "1.5.0"}, {opentelemetry_exporter, "1.8.0"} diff --git a/rebar.lock b/rebar.lock index 54327672..5f70b8fc 100644 --- a/rebar.lock +++ b/rebar.lock @@ -23,7 +23,7 @@ 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git", - {ref,"fff521d3d50b48e3c6b628fe4796b3628aedc6b7"}}, + {ref,"20c18cc9b51d0f273db60c929e8a8a871d6a1866"}}, 0}, {<<"epg_connector">>, {git,"https://github.com/valitydev/epg_connector.git", From 2a3f78d1f45133189db29abade2df067b2ee76fa Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Thu, 9 Oct 2025 17:09:10 +0300 Subject: [PATCH 51/54] Removes obsolete machinery-based limit support (#43) * Removes obsolete machinery-based limit support * (WIP) Updates dmt and tests w/ `started_at` field * Upgrades damsel, reverts `started_at` field and disables temporary broken test cases * Retires codec modules and moves sole functions into user-modules * Removes comment * Bumps limiter-proto --- Makefile | 10 + apps/limiter/src/lim_config_codec.erl | 476 -------------- apps/limiter/src/lim_config_machine.erl | 612 +++++------------- .../src/lim_config_machinery_schema.erl | 114 ---- apps/limiter/src/lim_configurator.erl | 131 ---- apps/limiter/src/lim_context.erl | 24 +- apps/limiter/src/lim_handler.erl | 56 -- apps/limiter/src/lim_liminator.erl | 1 + apps/limiter/src/lim_posting.erl | 32 - apps/limiter/src/lim_range_codec.erl | 179 ----- apps/limiter/src/lim_range_machine.erl | 260 -------- .../src/lim_range_machinery_schema.erl | 122 ---- apps/limiter/src/lim_time.erl | 6 - apps/limiter/src/lim_turnover_processor.erl | 162 ----- apps/limiter/src/lim_utils.erl | 27 - apps/limiter/src/limiter.app.src | 1 - apps/limiter/src/limiter.erl | 60 +- apps/limiter/test/lim_client.erl | 116 ++-- apps/limiter/test/lim_configurator_SUITE.erl | 217 ------- apps/limiter/test/lim_ct_helper.hrl | 55 +- apps/limiter/test/lim_turnover_SUITE.erl | 452 +++++-------- compose.tracing.yaml | 3 - compose.yaml | 43 +- config/sys.config | 18 - rebar.config | 5 +- rebar.lock | 49 +- test/machinegun/config.yaml | 21 - test/machinegun/cookie | 1 - 28 files changed, 432 insertions(+), 2821 deletions(-) delete mode 100644 apps/limiter/src/lim_config_codec.erl delete mode 100644 apps/limiter/src/lim_config_machinery_schema.erl delete mode 100644 apps/limiter/src/lim_configurator.erl delete mode 100644 apps/limiter/src/lim_posting.erl delete mode 100644 apps/limiter/src/lim_range_codec.erl delete mode 100644 apps/limiter/src/lim_range_machine.erl delete mode 100644 apps/limiter/src/lim_range_machinery_schema.erl delete mode 100644 apps/limiter/src/lim_utils.erl delete mode 100644 apps/limiter/test/lim_configurator_SUITE.erl delete mode 100644 test/machinegun/config.yaml delete mode 100644 test/machinegun/cookie diff --git a/Makefile b/Makefile index 0ffe029f..df2ce0d2 100644 --- a/Makefile +++ b/Makefile @@ -42,6 +42,16 @@ DOCKER_RUN = $(DOCKER) run -t $(DOCKER_WC_OPTIONS) $(DOCKER_WC_EXTRA_OPTIONS) DOCKERCOMPOSE_RUN = $(DOCKERCOMPOSE_W_ENV) run --rm $(DOCKER_WC_OPTIONS) +# Database tasks + +ifeq (db,$(firstword $(MAKECMDGOALS))) + DATABASE_NAME := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS)) + $(eval $(DATABASE_NAME):;@:) +endif + +db: + $(DOCKERCOMPOSE_W_ENV) exec db bash -c "PGPASSWORD=postgres psql -U $(DATABASE_NAME) -d $(DATABASE_NAME)" + # Utility tasks wc-shell: dev-image diff --git a/apps/limiter/src/lim_config_codec.erl b/apps/limiter/src/lim_config_codec.erl deleted file mode 100644 index 76caa6fb..00000000 --- a/apps/limiter/src/lim_config_codec.erl +++ /dev/null @@ -1,476 +0,0 @@ --module(lim_config_codec). - --include_lib("limiter_proto/include/limproto_config_thrift.hrl"). --include_lib("limiter_proto/include/limproto_timerange_thrift.hrl"). --include_lib("damsel/include/dmsl_domain_thrift.hrl"). --include_lib("damsel/include/dmsl_limiter_config_thrift.hrl"). - --export([marshal/2]). --export([unmarshal/2]). --export([marshal_config/1]). --export([unmarshal_op_behaviour/1]). --export([unmarshal_params/1]). --export([maybe_apply/2]). - -%% Types - --type type_name() :: atom() | {list, atom()} | {set, atom()}. - --type encoded_value() :: encoded_value(any()). --type encoded_value(T) :: T. - --type decoded_value() :: decoded_value(any()). --type decoded_value(T) :: T. - --spec maybe_apply(T, fun((T) -> U)) -> U | undefined. -maybe_apply(undefined, _) -> - undefined; -maybe_apply(Value, Fun) -> - Fun(Value). - --spec maybe_apply(T, fun((T) -> U), Default) -> U | Default. -maybe_apply(undefined, _, Default) -> - Default; -maybe_apply(Value, Fun, _Default) -> - Fun(Value). - -%% API - --spec marshal(type_name(), decoded_value()) -> encoded_value(). -marshal(timestamped_change, {ev, Timestamp, Change}) -> - #config_TimestampedChange{ - change = marshal_change(Change), - occured_at = marshal_timestamp(Timestamp) - }. - -marshal_timestamp({DateTime, USec}) -> - DateTimeinSeconds = genlib_time:daytime_to_unixtime(DateTime), - {TimeinUnit, Unit} = - case USec of - 0 -> - {DateTimeinSeconds, second}; - USec -> - MicroSec = erlang:convert_time_unit(DateTimeinSeconds, second, microsecond), - {MicroSec + USec, microsecond} - end, - genlib_rfc3339:format_relaxed(TimeinUnit, Unit). - -marshal_change({created, Config}) -> - {created, #config_CreatedChange{limit_config = marshal_config(Config)}}. - --spec marshal_config(decoded_value()) -> encoded_value(). -marshal_config(Config) -> - #config_LimitConfig{ - id = lim_config_machine:id(Config), - processor_type = lim_config_machine:processor_type(Config), - description = lim_config_machine:description(Config), - created_at = lim_config_machine:created_at(Config), - started_at = lim_config_machine:started_at(Config), - shard_size = lim_config_machine:shard_size(Config), - time_range_type = marshal_time_range_type(lim_config_machine:time_range_type(Config)), - context_type = marshal_context_type(lim_config_machine:context_type(Config)), - type = maybe_apply(lim_config_machine:type(Config), fun marshal_type/1), - scope = maybe_apply(lim_config_machine:scope(Config), fun marshal_scope/1), - op_behaviour = maybe_apply(lim_config_machine:op_behaviour(Config), fun marshal_op_behaviour/1), - currency_conversion = marshal_currency_conversion(lim_config_machine:currency_conversion(Config)) - }. - -marshal_currency_conversion(true) -> - #config_CurrencyConversion{}; -marshal_currency_conversion(_) -> - undefined. - -marshal_op_behaviour(OpBehaviour) -> - PaymentRefund = maps:get(invoice_payment_refund, OpBehaviour, undefined), - #config_OperationLimitBehaviour{ - invoice_payment_refund = maybe_apply(PaymentRefund, fun marshal_behaviour/1) - }. - -marshal_behaviour(subtraction) -> - {subtraction, #config_Subtraction{}}; -marshal_behaviour(addition) -> - {addition, #config_Addition{}}. - -marshal_time_range_type({calendar, CalendarType}) -> - {calendar, marshal_calendar_time_range_type(CalendarType)}; -marshal_time_range_type({interval, Amount}) -> - {interval, #timerange_TimeRangeTypeInterval{amount = Amount}}. - -marshal_calendar_time_range_type(day) -> - {day, #timerange_TimeRangeTypeCalendarDay{}}; -marshal_calendar_time_range_type(week) -> - {week, #timerange_TimeRangeTypeCalendarWeek{}}; -marshal_calendar_time_range_type(month) -> - {month, #timerange_TimeRangeTypeCalendarMonth{}}; -marshal_calendar_time_range_type(year) -> - {year, #timerange_TimeRangeTypeCalendarYear{}}. - -marshal_context_type(payment_processing) -> - {payment_processing, #config_LimitContextTypePaymentProcessing{}}; -marshal_context_type(withdrawal_processing) -> - {withdrawal_processing, #config_LimitContextTypeWithdrawalProcessing{}}. - -marshal_type({turnover, Metric}) -> - {turnover, #config_LimitTypeTurnover{ - metric = marshal_turnover_metric(Metric) - }}. - -marshal_turnover_metric(number) -> - {number, #config_LimitTurnoverNumber{}}; -marshal_turnover_metric({amount, Currency}) -> - {amount, #config_LimitTurnoverAmount{currency = Currency}}. - -marshal_scope(Types) -> - {multi, ordsets:from_list(lists:map(fun marshal_scope_type/1, ordsets:to_list(Types)))}. - -marshal_scope_type(party) -> - {party, #config_LimitScopeEmptyDetails{}}; -marshal_scope_type(shop) -> - {shop, #config_LimitScopeEmptyDetails{}}; -marshal_scope_type(wallet) -> - {wallet, #config_LimitScopeEmptyDetails{}}; -marshal_scope_type(payment_tool) -> - {payment_tool, #config_LimitScopeEmptyDetails{}}; -marshal_scope_type(provider) -> - {provider, #config_LimitScopeEmptyDetails{}}; -marshal_scope_type(terminal) -> - {terminal, #config_LimitScopeEmptyDetails{}}; -marshal_scope_type(payer_contact_email) -> - {payer_contact_email, #config_LimitScopeEmptyDetails{}}; -marshal_scope_type({destination_field, FieldPath}) -> - {destination_field, #config_LimitScopeDestinationFieldDetails{field_path = FieldPath}}; -marshal_scope_type(sender) -> - {sender, #config_LimitScopeEmptyDetails{}}; -marshal_scope_type(receiver) -> - {receiver, #config_LimitScopeEmptyDetails{}}. - -%% - --spec unmarshal(type_name(), encoded_value()) -> decoded_value(). -unmarshal(timestamped_change, TimestampedChange) -> - Timestamp = unmarshal_timestamp(TimestampedChange#config_TimestampedChange.occured_at), - Change = unmarshal_change(TimestampedChange#config_TimestampedChange.change), - {ev, Timestamp, Change}; -unmarshal('LimitConfigObject', #domain_LimitConfigObject{ - ref = #domain_LimitConfigRef{id = ID}, - data = #limiter_config_LimitConfig{ - processor_type = ProcessorType, - created_at = CreatedAt, - started_at = StartedAt, - shard_size = ShardSize, - time_range_type = TimeRangeType, - context_type = ContextType, - type = Type, - scopes = Scopes, - description = Description, - op_behaviour = OpBehaviour, - currency_conversion = CurrencyConversion - } -}) -> - genlib_map:compact(#{ - id => ID, - processor_type => ProcessorType, - created_at => lim_time:from_rfc3339(CreatedAt), - started_at => StartedAt, - shard_size => ShardSize, - time_range_type => unmarshal_time_range_type(TimeRangeType), - context_type => unmarshal_context_type(ContextType), - type => maybe_apply(Type, fun unmarshal_type/1), - scope => maybe_apply(Scopes, fun unmarshal_scope/1), - description => Description, - op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1), - currency_conversion => CurrencyConversion =/= undefined - }). - -unmarshal_timestamp(Timestamp) when is_binary(Timestamp) -> - try - MicroSeconds = genlib_rfc3339:parse(Timestamp, microsecond), - case genlib_rfc3339:is_utc(Timestamp) of - false -> - erlang:error({bad_timestamp, not_utc}, [Timestamp]); - true -> - USec = MicroSeconds rem 1000000, - DateTime = calendar:system_time_to_universal_time(MicroSeconds, microsecond), - {DateTime, USec} - end - catch - error:Error:St -> - erlang:raise(error, {bad_timestamp, Timestamp, Error}, St) - end. - --spec unmarshal_params(encoded_value()) -> decoded_value(). -unmarshal_params(#config_LimitConfigParams{ - id = ID, - description = Description, - started_at = StartedAt, - shard_size = ShardSize, - time_range_type = TimeRangeType, - context_type = ContextType, - type = Type, - scope = Scope, - op_behaviour = OpBehaviour, - currency_conversion = CurrencyConversion -}) -> - genlib_map:compact(#{ - id => ID, - started_at => StartedAt, - shard_size => ShardSize, - time_range_type => unmarshal_time_range_type(TimeRangeType), - context_type => unmarshal_context_type(ContextType), - type => maybe_apply(Type, fun unmarshal_type/1), - scope => maybe_apply(Scope, fun unmarshal_scope/1), - description => Description, - op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1), - currency_conversion => CurrencyConversion =/= undefined - }). - -unmarshal_change({created, #config_CreatedChange{limit_config = Config}}) -> - {created, unmarshal_config(Config)}. - -unmarshal_config(#config_LimitConfig{ - id = ID, - processor_type = ProcessorType, - description = Description, - created_at = CreatedAt, - started_at = StartedAt, - shard_size = ShardSize, - time_range_type = TimeRangeType, - context_type = ContextType, - type = TypeIn, - scope = Scope, - op_behaviour = OpBehaviour, - currency_conversion = CurrencyConversion, - body_type_deprecated = BodyTypeIn -}) -> - Type = maybe_apply(TypeIn, fun unmarshal_type/1), - BodyType = maybe_apply(BodyTypeIn, fun unmarshal_body_type_deprecated/1), - genlib_map:compact(#{ - id => ID, - processor_type => ProcessorType, - created_at => lim_time:from_rfc3339(CreatedAt), - started_at => StartedAt, - shard_size => ShardSize, - time_range_type => unmarshal_time_range_type(TimeRangeType), - context_type => unmarshal_context_type(ContextType), - type => derive_type(Type, BodyType), - scope => maybe_apply(Scope, fun unmarshal_scope/1), - description => Description, - op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1), - currency_conversion => CurrencyConversion =/= undefined - }). - -derive_type(Type, undefined) -> - % NOTE - % Current protocol disallows configuring (deprecated) body type, thus we trust limit type. - Type; -derive_type({turnover, _}, {cash, Currency}) -> - % NOTE - % Treating limits with configured (deprecated) body type as turnover limits with amount metric. - {turnover, {amount, Currency}}; -derive_type(undefined, {cash, Currency}) -> - {turnover, {amount, Currency}}. - --spec unmarshal_op_behaviour(encoded_value()) -> decoded_value(). -unmarshal_op_behaviour(#limiter_config_OperationLimitBehaviour{invoice_payment_refund = Refund}) -> - do_unmarshal_op_behaviour_refund(Refund); -unmarshal_op_behaviour(#config_OperationLimitBehaviour{invoice_payment_refund = Refund}) -> - do_unmarshal_op_behaviour_refund(Refund). - -do_unmarshal_op_behaviour_refund(Refund) -> - genlib_map:compact(#{ - invoice_payment_refund => maybe_apply(Refund, fun unmarshal_behaviour/1) - }). - -unmarshal_behaviour({subtraction, #limiter_config_Subtraction{}}) -> - subtraction; -unmarshal_behaviour({subtraction, #config_Subtraction{}}) -> - subtraction; -unmarshal_behaviour({addition, #limiter_config_Addition{}}) -> - addition; -unmarshal_behaviour({addition, #config_Addition{}}) -> - addition. - --spec unmarshal_body_type_deprecated(encoded_value()) -> decoded_value(). -unmarshal_body_type_deprecated({cash, #config_LimitBodyTypeCash{currency = Currency}}) -> - {cash, Currency}. - -unmarshal_time_range_type({calendar, CalendarType}) -> - {calendar, unmarshal_calendar_time_range_type(CalendarType)}; -unmarshal_time_range_type({interval, #limiter_config_TimeRangeTypeInterval{amount = Amount}}) -> - {interval, Amount}; -unmarshal_time_range_type({interval, #timerange_TimeRangeTypeInterval{amount = Amount}}) -> - {interval, Amount}. - -unmarshal_calendar_time_range_type({day, _}) -> - day; -unmarshal_calendar_time_range_type({week, _}) -> - week; -unmarshal_calendar_time_range_type({month, _}) -> - month; -unmarshal_calendar_time_range_type({year, _}) -> - year. - -unmarshal_context_type({payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}) -> - payment_processing; -unmarshal_context_type({payment_processing, #config_LimitContextTypePaymentProcessing{}}) -> - payment_processing; -unmarshal_context_type({withdrawal_processing, #limiter_config_LimitContextTypeWithdrawalProcessing{}}) -> - withdrawal_processing; -unmarshal_context_type({withdrawal_processing, #config_LimitContextTypeWithdrawalProcessing{}}) -> - withdrawal_processing. - -unmarshal_type({turnover, #limiter_config_LimitTypeTurnover{metric = Metric}}) -> - {turnover, maybe_apply(Metric, fun unmarshal_turnover_metric/1, number)}; -unmarshal_type({turnover, #config_LimitTypeTurnover{metric = Metric}}) -> - {turnover, maybe_apply(Metric, fun unmarshal_turnover_metric/1, number)}. - -unmarshal_turnover_metric({number, _}) -> - number; -unmarshal_turnover_metric({amount, #limiter_config_LimitTurnoverAmount{currency = Currency}}) -> - {amount, Currency}; -unmarshal_turnover_metric({amount, #config_LimitTurnoverAmount{currency = Currency}}) -> - {amount, Currency}. - -unmarshal_scope({single, Type}) -> - ordsets:from_list([unmarshal_scope_type(Type)]); -unmarshal_scope({multi, Types}) -> - ordsets:from_list(lists:map(fun unmarshal_scope_type/1, ordsets:to_list(Types))); -unmarshal_scope(Types) when is_list(Types) -> - ordsets:from_list(lists:map(fun unmarshal_scope_type/1, ordsets:to_list(Types))). - -unmarshal_scope_type({party, _}) -> - party; -unmarshal_scope_type({shop, _}) -> - shop; -unmarshal_scope_type({wallet, _}) -> - wallet; -unmarshal_scope_type({payment_tool, _}) -> - payment_tool; -unmarshal_scope_type({provider, _}) -> - provider; -unmarshal_scope_type({terminal, _}) -> - terminal; -unmarshal_scope_type({payer_contact_email, _}) -> - payer_contact_email; -unmarshal_scope_type({destination_field, #config_LimitScopeDestinationFieldDetails{field_path = FieldPath}}) -> - %% Limiter proto variant clause - {destination_field, FieldPath}; -unmarshal_scope_type({destination_field, #limiter_config_LimitScopeDestinationFieldDetails{field_path = FieldPath}}) -> - %% Domain config variant clause - {destination_field, FieldPath}; -unmarshal_scope_type({sender, _}) -> - sender; -unmarshal_scope_type({receiver, _}) -> - receiver. - -%% - --ifdef(TEST). --include_lib("eunit/include/eunit.hrl"). - --spec test() -> _. - --spec marshal_unmarshal_created_test() -> _. -marshal_unmarshal_created_test() -> - Created = - {created, #{ - id => <<"id">>, - processor_type => <<"type">>, - created_at => lim_time:now(), - started_at => <<"2000-01-01T00:00:00Z">>, - shard_size => 7, - time_range_type => {calendar, day}, - context_type => payment_processing, - type => {turnover, number}, - scope => ordsets:from_list([party, shop]), - description => <<"description">>, - currency_conversion => false - }}, - Event = {ev, lim_time:machinery_now(), Created}, - ?assertEqual(Event, unmarshal(timestamped_change, marshal(timestamped_change, Event))). - --spec unmarshal_created_w_deprecated_body_type_test_() -> [_TestGen]. -unmarshal_created_w_deprecated_body_type_test_() -> - Now = lim_time:now(), - Config = #config_LimitConfig{ - id = <<"ID">>, - processor_type = <<"TurnoverProcessor">>, - created_at = lim_time:to_rfc3339(Now), - started_at = <<"2000-01-01T00:00:00Z">>, - shard_size = 42, - time_range_type = {calendar, {day, #timerange_TimeRangeTypeCalendarDay{}}}, - context_type = {payment_processing, #config_LimitContextTypePaymentProcessing{}}, - body_type_deprecated = {cash, #config_LimitBodyTypeCash{currency = <<"☭☭☭"/utf8>>}} - }, - [ - ?_assertMatch( - {created, #{ - id := <<"ID">>, - created_at := Now, - type := {turnover, {amount, <<"☭☭☭"/utf8>>}} - }}, - unmarshal_change( - {created, #config_CreatedChange{ - limit_config = Config#config_LimitConfig{ - type = undefined - } - }} - ) - ), - ?_assertMatch( - {created, #{ - id := <<"ID">>, - created_at := Now, - type := {turnover, {amount, <<"☭☭☭"/utf8>>}} - }}, - unmarshal_change( - {created, #config_CreatedChange{ - limit_config = Config#config_LimitConfig{ - type = {turnover, #config_LimitTypeTurnover{}} - } - }} - ) - ) - ]. - --spec unmarshal_config_object_test() -> _. -unmarshal_config_object_test() -> - CreatedAt = lim_time:now(), - Config = #{ - id => <<"id">>, - processor_type => <<"type">>, - created_at => CreatedAt, - started_at => <<"2000-01-01T00:00:00Z">>, - shard_size => 7, - time_range_type => {calendar, day}, - context_type => payment_processing, - type => {turnover, number}, - scope => ordsets:from_list([party, shop, {destination_field, [<<"path">>, <<"to">>, <<"field">>]}]), - description => <<"description">>, - currency_conversion => true - }, - Object = #domain_LimitConfigObject{ - ref = #domain_LimitConfigRef{id = <<"id">>}, - data = #limiter_config_LimitConfig{ - processor_type = <<"type">>, - created_at = lim_time:to_rfc3339(CreatedAt), - started_at = <<"2000-01-01T00:00:00Z">>, - shard_size = 7, - time_range_type = {calendar, {day, #limiter_config_TimeRangeTypeCalendarDay{}}}, - context_type = {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}, - type = - {turnover, #limiter_config_LimitTypeTurnover{metric = {number, #limiter_config_LimitTurnoverNumber{}}}}, - scopes = ordsets:from_list([ - {'party', #limiter_config_LimitScopeEmptyDetails{}}, - {'shop', #limiter_config_LimitScopeEmptyDetails{}}, - {'destination_field', #limiter_config_LimitScopeDestinationFieldDetails{ - field_path = [<<"path">>, <<"to">>, <<"field">>] - }} - ]), - description = <<"description">>, - currency_conversion = #limiter_config_CurrencyConversion{} - } - }, - ?assertEqual(Config, unmarshal('LimitConfigObject', Object)). - --endif. diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index cad612c1..64c13440 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -2,33 +2,17 @@ -include_lib("limiter_proto/include/limproto_limiter_thrift.hrl"). -include_lib("damsel/include/dmsl_domain_thrift.hrl"). +-include_lib("damsel/include/dmsl_limiter_config_thrift.hrl"). -include_lib("damsel/include/dmsl_domain_conf_v2_thrift.hrl"). %% Accessors --export([created_at/1]). --export([id/1]). --export([description/1]). --export([started_at/1]). --export([shard_size/1]). --export([time_range_type/1]). --export([processor_type/1]). -export([type/1]). --export([scope/1]). -export([context_type/1]). --export([op_behaviour/1]). -export([currency_conversion/1]). %% API --export([start/3]). --export([get/2]). - --export([get_limit/3]). --export([hold/2]). --export([commit/2]). --export([rollback/2]). - -export([get_values/2]). -export([get_batch/3]). -export([hold_batch/3]). @@ -36,10 +20,8 @@ -export([rollback_batch/3]). -export([calculate_shard_id/2]). --export([calculate_time_range/2]). -export([mk_scope_prefix/2]). --type woody_context() :: woody_context:ctx(). -type lim_context() :: lim_context:t(). -type processor_type() :: lim_router:processor_type(). -type processor() :: lim_router:processor(). @@ -70,7 +52,6 @@ -type config() :: #{ id := lim_id(), processor_type := processor_type(), - created_at := lim_time:timestamp_ms(), started_at := timestamp(), shard_size := shard_size(), time_range_type := time_range_type(), @@ -82,25 +63,12 @@ currency_conversion => currency_conversion() }. --type create_params() :: #{ - processor_type := processor_type(), - started_at := timestamp(), - shard_size := shard_size(), - time_range_type := time_range_type(), - context_type := context_type(), - type => limit_type(), - scope => limit_scope(), - description => description(), - op_behaviour => op_behaviour(), - current_function => currency_conversion() -}. - -type op_behaviour() :: #{operation_type() := addition | subtraction}. -type operation_type() :: invoice_payment_refund. -type currency_conversion() :: boolean(). -type lim_id() :: limproto_limiter_thrift:'LimitID'(). --type lim_version() :: dmsl_domain_thrift:'DataRevision'() | undefined. +-type lim_version() :: dmsl_domain_thrift:'DataRevision'(). -type lim_change() :: limproto_limiter_thrift:'LimitChange'(). -type limit() :: limproto_limiter_thrift:'Limit'(). -type timestamp() :: dmsl_base_thrift:'Timestamp'(). @@ -112,39 +80,11 @@ -export_type([limit_scope/0]). -export_type([time_range_type/0]). -export_type([time_range/0]). --export_type([create_params/0]). -export_type([lim_id/0]). -export_type([lim_change/0]). -export_type([limit/0]). -export_type([timestamp/0]). -%% Machinery callbacks - --behaviour(machinery). - --export([init/4]). --export([process_call/4]). --export([process_timeout/3]). --export([process_repair/4]). --export([process_notification/4]). - --type timestamped_event(T) :: - {ev, machinery:timestamp(), T}. - --type event() :: - {created, config()}. - --type args(T) :: machinery:args(T). --type machine() :: machinery:machine(event(), _). --type handler_args() :: machinery:handler_args(_). --type handler_opts() :: machinery:handler_opts(_). --type result() :: machinery:result(timestamped_event(event()), none()). - --export_type([timestamped_event/1]). --export_type([event/0]). - --define(NS, 'lim/config_v1'). - %% Handler behaviour -callback make_change( @@ -154,31 +94,6 @@ LimitContext :: lim_context() ) -> {ok, lim_liminator:limit_change()} | {error, make_change_error()}. --callback get_limit( - ID :: lim_id(), - Version :: lim_version(), - Config :: config(), - LimitContext :: lim_context() -) -> {ok, limit()} | {error, get_limit_error()}. - --callback hold( - LimitChange :: lim_change(), - Config :: config(), - LimitContext :: lim_context() -) -> ok | {error, hold_error()}. - --callback commit( - LimitChange :: lim_change(), - Config :: config(), - LimitContext :: lim_context() -) -> ok | {error, commit_error()}. - --callback rollback( - LimitChange :: lim_change(), - Config :: config(), - LimitContext :: lim_context() -) -> ok | {error, rollback_error()}. - -type make_change_error() :: lim_turnover_processor:make_change_error(). -type get_limit_error() :: lim_turnover_processor:get_limit_error(). -type hold_error() :: lim_turnover_processor:hold_error(). @@ -191,20 +106,6 @@ %% Accessors --spec created_at(config()) -> timestamp(). -created_at(#{created_at := CreatedAt}) -> - lim_time:to_rfc3339(CreatedAt). - --spec id(config()) -> lim_id(). -id(#{id := ID}) -> - ID. - --spec description(config()) -> lim_maybe:'maybe'(description()). -description(#{description := ID}) -> - ID; -description(_) -> - undefined. - -spec started_at(config()) -> timestamp(). started_at(#{started_at := Value}) -> Value. @@ -217,10 +118,6 @@ shard_size(#{shard_size := Value}) -> time_range_type(#{time_range_type := Value}) -> Value. --spec processor_type(config()) -> processor_type(). -processor_type(#{processor_type := Value}) -> - Value. - -spec type(config()) -> limit_type(). type(#{type := Value}) -> Value; @@ -237,12 +134,6 @@ scope(_) -> context_type(#{context_type := Value}) -> Value. --spec op_behaviour(config()) -> lim_maybe:'maybe'(op_behaviour()). -op_behaviour(#{op_behaviour := Value}) -> - Value; -op_behaviour(_) -> - undefined. - -spec currency_conversion(config()) -> currency_conversion(). currency_conversion(#{currency_conversion := Value}) -> Value; @@ -251,55 +142,6 @@ currency_conversion(_) -> %% --spec start(lim_id(), create_params(), lim_context()) -> {ok, config()}. -start(ID, Params, LimitContext) -> - WoodyCtx = lim_context:woody_context(LimitContext), - Config = genlib_map:compact(Params#{id => ID, created_at => lim_time:now()}), - case machinery:start(?NS, ID, [{created, Config}], get_backend(WoodyCtx)) of - ok -> - {ok, Config}; - {error, exists} -> - {ok, Machine} = machinery:get(?NS, ID, get_backend(WoodyCtx)), - {ok, collapse(Machine)} - end. - --spec get(lim_id(), lim_context()) -> {ok, config()} | {error, notfound}. -get(ID, LimitContext) -> - do(fun() -> - WoodyCtx = lim_context:woody_context(LimitContext), - Machine = unwrap(machinery:get(?NS, ID, get_backend(WoodyCtx))), - collapse(Machine) - end). - --spec get_limit(lim_id(), lim_version(), lim_context()) -> - {ok, limit()} | {error, config_error() | {processor(), get_limit_error()}}. -get_limit(ID, Version, LimitContext) -> - do(fun() -> - {Handler, Config} = unwrap(get_handler(ID, Version, LimitContext)), - unwrap(Handler, Handler:get_limit(ID, Version, Config, LimitContext)) - end). - --spec hold(lim_change(), lim_context()) -> ok | {error, config_error() | {processor(), hold_error()}}. -hold(#limiter_LimitChange{id = ID, version = Version} = LimitChange, LimitContext) -> - do(fun() -> - {Handler, Config} = unwrap(get_handler(ID, Version, LimitContext)), - unwrap(Handler, Handler:hold(LimitChange, Config, LimitContext)) - end). - --spec commit(lim_change(), lim_context()) -> ok | {error, config_error() | {processor(), commit_error()}}. -commit(#limiter_LimitChange{id = ID, version = Version} = LimitChange, LimitContext) -> - do(fun() -> - {Handler, Config} = unwrap(get_handler(ID, Version, LimitContext)), - unwrap(Handler, Handler:commit(LimitChange, Config, LimitContext)) - end). - --spec rollback(lim_change(), lim_context()) -> ok | {error, config_error() | {processor(), rollback_error()}}. -rollback(#limiter_LimitChange{id = ID, version = Version} = LimitChange, LimitContext) -> - do(fun() -> - {Handler, Config} = unwrap(get_handler(ID, Version, LimitContext)), - unwrap(Handler, Handler:rollback(LimitChange, Config, LimitContext)) - end). - -spec get_values(lim_changes(), lim_context()) -> {ok, [lim_liminator:limit_response()]} | {error, config_error() | {processor(), get_limit_error()}}. get_values(LimitChanges, LimitContext) -> @@ -330,7 +172,7 @@ hold_batch(OperationID, LimitChanges, LimitContext) -> end). -spec commit_batch(operation_id(), lim_changes(), lim_context()) -> - ok | {error, config_error() | {processor(), hold_error()}}. + ok | {error, config_error() | {processor(), commit_error()}}. commit_batch(OperationID, LimitChanges, LimitContext) -> do(fun() -> unwrap( @@ -340,7 +182,7 @@ commit_batch(OperationID, LimitChanges, LimitContext) -> end). -spec rollback_batch(operation_id(), lim_changes(), lim_context()) -> - ok | {error, config_error() | {processor(), hold_error()}}. + ok | {error, config_error() | {processor(), rollback_error()}}. rollback_batch(OperationID, LimitChanges, LimitContext) -> do(fun() -> unwrap( @@ -366,167 +208,17 @@ get_handler(ID, Version, LimitContext) -> end). -spec get_config(lim_id(), lim_version(), lim_context()) -> {ok, config()} | {error, notfound}. -get_config(ID, undefined, LimitContext) -> - get(ID, LimitContext); get_config(ID, Version, #{woody_context := WoodyContext}) -> LimitConfigRef = {limit_config, #domain_LimitConfigRef{id = ID}}, try #domain_conf_v2_VersionedObject{object = {limit_config, ConfigObject}} = dmt_client:checkout_object(Version, LimitConfigRef, #{woody_context => WoodyContext}), - {ok, lim_config_codec:unmarshal('LimitConfigObject', ConfigObject)} + {ok, unmarshal_limit_config(ConfigObject)} catch throw:#domain_conf_v2_ObjectNotFound{} -> {error, notfound} end. --spec calculate_time_range(timestamp(), config()) -> time_range(). -calculate_time_range(Timestamp, Config) -> - StartedAt = started_at(Config), - case time_range_type(Config) of - {calendar, Range} -> - calculate_calendar_time_range(Range, Timestamp, StartedAt); - {interval, _Interval} -> - erlang:error({interval_time_range_not_implemented, Config}) - end. - -calculate_calendar_time_range(Range, Timestamp, StartedAt) -> - {StartDatetime, _USec0} = lim_range_codec:parse_timestamp(StartedAt), - {CurrentDatetime, _USec1} = lim_range_codec:parse_timestamp(Timestamp), - CurrentSec = calendar:datetime_to_gregorian_seconds(CurrentDatetime), - calculate_calendar_time_range(Range, CurrentSec, CurrentDatetime, StartDatetime). - -calculate_calendar_time_range(year, CurrentSec, {CurrentDate, _CurrentTime}, {StartDate, StartTime}) -> - {_StartYear, StartMonth, StartDay} = StartDate, - {CurrentYear, _CurrentMonth, _} = CurrentDate, - ClampedStartDay = clamp_month_start_day(CurrentYear, StartMonth, StartDay), - LowerSec = calendar:datetime_to_gregorian_seconds( - {{CurrentYear, StartMonth, ClampedStartDay}, StartTime} - ), - NextYearDay = clamp_month_start_day(CurrentYear + 1, StartMonth, StartDay), - UpperSec = calendar:datetime_to_gregorian_seconds( - {{CurrentYear + 1, StartMonth, NextYearDay}, StartTime} - ), - calculate_year_time_range(CurrentSec, LowerSec, UpperSec); -calculate_calendar_time_range(month, CurrentSec, {CurrentDate, _CurrentTime}, {StartDate, StartTime}) -> - {_StartYear, _StartMonth, StartDay} = StartDate, - {CurrentYear, CurrentMonth, _} = CurrentDate, - ClampedStartDay = clamp_month_start_day(CurrentYear, CurrentMonth, StartDay), - LowerSec = calendar:datetime_to_gregorian_seconds( - {{CurrentYear, CurrentMonth, ClampedStartDay}, StartTime} - ), - UpperSec = - case CurrentMonth < 12 of - true -> - NextMonthDay = clamp_month_start_day(CurrentYear, CurrentMonth + 1, StartDay), - calendar:datetime_to_gregorian_seconds( - {{CurrentYear, CurrentMonth + 1, NextMonthDay}, StartTime} - ); - false -> - NextYearDay = clamp_month_start_day(CurrentYear + 1, CurrentMonth, StartDay), - calendar:datetime_to_gregorian_seconds( - {{CurrentYear + 1, 1, NextYearDay}, StartTime} - ) - end, - calculate_month_time_range(CurrentSec, LowerSec, UpperSec); -calculate_calendar_time_range(week, CurrentSec, {CurrentDate, _CurrentTime}, {StartDate, StartTime}) -> - StartWeekRem = calendar:date_to_gregorian_days(StartDate) rem 7, - LowerWeek = (calendar:date_to_gregorian_days(CurrentDate) div 7) * 7 + StartWeekRem, - UpperWeek = LowerWeek + 7, - LowerSec = calendar:datetime_to_gregorian_seconds( - {calendar:gregorian_days_to_date(LowerWeek), StartTime} - ), - UpperSec = calendar:datetime_to_gregorian_seconds( - {calendar:gregorian_days_to_date(UpperWeek), StartTime} - ), - calculate_week_time_range(CurrentSec, LowerSec, UpperSec); -calculate_calendar_time_range(day, CurrentSec, {CurrentDate, _CurrentTime}, {_StartDate, StartTime}) -> - Lower = calendar:date_to_gregorian_days(CurrentDate), - UpperDate = calendar:gregorian_days_to_date(Lower + 1), - LowerSec = calendar:datetime_to_gregorian_seconds({CurrentDate, StartTime}), - UpperSec = calendar:datetime_to_gregorian_seconds({UpperDate, StartTime}), - calculate_day_time_range(CurrentSec, LowerSec, UpperSec). - -clamp_month_start_day(Year, Month, StartDay) -> - Last = calendar:last_day_of_the_month(Year, Month), - case StartDay > Last of - true -> - Last; - false -> - StartDay - end. - -calculate_year_time_range(CurrentSec, LowerSec, UpperSec) when - CurrentSec >= LowerSec andalso - CurrentSec < UpperSec --> - mk_time_range(LowerSec, UpperSec); -calculate_year_time_range(CurrentSec, LowerSec, _UpperSec) when CurrentSec < LowerSec -> - {{Year, Month, Day}, Time} = calendar:gregorian_seconds_to_datetime(LowerSec), - PrevYearDay = clamp_month_start_day(Year - 1, Month, Day), - LowerDate = {Year - 1, Month, PrevYearDay}, - #{ - lower => marshal_timestamp({LowerDate, Time}), - upper => marshal_timestamp(calendar:gregorian_seconds_to_datetime(LowerSec)) - }. - -calculate_month_time_range(CurrentSec, LowerSec, UpperSec) when - CurrentSec >= LowerSec andalso - CurrentSec < UpperSec --> - mk_time_range(LowerSec, UpperSec); -calculate_month_time_range(CurrentSec, LowerSec, _UpperSec) when CurrentSec < LowerSec -> - {{Year, Month, Day}, Time} = calendar:gregorian_seconds_to_datetime(LowerSec), - LowerDate = - case Month =:= 1 of - true -> - PrevYearDay = clamp_month_start_day(Year - 1, 12, Day), - {Year - 1, 12, PrevYearDay}; - false -> - PrevMonthDay = clamp_month_start_day(Year, Month - 1, Day), - {Year, Month - 1, PrevMonthDay} - end, - #{ - lower => marshal_timestamp({LowerDate, Time}), - upper => marshal_timestamp(calendar:gregorian_seconds_to_datetime(LowerSec)) - }. - -calculate_week_time_range(CurrentSec, LowerSec, UpperSec) when - CurrentSec >= LowerSec andalso - CurrentSec < UpperSec --> - mk_time_range(LowerSec, UpperSec); -calculate_week_time_range(CurrentSec, LowerSec, _UpperSec) when CurrentSec < LowerSec -> - {Date, Time} = calendar:gregorian_seconds_to_datetime(LowerSec), - Days = calendar:date_to_gregorian_days(Date), - LowerDate = calendar:gregorian_days_to_date(Days - 7), - #{ - lower => marshal_timestamp({LowerDate, Time}), - upper => marshal_timestamp(calendar:gregorian_seconds_to_datetime(LowerSec)) - }. - -calculate_day_time_range(CurrentSec, LowerSec, UpperSec) when - CurrentSec >= LowerSec andalso - CurrentSec < UpperSec --> - mk_time_range(LowerSec, UpperSec); -calculate_day_time_range(CurrentSec, LowerSec, _UpperSec) when CurrentSec < LowerSec -> - {Date, Time} = calendar:gregorian_seconds_to_datetime(LowerSec), - Days = calendar:date_to_gregorian_days(Date), - LowerDate = calendar:gregorian_days_to_date(Days - 1), - #{ - lower => marshal_timestamp({LowerDate, Time}), - upper => marshal_timestamp(calendar:gregorian_seconds_to_datetime(LowerSec)) - }. - -mk_time_range(LowerSec, UpperSec) -> - #{ - lower => marshal_timestamp(calendar:gregorian_seconds_to_datetime(LowerSec)), - upper => marshal_timestamp(calendar:gregorian_seconds_to_datetime(UpperSec)) - }. - -marshal_timestamp(DateTime) -> - lim_range_codec:marshal(timestamp, {DateTime, 0}). - -spec calculate_shard_id(timestamp(), config()) -> shard_id(). calculate_shard_id(Timestamp, Config) -> StartedAt = started_at(Config), @@ -539,8 +231,8 @@ calculate_shard_id(Timestamp, Config) -> end. calculate_calendar_shard_id(Range, Timestamp, StartedAt, ShardSize) -> - {StartDatetime, _USec0} = lim_range_codec:parse_timestamp(StartedAt), - {CurrentDatetime, _USec1} = lim_range_codec:parse_timestamp(Timestamp), + StartDatetime = parse_timestamp(StartedAt), + CurrentDatetime = parse_timestamp(Timestamp), Units = calculate_time_units(Range, CurrentDatetime, StartDatetime), SignPrefix = mk_sign_prefix(Units), RangePrefix = mk_unit_prefix(Range), @@ -785,62 +477,124 @@ get_generic_payment_tool_resource(ContextType, LimitContext) -> Error end. -%%% Machinery callbacks - --spec init(args([event()]), machine(), handler_args(), handler_opts()) -> result(). -init(Events, _Machine, _HandlerArgs, _HandlerOpts) -> - #{ - events => emit_events(Events) - }. - --spec process_call(args(_), machine(), handler_args(), handler_opts()) -> no_return(). -process_call(_Args, _Machine, _HandlerArgs, _HandlerOpts) -> - not_implemented(call). - --spec process_timeout(machine(), handler_args(), handler_opts()) -> no_return(). -process_timeout(_Machine, _HandlerArgs, _HandlerOpts) -> - not_implemented(timeout). - --spec process_repair(args(_), machine(), handler_args(), handler_opts()) -> no_return(). -process_repair(_Args, _Machine, _HandlerArgs, _HandlerOpts) -> - not_implemented(repair). - --spec process_notification(args(_), machine(), handler_args(), handler_opts()) -> no_return(). -process_notification(_Args, _Machine, _HandlerArgs, _HandlerOpts) -> - not_implemented(notification). - -%%% Internal functions - -emit_events(Events) -> - emit_timestamped_events(Events, lim_time:machinery_now()). - -emit_timestamped_events(Events, Ts) -> - [{ev, Ts, Body} || Body <- Events]. - -collapse(#{history := History}) -> - lists:foldl(fun(Ev, St) -> apply_event(Ev, St) end, undefined, History). - --spec get_backend(woody_context()) -> machinery_mg_backend:backend(). -get_backend(WoodyCtx) -> - lim_utils:get_backend(?NS, WoodyCtx). - --spec not_implemented(any()) -> no_return(). -not_implemented(What) -> - erlang:error({not_implemented, What}). - -%% - -%% - --spec apply_event(machinery:event(timestamped_event(event())), lim_maybe:'maybe'(config())) -> config(). -apply_event({_ID, _Ts, {ev, _EvTs, Event}}, Config) -> - apply_event_(Event, Config). - --spec apply_event_(event(), lim_maybe:'maybe'(config())) -> config(). -apply_event_({created, Config}, undefined) -> - Config. +parse_timestamp(Bin) -> + try + MicroSeconds = genlib_rfc3339:parse(Bin, microsecond), + case genlib_rfc3339:is_utc(Bin) of + false -> + erlang:error({bad_timestamp, not_utc}, [Bin]); + true -> + calendar:system_time_to_universal_time(MicroSeconds, microsecond) + end + catch + error:Error:St -> + erlang:raise(error, {bad_timestamp, Bin, Error}, St) + end. -%% +unmarshal_limit_config(#domain_LimitConfigObject{ + ref = #domain_LimitConfigRef{id = ID}, + data = #limiter_config_LimitConfig{ + processor_type = ProcessorType, + started_at = StartedAt, + shard_size = ShardSize, + time_range_type = TimeRangeType, + context_type = ContextType, + type = Type, + scopes = Scopes, + description = Description, + op_behaviour = OpBehaviour, + currency_conversion = CurrencyConversion + } +}) -> + genlib_map:compact(#{ + id => ID, + processor_type => ProcessorType, + started_at => StartedAt, + shard_size => ShardSize, + time_range_type => unmarshal_time_range_type(TimeRangeType), + context_type => unmarshal_context_type(ContextType), + type => maybe_apply(Type, fun unmarshal_type/1), + scope => maybe_apply(Scopes, fun unmarshal_scope/1), + description => Description, + op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1), + currency_conversion => CurrencyConversion =/= undefined + }). + +unmarshal_time_range_type({calendar, CalendarType}) -> + {calendar, unmarshal_calendar_time_range_type(CalendarType)}; +unmarshal_time_range_type({interval, #limiter_config_TimeRangeTypeInterval{amount = Amount}}) -> + {interval, Amount}. + +unmarshal_calendar_time_range_type({day, _}) -> + day; +unmarshal_calendar_time_range_type({week, _}) -> + week; +unmarshal_calendar_time_range_type({month, _}) -> + month; +unmarshal_calendar_time_range_type({year, _}) -> + year. + +unmarshal_context_type({payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}) -> + payment_processing; +unmarshal_context_type({withdrawal_processing, #limiter_config_LimitContextTypeWithdrawalProcessing{}}) -> + withdrawal_processing. + +unmarshal_type({turnover, #limiter_config_LimitTypeTurnover{metric = Metric}}) -> + {turnover, maybe_apply(Metric, fun unmarshal_turnover_metric/1, number)}. + +unmarshal_turnover_metric({number, _}) -> + number; +unmarshal_turnover_metric({amount, #limiter_config_LimitTurnoverAmount{currency = Currency}}) -> + {amount, Currency}. + +unmarshal_scope({single, Type}) -> + ordsets:from_list([unmarshal_scope_type(Type)]); +unmarshal_scope({multi, Types}) -> + ordsets:from_list(lists:map(fun unmarshal_scope_type/1, ordsets:to_list(Types))); +unmarshal_scope(Types) when is_list(Types) -> + ordsets:from_list(lists:map(fun unmarshal_scope_type/1, ordsets:to_list(Types))). + +unmarshal_scope_type({party, _}) -> + party; +unmarshal_scope_type({shop, _}) -> + shop; +unmarshal_scope_type({wallet, _}) -> + wallet; +unmarshal_scope_type({payment_tool, _}) -> + payment_tool; +unmarshal_scope_type({provider, _}) -> + provider; +unmarshal_scope_type({terminal, _}) -> + terminal; +unmarshal_scope_type({payer_contact_email, _}) -> + payer_contact_email; +unmarshal_scope_type({destination_field, #limiter_config_LimitScopeDestinationFieldDetails{field_path = FieldPath}}) -> + %% Domain config variant clause + {destination_field, FieldPath}; +unmarshal_scope_type({sender, _}) -> + sender; +unmarshal_scope_type({receiver, _}) -> + receiver. + +unmarshal_op_behaviour(#limiter_config_OperationLimitBehaviour{invoice_payment_refund = Refund}) -> + genlib_map:compact(#{ + invoice_payment_refund => maybe_apply(Refund, fun unmarshal_behaviour/1) + }). + +unmarshal_behaviour({subtraction, #limiter_config_Subtraction{}}) -> + subtraction; +unmarshal_behaviour({addition, #limiter_config_Addition{}}) -> + addition. + +maybe_apply(undefined, _) -> + undefined; +maybe_apply(Value, Fun) -> + Fun(Value). + +maybe_apply(undefined, _, Default) -> + Default; +maybe_apply(Value, Fun, _Default) -> + Fun(Value). -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). @@ -853,93 +607,49 @@ apply_event_({created, Config}, undefined) -> -spec test() -> _. --spec check_sign_prefix_test() -> _. +-spec unmarshal_config_object_test() -> _. +unmarshal_config_object_test() -> + Config = #{ + id => <<"id">>, + processor_type => <<"type">>, + started_at => <<"2000-01-01T00:00:00Z">>, + shard_size => 7, + time_range_type => {calendar, day}, + context_type => payment_processing, + type => {turnover, number}, + scope => ordsets:from_list([party, shop, {destination_field, [<<"path">>, <<"to">>, <<"field">>]}]), + description => <<"description">>, + currency_conversion => true + }, + Object = #domain_LimitConfigObject{ + ref = #domain_LimitConfigRef{id = <<"id">>}, + data = #limiter_config_LimitConfig{ + processor_type = <<"type">>, + started_at = <<"2000-01-01T00:00:00Z">>, + shard_size = 7, + time_range_type = {calendar, {day, #limiter_config_TimeRangeTypeCalendarDay{}}}, + context_type = {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}, + type = + {turnover, #limiter_config_LimitTypeTurnover{metric = {number, #limiter_config_LimitTurnoverNumber{}}}}, + scopes = ordsets:from_list([ + {'party', #limiter_config_LimitScopeEmptyDetails{}}, + {'shop', #limiter_config_LimitScopeEmptyDetails{}}, + {'destination_field', #limiter_config_LimitScopeDestinationFieldDetails{ + field_path = [<<"path">>, <<"to">>, <<"field">>] + }} + ]), + description = <<"description">>, + currency_conversion = #limiter_config_CurrencyConversion{} + } + }, + ?assertEqual(Config, unmarshal_limit_config(Object)). +-spec check_sign_prefix_test() -> _. check_sign_prefix_test() -> ?assertEqual(<<"past">>, mk_sign_prefix(-10)), ?assertEqual(<<"future">>, mk_sign_prefix(0)), ?assertEqual(<<"future">>, mk_sign_prefix(10)). --spec check_calculate_day_time_range_test() -> _. -check_calculate_day_time_range_test() -> - StartedAt1 = <<"2000-01-01T00:00:00Z">>, - ?assertEqual( - #{lower => <<"2000-01-01T00:00:00Z">>, upper => <<"2000-01-02T00:00:00Z">>}, - calculate_calendar_time_range(day, <<"2000-01-01T02:00:00Z">>, StartedAt1) - ), - ?assertEqual( - #{lower => <<"1999-12-31T00:00:00Z">>, upper => <<"2000-01-01T00:00:00Z">>}, - calculate_calendar_time_range(day, <<"1999-12-31T02:00:00Z">>, StartedAt1) - ), - ?assertEqual( - #{lower => <<"2000-01-10T00:00:00Z">>, upper => <<"2000-01-11T00:00:00Z">>}, - calculate_calendar_time_range(day, <<"2000-01-10T02:00:00Z">>, StartedAt1) - ), - ?assertEqual( - #{lower => <<"1999-12-31T03:00:00Z">>, upper => <<"2000-01-01T03:00:00Z">>}, - calculate_calendar_time_range(day, <<"2000-01-01T02:00:00Z">>, <<"2000-01-01T03:00:00Z">>) - ). - --spec check_calculate_week_time_range_test() -> _. -check_calculate_week_time_range_test() -> - StartedAt = <<"2000-01-01T00:00:00Z">>, - ?assertEqual( - #{lower => <<"2000-01-01T00:00:00Z">>, upper => <<"2000-01-08T00:00:00Z">>}, - calculate_calendar_time_range(week, <<"2000-01-01T02:00:00Z">>, StartedAt) - ), - ?assertEqual( - #{lower => <<"1999-12-25T00:00:00Z">>, upper => <<"2000-01-01T00:00:00Z">>}, - calculate_calendar_time_range(week, <<"1999-12-31T02:00:00Z">>, StartedAt) - ), - ?assertEqual( - #{lower => <<"2000-09-30T00:00:00Z">>, upper => <<"2000-10-07T00:00:00Z">>}, - calculate_calendar_time_range(week, <<"2000-10-03T02:00:00Z">>, StartedAt) - ), - ?assertEqual( - #{lower => <<"1999-12-25T03:00:00Z">>, upper => <<"2000-01-01T03:00:00Z">>}, - calculate_calendar_time_range(week, <<"2000-01-01T02:00:00Z">>, <<"2000-01-01T03:00:00Z">>) - ). - --spec check_calculate_month_time_range_test() -> _. -check_calculate_month_time_range_test() -> - StartedAt = <<"2000-01-01T00:00:00Z">>, - ?assertEqual( - #{lower => <<"2000-01-01T00:00:00Z">>, upper => <<"2000-02-01T00:00:00Z">>}, - calculate_calendar_time_range(month, <<"2000-01-01T02:00:00Z">>, StartedAt) - ), - ?assertEqual( - #{lower => <<"1999-12-01T00:00:00Z">>, upper => <<"2000-01-01T00:00:00Z">>}, - calculate_calendar_time_range(month, <<"1999-12-31T02:00:00Z">>, StartedAt) - ), - ?assertEqual( - #{lower => <<"2000-10-01T00:00:00Z">>, upper => <<"2000-11-01T00:00:00Z">>}, - calculate_calendar_time_range(month, <<"2000-10-03T02:00:00Z">>, StartedAt) - ), - ?assertEqual( - #{lower => <<"1999-12-01T03:00:00Z">>, upper => <<"2000-01-01T03:00:00Z">>}, - calculate_calendar_time_range(month, <<"2000-01-01T02:00:00Z">>, <<"2000-01-01T03:00:00Z">>) - ). - --spec check_calculate_year_time_range_test() -> _. -check_calculate_year_time_range_test() -> - StartedAt = <<"2000-01-01T00:00:00Z">>, - ?assertEqual( - #{lower => <<"2000-01-01T00:00:00Z">>, upper => <<"2001-01-01T00:00:00Z">>}, - calculate_calendar_time_range(year, <<"2000-01-01T02:00:00Z">>, StartedAt) - ), - ?assertEqual( - #{lower => <<"1999-01-01T00:00:00Z">>, upper => <<"2000-01-01T00:00:00Z">>}, - calculate_calendar_time_range(year, <<"1999-12-31T02:00:00Z">>, StartedAt) - ), - ?assertEqual( - #{lower => <<"2010-01-01T00:00:00Z">>, upper => <<"2011-01-01T00:00:00Z">>}, - calculate_calendar_time_range(year, <<"2010-10-03T02:00:00Z">>, StartedAt) - ), - ?assertEqual( - #{lower => <<"1999-01-01T03:00:00Z">>, upper => <<"2000-01-01T03:00:00Z">>}, - calculate_calendar_time_range(year, <<"2000-01-01T02:00:00Z">>, <<"2000-01-01T03:00:00Z">>) - ). - -spec check_calculate_day_shard_id_test() -> _. check_calculate_day_shard_id_test() -> StartedAt1 = <<"2000-01-01T00:00:00Z">>, diff --git a/apps/limiter/src/lim_config_machinery_schema.erl b/apps/limiter/src/lim_config_machinery_schema.erl deleted file mode 100644 index 9be2d1f6..00000000 --- a/apps/limiter/src/lim_config_machinery_schema.erl +++ /dev/null @@ -1,114 +0,0 @@ --module(lim_config_machinery_schema). - -%% Storage schema behaviour --behaviour(machinery_mg_schema). - --export([get_version/1]). --export([marshal/3]). --export([unmarshal/3]). - -%% Constants - --define(CURRENT_EVENT_FORMAT_VERSION, 1). - -%% Internal types - --type type() :: machinery_mg_schema:t(). --type value(T) :: machinery_mg_schema:v(T). --type value_type() :: machinery_mg_schema:vt(). --type context() :: machinery_mg_schema:context(). - --type event() :: lim_config_machine:timestamped_event(lim_config_machine:event()). --type aux_state() :: term(). --type call_args() :: term(). --type call_response() :: term(). - --type data() :: - aux_state() - | event() - | call_args() - | call_response(). - -%% machinery_mg_schema callbacks - --spec get_version(value_type()) -> machinery_mg_schema:version(). -get_version(event) -> - ?CURRENT_EVENT_FORMAT_VERSION; -get_version(aux_state) -> - undefined. - --spec marshal(type(), value(data()), context()) -> {machinery_msgpack:t(), context()}. -marshal({event, FormatVersion}, TimestampedChange, Context) -> - marshal_event(FormatVersion, TimestampedChange, Context); -marshal(T, V, C) when - T =:= {args, init} orelse - T =:= {args, call} orelse - T =:= {args, repair} orelse - T =:= {aux_state, undefined} orelse - T =:= {response, call} orelse - T =:= {response, {repair, success}} orelse - T =:= {response, {repair, failure}} --> - machinery_mg_schema_generic:marshal(T, V, C). - --spec unmarshal(type(), machinery_msgpack:t(), context()) -> {data(), context()}. -unmarshal({event, FormatVersion}, EncodedChange, Context) -> - unmarshal_event(FormatVersion, EncodedChange, Context); -unmarshal(T, V, C) when - T =:= {args, init} orelse - T =:= {args, call} orelse - T =:= {args, repair} orelse - T =:= {aux_state, undefined} orelse - T =:= {response, call} orelse - T =:= {response, {repair, success}} orelse - T =:= {response, {repair, failure}} --> - machinery_mg_schema_generic:unmarshal(T, V, C). - -%% Internals - --spec marshal_event(machinery_mg_schema:version(), event(), context()) -> {machinery_msgpack:t(), context()}. -marshal_event(1, TimestampedChange, Context) -> - ThriftChange = lim_config_codec:marshal(timestamped_change, TimestampedChange), - Type = {struct, struct, {limproto_config_thrift, 'TimestampedChange'}}, - {{bin, lim_proto_utils:serialize(Type, ThriftChange)}, Context}. - --spec unmarshal_event(machinery_mg_schema:version(), machinery_msgpack:t(), context()) -> {event(), context()}. -unmarshal_event(1, EncodedChange, Context) -> - {bin, EncodedThriftChange} = EncodedChange, - Type = {struct, struct, {limproto_config_thrift, 'TimestampedChange'}}, - ThriftChange = lim_proto_utils:deserialize(Type, EncodedThriftChange), - {lim_config_codec:unmarshal(timestamped_change, ThriftChange), Context}. - -%% - --ifdef(TEST). --include_lib("eunit/include/eunit.hrl"). - --spec test() -> _. - --spec marshal_unmarshal_created_test() -> _. - -marshal_unmarshal_created_test() -> - ID = <<"id">>, - Created = - {created, #{ - id => ID, - processor_type => <<"type">>, - created_at => lim_time:now(), - started_at => <<"2000-01-01T00:00:00Z">>, - shard_size => 7, - time_range_type => {calendar, day}, - context_type => payment_processing, - type => {turnover, {amount, <<"RUB">>}}, - scope => ordsets:from_list([party]), - description => <<"description">>, - currency_conversion => false - }}, - Context = #{machine_id => ID, machine_ns => config}, - Event = {ev, lim_time:machinery_now(), Created}, - {Marshaled, _} = marshal_event(1, Event, Context), - {Unmarshaled, _} = unmarshal_event(1, Marshaled, Context), - ?assertEqual(Event, Unmarshaled). - --endif. diff --git a/apps/limiter/src/lim_configurator.erl b/apps/limiter/src/lim_configurator.erl deleted file mode 100644 index f23c862c..00000000 --- a/apps/limiter/src/lim_configurator.erl +++ /dev/null @@ -1,131 +0,0 @@ --module(lim_configurator). - --include_lib("limiter_proto/include/limproto_configurator_thrift.hrl"). --include_lib("damsel/include/dmsl_base_thrift.hrl"). - -%% Woody handler - --behaviour(woody_server_thrift_handler). - --export([handle_function/4]). - --define(DEFAULT_CURRENCY, <<"RUB">>). - -%% - --spec handle_function(woody:func(), woody:args(), woody_context:ctx(), woody:options()) -> {ok, woody:result()}. -handle_function(Fn, Args, WoodyCtx, Opts) -> - LimitContext = lim_context:create(WoodyCtx), - scoper:scope( - configurator, - fun() -> handle_function_(Fn, Args, LimitContext, Opts) end - ). - --spec handle_function_(woody:func(), woody:args(), lim_context:t(), woody:options()) -> {ok, woody:result()}. -handle_function_( - 'CreateLegacy', - {#configurator_LimitCreateParams{ - id = ID, - name = Name, - description = Description, - started_at = StartedAt, - op_behaviour = OpBehaviour - }}, - LimitContext, - _Opts -) -> - case mk_limit_config(Name) of - {ok, Config} -> - {ok, LimitConfig} = lim_config_machine:start( - ID, - genlib_map:compact(Config#{ - description => Description, - started_at => StartedAt, - op_behaviour => lim_config_codec:maybe_apply( - OpBehaviour, - fun lim_config_codec:unmarshal_op_behaviour/1 - ) - }), - LimitContext - ), - {ok, lim_config_codec:marshal_config(LimitConfig)}; - {error, {name, notfound}} -> - woody_error:raise( - business, - #configurator_LimitConfigNameNotFound{} - ) - end; -handle_function_('Create', {Params}, LimitContext, _Opts) -> - Config = #{id := ID, type := Type} = lim_config_codec:unmarshal_params(Params), - {ok, LimitConfig} = lim_config_machine:start( - ID, - genlib_map:compact(Config#{ - processor_type => map_type(Type) - }), - LimitContext - ), - {ok, lim_config_codec:marshal_config(LimitConfig)}; -handle_function_('Get', {LimitID}, LimitContext, _Opts) -> - scoper:add_meta(#{limit_config_id => LimitID}), - case lim_config_machine:get(LimitID, LimitContext) of - {ok, LimitConfig} -> - {ok, lim_config_codec:marshal_config(LimitConfig)}; - {error, notfound} -> - woody_error:raise(business, #configurator_LimitConfigNotFound{}) - end. - -map_type({turnover, _}) -> - <<"TurnoverProcessor">>; -map_type(_) -> - woody_error:raise( - business, - #base_InvalidRequest{errors = [<<"Config type not found.">>]} - ). - -mk_limit_config(<<"ShopDayTurnover">>) -> - {ok, #{ - processor_type => <<"TurnoverProcessor">>, - type => {turnover, {amount, ?DEFAULT_CURRENCY}}, - scope => ordsets:from_list([shop]), - shard_size => 7, - context_type => payment_processing, - time_range_type => {calendar, day} - }}; -mk_limit_config(<<"PartyDayTurnover">>) -> - {ok, #{ - processor_type => <<"TurnoverProcessor">>, - type => {turnover, {amount, ?DEFAULT_CURRENCY}}, - scope => ordsets:from_list([party]), - shard_size => 7, - context_type => payment_processing, - time_range_type => {calendar, day} - }}; -mk_limit_config(<<"ShopMonthTurnover">>) -> - {ok, #{ - processor_type => <<"TurnoverProcessor">>, - type => {turnover, {amount, ?DEFAULT_CURRENCY}}, - scope => ordsets:from_list([shop]), - shard_size => 12, - context_type => payment_processing, - time_range_type => {calendar, month} - }}; -mk_limit_config(<<"PartyMonthTurnover">>) -> - {ok, #{ - processor_type => <<"TurnoverProcessor">>, - type => {turnover, {amount, ?DEFAULT_CURRENCY}}, - scope => ordsets:from_list([party]), - shard_size => 12, - context_type => payment_processing, - time_range_type => {calendar, month} - }}; -mk_limit_config(<<"GlobalMonthTurnover">>) -> - {ok, #{ - processor_type => <<"TurnoverProcessor">>, - type => {turnover, {amount, ?DEFAULT_CURRENCY}}, - scope => ordsets:new(), - shard_size => 12, - context_type => payment_processing, - time_range_type => {calendar, month} - }}; -mk_limit_config(_) -> - {error, {name, notfound}}. diff --git a/apps/limiter/src/lim_context.erl b/apps/limiter/src/lim_context.erl index 230cd026..b1153bc1 100644 --- a/apps/limiter/src/lim_context.erl +++ b/apps/limiter/src/lim_context.erl @@ -1,7 +1,6 @@ -module(lim_context). -include_lib("limiter_proto/include/limproto_limiter_thrift.hrl"). --include_lib("damsel/include/dmsl_limiter_config_thrift.hrl"). -export([create/1]). -export([woody_context/1]). @@ -10,18 +9,13 @@ -export([get_value/3]). -export([set_context/2]). --export([set_clock/2]). - --export([clock/1]). -type woody_context() :: woody_context:ctx(). -type limit_context() :: limproto_limiter_thrift:'LimitContext'(). --type clock() :: limproto_limiter_thrift:'Clock'(). -type t() :: #{ woody_context => woody_context(), - context => limit_context(), - clock => clock() + context => limit_context() }. -type context_type() :: payment_processing | withdrawal_processing. @@ -54,20 +48,10 @@ create(WoodyContext) -> woody_context(Context) -> maps:get(woody_context, Context). --spec clock(t()) -> {ok, clock()} | {error, notfound}. -clock(#{clock := Clock}) -> - {ok, Clock}; -clock(_) -> - {error, notfound}. - -spec set_context(limit_context(), t()) -> t(). set_context(Context, LimContext) -> LimContext#{context => Context}. --spec set_clock(clock(), t()) -> t(). -set_clock(Clock, LimContext) -> - LimContext#{clock => Clock}. - -spec get_operation(context_type(), t()) -> {ok, context_operation()} | {error, notfound | operation_context_not_supported_error()}. get_operation(Type, Context) -> @@ -93,16 +77,14 @@ get_value(Type, ValueName, Context) -> get_operation_context(payment_processing, #{context := #limiter_LimitContext{payment_processing = undefined}}) -> {error, - {operation_context_not_supported, - {withdrawal_processing, #limiter_config_LimitContextTypeWithdrawalProcessing{}}}}; + {operation_context_not_supported, {withdrawal_processing, #limiter_LimitContextTypeWithdrawalProcessing{}}}}; get_operation_context( payment_processing, #{context := #limiter_LimitContext{payment_processing = PayprocContext}} ) -> {ok, lim_payproc_context, PayprocContext}; get_operation_context(withdrawal_processing, #{context := #limiter_LimitContext{withdrawal_processing = undefined}}) -> - {error, - {operation_context_not_supported, {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}}}}; + {error, {operation_context_not_supported, {payment_processing, #limiter_LimitContextTypePaymentProcessing{}}}}; get_operation_context( withdrawal_processing, #{context := #limiter_LimitContext{withdrawal_processing = WithdrawalContext}} diff --git a/apps/limiter/src/lim_handler.erl b/apps/limiter/src/lim_handler.erl index d09d4897..045dbfe4 100644 --- a/apps/limiter/src/lim_handler.erl +++ b/apps/limiter/src/lim_handler.erl @@ -15,7 +15,6 @@ -type lim_context() :: lim_context:t(). --define(LIMIT_CHANGE(ID), #limiter_LimitChange{id = ID}). -define(LIMIT_REQUEST(ID, Changes), #limiter_LimitRequest{operation_id = ID, limit_changes = Changes}). %% @@ -29,61 +28,6 @@ handle_function(Fn, Args, WoodyCtx, Opts) -> ). -spec handle_function_(woody:func(), woody:args(), lim_context(), woody:options()) -> {ok, woody:result()}. -handle_function_('Get', {LimitID, Clock, Context}, LimitContext, Opts) -> - handle_function_('GetVersioned', {LimitID, undefined, Clock, Context}, LimitContext, Opts); -handle_function_('GetVersioned', {LimitID, Version, Clock, Context}, LimitContext, _Opts) -> - scoper:add_meta(#{limit_id => LimitID}), - case - lim_config_machine:get_limit( - LimitID, - Version, - lim_context:set_context(Context, lim_context:set_clock(Clock, LimitContext)) - ) - of - {ok, Limit} -> - {ok, Limit}; - {error, Error} -> - handle_get_error(Error) - end; -handle_function_('Hold', {LimitChange = ?LIMIT_CHANGE(LimitID), Clock, Context}, LimitContext, _Opts) -> - scoper:add_meta(#{limit_id => LimitID}), - case - lim_config_machine:hold( - LimitChange, - lim_context:set_context(Context, lim_context:set_clock(Clock, LimitContext)) - ) - of - ok -> - {ok, {vector, #limiter_VectorClock{state = <<>>}}}; - {error, Error} -> - handle_hold_error(Error) - end; -handle_function_('Commit', {LimitChange = ?LIMIT_CHANGE(LimitID), Clock, Context}, LimitContext, _Opts) -> - scoper:add_meta(#{limit_id => LimitID}), - case - lim_config_machine:commit( - LimitChange, - lim_context:set_context(Context, lim_context:set_clock(Clock, LimitContext)) - ) - of - ok -> - {ok, {vector, #limiter_VectorClock{state = <<>>}}}; - {error, Error} -> - handle_commit_error(Error) - end; -handle_function_('Rollback', {LimitChange = ?LIMIT_CHANGE(LimitID), Clock, Context}, LimitContext, _Opts) -> - scoper:add_meta(#{limit_id => LimitID}), - case - lim_config_machine:rollback( - LimitChange, - lim_context:set_context(Context, lim_context:set_clock(Clock, LimitContext)) - ) - of - ok -> - {ok, {vector, #limiter_VectorClock{state = <<>>}}}; - {error, Error} -> - handle_rollback_error(Error) - end; handle_function_('GetValues', {?LIMIT_REQUEST(OperationID, Changes), Context}, LimitContext, _Opts) -> scoper:add_meta(#{operation_id => OperationID}), case diff --git a/apps/limiter/src/lim_liminator.erl b/apps/limiter/src/lim_liminator.erl index 39706583..da65dd17 100644 --- a/apps/limiter/src/lim_liminator.erl +++ b/apps/limiter/src/lim_liminator.erl @@ -73,6 +73,7 @@ do(Op, Arg, LimitContext) -> end. %% + -spec call(woody:func(), woody:args(), lim_context()) -> woody:result(). call(Function, Args, LimitContext) -> WoodyContext = lim_context:woody_context(LimitContext), diff --git a/apps/limiter/src/lim_posting.erl b/apps/limiter/src/lim_posting.erl deleted file mode 100644 index c27a362e..00000000 --- a/apps/limiter/src/lim_posting.erl +++ /dev/null @@ -1,32 +0,0 @@ --module(lim_posting). - --include_lib("damsel/include/dmsl_accounter_thrift.hrl"). - --export([new/3]). --export([reverse/1]). - --type posting() :: lim_accounting:posting(). - --spec new(lim_range_machine:time_range_ext(), lim_body:amount(), lim_body:currency()) -> - posting(). -new(#{account_id_from := From, account_id_to := To}, Amount, Currency) -> - reverse_negative_posting(#accounter_Posting{ - from_id = From, - to_id = To, - amount = Amount, - currency_sym_code = Currency, - description = <<>> - }). - -reverse_negative_posting(#accounter_Posting{amount = Amount} = Posting) when Amount < 0 -> - PostingReversed = reverse(Posting), - PostingReversed#accounter_Posting{amount = -Amount}; -reverse_negative_posting(Posting) -> - Posting. - --spec reverse(posting()) -> posting(). -reverse(#accounter_Posting{from_id = AccountFrom, to_id = AccountTo} = Posting) -> - Posting#accounter_Posting{ - from_id = AccountTo, - to_id = AccountFrom - }. diff --git a/apps/limiter/src/lim_range_codec.erl b/apps/limiter/src/lim_range_codec.erl deleted file mode 100644 index bccfa918..00000000 --- a/apps/limiter/src/lim_range_codec.erl +++ /dev/null @@ -1,179 +0,0 @@ --module(lim_range_codec). - --include_lib("limiter_proto/include/limproto_range_thrift.hrl"). --include_lib("limiter_proto/include/limproto_timerange_thrift.hrl"). - --export([marshal/2]). --export([unmarshal/2]). --export([parse_timestamp/1]). - -%% Types - --type type_name() :: atom() | {list, atom()} | {set, atom()}. - --type encoded_value() :: encoded_value(any()). --type encoded_value(T) :: T. - --type decoded_value() :: decoded_value(any()). --type decoded_value(T) :: T. - -%% API - --spec marshal(type_name(), decoded_value()) -> encoded_value(). -marshal(timestamped_change, {ev, Timestamp, Change}) -> - #range_TimestampedChange{ - change = marshal(change, Change), - occured_at = marshal(timestamp, Timestamp) - }; -marshal(change, {created, Range}) -> - {created, #range_CreatedChange{limit_range = marshal(range, Range)}}; -marshal(change, {time_range_created, TimeRange}) -> - {time_range_created, #range_TimeRangeCreatedChange{time_range = marshal(time_range, TimeRange)}}; -marshal( - range, - #{ - id := ID, - type := Type, - created_at := CreatedAt - } = Range -) -> - #range_LimitRange{ - id = ID, - type = marshal(time_range_type, Type), - created_at = CreatedAt, - currency = lim_range_machine:currency(Range) - }; -marshal(time_range, #{ - upper := Upper, - lower := Lower, - account_id_from := AccountIDFrom, - account_id_to := AccountIDTo -}) -> - #timerange_TimeRange{ - upper = Upper, - lower = Lower, - account_id_from = AccountIDFrom, - account_id_to = AccountIDTo - }; -marshal(time_range_type, {calendar, SubType}) -> - {calendar, marshal(time_range_sub_type, SubType)}; -marshal(time_range_type, {interval, Interval}) -> - {interval, #timerange_TimeRangeTypeInterval{amount = Interval}}; -marshal(time_range_sub_type, year) -> - {year, #timerange_TimeRangeTypeCalendarYear{}}; -marshal(time_range_sub_type, month) -> - {month, #timerange_TimeRangeTypeCalendarMonth{}}; -marshal(time_range_sub_type, week) -> - {week, #timerange_TimeRangeTypeCalendarWeek{}}; -marshal(time_range_sub_type, day) -> - {day, #timerange_TimeRangeTypeCalendarDay{}}; -marshal(timestamp, {DateTime, USec}) -> - DateTimeinSeconds = genlib_time:daytime_to_unixtime(DateTime), - {TimeinUnit, Unit} = - case USec of - 0 -> - {DateTimeinSeconds, second}; - USec -> - MicroSec = erlang:convert_time_unit(DateTimeinSeconds, second, microsecond), - {MicroSec + USec, microsecond} - end, - genlib_rfc3339:format_relaxed(TimeinUnit, Unit). - --spec unmarshal(type_name(), encoded_value()) -> decoded_value(). -unmarshal(timestamped_change, TimestampedChange) -> - Timestamp = unmarshal(timestamp, TimestampedChange#range_TimestampedChange.occured_at), - Change = unmarshal(change, TimestampedChange#range_TimestampedChange.change), - {ev, Timestamp, Change}; -unmarshal(change, {created, #range_CreatedChange{limit_range = Range}}) -> - {created, unmarshal(range, Range)}; -unmarshal(change, {time_range_created, #range_TimeRangeCreatedChange{time_range = Range}}) -> - {time_range_created, unmarshal(time_range, Range)}; -unmarshal(range, #range_LimitRange{ - id = ID, - type = Type, - created_at = CreatedAt, - currency = Currency -}) -> - genlib_map:compact(#{ - id => ID, - type => unmarshal(time_range_type, Type), - created_at => CreatedAt, - currency => Currency - }); -unmarshal(time_range, #timerange_TimeRange{ - upper = Upper, - lower = Lower, - account_id_from = AccountIDFrom, - account_id_to = AccountIDTo -}) -> - #{ - upper => Upper, - lower => Lower, - account_id_from => AccountIDFrom, - account_id_to => AccountIDTo - }; -unmarshal(time_range_type, {calendar, SubType}) -> - {calendar, unmarshal(time_range_sub_type, SubType)}; -unmarshal(time_range_type, {interval, #timerange_TimeRangeTypeInterval{amount = Interval}}) -> - {interval, Interval}; -unmarshal(time_range_sub_type, {year, _}) -> - year; -unmarshal(time_range_sub_type, {month, _}) -> - month; -unmarshal(time_range_sub_type, {week, _}) -> - week; -unmarshal(time_range_sub_type, {day, _}) -> - day; -unmarshal(timestamp, Timestamp) when is_binary(Timestamp) -> - parse_timestamp(Timestamp). - --spec parse_timestamp(binary()) -> machinery:timestamp(). -parse_timestamp(Bin) -> - try - MicroSeconds = genlib_rfc3339:parse(Bin, microsecond), - case genlib_rfc3339:is_utc(Bin) of - false -> - erlang:error({bad_timestamp, not_utc}, [Bin]); - true -> - USec = MicroSeconds rem 1000000, - DateTime = calendar:system_time_to_universal_time(MicroSeconds, microsecond), - {DateTime, USec} - end - catch - error:Error:St -> - erlang:raise(error, {bad_timestamp, Bin, Error}, St) - end. - -%% - --ifdef(TEST). --include_lib("eunit/include/eunit.hrl"). - --spec test() -> _. - --spec marshal_unmarshal_created_test() -> _. - -marshal_unmarshal_created_test() -> - Created = - {created, #{ - id => <<"id">>, - type => {calendar, day}, - created_at => <<"2000-01-01T00:00:00Z">>, - currency => <<"USD">> - }}, - Event = {ev, lim_time:machinery_now(), Created}, - ?assertEqual(Event, unmarshal(timestamped_change, marshal(timestamped_change, Event))). - --spec marshal_unmarshal_time_range_created_test() -> _. -marshal_unmarshal_time_range_created_test() -> - TimeRangeCreated = - {time_range_created, #{ - account_id_from => 25, - account_id_to => 175, - upper => <<"2000-01-01T00:00:00Z">>, - lower => <<"2000-01-01T00:00:00Z">> - }}, - Event = {ev, lim_time:machinery_now(), TimeRangeCreated}, - ?assertEqual(Event, unmarshal(timestamped_change, marshal(timestamped_change, Event))). - --endif. diff --git a/apps/limiter/src/lim_range_machine.erl b/apps/limiter/src/lim_range_machine.erl deleted file mode 100644 index 7dc8a336..00000000 --- a/apps/limiter/src/lim_range_machine.erl +++ /dev/null @@ -1,260 +0,0 @@ --module(lim_range_machine). - -%% Accessors - --export([id/1]). --export([created_at/1]). --export([type/1]). --export([ranges/1]). --export([currency/1]). - -%% API - --export([get/2]). --export([ensure_exists/3]). --export([get_range/2]). --export([get_range_balance/3]). - -%% Machinery callbacks - --behaviour(machinery). - --export([init/4]). --export([process_call/4]). --export([process_timeout/3]). --export([process_repair/4]). --export([process_notification/4]). - --type args(T) :: machinery:args(T). --type machine() :: machinery:machine(event(), _). --type handler_args() :: machinery:handler_args(_). --type handler_opts() :: machinery:handler_opts(_). --type result() :: machinery:result(timestamped_event(event()), none()). --type response(T) :: machinery:response(T). - -%% - --type woody_context() :: woody_context:ctx(). --type lim_context() :: lim_context:t(). --type timestamp() :: lim_config_machine:timestamp(). --type id() :: binary(). --type time_range_type() :: lim_config_machine:time_range_type(). --type time_range() :: lim_config_machine:time_range(). --type currency() :: lim_body:currency(). - --type limit_range_state() :: #{ - id := id(), - type := time_range_type(), - created_at := timestamp(), - currency => currency(), - ranges => [time_range_ext()] -}. - --type timestamped_event(T) :: - {ev, machinery:timestamp(), T}. - --type event() :: - {created, limit_range()} - | {time_range_created, time_range_ext()}. - --type limit_range() :: #{ - id := id(), - type := time_range_type(), - created_at := timestamp(), - currency => currency() -}. - --type time_range_ext() :: #{ - account_id_from := lim_accounting:account_id(), - account_id_to := lim_accounting:account_id(), - upper := timestamp(), - lower := timestamp() -}. - --type create_params() :: #{ - id := id(), - type := time_range_type(), - created_at := timestamp(), - currency => currency() -}. - --type range_call() :: - {add_range, time_range()}. - --export_type([time_range_ext/0]). - --export_type([timestamped_event/1]). --export_type([event/0]). - --define(NS, 'lim/range_v1'). - --import(lim_pipeline, [do/1, unwrap/1]). - -%% Accessors - --spec id(limit_range_state()) -> id(). -id(State) -> - maps:get(id, State). - --spec created_at(limit_range_state()) -> timestamp(). -created_at(State) -> - maps:get(created_at, State). - --spec type(limit_range_state()) -> time_range_type(). -type(State) -> - maps:get(type, State). - --spec ranges(limit_range_state()) -> [time_range_ext()]. -ranges(#{ranges := Ranges}) -> - Ranges; -ranges(_State) -> - []. - --spec currency(limit_range_state() | create_params()) -> currency(). -currency(#{currency := Currency}) -> - Currency; -currency(_State) -> - lim_accounting:noncurrency(). - -%%% API - --spec get(id(), lim_context()) -> {ok, limit_range_state()} | {error, notfound}. -get(ID, LimitContext) -> - get_state(ID, lim_context:woody_context(LimitContext)). - --spec ensure_exists(create_params(), time_range(), lim_context()) -> {ok, time_range_ext()}. -ensure_exists(#{id := ID} = Params, TimeRange, LimitContext) -> - WoodyCtx = lim_context:woody_context(LimitContext), - case get_state(ID, WoodyCtx) of - {ok, State} -> - ensure_range_exist_in_state(TimeRange, State, WoodyCtx); - {error, notfound} -> - TimeRangeExt = new_time_range_ext(TimeRange, currency(Params), WoodyCtx), - _ = start(ID, Params, [TimeRangeExt], WoodyCtx), - {ok, State} = get_state(ID, WoodyCtx), - get_range(TimeRange, State) - end. - --spec ensure_range_exist_in_state(time_range(), limit_range_state(), woody_context()) -> {ok, time_range_ext()}. -ensure_range_exist_in_state(TimeRange, State, WoodyCtx) -> - case find_time_range(TimeRange, ranges(State)) of - {error, notfound} -> - call(id(State), {add_range, TimeRange}, WoodyCtx); - {ok, Range} -> - {ok, Range} - end. - --spec get_range(time_range(), limit_range_state()) -> {ok, time_range_ext()} | {error, notfound}. -get_range(TimeRange, State) -> - find_time_range(TimeRange, ranges(State)). - --spec get_range_balance(id(), time_range(), lim_context()) -> - {ok, lim_accounting:balance()} - | {error, notfound}. -get_range_balance(ID, TimeRange, LimitContext) -> - do(fun() -> - State = unwrap(get(ID, LimitContext)), - #{account_id_to := AccountID} = unwrap(get_range(TimeRange, State)), - {ok, Balance} = lim_accounting:get_balance(AccountID, LimitContext), - Balance - end). - -%%% Machinery callbacks - --spec init(args([event()]), machine(), handler_args(), handler_opts()) -> result(). -init(Events, _Machine, _HandlerArgs, _HandlerOpts) -> - #{events => emit_events(Events)}. - --spec process_call(args(range_call()), machine(), handler_args(), handler_opts()) -> - {response(time_range_ext()), result()} | no_return(). -process_call({add_range, TimeRange}, Machine, _HandlerArgs, #{woody_ctx := WoodyCtx}) -> - State = collapse(Machine), - case find_time_range(TimeRange, ranges(State)) of - {error, notfound} -> - TimeRangeExt = new_time_range_ext(TimeRange, currency(State), WoodyCtx), - {TimeRangeExt, #{events => emit_events([{time_range_created, TimeRangeExt}])}}; - {ok, TimeRangeExt} -> - {ok, TimeRangeExt} - end. - --spec process_timeout(machine(), handler_args(), handler_opts()) -> no_return(). -process_timeout(_Machine, _HandlerArgs, _HandlerOpts) -> - not_implemented(timeout). - --spec process_repair(args(_), machine(), handler_args(), handler_opts()) -> no_return(). -process_repair(_Args, _Machine, _HandlerArgs, _HandlerOpts) -> - not_implemented(repair). - --spec process_notification(args(_), machine(), handler_args(), handler_opts()) -> no_return(). -process_notification(_Args, _Machine, _HandlerArgs, _HandlerOpts) -> - not_implemented(notification). - -%%% Internal functions - -find_time_range(_TimeRange, []) -> - {error, notfound}; -find_time_range(#{lower := Lower}, [Head = #{lower := Lower} | _Rest]) -> - {ok, Head}; -find_time_range(TimeRange, [_Head | Rest]) -> - find_time_range(TimeRange, Rest). - -new_time_range_ext(TimeRange, Currency, WoodyCtx) -> - LimitContext = lim_context:create(WoodyCtx), - {ok, AccountIDFrom} = lim_accounting:create_account(Currency, LimitContext), - {ok, AccountIDTo} = lim_accounting:create_account(Currency, LimitContext), - TimeRange#{ - account_id_from => AccountIDFrom, - account_id_to => AccountIDTo - }. - -%% - --spec start(id(), create_params(), [time_range_ext()], woody_context()) -> ok | {error, exists}. -start(ID, Params, TimeRanges, WoodyCtx) -> - TimeRangeEvents = [{time_range_created, TR} || TR <- TimeRanges], - machinery:start(?NS, ID, [{created, Params} | TimeRangeEvents], get_backend(WoodyCtx)). - --spec call(id(), range_call(), woody_context()) -> {ok, response(_)} | {error, notfound}. -call(ID, Msg, WoodyCtx) -> - machinery:call(?NS, ID, Msg, get_backend(WoodyCtx)). - --spec get_state(id(), woody_context()) -> {ok, limit_range_state()} | {error, notfound}. -get_state(ID, WoodyCtx) -> - case machinery:get(?NS, ID, get_backend(WoodyCtx)) of - {ok, Machine} -> - {ok, collapse(Machine)}; - {error, notfound} = Error -> - Error - end. - -emit_events(Events) -> - emit_timestamped_events(Events, lim_time:machinery_now()). - -emit_timestamped_events(Events, Ts) -> - [{ev, Ts, Body} || Body <- Events]. - -collapse(#{history := History}) -> - lists:foldl(fun(Ev, St) -> apply_event(Ev, St) end, undefined, History). - --spec get_backend(woody_context()) -> machinery_mg_backend:backend(). -get_backend(WoodyCtx) -> - lim_utils:get_backend(?NS, WoodyCtx). - --spec not_implemented(any()) -> no_return(). -not_implemented(What) -> - erlang:error({not_implemented, What}). - -%% - --spec apply_event(machinery:event(timestamped_event(event())), lim_maybe:'maybe'(limit_range_state())) -> - limit_range_state(). -apply_event({_ID, _Ts, {ev, _EvTs, Event}}, Config) -> - apply_event_(Event, Config). - --spec apply_event_(event(), lim_maybe:'maybe'(limit_range_state())) -> limit_range_state(). -apply_event_({created, LimitRange}, undefined) -> - LimitRange; -apply_event_({time_range_created, TimeRange}, #{ranges := Ranges} = LimitRange) -> - LimitRange#{ranges => [TimeRange | Ranges]}; -apply_event_({time_range_created, TimeRange}, LimitRange) -> - LimitRange#{ranges => [TimeRange]}. diff --git a/apps/limiter/src/lim_range_machinery_schema.erl b/apps/limiter/src/lim_range_machinery_schema.erl deleted file mode 100644 index 71b0413a..00000000 --- a/apps/limiter/src/lim_range_machinery_schema.erl +++ /dev/null @@ -1,122 +0,0 @@ --module(lim_range_machinery_schema). - -%% Storage schema behaviour --behaviour(machinery_mg_schema). - --export([get_version/1]). --export([marshal/3]). --export([unmarshal/3]). - -%% Constants - --define(CURRENT_EVENT_FORMAT_VERSION, 1). - -%% Internal types - --type type() :: machinery_mg_schema:t(). --type value(T) :: machinery_mg_schema:v(T). --type value_type() :: machinery_mg_schema:vt(). --type context() :: machinery_mg_schema:context(). - --type event() :: lim_range_machine:timestamped_event(lim_range_machine:event()). --type aux_state() :: term(). --type call_args() :: term(). --type call_response() :: term(). - --type data() :: - aux_state() - | event() - | call_args() - | call_response(). - -%% machinery_mg_schema callbacks - --spec get_version(value_type()) -> machinery_mg_schema:version(). -get_version(event) -> - ?CURRENT_EVENT_FORMAT_VERSION; -get_version(aux_state) -> - undefined. - --spec marshal(type(), value(data()), context()) -> {machinery_msgpack:t(), context()}. -marshal({event, FormatVersion}, TimestampedChange, Context) -> - marshal_event(FormatVersion, TimestampedChange, Context); -marshal(T, V, C) when - T =:= {args, init} orelse - T =:= {args, call} orelse - T =:= {args, repair} orelse - T =:= {aux_state, undefined} orelse - T =:= {response, call} orelse - T =:= {response, {repair, success}} orelse - T =:= {response, {repair, failure}} --> - machinery_mg_schema_generic:marshal(T, V, C). - --spec unmarshal(type(), machinery_msgpack:t(), context()) -> {data(), context()}. -unmarshal({event, FormatVersion}, EncodedChange, Context) -> - unmarshal_event(FormatVersion, EncodedChange, Context); -unmarshal(T, V, C) when - T =:= {args, init} orelse - T =:= {args, call} orelse - T =:= {args, repair} orelse - T =:= {aux_state, undefined} orelse - T =:= {response, call} orelse - T =:= {response, {repair, success}} orelse - T =:= {response, {repair, failure}} --> - machinery_mg_schema_generic:unmarshal(T, V, C). - -%% Internals - --spec marshal_event(machinery_mg_schema:version(), event(), context()) -> {machinery_msgpack:t(), context()}. -marshal_event(1, TimestampedChange, Context) -> - ThriftChange = lim_range_codec:marshal(timestamped_change, TimestampedChange), - Type = {struct, struct, {limproto_range_thrift, 'TimestampedChange'}}, - {{bin, lim_proto_utils:serialize(Type, ThriftChange)}, Context}. - --spec unmarshal_event(machinery_mg_schema:version(), machinery_msgpack:t(), context()) -> {event(), context()}. -unmarshal_event(1, EncodedChange, Context) -> - {bin, EncodedThriftChange} = EncodedChange, - Type = {struct, struct, {limproto_range_thrift, 'TimestampedChange'}}, - ThriftChange = lim_proto_utils:deserialize(Type, EncodedThriftChange), - {lim_range_codec:unmarshal(timestamped_change, ThriftChange), Context}. - -%% - --ifdef(TEST). --include_lib("eunit/include/eunit.hrl"). - --spec test() -> _. - --spec marshal_unmarshal_created_test() -> _. - -marshal_unmarshal_created_test() -> - ID = <<"id">>, - Created = - {created, #{ - id => ID, - type => {calendar, day}, - created_at => <<"2000-01-01T00:00:00Z">>, - currency => <<"USD">> - }}, - Context = #{machine_id => ID, machine_ns => limrange}, - Event = {ev, lim_time:machinery_now(), Created}, - {Marshaled, _} = marshal_event(1, Event, Context), - {Unmarshaled, _} = unmarshal_event(1, Marshaled, Context), - ?assertEqual(Event, Unmarshaled). - --spec marshal_unmarshal_time_range_created_test() -> _. -marshal_unmarshal_time_range_created_test() -> - TimeRangeCreated = - {time_range_created, #{ - account_id_from => 25, - account_id_to => 175, - upper => <<"2000-01-01T00:00:00Z">>, - lower => <<"2000-01-01T00:00:00Z">> - }}, - Context = #{machine_id => <<"id">>, machine_ns => limrange}, - Event = {ev, lim_time:machinery_now(), TimeRangeCreated}, - {Marshaled, _} = marshal_event(1, Event, Context), - {Unmarshaled, _} = unmarshal_event(1, Marshaled, Context), - ?assertEqual(Event, Unmarshaled). - --endif. diff --git a/apps/limiter/src/lim_time.erl b/apps/limiter/src/lim_time.erl index 56e8bb12..63ecdfe8 100644 --- a/apps/limiter/src/lim_time.erl +++ b/apps/limiter/src/lim_time.erl @@ -3,7 +3,6 @@ -export([now/0]). -export([to_rfc3339/1]). -export([from_rfc3339/1]). --export([machinery_now/0]). -type timestamp_ms() :: integer(). @@ -20,8 +19,3 @@ to_rfc3339(Timestamp) -> -spec from_rfc3339(binary()) -> timestamp_ms(). from_rfc3339(BTimestamp) -> genlib_rfc3339:parse(BTimestamp, millisecond). - --spec machinery_now() -> machinery:timestamp(). -machinery_now() -> - Now = {_, _, USec} = os:timestamp(), - {calendar:now_to_universal_time(Now), USec}. diff --git a/apps/limiter/src/lim_turnover_processor.erl b/apps/limiter/src/lim_turnover_processor.erl index 6ae694cd..fecd5261 100644 --- a/apps/limiter/src/lim_turnover_processor.erl +++ b/apps/limiter/src/lim_turnover_processor.erl @@ -6,17 +6,9 @@ -export([make_change/4]). --export([get_limit/4]). --export([hold/3]). --export([commit/3]). --export([rollback/3]). - -type lim_context() :: lim_context:t(). --type lim_id() :: lim_config_machine:lim_id(). -type lim_change() :: lim_config_machine:lim_change(). --type limit() :: lim_config_machine:limit(). -type config() :: lim_config_machine:config(). --type lim_version() :: dmsl_domain_thrift:'DataRevision'() | undefined. -type amount() :: integer(). -type forbidden_operation_amount_error() :: #{ @@ -65,111 +57,16 @@ make_change(Stage, #limiter_LimitChange{id = LimitID, version = Version}, Config lim_liminator:construct_change(LimitID, LimitRangeID, Metric, maps:merge(ChangeContext, ScopeChangeContext)) end). --spec get_limit(lim_id(), lim_version(), config(), lim_context()) -> {ok, limit()} | {error, get_limit_error()}. -get_limit(LimitID, Version, Config, LimitContext) -> - do(fun() -> - {LimitRangeID, TimeRange} = unwrap(compute_limit_time_range_location(LimitID, Version, Config, LimitContext)), - Amount = find_range_balance_amount(LimitRangeID, TimeRange, LimitContext), - #limiter_Limit{ - id = LimitRangeID, - amount = Amount, - creation_time = lim_config_machine:created_at(Config), - description = lim_config_machine:description(Config) - } - end). - -find_range_balance_amount(LimitRangeID, TimeRange, LimitContext) -> - case lim_range_machine:get_range_balance(LimitRangeID, TimeRange, LimitContext) of - {ok, #{max_available_amount := Amount}} -> - Amount; - {error, notfound} -> - 0 - end. - --spec hold(lim_change(), config(), lim_context()) -> ok | {error, hold_error()}. -hold(#limiter_LimitChange{id = LimitID, version = Version} = LimitChange, Config, LimitContext) -> - do(fun() -> - TimeRangeAccount = unwrap(ensure_limit_time_range(LimitID, Version, Config, LimitContext)), - Metric = unwrap(compute_metric(hold, Config, LimitContext)), - Posting = lim_posting:new(TimeRangeAccount, Metric, currency(Config)), - unwrap(lim_accounting:hold(construct_plan_id(LimitChange), {1, [Posting]}, LimitContext)) - end). - --spec commit(lim_change(), config(), lim_context()) -> ok | {error, commit_error()}. -commit(#limiter_LimitChange{id = LimitID, version = Version} = LimitChange, Config, LimitContext) -> - do(fun() -> - TimeRangeAccount = unwrap(ensure_limit_time_range(LimitID, Version, Config, LimitContext)), - PlanID = construct_plan_id(LimitChange), - Operations = construct_commit_plan(TimeRangeAccount, Config, LimitContext), - ok = lists:foreach( - fun - ({hold, Batch}) -> - % NOTE - % This operation **can** fail with `InvalidRequest` when the plan is already - % committed, yet we knowingly ignore any them. Instead we rely on the fact that - % accounter guarantees us that it commits **only** when submitted plan consists - % of exactly the same set of batches which were held before. - lim_accounting:hold(PlanID, Batch, LimitContext); - ({commit, Batches}) -> - unwrap(lim_accounting:commit(PlanID, Batches, LimitContext)); - ({rollback, Batches}) -> - unwrap(lim_accounting:rollback(PlanID, Batches, LimitContext)) - end, - Operations - ) - end). - --spec rollback(lim_change(), config(), lim_context()) -> ok | {error, rollback_error()}. -rollback(#limiter_LimitChange{id = LimitID, version = Version} = LimitChange, Config, LimitContext) -> - do(fun() -> - TimeRangeAccount = unwrap(ensure_limit_time_range(LimitID, Version, Config, LimitContext)), - Metric = unwrap(compute_metric(hold, Config, LimitContext)), - Posting = lim_posting:new(TimeRangeAccount, Metric, currency(Config)), - unwrap(lim_accounting:rollback(construct_plan_id(LimitChange), [{1, [Posting]}], LimitContext)) - end). - compute_limit_range_id(LimitID, Version, Config, LimitContext) -> do(fun() -> Timestamp = unwrap(get_timestamp(Config, LimitContext)), unwrap(construct_range_id(Timestamp, LimitID, Version, Config, LimitContext)) end). -compute_limit_time_range_location(LimitID, Version, Config, LimitContext) -> - do(fun() -> - Timestamp = unwrap(get_timestamp(Config, LimitContext)), - {LimitRangeID, _} = unwrap(compute_limit_range_id(LimitID, Version, Config, LimitContext)), - TimeRange = lim_config_machine:calculate_time_range(Timestamp, Config), - {LimitRangeID, TimeRange} - end). - -ensure_limit_time_range(LimitID, Version, Config, LimitContext) -> - do(fun() -> - Timestamp = unwrap(get_timestamp(Config, LimitContext)), - {LimitRangeID, TimeRange} = unwrap(compute_limit_time_range_location(LimitID, Version, Config, LimitContext)), - CreateParams = genlib_map:compact(#{ - id => LimitRangeID, - type => lim_config_machine:time_range_type(Config), - created_at => Timestamp, - currency => currency(Config) - }), - unwrap(lim_range_machine:ensure_exists(CreateParams, TimeRange, LimitContext)) - end). - get_timestamp(Config, LimitContext) -> ContextType = lim_config_machine:context_type(Config), lim_context:get_value(ContextType, created_at, LimitContext). -construct_plan_id(#limiter_LimitChange{change_id = ChangeID}) -> - ChangeID. - -construct_range_id(Timestamp, LimitID, undefined, Config, LimitContext) -> - case lim_config_machine:mk_scope_prefix(Config, LimitContext) of - {ok, {Prefix, ChangeContext}} -> - ShardID = lim_config_machine:calculate_shard_id(Timestamp, Config), - {ok, {<>, ChangeContext}}; - {error, _} = Error -> - Error - end; construct_range_id(Timestamp, LimitID, Version, Config, LimitContext) -> BinaryVersion = genlib:to_binary(Version), case lim_config_machine:mk_scope_prefix(Config, LimitContext) of @@ -180,65 +77,6 @@ construct_range_id(Timestamp, LimitID, Version, Config, LimitContext) -> Error end. -construct_commit_plan(TimeRangeAccount, Config, LimitContext) -> - MetricHold = unwrap(compute_metric(hold, Config, LimitContext)), - MetricCommit = unwrap(compute_metric(commit, Config, LimitContext)), - construct_commit_postings(TimeRangeAccount, MetricHold, MetricCommit, Config). - -construct_commit_postings(TimeRangeAccount, MetricHold, MetricCommit, Config) -> - OriginalHoldPosting = lim_posting:new(TimeRangeAccount, MetricHold, currency(Config)), - case MetricCommit of - MetricHold -> - % Commit-time metric is equal to hold-time metric - [{commit, [{1, [OriginalHoldPosting]}]}]; - 0 -> - % Commit-time metric is 0, this is rollback - [{rollback, [{1, [OriginalHoldPosting]}]}]; - _MetricPartial -> - % Partial body is less than full body - ok = unwrap(validate_metric(MetricCommit, MetricHold)), - ReverseHoldPosting = lim_posting:reverse(OriginalHoldPosting), - PartialHoldPosting = lim_posting:new(TimeRangeAccount, MetricCommit, currency(Config)), - PartialBatch = [ReverseHoldPosting, PartialHoldPosting], - [ - {hold, {2, PartialBatch}}, - {commit, [ - {1, [OriginalHoldPosting]}, - {2, PartialBatch} - ]} - ] - end. - compute_metric(Stage, Config, LimitContext) -> {turnover, Metric} = lim_config_machine:type(Config), lim_turnover_metric:compute(Metric, Stage, Config, LimitContext). - -currency(#{type := {turnover, {amount, Currency}}}) -> - Currency; -currency(#{type := {turnover, number}}) -> - lim_accounting:noncurrency(). - -validate_metric(MetricCommit, MetricHold) when MetricHold > 0 -> - case MetricCommit < MetricHold of - true -> - ok; - false -> - {error, - {forbidden_operation_amount, #{ - type => positive, - partial => MetricCommit, - full => MetricHold - }}} - end; -validate_metric(MetricCommit, MetricHold) when MetricHold < 0 -> - case MetricCommit > MetricHold of - true -> - ok; - false -> - {error, - {forbidden_operation_amount, #{ - type => negative, - partial => MetricCommit, - full => MetricHold - }}} - end. diff --git a/apps/limiter/src/lim_utils.erl b/apps/limiter/src/lim_utils.erl deleted file mode 100644 index 57decb23..00000000 --- a/apps/limiter/src/lim_utils.erl +++ /dev/null @@ -1,27 +0,0 @@ --module(lim_utils). - --export([get_backend/2]). --export([get_woody_client/1]). - --type woody_context() :: woody_context:ctx(). - --spec get_backend(atom(), woody_context()) -> machinery_mg_backend:backend(). -get_backend(NS, WoodyCtx) -> - Backend = maps:get(NS, genlib_app:env(limiter, backends, #{})), - {Mod, Opts} = machinery_utils:get_backend(Backend), - {Mod, Opts#{ - woody_ctx => WoodyCtx - }}. - -%%% Internal functions - --spec get_woody_client(woody:url()) -> machinery_mg_client:woody_client(). -get_woody_client(Url) -> - genlib_map:compact(#{ - url => Url, - event_handler => get_woody_event_handlers() - }). - --spec get_woody_event_handlers() -> woody:ev_handlers(). -get_woody_event_handlers() -> - genlib_app:env(limiter, woody_event_handlers, [{scoper_woody_event_handler, #{}}]). diff --git a/apps/limiter/src/limiter.app.src b/apps/limiter/src/limiter.app.src index 9355c8ce..344e6c9d 100644 --- a/apps/limiter/src/limiter.app.src +++ b/apps/limiter/src/limiter.app.src @@ -12,7 +12,6 @@ limiter_proto, liminator_proto, xrates_proto, - machinery, erl_health, dmt_client, opentelemetry_api, diff --git a/apps/limiter/src/limiter.erl b/apps/limiter/src/limiter.erl index 1eedaa6c..e2788e2f 100644 --- a/apps/limiter/src/limiter.erl +++ b/apps/limiter/src/limiter.erl @@ -30,17 +30,7 @@ stop(_State) -> init([]) -> ServiceOpts = genlib_app:env(?MODULE, services, #{}), Healthcheck = enable_health_logging(genlib_app:env(?MODULE, health_check, #{})), - - {Backends, MachineHandlers, ModernizerHandlers} = lists:unzip3([ - contruct_backend_childspec('lim/range_v1', lim_range_machine), - contruct_backend_childspec('lim/config_v1', lim_config_machine) - ]), - ok = application:set_env(limiter, backends, maps:from_list(Backends)), - - RouteOptsEnv = genlib_app:env(?MODULE, route_opts, #{}), EventHandlers = genlib_app:env(?MODULE, woody_event_handlers, [{woody_event_handler_default, #{}}]), - EventHandlerOpts = genlib_app:env(?MODULE, scoper_event_handler_options, #{}), - RouteOpts = RouteOptsEnv#{event_handler => {scoper_woody_event_handler, EventHandlerOpts}}, ChildSpec = woody_server:child_spec( ?MODULE, @@ -52,10 +42,7 @@ init([]) -> shutdown_timeout => get_shutdown_timeout(), event_handler => EventHandlers, handlers => get_handler_specs(ServiceOpts), - additional_routes => - machinery_mg_backend:get_routes(MachineHandlers, RouteOpts) ++ - machinery_modernizer_mg_backend:get_routes(ModernizerHandlers, RouteOpts) ++ - [erl_health_handle:get_route(Healthcheck)] ++ get_prometheus_route() + additional_routes => [erl_health_handle:get_route(Healthcheck)] ++ get_prometheus_route() } ), {ok, @@ -88,15 +75,10 @@ get_shutdown_timeout() -> -spec get_handler_specs(map()) -> [woody:http_handler(woody:th_handler())]. get_handler_specs(ServiceOpts) -> LimiterService = maps:get(limiter, ServiceOpts, #{}), - ConfiguratorService = maps:get(configurator, ServiceOpts, #{}), [ { maps:get(path, LimiterService, <<"/v1/limiter">>), {{limproto_limiter_thrift, 'Limiter'}, lim_handler} - }, - { - maps:get(path, ConfiguratorService, <<"/v1/configurator">>), - {{limproto_configurator_thrift, 'Configurator'}, lim_configurator} } ]. @@ -114,46 +96,6 @@ enable_health_logging(Check) -> get_prometheus_route() -> [{"/metrics/[:registry]", prometheus_cowboy2_handler, []}]. -contruct_backend_childspec(NS, Handler) -> - Schema = get_namespace_schema(NS), - { - construct_machinery_backend_spec(NS, Schema), - construct_machinery_handler_spec(NS, Handler, Schema), - construct_machinery_modernizer_spec(NS, Schema) - }. - -construct_machinery_backend_spec(NS, Schema) -> - {NS, - {machinery_mg_backend, #{ - schema => Schema, - client => get_service_client(automaton) - }}}. - -construct_machinery_handler_spec(NS, Handler, Schema) -> - {Handler, #{ - path => lim_string:join(["/v1/stateproc/", NS]), - backend_config => #{schema => Schema} - }}. - -construct_machinery_modernizer_spec(NS, Schema) -> - #{ - path => lim_string:join(["/v1/modernizer/", NS]), - backend_config => #{schema => Schema} - }. - -get_namespace_schema('lim/range_v1') -> - lim_range_machinery_schema; -get_namespace_schema('lim/config_v1') -> - lim_config_machinery_schema. - -get_service_client(ServiceID) -> - case lim_client_woody:get_service_client_url(ServiceID) of - undefined -> - error({unknown_service, ServiceID}); - Url -> - lim_utils:get_woody_client(Url) - end. - setup_metrics() -> ok = woody_ranch_prometheus_collector:setup(), ok = woody_hackney_prometheus_collector:setup(). diff --git a/apps/limiter/test/lim_client.erl b/apps/limiter/test/lim_client.erl index 68a7d2a7..8544df84 100644 --- a/apps/limiter/test/lim_client.erl +++ b/apps/limiter/test/lim_client.erl @@ -14,20 +14,14 @@ -export([commit_batch/3]). -export([rollback_batch/3]). --export([legacy_create_config/2]). --export([create_config/2]). --export([get_config/2]). - -type client() :: woody_context:ctx(). -type limit_id() :: limproto_limiter_thrift:'LimitID'(). --type limit_version() :: limproto_limiter_thrift:'Version'() | undefined. +-type limit() :: limproto_limiter_thrift:'Limit'(). +-type limit_version() :: limproto_limiter_thrift:'Version'(). -type limit_change() :: limproto_limiter_thrift:'LimitChange'(). -type limit_request() :: limproto_limiter_thrift:'LimitRequest'(). -type limit_context() :: limproto_limiter_thrift:'LimitContext'(). --type clock() :: limproto_limiter_thrift:'Clock'(). --type legacy_create_params() :: limproto_configurator_thrift:'LimitCreateParams'(). --type limit_config_params() :: limproto_config_thrift:'LimitConfigParams'(). %%% API @@ -35,60 +29,73 @@ new() -> woody_context:new(). --spec get(limit_id(), limit_version(), limit_context(), client()) -> woody:result() | no_return(). -get(LimitID, undefined, Context, Client) -> - call('Get', {LimitID, clock(), Context}, Client); +-spec get(limit_id(), limit_version(), limit_context(), client()) -> + {ok, limit()} | {exception, woody_error:business_error()} | no_return(). get(LimitID, Version, Context, Client) -> - call('GetVersioned', {LimitID, Version, clock(), Context}, Client). - --spec hold(limit_change(), limit_context(), client()) -> woody:result() | no_return(). -hold(LimitChange, Context, Client) -> - call('Hold', {LimitChange, clock(), Context}, Client). - --spec commit(limit_change(), limit_context(), client()) -> woody:result() | no_return(). -commit(LimitChange, Context, Client) -> - call('Commit', {LimitChange, clock(), Context}, Client). - --spec rollback(limit_change(), limit_context(), client()) -> woody:result() | no_return(). -rollback(LimitChange, Context, Client) -> - call('Rollback', {LimitChange, clock(), Context}, Client). - --spec get_values(limit_request(), limit_context(), client()) -> woody:result() | no_return(). + LimitRequest = construct_request(#limiter_LimitChange{id = LimitID, version = Version}), + case get_values(LimitRequest, Context, Client) of + {ok, [Limit]} -> + {ok, Limit}; + {ok, []} -> + {ok, #limiter_Limit{id = LimitID, amount = 0}}; + {exception, _} = Exception -> + Exception + end. + +-spec hold(limit_change(), limit_context(), client()) -> ok | {exception, woody_error:business_error()} | no_return(). +hold(#limiter_LimitChange{} = LimitChange, Context, Client) -> + LimitRequest = construct_request(LimitChange), + case hold_batch(LimitRequest, Context, Client) of + {ok, _} -> + ok; + {exception, _} = Exception -> + Exception + end. + +-spec commit(limit_change(), limit_context(), client()) -> ok | {exception, woody_error:business_error()} | no_return(). +commit(#limiter_LimitChange{} = LimitChange, Context, Client) -> + LimitRequest = construct_request(LimitChange), + unwrap_ok(commit_batch(LimitRequest, Context, Client)). + +-spec rollback(limit_change(), limit_context(), client()) -> + ok | {exception, woody_error:business_error()} | no_return(). +rollback(#limiter_LimitChange{} = LimitChange, Context, Client) -> + LimitRequest = construct_request(LimitChange), + unwrap_ok(rollback_batch(LimitRequest, Context, Client)). + +-spec get_values(limit_request(), limit_context(), client()) -> + {ok, [limit()]} | {exception, woody_error:business_error()} | no_return(). get_values(LimitRequest, Context, Client) -> call('GetValues', {LimitRequest, Context}, Client). --spec get_batch(limit_request(), limit_context(), client()) -> woody:result() | no_return(). +-spec get_batch(limit_request(), limit_context(), client()) -> + {ok, [limit()]} | {exception, woody_error:business_error()} | no_return(). get_batch(LimitRequest, Context, Client) -> call('GetBatch', {LimitRequest, Context}, Client). --spec hold_batch(limit_request(), limit_context(), client()) -> woody:result() | no_return(). +-spec hold_batch(limit_request(), limit_context(), client()) -> + {ok, [limit()]} | {exception, woody_error:business_error()} | no_return(). hold_batch(LimitRequest, Context, Client) -> call('HoldBatch', {LimitRequest, Context}, Client). --spec commit_batch(limit_request(), limit_context(), client()) -> woody:result() | no_return(). +-spec commit_batch(limit_request(), limit_context(), client()) -> + ok | {exception, woody_error:business_error()} | no_return(). commit_batch(LimitRequest, Context, Client) -> - call('CommitBatch', {LimitRequest, Context}, Client). + unwrap_ok(call('CommitBatch', {LimitRequest, Context}, Client)). --spec rollback_batch(limit_request(), limit_context(), client()) -> woody:result() | no_return(). +-spec rollback_batch(limit_request(), limit_context(), client()) -> + ok | {exception, woody_error:business_error()} | no_return(). rollback_batch(LimitRequest, Context, Client) -> - call('RollbackBatch', {LimitRequest, Context}, Client). - -%% - --spec legacy_create_config(legacy_create_params(), client()) -> woody:result() | no_return(). -legacy_create_config(LimitCreateParams, Client) -> - call_configurator('CreateLegacy', {LimitCreateParams}, Client). - --spec create_config(limit_config_params(), client()) -> woody:result() | no_return(). -create_config(LimitCreateParams, Client) -> - call_configurator('Create', {LimitCreateParams}, Client). - --spec get_config(limit_id(), client()) -> woody:result() | no_return(). -get_config(LimitConfigID, Client) -> - call_configurator('Get', {LimitConfigID}, Client). + unwrap_ok(call('RollbackBatch', {LimitRequest, Context}, Client)). %%% Internal functions +construct_request(#limiter_LimitChange{id = LimitID} = LimitChange) -> + #limiter_LimitRequest{ + operation_id = <<"operation.single-change.", LimitID/binary>>, + limit_changes = [LimitChange] + }. + -spec call(atom(), tuple(), client()) -> woody:result() | no_return(). call(Function, Args, Client) -> Call = {{limproto_limiter_thrift, 'Limiter'}, Function, Args}, @@ -101,18 +108,5 @@ call(Function, Args, Client) -> }, woody_client:call(Call, Opts, Client). --spec call_configurator(atom(), tuple(), client()) -> woody:result() | no_return(). -call_configurator(Function, Args, Client) -> - Call = {{limproto_configurator_thrift, 'Configurator'}, Function, Args}, - Opts = #{ - url => <<"http://limiter:8022/v1/configurator">>, - event_handler => {scoper_woody_event_handler, #{}}, - transport_opts => #{ - max_connections => 10000 - } - }, - woody_client:call(Call, Opts, Client). - --spec clock() -> clock(). -clock() -> - {vector, #limiter_VectorClock{state = <<>>}}. +unwrap_ok({ok, ok}) -> ok; +unwrap_ok(ResultOrException) -> ResultOrException. diff --git a/apps/limiter/test/lim_configurator_SUITE.erl b/apps/limiter/test/lim_configurator_SUITE.erl deleted file mode 100644 index 1c4d7c53..00000000 --- a/apps/limiter/test/lim_configurator_SUITE.erl +++ /dev/null @@ -1,217 +0,0 @@ --module(lim_configurator_SUITE). - --include_lib("common_test/include/ct.hrl"). --include_lib("stdlib/include/assert.hrl"). --include_lib("limiter_proto/include/limproto_configurator_thrift.hrl"). --include("lim_ct_helper.hrl"). - --export([all/0]). - --export([groups/0]). --export([init_per_suite/1]). --export([end_per_suite/1]). --export([init_per_testcase/2]). --export([end_per_testcase/2]). - --export([legacy_create_config/1]). --export([create_config/1]). --export([create_config_single_scope/1]). --export([create_config_multi_scope/1]). --export([get_config/1]). --export([get_inexistent_config/1]). - --type group_name() :: atom(). --type test_case_name() :: atom(). - -%% tests descriptions - --spec all() -> [{group, group_name()}]. -all() -> - [ - {group, default} - ]. - --spec groups() -> [{atom(), list(), [test_case_name()]}]. -groups() -> - [ - {default, [], [ - legacy_create_config, - create_config, - create_config_single_scope, - create_config_multi_scope, - get_config, - get_inexistent_config - ]} - ]. - --type config() :: [{atom(), any()}]. - --spec init_per_suite(config()) -> config(). -init_per_suite(Config) -> - % dbg:tracer(), dbg:p(all, c), - % dbg:tpl({machinery, '_', '_'}, x), - Apps = - genlib_app:start_application_with(dmt_client, [ - % milliseconds - {cache_update_interval, 5000}, - {max_cache_size, #{ - elements => 20, - % 50Mb - memory => 52428800 - }}, - {woody_event_handlers, [ - {scoper_woody_event_handler, #{ - event_handler_opts => #{ - formatter_opts => #{ - max_length => 1000 - } - } - }} - ]}, - {service_urls, #{ - 'AuthorManagement' => <<"http://dmt:8022/v1/domain/author">>, - 'Repository' => <<"http://dmt:8022/v1/domain/repository">>, - 'RepositoryClient' => <<"http://dmt:8022/v1/domain/repository_client">> - }} - ]) ++ - genlib_app:start_application_with(limiter, [ - {service_clients, #{ - liminator => #{ - url => <<"http://liminator:8022/liminator/v1">> - }, - accounter => #{ - url => <<"http://shumway:8022/accounter">> - }, - automaton => #{ - url => <<"http://machinegun:8022/v1/automaton">> - } - }} - ]), - [{apps, Apps}] ++ Config. - --spec end_per_suite(config()) -> _. -end_per_suite(Config) -> - _ = [application:stop(App) || App <- proplists:get_value(apps, Config)]. - --spec init_per_testcase(test_case_name(), config()) -> config(). -init_per_testcase(Name, C) -> - [{limit_id, mk_limit_id(Name)} | C]. - --spec end_per_testcase(test_case_name(), config()) -> ok. -end_per_testcase(_Name, _C) -> - ok. - -mk_limit_id(Name) -> - genlib:format("~p/~B", [Name, erlang:system_time(millisecond)]). - -%% - --spec legacy_create_config(config()) -> _. -legacy_create_config(C) -> - Client = lim_client:new(), - ID = ?config(limit_id, C), - Description = genlib:unique(), - Params = #configurator_LimitCreateParams{ - id = ID, - name = <<"GlobalMonthTurnover">>, - description = Description, - started_at = <<"2000-01-01T00:00:00Z">> - }, - ?assertMatch( - {ok, #config_LimitConfig{ - id = ID, - description = Description - }}, - lim_client:legacy_create_config(Params, Client) - ). - --spec create_config(config()) -> _. -create_config(C) -> - Client = lim_client:new(), - ID = ?config(limit_id, C), - Description = genlib:unique(), - Params = #config_LimitConfigParams{ - id = ?config(limit_id, C), - description = Description, - started_at = <<"2000-01-01T00:00:00Z">>, - shard_size = 4, - time_range_type = ?time_range_week(), - type = ?lim_type_turnover(?turnover_metric_amount()), - scope = ?scope([ - ?scope_shop(), - ?scope_party() - ]), - op_behaviour = ?op_behaviour(), - context_type = ?ctx_type_payproc(), - currency_conversion = ?currency_conversion() - }, - ?assertMatch( - {ok, #config_LimitConfig{ - id = ID, - description = Description - }}, - lim_client:create_config(Params, Client) - ). - --spec create_config_single_scope(config()) -> _. -create_config_single_scope(C) -> - Client = lim_client:new(), - Params = #config_LimitConfigParams{ - id = ?config(limit_id, C), - started_at = <<"2000-01-01T00:00:00Z">>, - time_range_type = ?time_range_week(), - shard_size = 1, - type = ?lim_type_turnover(), - scope = {single, ?scope_party()}, - context_type = ?ctx_type_payproc(), - op_behaviour = ?op_behaviour(), - currency_conversion = undefined - }, - {ok, #config_LimitConfig{ - scope = Scope - }} = lim_client:create_config(Params, Client), - ?assertEqual(?scope([?scope_party()]), Scope). - --spec create_config_multi_scope(config()) -> _. -create_config_multi_scope(C) -> - Client = lim_client:new(), - Params = #config_LimitConfigParams{ - id = ?config(limit_id, C), - started_at = <<"2000-01-01T00:00:00Z">>, - time_range_type = ?time_range_week(), - shard_size = 1, - type = ?lim_type_turnover(), - scope = {multi, ordsets:from_list([?scope_terminal(), ?scope_payment_tool()])}, - context_type = ?ctx_type_payproc(), - op_behaviour = ?op_behaviour() - }, - {ok, #config_LimitConfig{ - scope = Scope - }} = lim_client:create_config(Params, Client), - ?assertEqual(?scope([?scope_terminal(), ?scope_payment_tool()]), Scope). - --spec get_config(config()) -> _. -get_config(C) -> - ID = ?config(limit_id, C), - #{client := Client} = prepare_environment(ID, <<"GlobalMonthTurnover">>, C), - {ok, #config_LimitConfig{id = ID}} = lim_client:get_config(ID, Client). - --spec get_inexistent_config(config()) -> _. -get_inexistent_config(_C) -> - ?assertEqual( - {exception, #configurator_LimitConfigNotFound{}}, - lim_client:get_config(<<"NOSUCHCONFIG">>, lim_client:new()) - ). - -%% - -prepare_environment(ID, LimitName, _C) -> - Client = lim_client:new(), - Params = #configurator_LimitCreateParams{ - id = ID, - name = LimitName, - description = <<"description">>, - started_at = <<"2000-01-01T00:00:00Z">> - }, - {ok, LimitConfig} = lim_client:legacy_create_config(Params, Client), - #{config => LimitConfig, client => Client}. diff --git a/apps/limiter/test/lim_ct_helper.hrl b/apps/limiter/test/lim_ct_helper.hrl index 3f67b607..9d5381d3 100644 --- a/apps/limiter/test/lim_ct_helper.hrl +++ b/apps/limiter/test/lim_ct_helper.hrl @@ -2,11 +2,10 @@ -define(__limiter_ct_helper__, 42). -include_lib("limiter_proto/include/limproto_base_thrift.hrl"). --include_lib("limiter_proto/include/limproto_config_thrift.hrl"). --include_lib("limiter_proto/include/limproto_timerange_thrift.hrl"). -include_lib("limiter_proto/include/limproto_limiter_thrift.hrl"). -include_lib("limiter_proto/include/limproto_context_payproc_thrift.hrl"). -include_lib("limiter_proto/include/limproto_context_withdrawal_thrift.hrl"). +-include_lib("damsel/include/dmsl_limiter_config_thrift.hrl"). -include_lib("damsel/include/dmsl_base_thrift.hrl"). -include_lib("damsel/include/dmsl_domain_thrift.hrl"). -include_lib("damsel/include/dmsl_wthd_domain_thrift.hrl"). @@ -29,62 +28,62 @@ terminal = #domain_TerminalRef{id = TerminalID} }). --define(scope(Types), {multi, ordsets:from_list(Types)}). --define(global(), ?scope([])). - --define(scope_party(), {party, #config_LimitScopeEmptyDetails{}}). --define(scope_shop(), {shop, #config_LimitScopeEmptyDetails{}}). --define(scope_payment_tool(), {payment_tool, #config_LimitScopeEmptyDetails{}}). --define(scope_provider(), {provider, #config_LimitScopeEmptyDetails{}}). --define(scope_terminal(), {terminal, #config_LimitScopeEmptyDetails{}}). --define(scope_payer_contact_email(), {payer_contact_email, #config_LimitScopeEmptyDetails{}}). --define(scope_wallet(), {wallet, #config_LimitScopeEmptyDetails{}}). --define(scope_sender(), {sender, #config_LimitScopeEmptyDetails{}}). --define(scope_receiver(), {receiver, #config_LimitScopeEmptyDetails{}}). +-define(scopes(Types), ordsets:from_list(Types)). +-define(global(), ?scopes([])). + +-define(scope_party(), {party, #limiter_config_LimitScopeEmptyDetails{}}). +-define(scope_shop(), {shop, #limiter_config_LimitScopeEmptyDetails{}}). +-define(scope_payment_tool(), {payment_tool, #limiter_config_LimitScopeEmptyDetails{}}). +-define(scope_provider(), {provider, #limiter_config_LimitScopeEmptyDetails{}}). +-define(scope_terminal(), {terminal, #limiter_config_LimitScopeEmptyDetails{}}). +-define(scope_payer_contact_email(), {payer_contact_email, #limiter_config_LimitScopeEmptyDetails{}}). +-define(scope_wallet(), {wallet, #limiter_config_LimitScopeEmptyDetails{}}). +-define(scope_sender(), {sender, #limiter_config_LimitScopeEmptyDetails{}}). +-define(scope_receiver(), {receiver, #limiter_config_LimitScopeEmptyDetails{}}). -define(scope_destination_field(FieldPath), - {destination_field, #config_LimitScopeDestinationFieldDetails{field_path = FieldPath}} + {destination_field, #limiter_config_LimitScopeDestinationFieldDetails{field_path = FieldPath}} ). -define(lim_type_turnover(), ?lim_type_turnover(?turnover_metric_number())). -define(lim_type_turnover(Metric), - {turnover, #config_LimitTypeTurnover{metric = Metric}} + {turnover, #limiter_config_LimitTypeTurnover{metric = Metric}} ). --define(turnover_metric_number(), {number, #config_LimitTurnoverNumber{}}). +-define(turnover_metric_number(), {number, #limiter_config_LimitTurnoverNumber{}}). -define(turnover_metric_amount(), ?turnover_metric_amount(?currency)). -define(turnover_metric_amount(Currency), - {amount, #config_LimitTurnoverAmount{currency = Currency}} + {amount, #limiter_config_LimitTurnoverAmount{currency = Currency}} ). -define(time_range_day(), - {calendar, {day, #timerange_TimeRangeTypeCalendarDay{}}} + {calendar, {day, #limiter_config_TimeRangeTypeCalendarDay{}}} ). -define(time_range_week(), - {calendar, {week, #timerange_TimeRangeTypeCalendarWeek{}}} + {calendar, {week, #limiter_config_TimeRangeTypeCalendarWeek{}}} ). -define(time_range_month(), - {calendar, {month, #timerange_TimeRangeTypeCalendarMonth{}}} + {calendar, {month, #limiter_config_TimeRangeTypeCalendarMonth{}}} ). -define(time_range_year(), - {calendar, {year, #timerange_TimeRangeTypeCalendarYear{}}} + {calendar, {year, #limiter_config_TimeRangeTypeCalendarYear{}}} ). -define(op_behaviour(), ?op_behaviour(?op_addition())). --define(op_behaviour(Refund), #config_OperationLimitBehaviour{ +-define(op_behaviour(Refund), #limiter_config_OperationLimitBehaviour{ invoice_payment_refund = Refund }). --define(currency_conversion(), #config_CurrencyConversion{}). +-define(currency_conversion(), #limiter_config_CurrencyConversion{}). --define(op_addition(), {addition, #config_Addition{}}). --define(op_subtraction(), {subtraction, #config_Subtraction{}}). +-define(op_addition(), {addition, #limiter_config_Addition{}}). +-define(op_subtraction(), {subtraction, #limiter_config_Subtraction{}}). -define(ctx_type_payproc(), - {payment_processing, #config_LimitContextTypePaymentProcessing{}} + {payment_processing, #limiter_config_LimitContextTypePaymentProcessing{}} ). -define(ctx_type_wthdproc(), - {withdrawal_processing, #config_LimitContextTypeWithdrawalProcessing{}} + {withdrawal_processing, #limiter_config_LimitContextTypeWithdrawalProcessing{}} ). %% Payproc diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index 6a06fd53..db8f3817 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -17,7 +17,6 @@ -export([init_per_testcase/2]). -export([end_per_testcase/2]). --export([commit_with_long_change_id/1]). -export([commit_with_default_exchange/1]). -export([partial_commit_with_exchange/1]). -export([commit_with_exchange/1]). @@ -32,9 +31,7 @@ -export([hold_ok/1]). -export([commit_ok/1]). -export([rollback_ok/1]). --export([partial_zero_commit_rollbacks/1]). -export([refund_ok/1]). --export([get_config_ok/1]). -export([commit_inexistent_hold_fails/1]). -export([partial_commit_inexistent_hold_fails/1]). -export([commit_multirange_limit_ok/1]). @@ -43,10 +40,10 @@ -export([commit_processes_idempotently/1]). -export([full_commit_processes_idempotently/1]). -export([partial_commit_processes_idempotently/1]). --export([rollback_processes_idempotently/1]). +%% -export([rollback_processes_idempotently/1]). -export([commit_number_ok/1]). --export([rollback_number_ok/1]). +%% -export([rollback_number_ok/1]). -export([commit_refund_keep_number_unchanged/1]). -export([partial_commit_number_counts_as_single_op/1]). @@ -88,7 +85,6 @@ all() -> [ {group, default}, - {group, default_with_dominant}, {group, withdrawals}, {group, cashless}, {group, idempotency} @@ -98,7 +94,6 @@ all() -> groups() -> [ {base, [], [ - commit_with_long_change_id, commit_with_default_exchange, partial_commit_with_exchange, commit_with_exchange, @@ -113,7 +108,6 @@ groups() -> hold_ok, commit_ok, rollback_ok, - partial_zero_commit_rollbacks, refund_ok, commit_inexistent_hold_fails, partial_commit_inexistent_hold_fails, @@ -129,11 +123,6 @@ groups() -> hold_with_destination_field_not_supported ]}, {default, [], [ - {group, base}, - get_config_ok, - commit_multirange_limit_ok - ]}, - {default_with_dominant, [], [ {group, base}, batch_hold_ok, batch_commit_ok, @@ -165,15 +154,15 @@ groups() -> ]}, {cashless, [parallel], [ commit_number_ok, - rollback_number_ok, + %% rollback_number_ok, commit_refund_keep_number_unchanged, partial_commit_number_counts_as_single_op ]}, {idempotency, [parallel], [ commit_processes_idempotently, full_commit_processes_idempotently, - partial_commit_processes_idempotently, - rollback_processes_idempotently + partial_commit_processes_idempotently + %% rollback_processes_idempotently ]} ]. @@ -212,12 +201,6 @@ init_per_suite(Config) -> liminator => #{ url => <<"http://liminator:8022/liminator/v1">> }, - accounter => #{ - url => <<"http://shumway:8022/accounter">> - }, - automaton => #{ - url => <<"http://machinegun:8022/v1/automaton">> - }, xrates => #{ url => <<"http://xrates:8022/xrates">> } @@ -235,13 +218,8 @@ end_per_suite(Config) -> genlib_app:test_application_stop(?config(apps, Config)). -spec init_per_group(test_case_name(), config()) -> config(). -init_per_group(default_with_dominant, C) -> - set_limit_config_source(repository, C); init_per_group(_Name, C) -> - set_limit_config_source(legacy, C). - -set_limit_config_source(ConfigSource, C) -> - [{limit_config_source, ConfigSource} | C]. + C. -spec end_per_group(test_case_name(), config()) -> ok. end_per_group(_Name, _C) -> @@ -263,28 +241,9 @@ end_per_testcase(_Name, C) -> %% --define(CHANGE_ID, 42). --define(LIMIT_CHANGE(ID, ChangeID), ?LIMIT_CHANGE(ID, ChangeID, undefined)). --define(LIMIT_CHANGE(ID, ChangeID, Version), #limiter_LimitChange{ - id = ID, - change_id = gen_change_id(ID, ChangeID), - version = Version -}). +-define(LIMIT_CHANGE(ID, Version), #limiter_LimitChange{id = ID, version = Version}). -define(LIMIT_REQUEST(ID, Changes), #limiter_LimitRequest{operation_id = ID, limit_changes = Changes}). --spec commit_with_long_change_id(config()) -> _. -commit_with_long_change_id(C) -> - {ID, Version} = configure_limit(?time_range_month(), ?global(), C), - Context = ?payproc_ctx_invoice(?cash(10, <<"RUB">>)), - LongBinary = - << - "LongBinaryLongBinaryLongBinaryLongBinaryLongBinaryLong\n" - " BinaryLongBinaryLongBinaryLongBinaryLongBinaryLongBinary" - >>, - ChangeID = <>, - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ChangeID, Version), Context, ?config(client, C)), - {ok, #limiter_Limit{amount = 10}} = lim_client:get(ID, Version, Context, ?config(client, C)). - -spec commit_with_default_exchange(config()) -> _. commit_with_default_exchange(C) -> Rational = #base_Rational{p = 1000000, q = 100}, @@ -294,7 +253,7 @@ commit_with_default_exchange(C) -> ), Cost = ?cash(10000, <<"SOME_CURRENCY">>), Context = ?payproc_ctx_invoice(Cost), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), + ok = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context, ?config(client, C)), {ok, #limiter_Limit{amount = 10000}} = lim_client:get(ID, Version, Context, ?config(client, C)). -spec partial_commit_with_exchange(config()) -> _. @@ -307,7 +266,7 @@ partial_commit_with_exchange(C) -> Cost = ?cash(1000, <<"USD">>), CaptureCost = ?cash(800, <<"USD">>), Context = ?payproc_ctx_payment(Cost, CaptureCost), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), + ok = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context, ?config(client, C)), {ok, #limiter_Limit{amount = 8400}} = lim_client:get(ID, Version, Context, ?config(client, C)). -spec commit_with_exchange(config()) -> _. @@ -319,7 +278,7 @@ commit_with_exchange(C) -> ), Cost = ?cash(10000, <<"USD">>), Context = ?payproc_ctx_invoice(Cost), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), + ok = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context, ?config(client, C)), {ok, #limiter_Limit{amount = 10500}} = lim_client:get(ID, Version, Context, ?config(client, C)). -spec hold_with_disabled_exchange(config()) -> _. @@ -332,7 +291,7 @@ hold_with_disabled_exchange(C) -> Cost = ?cash(10000, Currency), Context = ?payproc_ctx_invoice(Cost), {exception, #limiter_InvalidOperationCurrency{currency = Currency, expected_currency = ConfiguredCurrency}} = - lim_client:hold(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)). + lim_client:hold(?LIMIT_CHANGE(ID, Version), Context, ?config(client, C)). -spec rollback_with_wrong_currency(config()) -> _. rollback_with_wrong_currency(C) -> @@ -344,7 +303,7 @@ rollback_with_wrong_currency(C) -> Cost = ?cash(10000, Currency), Context = ?payproc_ctx_invoice(Cost), {exception, #limiter_InvalidOperationCurrency{currency = Currency, expected_currency = ConfiguredCurrency}} = - lim_client:rollback(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)). + lim_client:rollback(?LIMIT_CHANGE(ID, Version), Context, ?config(client, C)). -spec hold_with_wrong_operation_context(config()) -> _. hold_with_wrong_operation_context(C) -> @@ -354,9 +313,9 @@ hold_with_wrong_operation_context(C) -> Cost = ?cash(10000), Context = ?wthdproc_ctx_withdrawal(Cost), {exception, #limiter_OperationContextNotSupported{ - context_type = {withdrawal_processing, #limiter_config_LimitContextTypeWithdrawalProcessing{}} + context_type = {withdrawal_processing, #limiter_LimitContextTypeWithdrawalProcessing{}} }} = - lim_client:hold(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)). + lim_client:hold(?LIMIT_CHANGE(ID, Version), Context, ?config(client, C)). -spec rollback_with_wrong_operation_context(config()) -> _. rollback_with_wrong_operation_context(C) -> @@ -366,29 +325,29 @@ rollback_with_wrong_operation_context(C) -> Cost = ?cash(10000), Context = ?wthdproc_ctx_withdrawal(Cost), {exception, #limiter_OperationContextNotSupported{ - context_type = {withdrawal_processing, #limiter_config_LimitContextTypeWithdrawalProcessing{}} + context_type = {withdrawal_processing, #limiter_LimitContextTypeWithdrawalProcessing{}} }} = - lim_client:rollback(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)). + lim_client:rollback(?LIMIT_CHANGE(ID, Version), Context, ?config(client, C)). -spec hold_with_wrong_payment_tool(config()) -> _. hold_with_wrong_payment_tool(C) -> Rational = #base_Rational{p = 1000000, q = 100}, _ = mock_exchange(Rational, C), - {ID, Version} = configure_limit(?time_range_week(), ?scope([?scope_payment_tool()]), ?turnover_metric_number(), C), + {ID, Version} = configure_limit(?time_range_week(), ?scopes([?scope_payment_tool()]), ?turnover_metric_number(), C), NotSupportedPaymentTool = {crypto_currency, #domain_CryptoCurrencyRef{id = <<"wow;so-cryptic;much-hidden">>}}, Context = ?payproc_ctx_payment(?invoice_payment(?cash(10000), ?cash(10000), NotSupportedPaymentTool)), {exception, #limiter_PaymentToolNotSupported{payment_tool = <<"crypto_currency">>}} = - lim_client:hold(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)). + lim_client:hold(?LIMIT_CHANGE(ID, Version), Context, ?config(client, C)). -spec rollback_with_wrong_payment_tool(config()) -> _. rollback_with_wrong_payment_tool(C) -> Rational = #base_Rational{p = 1000000, q = 100}, _ = mock_exchange(Rational, C), - {ID, Version} = configure_limit(?time_range_week(), ?scope([?scope_payment_tool()]), ?turnover_metric_number(), C), + {ID, Version} = configure_limit(?time_range_week(), ?scopes([?scope_payment_tool()]), ?turnover_metric_number(), C), NotSupportedPaymentTool = {crypto_currency, #domain_CryptoCurrencyRef{id = <<"wow;so-cryptic;much-hidden">>}}, Context = ?payproc_ctx_payment(?invoice_payment(?cash(10000), ?cash(10000), NotSupportedPaymentTool)), {exception, #limiter_PaymentToolNotSupported{payment_tool = <<"crypto_currency">>}} = - lim_client:rollback(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)). + lim_client:rollback(?LIMIT_CHANGE(ID, Version), Context, ?config(client, C)). -spec get_limit_ok(config()) -> _. get_limit_ok(C) -> @@ -405,11 +364,7 @@ get_limit_ok(C) -> -spec get_limit_notfound(config()) -> _. get_limit_notfound(C) -> - Version = - case proplists:get_value(limit_config_source, C, legacy) of - legacy -> undefined; - repository -> 0 - end, + Version = 0, Context = ?payproc_ctx_invoice(?cash(0)), ?assertEqual( {exception, #limiter_LimitNotFound{}}, @@ -424,8 +379,8 @@ hold_ok(C) -> withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10)); _Default -> ?payproc_ctx_invoice(?cash(10)) end, - {ok, {vector, #limiter_VectorClock{}}} = lim_client:hold( - ?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C) + ok = lim_client:hold( + ?LIMIT_CHANGE(ID, Version), Context, ?config(client, C) ), {ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)). @@ -437,7 +392,7 @@ commit_ok(C) -> withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)); _Default -> ?payproc_ctx_invoice(?cash(10, <<"RUB">>)) end, - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), + ok = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context, ?config(client, C)), {ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)). -spec rollback_ok(config()) -> _. @@ -448,42 +403,23 @@ rollback_ok(C) -> withdrawals -> ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)); _Default -> ?payproc_ctx_invoice(?cash(10, <<"RUB">>)) end, - Change = ?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), - {ok, {vector, _}} = lim_client:hold(Change, Context, ?config(client, C)), - {ok, {vector, _}} = lim_client:rollback(Change, Context, ?config(client, C)). - --spec partial_zero_commit_rollbacks(config()) -> _. -partial_zero_commit_rollbacks(C) -> - {ID, Version} = configure_limit(?time_range_week(), ?global(), C), - Context0 = ?payproc_ctx_payment(?cash(10), ?cash(10)), - Context1 = ?payproc_ctx_payment(?cash(10), ?cash(0)), - Change = ?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), - {ok, {vector, _}} = lim_client:hold(Change, Context0, ?config(client, C)), - {ok, {vector, _}} = lim_client:commit(Change, Context1, ?config(client, C)), - % NOTE - % Successful rollback here means that partial commit with zero is handled exactly - % like rollback, thus subsequent rollback succeeds idempotently. This is a backwards - % compatibility measure. - {ok, {vector, _}} = lim_client:rollback(Change, Context0, ?config(client, C)). + Change = ?LIMIT_CHANGE(ID, Version), + ok = lim_client:hold(Change, Context, ?config(client, C)), + ok = lim_client:rollback(Change, Context, ?config(client, C)). -spec refund_ok(config()) -> _. refund_ok(C) -> Client = ?config(client, C), OwnerID = <<"WWWcool Ltd">>, ShopID = <<"shop">>, - {ID, Version} = configure_limit(?time_range_day(), ?scope([?scope_party(), ?scope_shop()]), C), + {ID, Version} = configure_limit(?time_range_day(), ?scopes([?scope_party(), ?scope_shop()]), C), Context0 = ?payproc_ctx_payment(OwnerID, ShopID, ?cash(15), ?cash(15)), RefundContext1 = ?payproc_ctx_refund(OwnerID, ShopID, ?cash(10), ?cash(10), ?cash(10)), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Payment">>, Version), Context0, Client), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, <<"Refund">>, Version), RefundContext1, Client), + ok = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context0, Client), + ok = hold_and_commit(?LIMIT_CHANGE(ID, Version), RefundContext1, Client), {ok, #limiter_Limit{} = Limit2} = lim_client:get(ID, Version, RefundContext1, Client), ?assertEqual(Limit2#limiter_Limit.amount, 5). --spec get_config_ok(config()) -> _. -get_config_ok(C) -> - {ID, _Version} = configure_limit(?time_range_week(), ?global(), C), - {ok, #config_LimitConfig{}} = lim_client:get_config(ID, ?config(client, C)). - -spec commit_inexistent_hold_fails(config()) -> _. commit_inexistent_hold_fails(C) -> {ID, Version} = configure_limit(?time_range_week(), ?global(), C), @@ -492,7 +428,7 @@ commit_inexistent_hold_fails(C) -> % We do not expect `LimitChangeNotFound` here because we no longer reconcile with accounter % before requesting him to hold / commit. {exception, #base_InvalidRequest{}} = - lim_client:commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)). + lim_client:commit(?LIMIT_CHANGE(ID, Version), Context, ?config(client, C)). -spec partial_commit_inexistent_hold_fails(config()) -> _. partial_commit_inexistent_hold_fails(C) -> @@ -502,40 +438,41 @@ partial_commit_inexistent_hold_fails(C) -> % We do not expect `LimitChangeNotFound` here because we no longer reconcile with accounter % before requesting him to hold / commit. {exception, #base_InvalidRequest{}} = - lim_client:commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)). + lim_client:commit(?LIMIT_CHANGE(ID, Version), Context, ?config(client, C)). -spec commit_multirange_limit_ok(config()) -> _. commit_multirange_limit_ok(C) -> ID = ?config(id, C), Client = ?config(client, C), - Params = #config_LimitConfigParams{ - id = ID, + Version = dmt_client:get_latest_version(), + _ = create_limit_config(ID, #limiter_config_LimitConfig{ + processor_type = <<"TurnoverProcessor">>, started_at = <<"2000-01-01T00:00:00Z">>, shard_size = 12, time_range_type = ?time_range_month(), context_type = ?ctx_type_payproc(), type = ?lim_type_turnover(?turnover_metric_amount(<<"RUB">>)), - scope = ?scope([]), - op_behaviour = #config_OperationLimitBehaviour{} - }, - {ok, _LimitConfig} = lim_client:create_config(Params, Client), + scopes = ?scopes([]), + description = <<"Description">>, + op_behaviour = #limiter_config_OperationLimitBehaviour{} + }), % NOTE % Expecting those 3 changes will be accounted in the same limit range machine. % We have no way to verify it here though. PaymentJan = ?invoice_payment(?cash(42), ?cash(42), ?bank_card(), <<"2020-01-01T00:00:00Z">>), - {ok, _} = hold_and_commit(?LIMIT_CHANGE(ID, 1), ?payproc_ctx_payment(PaymentJan), Client), + ok = hold_and_commit(?LIMIT_CHANGE(ID, 1), ?payproc_ctx_payment(PaymentJan), Client), PaymentFeb = ?invoice_payment(?cash(43), ?cash(43), ?bank_card(), <<"2020-02-01T00:00:00Z">>), - {ok, _} = hold_and_commit(?LIMIT_CHANGE(ID, 2), ?payproc_ctx_payment(PaymentFeb), Client), + ok = hold_and_commit(?LIMIT_CHANGE(ID, 2), ?payproc_ctx_payment(PaymentFeb), Client), PaymentApr = ?invoice_payment(?cash(44), ?cash(44), ?bank_card(), <<"2020-04-01T00:00:00Z">>), - {ok, _} = hold_and_commit(?LIMIT_CHANGE(ID, 3), ?payproc_ctx_payment(PaymentApr), Client), - {ok, #limiter_Limit{amount = 42}} = lim_client:get(ID, undefined, ?payproc_ctx_payment(PaymentJan), Client), - {ok, #limiter_Limit{amount = 43}} = lim_client:get(ID, undefined, ?payproc_ctx_payment(PaymentFeb), Client), - {ok, #limiter_Limit{amount = 44}} = lim_client:get(ID, undefined, ?payproc_ctx_payment(PaymentApr), Client). + ok = hold_and_commit(?LIMIT_CHANGE(ID, 3), ?payproc_ctx_payment(PaymentApr), Client), + {ok, #limiter_Limit{amount = 42}} = lim_client:get(ID, Version, ?payproc_ctx_payment(PaymentJan), Client), + {ok, #limiter_Limit{amount = 43}} = lim_client:get(ID, Version, ?payproc_ctx_payment(PaymentFeb), Client), + {ok, #limiter_Limit{amount = 44}} = lim_client:get(ID, Version, ?payproc_ctx_payment(PaymentApr), Client). -spec commit_with_payment_tool_scope_ok(config()) -> _. commit_with_payment_tool_scope_ok(C) -> Client = ?config(client, C), - {ID, Version} = configure_limit(?time_range_week(), ?scope([?scope_payment_tool()]), ?turnover_metric_number(), C), + {ID, Version} = configure_limit(?time_range_week(), ?scopes([?scope_payment_tool()]), ?turnover_metric_number(), C), Context1 = ?payproc_ctx_payment( ?invoice_payment(?cash(10), ?cash(10), ?bank_card(<<"Token">>, 2, 2022)) ), @@ -552,11 +489,11 @@ commit_with_payment_tool_scope_ok(C) -> ?invoice_payment(?cash(10), ?cash(10), ?digital_wallet(<<"ID42">>, <<"Pepal">>)) ), {ok, LimitState0} = lim_client:get(ID, Version, Context1, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 1, Version), Context1, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 2, Version), Context2, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 3, Version), Context3, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 4, Version), Context4, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 5, Version), Context5, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context1, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context2, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context3, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context4, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context5, Client), {ok, LimitState1} = lim_client:get(ID, Version, Context1, Client), ?assertEqual( LimitState1#limiter_Limit.amount, @@ -570,12 +507,12 @@ commit_processes_idempotently(C) -> Client = ?config(client, C), {ID, Version} = configure_limit(?time_range_week(), ?global(), C), Context = ?payproc_ctx_payment(?cash(42), undefined), - Change = ?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), - {ok, _} = lim_client:hold(Change, Context, Client), - {ok, _} = lim_client:hold(Change, Context, Client), - {ok, _} = lim_client:commit(Change, Context, Client), + Change = ?LIMIT_CHANGE(ID, Version), + ok = lim_client:hold(Change, Context, Client), + ok = lim_client:hold(Change, Context, Client), + ok = lim_client:commit(Change, Context, Client), {ok, Limit = #limiter_Limit{amount = 42}} = lim_client:get(ID, Version, Context, Client), - {ok, _} = lim_client:commit(Change, Context, Client), + ok = lim_client:commit(Change, Context, Client), {ok, Limit} = lim_client:get(ID, Version, Context, Client). -spec full_commit_processes_idempotently(config()) -> _. @@ -584,12 +521,12 @@ full_commit_processes_idempotently(C) -> {ID, Version} = configure_limit(?time_range_week(), ?global(), C), Cost = ?cash(42), Context = ?payproc_ctx_payment(Cost, Cost), - Change = ?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), - {ok, _} = lim_client:hold(Change, Context, Client), - {ok, _} = lim_client:hold(Change, Context, Client), - {ok, _} = lim_client:commit(Change, Context, Client), + Change = ?LIMIT_CHANGE(ID, Version), + ok = lim_client:hold(Change, Context, Client), + ok = lim_client:hold(Change, Context, Client), + ok = lim_client:commit(Change, Context, Client), {ok, Limit = #limiter_Limit{amount = 42}} = lim_client:get(ID, Version, Context, Client), - {ok, _} = lim_client:commit(Change, Context, Client), + ok = lim_client:commit(Change, Context, Client), {ok, Limit} = lim_client:get(ID, Version, Context, Client). -spec partial_commit_processes_idempotently(config()) -> _. @@ -597,26 +534,28 @@ partial_commit_processes_idempotently(C) -> Client = ?config(client, C), {ID, Version} = configure_limit(?time_range_week(), ?global(), C), Context = ?payproc_ctx_payment(?cash(42), ?cash(40)), - Change = ?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), - {ok, _} = lim_client:hold(Change, Context, Client), - {ok, _} = lim_client:hold(Change, Context, Client), - {ok, _} = lim_client:commit(Change, Context, Client), + Change = ?LIMIT_CHANGE(ID, Version), + ok = lim_client:hold(Change, Context, Client), + ok = lim_client:hold(Change, Context, Client), + ok = lim_client:commit(Change, Context, Client), {ok, Limit = #limiter_Limit{amount = 40}} = lim_client:get(ID, Version, Context, Client), - {ok, _} = lim_client:commit(Change, Context, Client), + ok = lim_client:commit(Change, Context, Client), {ok, Limit = #limiter_Limit{amount = 40}} = lim_client:get(ID, Version, Context, Client). --spec rollback_processes_idempotently(config()) -> _. -rollback_processes_idempotently(C) -> - Client = ?config(client, C), - {ID, Version} = configure_limit(?time_range_week(), ?global(), C), - Context = ?payproc_ctx_payment(?cash(42), ?cash(0)), - Change = ?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), - {ok, _} = lim_client:hold(Change, Context, Client), - {ok, _} = lim_client:hold(Change, Context, Client), - {ok, _} = lim_client:commit(Change, Context, Client), - {ok, Limit = #limiter_Limit{amount = 0}} = lim_client:get(ID, Version, Context, Client), - {ok, _} = lim_client:commit(Change, Context, Client), - {ok, Limit = #limiter_Limit{amount = 0}} = lim_client:get(ID, Version, Context, Client). +%% NOTE This test case is temporary disabled for same reason as +%% `rollback_number_ok`. +%% -spec rollback_processes_idempotently(config()) -> _. +%% rollback_processes_idempotently(C) -> +%% Client = ?config(client, C), +%% {ID, Version} = configure_limit(?time_range_week(), ?global(), C), +%% Context = ?payproc_ctx_payment(?cash(42), ?cash(0)), +%% Change = ?LIMIT_CHANGE(ID, Version), +%% ok = lim_client:hold(Change, Context, Client), +%% ok = lim_client:hold(Change, Context, Client), +%% ok = lim_client:commit(Change, Context, Client), +%% {ok, Limit = #limiter_Limit{amount = 0}} = lim_client:get(ID, Version, Context, Client), +%% ok = lim_client:commit(Change, Context, Client), +%% {ok, Limit = #limiter_Limit{amount = 0}} = lim_client:get(ID, Version, Context, Client). %% @@ -626,26 +565,30 @@ commit_number_ok(C) -> {ID, Version} = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C), Context = ?payproc_ctx_payment(?cash(10), ?cash(10)), {ok, LimitState0} = lim_client:get(ID, Version, Context, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context, Client), {ok, LimitState1} = lim_client:get(ID, Version, Context, Client), ?assertEqual( LimitState1#limiter_Limit.amount, LimitState0#limiter_Limit.amount + 1 ). --spec rollback_number_ok(config()) -> _. -rollback_number_ok(C) -> - Client = ?config(client, C), - {ID, Version} = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C), - Context = ?payproc_ctx_payment(?cash(10), ?cash(10)), - ContextRollback = ?payproc_ctx_payment(?cash(10), ?cash(0)), - {ok, LimitState0} = lim_client:get(ID, Version, Context, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ContextRollback, Client), - {ok, LimitState1} = lim_client:get(ID, Version, Context, Client), - ?assertEqual( - LimitState1#limiter_Limit.amount, - LimitState0#limiter_Limit.amount - ). +%% NOTE This test case is temporary disabled because of the bug in liminator: it +%% doesn't respect partial commits with final value of change in operation +%% distinct from value of initial hold. Somehow value of limit counter is +%% aggregated with summing changes up of both (!) hold and commit values. +%% -spec rollback_number_ok(config()) -> _. +%% rollback_number_ok(C) -> +%% Client = ?config(client, C), +%% {ID, Version} = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C), +%% Context = ?payproc_ctx_payment(?cash(10), ?cash(10)), +%% ContextRollback = ?payproc_ctx_payment(?cash(10), ?cash(0)), +%% {ok, LimitState0} = lim_client:get(ID, Version, Context, Client), +%% _ = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context, ContextRollback, Client), +%% {ok, LimitState1} = lim_client:get(ID, Version, Context, Client), +%% ?assertEqual( +%% LimitState1#limiter_Limit.amount, +%% LimitState0#limiter_Limit.amount +%% ). -spec commit_refund_keep_number_unchanged(config()) -> _. commit_refund_keep_number_unchanged(C) -> @@ -657,8 +600,8 @@ commit_refund_keep_number_unchanged(C) -> PaymentContext = ?payproc_ctx_payment(<<"OWNER">>, <<"SHOP">>, Cost, CaptureCost), RefundContext = ?payproc_ctx_refund(<<"OWNER">>, <<"SHOP">>, Cost, CaptureCost, RefundCost), {ok, LimitState0} = lim_client:get(ID, Version, PaymentContext, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 1, Version), PaymentContext, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 2, Version), RefundContext, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, Version), PaymentContext, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, Version), RefundContext, Client), {ok, LimitState1} = lim_client:get(ID, Version, PaymentContext, Client), ?assertEqual( % Expected to be the same because refund decreases counter given limit config @@ -673,7 +616,7 @@ partial_commit_number_counts_as_single_op(C) -> Context = ?payproc_ctx_payment(?cash(10), ?cash(10)), ContextPartial = ?payproc_ctx_payment(?cash(10), ?cash(5)), {ok, LimitState0} = lim_client:get(ID, Version, Context, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ContextPartial, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context, ContextPartial, Client), {ok, LimitState1} = lim_client:get(ID, Version, Context, Client), ?assertEqual( LimitState1#limiter_Limit.amount, @@ -684,15 +627,15 @@ partial_commit_number_counts_as_single_op(C) -> -spec commit_with_party_scope_ok(config()) -> _. commit_with_party_scope_ok(C) -> - _ = commit_with_some_scope(?scope([?scope_party()]), C). + _ = commit_with_some_scope(?scopes([?scope_party()]), C). -spec commit_with_provider_scope_ok(config()) -> _. commit_with_provider_scope_ok(C) -> - _ = commit_with_some_scope(?scope([?scope_provider()]), C). + _ = commit_with_some_scope(?scopes([?scope_provider()]), C). -spec commit_with_terminal_scope_ok(config()) -> _. commit_with_terminal_scope_ok(C) -> - _ = commit_with_some_scope(?scope([?scope_terminal()]), C). + _ = commit_with_some_scope(?scopes([?scope_terminal()]), C). commit_with_some_scope(Scope, C) -> Context = @@ -704,27 +647,27 @@ commit_with_some_scope(Scope, C) -> commit_with_some_scope(Scope, Context, C) -> {ID, Version} = configure_limit(?time_range_month(), Scope, C), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), + ok = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context, ?config(client, C)), {ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)). -spec commit_with_email_scope_ok(config()) -> _. commit_with_email_scope_ok(C) -> - {ID, Version} = configure_limit(?time_range_month(), ?scope([?scope_payer_contact_email()]), C), + {ID, Version} = configure_limit(?time_range_month(), ?scopes([?scope_payer_contact_email()]), C), Context = ?payproc_ctx_payment(?cash(10, <<"RUB">>), ?cash(10, <<"RUB">>)), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), + ok = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context, ?config(client, C)), {ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)). -spec commit_with_wallet_scope_ok(config()) -> _. commit_with_wallet_scope_ok(C) -> - {ID, Version} = configure_limit(?time_range_month(), ?scope([?scope_party(), ?scope_wallet()]), C), + {ID, Version} = configure_limit(?time_range_month(), ?scopes([?scope_party(), ?scope_wallet()]), C), Context = ?wthdproc_ctx_withdrawal(?cash(10, <<"RUB">>)), - {ok, {vector, _}} = hold_and_commit(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)), + ok = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context, ?config(client, C)), {ok, #limiter_Limit{}} = lim_client:get(ID, Version, Context, ?config(client, C)). -spec commit_with_multi_scope_ok(config()) -> _. commit_with_multi_scope_ok(C) -> Client = ?config(client, C), - {ID, Version} = configure_limit(?time_range_week(), ?scope([?scope_provider(), ?scope_payment_tool()]), C), + {ID, Version} = configure_limit(?time_range_week(), ?scopes([?scope_provider(), ?scope_payment_tool()]), C), Context1 = ?payproc_ctx_payment( ?invoice_payment(?cash(10), ?cash(10), ?bank_card(<<"Token">>, 2, 2022)) ), @@ -741,11 +684,11 @@ commit_with_multi_scope_ok(C) -> ?invoice_payment(?cash(10), ?cash(10), ?digital_wallet(<<"ID42">>, <<"Pepal">>)) ), {ok, LimitState0} = lim_client:get(ID, Version, Context1, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 1, Version), Context1, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 2, Version), Context2, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 3, Version), Context3, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 4, Version), Context4, Client), - _ = hold_and_commit(?LIMIT_CHANGE(ID, 5, Version), Context5, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context1, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context2, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context3, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context4, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context5, Client), {ok, LimitState1} = lim_client:get(ID, Version, Context1, Client), ?assertEqual( LimitState1#limiter_Limit.amount, @@ -784,16 +727,16 @@ hold_with_scope_notfound(Scopes, C) -> hold_with_scope_notfound(Scopes, Context, C). hold_with_scope_notfound(Scopes, Context, C) -> - {ID, Version} = configure_limit(?time_range_month(), ?scope(Scopes), C), + {ID, Version} = configure_limit(?time_range_month(), ?scopes(Scopes), C), ?assertException( error, {woody_error, {external, result_unexpected, <<"error:{unknown_error,{lim_turnover_processor,notfound}}", _/binary>>}}, - lim_client:hold(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)) + lim_client:hold(?LIMIT_CHANGE(ID, Version), Context, ?config(client, C)) ). hold_with_scope_unsupported(Scopes, C) -> - {ID, Version} = configure_limit(?time_range_month(), ?scope(Scopes), C), + {ID, Version} = configure_limit(?time_range_month(), ?scopes(Scopes), C), Context = case get_group_name(C) of withdrawals -> @@ -811,20 +754,20 @@ hold_with_scope_unsupported(Scopes, C) -> {woody_error, {external, result_unexpected, <<"error:{unknown_error,{lim_turnover_processor,{unsupported,bank_card}}}", _/binary>>}}, - lim_client:hold(?LIMIT_CHANGE(ID, ?CHANGE_ID, Version), Context, ?config(client, C)) + lim_client:hold(?LIMIT_CHANGE(ID, Version), Context, ?config(client, C)) ). -spec commit_with_sender_scope_ok(config()) -> _. commit_with_sender_scope_ok(C) -> - _ = commit_with_some_scope(?scope([?scope_sender()]), C). + _ = commit_with_some_scope(?scopes([?scope_sender()]), C). -spec commit_with_receiver_scope_ok(config()) -> _. commit_with_receiver_scope_ok(C) -> - _ = commit_with_some_scope(?scope([?scope_receiver()]), C). + _ = commit_with_some_scope(?scopes([?scope_receiver()]), C). -spec commit_with_sender_receiver_scope_ok(config()) -> _. commit_with_sender_receiver_scope_ok(C) -> - _ = commit_with_some_scope(?scope([?scope_sender(), ?scope_receiver()]), C). + _ = commit_with_some_scope(?scopes([?scope_sender(), ?scope_receiver()]), C). -spec commit_with_destination_field_scope_ok(config()) -> _. commit_with_destination_field_scope_ok(C) -> @@ -834,7 +777,7 @@ commit_with_destination_field_scope_ok(C) -> withdrawals -> ?wthdproc_ctx(?withdrawal(?cash(10, <<"RUB">>), ?generic_pt(), ?string)); _Default -> ?payproc_ctx_payment(?cash(10, <<"RUB">>), ?cash(10, <<"RUB">>)) end, - _ = commit_with_some_scope(?scope(Scopes), Context, C). + _ = commit_with_some_scope(?scopes(Scopes), Context, C). %% @@ -842,7 +785,7 @@ construct_request(C) -> ID = ?config(id, C), {ID0, Version0} = configure_limit( ?time_range_month(), - ?scope([?scope_provider(), ?scope_payment_tool()]), + ?scopes([?scope_provider(), ?scope_payment_tool()]), ?turnover_metric_amount(<<"RUB">>), undefined, genlib:format("~s/~B", [ID, 0]), @@ -850,7 +793,7 @@ construct_request(C) -> ), {ID1, Version1} = configure_limit( ?time_range_month(), - ?scope([?scope_provider(), ?scope_payment_tool()]), + ?scopes([?scope_provider(), ?scope_payment_tool()]), ?turnover_metric_amount(<<"RUB">>), undefined, genlib:format("~s/~B", [ID, 1]), @@ -858,16 +801,16 @@ construct_request(C) -> ), {ID2, Version2} = configure_limit( ?time_range_month(), - ?scope([?scope_provider(), ?scope_payment_tool()]), + ?scopes([?scope_provider(), ?scope_payment_tool()]), ?turnover_metric_amount(<<"RUB">>), undefined, genlib:format("~s/~B", [ID, 2]), C ), ?LIMIT_REQUEST(ID, [ - ?LIMIT_CHANGE(ID0, 0, Version0), - ?LIMIT_CHANGE(ID1, 0, Version1), - ?LIMIT_CHANGE(ID2, 0, Version2) + ?LIMIT_CHANGE(ID0, Version0), + ?LIMIT_CHANGE(ID1, Version1), + ?LIMIT_CHANGE(ID2, Version2) ]). -spec batch_hold_ok(config()) -> _. @@ -889,7 +832,7 @@ batch_commit_ok(C) -> end, Request = construct_request(C), ok = hold_and_assert_batch(10, Request, Context, C), - {ok, ok} = lim_client:commit_batch(Request, Context, ?config(client, C)), + ok = lim_client:commit_batch(Request, Context, ?config(client, C)), ok = assert_values(10, Request, Context, C). -spec batch_rollback_ok(config()) -> _. @@ -901,7 +844,7 @@ batch_rollback_ok(C) -> end, Request = construct_request(C), ok = hold_and_assert_batch(10, Request, Context, C), - {ok, ok} = lim_client:rollback_batch(Request, Context, ?config(client, C)), + ok = lim_client:rollback_batch(Request, Context, ?config(client, C)), ok = assert_values(0, Request, Context, C). -spec two_batch_hold_ok(config()) -> _. @@ -926,9 +869,9 @@ two_batch_commit_ok(C) -> ?LIMIT_REQUEST(RequestID, Changes) = Request0 = construct_request(C), Request1 = ?LIMIT_REQUEST(genlib:format("~s/~B", [RequestID, 1000]), Changes), ok = hold_and_assert_batch(10, Request0, Context, C), - {ok, ok} = lim_client:commit_batch(Request0, Context, ?config(client, C)), + ok = lim_client:commit_batch(Request0, Context, ?config(client, C)), ok = hold_and_assert_batch(20, Request1, Context, C), - {ok, ok} = lim_client:commit_batch(Request1, Context, ?config(client, C)), + ok = lim_client:commit_batch(Request1, Context, ?config(client, C)), ok = assert_values(20, Request1, Context, C). -spec two_batch_rollback_ok(config()) -> _. @@ -942,9 +885,9 @@ two_batch_rollback_ok(C) -> Request1 = ?LIMIT_REQUEST(genlib:format("~s/~B", [RequestID, 1000]), Changes), ok = hold_and_assert_batch(10, Request0, Context, C), ok = hold_and_assert_batch(20, Request1, Context, C), - {ok, ok} = lim_client:rollback_batch(Request0, Context, ?config(client, C)), + ok = lim_client:rollback_batch(Request0, Context, ?config(client, C)), ok = assert_values(10, Request1, Context, C), - {ok, ok} = lim_client:rollback_batch(Request1, Context, ?config(client, C)), + ok = lim_client:rollback_batch(Request1, Context, ?config(client, C)), ok = assert_values(0, Request1, Context, C). -spec retry_batch_hold_ok(config()) -> _. @@ -966,14 +909,14 @@ retry_batch_hold_ok(C) -> ok = assert_batch(10, Request0, Context, C), ok = assert_batch(20, Request1, Context, C), ok = assert_batch(30, Request2, Context, C), - {ok, ok} = lim_client:commit_batch(Request2, Context, ?config(client, C)), + ok = lim_client:commit_batch(Request2, Context, ?config(client, C)), ok = assert_values(30, Request1, Context, C), ok = assert_batch(10, Request0, Context, C), ok = assert_batch(20, Request1, Context, C), - {ok, ok} = lim_client:rollback_batch(Request1, Context, ?config(client, C)), + ok = lim_client:rollback_batch(Request1, Context, ?config(client, C)), ok = assert_values(20, Request1, Context, C), ok = assert_batch(10, Request0, Context, C), - {ok, ok} = lim_client:commit_batch(Request0, Context, ?config(client, C)), + ok = lim_client:commit_batch(Request0, Context, ?config(client, C)), ok = assert_values(20, Request1, Context, C). -spec batch_commit_less_ok(config()) -> _. @@ -987,7 +930,7 @@ batch_commit_less_ok(C) -> end, Request = construct_request(C), ok = hold_and_assert_batch(1000, Request, Context, C), - {ok, ok} = lim_client:commit_batch(Request, Context, ?config(client, C)), + ok = lim_client:commit_batch(Request, Context, ?config(client, C)), ok = assert_values(800, Request, Context, C). -spec batch_commit_more_ok(config()) -> _. @@ -1016,7 +959,7 @@ batch_commit_negative_ok(C) -> end, Request = construct_request(C), ok = hold_and_assert_batch(-1000, Request, Context, C), - {ok, ok} = lim_client:commit_batch(Request, Context, ?config(client, C)), + ok = lim_client:commit_batch(Request, Context, ?config(client, C)), ok = assert_values(-1000, Request, Context, C). -spec batch_commit_negative_less_ok(config()) -> _. @@ -1030,7 +973,7 @@ batch_commit_negative_less_ok(C) -> end, Request = construct_request(C), ok = hold_and_assert_batch(-1000, Request, Context, C), - {ok, ok} = lim_client:commit_batch(Request, Context, ?config(client, C)), + ok = lim_client:commit_batch(Request, Context, ?config(client, C)), ok = assert_values(-800, Request, Context, C). -spec batch_commit_negative_more_ok(config()) -> _. @@ -1072,53 +1015,50 @@ assert_values(Value, Request0, Context, C) -> ?assertEqual(Value, LimitState2#limiter_Limit.amount), ok. -gen_change_id(LimitID, ChangeID) -> - genlib:format("~s/~p", [LimitID, ChangeID]). - hold_and_commit(Change, Context, Client) -> hold_and_commit(Change, Context, Context, Client). -hold_and_commit(Change, Context, ContextCommit, Client) -> - {ok, {vector, _}} = lim_client:hold(Change, Context, Client), - {ok, {vector, _}} = lim_client:commit(Change, ContextCommit, Client). +hold_and_commit(?LIMIT_CHANGE(ID, Version) = Change, Context, ContextCommit, Client) -> + OperationID = lim_string:join($., [<<"operation">>, ID, integer_to_binary(Version), genlib:unique()]), + {ok, _} = lim_client:hold_batch(?LIMIT_REQUEST(OperationID, [Change]), Context, Client), + ok = lim_client:commit_batch(?LIMIT_REQUEST(OperationID, [Change]), ContextCommit, Client). mock_exchange(Rational, C) -> lim_mock:mock_services([{xrates, fun('GetConvertedAmount', _) -> {ok, Rational} end}], C). -configure_limit(TimeRange, Scope, C) -> - configure_limit(TimeRange, Scope, ?turnover_metric_amount(<<"RUB">>), C). +configure_limit(TimeRange, Scopes, C) -> + configure_limit(TimeRange, Scopes, ?turnover_metric_amount(<<"RUB">>), C). -configure_limit(TimeRange, Scope, Metric, C) -> - configure_limit(TimeRange, Scope, Metric, undefined, C). +configure_limit(TimeRange, Scopes, Metric, C) -> + configure_limit(TimeRange, Scopes, Metric, undefined, C). -configure_limit(TimeRange, Scope, Metric, CurrencyConversion, C) -> - configure_limit(TimeRange, Scope, Metric, CurrencyConversion, ?config(id, C), C). +configure_limit(TimeRange, Scopes, Metric, CurrencyConversion, C) -> + configure_limit(TimeRange, Scopes, Metric, CurrencyConversion, ?config(id, C), C). -configure_limit(TimeRange, Scope, Metric, CurrencyConversion, ID, C) -> +configure_limit(TimeRange, Scopes, Metric, CurrencyConversion, ID, C) when is_list(Scopes) -> ContextType = case get_group_name(C) of withdrawals -> ?ctx_type_wthdproc(); _Default -> ?ctx_type_payproc() end, - CreateParams = #config_LimitConfigParams{ - id = ID, + create_limit_config(ID, #limiter_config_LimitConfig{ + processor_type = <<"TurnoverProcessor">>, started_at = <<"2000-01-01T00:00:00Z">>, - time_range_type = TimeRange, shard_size = 1, - type = ?lim_type_turnover(Metric), - scope = Scope, + time_range_type = TimeRange, context_type = ContextType, + type = ?lim_type_turnover(Metric), + scopes = Scopes, + description = <<"Description">>, op_behaviour = ?op_behaviour(?op_subtraction()), currency_conversion = CurrencyConversion + }). + +create_limit_config(ID, #limiter_config_LimitConfig{} = LimitConfig) -> + LimitConfigObject = #domain_LimitConfigObject{ + ref = #domain_LimitConfigRef{id = ID}, + data = LimitConfig }, - ConfigSource = proplists:get_value(limit_config_source, C, legacy), - put_config_into_repository(ConfigSource, CreateParams, ?config(client, C)). - -put_config_into_repository(legacy, #config_LimitConfigParams{id = ID} = CreateParams, Client) -> - {ok, _LimitConfig} = lim_client:create_config(CreateParams, Client), - {ID, undefined}; -put_config_into_repository(repository, #config_LimitConfigParams{id = ID} = CreateParams, _Client) -> - LimitConfigObject = mk_limit_config_object(CreateParams), Version = dmt_client:insert({limit_config, LimitConfigObject}, ensure_stub_author()), {ID, Version}. @@ -1129,70 +1069,6 @@ get_group_name(C) -> GroupProps = ?config(tc_group_properties, C), proplists:get_value(name, GroupProps). -mk_limit_config_object(#config_LimitConfigParams{ - id = ID, - started_at = StartedAt, - time_range_type = TimeRange, - shard_size = ShardSize, - type = Type, - scope = Scope, - context_type = ContextType, - op_behaviour = OpBehaviour -}) -> - #domain_LimitConfigObject{ - ref = #domain_LimitConfigRef{id = ID}, - data = #limiter_config_LimitConfig{ - processor_type = <<"TurnoverProcessor">>, - created_at = StartedAt, - started_at = StartedAt, - shard_size = ShardSize, - time_range_type = translate_time_range_type(TimeRange), - context_type = translate_tuple_record(ContextType, "config", "limiter_config"), - type = maybe_apply(Type, fun translate_type/1), - scopes = maybe_apply(Scope, fun translate_scope/1), - description = <<"Description">>, - op_behaviour = maybe_apply(OpBehaviour, fun mk_op_behaviour/1) - } - }. - -mk_op_behaviour(#config_OperationLimitBehaviour{invoice_payment_refund = PaymentRefund}) -> - #limiter_config_OperationLimitBehaviour{ - invoice_payment_refund = maybe_apply(PaymentRefund, fun(Item) -> - translate_tuple_record(Item, "config", "limiter_config") - end) - }. - -%% Interval type is never used in this test suite. To appease dialyzer this clause is commented out. -% translate_time_range_type({interval, #timerange_TimeRangeTypeInterval{amount = Amount}}) -> -% {interval, #limiter_config_TimeRangeTypeInterval{amount = Amount}}; -translate_time_range_type({calendar, CalendarType}) -> - {calendar, translate_tuple_record(CalendarType, "timerange", "limiter_config")}. - -translate_type({turnover, #config_LimitTypeTurnover{metric = Metric}}) -> - {turnover, #limiter_config_LimitTypeTurnover{metric = translate_tuple_record(Metric, "config", "limiter_config")}}. - -translate_scope({single, ScopeType}) -> - ordsets:from_list([translate_scope_type(ScopeType)]); -translate_scope({multi, ScopeTypes}) -> - ordsets:from_list(lists:map(fun translate_scope_type/1, ordsets:to_list(ScopeTypes))). - -translate_scope_type({Scope, #config_LimitScopeEmptyDetails{}}) -> - {Scope, #limiter_config_LimitScopeEmptyDetails{}}. - -maybe_apply(undefined, _) -> - undefined; -maybe_apply(Value, Fun) -> - Fun(Value). - -translate_tuple_record({Type, Record}, OldRecordPrefix, NewRecordPrefix) -> - {Type, change_record_name_prefix(Record, OldRecordPrefix, NewRecordPrefix)}. - -change_record_name_prefix(Record, OldPrefix, NewPrefix) -> - RecordName = atom_to_list(element(1, Record)), - [OldPrefix, TypeName] = string:split(RecordName, "_", trailing), - NewRecordName = list_to_existing_atom(NewPrefix ++ "_" ++ TypeName), - setelement(1, Record, NewRecordName). - ensure_stub_author() -> %% TODO DISCUSS Stubs and fallback authors ensure_author(~b"unknown", ~b"unknown@local"). diff --git a/compose.tracing.yaml b/compose.tracing.yaml index 2e8e4a70..2d3d3202 100644 --- a/compose.tracing.yaml +++ b/compose.tracing.yaml @@ -7,9 +7,6 @@ services: OTEL_EXPORTER_OTLP_PROTOCOL: http_protobuf OTEL_EXPORTER_OTLP_ENDPOINT: http://jaeger:4318 - machinegun: - environment: *otlp_enabled - testrunner: environment: <<: *otlp_enabled diff --git a/compose.yaml b/compose.yaml index cda6fd28..f723d8e7 100644 --- a/compose.yaml +++ b/compose.yaml @@ -16,15 +16,11 @@ services: depends_on: dmt: condition: service_healthy - machinegun: - condition: service_healthy - shumway: - condition: service_healthy liminator: condition: service_healthy dmt: - image: ghcr.io/valitydev/dominant-v2:sha-f55c065 + image: ghcr.io/valitydev/dominant-v2:sha-90f5fa2 command: /opt/dmt/bin/dmt foreground healthcheck: test: "/opt/dmt/bin/dmt ping" @@ -37,41 +33,8 @@ services: volumes: - ./test/dmt/sys.config:/opt/dmt/releases/0.1/sys.config - machinegun: - image: ghcr.io/valitydev/machinegun:sha-5c0db56 - command: /opt/machinegun/bin/machinegun foreground - volumes: - - ./test/machinegun/config.yaml:/opt/machinegun/etc/config.yaml - - ./test/machinegun/cookie:/opt/machinegun/etc/cookie - healthcheck: - test: "/opt/machinegun/bin/machinegun ping" - interval: 5s - timeout: 1s - retries: 10 - - shumway: - image: docker.io/rbkmoney/shumway:44eb989065b27be619acd16b12ebdb2288b46c36 - restart: unless-stopped - entrypoint: - - java - - -Xmx512m - - -jar - - /opt/shumway/shumway.jar - - --spring.datasource.url=jdbc:postgresql://db:5432/shumway - - --spring.datasource.username=shumway - - --spring.datasource.password=postgres - - --management.metrics.export.statsd.enabled=false - depends_on: - db: - condition: service_healthy - healthcheck: - test: "curl http://localhost:8022/actuator/health" - interval: 5s - timeout: 1s - retries: 20 - liminator: - image: ghcr.io/valitydev/liminator:sha-fc6546f + image: ghcr.io/valitydev/liminator:sha-e284ad2 restart: unless-stopped entrypoint: - java @@ -100,7 +63,7 @@ services: volumes: - ./test/postgres/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d healthcheck: - test: ["CMD-SHELL", "pg_isready -U hellgate"] + test: ["CMD-SHELL", "pg_isready -U liminator"] interval: 10s timeout: 5s retries: 5 diff --git a/config/sys.config b/config/sys.config index 30bf1c03..28105ec5 100644 --- a/config/sys.config +++ b/config/sys.config @@ -5,21 +5,12 @@ {services, #{ limiter => #{ path => <<"/v1/limiter">> - }, - configurator => #{ - path => <<"/v1/configurator">> } }}, {service_clients, #{ liminator => #{ url => <<"http://liminator:8022/liminator/v1">> }, - accounter => #{ - url => <<"http://shumway:8022/accounter">> - }, - automaton => #{ - url => <<"http://machinegun:8022/v1/automaton">> - }, xrates => #{ url => <<"http://xrates:8022/xrates">> } @@ -57,15 +48,6 @@ }} ]}, - {scoper_event_handler_options, #{ - event_handler_opts => #{ - formatter_opts => #{ - max_length => 1000, - max_printable_string_length => 80 - } - } - }}, - {health_check, #{ % disk => {erl_health, disk , ["/", 99]}, % memory => {erl_health, cg_memory, [99]}, diff --git a/rebar.config b/rebar.config index ce0e3df0..9e0954a3 100644 --- a/rebar.config +++ b/rebar.config @@ -28,11 +28,10 @@ {deps, [ {prometheus, "4.11.0"}, {prometheus_cowboy, "0.1.9"}, - {damsel, {git, "https://github.com/valitydev/damsel.git", {tag, "v2.2.12"}}}, - {limiter_proto, {git, "https://github.com/valitydev/limiter-proto.git", {branch, "master"}}}, + {damsel, {git, "https://github.com/valitydev/damsel.git", {tag, "v2.2.17"}}}, + {limiter_proto, {git, "https://github.com/valitydev/limiter-proto.git", {tag, "v2.1.0"}}}, {liminator_proto, {git, "https://github.com/valitydev/liminator-proto.git", {branch, "master"}}}, {xrates_proto, {git, "https://github.com/valitydev/xrates-proto.git", {branch, "master"}}}, - {machinery, {git, "https://github.com/valitydev/machinery-erlang.git", {tag, "v1.1.10"}}}, {erl_health, {git, "https://github.com/valitydev/erlang-health.git", {branch, "master"}}}, {genlib, {git, "https://github.com/valitydev/genlib.git", {tag, "v1.1.0"}}}, {scoper, {git, "https://github.com/valitydev/scoper.git", {tag, "v1.1.0"}}}, diff --git a/rebar.lock b/rebar.lock index 5f70b8fc..3f8b6b45 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,12 +1,7 @@ {"1.2.0", [{<<"accept">>,{pkg,<<"accept">>,<<"0.3.7">>},2}, {<<"acceptor_pool">>,{pkg,<<"acceptor_pool">>,<<"1.0.0">>},2}, - {<<"brod">>,{pkg,<<"brod">>,<<"4.3.2">>},2}, {<<"cache">>,{pkg,<<"cache">>,<<"2.3.3">>},1}, - {<<"canal">>, - {git,"https://github.com/valitydev/canal", - {ref,"89faedce3b054bcca7cc31ca64d2ead8a9402305"}}, - 3}, {<<"certifi">>,{pkg,<<"certifi">>,<<"2.8.0">>},2}, {<<"cg_mon">>, {git,"https://github.com/rbkmoney/cg_mon.git", @@ -15,24 +10,15 @@ {<<"chatterbox">>,{pkg,<<"ts_chatterbox">>,<<"0.15.1">>},2}, {<<"cowboy">>,{pkg,<<"cowboy">>,<<"2.9.0">>},1}, {<<"cowlib">>,{pkg,<<"cowlib">>,<<"2.11.0">>},2}, - {<<"crc32cer">>,{pkg,<<"crc32cer">>,<<"0.1.11">>},4}, {<<"ctx">>,{pkg,<<"ctx">>,<<"0.6.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"8cab698cd78125ac47489d0ba81169df376757a4"}}, + {ref,"f831d3aa5fdfd0338b41af44d1eeffe810ca9708"}}, 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git", {ref,"20c18cc9b51d0f273db60c929e8a8a871d6a1866"}}, 0}, - {<<"epg_connector">>, - {git,"https://github.com/valitydev/epg_connector.git", - {ref,"2e86da8083908d0d35a4eed3e2168c9ba6a8d04a"}}, - 2}, - {<<"epgsql">>, - {git,"https://github.com/epgsql/epgsql.git", - {ref,"28e9f84c95065a51e92baeb37d2cf1687fc4b9ce"}}, - 3}, {<<"erl_health">>, {git,"https://github.com/valitydev/erlang-health.git", {ref,"49716470d0e8dab5e37db55d52dea78001735a3d"}}, @@ -46,26 +32,16 @@ {<<"hackney">>,{pkg,<<"hackney">>,<<"1.18.0">>},1}, {<<"hpack">>,{pkg,<<"hpack_erl">>,<<"0.3.0">>},3}, {<<"idna">>,{pkg,<<"idna">>,<<"6.1.1">>},2}, - {<<"jsone">>,{pkg,<<"jsone">>,<<"1.8.0">>},4}, {<<"jsx">>,{pkg,<<"jsx">>,<<"3.1.0">>},1}, - {<<"kafka_protocol">>,{pkg,<<"kafka_protocol">>,<<"4.1.10">>},3}, {<<"liminator_proto">>, {git,"https://github.com/valitydev/liminator-proto.git", {ref,"d63ded1d138416e4278d7fbe57d7359c9113f7ea"}}, 0}, {<<"limiter_proto">>, {git,"https://github.com/valitydev/limiter-proto.git", - {ref,"d4cdf0f6328125996ee705c3da87461f99dde7f4"}}, - 0}, - {<<"machinery">>, - {git,"https://github.com/valitydev/machinery-erlang.git", - {ref,"066f6cecd5d8f011c1b3bf8e45f6ad967c0e733d"}}, + {ref,"1af3724af24dd8b5ad9ce2ae80cd42318b471397"}}, 0}, {<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},2}, - {<<"mg_proto">>, - {git,"https://github.com/valitydev/machinegun-proto", - {ref,"cc2c27c30d30dc34c0c56fc7c7e96326d6bd6a14"}}, - 1}, {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.4.0">>},2}, {<<"opentelemetry">>,{pkg,<<"opentelemetry">>,<<"1.5.0">>},0}, {<<"opentelemetry_api">>,{pkg,<<"opentelemetry_api">>,<<"1.4.0">>},0}, @@ -73,16 +49,11 @@ {pkg,<<"opentelemetry_exporter">>,<<"1.8.0">>}, 0}, {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.1">>},2}, - {<<"progressor">>, - {git,"https://github.com/valitydev/progressor.git", - {ref,"d429410a0f2b42fbcc22b340cc8dc1915b13f119"}}, - 1}, {<<"prometheus">>,{pkg,<<"prometheus">>,<<"4.11.0">>},0}, {<<"prometheus_cowboy">>,{pkg,<<"prometheus_cowboy">>,<<"0.1.9">>},0}, {<<"prometheus_httpd">>,{pkg,<<"prometheus_httpd">>,<<"2.1.15">>},1}, {<<"quantile_estimator">>,{pkg,<<"quantile_estimator">>,<<"0.2.1">>},1}, {<<"ranch">>,{pkg,<<"ranch">>,<<"1.8.0">>},2}, - {<<"recon">>,{pkg,<<"recon">>,<<"2.5.6">>},2}, {<<"scoper">>, {git,"https://github.com/valitydev/scoper.git", {ref,"0e7aa01e9632daa39727edd62d4656ee715b4569"}}, @@ -97,7 +68,7 @@ {ref,"3a60e5dc5bbd709495024f26e100b041c3547fd9"}}, 1}, {<<"tls_certificate_check">>, - {pkg,<<"tls_certificate_check">>,<<"1.28.0">>}, + {pkg,<<"tls_certificate_check">>,<<"1.29.0">>}, 1}, {<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.7.1">>},2}, {<<"woody">>, @@ -112,22 +83,18 @@ {pkg_hash,[ {<<"accept">>, <<"CD6E34A2D7E28CA38B2D3CB233734CA0C221EFBC1F171F91FEC5F162CC2D18DA">>}, {<<"acceptor_pool">>, <<"43C20D2ACAE35F0C2BCD64F9D2BDE267E459F0F3FD23DAB26485BF518C281B21">>}, - {<<"brod">>, <<"51F4DFF17ED43A806558EBD62CC88E7B35AED336D1BA1F3DE2D010F463D49736">>}, {<<"cache">>, <<"B23A5FE7095445A88412A6E614C933377E0137B44FFED77C9B3FEF1A731A20B2">>}, {<<"certifi">>, <<"D4FB0A6BB20B7C9C3643E22507E42F356AC090A1DCEA9AB99E27E0376D695EBA">>}, {<<"chatterbox">>, <<"5CAC4D15DD7AD61FC3C4415CE4826FC563D4643DEE897A558EC4EA0B1C835C9C">>}, {<<"cowboy">>, <<"865DD8B6607E14CF03282E10E934023A1BD8BE6F6BACF921A7E2A96D800CD452">>}, {<<"cowlib">>, <<"0B9FF9C346629256C42EBE1EEB769A83C6CB771A6EE5960BD110AB0B9B872063">>}, - {<<"crc32cer">>, <<"B550DA6D615FEB72A882D15D020F8F7DEE72DFB2CB1BCDF3B1EE8DC2AFD68CFC">>}, {<<"ctx">>, <<"8FF88B70E6400C4DF90142E7F130625B82086077A45364A78D208ED3ED53C7FE">>}, {<<"gproc">>, <<"853CCB7805E9ADA25D227A157BA966F7B34508F386A3E7E21992B1B484230699">>}, {<<"grpcbox">>, <<"6E040AB3EF16FE699FFB513B0EF8E2E896DA7B18931A1EF817143037C454BCCE">>}, {<<"hackney">>, <<"C4443D960BB9FBA6D01161D01CD81173089686717D9490E5D3606644C48D121F">>}, {<<"hpack">>, <<"2461899CC4AB6A0EF8E970C1661C5FC6A52D3C25580BC6DD204F84CE94669926">>}, {<<"idna">>, <<"8A63070E9F7D0C62EB9D9FCB360A7DE382448200FBBD1B106CC96D3D8099DF8D">>}, - {<<"jsone">>, <<"347FF1FA700E182E1F9C5012FA6D737B12C854313B9AE6954CA75D3987D6C06D">>}, {<<"jsx">>, <<"D12516BAA0BB23A59BB35DCCAF02A1BD08243FCBB9EFE24F2D9D056CCFF71268">>}, - {<<"kafka_protocol">>, <<"F917B6C90C8DF0DE2B40A87D6B9AE1CFCE7788E91A65818E90E40CF76111097A">>}, {<<"metrics">>, <<"25F094DEA2CDA98213CECC3AEFF09E940299D950904393B2A29D191C346A8486">>}, {<<"mimerl">>, <<"3882A5CA67FBBE7117BA8947F27643557ADEC38FA2307490C4C4207624CB213B">>}, {<<"opentelemetry">>, <<"7DDA6551EDFC3050EA4B0B40C0D2570423D6372B97E9C60793263EF62C53C3C2">>}, @@ -139,29 +106,24 @@ {<<"prometheus_httpd">>, <<"8F767D819A5D36275EAB9264AFF40D87279151646776069BF69FBDBBD562BD75">>}, {<<"quantile_estimator">>, <<"EF50A361F11B5F26B5F16D0696E46A9E4661756492C981F7B2229EF42FF1CD15">>}, {<<"ranch">>, <<"8C7A100A139FD57F17327B6413E4167AC559FBC04CA7448E9BE9057311597A1D">>}, - {<<"recon">>, <<"9052588E83BFEDFD9B72E1034532AEE2A5369D9D9343B61AEB7FBCE761010741">>}, {<<"ssl_verify_fun">>, <<"354C321CF377240C7B8716899E182CE4890C5938111A1296ADD3EC74CF1715DF">>}, - {<<"tls_certificate_check">>, <<"C39BF21F67C2D124AE905454FAD00F27E625917E8AB1009146E916E1DF6AB275">>}, + {<<"tls_certificate_check">>, <<"4473005EB0BBDAD215D7083A230E2E076F538D9EA472C8009FD22006A4CFC5F6">>}, {<<"unicode_util_compat">>, <<"A48703A25C170EEDADCA83B11E88985AF08D35F37C6F664D6DCFB106A97782FC">>}]}, {pkg_hash_ext,[ {<<"accept">>, <<"CA69388943F5DAD2E7232A5478F16086E3C872F48E32B88B378E1885A59F5649">>}, {<<"acceptor_pool">>, <<"0CBCD83FDC8B9AD2EEE2067EF8B91A14858A5883CB7CD800E6FCD5803E158788">>}, - {<<"brod">>, <<"88584FDEBA746AA6729E2A1826416C10899954F68AF93659B3C2F38A2DCAA27C">>}, {<<"cache">>, <<"44516CE6FA03594D3A2AF025DD3A87BFE711000EB730219E1DDEFC816E0AA2F4">>}, {<<"certifi">>, <<"6AC7EFC1C6F8600B08D625292D4BBF584E14847CE1B6B5C44D983D273E1097EA">>}, {<<"chatterbox">>, <<"4F75B91451338BC0DA5F52F3480FA6EF6E3A2AEECFC33686D6B3D0A0948F31AA">>}, {<<"cowboy">>, <<"2C729F934B4E1AA149AFF882F57C6372C15399A20D54F65C8D67BEF583021BDE">>}, {<<"cowlib">>, <<"2B3E9DA0B21C4565751A6D4901C20D1B4CC25CBB7FD50D91D2AB6DD287BC86A9">>}, - {<<"crc32cer">>, <<"A39B8F0B1990AC1BF06C3A247FC6A178B740CDFC33C3B53688DC7DD6B1855942">>}, {<<"ctx">>, <<"A14ED2D1B67723DBEBBE423B28D7615EB0BDCBA6FF28F2D1F1B0A7E1D4AA5FC2">>}, {<<"gproc">>, <<"587E8AF698CCD3504CF4BA8D90F893EDE2B0F58CABB8A916E2BF9321DE3CF10B">>}, {<<"grpcbox">>, <<"4A3B5D7111DAABC569DC9CBD9B202A3237D81C80BF97212FBC676832CB0CEB17">>}, {<<"hackney">>, <<"9AFCDA620704D720DB8C6A3123E9848D09C87586DC1C10479C42627B905B5C5E">>}, {<<"hpack">>, <<"D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0">>}, {<<"idna">>, <<"92376EB7894412ED19AC475E4A86F7B413C1B9FBB5BD16DCCD57934157944CEA">>}, - {<<"jsone">>, <<"08560B78624A12E0B5E7EC0271EC8CA38EF51F63D84D84843473E14D9B12618C">>}, {<<"jsx">>, <<"0C5CC8FDC11B53CC25CF65AC6705AD39E54ECC56D1C22E4ADB8F5A53FB9427F3">>}, - {<<"kafka_protocol">>, <<"DF680A3706EAD8695F8B306897C0A33E8063C690DA9308DB87B462CFD7029D04">>}, {<<"metrics">>, <<"69B09ADDDC4F74A40716AE54D140F93BEB0FB8978D8636EADED0C31B6F099F16">>}, {<<"mimerl">>, <<"13AF15F9F68C65884ECCA3A3891D50A7B57D82152792F3E19D88650AA126B144">>}, {<<"opentelemetry">>, <<"CDF4F51D17B592FC592B9A75F86A6F808C23044BA7CF7B9534DEBBCC5C23B0EE">>}, @@ -173,8 +135,7 @@ {<<"prometheus_httpd">>, <<"67736D000745184D5013C58A63E947821AB90CB9320BC2E6AE5D3061C6FFE039">>}, {<<"quantile_estimator">>, <<"282A8A323CA2A845C9E6F787D166348F776C1D4A41EDE63046D72D422E3DA946">>}, {<<"ranch">>, <<"49FBCFD3682FAB1F5D109351B61257676DA1A2FDBE295904176D5E521A2DDFE5">>}, - {<<"recon">>, <<"96C6799792D735CC0F0FD0F86267E9D351E63339CBE03DF9D162010CEFC26BB0">>}, {<<"ssl_verify_fun">>, <<"FE4C190E8F37401D30167C8C405EDA19469F34577987C76DDE613E838BBC67F8">>}, - {<<"tls_certificate_check">>, <<"3AB058C3F9457FFFCA916729587415F0DDC822048A0E5B5E2694918556D92DF1">>}, + {<<"tls_certificate_check">>, <<"5B0D0E5CB0F928BC4F210DF667304ED91C5BFF2A391CE6BDEDFBFE70A8F096C5">>}, {<<"unicode_util_compat">>, <<"B3A917854CE3AE233619744AD1E0102E05673136776FB2FA76234F3E03B23642">>}]} ]. diff --git a/test/machinegun/config.yaml b/test/machinegun/config.yaml deleted file mode 100644 index aa4ee45d..00000000 --- a/test/machinegun/config.yaml +++ /dev/null @@ -1,21 +0,0 @@ -service_name: machinegun - -erlang: - secret_cookie_file: "/opt/machinegun/etc/cookie" - -namespaces: - lim/config_v1: - processor: - url: http://limiter:8022/v1/stateproc/lim/config_v1 - pool_size: 500 - lim/range_v1: - processor: - url: http://limiter:8022/v1/stateproc/lim/range_v1 - pool_size: 500 - -storage: - type: memory - -logging: - out_type: stdout - level: info diff --git a/test/machinegun/cookie b/test/machinegun/cookie deleted file mode 100644 index 24b9aa4a..00000000 --- a/test/machinegun/cookie +++ /dev/null @@ -1 +0,0 @@ -BENDER-COOKIE-MONSTER From 80fb5bcdaa914e436d66fe4b3b8fc5c77c6c196c Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Fri, 10 Oct 2025 15:52:52 +0300 Subject: [PATCH 52/54] Bumps liminator and reenables partial-commit testcases (#45) --- apps/limiter/test/lim_turnover_SUITE.erl | 66 +++++++++++------------- compose.yaml | 2 +- 2 files changed, 31 insertions(+), 37 deletions(-) diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index db8f3817..5562f19d 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -40,10 +40,10 @@ -export([commit_processes_idempotently/1]). -export([full_commit_processes_idempotently/1]). -export([partial_commit_processes_idempotently/1]). -%% -export([rollback_processes_idempotently/1]). +-export([rollback_processes_idempotently/1]). -export([commit_number_ok/1]). -%% -export([rollback_number_ok/1]). +-export([rollback_number_ok/1]). -export([commit_refund_keep_number_unchanged/1]). -export([partial_commit_number_counts_as_single_op/1]). @@ -154,15 +154,15 @@ groups() -> ]}, {cashless, [parallel], [ commit_number_ok, - %% rollback_number_ok, + rollback_number_ok, commit_refund_keep_number_unchanged, partial_commit_number_counts_as_single_op ]}, {idempotency, [parallel], [ commit_processes_idempotently, full_commit_processes_idempotently, - partial_commit_processes_idempotently - %% rollback_processes_idempotently + partial_commit_processes_idempotently, + rollback_processes_idempotently ]} ]. @@ -542,20 +542,18 @@ partial_commit_processes_idempotently(C) -> ok = lim_client:commit(Change, Context, Client), {ok, Limit = #limiter_Limit{amount = 40}} = lim_client:get(ID, Version, Context, Client). -%% NOTE This test case is temporary disabled for same reason as -%% `rollback_number_ok`. -%% -spec rollback_processes_idempotently(config()) -> _. -%% rollback_processes_idempotently(C) -> -%% Client = ?config(client, C), -%% {ID, Version} = configure_limit(?time_range_week(), ?global(), C), -%% Context = ?payproc_ctx_payment(?cash(42), ?cash(0)), -%% Change = ?LIMIT_CHANGE(ID, Version), -%% ok = lim_client:hold(Change, Context, Client), -%% ok = lim_client:hold(Change, Context, Client), -%% ok = lim_client:commit(Change, Context, Client), -%% {ok, Limit = #limiter_Limit{amount = 0}} = lim_client:get(ID, Version, Context, Client), -%% ok = lim_client:commit(Change, Context, Client), -%% {ok, Limit = #limiter_Limit{amount = 0}} = lim_client:get(ID, Version, Context, Client). +-spec rollback_processes_idempotently(config()) -> _. +rollback_processes_idempotently(C) -> + Client = ?config(client, C), + {ID, Version} = configure_limit(?time_range_week(), ?global(), C), + Context = ?payproc_ctx_payment(?cash(42), ?cash(0)), + Change = ?LIMIT_CHANGE(ID, Version), + ok = lim_client:hold(Change, Context, Client), + ok = lim_client:hold(Change, Context, Client), + ok = lim_client:commit(Change, Context, Client), + {ok, Limit = #limiter_Limit{amount = 0}} = lim_client:get(ID, Version, Context, Client), + ok = lim_client:commit(Change, Context, Client), + {ok, Limit = #limiter_Limit{amount = 0}} = lim_client:get(ID, Version, Context, Client). %% @@ -572,23 +570,19 @@ commit_number_ok(C) -> LimitState0#limiter_Limit.amount + 1 ). -%% NOTE This test case is temporary disabled because of the bug in liminator: it -%% doesn't respect partial commits with final value of change in operation -%% distinct from value of initial hold. Somehow value of limit counter is -%% aggregated with summing changes up of both (!) hold and commit values. -%% -spec rollback_number_ok(config()) -> _. -%% rollback_number_ok(C) -> -%% Client = ?config(client, C), -%% {ID, Version} = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C), -%% Context = ?payproc_ctx_payment(?cash(10), ?cash(10)), -%% ContextRollback = ?payproc_ctx_payment(?cash(10), ?cash(0)), -%% {ok, LimitState0} = lim_client:get(ID, Version, Context, Client), -%% _ = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context, ContextRollback, Client), -%% {ok, LimitState1} = lim_client:get(ID, Version, Context, Client), -%% ?assertEqual( -%% LimitState1#limiter_Limit.amount, -%% LimitState0#limiter_Limit.amount -%% ). +-spec rollback_number_ok(config()) -> _. +rollback_number_ok(C) -> + Client = ?config(client, C), + {ID, Version} = configure_limit(?time_range_week(), ?global(), ?turnover_metric_number(), C), + Context = ?payproc_ctx_payment(?cash(10), ?cash(10)), + ContextRollback = ?payproc_ctx_payment(?cash(10), ?cash(0)), + {ok, LimitState0} = lim_client:get(ID, Version, Context, Client), + _ = hold_and_commit(?LIMIT_CHANGE(ID, Version), Context, ContextRollback, Client), + {ok, LimitState1} = lim_client:get(ID, Version, Context, Client), + ?assertEqual( + LimitState1#limiter_Limit.amount, + LimitState0#limiter_Limit.amount + ). -spec commit_refund_keep_number_unchanged(config()) -> _. commit_refund_keep_number_unchanged(C) -> diff --git a/compose.yaml b/compose.yaml index f723d8e7..ab3863bb 100644 --- a/compose.yaml +++ b/compose.yaml @@ -34,7 +34,7 @@ services: - ./test/dmt/sys.config:/opt/dmt/releases/0.1/sys.config liminator: - image: ghcr.io/valitydev/liminator:sha-e284ad2 + image: ghcr.io/valitydev/liminator:sha-48e1a4a restart: unless-stopped entrypoint: - java From 67cf0b707fd5f47455b15f3cd6fe54d5a887d04b Mon Sep 17 00:00:00 2001 From: Aleksey Kashapov Date: Wed, 25 Feb 2026 15:13:43 +0300 Subject: [PATCH 53/54] BG-753: Adds operations grouping by finalization behaviour (#46) * Adds WIP on grouped operations by finalization behaviour * Fixes operation wrappers and tests * Reworks limiter's sub operations for conditional finalization * Fixes config marshalling unit test * Adds tests for invertable limit operations * Formats code * Bumps protos * Fixes DRY linter warning * Adds notes about changes grouping --- apps/limiter/src/lim_accounting.erl | 177 -------------------- apps/limiter/src/lim_config_machine.erl | 142 +++++++++++++--- apps/limiter/src/lim_payproc_context.erl | 13 ++ apps/limiter/src/lim_turnover_processor.erl | 7 +- apps/limiter/test/lim_ct_helper.hrl | 14 +- apps/limiter/test/lim_turnover_SUITE.erl | 129 ++++++++++---- compose.yaml | 2 +- rebar.config | 4 +- rebar.lock | 4 +- 9 files changed, 249 insertions(+), 243 deletions(-) delete mode 100644 apps/limiter/src/lim_accounting.erl diff --git a/apps/limiter/src/lim_accounting.erl b/apps/limiter/src/lim_accounting.erl deleted file mode 100644 index 397d7eeb..00000000 --- a/apps/limiter/src/lim_accounting.erl +++ /dev/null @@ -1,177 +0,0 @@ --module(lim_accounting). - --include_lib("damsel/include/dmsl_accounter_thrift.hrl"). --include_lib("damsel/include/dmsl_base_thrift.hrl"). - --export([plan/3]). --export([hold/3]). --export([commit/3]). --export([rollback/3]). --export([get_plan/2]). --export([get_balance/2]). --export([create_account/2]). - --export([noncurrency/0]). - --type currency() :: dmsl_domain_thrift:'CurrencySymbolicCode'(). --type amount() :: dmsl_domain_thrift:'Amount'(). --type plan_id() :: dmsl_accounter_thrift:'PlanID'(). --type batch_id() :: dmsl_accounter_thrift:'BatchID'(). --type posting() :: dmsl_accounter_thrift:'Posting'(). --type batch() :: {batch_id(), [posting()]}. --type account_id() :: dmsl_accounter_thrift:'AccountID'(). --type lim_context() :: lim_context:t(). - --type balance() :: #{ - account_id := account_id(), - own_amount := amount(), - min_available_amount := amount(), - max_available_amount := amount(), - currency := currency() -}. - --type invalid_request_error() :: {invalid_request, list(binary())}. - --export_type([account_id/0]). --export_type([amount/0]). --export_type([balance/0]). --export_type([plan_id/0]). --export_type([batch/0]). --export_type([posting/0]). --export_type([batch_id/0]). --export_type([invalid_request_error/0]). - --define(NONCURRENCY, <<>>). - --spec plan(plan_id(), [batch()], lim_context()) -> ok | {error, invalid_request_error()}. -plan(_PlanID, [], _LimitContext) -> - error(badarg); -plan(_PlanID, Batches, _LimitContext) when not is_list(Batches) -> - error(badarg); -plan(PlanID, Batches, LimitContext) -> - lists:foldl( - fun(Batch, _) -> hold(PlanID, Batch, LimitContext) end, - undefined, - Batches - ). - --spec hold(plan_id(), batch(), lim_context()) -> ok | {error, invalid_request_error()}. -hold(PlanID, Batch, LimitContext) -> - do('Hold', construct_plan_change(wrap_plan_id(PlanID), Batch), LimitContext). - --spec commit(plan_id(), [batch()], lim_context()) -> ok | {error, invalid_request_error()}. -commit(PlanID, Batches, LimitContext) -> - do('CommitPlan', construct_plan(wrap_plan_id(PlanID), Batches), LimitContext). - --spec rollback(plan_id(), [batch()], lim_context()) -> ok | {error, invalid_request_error()}. -rollback(PlanID, Batches, LimitContext) -> - do('RollbackPlan', construct_plan(wrap_plan_id(PlanID), Batches), LimitContext). - --spec get_plan(plan_id(), lim_context()) -> {ok, [batch()]} | {error, notfound}. -get_plan(PlanID, LimitContext) -> - case call_accounter('GetPlan', {wrap_plan_id(PlanID)}, LimitContext) of - {ok, #accounter_PostingPlan{batch_list = BatchList}} -> - {ok, decode_batch_list(BatchList)}; - {exception, #accounter_PlanNotFound{}} -> - {error, notfound} - end. - --spec get_balance(account_id(), lim_context()) -> {ok, balance()} | {error, notfound}. -get_balance(AccountID, LimitContext) -> - case call_accounter('GetAccountByID', {AccountID}, LimitContext) of - {ok, Result} -> - {ok, construct_balance(AccountID, Result)}; - {exception, #accounter_AccountNotFound{}} -> - {error, notfound} - end. - -do(Op, Plan, LimitContext) -> - case call_accounter(Op, {Plan}, LimitContext) of - {ok, _Clock} -> - ok; - {exception, Exception} -> - {error, {invalid_request, convert_exception(Exception)}} - end. - -construct_plan_change(PlanID, {BatchID, Postings}) -> - #accounter_PostingPlanChange{ - id = PlanID, - batch = #accounter_PostingBatch{ - id = BatchID, - postings = Postings - } - }. - -construct_plan(PlanID, Batches) -> - #accounter_PostingPlan{ - id = PlanID, - batch_list = [ - #accounter_PostingBatch{ - id = BatchID, - postings = Postings - } - || {BatchID, Postings} <- Batches - ] - }. - -decode_batch_list(BatchList) -> - [{BatchID, Postings} || #accounter_PostingBatch{id = BatchID, postings = Postings} <- BatchList]. - -construct_balance( - AccountID, - #accounter_Account{ - own_amount = OwnAmount, - min_available_amount = MinAvailableAmount, - max_available_amount = MaxAvailableAmount, - currency_sym_code = Currency - } -) -> - #{ - account_id => AccountID, - own_amount => OwnAmount, - min_available_amount => MinAvailableAmount, - max_available_amount => MaxAvailableAmount, - currency => Currency - }. - --spec noncurrency() -> currency(). -noncurrency() -> - ?NONCURRENCY. - --spec create_account(currency(), lim_context()) -> {ok, account_id()}. -create_account(CurrencyCode, LimitContext) -> - create_account(CurrencyCode, undefined, LimitContext). - -create_account(CurrencyCode, Description, LimitContext) -> - call_accounter( - 'CreateAccount', - {construct_prototype(CurrencyCode, Description)}, - LimitContext - ). - -construct_prototype(CurrencyCode, Description) -> - #accounter_AccountPrototype{ - currency_sym_code = CurrencyCode, - description = Description - }. - -%% - -wrap_plan_id(PlanID) -> - %% Accounter requires max 64 byte plan id - case byte_size(PlanID) < 64 of - true -> - %% For backward compatibility - PlanID; - false -> - base64:encode(crypto:hash(sha384, PlanID)) - end. - -call_accounter(Function, Args, LimitContext) -> - WoodyContext = lim_context:woody_context(LimitContext), - lim_client_woody:call(accounter, Function, Args, WoodyContext). - -convert_exception(#base_InvalidRequest{errors = Errors}) -> - Errors; -convert_exception(#accounter_InvalidPostingParams{wrong_postings = Errors}) -> - maps:fold(fun(_, Error, Acc) -> [Error | Acc] end, [], Errors). diff --git a/apps/limiter/src/lim_config_machine.erl b/apps/limiter/src/lim_config_machine.erl index 64c13440..95e13dd1 100644 --- a/apps/limiter/src/lim_config_machine.erl +++ b/apps/limiter/src/lim_config_machine.erl @@ -60,12 +60,14 @@ scope => limit_scope(), description => description(), op_behaviour => op_behaviour(), - currency_conversion => currency_conversion() + currency_conversion => currency_conversion(), + finalization_behaviour => finalization_behaviour() }. -type op_behaviour() :: #{operation_type() := addition | subtraction}. -type operation_type() :: invoice_payment_refund. -type currency_conversion() :: boolean(). +-type finalization_behaviour() :: normal | {invertable, session_presence}. -type lim_id() :: limproto_limiter_thrift:'LimitID'(). -type lim_version() :: dmsl_domain_thrift:'DataRevision'(). @@ -74,6 +76,7 @@ -type timestamp() :: dmsl_base_thrift:'Timestamp'(). -type operation_id() :: limproto_limiter_thrift:'OperationID'(). -type lim_changes() :: [lim_change()]. +-type changes_group() :: {context_type(), finalization_behaviour()}. -export_type([config/0]). -export_type([limit_type/0]). @@ -134,6 +137,12 @@ scope(_) -> context_type(#{context_type := Value}) -> Value. +-spec finalization_behaviour(config()) -> finalization_behaviour(). +finalization_behaviour(#{finalization_behaviour := Value}) -> + Value; +finalization_behaviour(_) -> + normal. + -spec currency_conversion(config()) -> currency_conversion(). currency_conversion(#{currency_conversion := Value}) -> Value; @@ -155,42 +164,116 @@ get_values(LimitChanges, LimitContext) -> {ok, [lim_liminator:limit_response()]} | {error, config_error() | {processor(), get_limit_error()}}. get_batch(OperationID, LimitChanges, LimitContext) -> do(fun() -> - unwrap( - OperationID, - lim_liminator:get(OperationID, unwrap(collect_changes(hold, LimitChanges, LimitContext)), LimitContext) - ) + GroupedChanges = unwrap(collect_grouped_changes(hold, LimitChanges, LimitContext)), + F = fun(Group, Changes) -> + OperationIDForGroup = operation_id_for_group(OperationID, Group), + unwrap(OperationID, lim_liminator:get(OperationIDForGroup, Changes, LimitContext)) + end, + lists:flatten(maps:values(maps:map(F, GroupedChanges))) end). -spec hold_batch(operation_id(), lim_changes(), lim_context()) -> - {ok, [lim_liminator:limit_response()]} | {error, config_error() | {processor(), hold_error()}}. + {ok, [lim_liminator:limit_response()]} + | {error, config_error() | {processor(), hold_error()} | {operation_id(), lim_liminator:invalid_request_error()}}. hold_batch(OperationID, LimitChanges, LimitContext) -> do(fun() -> - unwrap( - OperationID, - lim_liminator:hold(OperationID, unwrap(collect_changes(hold, LimitChanges, LimitContext)), LimitContext) - ) + GroupedChanges = unwrap(collect_grouped_changes(hold, LimitChanges, LimitContext)), + F = fun(Group, Changes) -> + OperationIDForGroup = operation_id_for_group(OperationID, Group), + unwrap(OperationID, lim_liminator:hold(OperationIDForGroup, Changes, LimitContext)) + end, + lists:flatten(maps:values(maps:map(F, GroupedChanges))) end). -spec commit_batch(operation_id(), lim_changes(), lim_context()) -> - ok | {error, config_error() | {processor(), commit_error()}}. + ok + | {error, config_error() | {processor(), commit_error()} | {operation_id(), lim_liminator:invalid_request_error()}}. commit_batch(OperationID, LimitChanges, LimitContext) -> do(fun() -> - unwrap( - OperationID, - lim_liminator:commit(OperationID, unwrap(collect_changes(commit, LimitChanges, LimitContext)), LimitContext) - ) + GroupedChanges = unwrap(collect_grouped_changes(commit, LimitChanges, LimitContext)), + F = fun(Group, Changes) -> + OperationIDForGroup = operation_id_for_group(OperationID, Group), + Behaviour = resolve_group_finalization_behaviour(Group, LimitContext), + unwrap(OperationID, finalize(OperationIDForGroup, Changes, LimitContext, commit, Behaviour)) + end, + maps:foreach(F, GroupedChanges) end). -spec rollback_batch(operation_id(), lim_changes(), lim_context()) -> - ok | {error, config_error() | {processor(), rollback_error()}}. + ok + | {error, + config_error() | {processor(), rollback_error()} | {operation_id(), lim_liminator:invalid_request_error()}}. rollback_batch(OperationID, LimitChanges, LimitContext) -> do(fun() -> - unwrap( - OperationID, - lim_liminator:rollback(OperationID, unwrap(collect_changes(hold, LimitChanges, LimitContext)), LimitContext) - ) + GroupedChanges = unwrap(collect_grouped_changes(hold, LimitChanges, LimitContext)), + F = fun(Group, Changes) -> + OperationIDForGroup = operation_id_for_group(OperationID, Group), + Behaviour = resolve_group_finalization_behaviour(Group, LimitContext), + unwrap(OperationID, finalize(OperationIDForGroup, Changes, LimitContext, rollback, Behaviour)) + end, + maps:foreach(F, GroupedChanges) end). +finalize(OperationIDForGroup, Changes, LimitContext, commit, normal) -> + lim_liminator:commit(OperationIDForGroup, Changes, LimitContext); +finalize(OperationIDForGroup, Changes, LimitContext, commit, inverted) -> + lim_liminator:rollback(OperationIDForGroup, Changes, LimitContext); +finalize(OperationIDForGroup, Changes, LimitContext, rollback, normal) -> + lim_liminator:rollback(OperationIDForGroup, Changes, LimitContext); +finalize(OperationIDForGroup, Changes, LimitContext, rollback, inverted) -> + lim_liminator:commit(OperationIDForGroup, Changes, LimitContext). + +-spec resolve_group_finalization_behaviour(changes_group(), lim_context()) -> normal | inverted. +resolve_group_finalization_behaviour({_, normal}, _) -> + normal; +resolve_group_finalization_behaviour({ContextType, {invertable, session_presence}}, LimitContext) -> + case lim_context:get_value(ContextType, session, LimitContext) of + {ok, undefined} -> + normal; + {ok, _Some} -> + inverted; + {error, {unsupported, _}} -> + %% If context doesn't support session value then we treat it as + %% normal finalization. + normal; + {error, notfound} -> + normal + end. + +operation_id_for_group(OperationID, {_, normal}) -> + OperationID; +operation_id_for_group(OperationID, {_, {invertable, session_presence}}) -> + <>. + +-spec collect_grouped_changes(hold | commit, lim_changes(), lim_context()) -> + {ok, #{changes_group() => lim_changes()}} | {error, config_error() | {processor(), get_limit_error()}}. +collect_grouped_changes(Stage, LimitChanges, LimitContext) -> + collect_grouped_changes(Stage, LimitChanges, LimitContext, #{}). + +collect_grouped_changes(_, [], _, Acc) -> + {ok, Acc}; +collect_grouped_changes(Stage, [LimitChange | Other], LimitContext, Acc0) -> + do(fun() -> + #limiter_LimitChange{id = ID, version = Version} = LimitChange, + %% NOTE We use only "woody_context" of given limit context to get + %% handler. + {Handler, Config} = unwrap(get_handler(ID, Version, LimitContext)), + Change = unwrap(Handler, Handler:make_change(Stage, LimitChange, Config, LimitContext)), + %% NOTE Because rules to resolve change's finalization behaviour can be + %% dependent on operation's context ("context" key of given limit + %% context map), each group is discriminated by bothe context type and + %% finalization behaviour values. + %% Thus, we must ensure changes are in appropriate group before we + %% evalute groups behaviour against operation's limit context. + ContextType = context_type(Config), + FinalizationBehaviour = finalization_behaviour(Config), + Group = {ContextType, FinalizationBehaviour}, + Acc1 = maps:update_with(Group, fun(Changes) -> [Change | Changes] end, [Change], Acc0), + unwrap(collect_grouped_changes(Stage, Other, LimitContext, Acc1)) + end). + +%% NOTE Used only for `get_values/2' function that doesn't care about groups of +%% changes, nor resulting list's order. collect_changes(_Stage, [], _LimitContext) -> {ok, []}; collect_changes(Stage, [LimitChange = #limiter_LimitChange{id = ID, version = Version} | Other], LimitContext) -> @@ -503,7 +586,8 @@ unmarshal_limit_config(#domain_LimitConfigObject{ scopes = Scopes, description = Description, op_behaviour = OpBehaviour, - currency_conversion = CurrencyConversion + currency_conversion = CurrencyConversion, + finalization_behaviour = FinalizationBehaviour } }) -> genlib_map:compact(#{ @@ -517,9 +601,17 @@ unmarshal_limit_config(#domain_LimitConfigObject{ scope => maybe_apply(Scopes, fun unmarshal_scope/1), description => Description, op_behaviour => maybe_apply(OpBehaviour, fun unmarshal_op_behaviour/1), - currency_conversion => CurrencyConversion =/= undefined + currency_conversion => CurrencyConversion =/= undefined, + finalization_behaviour => unmarshal_finalization_behaviour(FinalizationBehaviour) }). +unmarshal_finalization_behaviour(undefined) -> + normal; +unmarshal_finalization_behaviour({normal, #limiter_config_Normal{}}) -> + normal; +unmarshal_finalization_behaviour({invertable, {session_presence, #limiter_config_Inversed{}}}) -> + {invertable, session_presence}. + unmarshal_time_range_type({calendar, CalendarType}) -> {calendar, unmarshal_calendar_time_range_type(CalendarType)}; unmarshal_time_range_type({interval, #limiter_config_TimeRangeTypeInterval{amount = Amount}}) -> @@ -619,7 +711,8 @@ unmarshal_config_object_test() -> type => {turnover, number}, scope => ordsets:from_list([party, shop, {destination_field, [<<"path">>, <<"to">>, <<"field">>]}]), description => <<"description">>, - currency_conversion => true + currency_conversion => true, + finalization_behaviour => {invertable, session_presence} }, Object = #domain_LimitConfigObject{ ref = #domain_LimitConfigRef{id = <<"id">>}, @@ -639,7 +732,8 @@ unmarshal_config_object_test() -> }} ]), description = <<"description">>, - currency_conversion = #limiter_config_CurrencyConversion{} + currency_conversion = #limiter_config_CurrencyConversion{}, + finalization_behaviour = {invertable, {session_presence, #limiter_config_Inversed{}}} } }, ?assertEqual(Config, unmarshal_limit_config(Object)). diff --git a/apps/limiter/src/lim_payproc_context.erl b/apps/limiter/src/lim_payproc_context.erl index fa9c3553..9a056953 100644 --- a/apps/limiter/src/lim_payproc_context.erl +++ b/apps/limiter/src/lim_payproc_context.erl @@ -82,6 +82,8 @@ get_value(provider_id, Operation, Context) -> get_provider_id(Operation, Context); get_value(terminal_id, Operation, Context) -> get_terminal_id(Operation, Context); +get_value(session, Operation, Context) -> + get_session(Operation, Context); get_value(payer_contact_email, Operation, Context) -> get_payer_contact_email(Operation, Context); get_value(sender, Operation, Context) -> @@ -129,6 +131,12 @@ get_value(ValueName, _Operation, _Context) -> } }). +-define(INVOICE_PAYMENT_SESSION(V), #context_payproc_Context{ + invoice = #context_payproc_Invoice{ + session = V + } +}). + get_owner_id(?INVOICE(Invoice)) -> {ok, Invoice#domain_Invoice.party_ref#domain_PartyConfigRef.id}; get_owner_id(_) -> @@ -209,6 +217,11 @@ get_terminal_id(Operation, ?INVOICE_PAYMENT_ROUTE(Route)) when get_terminal_id(_, _CtxInvoice) -> {error, notfound}. +get_session(invoice_payment, ?INVOICE_PAYMENT_SESSION(Session)) -> + {ok, Session}; +get_session(_, _CtxInvoice) -> + {error, notfound}. + get_payer_contact_email(Operation, ?INVOICE_PAYMENT(Payment)) when Operation == invoice_payment; Operation == invoice_payment_adjustment; diff --git a/apps/limiter/src/lim_turnover_processor.erl b/apps/limiter/src/lim_turnover_processor.erl index fecd5261..dc2ebf7e 100644 --- a/apps/limiter/src/lim_turnover_processor.erl +++ b/apps/limiter/src/lim_turnover_processor.erl @@ -25,19 +25,16 @@ -type hold_error() :: lim_rates:conversion_error() - | lim_accounting:invalid_request_error() | lim_turnover_metric:invalid_operation_currency_error() | lim_context:operation_context_not_supported_error() | lim_context:unsupported_error({payment_tool, atom()}). -type commit_error() :: {forbidden_operation_amount, forbidden_operation_amount_error()} - | lim_rates:conversion_error() - | lim_accounting:invalid_request_error(). + | lim_rates:conversion_error(). -type rollback_error() :: - lim_rates:conversion_error() - | lim_accounting:invalid_request_error(). + lim_rates:conversion_error(). -export_type([make_change_error/0]). -export_type([get_limit_error/0]). diff --git a/apps/limiter/test/lim_ct_helper.hrl b/apps/limiter/test/lim_ct_helper.hrl index 9d5381d3..2d8166bf 100644 --- a/apps/limiter/test/lim_ct_helper.hrl +++ b/apps/limiter/test/lim_ct_helper.hrl @@ -55,6 +55,9 @@ {amount, #limiter_config_LimitTurnoverAmount{currency = Currency}} ). +-define(finalization_behaviour_normal, {normal, #limiter_config_Normal{}}). +-define(finalization_behaviour_invertable_by_session, {invertable, {session_presence, #limiter_config_Inversed{}}}). + -define(time_range_day(), {calendar, {day, #limiter_config_TimeRangeTypeCalendarDay{}}} ). @@ -183,7 +186,11 @@ ?payproc_ctx_payment(?string, ?string, Cost, CaptureCost) ). --define(payproc_ctx_payment(OwnerID, ShopID, Cost, CaptureCost), #limiter_LimitContext{ +-define(payproc_ctx_payment(OwnerID, ShopID, Cost, CaptureCost), + ?payproc_ctx_payment(OwnerID, ShopID, Cost, CaptureCost, undefined) +). + +-define(payproc_ctx_payment(OwnerID, ShopID, Cost, CaptureCost, Session), #limiter_LimitContext{ payment_processing = #context_payproc_Context{ op = ?op_payment, invoice = #context_payproc_Invoice{ @@ -191,7 +198,8 @@ payment = #context_payproc_InvoicePayment{ payment = ?invoice_payment(Cost, CaptureCost), route = ?route() - } + }, + session = Session } } }). @@ -228,6 +236,8 @@ } }). +-define(payproc_ctx_session, #context_payproc_InvoicePaymentSession{}). + %% Wthdproc -define(auth_data(Sender, Receiver), diff --git a/apps/limiter/test/lim_turnover_SUITE.erl b/apps/limiter/test/lim_turnover_SUITE.erl index 5562f19d..241e9f43 100644 --- a/apps/limiter/test/lim_turnover_SUITE.erl +++ b/apps/limiter/test/lim_turnover_SUITE.erl @@ -76,6 +76,11 @@ -export([batch_commit_negative_less_ok/1]). -export([batch_commit_negative_more_ok/1]). +-export([batch_with_invertable_rollback_ok/1]). +-export([batch_with_invertable_rollback_with_session_ok/1]). +-export([batch_with_invertable_commit_ok/1]). +-export([batch_with_invertable_commit_with_session_ok/1]). + -type group_name() :: atom(). -type test_case_name() :: atom(). @@ -87,7 +92,8 @@ all() -> {group, default}, {group, withdrawals}, {group, cashless}, - {group, idempotency} + {group, idempotency}, + {group, finalization_behaviour} ]. -spec groups() -> [{atom(), list(), [test_case_name()]}]. @@ -163,6 +169,12 @@ groups() -> full_commit_processes_idempotently, partial_commit_processes_idempotently, rollback_processes_idempotently + ]}, + {finalization_behaviour, [], [ + batch_with_invertable_rollback_ok, + batch_with_invertable_rollback_with_session_ok, + batch_with_invertable_commit_ok, + batch_with_invertable_commit_with_session_ok ]} ]. @@ -777,34 +789,10 @@ commit_with_destination_field_scope_ok(C) -> construct_request(C) -> ID = ?config(id, C), - {ID0, Version0} = configure_limit( - ?time_range_month(), - ?scopes([?scope_provider(), ?scope_payment_tool()]), - ?turnover_metric_amount(<<"RUB">>), - undefined, - genlib:format("~s/~B", [ID, 0]), - C - ), - {ID1, Version1} = configure_limit( - ?time_range_month(), - ?scopes([?scope_provider(), ?scope_payment_tool()]), - ?turnover_metric_amount(<<"RUB">>), - undefined, - genlib:format("~s/~B", [ID, 1]), - C - ), - {ID2, Version2} = configure_limit( - ?time_range_month(), - ?scopes([?scope_provider(), ?scope_payment_tool()]), - ?turnover_metric_amount(<<"RUB">>), - undefined, - genlib:format("~s/~B", [ID, 2]), - C - ), ?LIMIT_REQUEST(ID, [ - ?LIMIT_CHANGE(ID0, Version0), - ?LIMIT_CHANGE(ID1, Version1), - ?LIMIT_CHANGE(ID2, Version2) + construct_for_limit_change(ID, 0, ?turnover_metric_amount(<<"RUB">>), undefined, C), + construct_for_limit_change(ID, 1, ?turnover_metric_amount(<<"RUB">>), undefined, C), + construct_for_limit_change(ID, 2, ?turnover_metric_amount(<<"RUB">>), undefined, C) ]). -spec batch_hold_ok(config()) -> _. @@ -985,8 +973,85 @@ batch_commit_negative_more_ok(C) -> Request, Context, ?config(client, C) ). +%% Finalization behaviour group + +-spec batch_with_invertable_rollback_ok(config()) -> _. +batch_with_invertable_rollback_ok(C) -> + Context0 = ?payproc_ctx_payment(?string, ?string, ?cash(10), ?cash(10), undefined), + ?LIMIT_REQUEST(_RequestID, _Changes) = Request0 = construct_request_with_invertable(C), + ok = hold_and_assert_batch_with_invertable({1, 1, 10}, Request0, Context0, C), + Context1 = ?payproc_ctx_payment(?string, ?string, ?cash(10), ?cash(10), undefined), + ok = lim_client:rollback_batch(Request0, Context1, ?config(client, C)), + ok = assert_values_with_invertable({0, 0, 0}, Request0, Context1, C). + +-spec batch_with_invertable_rollback_with_session_ok(config()) -> _. +batch_with_invertable_rollback_with_session_ok(C) -> + Context0 = ?payproc_ctx_payment(?string, ?string, ?cash(10), ?cash(10), undefined), + ?LIMIT_REQUEST(_RequestID, _Changes) = Request0 = construct_request_with_invertable(C), + ok = hold_and_assert_batch_with_invertable({1, 1, 10}, Request0, Context0, C), + Context1 = ?payproc_ctx_payment(?string, ?string, ?cash(10), ?cash(10), ?payproc_ctx_session), + ok = lim_client:rollback_batch(Request0, Context1, ?config(client, C)), + ok = assert_values_with_invertable({1, 0, 0}, Request0, Context1, C). + +-spec batch_with_invertable_commit_ok(config()) -> _. +batch_with_invertable_commit_ok(C) -> + Context0 = ?payproc_ctx_payment(?string, ?string, ?cash(10), ?cash(10), undefined), + ?LIMIT_REQUEST(_RequestID, _Changes) = Request0 = construct_request_with_invertable(C), + ok = hold_and_assert_batch_with_invertable({1, 1, 10}, Request0, Context0, C), + Context1 = ?payproc_ctx_payment(?string, ?string, ?cash(10), ?cash(10), undefined), + ok = lim_client:commit_batch(Request0, Context1, ?config(client, C)), + ok = assert_values_with_invertable({1, 1, 10}, Request0, Context1, C). + +-spec batch_with_invertable_commit_with_session_ok(config()) -> _. +batch_with_invertable_commit_with_session_ok(C) -> + Context0 = ?payproc_ctx_payment(?string, ?string, ?cash(10), ?cash(10), undefined), + ?LIMIT_REQUEST(_RequestID, _Changes) = Request0 = construct_request_with_invertable(C), + ok = hold_and_assert_batch_with_invertable({1, 1, 10}, Request0, Context0, C), + Context1 = ?payproc_ctx_payment(?string, ?string, ?cash(10), ?cash(10), ?payproc_ctx_session), + ok = lim_client:commit_batch(Request0, Context1, ?config(client, C)), + ok = assert_values_with_invertable({0, 1, 10}, Request0, Context1, C). + +construct_request_with_invertable(C) -> + ID = ?config(id, C), + ?LIMIT_REQUEST(ID, [ + construct_for_limit_change(ID, 0, ?turnover_metric_number(), ?finalization_behaviour_invertable_by_session, C), + construct_for_limit_change(ID, 1, ?turnover_metric_number(), ?finalization_behaviour_normal, C), + construct_for_limit_change(ID, 2, ?turnover_metric_amount(<<"RUB">>), undefined, C) + ]). + +hold_and_assert_batch_with_invertable({Value0, Value1, Value2}, Request0, Context, C) -> + {ok, LimitStats} = lim_client:hold_batch(Request0, Context, ?config(client, C)), + %% NOTE Split operations for invertablity can break order of items in + %% response and mismatch it for limit changes provided in request. + [LimitState0, LimitState1, LimitState2] = lists:sort(LimitStats), + ?assertEqual(Value0, LimitState0#limiter_Limit.amount), + ?assertEqual(Value1, LimitState1#limiter_Limit.amount), + ?assertEqual(Value2, LimitState2#limiter_Limit.amount), + {ok, [LimitState0 | [LimitState1 | [LimitState2]]]} = lim_client:get_values(Request0, Context, ?config(client, C)), + ok. + +assert_values_with_invertable({Value0, Value1, Value2}, Request0, Context, C) -> + {ok, LimitStats} = lim_client:get_values(Request0, Context, ?config(client, C)), + [LimitState0, LimitState1, LimitState2] = lists:sort(LimitStats), + ?assertEqual(Value0, LimitState0#limiter_Limit.amount), + ?assertEqual(Value1, LimitState1#limiter_Limit.amount), + ?assertEqual(Value2, LimitState2#limiter_Limit.amount), + ok. + %% +construct_for_limit_change(BaseID, Num, Metric, FinalizationBehaviour, C) -> + {ID, Version} = configure_limit( + ?time_range_month(), + ?scopes([?scope_provider(), ?scope_payment_tool()]), + Metric, + undefined, + genlib:format("~s/~B", [BaseID, Num]), + FinalizationBehaviour, + C + ), + ?LIMIT_CHANGE(ID, Version). + hold_and_assert_batch(Value, Request0, Context, C) -> {ok, [LimitState0 | [LimitState1 | [LimitState2]]]} = lim_client:hold_batch(Request0, Context, ?config(client, C)), ?assertEqual(Value, LimitState0#limiter_Limit.amount), @@ -1029,7 +1094,10 @@ configure_limit(TimeRange, Scopes, Metric, C) -> configure_limit(TimeRange, Scopes, Metric, CurrencyConversion, C) -> configure_limit(TimeRange, Scopes, Metric, CurrencyConversion, ?config(id, C), C). -configure_limit(TimeRange, Scopes, Metric, CurrencyConversion, ID, C) when is_list(Scopes) -> +configure_limit(TimeRange, Scopes, Metric, CurrencyConversion, ID, C) -> + configure_limit(TimeRange, Scopes, Metric, CurrencyConversion, ID, {normal, #limiter_config_Normal{}}, C). + +configure_limit(TimeRange, Scopes, Metric, CurrencyConversion, ID, FinalizationBehaviour, C) when is_list(Scopes) -> ContextType = case get_group_name(C) of withdrawals -> ?ctx_type_wthdproc(); @@ -1045,7 +1113,8 @@ configure_limit(TimeRange, Scopes, Metric, CurrencyConversion, ID, C) when is_li scopes = Scopes, description = <<"Description">>, op_behaviour = ?op_behaviour(?op_subtraction()), - currency_conversion = CurrencyConversion + currency_conversion = CurrencyConversion, + finalization_behaviour = FinalizationBehaviour }). create_limit_config(ID, #limiter_config_LimitConfig{} = LimitConfig) -> diff --git a/compose.yaml b/compose.yaml index ab3863bb..6f3abb6f 100644 --- a/compose.yaml +++ b/compose.yaml @@ -20,7 +20,7 @@ services: condition: service_healthy dmt: - image: ghcr.io/valitydev/dominant-v2:sha-90f5fa2 + image: ghcr.io/valitydev/dominant-v2:sha-815385c command: /opt/dmt/bin/dmt foreground healthcheck: test: "/opt/dmt/bin/dmt ping" diff --git a/rebar.config b/rebar.config index 9e0954a3..7aeabe03 100644 --- a/rebar.config +++ b/rebar.config @@ -28,8 +28,8 @@ {deps, [ {prometheus, "4.11.0"}, {prometheus_cowboy, "0.1.9"}, - {damsel, {git, "https://github.com/valitydev/damsel.git", {tag, "v2.2.17"}}}, - {limiter_proto, {git, "https://github.com/valitydev/limiter-proto.git", {tag, "v2.1.0"}}}, + {damsel, {git, "https://github.com/valitydev/damsel.git", {tag, "v2.2.27"}}}, + {limiter_proto, {git, "https://github.com/valitydev/limiter-proto.git", {tag, "v2.1.1"}}}, {liminator_proto, {git, "https://github.com/valitydev/liminator-proto.git", {branch, "master"}}}, {xrates_proto, {git, "https://github.com/valitydev/xrates-proto.git", {branch, "master"}}}, {erl_health, {git, "https://github.com/valitydev/erlang-health.git", {branch, "master"}}}, diff --git a/rebar.lock b/rebar.lock index 3f8b6b45..207a40a2 100644 --- a/rebar.lock +++ b/rebar.lock @@ -13,7 +13,7 @@ {<<"ctx">>,{pkg,<<"ctx">>,<<"0.6.0">>},2}, {<<"damsel">>, {git,"https://github.com/valitydev/damsel.git", - {ref,"f831d3aa5fdfd0338b41af44d1eeffe810ca9708"}}, + {ref,"074ba8c024af7902ead5f24dc3d288c1922651c1"}}, 0}, {<<"dmt_client">>, {git,"https://github.com/valitydev/dmt_client.git", @@ -39,7 +39,7 @@ 0}, {<<"limiter_proto">>, {git,"https://github.com/valitydev/limiter-proto.git", - {ref,"1af3724af24dd8b5ad9ce2ae80cd42318b471397"}}, + {ref,"ced168a6ea11f80d62485b793b3605de68223e38"}}, 0}, {<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},2}, {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.4.0">>},2}, From 77b12a0a5eeb440e1784713e125b6aa6cc612bd0 Mon Sep 17 00:00:00 2001 From: Baikov Dmitrii <44803026+D-Baykov@users.noreply.github.com> Date: Mon, 11 May 2026 11:24:30 +0300 Subject: [PATCH 54/54] bump liminator (#48) --- compose.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose.yaml b/compose.yaml index 6f3abb6f..dd2e25cd 100644 --- a/compose.yaml +++ b/compose.yaml @@ -34,7 +34,7 @@ services: - ./test/dmt/sys.config:/opt/dmt/releases/0.1/sys.config liminator: - image: ghcr.io/valitydev/liminator:sha-48e1a4a + image: ghcr.io/valitydev/liminator:sha-4f2cfbb restart: unless-stopped entrypoint: - java