Merge remote-tracking branch 'github/pr/15'
diff --git a/README.md b/README.md
index 7d5cc70..5b5223f 100644
--- a/README.md
+++ b/README.md
@@ -101,6 +101,22 @@
- if the same plugin provides multiple implementations of the same service
the order is as defined in providers callback
+## decide functionality
+
+There are cases when we want to call configured providers until any of them
+would make a decission. We also would want to be able to find out if any
+decision has been made so we could call default handler. In order to be able
+to do so there is couch_epi:decide/5. Every service which uses this feature
+would get either:
+
+ - no_decision
+ - {decided, Decision :: term()}
+
+The provider module should return one of the above results. The current logic is
+to call all configured providers in order of their definition until we get
+`{decided, term()}`. If none of the providers would return this term we would
+return `no_decision`.
+
# couch_epi_plugin behaviour
The module implementing behaviour need to export following functions:
diff --git a/src/couch_epi.erl b/src/couch_epi.erl
index 69f72a2..ddb3c48 100644
--- a/src/couch_epi.erl
+++ b/src/couch_epi.erl
@@ -22,7 +22,7 @@
keys/1, subscribers/1]).
%% apply
--export([apply/5]).
+-export([apply/5, decide/5]).
-export([any/5, all/5]).
-export([is_configured/3]).
@@ -169,3 +169,10 @@
register_service(Plugin, Children) ->
couch_epi_sup:plugin_childspecs(Plugin, Children).
+
+-spec decide(Handle :: handle(), ServiceId :: atom(), Function :: atom(),
+ Args :: [term()], Opts :: apply_opts()) ->
+ no_decision | {decided, term()}.
+
+decide(Handle, ServiceId, Function, Args, Opts) when Handle /= undefined ->
+ couch_epi_functions_gen:decide(Handle, ServiceId, Function, Args, Opts).
diff --git a/src/couch_epi_functions_gen.erl b/src/couch_epi_functions_gen.erl
index 311dd62..7408593 100644
--- a/src/couch_epi_functions_gen.erl
+++ b/src/couch_epi_functions_gen.erl
@@ -22,7 +22,8 @@
-export([
apply/4,
apply/5,
- modules/3
+ modules/3,
+ decide/5
]).
-ifdef(TEST).
@@ -34,7 +35,8 @@
-record(opts, {
ignore_errors = false,
pipe = false,
- concurrent = false
+ concurrent = false,
+ interruptible = false
}).
get_handle(ServiceId) ->
@@ -51,6 +53,15 @@
Modules = providers(Handle, Function, length(Args), DispatchOpts),
dispatch(Handle, Modules, Function, Args, DispatchOpts).
+-spec decide(Handle :: atom(), ServiceId :: atom(), Function :: atom(),
+ Args :: [term()], Opts :: couch_epi:apply_opts()) ->
+ no_decision | {decided, term()}.
+
+decide(Handle, _ServiceId, Function, Args, Opts) ->
+ DispatchOpts = parse_opts([interruptible|Opts]),
+ Modules = providers(Handle, Function, length(Args), DispatchOpts),
+ dispatch(Handle, Modules, Function, Args, DispatchOpts).
+
%% ------------------------------------------------------------------
%% Codegeneration routines
%% ------------------------------------------------------------------
@@ -241,6 +252,9 @@
lists:foldl(fun(Module, Acc) ->
Handle:dispatch(Module, Function, Acc)
end, Args, Modules);
+dispatch(Handle, Modules, Function, Args,
+ #opts{interruptible = true}) ->
+ apply_while(Modules, Handle, Function, Args);
dispatch(Handle, Modules, Function, Args, #opts{} = Opts) ->
[do_dispatch(Handle, Module, Function, Args, Opts) || Module <- Modules].
@@ -258,6 +272,15 @@
do_dispatch(Handle, Module, Function, Args, #opts{}) ->
Handle:dispatch(Module, Function, Args).
+apply_while([], _Handle, _Function, _Args) ->
+ no_decision;
+apply_while([Module | Modules], Handle, Function, Args) ->
+ case Handle:dispatch(Module, Function, Args) of
+ no_decision ->
+ apply_while(Modules, Handle, Function, Args);
+ {decided, _Decission} = Result ->
+ Result
+ end.
parse_opts(Opts) ->
parse_opts(Opts, #opts{}).
@@ -268,6 +291,8 @@
parse_opts(Rest, Acc#opts{pipe = true});
parse_opts([concurrent|Rest], #opts{} = Acc) ->
parse_opts(Rest, Acc#opts{concurrent = true});
+parse_opts([interruptible|Rest], #opts{} = Acc) ->
+ parse_opts(Rest, Acc#opts{interruptible = true});
parse_opts([], Acc) ->
Acc.
@@ -326,4 +351,52 @@
ok.
+generate_module(Name, Body) ->
+ Tokens = couch_epi_codegen:scan(Body),
+ couch_epi_codegen:generate(Name, Tokens).
+
+decide_module(decide) ->
+ "
+ -export([inc/1]).
+
+ inc(A) ->
+ {decided, A + 1}.
+ ";
+decide_module(no_decision) ->
+ "
+ -export([inc/1]).
+
+ inc(_A) ->
+ no_decision.
+ ".
+
+decide_test() ->
+ ok = generate_module(decide, decide_module(decide)),
+ ok = generate_module(no_decision, decide_module(no_decision)),
+
+ DecideDef = {foo_app, [{decide, [{inc, 1}]}]},
+ NoDecissionDef = {bar_app, [{no_decision, [{inc, 1}]}]},
+
+ DecideFirstHandle = decide_first_handle,
+ ok = generate(DecideFirstHandle, [DecideDef, NoDecissionDef]),
+ ?assertMatch([decide, no_decision], DecideFirstHandle:providers(inc, 1)),
+ ?assertMatch({decided,4}, decide(DecideFirstHandle, anything, inc, [3], [])),
+
+ DecideSecondHandle = decide_second_handle,
+ ok = generate(DecideSecondHandle, [NoDecissionDef, DecideDef]),
+ ?assertMatch([no_decision, decide], DecideSecondHandle:providers(inc, 1)),
+ ?assertMatch({decided,4}, decide(DecideSecondHandle, anything, inc, [3], [])),
+
+ NoDecissionHandle = no_decision_handle,
+ ok = generate(NoDecissionHandle, [NoDecissionDef]),
+ ?assertMatch([no_decision], NoDecissionHandle:providers(inc, 1)),
+ ?assertMatch(no_decision, decide(NoDecissionHandle, anything, inc, [3], [])),
+
+ NoHandle = no_handle,
+ ok = generate(NoHandle, []),
+ ?assertMatch([], NoHandle:providers(inc, 1)),
+ ?assertMatch(no_decision, decide(NoHandle, anything, inc, [3], [])),
+
+ ok.
+
-endif.