Packages
nova
0.1.0
0.15.1
0.14.3
0.14.1
0.13.7
0.13.1
0.13.0
0.12.1
0.12.0
0.11.0
0.10.4
0.10.3
0.10.2
0.10.1
0.10.0
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.11
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.2
0.6.1
0.6.0
0.5.2
0.5.1
0.4.4
0.4.3
0.4.2
0.4.1
0.2.2
0.1.0
0.0.1
0.0.0
Nova is a web application framework
Current section
Files
Jump to
Current section
Files
src/nova_controller.erl
%%% @author Niclas Axelsson <niclas@burbas.se>
%%% @copyright (C) 2018, Niclas Axelsson
%%% @doc
%%%
%%% @end
%%% Created : 25 Jun 2018 by Niclas Axelsson <niclas@burbas.se>
-module(nova_controller).
-export([
init/2,
terminate/3
]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Public functions %
%%%%%%%%%%%%%%%%%%%%%%%%%%%
init(Req, State = #{secure := false}) -> dispatch(Req, State);
init(Req, State = #{secure := {Mod, Func}}) ->
case Mod:Func(Req) of
true ->
dispatch(Req, State);
_ ->
Req1 = cowboy_req:reply(401, Req),
{ok, Req1, State}
end.
terminate(_Reason, _Req, _State) ->
ok.
%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Private functions %
%%%%%%%%%%%%%%%%%%%%%%%%%%%
dispatch(Req = #{method := ReqMethod},
State = #{mod := Mod, func := Func, method := Method}) when Method == '_' orelse
Method == ReqMethod ->
handle(Mod, Func, Req, State);
dispatch(Req, State) ->
Req1 = cowboy_req:reply(404, Req),
{ok, Req1, State}.
handle(Mod, Fun, Req, State) ->
try Mod:Fun(Req) of
RetObj ->
handle1(RetObj, {Mod, Fun}, Req, State)
catch
Type:Exception ->
logger:error("Controller failed with ~p:~p", [Type, Exception])
end.
handle1(RetObj, {Mod, Fun}, Req = #{method := Method}, State) ->
case RetObj of
{json, JSON} ->
EncodedJSON = jsone:encode(JSON, [undefined_as_null]),
StatusCode = case Method of
<<"POST">> -> 201;
_ -> 200
end,
Req1 = cowboy_req:reply(StatusCode, #{
<<"content-type">> => <<"application/json">>
}, EncodedJSON, Req),
{ok, Req1, State};
{json, StatusCode, Headers, JSON} ->
EncodedJSON = jsone:encode(JSON, [undefined_as_null]),
Req1 = cowboy_req:reply(StatusCode,
maps:merge(#{<<"content-type">> =>
<<"application/json">>},
Headers),
EncodedJSON,
Req),
{ok, Req1, State};
{ok, Variables} ->
%% Derive the view from module
ViewNameAtom = get_view_name(Mod),
handle_view(ViewNameAtom, Variables, #{}, Req, State);
{ok, Variables, Options} ->
View =
case maps:get(view, Options, undefined) of
undefined ->
ViewName = atom_to_list(Mod) ++ "_dtl",
list_to_atom(ViewName);
CustomView ->
CustomView
end,
handle_view(View, Variables, Options, Req, State);
{status, Status} when is_integer(Status) ->
Req1 = cowboy_req:reply(Status, #{}, Req),
{ok, Req1, State};
{status, Status, Headers} when is_integer(Status) ->
Req1 = cowboy_req:reply(Status, Headers, Req),
{ok, Req1, State};
{cowboy_req, CowboyReq} ->
{ok, CowboyReq, State};
{extern_handler, Module, Function, Payload} ->
try Module:Function(Payload, Req) of
{external_handler, _, _, _} ->
logger:error("Infinite loop detected"),
Req1 = cowboy_req:reply(500, #{}, Req),
{ok, Req1, State};
RetObject ->
handle1(RetObject, {Mod, Fun}, Req, State)
catch
_:_ ->
logger:info("External handler failed")
end;
Other ->
logger:info("Unsupported return value from controller ~p:~p/1. Returned: ~p", [Mod, Fun, Other]),
Req1 = cowboy_req:reply(500, #{}, Req),
{ok, Req1, State}
end.
handle_view(View, Variables, Options, Req, State) ->
{ok, HTML} = render_dtl(View, Variables, []),
Headers =
case maps:get(headers, Options, undefined) of
undefined ->
#{<<"content-type">> => <<"text/html">>};
UserHeaders ->
UserHeaders
end,
StatusCode = maps:get(status_code, Options, 200),
Req1 = cowboy_req:reply(StatusCode, Headers, HTML, Req),
{ok, Req1, State}.
render_dtl(View, Variables, Options) ->
case code:is_loaded(View) of
false ->
%% Cast a warning since the module could not be found
logger:warning("Could not render ~p cause it's not loaded.", [View]);
_ ->
View:render(Variables, Options)
end.
get_view_name(Mod) when is_atom(Mod) ->
StrName = get_view_name(erlang:atom_to_list(Mod)),
erlang:list_to_atom(StrName);
get_view_name([$_, $c, $o, $n, $t, $r, $o, $l, $l, $e, $r]) ->
"_dtl";
get_view_name([H|T]) ->
[H|get_view_name(T)].