diff --git a/Makefile b/Makefile index a2fd0b8..d450cb1 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,7 @@ BASE_IMAGE_TAG := 688cee70c0eb6540709fe35b816c81a90dc542ea BUILD_IMAGE_TAG := 917afcdd0c0a07bf4155d597bbba72e962e1a34a CALL_ANYWHERE := \ submodules \ - all compile xref lint dialyze cover release clean distclean + all compile xref lint format check_format dialyze cover release clean distclean CALL_W_CONTAINER := $(CALL_ANYWHERE) test @@ -45,6 +45,12 @@ xref: lint: $(REBAR) lint +check_format: + $(REBAR) fmt -c + +format: + $(REBAR) fmt -w + dialyze: $(REBAR) dialyzer diff --git a/build_utils b/build_utils index 2c4c228..e131872 160000 --- a/build_utils +++ b/build_utils @@ -1 +1 @@ -Subproject commit 2c4c2289ad7919ef953603f70d5cc967419ec2dd +Subproject commit e1318727d4d0c3e48f5122bf3197158b6695f50e diff --git a/rebar.config b/rebar.config index c6878cf..888938f 100644 --- a/rebar.config +++ b/rebar.config @@ -1,6 +1,5 @@ %% Common project erlang options. {erl_opts, [ - % mandatory debug_info, warnings_as_errors, @@ -27,74 +26,38 @@ %% Common project dependencies. {deps, [ - {cowboy, "2.8.0"}, {jsx, "3.0.0"}, {jesse, "1.5.5"}, - {gun, - {git, "https://github.com/ninenines/gun.git", - {branch, "master"} - }}, - {genlib, - {git, "https://github.com/rbkmoney/genlib.git", - {branch, "master"} - } - }, - {thrift, - {git, "https://github.com/rbkmoney/thrift_erlang.git", - {branch, "master"} - } - }, - {woody, - {git, "https://github.com/rbkmoney/woody_erlang.git", - {branch, "master"} - } - }, + {gun, {git, "https://github.com/ninenines/gun.git", {branch, "master"}}}, + {genlib, {git, "https://github.com/rbkmoney/genlib.git", {branch, "master"}}}, + {thrift, {git, "https://github.com/rbkmoney/thrift_erlang.git", {branch, "master"}}}, + {woody, {git, "https://github.com/rbkmoney/woody_erlang.git", {branch, "master"}}}, {woody_user_identity, - {git, "https://github.com/rbkmoney/woody_erlang_user_identity.git", - {branch, "master"} - } - }, - {bouncer_proto, - {git, "git@github.com:rbkmoney/bouncer-proto.git", - {branch, "master"} - } - }, + {git, "https://github.com/rbkmoney/woody_erlang_user_identity.git", {branch, "master"}}}, + {bouncer_proto, {git, "git@github.com:rbkmoney/bouncer-proto.git", {branch, "master"}}}, {org_management_proto, - {git, "git@github.com:rbkmoney/org-management-proto.git", - {branch, "master"} - } - }, - {scoper, - {git, "https://github.com/rbkmoney/scoper.git", - {branch, "master"} - } - }, - {erl_health, - {git, "https://github.com/rbkmoney/erlang-health.git", - {branch, "master"} - } - }, + {git, "git@github.com:rbkmoney/org-management-proto.git", {branch, "master"}}}, + {scoper, {git, "https://github.com/rbkmoney/scoper.git", {branch, "master"}}}, + {erl_health, {git, "https://github.com/rbkmoney/erlang-health.git", {branch, "master"}}}, % Production-only deps. % Defined here for the sake of rebar-locking. {recon, "2.5.1"}, {logger_logstash_formatter, - {git, "https://github.com/rbkmoney/logger_logstash_formatter.git", - {branch, "master"} - } - }, - {how_are_you, - {git, "https://github.com/rbkmoney/how_are_you.git", - {branch, "master"} - } - } - + {git, "https://github.com/rbkmoney/logger_logstash_formatter.git", {branch, "master"}}}, + {how_are_you, {git, "https://github.com/rbkmoney/how_are_you.git", {branch, "master"}}} ]}. %% Helpful plugins. {plugins, [ - rebar3_lint + rebar3_lint, + {erlfmt, "0.10.0"} +]}. + +{erlfmt, [ + {print_width, 100}, + {files, ["{src,include,test}/*.{hrl,erl}", "rebar.config"]} ]}. %% Linter config. @@ -104,10 +67,12 @@ filter => "*.erl", ruleset => erl_files, rules => [ - {elvis_style, invalid_dynamic_call, #{ignore => [ - % Uses thrift reflection through `struct_info/1`. - bouncer_thrift - ]}} + {elvis_style, invalid_dynamic_call, #{ + ignore => [ + % Uses thrift reflection through `struct_info/1`. + bouncer_thrift + ] + }} ] }, #{ @@ -161,6 +126,7 @@ deprecated_functions_calls, deprecated_functions ]}. + % at will % {xref_warnings, true}. @@ -169,11 +135,15 @@ %% Relx configuration {relx, [ - {release, {bouncer , "0.1.0"}, [ - {recon , load}, % tools for introspection - {runtime_tools, load}, % debugger - {tools , load}, % profiler - {logger_logstash_formatter, load}, % logger formatter + {release, {bouncer, "0.1.0"}, [ + % tools for introspection + {recon, load}, + % debugger + {runtime_tools, load}, + % profiler + {tools, load}, + % logger formatter + {logger_logstash_formatter, load}, how_are_you, bouncer ]}, @@ -197,7 +167,6 @@ ]}. {profiles, [ - {prod, [ {relx, [ {dev_mode, false}, @@ -210,5 +179,4 @@ {cover_enabled, true}, {deps, []} ]} - ]}. diff --git a/src/bouncer.erl b/src/bouncer.erl index aa35f15..9f7a81a 100644 --- a/src/bouncer.erl +++ b/src/bouncer.erl @@ -3,6 +3,7 @@ %% Application callbacks -behaviour(application). + -export([start/2]). -export([prep_stop/1]). -export([stop/1]). @@ -10,18 +11,16 @@ %% Supervisor callbacks -behaviour(supervisor). + -export([init/1]). %% --spec start(normal, any()) -> - {ok, pid()} | {error, any()}. - +-spec start(normal, any()) -> {ok, pid()} | {error, any()}. start(_StartType, _StartArgs) -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). --spec prep_stop(State) -> - State. +-spec prep_stop(State) -> State. prep_stop(State) -> % NOTE % We have to do it in this magic `prep_stop/1` here because for some inexplicable reason the @@ -29,17 +28,13 @@ prep_stop(State) -> ok = bouncer_audit_log:stop(genlib_app:env(?MODULE, audit, #{})), State. --spec stop(any()) -> - ok. - +-spec stop(any()) -> ok. stop(_State) -> ok. %% --spec init([]) -> - {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. - +-spec init([]) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}. init([]) -> AuditPulse = bouncer_audit_log:init(genlib_app:env(?MODULE, audit, #{})), ServiceOpts = genlib_app:env(?MODULE, services, #{}), @@ -48,56 +43,46 @@ init([]) -> 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 => + 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, AuditPulse) ++ get_stub_handler_specs(ServiceOpts), additional_routes => [erl_health_handle:get_route(Healthcheck)] } ), - {ok, { - #{strategy => one_for_all, intensity => 6, period => 30}, - [ChildSpec] - }}. - --spec get_ip_address() -> - inet:ip_address(). + {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(). - +-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(). - +-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(). - +-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(). - +-spec get_shutdown_timeout() -> timeout(). get_shutdown_timeout() -> genlib_app:env(?MODULE, shutdown_timeout, 0). -spec get_handler_specs(map(), bouncer_arbiter_pulse:handlers()) -> [woody:http_handler(woody:th_handler())]. - get_handler_specs(ServiceOpts, AuditPulse) -> ArbiterService = maps:get(arbiter, ServiceOpts, #{}), ArbiterPulse = maps:get(pulse, ArbiterService, []), @@ -122,12 +107,10 @@ get_stub_handler_specs(ServiceOpts) -> %% --spec enable_health_logging(erl_health:check()) -> - erl_health:check(). - +-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, + fun(_, Runner) -> #{runner => Runner, event_handler => EvHandler} end, Check ). diff --git a/src/bouncer_arbiter.erl b/src/bouncer_arbiter.erl index 81d7213..21216ac 100644 --- a/src/bouncer_arbiter.erl +++ b/src/bouncer_arbiter.erl @@ -14,9 +14,9 @@ %% ``` -type ruleset_id() :: iodata(). --type judgement() :: {resolution(), [assertion()]}. +-type judgement() :: {resolution(), [assertion()]}. -type resolution() :: allowed | forbidden | {restricted, map()}. --type assertion() :: {_Code :: binary(), _Details :: #{binary() => _}}. +-type assertion() :: {_Code :: binary(), _Details :: #{binary() => _}}. -export_type([judgement/0]). -export_type([resolution/0]). @@ -26,12 +26,11 @@ %% -spec judge(ruleset_id(), bouncer_context:ctx()) -> - {ok, judgement()} | - {error, - ruleset_notfound | - {ruleset_invalid, _Details} | - {unavailable | unknown, _Reason} - }. + {ok, judgement()} + | {error, + ruleset_notfound + | {ruleset_invalid, _Details} + | {unavailable | unknown, _Reason}}. judge(RulesetID, Context) -> case mk_opa_client() of {ok, Client} -> @@ -48,8 +47,7 @@ judge(RulesetID, Context) -> Error end. --spec infer_judgement(document()) -> - {ok, judgement()} | {error, {ruleset_invalid, _Details}}. +-spec infer_judgement(document()) -> {ok, judgement()} | {error, {ruleset_invalid, _Details}}. infer_judgement(Document) -> case jesse:validate_with_schema(get_judgement_schema(), Document) of {ok, _} -> @@ -74,8 +72,7 @@ extract_assertions(Assertions) -> extract_assertion(Assertion = #{<<"code">> := Code}) -> {Code, maps:without([<<"code">>], Assertion)}. --spec get_judgement_schema() -> - jesse:schema(). +-spec get_judgement_schema() -> jesse:schema(). get_judgement_schema() -> % TODO % Worth declaring in a separate file? Should be helpful w/ CI-like activities. @@ -121,37 +118,36 @@ get_judgement_schema() -> -type endpoint() :: {inet:hostname() | inet:ip_address(), inet:port_number()}. -type client_opts() :: #{ - endpoint := endpoint(), - transport => tcp | tls, - tcp_opts => [gen_tcp:connect_option()], - tls_opts => [ssl:tls_client_option()], - connect_timeout => timeout(), + endpoint := endpoint(), + transport => tcp | tls, + tcp_opts => [gen_tcp:connect_option()], + tls_opts => [ssl:tls_client_option()], + connect_timeout => timeout(), domain_lookup_timeout => timeout(), - request_timeout => timeout(), - http_opts => gun:http_opts(), - http2_opts => gun:http2_opts(), + request_timeout => timeout(), + http_opts => gun:http_opts(), + http2_opts => gun:http2_opts(), % TODO % Pulse over gun event handler mechanic. - event_handler => {module(), _State} + event_handler => {module(), _State} }. -define(DEFAULT_CLIENT_OPTS, #{ domain_lookup_timeout => 1000, - connect_timeout => 1000, - request_timeout => 1000 + connect_timeout => 1000, + request_timeout => 1000 }). -type client() :: {pid(), client_opts()}. -type document() :: - null | - binary() | - number() | - boolean() | - #{atom() | binary() => document()} | - [document()]. + null + | binary() + | number() + | boolean() + | #{atom() | binary() => document()} + | [document()]. --spec mk_opa_client() -> - {ok, client()} | {error, {unavailable, _Reason}}. +-spec mk_opa_client() -> {ok, client()} | {error, {unavailable, _Reason}}. mk_opa_client() -> Opts = get_opa_client_opts(), {Host, Port} = maps:get(endpoint, Opts), @@ -186,11 +182,10 @@ mk_opa_client() -> end. -spec request_opa_document(_ID :: iodata(), _Input :: document(), client()) -> - {ok, document()} | - {error, - notfound | - {unknown, _Reason} - }. + {ok, document()} + | {error, + notfound + | {unknown, _Reason}}. request_opa_document(ID, Input, {Client, Opts}) -> Path = join_path(<<"/v1/data">>, ID), % TODO @@ -200,11 +195,12 @@ request_opa_document(ID, Input, {Client, Opts}) -> CType = <<"application/json; charset=utf-8">>, Headers = #{ <<"content-type">> => CType, - <<"accept">> => CType + <<"accept">> => CType }, Timeout = maps:get(request_timeout, Opts), StreamRef = gun:post(Client, Path, Headers, Body), - Deadline = erlang:monotonic_time(millisecond) + Timeout, % TODO think about implications + % TODO think about implications + Deadline = erlang:monotonic_time(millisecond) + Timeout, case gun:await(Client, StreamRef, Timeout) of {response, nofin, 200, _Headers} -> TimeoutLeft = Deadline - erlang:monotonic_time(millisecond), @@ -222,8 +218,7 @@ request_opa_document(ID, Input, {Client, Opts}) -> {error, {unknown, Reason}} end. --spec decode_document(binary()) -> - {ok, document()} | {error, notfound}. +-spec decode_document(binary()) -> {ok, document()} | {error, notfound}. decode_document(Response) -> case jsx:decode(Response) of #{<<"result">> := Result} -> @@ -232,8 +227,7 @@ decode_document(Response) -> {error, notfound} end. --spec get_opa_client_opts() -> - client_opts(). +-spec get_opa_client_opts() -> client_opts(). get_opa_client_opts() -> maps:merge( ?DEFAULT_CLIENT_OPTS, @@ -249,7 +243,7 @@ normalize_path(P = <<$/, P1/binary>>) -> S1 = byte_size(P1), case S1 > 0 andalso binary:last(P1) of $/ -> binary:part(P, 0, S1); - _ -> P + _ -> P end; normalize_path(P) when is_binary(P) -> normalize_path(<<$/, P/binary>>); diff --git a/src/bouncer_arbiter_handler.erl b/src/bouncer_arbiter_handler.erl index 098c72d..19b2357 100644 --- a/src/bouncer_arbiter_handler.erl +++ b/src/bouncer_arbiter_handler.erl @@ -5,6 +5,7 @@ %% Woody handler -behaviour(woody_server_thrift_handler). + -export([handle_function/4]). %% @@ -14,7 +15,7 @@ }. -record(st, { - pulse :: bouncer_arbiter_pulse:handlers(), + pulse :: bouncer_arbiter_pulse:handlers(), pulse_metadata :: bouncer_arbiter_pulse:metadata() }). @@ -27,10 +28,12 @@ handle_function(Fn, Args, WoodyCtx, Opts) -> do_handle_function('Judge', {RulesetID, ContextIn}, WoodyCtx, Opts) -> St = #st{ - pulse = maps:get(pulse, Opts, []), + pulse = maps:get(pulse, Opts, []), pulse_metadata = #{woody_ctx => WoodyCtx} }, - try handle_judge(RulesetID, ContextIn, St) catch + try + handle_judge(RulesetID, ContextIn, St) + catch throw:{woody, Class, Details} -> woody_error:raise(Class, Details); C:R:S -> @@ -58,8 +61,7 @@ handle_judge(RulesetID, ContextIn, St0) -> handle_network_error(Reason, St2) end. --spec handle_network_error(_Reason, st()) -> - no_return(). +-spec handle_network_error(_Reason, st()) -> no_return(). handle_network_error({unavailable, Reason} = Error, St) -> ok = handle_judgement_beat({failed, Error}, St), throw({woody, system, {external, resource_unavailable, genlib:format(Reason)}}); @@ -69,16 +71,15 @@ handle_network_error({unknown, Reason} = Error, St) -> %% --type fragment_id() :: binary(). +-type fragment_id() :: binary(). -type fragment_metadata() :: #{atom() => _}. --type thrift_judgement() :: bouncer_decisions_thrift:'Judgement'(). --type thrift_context() :: bouncer_decisions_thrift:'Context'(). --type thrift_fragment() :: bouncer_context_thrift:'ContextFragment'(). +-type thrift_judgement() :: bouncer_decisions_thrift:'Judgement'(). +-type thrift_context() :: bouncer_decisions_thrift:'Context'(). +-type thrift_fragment() :: bouncer_context_thrift:'ContextFragment'(). -type thrift_fragment_type() :: bouncer_context_thrift:'ContextFragmentType'(). --spec encode_judgement(bouncer_arbiter:judgement()) -> - thrift_judgement(). +-spec encode_judgement(bouncer_arbiter:judgement()) -> thrift_judgement(). encode_judgement({Resolution, _Assertions}) -> #bdcs_Judgement{ resolution = encode_resolution(Resolution) @@ -97,15 +98,14 @@ encode_restrictions(Restrictions) -> {struct, _, StructDef} = bouncer_restriction_thrift:struct_info('Restrictions'), bouncer_thrift:json_to_thrift_struct(StructDef, Restrictions, #brstn_Restrictions{}). --spec decode_context(thrift_context(), st()) -> - {bouncer_context:ctx(), st()}. +-spec decode_context(thrift_context(), st()) -> {bouncer_context:ctx(), st()}. decode_context(#bdcs_Context{fragments = FragmentsIn}, St0) -> % 1. Decode each fragment. {Fragments, St1} = decode_fragments(FragmentsIn, St0), % 2. Merge each decoded context into an empty context. Accumulate conflicts associated with % corresponding fragment id. {Ctx, Conflicts} = maps:fold( - fun (ID, Ctx, {CtxAcc, DiscardAcc}) -> + fun(ID, Ctx, {CtxAcc, DiscardAcc}) -> case bouncer_context:merge(CtxAcc, Ctx) of {CtxAcc1, undefined} -> {CtxAcc1, DiscardAcc}; @@ -135,14 +135,14 @@ decode_context(#bdcs_Context{fragments = FragmentsIn}, St0) -> {#{fragment_id() => bouncer_context:ctx()}, st()}. decode_fragments(Fragments, St0) -> {Ctxs, Errors, PulseMeta} = maps:fold( - fun (ID, Fragment, {CtxAcc, ErrorAcc, PulseMetaAcc}) -> + fun(ID, Fragment, {CtxAcc, ErrorAcc, PulseMetaAcc}) -> Type = Fragment#bctx_ContextFragment.type, Content = genlib:define(Fragment#bctx_ContextFragment.content, <<>>), case decode_fragment(Type, Content) of {ok, Ctx, Meta} -> PulseMeta = #{ - type => Type, - context => Ctx, + type => Type, + context => Ctx, metadata => Meta }, { @@ -177,12 +177,10 @@ decode_fragment(v1_thrift_binary, Content) -> %% --spec append_pulse_metadata(bouncer_arbiter_pulse:metadata(), st()) -> - st(). +-spec append_pulse_metadata(bouncer_arbiter_pulse:metadata(), st()) -> st(). append_pulse_metadata(Metadata, St = #st{pulse_metadata = MetadataWas}) -> St#st{pulse_metadata = maps:merge(MetadataWas, Metadata)}. --spec handle_judgement_beat(_Beat, st()) -> - ok. +-spec handle_judgement_beat(_Beat, st()) -> ok. handle_judgement_beat(Beat, #st{pulse = Pulse, pulse_metadata = Metadata}) -> bouncer_arbiter_pulse:handle_beat({judgement, Beat}, Metadata, Pulse). diff --git a/src/bouncer_arbiter_pulse.erl b/src/bouncer_arbiter_pulse.erl index 1ec0f62..73927a1 100644 --- a/src/bouncer_arbiter_pulse.erl +++ b/src/bouncer_arbiter_pulse.erl @@ -2,22 +2,21 @@ -type beat() :: {judgement, - started | - {completed, bouncer_arbiter:judgement()} | - {failed, _Reason} - }. + started + | {completed, bouncer_arbiter:judgement()} + | {failed, _Reason}}. -type metadata() :: #{ - ruleset => id(), - context => bouncer_context:ctx(), + ruleset => id(), + context => bouncer_context:ctx(), fragments => #{id() => fragment()}, woody_ctx => woody_context:ctx() }. -type id() :: binary(). -type fragment() :: #{ - type => atom(), - context => bouncer_context:ctx(), + type => atom(), + context => bouncer_context:ctx(), metadata => map() }. @@ -30,18 +29,17 @@ -type handler(St) :: {module(), St}. -type handlers() :: [handler()]. -type handlers(St) :: [handler(St)]. + -export_type([handler/0]). -export_type([handler/1]). -export_type([handlers/0]). -export_type([handlers/1]). --callback handle_beat(beat(), metadata(), _Opts) -> - ok. +-callback handle_beat(beat(), metadata(), _Opts) -> ok. -export([handle_beat/3]). --spec handle_beat(beat(), metadata(), handlers()) -> - ok. +-spec handle_beat(beat(), metadata(), handlers()) -> ok. handle_beat(Beat, Metadata, [{Mod, Opts} | Rest]) -> % NOTE % Generally, we don't want some fault to propagate from event handler to the business logic diff --git a/src/bouncer_audit_log.erl b/src/bouncer_audit_log.erl index 8cbeb11..3253843 100644 --- a/src/bouncer_audit_log.erl +++ b/src/bouncer_audit_log.erl @@ -4,6 +4,7 @@ -export([stop/1]). -behaviour(bouncer_arbiter_pulse). + -export([handle_beat/3]). -define(DEFAULT_LOG_LEVEL, notice). @@ -54,8 +55,7 @@ -type st() :: {log, logger:level()}. --spec init(opts()) -> - bouncer_arbiter_pulse:handlers(st()). +-spec init(opts()) -> bouncer_arbiter_pulse:handlers(st()). init(Opts) -> _ = assert_strict_opts(?OPTS, Opts), init_log_handler(maps:get(log, Opts, #{})). @@ -66,7 +66,7 @@ init_log_handler(LogOpts = #{}) -> BackendConfig = mk_logger_backend_config(maps:get(backend, LogOpts, #{})), HandlerConfig0 = maps:with([formatter], LogOpts), HandlerConfig1 = HandlerConfig0#{ - config => BackendConfig, + config => BackendConfig, % NOTE % This two options together ensure that _only_ audit logs will flow through to the backend. filters => [{domain, {fun logger_filters:domain/2, {log, sub, ?LOG_DOMAIN}}}], @@ -93,11 +93,7 @@ mk_logger_backend_config(BackendOpts) -> Type = validate_log_type(maps:get(type, BackendOpts, standard_io)), mk_logger_backend_config(Type, BackendOpts). -validate_log_type(Type) when - Type == standard_io; - Type == standard_error; - Type == file --> +validate_log_type(Type) when Type == standard_io; Type == standard_error; Type == file -> Type; validate_log_type(Type) -> erlang:error(badarg, [Type]). @@ -158,13 +154,11 @@ assert_strict_opts(Ks, Opts) -> %% --spec stop(opts()) -> - ok. +-spec stop(opts()) -> ok. stop(Opts = #{}) -> stop_log_handler(maps:get(log, Opts, #{})). --spec stop_log_handler(log_opts()) -> - ok. +-spec stop_log_handler(log_opts()) -> ok. stop_log_handler(LogOpts = #{}) -> Level = maps:get(level, LogOpts, ?DEFAULT_LOG_LEVEL), ok = log(Level, "audit log stopped", #{}), @@ -175,11 +169,10 @@ stop_log_handler(disabled) -> %% --type beat() :: bouncer_arbiter_pulse:beat(). +-type beat() :: bouncer_arbiter_pulse:beat(). -type metadata() :: bouncer_arbiter_pulse:metadata(). --spec handle_beat(beat(), metadata(), st()) -> - ok. +-spec handle_beat(beat(), metadata(), st()) -> ok. handle_beat(Beat, Metadata, {log, Level}) -> log( get_severity(Beat, Level), @@ -200,34 +193,35 @@ log(Severity, Message, Metadata) -> ok. get_severity({judgement, started}, _Level) -> debug; -get_severity(_, Level) -> Level. +get_severity(_, Level) -> Level. -get_message({judgement, started}) -> <<"judgement started">>; +get_message({judgement, started}) -> <<"judgement started">>; get_message({judgement, {completed, _}}) -> <<"judgement completed">>; -get_message({judgement, {failed, _}}) -> <<"judgement failed">>. +get_message({judgement, {failed, _}}) -> <<"judgement failed">>. get_beat_metadata({judgement, Event}) -> #{ - judgement => case Event of - started -> - #{ - event => started - }; - {completed, {Resolution, Assertions}} -> - encode_restrictions(Resolution, #{ - event => completed, - resolution => encode_resolution(Resolution), - assertions => lists:map(fun encode_assertion/1, Assertions) - }); - {failed, Error} -> - #{ - event => failed, - error => encode_error(Error) - } - end + judgement => + case Event of + started -> + #{ + event => started + }; + {completed, {Resolution, Assertions}} -> + encode_restrictions(Resolution, #{ + event => completed, + resolution => encode_resolution(Resolution), + assertions => lists:map(fun encode_assertion/1, Assertions) + }); + {failed, Error} -> + #{ + event => failed, + error => encode_error(Error) + } + end }. -encode_resolution(allowed) -> <<"allowed">>; +encode_resolution(allowed) -> <<"allowed">>; encode_resolution(forbidden) -> <<"forbidden">>; encode_resolution({restricted, _Restrictions}) -> <<"restricted">>. @@ -257,7 +251,7 @@ extract_metadata(Metadata, Acc) -> extract_opt_meta(K, Metadata, EncodeFun, Acc) -> case maps:find(K, Metadata) of {ok, V} -> Acc#{K => EncodeFun(V)}; - error -> Acc + error -> Acc end. encode_id(ID) when is_binary(ID) -> diff --git a/src/bouncer_context.erl b/src/bouncer_context.erl index 8fc5f28..7e2afa8 100644 --- a/src/bouncer_context.erl +++ b/src/bouncer_context.erl @@ -23,17 +23,14 @@ %% --spec empty() -> - ctx(). +-spec empty() -> ctx(). empty() -> #{}. --spec merge(ctx(), ctx()) -> - {_Merged :: ctx(), _Conflicting :: ctx() | undefined}. - +-spec merge(ctx(), ctx()) -> {_Merged :: ctx(), _Conflicting :: ctx() | undefined}. merge(Ctx1, Ctx2) -> maps:fold( - fun (K, V2, {CtxAcc, ConflictAcc}) -> + fun(K, V2, {CtxAcc, ConflictAcc}) -> case maps:find(K, CtxAcc) of {ok, V1} -> {VM, Conflict} = merge_values(V1, V2), @@ -54,10 +51,11 @@ merge_values(V1, V2) -> case ordsets:is_set(V1) and ordsets:is_set(V2) of true -> Intersection = ordsets:intersection(V1, V2), - MaybeConflict = case ordsets:size(Intersection) of - 0 -> undefined; - _ -> Intersection - end, + MaybeConflict = + case ordsets:size(Intersection) of + 0 -> undefined; + _ -> Intersection + end, {ordsets:union(V1, V2), MaybeConflict}; false when V1 =:= V2 -> {V2, undefined}; diff --git a/src/bouncer_context_v1.erl b/src/bouncer_context_v1.erl index 7fbd34c..896361a 100644 --- a/src/bouncer_context_v1.erl +++ b/src/bouncer_context_v1.erl @@ -7,9 +7,9 @@ -type metadata() :: #{ version := #{ - current := vsn(), + current := vsn(), original := vsn(), - latest := vsn() + latest := vsn() } }. @@ -19,13 +19,13 @@ %% -define(THRIFT_TYPE, - {struct, struct, {bouncer_context_v1_thrift, 'ContextFragment'}}). + {struct, struct, {bouncer_context_v1_thrift, 'ContextFragment'}} +). -type thrift_ctx_fragment() :: bouncer_context_v1_thrift:'ContextFragment'(). -spec decode(format(), _Content :: binary()) -> {ok, bouncer_context:ctx(), metadata()} | {error, _Reason}. - decode(thrift, Content) -> Codec = thrift_strict_binary_codec:new(Content), case thrift_strict_binary_codec:read(Codec, ?THRIFT_TYPE) of @@ -40,15 +40,14 @@ decode(thrift, Content) -> Error end. --spec from_thrift(thrift_ctx_fragment()) -> - {ok, bouncer_context:ctx(), metadata()}. +-spec from_thrift(thrift_ctx_fragment()) -> {ok, bouncer_context:ctx(), metadata()}. from_thrift(#bctx_v1_ContextFragment{} = Ctx0) -> Ctx1 = try_upgrade(Ctx0), Metadata = #{ version => #{ - current => Ctx1#bctx_v1_ContextFragment.vsn, + current => Ctx1#bctx_v1_ContextFragment.vsn, original => Ctx0#bctx_v1_ContextFragment.vsn, - latest => ?BCTX_V1_HEAD + latest => ?BCTX_V1_HEAD } }, {ok, from_thrift_context(Ctx1), Metadata}. @@ -60,15 +59,13 @@ from_thrift_context(Ctx) -> % This 3 refers to the first data field in a ContextFragment, after version field. bouncer_thrift:from_thrift_struct(StructDef, Ctx, 3, #{}). --spec try_upgrade(thrift_ctx_fragment()) -> - thrift_ctx_fragment(). +-spec try_upgrade(thrift_ctx_fragment()) -> thrift_ctx_fragment(). try_upgrade(#bctx_v1_ContextFragment{vsn = ?BCTX_V1_HEAD} = Ctx) -> Ctx. %% --spec encode(format(), bouncer_context:ctx()) -> - {ok, _Content} | {error, _}. +-spec encode(format(), bouncer_context:ctx()) -> {ok, _Content} | {error, _}. encode(thrift, Context) -> Codec = thrift_strict_binary_codec:new(), CtxThrift = to_thrift(Context), @@ -79,8 +76,7 @@ encode(thrift, Context) -> Error end. --spec to_thrift(bouncer_context:ctx()) -> - thrift_ctx_fragment() | no_return(). +-spec to_thrift(bouncer_context:ctx()) -> thrift_ctx_fragment() | no_return(). to_thrift(Context) -> {struct, _, StructDef} = bouncer_context_v1_thrift:struct_info('ContextFragment'), bouncer_thrift:to_thrift_struct(StructDef, Context, #bctx_v1_ContextFragment{}). diff --git a/src/bouncer_thrift.erl b/src/bouncer_thrift.erl index a9d9041..22c14ba 100644 --- a/src/bouncer_thrift.erl +++ b/src/bouncer_thrift.erl @@ -12,12 +12,18 @@ -type type_ref() :: {module(), atom()}. -type field_type() :: - bool | byte | i16 | i32 | i64 | string | double | - {enum, type_ref()} | - {struct, struct_flavour(), type_ref()} | - {list, field_type()} | - {set, field_type()} | - {map, field_type(), field_type()}. + bool + | byte + | i16 + | i32 + | i64 + | string + | double + | {enum, type_ref()} + | {struct, struct_flavour(), type_ref()} + | {list, field_type()} + | {set, field_type()} + | {map, field_type(), field_type()}. -type struct_field_info() :: {field_num(), field_req(), field_type(), field_name(), any()}. diff --git a/test/bouncer_audit_tests_SUITE.erl b/test/bouncer_audit_tests_SUITE.erl index 27a993c..780e777 100644 --- a/test/bouncer_audit_tests_SUITE.erl +++ b/test/bouncer_audit_tests_SUITE.erl @@ -27,9 +27,7 @@ -define(OPA_ENDPOINT, {?OPA_HOST, 8181}). -define(API_RULESET_ID, "service/authz/api"). --spec all() -> - [atom()]. - +-spec all() -> [atom()]. all() -> [ invalid_config_fails_start, @@ -43,31 +41,24 @@ all() -> % write_queue_overload_fails_request ]. --spec init_per_suite(config()) -> - config(). - +-spec init_per_suite(config()) -> config(). init_per_suite(C) -> Apps = genlib_app:start_application(woody) ++ - genlib_app:start_application_with(scoper, [ - {storage, scoper_storage_logger} - ]), + genlib_app:start_application_with(scoper, [ + {storage, scoper_storage_logger} + ]), [{suite_apps, Apps} | C]. --spec end_per_suite(config()) -> - ok. +-spec end_per_suite(config()) -> ok. end_per_suite(C) -> genlib_app:stop_unload_applications(?CONFIG(suite_apps, C)). --spec init_per_testcase(testcase_name(), config()) -> - config(). - +-spec init_per_testcase(testcase_name(), config()) -> config(). init_per_testcase(Name, C) -> [{testcase, Name} | C]. --spec end_per_testcase(testcase_name(), config()) -> - config(). - +-spec end_per_testcase(testcase_name(), config()) -> config(). end_per_testcase(_Name, _C) -> ok. @@ -83,48 +74,71 @@ end_per_testcase(_Name, _C) -> invalid_config_fails_start(C) -> ?assertError( {bouncer, {{{badkey, file}, _Stacktrace}, _}}, - start_stop_bouncer([ - {audit, #{log => #{ - backend => #{ - % NOTE - % Missing target filename here. - type => file - } - }}} - ], C) + start_stop_bouncer( + [ + {audit, #{ + log => #{ + backend => #{ + % NOTE + % Missing target filename here. + type => file + } + } + }} + ], + C + ) ), ?assertError( {bouncer, {{badarg, _Stacktrace}, _}}, - start_stop_bouncer([ - {audit, #{log => #{ - level => blarg - }}} - ], C) + start_stop_bouncer( + [ + {audit, #{ + log => #{ + level => blarg + } + }} + ], + C + ) ). unrecognized_config_fails_start(C) -> ?assertError( {bouncer, {{{unrecognized_opts, #{blarg := _}}, _Stacktrace}, _}}, - start_stop_bouncer([ - {audit, #{blarg => blorg}} - ], C) + start_stop_bouncer( + [ + {audit, #{blarg => blorg}} + ], + C + ) ), ?assertError( {bouncer, {{{unrecognized_opts, #{blarg := _}}, _Stacktrace}, _}}, - start_stop_bouncer([ - {audit, #{log => #{ - blarg => blorg - }}} - ], C) + start_stop_bouncer( + [ + {audit, #{ + log => #{ + blarg => blorg + } + }} + ], + C + ) ), ?assertError( {bouncer, {{{unrecognized_opts, #{hello := _}}, _Stacktrace}, _}}, - start_stop_bouncer([ - {audit, #{log => #{ - level => notice, - hello => <<"mike">> - }}} - ], C) + start_stop_bouncer( + [ + {audit, #{ + log => #{ + level => notice, + hello => <<"mike">> + } + }} + ], + C + ) ). start_stop_bouncer(Env, C) -> @@ -135,11 +149,16 @@ start_stop_bouncer(Env, C) -> write_error_fails_request(C) -> Dirname = mk_temp_dir(?CONFIG(testcase, C)), Filename = filename:join(Dirname, "audit.log"), - C1 = start_bouncer([{audit, #{ - log => #{ - backend => #{type => file, file => Filename} - } - }}], C), + C1 = start_bouncer( + [ + {audit, #{ + log => #{ + backend => #{type => file, file => Filename} + } + }} + ], + C + ), Client = mk_client(C1), try ok = file:delete(Filename), @@ -158,24 +177,30 @@ write_queue_overload_fails_request(C) -> Concurrency = QLen * 10, Dirname = mk_temp_dir(?CONFIG(testcase, C)), Filename = filename:join(Dirname, "audit.log"), - C1 = start_bouncer([{audit, #{ - log => #{ - backend => #{type => file, file => Filename, flush_qlen => QLen}, - formatter => {logger_logstash_formatter, #{single_line => true}} - } - }}], C), + C1 = start_bouncer( + [ + {audit, #{ + log => #{ + backend => #{type => file, file => Filename, flush_qlen => QLen}, + formatter => {logger_logstash_formatter, #{single_line => true}} + } + }} + ], + C + ), Client = mk_client(C1), Results = genlib_pmap:safemap( - fun (_) -> + fun(_) -> call_judge(?API_RULESET_ID, ?CONTEXT(#{}), Client) end, lists:seq(1, Concurrency) ), _ = stop_bouncer(C1), try - {Succeeded, _Failed} = lists:partition(fun ({R, _}) -> R == ok end, Results), + {Succeeded, _Failed} = lists:partition(fun({R, _}) -> R == ok end, Results), {ok, LogfileContents} = file:read_file(Filename), - NumLogEvents = binary:matches(LogfileContents, <<"\"type\":\"audit\"">>), % TODO kinda hacky + % TODO kinda hacky + NumLogEvents = binary:matches(LogfileContents, <<"\"type\":\"audit\"">>), ?assertEqual(length(Succeeded), length(NumLogEvents)) after rm_temp_dir(Dirname) @@ -229,21 +254,24 @@ start_bouncer(Env, C) -> IP = "127.0.0.1", Port = 8022, ArbiterPath = <<"/v1/arbiter">>, - Apps = start_application_with(bouncer, [ - {ip, IP}, - {port, Port}, - {services, #{ - arbiter => #{path => ArbiterPath} - }}, - {transport_opts, #{ - max_connections => 1000, - num_acceptors => 4 - }}, - {opa, #{ - endpoint => ?OPA_ENDPOINT, - transport => tcp - }} - ] ++ Env), + Apps = start_application_with( + bouncer, + [ + {ip, IP}, + {port, Port}, + {services, #{ + arbiter => #{path => ArbiterPath} + }}, + {transport_opts, #{ + max_connections => 1000, + num_acceptors => 4 + }}, + {opa, #{ + endpoint => ?OPA_ENDPOINT, + transport => tcp + }} + ] ++ Env + ), Services = #{ arbiter => mk_url(IP, Port, ArbiterPath) }, @@ -253,8 +281,11 @@ mk_url(IP, Port, Path) -> iolist_to_binary(["http://", IP, ":", genlib:to_binary(Port), Path]). stop_bouncer(C) -> - ct_helper:with_config(testcase_apps, C, - fun (Apps) -> genlib_app:stop_unload_applications(Apps) end). + ct_helper:with_config( + testcase_apps, + C, + fun(Apps) -> genlib_app:stop_unload_applications(Apps) end + ). start_application_with(App, Env) -> _ = application:load(App), diff --git a/test/bouncer_stub_tests_SUITE.erl b/test/bouncer_stub_tests_SUITE.erl index 0e2c474..cb39564 100644 --- a/test/bouncer_stub_tests_SUITE.erl +++ b/test/bouncer_stub_tests_SUITE.erl @@ -28,16 +28,13 @@ -define(OPA_ENDPOINT, {?OPA_HOST, 8181}). -define(API_RULESET_ID, "service/authz/api"). --spec all() -> - [atom()]. - +-spec all() -> [atom()]. all() -> [ {group, general} ]. --spec groups() -> - [{group_name(), list(), [test_case_name()]}]. +-spec groups() -> [{group_name(), list(), [test_case_name()]}]. groups() -> [ {general, [parallel], [ @@ -45,27 +42,21 @@ groups() -> ]} ]. --spec init_per_suite(config()) -> - config(). - +-spec init_per_suite(config()) -> config(). init_per_suite(C) -> Apps = genlib_app:start_application(woody) ++ - genlib_app:start_application_with(scoper, [ - {storage, scoper_storage_logger} - ]), + genlib_app:start_application_with(scoper, [ + {storage, scoper_storage_logger} + ]), [{suite_apps, Apps} | C]. --spec end_per_suite(config()) -> - ok. +-spec end_per_suite(config()) -> ok. end_per_suite(C) -> genlib_app:stop_unload_applications(?CONFIG(suite_apps, C)). --spec init_per_group(group_name(), config()) -> - config(). -init_per_group(Name, C) when - Name == general --> +-spec init_per_group(group_name(), config()) -> config(). +init_per_group(Name, C) when Name == general -> start_bouncer([], C); init_per_group(_Name, C) -> C. @@ -74,15 +65,18 @@ start_bouncer(Env, C) -> IP = "127.0.0.1", Port = 8022, OrgmgmtPath = <<"/v1/org_management_stub">>, - Apps = genlib_app:start_application_with(bouncer, [ - {ip, IP}, - {port, Port}, - {services, #{ - org_management => #{ - path => OrgmgmtPath - } - }} - ] ++ Env), + Apps = genlib_app:start_application_with( + bouncer, + [ + {ip, IP}, + {port, Port}, + {services, #{ + org_management => #{ + path => OrgmgmtPath + } + }} + ] ++ Env + ), Services = #{ org_management => mk_url(IP, Port, OrgmgmtPath) }, @@ -91,24 +85,22 @@ start_bouncer(Env, C) -> mk_url(IP, Port, Path) -> iolist_to_binary(["http://", IP, ":", genlib:to_binary(Port), Path]). --spec end_per_group(group_name(), config()) -> - _. +-spec end_per_group(group_name(), config()) -> _. end_per_group(_Name, C) -> stop_bouncer(C). stop_bouncer(C) -> - ct_helper:with_config(group_apps, C, - fun (Apps) -> genlib_app:stop_unload_applications(Apps) end). - --spec init_per_testcase(atom(), config()) -> - config(). + ct_helper:with_config( + group_apps, + C, + fun(Apps) -> genlib_app:stop_unload_applications(Apps) end + ). +-spec init_per_testcase(atom(), config()) -> config(). init_per_testcase(Name, C) -> [{testcase, Name} | C]. --spec end_per_testcase(atom(), config()) -> - config(). - +-spec end_per_testcase(atom(), config()) -> config(). end_per_testcase(_Name, _C) -> ok. diff --git a/test/bouncer_tests_SUITE.erl b/test/bouncer_tests_SUITE.erl index 44f193c..6a0da7d 100644 --- a/test/bouncer_tests_SUITE.erl +++ b/test/bouncer_tests_SUITE.erl @@ -31,6 +31,7 @@ -export([request_timeout_means_unknown/1]). -behaviour(bouncer_arbiter_pulse). + -export([handle_beat/3]). -include_lib("bouncer_proto/include/bouncer_decisions_thrift.hrl"). @@ -47,9 +48,7 @@ -define(OPA_ENDPOINT, {?OPA_HOST, 8181}). -define(API_RULESET_ID, "service/authz/api"). --spec all() -> - [atom()]. - +-spec all() -> [atom()]. all() -> [ {group, general}, @@ -57,8 +56,7 @@ all() -> {group, network_error_mapping} ]. --spec groups() -> - [{group_name(), list(), [test_case_name()]}]. +-spec groups() -> [{group_name(), list(), [test_case_name()]}]. groups() -> [ {general, [parallel], [ @@ -84,28 +82,21 @@ groups() -> ]} ]. --spec init_per_suite(config()) -> - config(). - +-spec init_per_suite(config()) -> config(). init_per_suite(C) -> Apps = genlib_app:start_application(woody) ++ - genlib_app:start_application_with(scoper, [ - {storage, scoper_storage_logger} - ]), + genlib_app:start_application_with(scoper, [ + {storage, scoper_storage_logger} + ]), [{suite_apps, Apps} | C]. --spec end_per_suite(config()) -> - ok. +-spec end_per_suite(config()) -> ok. end_per_suite(C) -> genlib_app:stop_unload_applications(?CONFIG(suite_apps, C)). --spec init_per_group(group_name(), config()) -> - config(). -init_per_group(Name, C) when - Name == general; - Name == rules_authz_api --> +-spec init_per_group(group_name(), config()) -> config(). +init_per_group(Name, C) when Name == general; Name == rules_authz_api -> start_bouncer([], [{groupname, Name} | C]); init_per_group(Name, C) -> [{groupname, Name} | C]. @@ -115,24 +106,27 @@ start_bouncer(Env, C) -> Port = 8022, ArbiterPath = <<"/v1/arbiter">>, {ok, StashPid} = ct_stash:start(), - Apps = genlib_app:start_application_with(bouncer, [ - {ip, IP}, - {port, Port}, - {services, #{ - arbiter => #{ - path => ArbiterPath, - pulse => [{?MODULE, StashPid}] - } - }}, - {transport_opts, #{ - max_connections => 1000, - num_acceptors => 4 - }}, - {opa, #{ - endpoint => ?OPA_ENDPOINT, - transport => tcp - }} - ] ++ Env), + Apps = genlib_app:start_application_with( + bouncer, + [ + {ip, IP}, + {port, Port}, + {services, #{ + arbiter => #{ + path => ArbiterPath, + pulse => [{?MODULE, StashPid}] + } + }}, + {transport_opts, #{ + max_connections => 1000, + num_acceptors => 4 + }}, + {opa, #{ + endpoint => ?OPA_ENDPOINT, + transport => tcp + }} + ] ++ Env + ), Services = #{ arbiter => mk_url(IP, Port, ArbiterPath) }, @@ -141,34 +135,34 @@ start_bouncer(Env, C) -> mk_url(IP, Port, Path) -> iolist_to_binary(["http://", IP, ":", genlib:to_binary(Port), Path]). --spec end_per_group(group_name(), config()) -> - _. +-spec end_per_group(group_name(), config()) -> _. end_per_group(_Name, C) -> stop_bouncer(C). stop_bouncer(C) -> - ct_helper:with_config(group_apps, C, - fun (Apps) -> genlib_app:stop_unload_applications(Apps) end), - ct_helper:with_config(stash, C, - fun (Pid) -> ?assertEqual(ok, ct_stash:destroy(Pid)) end). - --spec init_per_testcase(atom(), config()) -> - config(). + ct_helper:with_config( + group_apps, + C, + fun(Apps) -> genlib_app:stop_unload_applications(Apps) end + ), + ct_helper:with_config( + stash, + C, + fun(Pid) -> ?assertEqual(ok, ct_stash:destroy(Pid)) end + ). +-spec init_per_testcase(atom(), config()) -> config(). init_per_testcase(Name, C) -> [{testcase, Name} | C]. --spec end_per_testcase(atom(), config()) -> - config(). - +-spec end_per_testcase(atom(), config()) -> config(). end_per_testcase(_Name, _C) -> ok. %% -define(CONTEXT(Fragments), #bdcs_Context{fragments = Fragments}). --define(JUDGEMENT(Resolution), - #bdcs_Judgement{resolution = Resolution}). +-define(JUDGEMENT(Resolution), #bdcs_Judgement{resolution = Resolution}). -spec missing_ruleset_notfound(config()) -> ok. -spec incorrect_ruleset_invalid1(config()) -> ok. @@ -198,9 +192,11 @@ incorrect_ruleset_invalid1(C) -> call_judge("trivial/incorrect1", ?CONTEXT(#{}), Client) ), ?assertMatch( - {judgement, {failed, {ruleset_invalid, [ - {data_invalid, _, wrong_size, _, [<<"resolution">>]} - ]}}}, + {judgement, + {failed, + {ruleset_invalid, [ + {data_invalid, _, wrong_size, _, [<<"resolution">>]} + ]}}}, lists:last(flush_beats(Client, C)) ). @@ -211,9 +207,11 @@ incorrect_ruleset_invalid2(C) -> call_judge("trivial/incorrect2", ?CONTEXT(#{}), Client) ), ?assertMatch( - {judgement, {failed, {ruleset_invalid, [ - {data_invalid, _, wrong_type, _, [<<"resolution">>, _]} - ]}}}, + {judgement, + {failed, + {ruleset_invalid, [ + {data_invalid, _, wrong_type, _, [<<"resolution">>, _]} + ]}}}, lists:last(flush_beats(Client, C)) ). @@ -224,9 +222,11 @@ incorrect_ruleset_invalid3(C) -> call_judge("trivial/incorrect3", ?CONTEXT(#{}), Client) ), ?assertMatch( - {judgement, {failed, {ruleset_invalid, [ - {data_invalid, _, no_extra_items_allowed, [<<"forbidden">>, [#{}], #{}], _} - ]}}}, + {judgement, + {failed, + {ruleset_invalid, [ + {data_invalid, _, no_extra_items_allowed, [<<"forbidden">>, [#{}], #{}], _} + ]}}}, lists:last(flush_beats(Client, C)) ). @@ -239,9 +239,11 @@ missing_content_invalid_context(C) -> call_judge(?API_RULESET_ID, Context, Client) ), ?assertMatch( - {judgement, {failed, {malformed_context, #{ - <<"missing">> := {v1_thrift_binary, {unexpected, _, _, _}} - }}}}, + {judgement, + {failed, + {malformed_context, #{ + <<"missing">> := {v1_thrift_binary, {unexpected, _, _, _}} + }}}}, lists:last(flush_beats(Client, C)) ). @@ -255,9 +257,11 @@ junk_content_invalid_context(C) -> call_judge(?API_RULESET_ID, Context, Client) ), ?assertMatch( - {judgement, {failed, {malformed_context, #{ - <<"missing">> := {v1_thrift_binary, {unexpected, _, _, _}} - }}}}, + {judgement, + {failed, + {malformed_context, #{ + <<"missing">> := {v1_thrift_binary, {unexpected, _, _, _}} + }}}}, lists:last(flush_beats(Client, C)) ). @@ -265,7 +269,7 @@ conflicting_context_invalid(C) -> Client = mk_client(C), Fragment1 = #{ user => #{ - id => <<"joeblow">>, + id => <<"joeblow">>, email => Email1 = <<"deadinside69@example.org">> }, requester => #{ @@ -274,7 +278,7 @@ conflicting_context_invalid(C) -> }, Fragment2 = #{ user => #{ - id => <<"joeblow">>, + id => <<"joeblow">>, email => <<"deadinside420@example.org">> }, requester => #{ @@ -290,9 +294,11 @@ conflicting_context_invalid(C) -> call_judge(?API_RULESET_ID, Context, Client) ), ?assertEqual( - {judgement, {failed, {conflicting_context, #{ - <<"frag2">> => #{user => #{email => Email1}} - }}}}, + {judgement, + {failed, + {conflicting_context, #{ + <<"frag2">> => #{user => #{email => Email1}} + }}}}, lists:last(flush_beats(Client, C)) ). @@ -300,7 +306,7 @@ distinct_sets_context_valid(C) -> Client = mk_client(C), Fragment1 = #{ user => #{ - id => <<"joeblow">>, + id => <<"joeblow">>, orgs => mk_ordset([ #{ id => <<"hoolie">>, @@ -315,7 +321,7 @@ distinct_sets_context_valid(C) -> }, Fragment2 = #{ user => #{ - id => <<"joeblow">>, + id => <<"joeblow">>, orgs => mk_ordset([ #{ id => <<"hoolie">>, @@ -354,11 +360,18 @@ restricted_search_invoices_shop_manager(C) -> mk_auth_session_token(), mk_env(), mk_op_search_invoices(mk_ordset([#{id => <<"SHOP">>}]), <<"PARTY">>), - mk_user(<<"USER">>, mk_ordset([ - mk_user_org(<<"PARTY">>, <<"OWNER">>, mk_ordset([ - mk_role(<<"Manager">>, <<"SHOP">>) - ])) - ])) + mk_user( + <<"USER">>, + mk_ordset([ + mk_user_org( + <<"PARTY">>, + <<"OWNER">>, + mk_ordset([ + mk_role(<<"Manager">>, <<"SHOP">>) + ]) + ) + ]) + ) ]), Context = ?CONTEXT(#{<<"root">> => mk_ctx_v1_fragment(Fragment)}), ?assertMatch( @@ -375,8 +388,9 @@ forbidden_expired(C) -> % Would be funny if this fails on some system too deep in the past. Fragment = maps:merge(mk_env(), #{ auth => #{ - method => <<"AccessToken">>, - expiration => <<"1991-12-26T17:00:00Z">> % ☭😢 + method => <<"AccessToken">>, + % ☭😢 + expiration => <<"1991-12-26T17:00:00Z">> } }), Context = ?CONTEXT(#{<<"root">> => mk_ctx_v1_fragment(Fragment)}), @@ -429,10 +443,12 @@ forbidden_w_empty_context(C) -> ). mk_user(UserID, UserOrgs) -> - #{user => #{ - id => UserID, - orgs => UserOrgs - }}. + #{ + user => #{ + id => UserID, + orgs => UserOrgs + } + }. mk_user_org(OrgID, OwnerID, Roles) -> #{ @@ -448,24 +464,30 @@ mk_auth_session_token() -> mk_auth_session_token(erlang:system_time(second) + 3600). mk_auth_session_token(ExpiresAt) -> - #{auth => #{ - method => <<"SessionToken">>, - expiration => format_ts(ExpiresAt, second) - }}. + #{ + auth => #{ + method => <<"SessionToken">>, + expiration => format_ts(ExpiresAt, second) + } + }. mk_op_search_invoices(Shops, PartyID) -> - #{anapi => #{ - op => #{ - id => <<"SearchInvoices">>, - shops => Shops, - party => #{id => PartyID} + #{ + anapi => #{ + op => #{ + id => <<"SearchInvoices">>, + shops => Shops, + party => #{id => PartyID} + } } - }}. + }. mk_env() -> - #{env => #{ - now => format_now() - }}. + #{ + env => #{ + now => format_now() + } + }. format_now() -> USec = erlang:system_time(second), @@ -482,11 +504,16 @@ format_ts(Ts, Unit) -> -spec request_timeout_means_unknown(config()) -> ok. connect_failed_means_unavailable(C) -> - C1 = start_bouncer([{opa, #{ - endpoint => {?OPA_HOST, 65535}, - transport => tcp, - event_handler => {ct_gun_event_h, []} - }}], C), + C1 = start_bouncer( + [ + {opa, #{ + endpoint => {?OPA_HOST, 65535}, + transport => tcp, + event_handler => {ct_gun_event_h, []} + }} + ], + C + ), Client = mk_client(C1), try ?assertError( @@ -550,16 +577,24 @@ request_timeout_means_unknown(C) -> end. start_proxy_bouncer(Proxy, C) -> - start_bouncer([{opa, #{ - endpoint => ct_proxy:endpoint(Proxy), - transport => tcp, - event_handler => {ct_gun_event_h, []} - }}], C). + start_bouncer( + [ + {opa, #{ + endpoint => ct_proxy:endpoint(Proxy), + transport => tcp, + event_handler => {ct_gun_event_h, []} + }} + ], + C + ). change_proxy_mode(Proxy, Scope, Mode, C) -> ModeWas = ct_proxy:mode(Proxy, Scope, Mode), - _ = ct:pal(debug, "[~p] set proxy ~p from '~p' to '~p'", - [?CONFIG(testcase, C), Scope, ModeWas, Mode]), + _ = ct:pal( + debug, + "[~p] set proxy ~p from '~p' to '~p'", + [?CONFIG(testcase, C), Scope, ModeWas, Mode] + ), ok. %% @@ -599,8 +634,7 @@ get_service_spec(arbiter) -> %% --spec handle_beat(bouncer_arbiter_pulse:beat(), bouncer_arbiter_pulse:metadata(), pid()) -> - ok. +-spec handle_beat(bouncer_arbiter_pulse:beat(), bouncer_arbiter_pulse:metadata(), pid()) -> ok. handle_beat(Beat, Metadata, StashPid) -> _ = stash_beat(Beat, Metadata, StashPid), ct:pal("~p [arbiter] ~0p:~nmetadata=~p", [self(), Beat, Metadata]). diff --git a/test/ct_gun_event_h.erl b/test/ct_gun_event_h.erl index a3c5d11..67b96ee 100644 --- a/test/ct_gun_event_h.erl +++ b/test/ct_gun_event_h.erl @@ -1,4 +1,5 @@ -module(ct_gun_event_h). + -behavior(gun_event). -export([init/2]). @@ -33,176 +34,147 @@ -type st() :: _. --spec init(gun_event:init_event(), st()) -> - st(). +-spec init(gun_event:init_event(), st()) -> st(). init(Event, State) -> _ = ct:pal("~p [gun] init: ~p", [self(), Event]), State. --spec domain_lookup_start(gun_event:domain_lookup_event(), st()) -> - st(). +-spec domain_lookup_start(gun_event:domain_lookup_event(), st()) -> st(). domain_lookup_start(Event, State) -> _ = ct:pal("~p [gun] domain lookup start: ~p", [self(), Event]), State. --spec domain_lookup_end(gun_event:domain_lookup_event(), st()) -> - st(). +-spec domain_lookup_end(gun_event:domain_lookup_event(), st()) -> st(). domain_lookup_end(Event, State) -> _ = ct:pal("~p [gun] domain lookup end: ~p", [self(), Event]), State. --spec connect_start(gun_event:connect_event(), st()) -> - st(). +-spec connect_start(gun_event:connect_event(), st()) -> st(). connect_start(Event, State) -> _ = ct:pal("~p [gun] connect start: ~p", [self(), Event]), State. --spec connect_end(gun_event:connect_event(), st()) -> - st(). +-spec connect_end(gun_event:connect_event(), st()) -> st(). connect_end(Event, State) -> _ = ct:pal("~p [gun] connect end: ~p", [self(), Event]), State. --spec tls_handshake_start(gun_event:tls_handshake_event(), st()) -> - st(). +-spec tls_handshake_start(gun_event:tls_handshake_event(), st()) -> st(). tls_handshake_start(Event, State) -> _ = ct:pal("~p [gun] tls handshake start: ~p", [self(), Event]), State. --spec tls_handshake_end(gun_event:tls_handshake_event(), st()) -> - st(). +-spec tls_handshake_end(gun_event:tls_handshake_event(), st()) -> st(). tls_handshake_end(Event, State) -> _ = ct:pal("~p [gun] tls handshake end: ~p", [self(), Event]), State. --spec request_start(gun_event:request_start_event(), st()) -> - st(). +-spec request_start(gun_event:request_start_event(), st()) -> st(). request_start(Event, State) -> _ = ct:pal("~p [gun] request start: ~p", [self(), Event]), State. --spec request_headers(gun_event:request_start_event(), st()) -> - st(). +-spec request_headers(gun_event:request_start_event(), st()) -> st(). request_headers(Event, State) -> _ = ct:pal("~p [gun] request headers: ~p", [self(), Event]), State. --spec request_end(gun_event:request_end_event(), st()) -> - st(). +-spec request_end(gun_event:request_end_event(), st()) -> st(). request_end(Event, State) -> _ = ct:pal("~p [gun] request end: ~p", [self(), Event]), State. --spec push_promise_start(gun_event:push_promise_start_event(), st()) -> - st(). +-spec push_promise_start(gun_event:push_promise_start_event(), st()) -> st(). push_promise_start(Event, State) -> _ = ct:pal("~p [gun] push promise start: ~p", [self(), Event]), State. --spec push_promise_end(gun_event:push_promise_end_event(), st()) -> - st(). +-spec push_promise_end(gun_event:push_promise_end_event(), st()) -> st(). push_promise_end(Event, State) -> _ = ct:pal("~p [gun] push promise end: ~p", [self(), Event]), State. --spec response_start(gun_event:response_start_event(), st()) -> - st(). +-spec response_start(gun_event:response_start_event(), st()) -> st(). response_start(Event, State) -> _ = ct:pal("~p [gun] response start: ~p", [self(), Event]), State. --spec response_inform(gun_event:response_headers_event(), st()) -> - st(). +-spec response_inform(gun_event:response_headers_event(), st()) -> st(). response_inform(Event, State) -> _ = ct:pal("~p [gun] response inform: ~p", [self(), Event]), State. --spec response_headers(gun_event:response_headers_event(), st()) -> - st(). +-spec response_headers(gun_event:response_headers_event(), st()) -> st(). response_headers(Event, State) -> _ = ct:pal("~p [gun] response headers: ~p", [self(), Event]), State. --spec response_trailers(gun_event:response_trailers_event(), st()) -> - st(). +-spec response_trailers(gun_event:response_trailers_event(), st()) -> st(). response_trailers(Event, State) -> _ = ct:pal("~p [gun] response trailers: ~p", [self(), Event]), State. --spec response_end(gun_event:response_end_event(), st()) -> - st(). +-spec response_end(gun_event:response_end_event(), st()) -> st(). response_end(Event, State) -> _ = ct:pal("~p [gun] response end: ~p", [self(), Event]), State. --spec ws_upgrade(gun_event:ws_upgrade_event(), st()) -> - st(). +-spec ws_upgrade(gun_event:ws_upgrade_event(), st()) -> st(). ws_upgrade(Event, State) -> _ = ct:pal("~p [gun] ws upgrade: ~p", [self(), Event]), State. --spec ws_recv_frame_start(gun_event:ws_recv_frame_start_event(), st()) -> - st(). +-spec ws_recv_frame_start(gun_event:ws_recv_frame_start_event(), st()) -> st(). ws_recv_frame_start(Event, State) -> _ = ct:pal("~p [gun] ws recv frame start: ~p", [self(), Event]), State. --spec ws_recv_frame_header(gun_event:ws_recv_frame_header_event(), st()) -> - st(). +-spec ws_recv_frame_header(gun_event:ws_recv_frame_header_event(), st()) -> st(). ws_recv_frame_header(Event, State) -> _ = ct:pal("~p [gun] ws recv frame header: ~p", [self(), Event]), State. --spec ws_recv_frame_end(gun_event:ws_recv_frame_end_event(), st()) -> - st(). +-spec ws_recv_frame_end(gun_event:ws_recv_frame_end_event(), st()) -> st(). ws_recv_frame_end(Event, State) -> _ = ct:pal("~p [gun] ws recv frame end: ~p", [self(), Event]), State. --spec ws_send_frame_start(gun_event:ws_send_frame_event(), st()) -> - st(). +-spec ws_send_frame_start(gun_event:ws_send_frame_event(), st()) -> st(). ws_send_frame_start(Event, State) -> _ = ct:pal("~p [gun] ws send frame start: ~p", [self(), Event]), State. --spec ws_send_frame_end(gun_event:ws_send_frame_event(), st()) -> - st(). +-spec ws_send_frame_end(gun_event:ws_send_frame_event(), st()) -> st(). ws_send_frame_end(Event, State) -> _ = ct:pal("~p [gun] ws send frame end: ~p", [self(), Event]), State. --spec protocol_changed(gun_event:protocol_changed_event(), st()) -> - st(). +-spec protocol_changed(gun_event:protocol_changed_event(), st()) -> st(). protocol_changed(Event, State) -> _ = ct:pal("~p [gun] protocol changed: ~p", [self(), Event]), State. --spec transport_changed(gun_event:transport_changed_event(), st()) -> - st(). +-spec transport_changed(gun_event:transport_changed_event(), st()) -> st(). transport_changed(Event, State) -> _ = ct:pal("~p [gun] transport changed: ~p", [self(), Event]), State. --spec origin_changed(gun_event:origin_changed_event(), st()) -> - st(). +-spec origin_changed(gun_event:origin_changed_event(), st()) -> st(). origin_changed(Event, State) -> _ = ct:pal("~p [gun] origin changed: ~p", [self(), Event]), State. --spec cancel(gun_event:cancel_event(), st()) -> - st(). +-spec cancel(gun_event:cancel_event(), st()) -> st(). cancel(Event, State) -> _ = ct:pal("~p [gun] cancel: ~p", [self(), Event]), State. --spec disconnect(gun_event:disconnect_event(), st()) -> - st(). +-spec disconnect(gun_event:disconnect_event(), st()) -> st(). disconnect(Event, State) -> _ = ct:pal("~p [gun] disconnect: ~p", [self(), Event]), State. --spec terminate(gun_event:terminate_event(), st()) -> - st(). +-spec terminate(gun_event:terminate_event(), st()) -> st(). terminate(Event, State) -> _ = ct:pal("~p [gun] terminate: ~p", [self(), Event]), State. diff --git a/test/ct_helper.erl b/test/ct_helper.erl index 09b6659..83354fe 100644 --- a/test/ct_helper.erl +++ b/test/ct_helper.erl @@ -11,26 +11,25 @@ %% --spec with_config(atom(), config(), fun ((_) -> R)) -> - R | undefined. +-spec with_config(atom(), config(), fun((_) -> R)) -> R | undefined. with_config(Name, C, Fun) -> case lists:keyfind(Name, 1, C) of {_, V} -> Fun(V); - false -> undefined + false -> undefined end. --spec get_temp_dir() -> - file:filename_all(). +-spec get_temp_dir() -> file:filename_all(). get_temp_dir() -> - hd(genlib_list:compact([ - get_env("TMPDIR"), - get_env("TEMP"), - get_env("TMP"), - "/tmp" - ])). + hd( + genlib_list:compact([ + get_env("TMPDIR"), + get_env("TEMP"), + get_env("TMP"), + "/tmp" + ]) + ). --spec get_env(string()) -> - string() | undefined. +-spec get_env(string()) -> string() | undefined. get_env(Name) -> case os:getenv(Name) of V when is_list(V) -> diff --git a/test/ct_proxy.erl b/test/ct_proxy.erl index 63568a5..1acd436 100644 --- a/test/ct_proxy.erl +++ b/test/ct_proxy.erl @@ -11,6 +11,7 @@ %% -behaviour(gen_server). + -export([ init/1, handle_call/3, @@ -25,23 +26,19 @@ -include_lib("kernel/include/inet.hrl"). -type endpoint() :: {inet:hostname(), inet:port_number()}. --type scope() :: listen | connection. --type mode() :: ignore | stop | relay. --type modes() :: #{scope() => mode()}. +-type scope() :: listen | connection. +-type mode() :: ignore | stop | relay. +-type modes() :: #{scope() => mode()}. -type proxy() :: pid(). --spec start_link(endpoint()) -> - {ok, proxy()}. +-spec start_link(endpoint()) -> {ok, proxy()}. --spec start_link(endpoint(), modes()) -> - {ok, proxy()}. +-spec start_link(endpoint(), modes()) -> {ok, proxy()}. --spec start_link(endpoint(), modes(), ranch_tcp:opts()) -> - {ok, proxy()}. +-spec start_link(endpoint(), modes(), ranch_tcp:opts()) -> {ok, proxy()}. --spec unlink(proxy()) -> - proxy(). +-spec unlink(proxy()) -> proxy(). start_link(Upstream) -> start_link(Upstream, #{}). @@ -61,44 +58,35 @@ unlink(Proxy) when is_pid(Proxy) -> true = erlang:unlink(Proxy), Proxy. --spec endpoint(proxy()) -> - endpoint(). +-spec endpoint(proxy()) -> endpoint(). endpoint(Proxy) when is_pid(Proxy) -> gen_server:call(Proxy, endpoint). --spec mode(proxy(), scope()) -> - {mode(), _Upstream :: endpoint()}. - +-spec mode(proxy(), scope()) -> {mode(), _Upstream :: endpoint()}. mode(Proxy, Scope) when is_pid(Proxy) -> gen_server:call(Proxy, {mode, Scope}). --spec mode(proxy(), scope(), mode()) -> - mode(). - +-spec mode(proxy(), scope(), mode()) -> mode(). mode(Proxy, Scope, Mode) when is_pid(Proxy) -> gen_server:call(Proxy, {mode, Scope, Mode}). --spec stop(proxy()) -> - ok. - +-spec stop(proxy()) -> ok. stop(Proxy) when is_pid(Proxy) -> proc_lib:stop(Proxy, shutdown). %% -record(st, { - lsock :: _Socket | undefined, + lsock :: _Socket | undefined, lsockopts :: list(), - acceptor :: pid() | undefined, - modes :: #{scope() => mode()}, - upstream :: {inet:ip_address(), inet:port_number()} + acceptor :: pid() | undefined, + modes :: #{scope() => mode()}, + upstream :: {inet:ip_address(), inet:port_number()} }). -type st() :: #st{}. --spec init(_) -> - {ok, st()}. - +-spec init(_) -> {ok, st()}. init({Upstream, Modes0, SocketOpts}) -> Modes = maps:merge(#{listen => relay, connection => relay}, Modes0), St = #st{ @@ -108,9 +96,7 @@ init({Upstream, Modes0, SocketOpts}) -> }, {ok, sync_mode(listen, stop, maps:get(listen, Modes), St)}. --spec handle_call(_Call, _From, st()) -> - {noreply, st()}. - +-spec handle_call(_Call, _From, st()) -> {noreply, st()}. handle_call(endpoint, _From, St = #st{}) -> {reply, get_endpoint(St), St}; handle_call({mode, Scope, Mode}, _From, St = #st{modes = Modes}) -> @@ -122,27 +108,19 @@ handle_call({mode, Scope}, _From, St = #st{modes = Modes, upstream = Endpoint}) handle_call(_Call, _From, St) -> {noreply, St}. --spec handle_cast(_Cast, st()) -> - {noreply, st()}. - +-spec handle_cast(_Cast, st()) -> {noreply, st()}. handle_cast(_Cast, St) -> {noreply, St}. --spec handle_info(_Info, st()) -> - {noreply, st()}. - +-spec handle_info(_Info, st()) -> {noreply, st()}. handle_info(_Info, St) -> {noreply, St}. --spec terminate(_Reason, st()) -> - _. - +-spec terminate(_Reason, st()) -> _. terminate(_Reason, _St) -> ok. --spec code_change(_Vsn | {down, _Vsn}, st(), _Extra) -> - {ok, st()}. - +-spec code_change(_Vsn | {down, _Vsn}, st(), _Extra) -> {ok, st()}. code_change(_Vsn, St, _Extra) -> {ok, St}. @@ -190,7 +168,7 @@ stop_listener(St = #st{lsock = LSock}) when lsock /= undefined -> start_acceptor(St = #st{acceptor = undefined, lsock = LSock}) -> ct:pal("start_acceptor @ ~p", [St]), Parent = self(), - Pid = erlang:spawn_link(fun () -> loop_acceptor(Parent, LSock) end), + Pid = erlang:spawn_link(fun() -> loop_acceptor(Parent, LSock) end), St#st{acceptor = Pid}. stop_acceptor(St = #st{acceptor = Pid}) when is_pid(Pid) -> @@ -198,19 +176,21 @@ stop_acceptor(St = #st{acceptor = Pid}) when is_pid(Pid) -> MRef = erlang:monitor(process, Pid), true = erlang:unlink(Pid), true = erlang:exit(Pid, shutdown), - receive {'DOWN', MRef, process, Pid, _Reason} -> - St#st{acceptor = undefined} + receive + {'DOWN', MRef, process, Pid, _Reason} -> + St#st{acceptor = undefined} end. loop_acceptor(Parent, LSock) -> - _ = case ranch_tcp:accept(LSock, infinity) of - {ok, CSock} -> - _ = ct:pal("accepted ~p from ~p", [CSock, ranch_tcp:peername(CSock)]), - _ = spawn_proxy_connection(Parent, CSock), - loop_acceptor(Parent, LSock); - {error, Reason} -> - exit(Reason) - end. + _ = + case ranch_tcp:accept(LSock, infinity) of + {ok, CSock} -> + _ = ct:pal("accepted ~p from ~p", [CSock, ranch_tcp:peername(CSock)]), + _ = spawn_proxy_connection(Parent, CSock), + loop_acceptor(Parent, LSock); + {error, Reason} -> + exit(Reason) + end. %% @@ -228,7 +208,7 @@ loop_acceptor(Parent, LSock) -> spawn_proxy_connection(Parent, CSock) -> ProxySt = #proxy{insock = CSock, parent = Parent}, - erlang:spawn_link(fun () -> loop_proxy_connection(ProxySt) end). + erlang:spawn_link(fun() -> loop_proxy_connection(ProxySt) end). loop_proxy_connection(St = #proxy{insock = InSock, parent = Parent, buffer = Buffer}) -> case ranch_tcp:recv(InSock, 0, ?PROXY_RECV_TIMEOUT) of @@ -241,7 +221,7 @@ loop_proxy_connection(St = #proxy{insock = InSock, parent = Parent, buffer = Buf ignore -> loop_proxy_connection(St); relay -> - loop_proxy_relay(St#proxy{buffer = Buffer1, upstream = Endpoint}) + loop_proxy_relay(St#proxy{buffer = Buffer1, upstream = Endpoint}) end; _ -> terminate(St) diff --git a/test/ct_stash.erl b/test/ct_stash.erl index cca1263..2768787 100644 --- a/test/ct_stash.erl +++ b/test/ct_stash.erl @@ -1,4 +1,5 @@ -module(ct_stash). + -behaviour(gen_server). -export([start/0]). @@ -22,23 +23,19 @@ -type key() :: _. -type entry() :: _. --spec start() -> - {ok, pid()}. +-spec start() -> {ok, pid()}. start() -> gen_server:start(?MODULE, [], []). --spec destroy(pid()) -> - ok | {error, {nonempty, _Left :: #{key() => entry()}}}. +-spec destroy(pid()) -> ok | {error, {nonempty, _Left :: #{key() => entry()}}}. destroy(Pid) -> call(Pid, destroy). --spec append(pid(), key(), entry()) -> - ok. +-spec append(pid(), key(), entry()) -> ok. append(Pid, Key, Entry) -> call(Pid, {append, Key, Entry}). --spec flush(pid(), key()) -> - {ok, [entry()]} | error. +-spec flush(pid(), key()) -> {ok, [entry()]} | error. flush(Pid, Key) -> call(Pid, {flush, Key}). @@ -47,13 +44,11 @@ call(Pid, Msg) -> %%% gen_server callbacks --spec init(term()) -> - {ok, atom()}. +-spec init(term()) -> {ok, atom()}. init(_) -> {ok, #{}}. --spec handle_call(term(), pid(), atom()) -> - {reply, atom(), atom()}. +-spec handle_call(term(), pid(), atom()) -> {reply, atom(), atom()}. handle_call({append, Key, Entry}, _From, State) -> Entries = maps:get(Key, State, []), State1 = maps:put(Key, [Entry | Entries], State), @@ -70,27 +65,23 @@ handle_call(destroy, _From, State) -> 0 -> {stop, shutdown, ok, State}; _ -> - Left = maps:map(fun (_, Entries) -> lists:reverse(Entries) end, State), + Left = maps:map(fun(_, Entries) -> lists:reverse(Entries) end, State), Reason = {error, {nonempty, Left}}, {stop, Reason, Reason, State} end. --spec handle_cast(term(), atom()) -> {noreply, atom()}. - +-spec handle_cast(term(), atom()) -> {noreply, atom()}. handle_cast(_Msg, State) -> {noreply, State}. -spec handle_info(term(), atom()) -> {noreply, atom()}. - handle_info(_Info, State) -> {noreply, State}. -spec terminate(term(), atom()) -> atom(). - terminate(_Reason, _State) -> ok. -spec code_change(term(), term(), term()) -> {ok, atom()}. - code_change(_OldVsn, State, _Extra) -> {ok, State}.