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.