Current section
Files
Jump to
Current section
Files
src/hecate_plugin_codegen_qry.erl
%%% @doc QRY desk templates for code generation.
%%%
%%% Templates for: list (page) endpoint, get-by-id endpoint, QRY tests.
%%% @end
-module(hecate_plugin_codegen_qry).
-export([desk/1]).
-import(hecate_plugin_codegen, [fmt/2, s/1, write_files/1,
pluralize/1, replace_src_with_test/1]).
%% ===================================================================
%% desk/1 -- QRY desk entry point
%% ===================================================================
-spec desk(map()) -> {ok, [string()]}.
desk(#{subject := Subject} = Opts) ->
OutDir = maps:get(output_dir, Opts, "src"),
TestDir = maps:get(test_dir, Opts, replace_src_with_test(OutDir)),
Type = maps:get(type, Opts, page),
Plural = pluralize(s(Subject)),
{DeskName, ApiMod, _ApiPath, Template, TestTpl} = case Type of
page ->
DN = "get_" ++ Plural ++ "_page",
AM = DN ++ "_api",
{DN, AM, "/api/" ++ Plural,
fun() -> tpl_qry_page_api(AM, Plural) end,
fun() -> tpl_qry_page_tests(AM, Plural) end};
by_id ->
DN = "get_" ++ s(Subject) ++ "_by_id",
AM = DN ++ "_api",
{DN, AM, "/api/" ++ Plural ++ "/:id",
fun() -> tpl_qry_by_id_api(AM, s(Subject)) end,
fun() -> tpl_qry_by_id_tests(AM, s(Subject)) end}
end,
DeskDir = filename:join(OutDir, DeskName),
Files = [
{filename:join(DeskDir, ApiMod ++ ".erl"), Template()},
{filename:join(TestDir, ApiMod ++ "_tests.erl"), TestTpl()}
],
write_files(Files).
%% ===================================================================
%% Templates
%% ===================================================================
tpl_qry_page_api(ApiMod, Plural) ->
fmt("%%% @doc API handler: GET /api/~s\n"
"-module(~s).\n"
"-export([init/2, routes/0]).\n"
"\n"
"routes() -> [{\"/api/~s\", ?MODULE, []}].\n"
"\n"
"init(Req0, State) ->\n"
" case cowboy_req:method(Req0) of\n"
" <<\"GET\">> -> handle_get(Req0, State);\n"
" _ -> hecate_plugin_api:method_not_allowed(Req0)\n"
" end.\n"
"\n"
"handle_get(Req0, _State) ->\n"
" QS = cowboy_req:parse_qs(Req0),\n"
" Filters = build_filters(QS, #{}),\n"
" %% TODO: Call read model query\n"
" hecate_plugin_api:json_ok(#{~s => [], filters => Filters}, Req0).\n"
"\n"
"build_filters(QS, Acc) ->\n"
" lists:foldl(fun parse_filter/2, Acc, QS).\n"
"\n"
"parse_filter({<<\"limit\">>, V}, Acc) -> maybe_int(limit, V, Acc);\n"
"parse_filter({<<\"offset\">>, V}, Acc) -> maybe_int(offset, V, Acc);\n"
"parse_filter(_, Acc) -> Acc.\n"
"\n"
"maybe_int(Key, V, Acc) ->\n"
" case catch binary_to_integer(V) of\n"
" I when is_integer(I) -> Acc#{Key => I};\n"
" _ -> Acc\n"
" end.\n",
[Plural, ApiMod, Plural, Plural]).
tpl_qry_by_id_api(ApiMod, Subject) ->
Plural = pluralize(Subject),
IdField = Subject ++ "_id",
fmt("%%% @doc API handler: GET /api/~s/:id\n"
"-module(~s).\n"
"-export([init/2, routes/0]).\n"
"\n"
"routes() -> [{\"/api/~s/:id\", ?MODULE, []}].\n"
"\n"
"init(Req0, State) ->\n"
" case cowboy_req:method(Req0) of\n"
" <<\"GET\">> -> handle_get(Req0, State);\n"
" _ -> hecate_plugin_api:method_not_allowed(Req0)\n"
" end.\n"
"\n"
"handle_get(Req0, _State) ->\n"
" Id = cowboy_req:binding(id, Req0),\n"
" %% TODO: Look up from read model\n"
" hecate_plugin_api:json_ok(#{~s => Id}, Req0).\n",
[Plural, ApiMod, Plural, IdField]).
tpl_qry_page_tests(ApiMod, Plural) ->
fmt("-module(~s_tests).\n"
"-include_lib(\"eunit/include/eunit.hrl\").\n"
"\n"
"routes_test() ->\n"
" Routes = ~s:routes(),\n"
" ?assertMatch([{\"/api/~s\", _, _}], Routes).\n",
[ApiMod, ApiMod, Plural]).
tpl_qry_by_id_tests(ApiMod, Subject) ->
Plural = pluralize(Subject),
fmt("-module(~s_tests).\n"
"-include_lib(\"eunit/include/eunit.hrl\").\n"
"\n"
"routes_test() ->\n"
" Routes = ~s:routes(),\n"
" ?assertMatch([{\"/api/~s/:id\", _, _}], Routes).\n",
[ApiMod, ApiMod, Plural]).