Current section
Files
Jump to
Current section
Files
src/evoq_middleware.erl
%% @doc Middleware behavior for command dispatch pipeline.
%%
%% Middleware can intercept commands at three stages:
%% - before_dispatch: Before command reaches aggregate
%% - after_dispatch: After successful command execution
%% - after_failure: After command execution fails
%%
%% Middleware can:
%% - Add data to pipeline assigns
%% - Halt the pipeline (prevents further processing)
%% - Modify the response
%%
%% @author rgfaber
-module(evoq_middleware).
-include("evoq.hrl").
%% Behavior callbacks
-callback before_dispatch(Pipeline :: #evoq_pipeline{}) -> #evoq_pipeline{}.
-callback after_dispatch(Pipeline :: #evoq_pipeline{}) -> #evoq_pipeline{}.
-callback after_failure(Pipeline :: #evoq_pipeline{}) -> #evoq_pipeline{}.
-optional_callbacks([before_dispatch/1, after_dispatch/1, after_failure/1]).
%% API
-export([chain/3]).
-export([assign/3, get_assign/2, get_assign/3]).
-export([halt/1, halted/1]).
-export([respond/2, get_response/1]).
%%====================================================================
%% API
%%====================================================================
%% @doc Chain a pipeline through a list of middleware modules.
-spec chain(#evoq_pipeline{}, atom(), [atom()]) -> #evoq_pipeline{}.
chain(Pipeline, _Stage, []) ->
Pipeline;
chain(#evoq_pipeline{halted = true} = Pipeline, _Stage, _Middleware) ->
Pipeline;
chain(Pipeline, Stage, [Module | Rest]) ->
NewPipeline = case erlang:function_exported(Module, Stage, 1) of
true -> Module:Stage(Pipeline);
false -> Pipeline
end,
chain(NewPipeline, Stage, Rest).
%% @doc Assign a value to the pipeline.
-spec assign(atom(), term(), #evoq_pipeline{}) -> #evoq_pipeline{}.
assign(Key, Value, #evoq_pipeline{assigns = Assigns} = Pipeline) ->
Pipeline#evoq_pipeline{assigns = Assigns#{Key => Value}}.
%% @doc Get an assigned value from the pipeline.
-spec get_assign(atom(), #evoq_pipeline{}) -> term() | undefined.
get_assign(Key, Pipeline) ->
get_assign(Key, Pipeline, undefined).
%% @doc Get an assigned value with a default.
-spec get_assign(atom(), #evoq_pipeline{}, term()) -> term().
get_assign(Key, #evoq_pipeline{assigns = Assigns}, Default) ->
maps:get(Key, Assigns, Default).
%% @doc Halt the pipeline.
-spec halt(#evoq_pipeline{}) -> #evoq_pipeline{}.
halt(Pipeline) ->
Pipeline#evoq_pipeline{halted = true}.
%% @doc Check if the pipeline is halted.
-spec halted(#evoq_pipeline{}) -> boolean().
halted(#evoq_pipeline{halted = Halted}) ->
Halted.
%% @doc Set the pipeline response.
-spec respond(term(), #evoq_pipeline{}) -> #evoq_pipeline{}.
respond(Response, Pipeline) ->
Pipeline#evoq_pipeline{response = Response}.
%% @doc Get the pipeline response.
-spec get_response(#evoq_pipeline{}) -> term().
get_response(#evoq_pipeline{response = Response}) ->
Response.