Skip to content

Commit

Permalink
Merge pull request #236 from splitio/sdks-8242-semver-imp
Browse files Browse the repository at this point in the history
Semver implementation
  • Loading branch information
sanzmauro authored May 14, 2024
2 parents ffacf6f + 9760831 commit 3e1d3fa
Show file tree
Hide file tree
Showing 26 changed files with 1,423 additions and 17 deletions.
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ jobs:
- '8.0'
- '8.1'
- '8.2'
- '8.3'
steps:
- name: Checkout code
uses: actions/checkout@v3
Expand Down
5 changes: 5 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
7.3.0 (May 14, 2024)
- Added support for targeting rules based on semantic versions (https://semver.org/).
- Added the logic to handle correctly when the SDK receives an unsupported Matcher type.
- Updated dependencies to allow `symfony/yaml` 7.

7.2.1 (March 6, 2024)
- Fix error on duplicated file flagSetsValidator.

Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"php": ">=7.3",
"psr/log": "1 - 3",
"predis/predis": "^2.0",
"symfony/yaml": "^5.3|^6.0"
"symfony/yaml": "^5.3|^6.0|^7.0"
},

"require-dev": {
Expand Down
7 changes: 7 additions & 0 deletions src/SplitIO/Exception/SemverParseException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
namespace SplitIO\Exception;

class SemverParseException extends \LogicException
{

}
7 changes: 7 additions & 0 deletions src/SplitIO/Exception/UnsupportedMatcherException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
namespace SplitIO\Exception;

class UnsupportedMatcherException extends \LogicException
{

}
33 changes: 30 additions & 3 deletions src/SplitIO/Grammar/Condition.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@
namespace SplitIO\Grammar;

use SplitIO\Exception\InvalidMatcherException;
use SplitIO\Split as SplitApp;
use SplitIO\Grammar\Condition\Combiner\AndCombiner;
use SplitIO\Grammar\Condition\Combiner\CombinerEnum;
use SplitIO\Grammar\Condition\Combiner\Factor\NotFactor;
use SplitIO\Grammar\Condition\ConditionTypeEnum;
use SplitIO\Grammar\Condition\Matcher;
use SplitIO\Grammar\Condition\Partition;
use SplitIO\Grammar\Condition\Matcher\AbstractMatcher;
use SplitIO\Grammar\Condition\ConditionTypeEnum;
use SplitIO\Grammar\Condition\Matcher\Dependency;
use SplitIO\Grammar\Condition\Partition;
use SplitIO\Grammar\Condition\Partition\TreatmentEnum;
use SplitIO\Sdk\Impressions\ImpressionLabel;
use SplitIO\Split as SplitApp;

class Condition
{
Expand Down Expand Up @@ -115,6 +117,31 @@ public function match($key, array $attributes = null, $bucketingKey = null)
return false;
}

public static function getDefaultCondition()
{
return new Condition(array(
'conditionType' => ConditionTypeEnum::WHITELIST,
'matcherGroup' => array(
'combiner' => CombinerEnum::_AND,
'matchers' => array(
array(
'matcherType' => Matcher::ALL_KEYS,
'negate' => false,
'userDefinedSegmentMatcherData' => null,
'whitelistMatcherData' => null
)
)
),
'partitions' => array(
array(
'treatment' => TreatmentEnum::CONTROL,
'size' => 100
)
),
'label' => ImpressionLabel::UNSUPPORTED_MATCHER
));
}

/**
* @return array|null
*/
Expand Down
38 changes: 37 additions & 1 deletion src/SplitIO/Grammar/Condition/Matcher.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
<?php
namespace SplitIO\Grammar\Condition;

use SplitIO\Exception\UnsupportedMatcherException;
use SplitIO\Grammar\Condition\Matcher\All;
use SplitIO\Grammar\Condition\Matcher\Between;
use SplitIO\Grammar\Condition\Matcher\BetweenSemver;
use SplitIO\Grammar\Condition\Matcher\EqualTo;
use SplitIO\Grammar\Condition\Matcher\EqualToSemver;
use SplitIO\Grammar\Condition\Matcher\GreaterThanOrEqualTo;
use SplitIO\Grammar\Condition\Matcher\GreaterThanOrEqualToSemver;
use SplitIO\Grammar\Condition\Matcher\InListSemver;
use SplitIO\Grammar\Condition\Matcher\LessThanOrEqualTo;
use SplitIO\Grammar\Condition\Matcher\LessThanOrEqualToSemver;
use SplitIO\Grammar\Condition\Matcher\Segment;
use SplitIO\Grammar\Condition\Matcher\Whitelist;
use SplitIO\Grammar\Condition\Matcher\StartsWith;
Expand Down Expand Up @@ -39,6 +45,11 @@ class Matcher
const IN_SPLIT_TREATMENT = 'IN_SPLIT_TREATMENT';
const EQUAL_TO_BOOLEAN = 'EQUAL_TO_BOOLEAN';
const MATCHES_STRING = 'MATCHES_STRING';
const EQUAL_TO_SEMVER = 'EQUAL_TO_SEMVER';
const GREATER_THAN_OR_EQUAL_TO_SEMVER = 'GREATER_THAN_OR_EQUAL_TO_SEMVER';
const LESS_THAN_OR_EQUAL_TO_SEMVER = 'LESS_THAN_OR_EQUAL_TO_SEMVER';
const BETWEEN_SEMVER = 'BETWEEN_SEMVER';
const IN_LIST_SEMVER = 'IN_LIST_SEMVER';

public static function factory($matcher)
{
Expand Down Expand Up @@ -129,9 +140,34 @@ public static function factory($matcher)
is_string($matcher['stringMatcherData']) ?
$matcher['stringMatcherData'] : null;
return new Regex($data, $negate, $attribute);
case self::EQUAL_TO_SEMVER:
$data = isset($matcher['stringMatcherData']) &&
is_string($matcher['stringMatcherData']) ?
$matcher['stringMatcherData'] : null;
return new EqualToSemver($data, $negate, $attribute);
case self::GREATER_THAN_OR_EQUAL_TO_SEMVER:
$data = isset($matcher['stringMatcherData']) &&
is_string($matcher['stringMatcherData']) ?
$matcher['stringMatcherData'] : null;
return new GreaterThanOrEqualToSemver($data, $negate, $attribute);
case self::LESS_THAN_OR_EQUAL_TO_SEMVER:
$data = isset($matcher['stringMatcherData']) &&
is_string($matcher['stringMatcherData']) ?
$matcher['stringMatcherData'] : null;
return new LessThanOrEqualToSemver($data, $negate, $attribute);
case self::BETWEEN_SEMVER:
$data = (isset($matcher['betweenStringMatcherData']) &&
is_array($matcher['betweenStringMatcherData']))
? $matcher['betweenStringMatcherData'] : null;
return new BetweenSemver($data, $negate, $attribute);
case self::IN_LIST_SEMVER:
$data = (isset($matcher['whitelistMatcherData']['whitelist']) &&
is_array($matcher['whitelistMatcherData']['whitelist']))
? $matcher['whitelistMatcherData']['whitelist'] : null;
return new InListSemver($data, $negate, $attribute);
// @codeCoverageIgnoreStart
default:
return null;
throw new UnsupportedMatcherException("Unable to create matcher for matcher type: " . $matcherType);
}
// @codeCoverageIgnoreEnd
}
Expand Down
48 changes: 48 additions & 0 deletions src/SplitIO/Grammar/Condition/Matcher/BetweenSemver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
namespace SplitIO\Grammar\Condition\Matcher;

use SplitIO\Grammar\Condition\Matcher;
use SplitIO\Grammar\Semver\Semver;
use SplitIO\Grammar\Semver\SemverComparer;
use SplitIO\Split as SplitApp;

class BetweenSemver extends AbstractMatcher
{
protected $startTarget = null;
protected $endTarget = null;

public function __construct($data, $negate = false, $attribute = null)
{
parent::__construct(Matcher::BETWEEN_SEMVER, $negate, $attribute);

if ($data != null) {
$this->startTarget = Semver::build($data['start']);
$this->endTarget = Semver::build($data['end']);
}
}

/**
*
* @param mixed $key
*/
protected function evalKey($key)
{
if ($key == null || !is_string($key) || $this->startTarget == null || $this->endTarget == null) {
return false;
}

$keySemver = Semver::build($key);
if ($keySemver == null) {
return false;
}

$result = SemverComparer::do($keySemver, $this->startTarget) >= 0
&& SemverComparer::do($keySemver, $this->endTarget) <= 0;

SplitApp::logger()->debug($this->startTarget->getVersion() . " <= "
. $keySemver->getVersion() . " <= " . $this->endTarget->getVersion()
. " | Result: " . $result);

return $result;
}
}
42 changes: 42 additions & 0 deletions src/SplitIO/Grammar/Condition/Matcher/EqualToSemver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php
namespace SplitIO\Grammar\Condition\Matcher;

use SplitIO\Grammar\Condition\Matcher;
use SplitIO\Grammar\Semver\Semver;
use SplitIO\Grammar\Semver\SemverComparer;
use SplitIO\Split as SplitApp;

class EqualToSemver extends AbstractMatcher
{
private $toCompare;

public function __construct($toCompare, $negate = false, $attribute = null)
{
parent::__construct(Matcher::EQUAL_TO_SEMVER, $negate, $attribute);

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

/**
*
* @param mixed $key
*/
protected function evalKey($key)
{
if ($key == null || $this->toCompare == null || !is_string($key)) {
return false;
}

$keySemver = Semver::build($key);
if ($keySemver == null) {
return false;
}

$result = SemverComparer::equals($this->toCompare, $keySemver);

SplitApp::logger()->debug($this->toCompare->getVersion() . " == "
. $keySemver->getVersion() . " | Result: " . $result);

return $result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php
namespace SplitIO\Grammar\Condition\Matcher;

use SplitIO\Grammar\Condition\Matcher;
use SplitIO\Grammar\Semver\Semver;
use SplitIO\Grammar\Semver\SemverComparer;
use SplitIO\Split as SplitApp;

class GreaterThanOrEqualToSemver extends AbstractMatcher
{
private $target;

public function __construct($toCompare, $negate = false, $attribute = null)
{
parent::__construct(Matcher::GREATER_THAN_OR_EQUAL_TO_SEMVER, $negate, $attribute);

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

/**
*
* @param mixed $key
*/
protected function evalKey($key)
{
if ($key == null || $this->target == null || !is_string($key)) {
return false;
}

$keySemver = Semver::build($key);
if ($keySemver == null) {
return false;
}

$result = SemverComparer::do($keySemver, $this->target) >= 0;

SplitApp::logger()->debug($this->target->getVersion() . " >= "
. $keySemver->getVersion() . " | Result: " . $result);

return $result;
}
}
51 changes: 51 additions & 0 deletions src/SplitIO/Grammar/Condition/Matcher/InListSemver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php
namespace SplitIO\Grammar\Condition\Matcher;

use SplitIO\Grammar\Condition\Matcher;
use SplitIO\Grammar\Semver\Semver;
use SplitIO\Grammar\Semver\SemverComparer;

class InListSemver extends AbstractMatcher
{
private $targetList;

public function __construct($targetList, $negate = false, $attribute = null)
{
$this->targetList = array();
parent::__construct(Matcher::IN_LIST_SEMVER, $negate, $attribute);

if (is_array($targetList)) {
foreach ($targetList as $item) {
$toAdd = Semver::build($item);

if ($toAdd != null) {
array_push($this->targetList, $toAdd);
}
}
}
}

/**
*
* @param mixed $key
*/
protected function evalKey($key)
{
if ($key == null || !is_string($key) || count($this->targetList) == 0) {
return false;
}

$keySemver = Semver::build($key);
if ($keySemver == null) {
return false;
}

foreach ($this->targetList as $item) {
if (SemverComparer::equals($keySemver, $item)) {
return true;
}
}

return false;
}
}
42 changes: 42 additions & 0 deletions src/SplitIO/Grammar/Condition/Matcher/LessThanOrEqualToSemver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php
namespace SplitIO\Grammar\Condition\Matcher;

use SplitIO\Grammar\Condition\Matcher;
use SplitIO\Grammar\Semver\Semver;
use SplitIO\Grammar\Semver\SemverComparer;
use SplitIO\Split as SplitApp;

class LessThanOrEqualToSemver extends AbstractMatcher
{
private $target;

public function __construct($toCompare, $negate = false, $attribute = null)
{
parent::__construct(Matcher::LESS_THAN_OR_EQUAL_TO_SEMVER, $negate, $attribute);

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

/**
*
* @param mixed $key
*/
protected function evalKey($key)
{
if ($key == null || $this->target == null || !is_string($key)) {
return false;
}

$keySemver = Semver::build($key);
if ($keySemver == null) {
return false;
}

$result = SemverComparer::do($keySemver, $this->target) <= 0;

SplitApp::logger()->debug($this->target->getVersion() . " <= "
. $keySemver->getVersion() . " | Result: " . $result);

return $result;
}
}
Loading

0 comments on commit 3e1d3fa

Please sign in to comment.