Packages
launchdarkly_server_sdk
1.0.0-beta2
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
Retired package: Deprecated - This version will not be able to connect to and communicate with LaunchDarkly after support for the HTTP/2 protocol is enabled.
Current section
Files
Jump to
Current section
Files
src/ldclient_rollout.erl
%%-------------------------------------------------------------------
%% @doc Rollout data type
%%
%% @end
%%-------------------------------------------------------------------
-module(ldclient_rollout).
%% API
-export([new/1]).
-export([bucket_user/4]).
-export([rollout_user/3]).
%% Types
-type rollout() :: #{
variations => [weighted_variation()],
bucket_by => ldclient_user:attribute()
}.
%% Describes how users will be bucketed into variations during a percentage rollout
-type weighted_variation() :: #{
variation => ldclient_flag:variation(),
weight => non_neg_integer() % 0 to 100000
}.
%% Describes a fraction of users who will receive a specific variation
-export_type([rollout/0]).
%%===================================================================
%% API
%%===================================================================
-spec new(map()) -> rollout().
new(#{
<<"variations">> := Variations,
<<"bucketBy">> := BucketBy
}) ->
#{
variations => parse_variations(Variations),
bucket_by => BucketBy
};
new(#{<<"variations">> := Variations}) ->
#{
variations => parse_variations(Variations),
bucket_by => key
}.
-spec rollout_user(rollout(), ldclient_flag:flag(), ldclient_user:user()) -> ldclient_flag:variation() | null.
rollout_user(#{variations := WeightedVariations, bucket_by := BucketBy}, #{key := FlagKey, salt := FlagSalt}, User) ->
Bucket = bucket_user(FlagKey, FlagSalt, User, BucketBy),
match_weighted_variations(Bucket, WeightedVariations).
-spec bucket_user(ldclient_flag:key(), binary(), ldclient_user:user(), ldclient_user:attribute()) -> float().
bucket_user(Key, Salt, User, BucketBy) ->
UserValue = ldclient_user:get(BucketBy, User),
UserSecondary = ldclient_user:get(secondary, User),
bucket_user_value(Key, Salt, bucketable_value(UserValue), UserSecondary).
%%===================================================================
%% Internal functions
%%===================================================================
-spec parse_variations([map()]) -> [weighted_variation()].
parse_variations(Variations) ->
F = fun(#{<<"variation">> := Variation, <<"weight">> := Weight}, Acc) ->
[#{variation => Variation, weight => Weight}|Acc];
(#{<<"variation">> := Variation}, Acc) ->
[#{variation => Variation, weight => 0}|Acc];
(_, Acc) ->
Acc
end,
lists:foldr(F, [], Variations).
-spec match_weighted_variations(float(), [weighted_variation()]) -> ldclient_flag:variation().
match_weighted_variations(_, []) -> null;
match_weighted_variations(Bucket, WeightedVariations) ->
match_weighted_variations(Bucket, WeightedVariations, 0.0).
-spec match_weighted_variations(float(), [weighted_variation()], float()) -> ldclient_flag:variation().
match_weighted_variations(_Bucket, [], _Sum) -> null;
match_weighted_variations(_Bucket, [#{variation := Variation}|[]], _Sum) -> Variation;
match_weighted_variations(Bucket, [#{variation := Variation, weight := Weight}|_], Sum)
when Bucket < Sum + Weight / 100000 ->
Variation;
match_weighted_variations(Bucket, [#{weight := Weight}|Rest], Sum) ->
match_weighted_variations(Bucket, Rest, Sum + Weight / 100000).
-spec bucket_user_value(ldclient_flag:key(), binary(), binary() | null, binary()) -> float().
bucket_user_value(_Key, _Salt, null, _Secondary) -> 0.0;
bucket_user_value(Key, Salt, UserAttribute, null) ->
bucket_hash(<<Key/binary, $., Salt/binary, $., UserAttribute/binary>>);
bucket_user_value(Key, Salt, UserAttribute, Secondary) ->
bucket_hash(<<Key/binary, $., Salt/binary, $., UserAttribute/binary, $., Secondary/binary>>).
-spec bucket_hash(binary()) -> float().
bucket_hash(Hash) ->
Sha1 = crypto:hash(sha, Hash),
Sha1Hex = lists:flatten([[io_lib:format("~2.16.0B",[X]) || <<X:8>> <= Sha1 ]]),
Sha1_15 = string:substr(Sha1Hex, 1, 15),
Int = list_to_integer(Sha1_15, 16),
Int / 1152921504606846975.
-spec bucketable_value(any()) -> binary() | null.
bucketable_value(V) when is_binary(V) -> V;
bucketable_value(V) when is_integer(V) -> list_to_binary(integer_to_list(V));
bucketable_value(V) when is_float(V) -> if V == trunc(V) -> list_to_binary(integer_to_list(trunc(V))); true -> null end;
bucketable_value(_) -> null.