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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .elp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[eqwalizer]
enable_all = false
max_tasks = 4
27 changes: 19 additions & 8 deletions .github/workflows/static-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@ permissions:
contents: read

jobs:
dialyzer:
name: Dialyzer
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: recursive

- name: Load .env
run: grep -v '^#' .env >> "$GITHUB_ENV"

- name: Build dev image
run: make dev-image

- name: Run dialyzer
run: make wc-dialyze

eqwalizer:
name: Eqwalizer
runs-on: ubuntu-latest
Expand All @@ -28,11 +46,4 @@ jobs:
run: make dev-image

- name: Run eqwalizer
run: |
make wc-eqwalizer 2>&1 | tee /tmp/eqwalizer.log
# `elp eqwalize-all` exits 0 even when it reports errors; fail the
# job ourselves if any module produced errors.
if grep -qE "^[0-9]+ ERRORS" /tmp/eqwalizer.log; then
echo "::error::Eqwalizer reported errors"
exit 1
fi
run: make wc-eqwalizer
2 changes: 1 addition & 1 deletion .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
erlang 28.0
erlang 28.5
rebar 3.25.0
8 changes: 5 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,13 @@ check-format:
dialyze:
$(REBAR) as test dialyzer

EQW_MODULES ?= $(shell grep -v '^\#' eqwalizer.modules | tr '\n' ' ')

eqwalizer:
$(REBAR) compile
# ERL_LIBS lets elp's erlang_service load compiled parse_transforms
# (e.g. dmt_domain_pt) when analysing modules that use them.
ERL_LIBS=$(CURDIR)/_build/default/lib elp eqwalize-all
@for m in $(EQW_MODULES); do \
ERL_LIBS=$(CURDIR)/_build/default/lib elp eqwalize $$m || exit 1; \
done

release:
$(REBAR) as prod release
Expand Down
57 changes: 3 additions & 54 deletions apps/dmt/src/dmt_api_woody_utils.erl
Original file line number Diff line number Diff line change
Expand Up @@ -5,51 +5,12 @@

%% API

-type service_opts() :: #{
url := binary() | list(),
transport_opts => term(),
resolver_opts => term()
}.

%% Options passed to every thrift handler via woody's `handlers` config.
-type handler_options() :: #{default_handling_timeout := timeout(), atom() => term()}.

-export_type([service_opts/0, handler_options/0]).

-spec get_woody_client(atom()) -> woody_client:options().
get_woody_client(Service) ->
Services = get_services(genlib_app:env(dmt_api, services, #{})),
Services = genlib_app:env(dmt_api, services, #{}),
make_woody_client(maps:get(Service, Services)).

-spec get_services(term()) -> #{atom() => service_opts()}.
get_services(Map) when is_map(Map) ->
maps:fold(fun fold_service/3, #{}, Map);
get_services(_) ->
#{}.

-spec fold_service(term(), term(), #{atom() => service_opts()}) ->
#{atom() => service_opts()}.
fold_service(K, #{url := Url} = V, Acc) when is_atom(K), is_binary(Url) ->
Acc#{K => build_service_opts(V, Url)};
fold_service(K, #{url := Url} = V, Acc) when is_atom(K), is_list(Url) ->
Acc#{K => build_service_opts(V, Url)};
fold_service(_, _, Acc) ->
Acc.

-spec build_service_opts(map(), binary() | list()) -> service_opts().
build_service_opts(V, Url) ->
Base = #{url => Url},
Base1 =
case maps:find(transport_opts, V) of
{ok, T} -> Base#{transport_opts => T};
error -> Base
end,
case maps:find(resolver_opts, V) of
{ok, R} -> Base1#{resolver_opts => R};
error -> Base1
end.

-spec make_woody_client(service_opts()) -> woody_client:options().
-spec make_woody_client(#{atom() => _}) -> woody_client:options().
make_woody_client(#{url := Url} = Service) ->
lists:foldl(
fun(Opt, Acc) ->
Expand All @@ -70,19 +31,7 @@ make_woody_client(#{url := Url} = Service) ->

-spec get_woody_event_handlers() -> woody:ev_handlers().
get_woody_event_handlers() ->
Default = [scoper_woody_event_handler],
case genlib_app:env(dmt_api, woody_event_handlers, Default) of
Handler when is_atom(Handler) -> Handler;
{Mod, Opts} when is_atom(Mod) -> {Mod, Opts};
[_ | _] = List -> [ensure_ev_handler(H) || H <- List];
[] -> Default;
_ -> Default
end.

-spec ensure_ev_handler(term()) -> woody:ev_handler() | no_return().
ensure_ev_handler(Mod) when is_atom(Mod) -> Mod;
ensure_ev_handler({Mod, Opts}) when is_atom(Mod) -> {Mod, Opts};
ensure_ev_handler(Other) -> erlang:error({bad_ev_handler, Other}).
genlib_app:env(dmt_api, woody_event_handlers, [scoper_woody_event_handler]).

-spec ensure_woody_deadline_set(woody_context:ctx(), woody_deadline:deadline()) -> woody_context:ctx().
ensure_woody_deadline_set(WoodyContext, Default) ->
Expand Down
8 changes: 1 addition & 7 deletions apps/dmt/src/dmt_app.erl
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,9 @@

-export([start/2, stop/1]).

-spec start(application:start_type(), term()) ->
{ok, pid()} | {ok, pid(), term()} | {error, term()}.
start(_StartType, _StartArgs) ->
case dmt_sup:start_link() of
ignore -> {error, ignore};
Other -> Other
end.
dmt_sup:start_link().

-spec stop(term()) -> ok.
stop(_State) ->
ok.

Expand Down
22 changes: 1 addition & 21 deletions apps/dmt/src/dmt_author.erl
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
-module(dmt_author).

%% Public API
-typing([eqwalizer]).

-include_lib("damsel/include/dmsl_domain_conf_v2_thrift.hrl").

Expand All @@ -20,12 +19,6 @@

-export_type([author_id/0, name/0, email/0, author/0]).

%% Optional: Extended API (can be uncommented if these functions should be exposed)
%% -export([
%% list/2,
%% search/2
%% ]).

-spec insert(name(), email()) ->
{ok, author_id()} | {ok, {already_exists, author_id()}} | {error, unknown}.
insert(Name, Email) ->
Expand All @@ -42,16 +35,3 @@ get_by_email(Email) ->
-spec delete(author_id()) -> ok | {error, author_not_found | term()}.
delete(AuthorID) ->
dmt_author_database:delete(?POOL_NAME, AuthorID).

%% Optional: Extended functionality that could be exposed if needed
%% @doc Lists authors with pagination
%% -spec list(Limit :: pos_integer(), Offset :: non_neg_integer()) ->
%% {ok, [#domain_conf_v2_Author{}]} | {error, term()}.
%% list(Limit, Offset) ->
%% dmt_author_database:list(?POOL_NAME, Limit, Offset).

%% @doc Searches for authors by name or email
%% -spec search(SearchTerm :: binary(), Limit :: pos_integer()) ->
%% {ok, [#domain_conf_v2_Author{}]} | {error, term()}.
%% search(SearchTerm, Limit) ->
%% dmt_author_database:search(?POOL_NAME, SearchTerm, Limit).
9 changes: 6 additions & 3 deletions apps/dmt/src/dmt_author_database.erl
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
-module(dmt_author_database).
-typing([eqwalizer]).

-include_lib("damsel/include/dmsl_domain_conf_v2_thrift.hrl").
-include_lib("epgsql/include/epgsql.hrl").
Expand All @@ -13,7 +14,7 @@
search/3
]).

-type worker() :: dmt_database:worker().
-type worker() :: atom().
-type author_id() :: dmt_author:author_id().
-type name() :: dmt_author:name().
-type email() :: dmt_author:email().
Expand Down Expand Up @@ -181,7 +182,9 @@ is_uuid(_) ->
-spec try_string_to_uuid(uuid:uuid_string()) -> boolean().
try_string_to_uuid(UUID) ->
try uuid:string_to_uuid(UUID) of
_ -> true
_ ->
true
catch
exit:badarg -> false
exit:badarg ->
false
end.
17 changes: 1 addition & 16 deletions apps/dmt/src/dmt_author_handler.erl
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,17 @@

-include_lib("damsel/include/dmsl_domain_conf_v2_thrift.hrl").

-behaviour(woody_server_thrift_handler).

%% API
-export([handle_function/4]).

-type options() :: dmt_api_woody_utils:handler_options().

-export_type([options/0]).

-spec handle_function(woody:func(), woody:args(), woody_context:ctx(), options()) ->
{ok, woody:result()} | no_return().
handle_function(Function, Args, WoodyContext0, Options) ->
DefaultDeadline = woody_deadline:from_timeout(default_handling_timeout(Options)),
WoodyContext = dmt_api_woody_utils:ensure_woody_deadline_set(WoodyContext0, DefaultDeadline),
%% Cast: `woody:args()` is `tuple() | any()`; each `do_handle_function/4`
%% clause pattern-matches a specific arg tuple guaranteed by the thrift
%% schema. The shape is enforced by woody at deserialisation, not by
%% the type system, so we cross the trust boundary explicitly here.
do_handle_function(Function, eqwalizer:dynamic_cast(Args), WoodyContext, Options).
do_handle_function(Function, Args, WoodyContext, Options).

-spec default_handling_timeout(options()) -> timeout().
default_handling_timeout(#{default_handling_timeout := Timeout}) ->
Timeout.

-spec do_handle_function(woody:func(), eqwalizer:dynamic(tuple()), woody_context:ctx(), options()) ->
{ok, woody:result()} | no_return().
%% Implement the Create function
do_handle_function('Create', {Params}, _Context, _Options) ->
#domain_conf_v2_AuthorParams{email = Email, name = Name} = Params,
Expand Down
Loading
Loading