Packages
launchdarkly_server_sdk
1.3.0
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_segment.erl
%%-------------------------------------------------------------------
%% @doc Segment data type
%% @private
%% @end
%%-------------------------------------------------------------------
-module(ldclient_segment).
%% API
-export([new/1]).
-export([match_user/2]).
%% Types
-type segment() :: #{
key => binary(),
deleted => boolean(),
excluded => [binary()],
included => [binary()],
rules => [rule()],
salt => binary(),
version => pos_integer()
}.
-type rule() :: #{
clauses => [ldclient_clause:clause()],
weight => null | non_neg_integer(),
bucketBy => ldclient_user:attribute(),
segmentKey => binary(),
segmentSalt => binary()
}.
-export_type([segment/0]).
%%===================================================================
%% API
%%===================================================================
-spec new(map()) -> segment().
new(RawSegmentMap) ->
SegmentTemplate = #{
<<"key">> => <<>>,
<<"deleted">> => false,
<<"excluded">> => [],
<<"included">> => [],
<<"rules">> => [],
<<"salt">> => <<>>,
<<"version">> => 0
},
SegmentMap = maps:merge(SegmentTemplate, RawSegmentMap),
new_from_template(SegmentMap).
-spec match_user(segment(), ldclient_user:user()) -> match | no_match.
match_user(Segment, User) ->
check_user_in_segment(Segment, User).
%%===================================================================
%% Internal functions
%%===================================================================
-spec new_from_template(map()) -> segment().
new_from_template(#{
<<"key">> := Key,
<<"deleted">> := Deleted,
<<"excluded">> := Excluded,
<<"included">> := Included,
<<"rules">> := Rules,
<<"salt">> := Salt,
<<"version">> := Version
}) ->
#{
key => Key,
deleted => Deleted,
excluded => Excluded,
included => Included,
rules => parse_rules(Key, Salt, Rules),
salt => Salt,
version => Version
}.
-spec parse_rules(binary(), binary(), [map()]) -> [rule()].
parse_rules(SegmentKey, SegmentSalt, Rules) ->
F = fun(#{<<"clauses">> := Clauses} = RuleRaw, Acc) ->
Rule = #{
segmentKey => SegmentKey,
segmentSalt => SegmentSalt,
clauses => parse_clauses(Clauses)
},
[parse_rule_optional_attributes(Rule, RuleRaw)|Acc];
(_, Acc) ->
Acc
end,
lists:foldr(F, [], Rules).
-spec parse_rule_optional_attributes(map(), map()) -> rule().
parse_rule_optional_attributes(Rule, RuleRaw) ->
Weight = maps:get(<<"weight">>, RuleRaw, null),
BucketBy = maps:get(<<"bucketBy">>, RuleRaw, key),
Rule#{weight => Weight, bucketBy => BucketBy}.
-spec parse_clauses([map()]) -> [ldclient_clause:clause()].
parse_clauses(Clauses) ->
F = fun(Clause) -> ldclient_clause:new(Clause) end,
lists:map(F, Clauses).
check_user_in_segment(Segment, User) ->
check_user_included(Segment, User).
check_user_included(#{included := Included} = Segment, #{key := UserKey} = User) ->
Result = lists:member(UserKey, Included),
check_user_included_result(Result, Segment, User).
check_user_included_result(true, _Segment, _User) -> match;
check_user_included_result(false, Segment, User) ->
check_user_excluded(Segment, User).
check_user_excluded(#{excluded := Excluded} = Segment, #{key := UserKey} = User) ->
Result = lists:member(UserKey, Excluded),
check_user_excluded_result(Result, Segment, User).
check_user_excluded_result(true, _Segment, _User) -> no_match;
check_user_excluded_result(false, #{rules := Rules}, User) ->
check_rules(Rules, User).
check_rules([], _User) -> no_match;
check_rules([Rule|Rest], User) ->
Result = check_rule(Rule, User),
check_rule_result({Result, Rule}, Rest, User).
check_rule_result({match, _Rule}, _Rest, _User) -> match;
check_rule_result({no_match, _Rule}, Rest, User) ->
check_rules(Rest, User).
check_rule(#{clauses := Clauses} = Rule, User) ->
Result = check_clauses(Clauses, User),
check_clauses_result(Result, Rule, User).
-spec check_clauses([ldclient_clause:clause()], ldclient_user:user()) -> match | no_match.
check_clauses([], _User) -> match;
check_clauses([Clause|Rest], User) ->
% Non-segment match
Result = ldclient_clause:match_user(Clause, User),
check_clause_result(Result, Rest, User).
check_clauses_result(no_match, _Rule, _User) -> no_match;
check_clauses_result(match, Rule, User) ->
check_rule_weight(Rule, User).
-spec check_clause_result(match | no_match, [ldclient_clause:clause()], ldclient_user:user()) -> match | no_match.
check_clause_result(no_match, _Rest, _User) -> no_match;
check_clause_result(match, Rest, User) ->
check_clauses(Rest, User).
check_rule_weight(#{weight := null}, _User) -> match;
check_rule_weight(Rule, User) ->
check_user_bucket(Rule, User).
check_user_bucket(#{segmentKey := SegmentKey, segmentSalt := SegmentSalt, bucketBy := BucketBy, weight := Weight}, User) ->
Bucket = ldclient_rollout:bucket_user(null, SegmentKey, SegmentSalt, User, BucketBy),
check_user_bucket_result(Bucket, Weight).
check_user_bucket_result(Bucket, Weight) when Bucket < Weight / 100000 -> match;
check_user_bucket_result(_, _) -> no_match.