Current section

Files

Jump to
launchdarkly_server_sdk src ldclient_flagbuilder.erl
Raw

src/ldclient_flagbuilder.erl

%%-------------------------------------------------------------------
%% @doc Flagbuilder
%%
%% @end
%%-------------------------------------------------------------------
-module(ldclient_flagbuilder).
% Internal functions
-export([new/1, key/1, build/2]).
% Public API
-export([
boolean_flag/1,
on/2,
off_variation/2,
fallthrough_variation/2,
variations/2,
variation_for_all/2,
value_for_all/2,
variation_for_context/4,
if_match/3,
if_not_match/3,
and_match/3,
and_not_match/3,
then_return/2,
clear_rules/1,
clear_targets/1,
if_match/4,
if_not_match/4,
and_match/4,
and_not_match/4
]).
-export_type([flag_builder/0, flag_rule_builder/0]).
-opaque flag_builder() :: #{
key => binary(),
on => boolean(),
variations => [ldclient_flag:variation_value()],
off_variation => non_neg_integer(),
fallthrough_variation => non_neg_integer(),
rules => [ldclient_rule:rule()],
context_targets => builder_targets()
}. %% A builder for feature flag configurations to be used with {@link ldclient_testdata}.
-type builder_targets() :: #{
Variation :: non_neg_integer() => #{
ContextKind :: ldclient_context:kind_value() => sets:set()
}
}. %% Representation of targets used by the flag builder.
%% In the LaunchDarkly model, a flag can have any number of rules, and a rule can have any number of
%% clauses. A clause is an individual test such as "name is 'X'". A rule matches a user if all of the
%% rule's clauses match the user.
%%
%% To start defining a rule, use either {@link if_match/4} or {@link if_not_match/4}.
%% This defines the first clause for the rule.
%% Optionally, you may add more clauses with {@link and_match/4} or {@link and_not_match/4} .
%% Finally, call {@link then_return/2} to finish defining the rule.
-opaque flag_rule_builder() :: #{
variation => non_neg_integer(),
clauses => [ldclient_clause:clause()],
flag => flag_builder()
}. %% A builder for feature flag rules to be used with {@link flag_builder()}.
-ifdef(TEST).
-compile(export_all).
-endif.
%% @doc Create a new flag builder.
%%
%% This method is used by other SDK modules, but should not be used by non-SDK code.
%% @private
%% @end
-spec new(FlagName :: binary() | string()) -> flag_builder().
new(FlagName) when is_list(FlagName) ->
new(list_to_binary(FlagName));
new(FlagName) -> #{
key => FlagName,
on => true,
fallthrough_variation => 0,
off_variation => 1,
variations => [true, false]
}.
%% @doc Build a flag from a flag builder.
%%
%% This method is used by other SDK modules, but should not be used by non-SDK code.
%% @private
%% @end
-spec build(Flag :: flag_builder(), Version :: non_neg_integer()) -> ldclient_flag:flag().
build(Flag = #{ key := Key,
on := On,
variations := Variations,
off_variation := OffVariation,
fallthrough_variation := FallthroughVariation }, Version) ->
Rules = maps:get(rules, Flag, []),
{ContextTargets, UserTargets} = build_targets(maps:get(context_targets, Flag, #{})),
#{
key => Key,
version => Version,
on => On,
variations => Variations,
offVariation => OffVariation,
fallthrough => FallthroughVariation,
trackEvents => false,
trackEventsFallthrough => false,
deleted => false,
debugEventsUntilDate => null,
prerequisites => [],
salt => <<"salt">>,
rules => Rules,
contextTargets => ContextTargets,
targets => UserTargets
}.
%% @doc Set the key on the flag builder.
%%
%% This method is used by other SDK modules, but should not be used by non-SDK code.
%% @private
%% @end
-spec key(FlagBuilder :: flag_builder()) -> binary().
key(#{key := FlagName}) ->
FlagName.
%% @doc Sets targeting to be on or off for this flag.
%%
%% The effect of this depends on the rest of the flag configuration, just as it does on the
%% real LaunchDarkly dashboard. In the default configuration that you get from calling
%% {@link ldclient_testdata:flag/2} with a new flag key, the flag will return `false'
%% whenever targeting is off, and `true' when targeting is on.
%%
%% @param IsOn true if targeting should be on
%% @param FlagBuilder the flag builder to modify
%% @returns the modified builder
%%
%% @end
-spec on(IsOn :: boolean(), FlagBuilder :: flag_builder()) -> flag_builder().
on(IsOn, FlagBuilder) ->
FlagBuilder#{on := IsOn}.
-spec is_boolean_flag(FlagBuilder :: flag_builder()) -> boolean().
is_boolean_flag(#{ variations := [true, false]}) -> true;
is_boolean_flag(_) -> false.
%% @doc Removes any existing rules from the flag.
%%
%% This undoes the effect of {@link if_match/3} and {@link if_not_match/3}.
%%
%% @param FlagBuilder the flag builder to modify
%% @returns the modified builder
%% @end
-spec clear_rules(FlagBuilder :: flag_builder()) -> flag_builder().
clear_rules(FlagBuilder) ->
maps:remove(rules, FlagBuilder).
%% @doc Removes any existing targets from the flag.
%%
%% This undoes the effect of {@link variation_for_context/4}.
%%
%% @param FlagBuilder the flag builder to modify
%% @returns the modified builder
%% @end
-spec clear_targets(FlagBuilder :: flag_builder()) -> flag_builder().
clear_targets(FlagBuilder) ->
maps:remove(context_targets, FlagBuilder).
%% @doc A shortcut for setting the flag to use the standard boolean configuration.
%%
%% This is the default for all new flags created with {@link ldclient_testdata:flag/2}.
%% The flag will have two variations, `true' and `false' (in that order); it will return
%% `false' whenever targeting is off, and `true' when targeting is on if no other
%% settings specify otherwise.
%%
%% @param FlagBuilder the flag builder to modify
%% @returns the modified builder
%% @end
-spec boolean_flag(FlagBuilder :: flag_builder()) -> flag_builder().
boolean_flag(FlagBuilder) ->
case is_boolean_flag(FlagBuilder) of
true -> FlagBuilder;
false -> fallthrough_variation(0,
off_variation(1,
variations([true, false],
FlagBuilder)))
end.
-spec variation_for_boolean(Variation :: boolean()) -> non_neg_integer().
variation_for_boolean(true) -> 0;
variation_for_boolean(false) -> 1.
-type variation() :: boolean() | non_neg_integer().
%% @doc Specifies the off variation for a flag.
%%
%% The off variation is the value that is returned whenever targeting is off
%%
%% If the flag was previously configured with other variations and a boolean Variation is specified,
%% this also changes the FlagBuilder to a boolean flag.
%%
%% @param Variation `true', `false', or the index of the desired variation to return: 0 for the first, 1 for the second, etc.
%% @param FlagBuilder the flag builder to modify
%% @returns the modified builder
%% @end
-spec off_variation(Variation :: variation(), FlagBuilder :: flag_builder()) -> flag_builder().
off_variation(Variation, FlagBuilder) when is_boolean(Variation) ->
off_variation(variation_for_boolean(Variation), boolean_flag(FlagBuilder));
off_variation(Variation, FlagBuilder) when is_integer(Variation) ->
FlagBuilder#{off_variation := Variation}.
%% @doc Specifies the fallthrough variation for a flag.
%%
%% The fallthrough is the value that is returned if targeting is on
%% and the user was not matched by a more specific target or rule.
%%
%% If the flag was previously configured with other variations and a boolean variation is specified,
%% this also changes the flagbuilder to a boolean flag.
%%
%% @param Variation `true', `false', or the index of the desired variation to return: 0 for the first, 1 for the second, etc.
%% @param FlagBuilder the flag builder to modify
%% @returns the modified builder
%% @end
-spec fallthrough_variation(Variation :: variation(), FlagBuilder :: flag_builder()) -> flag_builder().
fallthrough_variation(Variation, FlagBuilder) when is_boolean(Variation) ->
fallthrough_variation(variation_for_boolean(Variation), boolean_flag(FlagBuilder));
fallthrough_variation(Variation, FlagBuilder) when is_integer(Variation) ->
FlagBuilder#{fallthrough_variation := Variation}.
%% @doc Sets the flag to always return the specified variation value for all contexts.
%%
%% The value may be of any JSON type.
%% This method changes the flag to have only a single variation, which is this value,
%% and to return the same variation regardless of whether targeting is on or off.
%% Any existing targets or rules are removed.
%%
%% @param Value the desired value to be returned for all contexts
%% @param FlagBuilder the flag builder to modify
%% @returns the modified builder
%% @end
-spec variations(Values :: [ldclient_flag:variation_value()], FlagBuilder :: flag_builder()) -> flag_builder().
variations(Values, FlagBuilder) ->
FlagBuilder#{variations := Values}.
%% @doc Sets the flag to always return the specified variation for all contexts.
%%
%% The variation is set, targeting is switched on, and any existing targets or rules are removed.
%% The fallthrough variation is set to the specified value.
%% The off variation is left unchanged.
%%
%% If the flag was previously configured with other variations and a boolean variation is specified,
%% this also changes the flagbuilder to a boolean flag.
%%
%% @param Variation `true', `false', or the index of the desired variation to return: 0 for the first, 1 for the second, etc.
%% @param FlagBuilder the flag builder to modify
%% @returns the modified builder
%% @end
-spec variation_for_all(Variation :: variation(), FlagBuilder :: flag_builder()) -> flag_builder().
variation_for_all(Variation, FlagBuilder) when is_boolean(Variation) ->
variation_for_all(variation_for_boolean(Variation), boolean_flag(FlagBuilder));
variation_for_all(Variation, FlagBuilder) when is_integer(Variation) ->
Fallthrough = fallthrough_variation(Variation, FlagBuilder),
NoTargets = clear_targets(Fallthrough),
NoRules = clear_rules(NoTargets),
on(true, NoRules).
%% @doc Sets the flag to always return the specified variation value for all contexts.
%%
%% The value may be of any JSON type, as defined by }. This method changes the
%% flag to have only a single variation, which is this value, and to return the same
%% variation regardless of whether targeting is on or off. Any existing targets or rules
%% are removed.
%%
%% @param Value the desired value to be returned for all contexts
%% @param FlagBuilder the flag builder to modify
%% @returns the modified builder
%% @end
-spec value_for_all(Value :: term(), FlagBuilder :: flag_builder()) -> flag_builder().
value_for_all(Value, FlagBuilder) ->
variation_for_all(0, variations([Value], FlagBuilder)).
%% @doc Sets the flag to return the specified variation for a specific context kind and key when
%% targeting is on.
%%
%% This has no effect when targeting is turned off for the flag.
%%
%% If the flag was previously configured with other variations and a boolean variation is specified,
%% this also changes the flagbuilder to a boolean flag.
%%
%% @param Variation `true', `false', or the index of the desired variation to return: 0 for the first, 1 for the second, etc.
%% @param ContextKind the kind of context to target
%% @param ContextKey the key of the context to target
%% @param FlagBuilder the flag builder to modify
%% @returns the modified builder
%% @end
-spec variation_for_context(
Variation :: variation(),
ContextKind :: ldclient_context:kind_value(),
ContextKey :: binary(),
FlagBuilder :: flag_builder()
) -> flag_builder().
variation_for_context(Variation, ContextKind, ContextKey, FlagBuilder) when is_boolean(Variation) ->
variation_for_context(variation_for_boolean(Variation), ContextKind, ContextKey, FlagBuilder);
variation_for_context(Variation, ContextKind, ContextKey, FlagBuilder) ->
ContextTargets = maps:get(context_targets, FlagBuilder, #{}),
TargetsForVariation = maps:get(Variation, ContextTargets, #{}),
KeysForKind = maps:get(ContextKind, TargetsForVariation, sets:new()),
FlagBuilder#{
context_targets => ContextTargets#{Variation => #{ContextKind => sets:add_element(ContextKey, KeysForKind)}}
}.
%% @doc Starts defining a flag rule, using the "is one of" operator. The kind of the context is implicitly "user"
%% for non-user contexts use {@link if_match/4}.
%%
%% For example, this creates a rule that returns `true' if the name is "Patsy" or "Edina":
%%
%% ```
%% {ok, Flag} = ldclient_testdata:flag(TestData, <<"flag">>),
%% RuleBuilder = ldclient_flagbuilder:if_match(<<"name">>, [<<"Patsy">>, <<"Edina">>], Flag),
%% UpdatedFlag = ldclient_flagbuilder:then_return(true, RuleBuilder),
%% ldclient_testdata:update(TestData, UpdatedFlag).
%% '''
%%
%% @param ContextAttribute the context attribute to match against
%% @param Values values to compare to
%% @param FlagBuilder the flag builder to modify
%% @returns a {@link flag_rule_builder()}; call {@link then_return/2} to finish the rule,
%% or add more tests with {@link and_match/4} or {@link and_not_match/4}.
%% @end
-spec if_match(
ContextAttribute :: ldclient_context:attribute_key(),
Values :: [term()],
FlagBuilder :: flag_builder()) -> flag_rule_builder().
if_match(ContextAttribute, Values, FlagBuilder) ->
if_match(<<"user">>, ContextAttribute, Values, FlagBuilder).
%% @doc Starts defining a flag rule, using the "is one of" operator.
%%
%% For example, this creates a rule that returns `true' if the name is "Patsy" or "Edina":
%%
%% ```
%% {ok, Flag} = ldclient_testdata:flag(TestData, <<"flag">>),
%% RuleBuilder = ldclient_flagbuilder:if_match(<<"user">>, <<"name">>, [<<"Patsy">>, <<"Edina">>], Flag),
%% UpdatedFlag = ldclient_flagbuilder:then_return(true, RuleBuilder),
%% ldclient_testdata:update(TestData, UpdatedFlag).
%% '''
%%
%% @param ContextAttribute the context attribute to match against
%% @param Values values to compare to
%% @param FlagBuilder the flag builder to modify
%% @returns a {@link flag_rule_builder()}; call {@link then_return/2} to finish the rule,
%% or add more tests with {@link and_match/4} or {@link and_not_match/4}.
%% @end
-spec if_match(
ContextKind :: ldclient_context:kind_value(),
ContextAttribute :: ldclient_context:attribute_key(),
Values :: [term()],
FlagBuilder :: flag_builder()) -> flag_rule_builder().
if_match(ContextKind, ContextAttribute, Values, FlagBuilder) ->
and_match(ContextKind, ContextAttribute, Values, #{ flag => FlagBuilder }).
%% @doc Starts defining a flag rule, using the "is not one of" operator. The kind of the context is implicitly
%% "user" for non-user contexts use {@link if_not_match/4}.
%%
%% For example, this creates a rule that returns `true' if the name is neither "Saffron" nor "Bubble":
%%
%% ```
%% {ok, Flag} = ldclient_testdata:flag(TestData, <<"flag">>),
%% RuleBuilder = ldclient_flagbuilder:if_not_match(<<"name">>, [<<"Saffron">>, <<"Bubble">>], Flag),
%% UpdatedFlag = ldclient_flagbuilder:then_return(true, RuleBuilder),
%% ldclient_testdata:update(TestData, UpdatedFlag).
%% '''
%%
%% @param ContextAttribute the context attribute to match against
%% @param Values values to compare to
%% @param FlagBuilder the flag builder to modify
%% @returns a {@link flag_rule_builder()}; call {@link then_return/2} to finish the rule,
%% or add more tests with {@link and_match/4} or {@link and_not_match/4}.
%% @end
-spec if_not_match(
ContextAttribute :: ldclient_context:attribute_key(),
Values :: [term()],
FlagBuilder :: flag_builder()) -> flag_rule_builder().
if_not_match(ContextAttribute, Values, FlagBuilder) ->
if_not_match(<<"user">>, ContextAttribute, Values, FlagBuilder).
%% @doc Starts defining a flag rule, using the "is not one of" operator.
%%
%% For example, this creates a rule that returns `true' if the name is neither "Saffron" nor "Bubble":
%%
%% ```
%% {ok, Flag} = ldclient_testdata:flag(TestData, <<"flag">>),
%% RuleBuilder = ldclient_flagbuilder:if_not_match(<<"user">>, <<"name">>, [<<"Saffron">>, <<"Bubble">>], Flag),
%% UpdatedFlag = ldclient_flagbuilder:then_return(true, RuleBuilder),
%% ldclient_testdata:update(TestData, UpdatedFlag).
%% '''
%%
%% @param ContextAttribute the context attribute to match against
%% @param Values values to compare to
%% @param FlagBuilder the flag builder to modify
%% @returns a {@link flag_rule_builder()}; call {@link then_return/2} to finish the rule,
%% or add more tests with {@link and_match/4} or {@link and_not_match/4}.
%% @end
-spec if_not_match(
ContextKind :: ldclient_context:kind_value(),
ContextAttribute :: ldclient_context:attribute_key(),
Values :: [term()],
FlagBuilder :: flag_builder()) -> flag_rule_builder().
if_not_match(ContextKind, ContextAttribute, Values, FlagBuilder) ->
and_not_match(ContextKind, ContextAttribute, Values, #{ flag => FlagBuilder }).
%%-------------------------------------------------------------------
%% Flag Rule Builder
%%-------------------------------------------------------------------
%% @doc Adds another clause, using the "is one of" operator. The kind of the context is implicitly "user"
%% for non-user contexts use {@link any_match/4}.
%%
%% For example, this creates a rule that returns `true' if the name is "Patsy" and the
%% country is "gb":
%%
%% ```
%% {ok, Flag} = ldclient_testdata:flag(TestData, <<"flag">>),
%% RuleBuilder = ldclient_flagbuilder:and_match(<<"country">>, [<<"gb">>],
%% ldclient_flagbuilder:if_match(<<"name">>, [<<"Patsy">>], Flag)),
%% UpdatedFlag = ldclient_flagbuilder:then_return(true, RuleBuilder),
%% ldclient_testdata:update(TestData, UpdatedFlag).
%% '''
%%
%% @param ContextAttribute the context attribute to match against
%% @param Values values to compare to
%% @param RuleBuilder the rule builder to modify
%% @returns the modified rule builder
%% @end
-spec and_match(
ContextAttribute :: ldclient_context:attribute_key(),
Values :: [term()],
RuleBuilder :: flag_rule_builder()) -> flag_rule_builder().
and_match(ContextAttribute, Values, RuleBuilder) ->
and_match(<<"user">>, ContextAttribute, Values, RuleBuilder).
%% @doc Adds another clause, using the "is one of" operator. The kind of the context is implicitly "user"
%% for non-user contexts use {@link any_match/4}.
%%
%% For example, this creates a rule that returns `true' if the name is "Patsy" and the
%% country is "gb":
%%
%% ```
%% {ok, Flag} = ldclient_testdata:flag(TestData, <<"flag">>),
%% RuleBuilder = ldclient_flagbuilder:and_match(<<"user">>, <<"country">>, [<<"gb">>],
%% ldclient_flagbuilder:if_match(<<"user">>, <<"name">>, [<<"Patsy">>], Flag)),
%% UpdatedFlag = ldclient_flagbuilder:then_return(true, RuleBuilder),
%% ldclient_testdata:update(TestData, UpdatedFlag).
%% '''
%%
%% @param ContextAttribute the context attribute to match against
%% @param Values values to compare to
%% @param RuleBuilder the rule builder to modify
%% @returns the modified rule builder
-spec and_match(
ContextKind:: ldclient_context:kind_value(),
ContextAttribute :: ldclient_context:attribute_key(),
Values :: [term()], RuleBuilder :: flag_rule_builder()) -> flag_rule_builder().
and_match(ContextKind, ContextAttribute, Values, RuleBuilder) ->
Clauses = maps:get(clauses, RuleBuilder, []),
maps:put(clauses, [new_clause(ContextKind, ContextAttribute, Values, false)|Clauses], RuleBuilder).
%% @doc Adds another clause, using the "is not one of" operator. The kind of the context is implicitly "user"
%% for non-user contexts use {@link any_match/4}.
%%
%% For example, this creates a rule that returns `true' if the name is "Patsy" and the
%% country is not "gb":
%%
%% ```
%% {ok, Flag} = ldclient_testdata:flag(TestData, <<"flag">>),
%% RuleBuilder = ldclient_flagbuilder:and_not_match(<<"country">>, [<<"gb">>],
%% ldclient_flagbuilder:if_match(<<"name">>, [<<"Patsy">>], Flag)),
%% UpdatedFlag = ldclient_flagbuilder:then_return(true, RuleBuilder),
%% ldclient_testdata:update(TestData, UpdatedFlag).
%% '''
%%
%% @param ContextAttribute the context attribute to match against
%% @param Values values to compare to
%% @param RuleBuilder the rule builder to modify
%% @returns the modified rule builder
%% @end
-spec and_not_match(
ContextAttribute :: ldclient_context:attribute_key(),
Values :: [term()],
RuleBuilder :: flag_rule_builder()) -> flag_rule_builder().
and_not_match(ContextAttribute, Values, RuleBuilder) ->
and_not_match(<<"user">>, ContextAttribute, Values, RuleBuilder).
%% @doc Adds another clause, using the "is not one of" operator.
%%
%% For example, this creates a rule that returns `true' if the name is "Patsy" and the
%% country is not "gb":
%%
%% ```
%% {ok, Flag} = ldclient_testdata:flag(TestData, <<"flag">>),
%% RuleBuilder = ldclient_flagbuilder:and_not_match(<<"user">>, <<"country">>, [<<"gb">>],
%% ldclient_flagbuilder:if_match(<<"user">>, <<"name">>, [<<"Patsy">>], Flag)),
%% UpdatedFlag = ldclient_flagbuilder:then_return(true, RuleBuilder),
%% ldclient_testdata:update(TestData, UpdatedFlag).
%% '''
%%
%% @param ContextAttribute the context attribute to match against
%% @param Values values to compare to
%% @param RuleBuilder the rule builder to modify
%% @returns the modified rule builder
%% @end
-spec and_not_match(
ContextKind :: ldclient_context:kind_value(),
ContextAttribute :: ldclient_context:attribute_key(),
Values :: [term()],
RuleBuilder :: flag_rule_builder()) -> flag_rule_builder().
and_not_match(ContextKind, ContextAttribute, Values, RuleBuilder) ->
Clauses = maps:get(clauses, RuleBuilder, []),
maps:put(clauses, [new_clause(ContextKind, ContextAttribute, Values, true)|Clauses], RuleBuilder).
%% @doc Finishes defining the rule, specifying the result variation.
%%
%% If the flag was previously configured with other variations and a boolean variation is specified,
%% this also changes the FlagBuilder to a boolean flag.
%%
%% @param Variation `true', `false', or the index of the desired variation to return: 0 for the first, 1 for the second, etc.
%% @param RuleBuilder the rule builder to use
%% @returns the modified flag builder that initially created this rule builder
%% @end
-spec then_return(Variation :: variation(), RuleBuilder :: flag_rule_builder()) -> flag_builder().
then_return(Variation, RuleBuilder) when is_boolean(Variation) ->
Flag = maps:get(flag, RuleBuilder),
BooleanRuleBuilder = maps:put(flag, boolean_flag(Flag), RuleBuilder),
then_return(variation_for_boolean(Variation), BooleanRuleBuilder);
then_return(Variation, RuleBuilder) when is_integer(Variation) ->
#{ flag := RuleFlag } = RuleBuilder,
ExistingRules = maps:get(rules, RuleFlag, []),
RuleBuilderWithVariation = maps:put(variation, Variation, RuleBuilder),
Rule = build_rule(length(ExistingRules), RuleBuilderWithVariation),
maps:put(rules, [Rule|ExistingRules], RuleFlag).
%%===================================================================
%% Internal functions
%%===================================================================
-spec new_clause(
ContextKind :: ldclient_context:kind_value(),
ContextAttribute :: ldclient_context:attribute_key(),
Values :: [term()],
Negate :: boolean()) -> ldclient_clause:clause().
new_clause(ContextKind, ContextAttribute, Values, Negate) ->
AttributeBinary = if
is_binary(ContextAttribute) -> ContextAttribute;
is_atom(ContextAttribute) -> atom_to_binary(ContextAttribute, utf8);
true -> unknown
end,
#{attribute => ldclient_attribute_reference:new(AttributeBinary),
values => Values,
negate => Negate,
op => in,
context_kind => ContextKind
}.
-spec build_rule(Index :: non_neg_integer(), RuleBuilder :: flag_rule_builder()) -> ldclient_rule:rule().
build_rule(Index, #{variation := Variation, clauses := Clauses}) ->
IndexBinary = integer_to_binary(Index),
#{
id => <<<<"rule">>/binary, IndexBinary/binary>>,
clauses => Clauses,
trackEvents => false,
variationOrRollout => Variation
}.
%% @doc Builds both context targets and user targets.
%%
%% When building targets there are special rules for user targets. A user target needs an entry in the context targets
%% to control the ordering of target evaluations, but this entry does not contain the targeted keys. Instead
%% evaluation will find a corresponding user target with the same variation. Here we always build both and add
%% the special entries for user targets.
%% @end
-spec build_targets(InTargets :: builder_targets()) ->
{ContextTargets :: [ldclient_flag:target()], UserTargets :: [ldclient_flag:target()]}.
build_targets(InTargets) ->
maps:fold(fun(Variation, KindsAndKeys, AccIn) ->
{ContextTargets, UserTargets} = AccIn,
{ContextTargetsToAdd, UserTargetsToAdd} = targets_for_kinds(Variation, KindsAndKeys),
{ContextTargets ++ ContextTargetsToAdd, UserTargets ++ UserTargetsToAdd}
end, {[], []}, InTargets).
-spec targets_for_kinds(
Variation :: non_neg_integer(), KindsAndKeys :: #{ContextKind :: ldclient_context:kind_value() => sets:set()}
) -> {ContextTargets :: [ldclient_flag:target()], UserTargets :: [ldclient_flag:target()]}.
targets_for_kinds(Variation, KindsAndKeys) ->
ContextTargets = maps:fold(fun(Kind, KeySet, AccIn) ->
[#{
variation => Variation,
contextKind => Kind,
values => case Kind of
<<"user">> -> [];
_ -> sets:to_list(KeySet)
end
}|AccIn]
end, [], KindsAndKeys),
UserTargets = maps:fold(fun(Kind, KeySet, AccIn) ->
case Kind of
<<"user">> -> [#{
contextKind => Kind,
variation => Variation,
values => sets:to_list(KeySet)
}|AccIn];
_ -> AccIn
end
end, [], KindsAndKeys),
{ContextTargets, UserTargets}.