Skip to content

Commit 47149d8

Browse files
authored
Merge pull request #223 from splitio/one-queue
2 parents a5c393f + 85b0018 commit 47149d8

File tree

20 files changed

+1112
-158
lines changed

20 files changed

+1112
-158
lines changed

dtos/notification.go

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ const (
1717
UpdateTypeSplitChange = "SPLIT_UPDATE"
1818
UpdateTypeSplitKill = "SPLIT_KILL"
1919
UpdateTypeSegmentChange = "SEGMENT_UPDATE"
20-
UpdateTypeContol = "CONTROL"
20+
UpdateTypeControl = "CONTROL"
2121
UpdateTypeLargeSegmentChange = "LS_DEFINITION_UPDATE"
22+
UpdateTypeRuleBasedChange = "RB_SEGMENT_UPDATE"
2223
)
2324

2425
// Control type constants
@@ -201,6 +202,7 @@ type SplitChangeUpdate struct {
201202
BaseUpdate
202203
previousChangeNumber *int64
203204
featureFlag *SplitDTO
205+
ruleBasedSegment *RuleBasedSegmentDTO
204206
}
205207

206208
func NewSplitChangeUpdate(baseUpdate BaseUpdate, pcn *int64, featureFlag *SplitDTO) *SplitChangeUpdate {
@@ -211,8 +213,26 @@ func NewSplitChangeUpdate(baseUpdate BaseUpdate, pcn *int64, featureFlag *SplitD
211213
}
212214
}
213215

214-
// UpdateType always returns UpdateTypeSplitChange for SplitUpdate messages
215-
func (u *SplitChangeUpdate) UpdateType() string { return UpdateTypeSplitChange }
216+
func NewRuleBasedSegmentChangeUpdate(baseUpdate BaseUpdate, pcn *int64, ruleBasedSegment *RuleBasedSegmentDTO) *SplitChangeUpdate {
217+
return &SplitChangeUpdate{
218+
BaseUpdate: baseUpdate,
219+
previousChangeNumber: pcn,
220+
ruleBasedSegment: ruleBasedSegment,
221+
}
222+
}
223+
224+
// UpdateType returns the type of update
225+
func (u *SplitChangeUpdate) UpdateType() string {
226+
if u.ruleBasedSegment != nil {
227+
return UpdateTypeRuleBasedChange
228+
}
229+
return UpdateTypeSplitChange
230+
}
231+
232+
// GetRuleBased returns rule-based segment
233+
func (u *SplitChangeUpdate) RuleBasedSegment() *RuleBasedSegmentDTO {
234+
return u.ruleBasedSegment
235+
}
216236

217237
// String returns the String representation of a split change notification
218238
func (u *SplitChangeUpdate) String() string {

dtos/notification_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,72 @@ func TestControlUpdate(t *testing.T) {
226226
}
227227
}
228228

229+
func TestRuleBasedSegmentChangeUpdate(t *testing.T) {
230+
// Test case 1: With rule-based segment data
231+
ruleBasedSegment := &RuleBasedSegmentDTO{
232+
Name: "test-segment",
233+
ChangeNumber: 123456,
234+
Conditions: []RuleBasedConditionDTO{
235+
{
236+
ConditionType: "WHITELIST",
237+
MatcherGroup: MatcherGroupDTO{
238+
Matchers: []MatcherDTO{},
239+
},
240+
},
241+
},
242+
}
243+
244+
rbUpdate := NewRuleBasedSegmentChangeUpdate(NewBaseUpdate(NewBaseMessage(123456789, "rb_channel"), 123456), nil, ruleBasedSegment)
245+
if rbUpdate.EventType() != SSEEventTypeMessage {
246+
t.Error("Unexpected EventType")
247+
}
248+
if rbUpdate.Timestamp() != 123456789 {
249+
t.Error("Unexpected Timestamp")
250+
}
251+
if rbUpdate.Channel() != "rb_channel" {
252+
t.Error("Unexpected Channel")
253+
}
254+
if rbUpdate.MessageType() != MessageTypeUpdate {
255+
t.Error("Unexpected MessageType")
256+
}
257+
if rbUpdate.ChangeNumber() != 123456 {
258+
t.Error("Unexpected ChangeNumber")
259+
}
260+
if rbUpdate.UpdateType() != UpdateTypeRuleBasedChange {
261+
t.Error("Unexpected UpdateType, got:", rbUpdate.UpdateType())
262+
}
263+
if rbUpdate.String() != "SplitChange(channel=rb_channel,changeNumber=123456,timestamp=123456789)" {
264+
t.Error("Unexpected String", rbUpdate.String())
265+
}
266+
if rbUpdate.RuleBasedSegment() == nil {
267+
t.Error("RuleBasedSegment should not be nil")
268+
}
269+
if rbUpdate.RuleBasedSegment().Name != "test-segment" {
270+
t.Error("Unexpected RuleBasedSegment name")
271+
}
272+
if rbUpdate.RuleBasedSegment().ChangeNumber != 123456 {
273+
t.Error("Unexpected RuleBasedSegment change number")
274+
}
275+
if len(rbUpdate.RuleBasedSegment().Conditions) != 1 {
276+
t.Error("RuleBasedSegment should have 1 condition")
277+
}
278+
if rbUpdate.RuleBasedSegment().Conditions[0].ConditionType != "WHITELIST" {
279+
t.Error("Unexpected condition type")
280+
}
281+
282+
// Test case 2: Without rule-based segment data
283+
rbUpdateNoSegment := NewRuleBasedSegmentChangeUpdate(NewBaseUpdate(NewBaseMessage(123456789, "rb_channel"), 123456), nil, nil)
284+
if rbUpdateNoSegment.EventType() != SSEEventTypeMessage {
285+
t.Error("Unexpected EventType for no segment case")
286+
}
287+
if rbUpdateNoSegment.UpdateType() != UpdateTypeSplitChange {
288+
t.Error("Unexpected UpdateType for no segment case, got:", rbUpdateNoSegment.UpdateType())
289+
}
290+
if rbUpdateNoSegment.RuleBasedSegment() != nil {
291+
t.Error("RuleBasedSegment should be nil for no segment case")
292+
}
293+
}
294+
229295
func TestLargeSegmentChangeUpdate(t *testing.T) {
230296
ls := []LargeSegmentRFDResponseDTO{
231297
{

dtos/split.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import (
66

77
// SplitChangesDTO structure to map JSON message sent by Split servers.
88
type SplitChangesDTO struct {
9-
FeatureFlags FeatureFlagsDTO `json:"ff"`
10-
RuleBasedSegments []RuleBasedSegmentsDTO `json:"rbs"`
9+
FeatureFlags FeatureFlagsDTO `json:"ff"`
10+
RuleBasedSegments RuleBasedSegmentsDTO `json:"rbs"`
1111
}
1212

1313
type FeatureFlagsDTO struct {

engine/validator/matchers.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ var unsupportedMatcherConditionReplacement []dtos.ConditionDTO = []dtos.Conditio
2121
},
2222
}}
2323

24+
// unsupportedMatcherRBConditionReplacement is the default condition to be used when a matcher is not supported
25+
var unsupportedMatcherRBConditionReplacement []dtos.RuleBasedConditionDTO = []dtos.RuleBasedConditionDTO{{
26+
ConditionType: grammar.ConditionTypeWhitelist,
27+
MatcherGroup: dtos.MatcherGroupDTO{
28+
Combiner: "AND",
29+
Matchers: []dtos.MatcherDTO{{MatcherType: grammar.MatcherTypeAllKeys, Negate: false}},
30+
},
31+
}}
32+
2433
func shouldOverrideConditions(conditions []dtos.ConditionDTO, logger logging.LoggerInterface) bool {
2534
for _, condition := range conditions {
2635
for _, matcher := range condition.MatcherGroup.Matchers {
@@ -33,13 +42,32 @@ func shouldOverrideConditions(conditions []dtos.ConditionDTO, logger logging.Log
3342
return false
3443
}
3544

45+
func shouldOverrideRBConditions(conditions []dtos.RuleBasedConditionDTO, logger logging.LoggerInterface) bool {
46+
for _, condition := range conditions {
47+
for _, matcher := range condition.MatcherGroup.Matchers {
48+
_, err := grammar.BuildMatcher(&matcher, &injection.Context{}, logger)
49+
if _, ok := err.(datatypes.UnsupportedMatcherError); ok {
50+
return true
51+
}
52+
}
53+
}
54+
return false
55+
}
56+
3657
// ProcessMatchers processes the matchers of a split and validates them
3758
func ProcessMatchers(split *dtos.SplitDTO, logger logging.LoggerInterface) {
3859
if shouldOverrideConditions(split.Conditions, logger) {
3960
split.Conditions = unsupportedMatcherConditionReplacement
4061
}
4162
}
4263

64+
// ProcessMatchers processes the matchers of a rule-based and validates them
65+
func ProcessRBMatchers(ruleBased *dtos.RuleBasedSegmentDTO, logger logging.LoggerInterface) {
66+
if shouldOverrideRBConditions(ruleBased.Conditions, logger) {
67+
ruleBased.Conditions = unsupportedMatcherRBConditionReplacement
68+
}
69+
}
70+
4371
// MakeUnsupportedMatcherConditionReplacement returns the default condition to be used when a matcher is not supported
4472
func MakeUnsupportedMatcherConditionReplacement() []dtos.ConditionDTO {
4573
return unsupportedMatcherConditionReplacement

engine/validator/matchers_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,60 @@ import (
99
"github.com/splitio/go-toolkit/v5/logging"
1010
)
1111

12+
func TestProcessRBMatchers(t *testing.T) {
13+
// Test case 1: Rule-based segment with unsupported matcher
14+
ruleBased := &dtos.RuleBasedSegmentDTO{
15+
Name: "test-segment",
16+
ChangeNumber: 123,
17+
Conditions: []dtos.RuleBasedConditionDTO{
18+
{
19+
ConditionType: grammar.ConditionTypeRollout,
20+
MatcherGroup: dtos.MatcherGroupDTO{
21+
Matchers: []dtos.MatcherDTO{
22+
{MatcherType: "NEW_MATCHER", KeySelector: nil},
23+
},
24+
},
25+
},
26+
},
27+
}
28+
ProcessRBMatchers(ruleBased, logging.NewLogger(nil))
29+
if len(ruleBased.Conditions) != 1 {
30+
t.Error("Conditions should have been overridden")
31+
}
32+
if ruleBased.Conditions[0].ConditionType != grammar.ConditionTypeWhitelist {
33+
t.Error("ConditionType should be WHITELIST")
34+
}
35+
if ruleBased.Conditions[0].MatcherGroup.Matchers[0].MatcherType != grammar.MatcherTypeAllKeys {
36+
t.Error("MatcherType should be ALL_KEYS")
37+
}
38+
39+
// Test case 2: Rule-based segment with supported matcher
40+
ruleBased = &dtos.RuleBasedSegmentDTO{
41+
Name: "test-segment",
42+
ChangeNumber: 123,
43+
Conditions: []dtos.RuleBasedConditionDTO{
44+
{
45+
ConditionType: grammar.ConditionTypeRollout,
46+
MatcherGroup: dtos.MatcherGroupDTO{
47+
Matchers: []dtos.MatcherDTO{
48+
{MatcherType: grammar.MatcherTypeEndsWith, KeySelector: nil, String: common.StringRef("test")},
49+
},
50+
},
51+
},
52+
},
53+
}
54+
ProcessRBMatchers(ruleBased, logging.NewLogger(nil))
55+
if len(ruleBased.Conditions) != 1 {
56+
t.Error("Conditions should not have been overridden")
57+
}
58+
if ruleBased.Conditions[0].ConditionType != grammar.ConditionTypeRollout {
59+
t.Error("ConditionType should be ROLLOUT")
60+
}
61+
if ruleBased.Conditions[0].MatcherGroup.Matchers[0].MatcherType != grammar.MatcherTypeEndsWith {
62+
t.Error("MatcherType should be ENDS_WITH")
63+
}
64+
}
65+
1266
func TestProcessMatchers(t *testing.T) {
1367
split := &dtos.SplitDTO{
1468
Conditions: []dtos.ConditionDTO{

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ require (
1212

1313
require (
1414
github.com/davecgh/go-spew v1.1.1 // indirect
15+
github.com/kr/pretty v0.1.0 // indirect
1516
github.com/pmezard/go-difflib v1.0.0 // indirect
1617
github.com/stretchr/objx v0.5.2 // indirect
18+
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
1719
)
1820

1921
require (

go.sum

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
1010
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1111
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
1212
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
13+
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
14+
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
15+
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
16+
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
17+
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
1318
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
1419
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
1520
github.com/redis/go-redis/v9 v9.0.4 h1:FC82T+CHJ/Q/PdyLW++GeCO+Ol59Y4T7R4jbgjvktgc=
@@ -26,7 +31,8 @@ golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM
2631
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
2732
golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E=
2833
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
29-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
3034
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
35+
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
36+
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
3137
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
3238
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

push/parser.go

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,14 @@ func (p *NotificationParserImpl) parseUpdate(data *genericData, nested *genericM
138138
case dtos.UpdateTypeLargeSegmentChange:
139139
largeSegments := p.processLargeSegmentMessage(nested)
140140
return nil, p.onLargeSegmentUpdate(dtos.NewLargeSegmentChangeUpdate(base, largeSegments))
141-
case dtos.UpdateTypeContol:
141+
case dtos.UpdateTypeControl:
142142
return p.onControlUpdate(dtos.NewControlUpdate(base.BaseMessage, nested.ControlType)), nil
143+
case dtos.UpdateTypeRuleBasedChange:
144+
ruleBased := p.processRuleBasedMessage(nested)
145+
if ruleBased == nil {
146+
return nil, p.onSplitUpdate(dtos.NewRuleBasedSegmentChangeUpdate(base, nil, nil))
147+
}
148+
return nil, p.onSplitUpdate(dtos.NewRuleBasedSegmentChangeUpdate(base, &nested.PreviousChangeNumber, ruleBased))
143149
default:
144150
// TODO: log full event in debug mode
145151
return nil, fmt.Errorf("invalid update type: %s", nested.Type)
@@ -157,10 +163,10 @@ func (p *NotificationParserImpl) processLargeSegmentMessage(nested *genericMessa
157163

158164
func (p *NotificationParserImpl) processMessage(nested *genericMessageData) *dtos.SplitDTO {
159165
compressType := getCompressType(nested.CompressType)
160-
if nested.FeatureFlagDefinition == nil || compressType == nil {
166+
if nested.Definition == nil || compressType == nil {
161167
return nil
162168
}
163-
ffDecoded, err := p.dataUtils.Decode(common.StringFromRef(nested.FeatureFlagDefinition))
169+
ffDecoded, err := p.dataUtils.Decode(common.StringFromRef(nested.Definition))
164170
if err != nil {
165171
p.logger.Debug(fmt.Sprintf("error decoding FeatureFlagDefinition: '%s'", err.Error()))
166172
return nil
@@ -182,6 +188,33 @@ func (p *NotificationParserImpl) processMessage(nested *genericMessageData) *dto
182188
return &featureFlag
183189
}
184190

191+
func (p *NotificationParserImpl) processRuleBasedMessage(nested *genericMessageData) *dtos.RuleBasedSegmentDTO {
192+
compressType := getCompressType(nested.CompressType)
193+
if nested.Definition == nil || compressType == nil {
194+
return nil
195+
}
196+
ruleBasedDecoded, err := p.dataUtils.Decode(common.StringFromRef(nested.Definition))
197+
if err != nil {
198+
p.logger.Debug(fmt.Sprintf("error decoding RuleBasedSegmentDefinition: '%s'", err.Error()))
199+
return nil
200+
}
201+
if common.IntFromRef(compressType) != datautils.None {
202+
ruleBasedDecoded, err = p.dataUtils.Decompress(ruleBasedDecoded, common.IntFromRef(compressType))
203+
if err != nil {
204+
p.logger.Debug(fmt.Sprintf("error decompressing RulebasedSegmentDefinition: '%s'", err.Error()))
205+
return nil
206+
}
207+
}
208+
209+
var ruleBased dtos.RuleBasedSegmentDTO
210+
err = json.Unmarshal([]byte(ruleBasedDecoded), &ruleBased)
211+
if err != nil {
212+
p.logger.Debug(fmt.Sprintf("error parsing rule-based segment json definition: '%s'", err.Error()))
213+
return nil
214+
}
215+
return &ruleBased
216+
}
217+
185218
type genericData struct {
186219

187220
// Error associated data
@@ -207,17 +240,17 @@ type metrics struct {
207240
}
208241

209242
type genericMessageData struct {
210-
Metrics metrics `json:"metrics"`
211-
Type string `json:"type"`
212-
ChangeNumber int64 `json:"changeNumber"`
213-
SplitName string `json:"splitName"`
214-
DefaultTreatment string `json:"defaultTreatment"`
215-
SegmentName string `json:"segmentName"`
216-
ControlType string `json:"controlType"`
217-
PreviousChangeNumber int64 `json:"pcn"`
218-
CompressType *int `json:"c"`
219-
FeatureFlagDefinition *string `json:"d"`
220-
LargeSegments []dtos.LargeSegmentRFDResponseDTO `json:"ls"`
243+
Metrics metrics `json:"metrics"`
244+
Type string `json:"type"`
245+
ChangeNumber int64 `json:"changeNumber"`
246+
SplitName string `json:"splitName"`
247+
DefaultTreatment string `json:"defaultTreatment"`
248+
SegmentName string `json:"segmentName"`
249+
ControlType string `json:"controlType"`
250+
PreviousChangeNumber int64 `json:"pcn"`
251+
CompressType *int `json:"c"`
252+
Definition *string `json:"d"`
253+
LargeSegments []dtos.LargeSegmentRFDResponseDTO `json:"ls"`
221254

222255
// {\"type\":\"SPLIT_UPDATE\",\"changeNumber\":1612909342671}"}
223256
}

0 commit comments

Comments
 (0)