This repository has been archived by the owner on Oct 11, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 15
/
segment.go
131 lines (107 loc) · 3.65 KB
/
segment.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package ldclient
// Segment describes a group of users
type Segment struct {
Key string `json:"key" bson:"key"`
Included []string `json:"included" bson:"included"`
Excluded []string `json:"excluded" bson:"excluded"`
Salt string `json:"salt" bson:"salt"`
Rules []SegmentRule `json:"rules" bson:"rules"`
Version int `json:"version" bson:"version"`
Deleted bool `json:"deleted" bson:"deleted"`
}
// GetKey returns the unique key describing a segment
func (s *Segment) GetKey() string {
return s.Key
}
// GetVersion returns the version of the segment
func (s *Segment) GetVersion() int {
return s.Version
}
// IsDeleted returns whether a flag has been deleted
func (s *Segment) IsDeleted() bool {
return s.Deleted
}
// Clone returns a copy of a segment
func (s *Segment) Clone() VersionedData {
s1 := *s
return &s1
}
// SegmentVersionedDataKind implements VersionedDataKind and provides methods to build storage engine for segments
type SegmentVersionedDataKind struct{}
// GetNamespace returns the a unique namespace identifier for feature flag objects
func (sk SegmentVersionedDataKind) GetNamespace() string {
return "segments"
}
// String returns the namespace
func (sk SegmentVersionedDataKind) String() string {
return sk.GetNamespace()
}
// GetDefaultItem returns a default segment representation
func (sk SegmentVersionedDataKind) GetDefaultItem() interface{} {
return &Segment{}
}
// MakeDeletedItem returns representation of a deleted segment
func (sk SegmentVersionedDataKind) MakeDeletedItem(key string, version int) VersionedData {
return &Segment{Key: key, Version: version, Deleted: true}
}
// Segments is convenience variable to access an instance of SegmentVersionedDataKind
var Segments SegmentVersionedDataKind
// SegmentRule describes a set of clauses that
type SegmentRule struct {
Id string `json:"id,omitempty" bson:"id,omitempty"`
Clauses []Clause `json:"clauses" bson:"clauses"`
Weight *int `json:"weight,omitempty" bson:"weight,omitempty"`
BucketBy *string `json:"bucketBy,omitempty" bson:"bucketBy,omitempty"`
}
// SegmentExplanation describes a rule that determines whether a user was included in or excluded from a segment
type SegmentExplanation struct {
Kind string
MatchedRule *SegmentRule
}
// ContainsUser returns whether a user belongs to the segment
func (s Segment) ContainsUser(user User) (bool, *SegmentExplanation) {
if user.Key == nil {
return false, nil
}
// Check if the user is included in the segment by key
for _, key := range s.Included {
if *user.Key == key {
return true, &SegmentExplanation{Kind: "included"}
}
}
// Check if the user is excluded from the segment by key
for _, key := range s.Excluded {
if *user.Key == key {
return false, &SegmentExplanation{Kind: "excluded"}
}
}
// Check if any of the segment rules match
for _, rule := range s.Rules {
if rule.MatchesUser(user, s.Key, s.Salt) {
reason := rule
return true, &SegmentExplanation{Kind: "rule", MatchedRule: &reason}
}
}
return false, nil
}
// MatchesUser returns whether a rule applies to a user
func (r SegmentRule) MatchesUser(user User, key, salt string) bool {
for _, clause := range r.Clauses {
if !clause.matchesUserNoSegments(user) {
return false
}
}
// If the Weight is absent, this rule matches
if r.Weight == nil {
return true
}
// All of the clauses are met. Check to see if the user buckets in
bucketBy := "key"
if r.BucketBy != nil {
bucketBy = *r.BucketBy
}
// Check whether the user buckets into the segment
bucket := bucketUser(user, key, bucketBy, salt)
weight := float32(*r.Weight) / 100000.0
return bucket < weight
}