1 package evaluation
2
3 import (
4 "github.com/launchdarkly/go-server-sdk-evaluation/v2/ldmodel"
5
6 "github.com/launchdarkly/go-sdk-common/v3/ldcontext"
7 "github.com/launchdarkly/go-sdk-common/v3/ldlog"
8 "github.com/launchdarkly/go-sdk-common/v3/ldreason"
9 "github.com/launchdarkly/go-sdk-common/v3/ldvalue"
10 )
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 type Result struct {
29
30 Detail ldreason.EvaluationDetail
31
32
33
34
35
36
37 IsExperiment bool
38 }
39
40 type evaluator struct {
41 dataProvider DataProvider
42 bigSegmentProvider BigSegmentProvider
43 errorLogger ldlog.BaseLogger
44 enableSecondaryKey bool
45 }
46
47 const (
48 preallocatedPrerequisiteChainSize = 20
49 preallocatedSegmentChainSize = 20
50 )
51
52
53
54
55
56 func NewEvaluator(dataProvider DataProvider) Evaluator {
57 return NewEvaluatorWithOptions(dataProvider)
58 }
59
60
61
62
63 func NewEvaluatorWithOptions(dataProvider DataProvider, options ...EvaluatorOption) Evaluator {
64 e := &evaluator{
65 dataProvider: dataProvider,
66 }
67 for _, o := range options {
68 if o != nil {
69 o.apply(e)
70 }
71 }
72 return e
73 }
74
75
76
77
78 type evaluationScope struct {
79 owner *evaluator
80 flag *ldmodel.FeatureFlag
81 context ldcontext.Context
82 prerequisiteFlagEventRecorder PrerequisiteFlagEventRecorder
83
84
85 bigSegmentsMemberships map[string]BigSegmentMembership
86 bigSegmentsStatus ldreason.BigSegmentsStatus
87 }
88
89 type evaluationStack struct {
90 prerequisiteFlagChain []string
91 segmentChain []string
92 }
93
94
95 func (e *evaluator) Evaluate(
96 flag *ldmodel.FeatureFlag,
97 context ldcontext.Context,
98 prerequisiteFlagEventRecorder PrerequisiteFlagEventRecorder,
99 ) Result {
100 if context.Err() != nil {
101 return Result{Detail: ldreason.NewEvaluationDetailForError(ldreason.EvalErrorUserNotSpecified, ldvalue.Null())}
102 }
103
104 es := evaluationScope{
105 owner: e,
106 flag: flag,
107 context: context,
108 prerequisiteFlagEventRecorder: prerequisiteFlagEventRecorder,
109 }
110
111
112
113
114 stack := evaluationStack{
115 prerequisiteFlagChain: make([]string, 0, preallocatedPrerequisiteChainSize),
116 segmentChain: make([]string, 0, preallocatedSegmentChainSize),
117 }
118
119 detail, _ := es.evaluate(stack)
120 if es.bigSegmentsStatus != "" {
121 detail.Reason = ldreason.NewEvalReasonFromReasonWithBigSegmentsStatus(detail.Reason,
122 es.bigSegmentsStatus)
123 }
124 return Result{Detail: detail, IsExperiment: isExperiment(flag, detail.Reason)}
125 }
126
127
128
129
130
131
132
133
134
135
136 func (es *evaluationScope) evaluate(stack evaluationStack) (ldreason.EvaluationDetail, bool) {
137 if !es.flag.On {
138 return es.getOffValue(ldreason.NewEvalReasonOff()), true
139 }
140
141 prereqErrorReason, ok := es.checkPrerequisites(stack)
142 if !ok {
143
144 if prereqErrorReason.GetKind() == ldreason.EvalReasonError {
145 return ldreason.NewEvaluationDetailForError(prereqErrorReason.GetErrorKind(), ldvalue.Null()), false
146 }
147
148 return es.getOffValue(prereqErrorReason), true
149 }
150
151
152 if variation := es.anyTargetMatchVariation(); variation.IsDefined() {
153 return es.getVariation(variation.IntValue(), ldreason.NewEvalReasonTargetMatch()), true
154 }
155
156
157 for ruleIndex, rule := range es.flag.Rules {
158 match, err := es.ruleMatchesContext(&rule, stack)
159 if err != nil {
160 es.logEvaluationError(err)
161 return ldreason.NewEvaluationDetailForError(errorKindForError(err), ldvalue.Null()), false
162 }
163 if match {
164 reason := ldreason.NewEvalReasonRuleMatch(ruleIndex, rule.ID)
165 return es.getValueForVariationOrRollout(rule.VariationOrRollout, reason), true
166 }
167 }
168
169 return es.getValueForVariationOrRollout(es.flag.Fallthrough, ldreason.NewEvalReasonFallthrough()), true
170 }
171
172
173
174
175 func (es *evaluationScope) evaluatePrerequisite(
176 prereqFlag *ldmodel.FeatureFlag,
177 stack evaluationStack,
178 ) (ldreason.EvaluationDetail, bool) {
179 for _, p := range stack.prerequisiteFlagChain {
180 if prereqFlag.Key == p {
181 err := circularPrereqReferenceError(prereqFlag.Key)
182 es.logEvaluationError(err)
183 return ldreason.EvaluationDetail{}, false
184 }
185 }
186 subScope := *es
187 subScope.flag = prereqFlag
188 result, ok := subScope.evaluate(stack)
189 es.bigSegmentsStatus = computeUpdatedBigSegmentsStatus(es.bigSegmentsStatus, subScope.bigSegmentsStatus)
190 return result, ok
191 }
192
193
194 func (es *evaluationScope) checkPrerequisites(stack evaluationStack) (ldreason.EvaluationReason, bool) {
195 if len(es.flag.Prerequisites) == 0 {
196 return ldreason.EvaluationReason{}, true
197 }
198
199 stack.prerequisiteFlagChain = append(stack.prerequisiteFlagChain, es.flag.Key)
200
201
202
203
204
205
206
207
208
209
210
211 for _, prereq := range es.flag.Prerequisites {
212 prereqFeatureFlag := es.owner.dataProvider.GetFeatureFlag(prereq.Key)
213 if prereqFeatureFlag == nil {
214 return ldreason.NewEvalReasonPrerequisiteFailed(prereq.Key), false
215 }
216 prereqOK := true
217
218 prereqResultDetail, prereqValid := es.evaluatePrerequisite(prereqFeatureFlag, stack)
219 if !prereqValid {
220
221 return ldreason.NewEvalReasonError(ldreason.EvalErrorMalformedFlag), false
222 }
223 if !prereqFeatureFlag.On || prereqResultDetail.IsDefaultValue() ||
224 prereqResultDetail.VariationIndex.IntValue() != prereq.Variation {
225
226
227 prereqOK = false
228 }
229
230 if es.prerequisiteFlagEventRecorder != nil {
231 event := PrerequisiteFlagEvent{es.flag.Key, es.context, prereqFeatureFlag, Result{
232 Detail: prereqResultDetail,
233 IsExperiment: isExperiment(prereqFeatureFlag, prereqResultDetail.Reason),
234 }}
235 es.prerequisiteFlagEventRecorder(event)
236 }
237
238 if !prereqOK {
239 return ldreason.NewEvalReasonPrerequisiteFailed(prereq.Key), false
240 }
241 }
242 return ldreason.EvaluationReason{}, true
243 }
244
245 func (es *evaluationScope) getVariation(index int, reason ldreason.EvaluationReason) ldreason.EvaluationDetail {
246 if index < 0 || index >= len(es.flag.Variations) {
247 err := badVariationError(index)
248 es.logEvaluationError(err)
249 return ldreason.NewEvaluationDetailForError(err.errorKind(), ldvalue.Null())
250 }
251 return ldreason.NewEvaluationDetail(es.flag.Variations[index], index, reason)
252 }
253
254 func (es *evaluationScope) getOffValue(reason ldreason.EvaluationReason) ldreason.EvaluationDetail {
255 if !es.flag.OffVariation.IsDefined() {
256 return ldreason.EvaluationDetail{Reason: reason}
257 }
258 return es.getVariation(es.flag.OffVariation.IntValue(), reason)
259 }
260
261 func (es *evaluationScope) getValueForVariationOrRollout(
262 vr ldmodel.VariationOrRollout,
263 reason ldreason.EvaluationReason,
264 ) ldreason.EvaluationDetail {
265 index, inExperiment, err := es.variationOrRolloutResult(vr, es.flag.Key, es.flag.Salt)
266 if err != nil {
267 es.logEvaluationError(err)
268 return ldreason.NewEvaluationDetailForError(errorKindForError(err), ldvalue.Null())
269 }
270 if inExperiment {
271 reason = reasonToExperimentReason(reason)
272 }
273 return es.getVariation(index, reason)
274 }
275
276 func (es *evaluationScope) anyTargetMatchVariation() ldvalue.OptionalInt {
277 if len(es.flag.ContextTargets) == 0 {
278
279
280 for _, t := range es.flag.Targets {
281 if variation := es.targetMatchVariation(&t); variation.IsDefined() {
282 return variation
283 }
284 }
285 } else {
286
287
288 for _, t := range es.flag.ContextTargets {
289 var variation ldvalue.OptionalInt
290 if (t.ContextKind == "" || t.ContextKind == ldcontext.DefaultKind) && len(t.Values) == 0 {
291 for _, t1 := range es.flag.Targets {
292 if t1.Variation == t.Variation {
293 variation = es.targetMatchVariation(&t1)
294 break
295 }
296 }
297 } else {
298 variation = es.targetMatchVariation(&t)
299 }
300 if variation.IsDefined() {
301 return variation
302 }
303 }
304 }
305 return ldvalue.OptionalInt{}
306 }
307
308 func (es *evaluationScope) targetMatchVariation(t *ldmodel.Target) ldvalue.OptionalInt {
309 if context := es.context.IndividualContextByKind(t.ContextKind); context.IsDefined() {
310 if ldmodel.EvaluatorAccessors.TargetFindKey(t, context.Key()) {
311 return ldvalue.NewOptionalInt(t.Variation)
312 }
313 }
314 return ldvalue.OptionalInt{}
315 }
316
317 func (es *evaluationScope) ruleMatchesContext(rule *ldmodel.FlagRule, stack evaluationStack) (bool, error) {
318
319 for _, clause := range rule.Clauses {
320 match, err := es.clauseMatchesContext(&clause, stack)
321 if !match || err != nil {
322 return match, err
323 }
324 }
325 return true, nil
326 }
327
328 func (es *evaluationScope) variationOrRolloutResult(
329 r ldmodel.VariationOrRollout, key, salt string) (variationIndex int, inExperiment bool, err error) {
330 if r.Variation.IsDefined() {
331 return r.Variation.IntValue(), false, nil
332 }
333 if len(r.Rollout.Variations) == 0 {
334
335 return -1, false, emptyRolloutError{}
336 }
337
338 isExperiment := r.Rollout.IsExperiment()
339
340 bucketVal, problem, err := es.computeBucketValue(isExperiment, r.Rollout.Seed, r.Rollout.ContextKind,
341 key, r.Rollout.BucketBy, salt)
342 if err != nil {
343 return -1, false, err
344 }
345 var sum float32
346
347 for _, bucket := range r.Rollout.Variations {
348 sum += float32(bucket.Weight) / 100000.0
349 if bucketVal < sum {
350 resultInExperiment := isExperiment && !bucket.Untracked &&
351 problem != bucketingFailureContextLacksDesiredKind
352 return bucket.Variation, resultInExperiment, nil
353 }
354 }
355
356
357
358
359
360
361 lastBucket := r.Rollout.Variations[len(r.Rollout.Variations)-1]
362 return lastBucket.Variation, isExperiment && !lastBucket.Untracked, nil
363 }
364
365 func (es *evaluationScope) logEvaluationError(err error) {
366 if err == nil || es.owner.errorLogger == nil {
367 return
368 }
369 es.owner.errorLogger.Printf("Invalid flag configuration detected in flag %q: %s",
370 es.flag.Key,
371 err,
372 )
373 }
374
375 func getApplicableContextKeyByKind(baseContext *ldcontext.Context, kind ldcontext.Kind) (string, bool) {
376 if mc := baseContext.IndividualContextByKind(kind); mc.IsDefined() {
377 return mc.Key(), true
378 }
379 return "", false
380 }
381
382 func reasonToExperimentReason(reason ldreason.EvaluationReason) ldreason.EvaluationReason {
383 switch reason.GetKind() {
384 case ldreason.EvalReasonFallthrough:
385 return ldreason.NewEvalReasonFallthroughExperiment(true)
386 case ldreason.EvalReasonRuleMatch:
387 return ldreason.NewEvalReasonRuleMatchExperiment(reason.GetRuleIndex(), reason.GetRuleID(), true)
388 default:
389 return reason
390 }
391 }
392
393 func isExperiment(flag *ldmodel.FeatureFlag, reason ldreason.EvaluationReason) bool {
394
395
396 if reason.IsInExperiment() {
397 return true
398 }
399
400 switch reason.GetKind() {
401 case ldreason.EvalReasonFallthrough:
402 return flag.TrackEventsFallthrough
403 case ldreason.EvalReasonRuleMatch:
404 i := reason.GetRuleIndex()
405 if i >= 0 && i < len(flag.Rules) {
406 return flag.Rules[i].TrackEvents
407 }
408 }
409 return false
410 }
411
View as plain text