%%%============================================================================ %%% Licensed under the Apache License, Version 2.0 (the "License"); %%% you may not use this file except in compliance with the License. %%% You may obtain a copy of the License at %%% %%% http://www.apache.org/licenses/LICENSE-2.0 %%% %%% Unless required by applicable law or agreed to in writing, software %%% distributed under the License is distributed on an "AS IS" BASIS, %%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %%% See the License for the specific language governing permissions and %%% limitations under the License. %%%============================================================================ %%% @private %%% @doc Provides expectation processing functions. -module(meck_expect). %% API -export_type([func_ari/0]). -export_type([expect/0]). -export([new/2]). -export([new/3]). -export([new_passthrough/1]). -export([new_dummy/2]). -export([func_ari/1]). -export([fetch_result/2]). %%%============================================================================ %%% Types %%%============================================================================ -type func_clause_spec() :: {meck_args_matcher:args_spec(), meck_ret_spec:ret_spec()}. -type func_clause() :: {meck_args_matcher:args_matcher(), meck_ret_spec:ret_spec()}. -type func_ari() :: {Func::atom(), Ari::byte()}. -opaque expect() :: {func_ari(), [func_clause()]}. %%%============================================================================ %%% API %%%============================================================================ -spec new(Func::atom(), fun() | func_clause_spec()) -> expect(). new(Func, StubFun) when is_function(StubFun) -> {arity, Arity} = erlang:fun_info(StubFun, arity), Clause = {meck_args_matcher:new(Arity), meck_ret_spec:exec(StubFun)}, {{Func, Arity}, [Clause]}; new(Func, ClauseSpecs) when is_list(ClauseSpecs) -> {Arity, Clauses} = parse_clause_specs(ClauseSpecs), {{Func, Arity}, Clauses}. -spec new(Func::atom(), meck_args_matcher:args_spec(), meck_ret_spec:ret_spec()) -> expect(). new(Func, ArgsSpec, RetSpec) -> {Ari, Clause} = parse_clause_spec({ArgsSpec, RetSpec}), {{Func, Ari}, [Clause]}. -spec new_passthrough(func_ari()) -> expect(). new_passthrough({Func, Ari}) -> {{Func, Ari}, [{meck_args_matcher:new(Ari), meck_ret_spec:passthrough()}]}. -spec new_dummy(func_ari(), meck_ret_spec:ret_spec()) -> expect(). new_dummy({Func, Ari}, RetSpec) -> {{Func, Ari}, [{meck_args_matcher:new(Ari), RetSpec}]}. -spec func_ari(expect()) -> func_ari(). func_ari({FuncAri, _Clauses}) -> FuncAri. -spec fetch_result(Args::[any()], expect()) -> {undefined, unchanged} | {meck_ret_spec:result_spec(), unchanged} | {meck_ret_spec:result_spec(), NewExpect::expect()}. fetch_result(Args, {FuncAri, Clauses}) -> case find_matching_clause(Args, Clauses) of not_found -> {undefined, unchanged}; {ArgsMatcher, RetSpec} -> case meck_ret_spec:retrieve_result(RetSpec) of {ResultSpec, unchanged} -> {ResultSpec, unchanged}; {ResultSpec, NewRetSpec} -> NewClauses = lists:keyreplace(ArgsMatcher, 1, Clauses, {ArgsMatcher, NewRetSpec}), {ResultSpec, {FuncAri, NewClauses}} end end. %%%============================================================================ %%% Internal functions %%%============================================================================ -spec parse_clause_specs([func_clause_spec()]) -> {Ari::byte(), [func_clause()]}. parse_clause_specs([ClauseSpec | Rest]) -> {Ari, Clause} = parse_clause_spec(ClauseSpec), parse_clause_specs(Rest, Ari, [Clause]). -spec parse_clause_specs([func_clause_spec()], FirstClauseAri::byte(), Clauses::[func_clause()]) -> {Ari::byte(), [func_clause()]}. parse_clause_specs([ClauseSpec | Rest], FirstClauseAri, Clauses) -> {Ari, Clause} = parse_clause_spec(ClauseSpec), case Ari of FirstClauseAri -> parse_clause_specs(Rest, FirstClauseAri, [Clause | Clauses]); _ -> erlang:error({invalid_arity, {{expected, FirstClauseAri}, {actual, Ari}, {clause, ClauseSpec}}}) end; parse_clause_specs([], FirstClauseAri, Clauses) -> {FirstClauseAri, lists:reverse(Clauses)}. -spec parse_clause_spec(func_clause_spec()) -> {Ari::byte(), func_clause()}. parse_clause_spec({ArgsSpec, RetSpec}) -> ArgsMatcher = meck_args_matcher:new(ArgsSpec), Ari = meck_args_matcher:arity(ArgsMatcher), Clause = {ArgsMatcher, RetSpec}, {Ari, Clause}. -spec find_matching_clause(Args::[any()], Defined::[func_clause()]) -> Matching::func_clause() | not_found. find_matching_clause(Args, [{ArgsMatcher, RetSpec} | Rest]) -> case meck_args_matcher:match(Args, ArgsMatcher) of true -> {ArgsMatcher, RetSpec}; _Else -> find_matching_clause(Args, Rest) end; find_matching_clause(_Args, []) -> not_found.