Skip to content

Commit 0e461ea

Browse files
authored
(U2C #10) support includedContexts/excludedContexts in segment (#111)
1 parent 5d88c20 commit 0e461ea

File tree

7 files changed

+137
-15
lines changed

7 files changed

+137
-15
lines changed

Makefile

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,6 @@ TEST_HARNESS_PARAMS := $(TEST_HARNESS_PARAMS) \
2424
-skip 'evaluation/parameterized/attribute references' \
2525
-skip 'evaluation/parameterized/bad attribute reference errors' \
2626
-skip 'evaluation/parameterized/prerequisites' \
27-
-skip 'evaluation/parameterized/segment match/included list is specific to user kind' \
28-
-skip 'evaluation/parameterized/segment match/includedContexts' \
29-
-skip 'evaluation/parameterized/segment match/excluded list is specific to user kind' \
30-
-skip 'evaluation/parameterized/segment match/excludedContexts' \
3127
-skip 'evaluation/parameterized/segment recursion' \
3228
-skip 'events'
3329

src/LaunchDarkly/Impl/Evaluation/Evaluator.php

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,22 @@ private function clauseMatchesContext(Clause $clause, LDContext $context): bool
189189

190190
private function segmentMatchesContext(Segment $segment, LDContext $context): bool
191191
{
192-
$key = $context->getKey();
193-
if (in_array($key, $segment->getIncluded(), true)) {
192+
if (EvaluatorHelpers::contextKeyIsInTargetList($context, null, $segment->getIncluded())) {
194193
return true;
195194
}
196-
if (in_array($key, $segment->getExcluded(), true)) {
195+
foreach ($segment->getIncludedContexts() as $t) {
196+
if (EvaluatorHelpers::contextKeyIsInTargetList($context, $t->getContextKind(), $t->getValues())) {
197+
return true;
198+
}
199+
}
200+
if (EvaluatorHelpers::contextKeyIsInTargetList($context, null, $segment->getExcluded())) {
197201
return false;
198202
}
203+
foreach ($segment->getExcludedContexts() as $t) {
204+
if (EvaluatorHelpers::contextKeyIsInTargetList($context, $t->getContextKind(), $t->getValues())) {
205+
return false;
206+
}
207+
}
199208
foreach ($segment->getRules() as $rule) {
200209
if ($this->segmentRuleMatchesContext($rule, $context, $segment->getKey(), $segment->getSalt())) {
201210
return true;
@@ -210,6 +219,7 @@ private function segmentRuleMatchesContext(
210219
string $segmentKey,
211220
string $segmentSalt
212221
): bool {
222+
$rulej = print_r($rule, true);
213223
foreach ($rule->getClauses() as $clause) {
214224
if (!EvaluatorHelpers::matchClauseWithoutSegments($clause, $context)) {
215225
return false;

src/LaunchDarkly/Impl/Model/Segment.php

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@ class Segment
1717
protected string $_key;
1818
protected int $_version;
1919
/** @var string[] */
20-
protected array $_included = [];
20+
protected array $_included;
2121
/** @var string[] */
22-
protected array $_excluded = [];
22+
protected array $_excluded;
23+
/** @var SegmentTarget[] */
24+
protected array $_includedContexts;
25+
/** @var SegmentTarget[] */
26+
protected array $_excludedContexts;
2327
protected string $_salt;
2428
/** @var SegmentRule[] */
2529
protected array $_rules = [];
@@ -30,6 +34,8 @@ public function __construct(
3034
int $version,
3135
array $included,
3236
array $excluded,
37+
array $includedContexts,
38+
array $excludedContexts,
3339
string $salt,
3440
array $rules,
3541
bool $deleted
@@ -38,6 +44,8 @@ public function __construct(
3844
$this->_version = $version;
3945
$this->_included = $included;
4046
$this->_excluded = $excluded;
47+
$this->_includedContexts = $includedContexts;
48+
$this->_excludedContexts = $excludedContexts;
4149
$this->_salt = $salt;
4250
$this->_rules = $rules;
4351
$this->_deleted = $deleted;
@@ -51,6 +59,8 @@ public static function getDecoder(): \Closure
5159
$v['version'],
5260
$v['included'] ?: [],
5361
$v['excluded'] ?: [],
62+
array_map(SegmentTarget::getDecoder(), ($v['includedContexts'] ?? null) ?: []),
63+
array_map(SegmentTarget::getDecoder(), ($v['excludedContexts'] ?? null) ?: []),
5464
$v['salt'],
5565
array_map(SegmentRule::getDecoder(), $v['rules'] ?: []),
5666
$v['deleted']
@@ -67,21 +77,36 @@ public function isDeleted(): bool
6777
return $this->_deleted;
6878
}
6979

80+
/** @return string[] */
7081
public function getExcluded(): array
7182
{
7283
return $this->_excluded;
7384
}
7485

86+
/** @return SegmentTarget[] */
87+
public function getExcludedContexts(): array
88+
{
89+
return $this->_excludedContexts;
90+
}
91+
92+
/** @return string[] */
7593
public function getIncluded(): array
7694
{
7795
return $this->_included;
7896
}
7997

98+
/** @return SegmentTarget[] */
99+
public function getIncludedContexts(): array
100+
{
101+
return $this->_includedContexts;
102+
}
103+
80104
public function getKey(): string
81105
{
82106
return $this->_key;
83107
}
84108

109+
/** @return SegmentRule[] */
85110
public function getRules(): array
86111
{
87112
return $this->_rules;
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace LaunchDarkly\Impl\Model;
6+
7+
/**
8+
* Internal data model class that describes a segment targeting list.
9+
*
10+
* Application code should never need to reference the data model directly.
11+
*
12+
* @ignore
13+
* @internal
14+
*/
15+
class SegmentTarget
16+
{
17+
private ?string $_contextKind;
18+
/** @var string[] */
19+
private array $_values;
20+
21+
public function __construct(?string $contextKind, array $values)
22+
{
23+
$this->_contextKind = $contextKind;
24+
$this->_values = $values;
25+
}
26+
27+
public static function getDecoder(): \Closure
28+
{
29+
return fn (array $v) => new SegmentTarget($v['contextKind'] ?? null, $v['values']);
30+
}
31+
32+
public function getContextKind(): ?string
33+
{
34+
return $this->_contextKind;
35+
}
36+
37+
/**
38+
* @return \string[]
39+
*/
40+
public function getValues(): array
41+
{
42+
return $this->_values;
43+
}
44+
}

tests/Impl/Evaluation/EvaluatorClauseTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ public function testClauseMatchByKindAttribute()
132132
public function testSegmentMatchClauseRetrievesSegmentFromStore()
133133
{
134134
$context = LDContext::create('key');
135-
$segment = ModelBuilders::segmentBuilder('segkey')->included([$context->getKey()])->build();
135+
$segment = ModelBuilders::segmentBuilder('segkey')->included($context->getKey())->build();
136136
$requester = new MockFeatureRequester();
137137
$requester->addSegment($segment);
138138
$evaluator = new Evaluator($requester);

tests/Impl/Evaluation/EvaluatorSegmentTest.php

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class EvaluatorSegmentTest extends TestCase
1717
public function testExplicitIncludeContext()
1818
{
1919
global $defaultContext;
20-
$segment = ModelBuilders::segmentBuilder('test')->included([$defaultContext->getKey()])->build();
20+
$segment = ModelBuilders::segmentBuilder('test')->included($defaultContext->getKey())->build();
2121
$this->assertTrue(self::segmentMatchesContext($segment, $defaultContext));
2222
}
2323

@@ -26,7 +26,7 @@ public function testExplicitExcludeContext()
2626
global $defaultContext;
2727
$segment = ModelBuilders::segmentBuilder('test')
2828
->rule(ModelBuilders::segmentRuleMatchingContext($defaultContext))
29-
->excluded([$defaultContext->getKey()])
29+
->excluded($defaultContext->getKey())
3030
->build();
3131
$this->assertFalse(self::segmentMatchesContext($segment, $defaultContext));
3232
}
@@ -35,11 +35,39 @@ public function testExplicitIncludeHasPrecedence()
3535
{
3636
global $defaultContext;
3737
$segment = ModelBuilders::segmentBuilder('test')
38-
->included([$defaultContext->getKey()])->excluded([$defaultContext->getKey()])
38+
->included($defaultContext->getKey())->excluded($defaultContext->getKey())
3939
->build();
4040
$this->assertTrue(self::segmentMatchesContext($segment, $defaultContext));
4141
}
4242

43+
public function testIncludedKeyForContextKind()
44+
{
45+
$c1 = LDContext::create('key1', 'kind1');
46+
$c2 = LDContext::create('key2', 'kind2');
47+
$multi = LDContext::createMulti($c1, $c2);
48+
$segment = ModelBuilders::segmentBuilder('test')
49+
->includedContexts('kind1', 'key1')
50+
->build();
51+
$this->assertTrue(self::segmentMatchesContext($segment, $c1));
52+
$this->assertFalse(self::segmentMatchesContext($segment, $c2));
53+
$this->assertTrue(self::segmentMatchesContext($segment, $multi));
54+
}
55+
56+
public function excludedKeyForContextKind()
57+
{
58+
$c1 = LDContext::create('key1', 'kind1');
59+
$c2 = LDContext::create('key2', 'kind2');
60+
$multi = LDContext::createMulti($c1, $c2);
61+
$segment = ModelBuilders::segmentBuilder('test')
62+
->excludedContexts('kind1', 'key1')
63+
->rule(ModelBuilders::segmentRuleMatchingContext($c1))
64+
->rule(ModelBuilders::segmentRuleMatchingContext($c2))
65+
->build();
66+
$this->assertFalse(self::segmentMatchesContext($segment, $c1));
67+
$this->assertTrue(self::segmentMatchesContext($segment, $c2));
68+
$this->assertFalse(self::segmentMatchesContext($segment, $multi));
69+
}
70+
4371
public function testMatchingRuleWithFullRollout()
4472
{
4573
global $defaultContext;

tests/SegmentBuilder.php

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use LaunchDarkly\Impl\Model\Segment;
66
use LaunchDarkly\Impl\Model\SegmentRule;
7+
use LaunchDarkly\Impl\Model\SegmentTarget;
78

89
class SegmentBuilder
910
{
@@ -13,6 +14,10 @@ class SegmentBuilder
1314
private array $_included = [];
1415
/** @var string[] */
1516
private array $_excluded = [];
17+
/** @var SegmentTarget[] */
18+
private array $_includedContexts = [];
19+
/** @var SegmentTarget[] */
20+
private array $_excludedContexts = [];
1621
private string $_salt = '';
1722
/** @var SegmentRule[] */
1823
private array $_rules = [];
@@ -30,24 +35,38 @@ public function build(): Segment
3035
$this->_version,
3136
$this->_included,
3237
$this->_excluded,
38+
$this->_includedContexts,
39+
$this->_excludedContexts,
3340
$this->_salt,
3441
$this->_rules,
3542
$this->_deleted
3643
);
3744
}
3845

39-
public function excluded(array $excluded): SegmentBuilder
46+
public function excluded(string ...$excluded): SegmentBuilder
4047
{
4148
$this->_excluded = $excluded;
4249
return $this;
4350
}
4451

45-
public function included(array $included): SegmentBuilder
52+
public function excludedContexts(string $contextKind, string ...$excluded): SegmentBuilder
53+
{
54+
$this->_excludedContexts[] = new SegmentTarget($contextKind, $excluded);
55+
return $this;
56+
}
57+
58+
public function included(string ...$included): SegmentBuilder
4659
{
4760
$this->_included = $included;
4861
return $this;
4962
}
5063

64+
public function includedContexts(string $contextKind, string ...$included): SegmentBuilder
65+
{
66+
$this->_includedContexts[] = new SegmentTarget($contextKind, $included);
67+
return $this;
68+
}
69+
5170
public function rule(SegmentRule $rule): SegmentBuilder
5271
{
5372
$this->_rules[] = $rule;

0 commit comments

Comments
 (0)