Current section

Files

Jump to
launchdarkly_server_sdk src ldclient_targets.erl
Raw

src/ldclient_targets.erl

%%-------------------------------------------------------------------
%% @doc Methods for evaluating targets.
%% @private
%% @end
%%-------------------------------------------------------------------
-module(ldclient_targets).
%% API
-export([eval_flag_targets/2]).
-type match_result() :: no_match | {match, non_neg_integer()}.
%% @doc Evaluate the targets of the flag against the specified context.
%%
%% If there are no matches, then return `no_match'.
%%
%% If there is a match, then return `{match, Variation}'.
%% @end
-spec eval_flag_targets(Flag :: ldclient_flag:flag(), Context :: ldclient_context:context()) -> match_result().
eval_flag_targets(
#{targets := [], contextTargets := []} = _Flag,
_Context
) ->
%% There are no targets to evaluate, so there is no match.
no_match;
eval_flag_targets(
#{targets := UserTargets, contextTargets := []} = _Flag,
Context
) ->
%% The context targets are empty, so evaluate user targets only.
check_user_targets(UserTargets, Context);
eval_flag_targets(
#{targets := UserTargets, contextTargets := ContextTargets} = _Flag,
Context
) ->
check_context_targets(ContextTargets, UserTargets, Context).
%%===================================================================
%% Internal functions
%%===================================================================
%% @doc Check context targets for any matches.
%%
%% This logic is independent of user targets checking because context targets have special logic
%% for when their kind is "user". The values list will be empty, and instead the user targets should
%% be checked for a target of the same variation. This allows for user targets to remain compatible
%% with existing SDKs and also not need to be duplicated for context targets.
%% @end
-spec check_context_targets(
ContextTargets :: [ldclient_flag:target()],
UserTargets :: [ldclient_flag:target()],
Context :: ldclient_context:context()
) -> match_result().
check_context_targets([], _UserTargets, _Context) -> no_match;
check_context_targets([#{contextKind := ContextKind, variation := Variation} = ContextTarget|Rest] = _Target, UserTargets, Context) ->
Result = case ContextKind of
%% In the case the context targets are for a user kind, then we are just using it for ordering, and we want
%% To check a user target with the same variation as the context target.
<<"user">> -> eval_target(find_user_target_for_variation(Variation, UserTargets), Context);
%% This was not a user kind, so just evaluate the target.
_ -> eval_target(ContextTarget, Context)
end,
%% If there is no match, then continue checking, if there is a match, then stop early and return it.
case Result of
no_match ->check_context_targets(Rest, UserTargets, Context);
_ -> Result
end.
%% @doc Check the user targets of the flag to see if there are any matches.
%%
%% @end
-spec check_user_targets(UserTargets :: [ldclient_flag:target()], Context :: ldclient_context:context()) -> match_result().
check_user_targets([], _Context) ->
no_match;
check_user_targets([Target|Rest] = _Targets, Context) ->
case eval_target(Target, Context) of
%% There was not a match, so continue checking.
no_match -> check_user_targets(Rest, Context);
%% There was a match, so return it and stop checking.
Match -> Match
end.
%% @doc Find a user target associated with a given variation.
%%
%% If there is no user target with a matching variation, then null will be returned.
%%
%% If there is a user target with a matching variation, then that target will be returned.
%% @end
-spec find_user_target_for_variation(
Variation :: non_neg_integer(),
UserTargets :: [ldclient_flag:target()]
) -> ldclient_flag:target() | null.
find_user_target_for_variation(Variation, UserTargets) ->
find_user_target_for_variation_result(lists:search(fun(Target) ->
#{variation := TargetVariation} = Target,
TargetVariation =:= Variation
end, UserTargets)).
%% @doc Convenience method to convert a lists:search result into either null or a target().
%%
%% @end
-spec find_user_target_for_variation_result(Result :: false | {value, ldclient_flag:target()}) -> null | ldclient_flag:target().
find_user_target_for_variation_result(false) -> null;
find_user_target_for_variation_result({value, Value}) -> Value.
%% @doc Evaluate a single target against a context.
%%
%% @end
-spec eval_target(Target :: null | ldclient_flag:target(), Context :: ldclient_context:context()) -> match_result().
eval_target(null, _Context) -> no_match;
eval_target(
#{contextKind := ContextKind, values := Values, variation := Variation} = _Target,
Context
) ->
Key = ldclient_context:get_key(ContextKind, Context),
Match = eval_target_key(Key, Values),
eval_target_result(Match, Variation).
%% @doc Convenience method to search for a key in a list of values. If the key is null, then it returns
%% false instead of performing the search.
%% @end
-spec eval_target_key(Key :: null | binary(), Values :: [binary()]) -> boolean().
eval_target_key(null = _Key, _Values) -> false;
eval_target_key(Key, Values) -> lists:member(Key, Values).
%% @doc Convenience method for turning a search result, and a variation into a match_result().
%%
%% @end
-spec eval_target_result(Match :: boolean(), Variation :: pos_integer()) -> match_result().
eval_target_result(false = _Match, _Variation) -> no_match;
eval_target_result(true = _Match, Variation) -> {match, Variation}.