1%%%============================================================================
2%%% Licensed under the Apache License, Version 2.0 (the "License");
3%%% you may not use this file except in compliance with the License.
4%%% You may obtain a copy of the License at
5%%%
6%%% http://www.apache.org/licenses/LICENSE-2.0
7%%%
8%%% Unless required by applicable law or agreed to in writing, software
9%%% distributed under the License is distributed on an "AS IS" BASIS,
10%%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11%%% See the License for the specific language governing permissions and
12%%% limitations under the License.
13%%%============================================================================
14
15%%% @private
16%%% @doc Provides expectation processing functions.
17-module(meck_expect).
18
19%% API
20-export_type([func_ari/0]).
21-export_type([expect/0]).
22
23-export([new/2]).
24-export([new/3]).
25-export([new_passthrough/1]).
26-export([new_dummy/2]).
27-export([func_ari/1]).
28-export([is_passthrough/1]).
29-export([fetch_result/2]).
30
31%%%============================================================================
32%%% Types
33%%%============================================================================
34
35-type func_clause_spec() :: {meck_args_matcher:args_spec(),
36                             meck_ret_spec:ret_spec()}.
37
38-type func_clause() :: {meck_args_matcher:args_matcher(),
39                        meck_ret_spec:ret_spec()}.
40
41-type func_ari() :: {Func::atom(), Ari::byte()}.
42-opaque expect() :: {func_ari(), [func_clause()]}.
43
44%%%============================================================================
45%%% API
46%%%============================================================================
47
48-spec new(Func::atom(), fun() | func_clause_spec()) -> expect().
49new(Func, StubFun) when is_function(StubFun) ->
50    {arity, Arity} = erlang:fun_info(StubFun, arity),
51    Clause = {meck_args_matcher:new(Arity), meck_ret_spec:exec(StubFun)},
52    {{Func, Arity}, [Clause]};
53new(Func, ClauseSpecs) when is_list(ClauseSpecs) ->
54    {Arity, Clauses} = parse_clause_specs(ClauseSpecs),
55    {{Func, Arity}, Clauses}.
56
57-spec new(Func::atom(),
58          meck_args_matcher:args_spec(),
59          meck_ret_spec:ret_spec()) ->
60        expect().
61new(Func, ArgsSpec, RetSpec) ->
62    {Ari, Clause} = parse_clause_spec({ArgsSpec, RetSpec}),
63    {{Func, Ari}, [Clause]}.
64
65-spec new_passthrough(func_ari()) -> expect().
66new_passthrough({Func, Ari}) ->
67    {{Func, Ari}, [{meck_args_matcher:new(Ari), meck_ret_spec:passthrough()}]}.
68
69-spec new_dummy(func_ari(), meck_ret_spec:ret_spec()) -> expect().
70new_dummy({Func, Ari}, RetSpec) ->
71    {{Func, Ari}, [{meck_args_matcher:new(Ari), RetSpec}]}.
72
73-spec func_ari(expect()) -> func_ari().
74func_ari({FuncAri, _Clauses}) ->
75    FuncAri.
76
77-spec is_passthrough(expect()) -> boolean().
78is_passthrough({_FuncAri, [{_Matcher, RetSpec}]}) ->
79    meck_ret_spec:is_passthrough(RetSpec);
80is_passthrough(_) ->
81    false.
82
83-spec fetch_result(Args::[any()], expect()) ->
84        {undefined, unchanged} |
85        {meck_ret_spec:result_spec(), unchanged} |
86        {meck_ret_spec:result_spec(), NewExpect::expect()}.
87fetch_result(Args, {FuncAri, Clauses}) ->
88    case find_matching_clause(Args, Clauses) of
89        not_found ->
90            {undefined, unchanged};
91        {ArgsMatcher, RetSpec} ->
92            case meck_ret_spec:retrieve_result(RetSpec) of
93                {ResultSpec, unchanged} ->
94                    {ResultSpec, unchanged};
95                {ResultSpec, NewRetSpec} ->
96                    NewClauses = lists:keyreplace(ArgsMatcher, 1, Clauses,
97                                                  {ArgsMatcher, NewRetSpec}),
98                    {ResultSpec, {FuncAri, NewClauses}}
99            end
100    end.
101
102%%%============================================================================
103%%% Internal functions
104%%%============================================================================
105
106-spec parse_clause_specs([func_clause_spec()]) -> {Ari::byte(), [func_clause()]}.
107parse_clause_specs([ClauseSpec | Rest]) ->
108    {Ari, Clause} = parse_clause_spec(ClauseSpec),
109    parse_clause_specs(Rest, Ari, [Clause]).
110
111-spec parse_clause_specs([func_clause_spec()],
112                         FirstClauseAri::byte(),
113                         Clauses::[func_clause()]) ->
114        {Ari::byte(), [func_clause()]}.
115parse_clause_specs([ClauseSpec | Rest], FirstClauseAri, Clauses) ->
116    {Ari, Clause} = parse_clause_spec(ClauseSpec),
117    case Ari of
118        FirstClauseAri ->
119            parse_clause_specs(Rest, FirstClauseAri, [Clause | Clauses]);
120        _ ->
121            erlang:error({invalid_arity, {{expected, FirstClauseAri},
122                                          {actual, Ari},
123                                          {clause, ClauseSpec}}})
124    end;
125parse_clause_specs([], FirstClauseAri, Clauses) ->
126    {FirstClauseAri, lists:reverse(Clauses)}.
127
128-spec parse_clause_spec(func_clause_spec()) ->
129        {Ari::byte(), func_clause()}.
130parse_clause_spec({ArgsSpec, RetSpec}) ->
131    ArgsMatcher = meck_args_matcher:new(ArgsSpec),
132    Ari = meck_args_matcher:arity(ArgsMatcher),
133    Clause = {ArgsMatcher, RetSpec},
134    {Ari, Clause}.
135
136-spec find_matching_clause(Args::[any()], Defined::[func_clause()]) ->
137        Matching::func_clause() | not_found.
138find_matching_clause(Args, [{ArgsMatcher, RetSpec} | Rest]) ->
139    case meck_args_matcher:match(Args, ArgsMatcher) of
140        true ->
141            {ArgsMatcher, RetSpec};
142        _Else ->
143            find_matching_clause(Args, Rest)
144    end;
145find_matching_clause(_Args, []) ->
146    not_found.
147