src/nova_plugin_handler.erl

-module(nova_plugin_handler).
-behaviour(cowboy_middleware).

-export([
         execute/2
        ]).

-include_lib("kernel/include/logger.hrl").

execute(Req = #{plugins := Plugins}, Env = #{plugin_state := pre_request}) ->
    %% This is a post plugin
    PostPlugins = proplists:get_value(post_request, Plugins, []),
    run_plugins(PostPlugins, post_request, Req, Env);
execute(Req = #{plugins := Plugins}, Env) ->
    %% Determine which pre-plugin this is
    PrePlugins = proplists:get_value(pre_request, Plugins, []),
    run_plugins(PrePlugins, pre_request, Req, Env);
execute(Req, Env) ->
    %% The router could not find any match for us
    {ok, Req, Env}.


run_plugins([], Callback, Req, Env) ->
    {ok, Req, Env#{plugin_state => Callback}};
run_plugins([{Module, Options}|Tl], Callback, Req, Env) ->
    ExtraState = maps:get(extra_state, Env, #{}),
    Req0 = Req#{extra_state => ExtraState},
    Args = case proplists:get_value(Callback, Module:module_info(exports)) of
               2 -> [Req0, Options];
               3 -> [Req0, Env, Options];
               _ -> {throw, bad_callback}
           end,
    try erlang:apply(Module, Callback, Args) of
        {ok, Req1} ->
            run_plugins(Tl, Callback, Req1, Env);
        {ok, Reply, Req1} ->
            Req2 = handle_reply(Reply, Req1),
            run_plugins(Tl, Callback, Req2, Env);
        {break, Req1} ->
            {ok, Req1};
        {break, Reply, Req1} ->
            Req2 = handle_reply(Reply, Req1),
            {ok, Req2};
        {stop, Req1} ->
            {stop, Req1};
        {stop, Reply, Req1} ->
            Req2 = handle_reply(Reply, Req1),
            {stop, Req2}
    catch
        Class:Reason:Stacktrace ->
            ?LOG_ERROR(#{msg => <<"Plugin crashed">>, class => Class, reason => Reason, stacktrace => Stacktrace}),
            Req1 = Req0#{crash_info => #{class => Class,
                                         reason => Reason,
                                         stacktrace => Stacktrace}},
            nova_router:render_status_page('_', 500, #{}, Req1, Env)
    end.

handle_reply({reply, Body}, Req) ->
    handle_reply({reply, 200, #{}, Body}, Req);
handle_reply({reply, Status, Body}, Req) ->
    handle_reply({reply, Status, #{}, Body}, Req);
handle_reply({reply, Status, Headers, Body}, Req) ->
    Req0 = cowboy_req:set_resp_headers(Headers, Req),
    Req1 = cowboy_req:set_resp_body(Body, Req0),
    Req1#{resp_status_code => Status};
handle_reply(_, Req) ->
    Req.