Skip to content

Commit 41b20b7

Browse files
authored
Merge pull request #35 from davejohnston/FFM-1219
[FFM-1219]: Fix how segment rules are processed
2 parents 873a19f + c2d908b commit 41b20b7

File tree

5 files changed

+138
-67
lines changed

5 files changed

+138
-67
lines changed

evaluation/feature.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import (
66
"reflect"
77
"strconv"
88

9-
"github.com/labstack/gommon/log"
9+
"github.com/drone/ff-golang-server-sdk/log"
1010

1111
"github.com/drone/ff-golang-server-sdk/types"
1212
)
@@ -88,7 +88,7 @@ func (c Clauses) Evaluate(target *Target, segments Segments) bool {
8888
// operator should be evaluated based on type of attribute
8989
op, err := target.GetOperator(clause.Attribute)
9090
if err != nil {
91-
fmt.Print(err)
91+
log.Warn(err)
9292
}
9393
if !clause.Evaluate(target, segments, op) {
9494
return false

evaluation/segment.go

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package evaluation
22

3-
import "strings"
3+
import (
4+
"strings"
5+
6+
"github.com/drone/ff-golang-server-sdk/log"
7+
)
48

59
// StrSlice helper type used for string slice operations
610
type StrSlice []string
@@ -25,6 +29,32 @@ func (slice StrSlice) ContainsSensitive(s string) bool {
2529
return false
2630
}
2731

32+
// SegmentRules is a set of clauses to determine if a target should be included in the segment.
33+
type SegmentRules Clauses
34+
35+
// Evaluate SegmentRules. This determines if a segment rule is being used to include a target with
36+
// the segment. SegmentRules are similar to ServingRules except a ServingRule can contain multiple clauses
37+
// but a Segment rule only contains one clause.
38+
//
39+
func (c SegmentRules) Evaluate(target *Target, segments Segments) bool {
40+
// OR operation
41+
for _, clause := range c {
42+
// operator should be evaluated based on type of attribute
43+
op, err := target.GetOperator(clause.Attribute)
44+
if err != nil {
45+
log.Warn(err)
46+
continue
47+
}
48+
if clause.Evaluate(target, segments, op) {
49+
return true
50+
}
51+
52+
// continue on next rule
53+
}
54+
// it means that there was no matching rule
55+
return false
56+
}
57+
2858
// Segment object used in feature flag evaluation.
2959
// Examples: beta users, premium customers
3060
type Segment struct {
@@ -41,24 +71,33 @@ type Segment struct {
4171
Included StrSlice
4272

4373
// An array of rules that can cause a user to be included in this segment.
44-
Rules Clauses
74+
Rules SegmentRules
4575
Tags []Tag
4676
Version int64
4777
}
4878

4979
// Evaluate segment based on target input
5080
func (s Segment) Evaluate(target *Target) bool {
81+
82+
// is target excluded from segment via the exclude list
83+
if s.Excluded.ContainsSensitive(target.Identifier) {
84+
log.Debugf("target %s excluded from segment %s via exclude list\n", target.Identifier, s.Identifier)
85+
return false
86+
}
87+
88+
// is target included from segment via the include list
5189
if s.Included.ContainsSensitive(target.Identifier) {
90+
log.Debugf("target %s included in segment %s via include list\n", target.Identifier, s.Identifier)
5291
return true
5392
}
5493

94+
// is target included in the segment via the clauses
5595
if s.Rules.Evaluate(target, nil) {
96+
log.Debugf("target %s included in segment %s via rules\n", target.Identifier, s.Identifier)
5697
return true
5798
}
5899

59-
if s.Excluded.ContainsSensitive(target.Identifier) {
60-
return true
61-
}
100+
log.Debugf("No rules to include target %s in segment %s\n", target.Identifier, s.Identifier)
62101
return false
63102
}
64103

evaluation/segment_test.go

Lines changed: 19 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,17 @@
11
package evaluation
22

33
import (
4-
"github.com/google/uuid"
5-
64
"testing"
75
)
86

97
func TestSegment_Evaluate(t *testing.T) {
108
type fields struct {
11-
Identifier string
12-
Name string
13-
CreatedAt *int64
14-
ModifiedAt *int64
15-
Environment *string
16-
Excluded StrSlice
17-
Included StrSlice
18-
Rules Clauses
19-
Tags []Tag
20-
Version int64
21-
}
22-
type args struct {
23-
target *Target
9+
Identifier string
10+
Excluded StrSlice
11+
Included StrSlice
12+
Rules SegmentRules
2413
}
14+
2515
f := false
2616
m := make(map[string]interface{})
2717
m["email"] = "[email protected]"
@@ -35,54 +25,28 @@ func TestSegment_Evaluate(t *testing.T) {
3525
tests := []struct {
3626
name string
3727
fields fields
38-
args args
28+
args Target
3929
want bool
4030
}{
41-
{name: "test included", fields: struct {
42-
Identifier string
43-
Name string
44-
CreatedAt *int64
45-
ModifiedAt *int64
46-
Environment *string
47-
Excluded StrSlice
48-
Included StrSlice
49-
Rules Clauses
50-
Tags []Tag
51-
Version int64
52-
}{Identifier: "beta", Name: "Beta users", CreatedAt: nil, ModifiedAt: nil, Environment: nil, Excluded: nil,
53-
Included: []string{"john"}, Rules: nil, Tags: nil, Version: 1}, args: struct{ target *Target }{target: &target}, want: true},
54-
{name: "test rules", fields: struct {
55-
Identifier string
56-
Name string
57-
CreatedAt *int64
58-
ModifiedAt *int64
59-
Environment *string
60-
Excluded StrSlice
61-
Included StrSlice
62-
Rules Clauses
63-
Tags []Tag
64-
Version int64
65-
}{Identifier: "beta", Name: "Beta users", CreatedAt: nil, ModifiedAt: nil, Environment: nil, Excluded: nil,
66-
Included: nil, Rules: []Clause{
67-
{Attribute: "email", ID: uuid.New().String(), Negate: false, Op: equalOperator, Value: []string{"[email protected]"}},
68-
}, Tags: nil, Version: 1}, args: struct{ target *Target }{target: &target}, want: true},
31+
{name: "test target included by list", fields: fields{Identifier: "beta", Included: []string{"john"}}, args: target, want: true},
32+
{name: "test target excluded by list", fields: fields{Identifier: "beta", Included: []string{"john"}, Excluded: []string{"john"}}, args: target, want: false},
33+
{name: "test target included by rules", fields: fields{Identifier: "beta", Rules: []Clause{{Attribute: "email", ID: "1", Op: equalOperator, Value: []string{"[email protected]"}}}}, args: target, want: true},
34+
{name: "test target not included by rules", fields: fields{Identifier: "beta", Rules: []Clause{{Attribute: "email", ID: "2", Op: equalOperator, Value: []string{"[email protected]"}}}}, args: target, want: false},
35+
{name: "test target rules evaluating with OR", fields: fields{Identifier: "beta", Rules: []Clause{
36+
{Attribute: "email", ID: "1", Op: equalOperator, Value: []string{"[email protected]"}},
37+
{Attribute: "email", ID: "2", Op: equalOperator, Value: []string{"[email protected]"}},
38+
}}, args: target, want: true},
6939
}
7040
for _, tt := range tests {
7141
val := tt
7242
t.Run(val.name, func(t *testing.T) {
7343
s := Segment{
74-
Identifier: val.fields.Identifier,
75-
Name: val.fields.Name,
76-
CreatedAt: val.fields.CreatedAt,
77-
ModifiedAt: val.fields.ModifiedAt,
78-
Environment: val.fields.Environment,
79-
Excluded: val.fields.Excluded,
80-
Included: val.fields.Included,
81-
Rules: val.fields.Rules,
82-
Tags: val.fields.Tags,
83-
Version: val.fields.Version,
44+
Identifier: val.fields.Identifier,
45+
Excluded: val.fields.Excluded,
46+
Included: val.fields.Included,
47+
Rules: val.fields.Rules,
8448
}
85-
if got := s.Evaluate(val.args.target); got != val.want {
49+
if got := s.Evaluate(&val.args); got != val.want {
8650
t.Errorf("Evaluate() = %v, want %v", got, val.want)
8751
}
8852
})

log/log.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package log
2+
3+
import "github.com/drone/ff-golang-server-sdk/logger"
4+
5+
// And just go global.
6+
var defaultLogger logger.Logger
7+
8+
// init creates the default logger. This can be changed
9+
func init() {
10+
defaultLogger, _ = logger.NewZapLogger(false)
11+
}
12+
13+
// SetLogger sets the default logger to be used by this package
14+
func SetLogger(logger logger.Logger) {
15+
defaultLogger = logger
16+
}
17+
18+
// Error logs an error message with the parameters
19+
func Error(args ...interface{}) {
20+
defaultLogger.Error(args...)
21+
}
22+
23+
// Errorf logs a formatted error message
24+
func Errorf(format string, args ...interface{}) {
25+
defaultLogger.Errorf(format, args...)
26+
}
27+
28+
// Fatalf logs a formatted fatal message. This will terminate the application
29+
func Fatalf(format string, args ...interface{}) {
30+
defaultLogger.Fatalf(format, args...)
31+
}
32+
33+
// Fatal logs an fatal message with the parameters. This will terminate the application
34+
func Fatal(args ...interface{}) {
35+
defaultLogger.Fatal(args...)
36+
}
37+
38+
// Infof logs a formatted info message
39+
func Infof(format string, args ...interface{}) {
40+
defaultLogger.Infof(format, args...)
41+
}
42+
43+
// Info logs an info message with the parameters
44+
func Info(args ...interface{}) {
45+
defaultLogger.Info(args...)
46+
}
47+
48+
// Warn logs an warn message with the parameters
49+
func Warn(args ...interface{}) {
50+
defaultLogger.Warn(args...)
51+
}
52+
53+
// Warnf logs a formatted warn message
54+
func Warnf(format string, args ...interface{}) {
55+
defaultLogger.Warnf(format, args...)
56+
}
57+
58+
// Debugf logs a formatted debug message
59+
func Debugf(format string, args ...interface{}) {
60+
defaultLogger.Debugf(format, args...)
61+
}
62+
63+
// Debug logs an debug message with the parameters
64+
func Debug(args ...interface{}) {
65+
defaultLogger.Debug(args...)
66+
}

rest/adapter.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package rest
22

3-
import "github.com/drone/ff-golang-server-sdk/evaluation"
3+
import (
4+
"github.com/drone/ff-golang-server-sdk/evaluation"
5+
)
46

57
func (wv WeightedVariation) convert() *evaluation.WeightedVariation {
68
return &evaluation.WeightedVariation{
@@ -144,21 +146,21 @@ func (s Segment) Convert() evaluation.Segment {
144146
if s.Excluded != nil {
145147
excluded = make(evaluation.StrSlice, len(*s.Excluded))
146148
for i, excl := range *s.Excluded {
147-
excluded[i] = excl.Name
149+
excluded[i] = excl.Identifier
148150
}
149151
}
150152

151153
included := make(evaluation.StrSlice, 0)
152154
if s.Included != nil {
153155
included = make(evaluation.StrSlice, len(*s.Included))
154156
for i, incl := range *s.Included {
155-
included[i] = incl.Name
157+
included[i] = incl.Identifier
156158
}
157159
}
158160

159-
rules := make(evaluation.Clauses, 0)
161+
rules := make(evaluation.SegmentRules, 0)
160162
if s.Rules != nil {
161-
rules = make(evaluation.Clauses, len(*s.Rules))
163+
rules = make(evaluation.SegmentRules, len(*s.Rules))
162164
for i, rule := range *s.Rules {
163165
rules[i] = evaluation.Clause{
164166
Attribute: rule.Attribute,

0 commit comments

Comments
 (0)