-module(nine_cowboy). -export([generate/2]). generate(ModuleName, Routes) -> [{attribute, 0, module, ModuleName}, {attribute, 0, behavior, cowboy_middleware}, {attribute, 0, export, [{execute, 2}]}, erl_syntax:revert(codegen_execute()), erl_syntax:revert(codegen_handle_route(Routes))]. codegen_execute() -> erl_syntax:function( erl_syntax:atom("execute"), [erl_syntax:clause( [erl_syntax:variable("Req"), erl_syntax:variable("Env")], none, [codegen_get_path(), codegen_get_method(), codegen_try_handle_route()] )] ). %% TODO: Change to nine_cowboy_util:get_path %% Where get_path will get path and split on / codegen_get_path() -> erl_syntax:match_expr( erl_syntax:variable("Path"), erl_syntax:application( erl_syntax:atom("nine_cowboy_util"), erl_syntax:atom("get_path"), [erl_syntax:variable("Req")] ) ). codegen_get_method() -> erl_syntax:match_expr( erl_syntax:variable("Method"), erl_syntax:application( erl_syntax:atom("cowboy_req"), erl_syntax:atom("method"), [erl_syntax:variable("Req")] ) ). codegen_try_handle_route() -> erl_syntax:try_expr( [codegen_handle_route_call()], [ erl_syntax:clause( [erl_syntax:map_expr([ erl_syntax:map_field_exact( erl_syntax:atom("resp"), erl_syntax:variable("Resp") ), erl_syntax:map_field_exact( erl_syntax:atom("upgrade"), erl_syntax:tuple([ erl_syntax:variable("UpgradeModule"), erl_syntax:variable("UpgradeHandler"), erl_syntax:variable("UpgradeHandlerOpts") ]) ), erl_syntax:map_field_exact( erl_syntax:atom("state"), erl_syntax:variable("State") ) ])], [], [erl_syntax:application( erl_syntax:variable("UpgradeModule"), erl_syntax:atom("upgrade"), [erl_syntax:variable("Resp"), erl_syntax:variable("Env"), erl_syntax:variable("UpgradeHandler"), erl_syntax:variable("State"), erl_syntax:variable("UpgradeHandlerOpts")] )] ), erl_syntax:clause( [erl_syntax:map_expr([ erl_syntax:map_field_exact( erl_syntax:atom("resp"), erl_syntax:variable("Resp") ), erl_syntax:map_field_exact( erl_syntax:atom("upgrade"), erl_syntax:tuple([ erl_syntax:variable("UpgradeModule"), erl_syntax:variable("UpgradeHandler"), erl_syntax:variable("UpgradeHandlerOpts") ]) ) ])], [], [erl_syntax:application( erl_syntax:variable("UpgradeModule"), erl_syntax:atom("upgrade"), [erl_syntax:variable("Resp"), erl_syntax:variable("Env"), erl_syntax:variable("UpgradeHandler"), erl_syntax:map_expr([]), erl_syntax:variable("UpgradeHandlerOpts")] )] ), erl_syntax:clause( [erl_syntax:map_expr([ erl_syntax:map_field_exact( erl_syntax:atom("resp"), erl_syntax:variable("Resp") ), erl_syntax:map_field_exact( erl_syntax:atom("upgrade"), erl_syntax:tuple([ erl_syntax:variable("UpgradeModule"), erl_syntax:variable("UpgradeHandler") ]) ), erl_syntax:map_field_exact( erl_syntax:atom("state"), erl_syntax:variable("State") ) ])], [], [erl_syntax:application( erl_syntax:variable("UpgradeModule"), erl_syntax:atom("upgrade"), [erl_syntax:variable("Resp"), erl_syntax:variable("Env"), erl_syntax:variable("UpgradeHandler"), erl_syntax:variable("State")] )] ), erl_syntax:clause( [erl_syntax:map_expr([ erl_syntax:map_field_exact( erl_syntax:atom("resp"), erl_syntax:variable("Resp") ), erl_syntax:map_field_exact( erl_syntax:atom("upgrade"), erl_syntax:tuple([ erl_syntax:variable("UpgradeModule"), erl_syntax:variable("UpgradeHandler") ]) ) ])], [], [erl_syntax:application( erl_syntax:variable("UpgradeModule"), erl_syntax:atom("upgrade"), [erl_syntax:variable("Resp"), erl_syntax:variable("Env"), erl_syntax:variable("UpgradeHandler"), erl_syntax:map_expr([])] )] ), erl_syntax:clause( [erl_syntax:map_expr([ erl_syntax:map_field_exact( erl_syntax:atom("resp"), erl_syntax:variable("Resp") ) ])], [], [codegen_tuple_response()] ) ], [erl_syntax:clause( [erl_syntax:tuple([ erl_syntax:variable("Class"), erl_syntax:variable("Reason"), erl_syntax:variable("Stacktrace") ])], [], [erl_syntax:application( erl_syntax:atom("erlang"), erl_syntax:atom("raise"), [erl_syntax:variable("Class"), erl_syntax:variable("Reason"), erl_syntax:variable("Stacktrace")] )] )] ). codegen_handle_route_call() -> erl_syntax:application( erl_syntax:atom("handle_route"), [erl_syntax:map_expr([ erl_syntax:map_field_assoc( erl_syntax:atom("path"), erl_syntax:variable("Path") ), erl_syntax:map_field_assoc( erl_syntax:atom("method"), erl_syntax:variable("Method") ), erl_syntax:map_field_assoc( erl_syntax:atom("req"), erl_syntax:variable("Req") ) ])] ). codegen_tuple_response() -> erl_syntax:tuple([ erl_syntax:atom("ok"), erl_syntax:variable("Resp"), erl_syntax:map_expr( erl_syntax:variable("Env"), [erl_syntax:map_field_assoc( erl_syntax:atom("result"), erl_syntax:atom("ok") )] ) ]). codegen_handle_route(Routes) -> erl_syntax:function( erl_syntax:atom("handle_route"), codegen_routes(Routes) ). codegen_routes(Routes) -> lists:map(fun codegen_route/1, Routes). codegen_route({Path, Method, Handlers}) -> TranslatedPath = translate_path(Path), Params = filter_path_params(TranslatedPath), erl_syntax:clause( [codegen_route_pattern(TranslatedPath, Method)], none, [codegen_path_params(Params) | codegen_handlers(Handlers)] %% codegen handler and middleware ). codegen_route_pattern(Path, Method) -> erl_syntax:match_expr( erl_syntax:map_expr([ erl_syntax:map_field_exact( erl_syntax:atom("path"), codegen_path(Path) ), erl_syntax:map_field_exact( erl_syntax:atom("method"), codegen_method(Method) ) ]), erl_syntax:variable("Context0") ). codegen_method(<<"_">>) -> erl_syntax:variable("_Method"); codegen_method(Method) -> binary_syntax(binary_to_list(Method)). translate_path(Path) -> SplitPath = string:split(Path, "/", all), lists:map(fun translate_path_param/1, SplitPath). translate_path_param(<<"*">>) -> all; translate_path_param(<<"*", Name/binary>>) -> {all, binary_to_atom(Name)}; translate_path_param(S) -> case string:split(S, ":") of [_] -> S; [<<>>, Suffix] -> binary_to_atom(Suffix); [Prefix, Suffix] -> {Prefix, binary_to_atom(Suffix)} end. -spec filter_path_params(list()) -> list(). filter_path_params(Path) -> lists:reverse( lists:foldl(fun(E, Acc) -> case E of all -> Acc; {all, Var} -> [Var | Acc]; {_Prefix, Suffix} -> [Suffix | Acc]; E2 when is_atom(E2) -> [E2 | Acc]; _ -> Acc end end, [], Path)). codegen_path(PathElements) -> do_codegen_path(lists:reverse(PathElements)). do_codegen_path([all | Rest]) -> do_codegen_path(erl_syntax:variable("_"), Rest); do_codegen_path([{all, Var} | Rest]) -> do_codegen_path( erl_syntax:variable(string:titlecase(atom_to_list(Var))), Rest ); do_codegen_path(PathElements) -> do_codegen_path(erl_syntax:nil(), PathElements). do_codegen_path(Init, PathElements) -> lists:foldl(fun (PathElement, Acc) -> erl_syntax:cons( codegen_path_element(PathElement), Acc ) end, Init, PathElements). codegen_path_element(<<>>) -> erl_syntax:binary([]); codegen_path_element({B, Var}) when is_binary(B) andalso is_atom(Var) -> erl_syntax:binary([ erl_syntax:binary_field(erl_syntax:string(binary_to_list(B))), erl_syntax:binary_field(erl_syntax:variable(string:titlecase(atom_to_list(Var)))) ]); codegen_path_element(PathElement) when is_atom(PathElement) -> erl_syntax:variable(string:titlecase(atom_to_list(PathElement))); codegen_path_element(PathElement) when is_binary(PathElement) -> binary_syntax(binary_to_list(PathElement)). codegen_path_params(Params) -> erl_syntax:match_expr( erl_syntax:variable("Context1"), erl_syntax:map_expr( erl_syntax:variable("Context0"), [erl_syntax:map_field_assoc( erl_syntax:atom("params"), erl_syntax:map_expr( lists:map(fun codegen_path_param/1, Params) ) )] ) ). codegen_path_param(Param) -> erl_syntax:map_field_assoc( erl_syntax:atom(atom_to_list(Param)), erl_syntax:variable(string:titlecase(atom_to_list(Param))) ). codegen_handlers(Handlers) -> {EnumHandlersTemp, _} = enumerate_handlers(Handlers), [{InitHandler, InitN} | EnumHandlers] = EnumHandlersTemp, lists:foldl( fun codegen_handler_case/2, [codegen_handler_call(InitHandler, InitN)], EnumHandlers ). codegen_handler_case({{HandlerModule, HandlerFunction}, N}, Acc) -> [erl_syntax:case_expr( codegen_handler_call({HandlerModule, HandlerFunction}, N), [erl_syntax:clause( [erl_syntax:match_expr( erl_syntax:map_expr([ erl_syntax:map_field_exact( erl_syntax:atom("resp"), erl_syntax:variable("_Resp") ) ]), erl_syntax:variable("Context" ++ integer_to_list(N + 1)))], [], [erl_syntax:variable("Context" ++ integer_to_list(N + 1))] ), erl_syntax:clause( [erl_syntax:variable("Context" ++ integer_to_list(N + 1))], [], Acc ) ])]. codegen_handler_call({HandlerModule, HandlerFunction}, N) -> erl_syntax:application( erl_syntax:atom(atom_to_list(HandlerModule)), erl_syntax:atom(atom_to_list(HandlerFunction)), [erl_syntax:variable("Context" ++ integer_to_list(N))] ). enumerate_handlers(Handlers) -> lists:foldl(fun (Handler, {Acc, Count}) -> {[{Handler, Count} | Acc], Count + 1} end, {[], 1}, Handlers). binary_syntax(S) -> erl_syntax:binary([ erl_syntax:binary_field(erl_syntax:string(S)) ]).