-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))
]).