Packages
launchdarkly_server_sdk
2.1.2
3.11.0
3.10.1
3.9.0
3.8.1
3.8.0
3.7.2
3.7.1
3.7.0
3.6.0
3.5.0
3.4.0
3.3.1
3.3.0
3.2.0
3.1.0
3.0.4
3.0.3
3.0.2
3.0.1
3.0.0
2.1.2
2.1.1
2.1.0
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
1.6.0
1.5.0
1.4.0
1.3.2
1.3.1
1.3.0
1.2.0
1.1.3
1.1.2
1.1.1
retired
1.1.0
retired
1.0.1
retired
1.0.0
retired
1.0.0-beta4
retired
1.0.0-beta3
retired
1.0.0-beta2
retired
LaunchDarkly SDK for Erlang
Current section
Files
Jump to
Current section
Files
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}.