Skip to content

Commit 2a11f15

Browse files
authored
Merge pull request #233 from splitio/sdks-8329-between
BetweenSemver Matcher implementation.
2 parents 6f0b33d + f492348 commit 2a11f15

File tree

9 files changed

+318
-12
lines changed

9 files changed

+318
-12
lines changed

src/SplitIO/Grammar/Condition/Matcher.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
use SplitIO\Exception\UnsupportedMatcherException;
55
use SplitIO\Grammar\Condition\Matcher\All;
66
use SplitIO\Grammar\Condition\Matcher\Between;
7+
use SplitIO\Grammar\Condition\Matcher\BetweenSemver;
78
use SplitIO\Grammar\Condition\Matcher\EqualTo;
89
use SplitIO\Grammar\Condition\Matcher\EqualToSemver;
910
use SplitIO\Grammar\Condition\Matcher\GreaterThanOrEqualTo;
1011
use SplitIO\Grammar\Condition\Matcher\GreaterThanOrEqualToSemver;
12+
use SplitIO\Grammar\Condition\Matcher\InListSemver;
1113
use SplitIO\Grammar\Condition\Matcher\LessThanOrEqualTo;
1214
use SplitIO\Grammar\Condition\Matcher\LessThanOrEqualToSemver;
1315
use SplitIO\Grammar\Condition\Matcher\Segment;
@@ -46,6 +48,8 @@ class Matcher
4648
const EQUAL_TO_SEMVER = 'EQUAL_TO_SEMVER';
4749
const GREATER_THAN_OR_EQUAL_TO_SEMVER = 'GREATER_THAN_OR_EQUAL_TO_SEMVER';
4850
const LESS_THAN_OR_EQUAL_TO_SEMVER = 'LESS_THAN_OR_EQUAL_TO_SEMVER';
51+
const BETWEEN_SEMVER = 'BETWEEN_SEMVER';
52+
const IN_LIST_SEMVER = 'IN_LIST_SEMVER';
4953

5054
public static function factory($matcher)
5155
{
@@ -151,6 +155,16 @@ public static function factory($matcher)
151155
is_string($matcher['stringMatcherData']) ?
152156
$matcher['stringMatcherData'] : null;
153157
return new LessThanOrEqualToSemver($data, $negate, $attribute);
158+
case self::BETWEEN_SEMVER:
159+
$data = (isset($matcher['betweenStringMatcherData']) &&
160+
is_array($matcher['betweenStringMatcherData']))
161+
? $matcher['betweenStringMatcherData'] : null;
162+
return new BetweenSemver($data, $negate, $attribute);
163+
case self::IN_LIST_SEMVER:
164+
$data = (isset($matcher['whitelistMatcherData']['whitelist']) &&
165+
is_array($matcher['whitelistMatcherData']['whitelist']))
166+
? $matcher['whitelistMatcherData']['whitelist'] : null;
167+
return new InListSemver($data, $negate, $attribute);
154168
// @codeCoverageIgnoreStart
155169
default:
156170
throw new UnsupportedMatcherException("Unable to create matcher for matcher type: " . $matcherType);
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
namespace SplitIO\Grammar\Condition\Matcher;
3+
4+
use SplitIO\Grammar\Condition\Matcher;
5+
use SplitIO\Grammar\Semver\Semver;
6+
use SplitIO\Grammar\Semver\SemverComparer;
7+
use SplitIO\Split as SplitApp;
8+
9+
class BetweenSemver extends AbstractMatcher
10+
{
11+
protected $startTarget = null;
12+
protected $endTarget = null;
13+
14+
public function __construct($data, $negate = false, $attribute = null)
15+
{
16+
parent::__construct(Matcher::BETWEEN_SEMVER, $negate, $attribute);
17+
18+
$this->startTarget = Semver::build($data['start']);
19+
$this->endTarget = Semver::build($data['end']);
20+
}
21+
22+
/**
23+
*
24+
* @param mixed $key
25+
*/
26+
protected function evalKey($key)
27+
{
28+
if ($key == null || !is_string($key) || $this->startTarget == null || $this->endTarget == null) {
29+
return false;
30+
}
31+
32+
$keySemver = Semver::build($key);
33+
if ($keySemver == null) {
34+
return false;
35+
}
36+
37+
$result = SemverComparer::do($keySemver, $this->startTarget) >= 0
38+
&& SemverComparer::do($keySemver, $this->endTarget) <= 0;
39+
40+
SplitApp::logger()->debug($this->startTarget->getVersion() . " <= "
41+
. $keySemver->getVersion() . " <= " . $this->endTarget->getVersion()
42+
. " | Result: " . $result);
43+
44+
return $result;
45+
}
46+
}

src/SplitIO/Grammar/Condition/Matcher/EqualToSemver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ protected function evalKey($key)
3232
return false;
3333
}
3434

35-
$result = SemverComparer::Equals($this->toCompare, $keySemver);
35+
$result = SemverComparer::equals($this->toCompare, $keySemver);
3636

3737
SplitApp::logger()->debug($this->toCompare->getVersion() . " == "
3838
. $keySemver->getVersion() . " | Result: " . $result);
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
<?php
2+
namespace SplitIO\Grammar\Condition\Matcher;
3+
4+
use SplitIO\Grammar\Condition\Matcher;
5+
use SplitIO\Grammar\Semver\Semver;
6+
use SplitIO\Grammar\Semver\SemverComparer;
7+
8+
class InListSemver extends AbstractMatcher
9+
{
10+
private $targetList;
11+
12+
public function __construct($targetList, $negate = false, $attribute = null)
13+
{
14+
$this->targetList = array();
15+
parent::__construct(Matcher::IN_LIST_SEMVER, $negate, $attribute);
16+
17+
if (is_array($targetList)) {
18+
foreach ($targetList as $item) {
19+
$toAdd = Semver::build($item);
20+
21+
if ($toAdd != null) {
22+
array_push($this->targetList, $toAdd);
23+
}
24+
}
25+
}
26+
}
27+
28+
/**
29+
*
30+
* @param mixed $key
31+
*/
32+
protected function evalKey($key)
33+
{
34+
if ($key == null || !is_string($key) || count($this->targetList) == 0) {
35+
return false;
36+
}
37+
38+
$keySemver = Semver::build($key);
39+
if ($keySemver == null) {
40+
return false;
41+
}
42+
43+
foreach ($this->targetList as $item) {
44+
if (SemverComparer::equals($keySemver, $item)) {
45+
return true;
46+
}
47+
}
48+
49+
return false;
50+
}
51+
}

src/SplitIO/Grammar/Condition/Matcher/LessThanOrEqualToSemver.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class LessThanOrEqualToSemver extends AbstractMatcher
1212

1313
public function __construct($toCompare, $negate = false, $attribute = null)
1414
{
15-
parent::__construct(Matcher::GREATER_THAN_OR_EQUAL_TO_SEMVER, $negate, $attribute);
15+
parent::__construct(Matcher::LESS_THAN_OR_EQUAL_TO_SEMVER, $negate, $attribute);
1616

1717
$this->target = Semver::build($toCompare);
1818
}

tests/Suite/Matchers/MatchersTest.php

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,69 @@ public function testLessThanOrEqualToSemverMatcher()
592592
$this->assertFalse($matcher->evaluate('2.2.2'));
593593
}
594594

595+
public function testBetweenSemverMatcher()
596+
{
597+
$this->setupSplitApp();
598+
599+
$condition = array(
600+
'matcherType' => 'BETWEEN_SEMVER',
601+
'betweenStringMatcherData' => array(
602+
'start' => '11.11.11',
603+
'end' => '22.22.22',
604+
)
605+
);
606+
607+
$matcher = Matcher::factory($condition);
608+
$this->assertTrue($matcher->evaluate('20.2.2'));
609+
$this->assertTrue($matcher->evaluate('16.5.6'));
610+
$this->assertTrue($matcher->evaluate('19.0.1'));
611+
$this->assertFalse($matcher->evaluate(null));
612+
$this->assertFalse($matcher->evaluate(''));
613+
$this->assertFalse($matcher->evaluate('22.22.25'));
614+
$this->assertFalse($matcher->evaluate('10.0.0'));
615+
616+
$condition = array(
617+
'matcherType' => 'BETWEEN_SEMVER',
618+
'betweenStringMatcherData' => null
619+
);
620+
621+
$matcher = Matcher::factory($condition);
622+
$this->assertFalse($matcher->evaluate('2.2.2'));
623+
}
624+
625+
public function testInListSemverMatcher()
626+
{
627+
$this->setupSplitApp();
628+
629+
$condition = array(
630+
'matcherType' => 'IN_LIST_SEMVER',
631+
'whitelistMatcherData' => array(
632+
'whitelist' => array(
633+
'1.1.1',
634+
'2.2.2',
635+
'3.3.3',
636+
)
637+
)
638+
);
639+
640+
$matcher = Matcher::factory($condition);
641+
$this->assertTrue($matcher->evaluate('1.1.1'));
642+
$this->assertTrue($matcher->evaluate('2.2.2'));
643+
$this->assertTrue($matcher->evaluate('3.3.3'));
644+
$this->assertFalse($matcher->evaluate(null));
645+
$this->assertFalse($matcher->evaluate(''));
646+
$this->assertFalse($matcher->evaluate('4.22.25'));
647+
$this->assertFalse($matcher->evaluate('10.0.0'));
648+
649+
$condition = array(
650+
'matcherType' => 'IN_LIST_SEMVER',
651+
'whitelistMatcherData' => null
652+
);
653+
654+
$matcher = Matcher::factory($condition);
655+
$this->assertFalse($matcher->evaluate('2.2.2'));
656+
}
657+
595658
public static function tearDownAfterClass(): void
596659
{
597660
Utils\Utils::cleanCache();

tests/Suite/Sdk/SdkClientTest.php

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,36 @@ public function testClientSemverMatchers()
300300
$this->validateLastImpression($redisClient, 'ltoet_semver_flag', 'user1', 'off');
301301
$this->assertEquals('v1', $splitSdk->getTreatment('user1', 'ltoet_semver_flag', array('version' => '11.22.33-rc.1')));
302302
$this->validateLastImpression($redisClient, 'ltoet_semver_flag', 'user1', 'v1');
303+
304+
//Assertions BetweenSemver
305+
$this->assertEquals('v1', $splitSdk->getTreatment('user1', 'between_semver_flag', array('version' => '6.9.0')));
306+
$this->validateLastImpression($redisClient, 'between_semver_flag', 'user1', 'v1');
307+
$this->assertEquals('v1', $splitSdk->getTreatment('user1', 'between_semver_flag', array('version' => '8.0.0-rc.22')));
308+
$this->validateLastImpression($redisClient, 'between_semver_flag', 'user1', 'v1');
309+
$this->assertEquals('v1', $splitSdk->getTreatment('user1', 'between_semver_flag', array('version' => '9.0.0+metadata')));
310+
$this->validateLastImpression($redisClient, 'between_semver_flag', 'user1', 'v1');
311+
$this->assertEquals('v1', $splitSdk->getTreatment('user1', 'between_semver_flag', array('version' => '10.4.4-rc.1+metadata')));
312+
$this->validateLastImpression($redisClient, 'between_semver_flag', 'user1', 'v1');
313+
$this->assertEquals('off', $splitSdk->getTreatment('user1', 'between_semver_flag', array('version' => '10.4.7-rc.1+metadata')));
314+
$this->validateLastImpression($redisClient, 'between_semver_flag', 'user1', 'off');
315+
$this->assertEquals('off', $splitSdk->getTreatment('user1', 'between_semver_flag', array('version' => '1.4.7')));
316+
$this->validateLastImpression($redisClient, 'between_semver_flag', 'user1', 'off');
317+
318+
//Assertions InListSemver
319+
//
320+
$this->assertEquals('v1', $splitSdk->getTreatment('user1', 'inlist_semver_flag', array('version' => '6.7.8')));
321+
$this->validateLastImpression($redisClient, 'inlist_semver_flag', 'user1', 'v1');
322+
$this->assertEquals('v1', $splitSdk->getTreatment('user1', 'inlist_semver_flag', array('version' => '1.1.1-alpha')));
323+
$this->validateLastImpression($redisClient, 'inlist_semver_flag', 'user1', 'v1');
324+
$this->assertEquals('v1', $splitSdk->getTreatment('user1', 'inlist_semver_flag', array('version' => '2.2.2+meta')));
325+
$this->validateLastImpression($redisClient, 'inlist_semver_flag', 'user1', 'v1');
326+
327+
$this->assertEquals('off', $splitSdk->getTreatment('user1', 'inlist_semver_flag', array('version' => '2.2.2')));
328+
$this->validateLastImpression($redisClient, 'inlist_semver_flag', 'user1', 'off');
329+
$this->assertEquals('off', $splitSdk->getTreatment('user1', 'inlist_semver_flag', array('version' => '1.1.1')));
330+
$this->validateLastImpression($redisClient, 'inlist_semver_flag', 'user1', 'off');
331+
$this->assertEquals('off', $splitSdk->getTreatment('user1', 'inlist_semver_flag', array('version' => '8.6.0-rc.2')));
332+
$this->validateLastImpression($redisClient, 'inlist_semver_flag', 'user1', 'off');
303333
}
304334

305335
public function testClientWithUnsupportedMatcher()

tests/Suite/Sdk/files/splitChanges.json

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,108 @@
640640
]
641641
}
642642
]
643+
},
644+
{
645+
"orgId": null,
646+
"environment": null,
647+
"trafficTypeId": null,
648+
"trafficTypeName": null,
649+
"name": "between_semver_flag",
650+
"seed": -1222652054,
651+
"status": "ACTIVE",
652+
"killed": false,
653+
"defaultTreatment": "off",
654+
"sets": [],
655+
"configurations": {
656+
"on": "{\"size\":15,\"test\":20}",
657+
"of": "{\"size\":15,\"defTreatment\":true}"
658+
},
659+
"conditions": [
660+
{
661+
"matcherGroup": {
662+
"combiner": "AND",
663+
"matchers": [
664+
{
665+
"keySelector": {
666+
"trafficType": "user",
667+
"attribute": "version"
668+
},
669+
"matcherType": "BETWEEN_SEMVER",
670+
"negate": false,
671+
"userDefinedSegmentMatcherData": null,
672+
"whitelistMatcherData": null,
673+
"betweenStringMatcherData": {
674+
"start": "5.0.0",
675+
"end": "10.4.6"
676+
}
677+
}
678+
]
679+
},
680+
"partitions": [
681+
{
682+
"treatment": "v1",
683+
"size": 100
684+
},
685+
{
686+
"treatment": "v2",
687+
"size": 0
688+
}
689+
]
690+
}
691+
]
692+
},
693+
{
694+
"orgId": null,
695+
"environment": null,
696+
"trafficTypeId": null,
697+
"trafficTypeName": null,
698+
"name": "inlist_semver_flag",
699+
"seed": -1222652054,
700+
"status": "ACTIVE",
701+
"killed": false,
702+
"defaultTreatment": "off",
703+
"sets": [],
704+
"configurations": {
705+
"on": "{\"size\":15,\"test\":20}",
706+
"of": "{\"size\":15,\"defTreatment\":true}"
707+
},
708+
"conditions": [
709+
{
710+
"matcherGroup": {
711+
"combiner": "AND",
712+
"matchers": [
713+
{
714+
"keySelector": {
715+
"trafficType": "user",
716+
"attribute": "version"
717+
},
718+
"matcherType": "IN_LIST_SEMVER",
719+
"negate": false,
720+
"userDefinedSegmentMatcherData": null,
721+
"whitelistMatcherData": {
722+
"whitelist": [
723+
"6.7.8",
724+
"2.2.2+meta",
725+
"1.1.1-alpha",
726+
"8.6.0-rc.1",
727+
"9.6.0-beta.1"
728+
]
729+
}
730+
}
731+
]
732+
},
733+
"partitions": [
734+
{
735+
"treatment": "v1",
736+
"size": 100
737+
},
738+
{
739+
"treatment": "v2",
740+
"size": 0
741+
}
742+
]
743+
}
744+
]
643745
}
644746
],
645747
"since": -1,

0 commit comments

Comments
 (0)