Skip to content

Commit ddbcc84

Browse files
committed
feat: Change fractional custom op from percentage-based to relative weighting. open-feature#828
Signed-off-by: Simon Schrottner <[email protected]>
1 parent cf77d56 commit ddbcc84

File tree

2 files changed

+72
-26
lines changed
  • providers/flagd/src
    • main/java/dev/openfeature/contrib/providers/flagd/resolver/process/targeting
    • test/java/dev/openfeature/contrib/providers/flagd/resolver/process/targeting

2 files changed

+72
-26
lines changed

providers/flagd/src/main/java/dev/openfeature/contrib/providers/flagd/resolver/process/targeting/Fractional.java

+25-21
Original file line numberDiff line numberDiff line change
@@ -50,37 +50,32 @@ public Object evaluate(List arguments, Object data) throws JsonLogicEvaluationEx
5050
}
5151

5252
final List<FractionProperty> propertyList = new ArrayList<>();
53+
int totalWeight = 0;
5354

54-
double distribution = 0;
5555
try {
5656
for (Object dist : distibutions) {
5757
FractionProperty fractionProperty = new FractionProperty(dist);
5858
propertyList.add(fractionProperty);
59-
distribution += fractionProperty.getPercentage();
59+
totalWeight += fractionProperty.getWeight();
6060
}
6161
} catch (JsonLogicException e) {
6262
log.debug("Error parsing fractional targeting rule", e);
6363
return null;
6464
}
6565

66-
if (distribution != 100) {
67-
log.debug("Fractional properties do not sum to 100");
68-
return null;
69-
}
70-
7166
// find distribution
72-
return distributeValue(bucketBy, propertyList);
67+
return distributeValue(bucketBy, propertyList, totalWeight);
7368
}
7469

75-
private static String distributeValue(final String hashKey, final List<FractionProperty> propertyList)
70+
private static String distributeValue(final String hashKey, final List<FractionProperty> propertyList, int totalWeight)
7671
throws JsonLogicEvaluationException {
7772
byte[] bytes = hashKey.getBytes(StandardCharsets.UTF_8);
7873
int mmrHash = MurmurHash3.hash32x86(bytes, 0, bytes.length, 0);
79-
int bucket = (int) ((Math.abs(mmrHash) * 1.0f / Integer.MAX_VALUE) * 100);
74+
float bucket = (Math.abs(mmrHash) * 1.0f / Integer.MAX_VALUE) * 100;
8075

81-
int bucketSum = 0;
76+
float bucketSum = 0;
8277
for (FractionProperty p : propertyList) {
83-
bucketSum += p.getPercentage();
78+
bucketSum += p.getPercentage(totalWeight);
8479

8580
if (bucket < bucketSum) {
8681
return p.getVariant();
@@ -95,7 +90,7 @@ private static String distributeValue(final String hashKey, final List<FractionP
9590
@SuppressWarnings({"checkstyle:NoFinalizer"})
9691
private static class FractionProperty {
9792
private final String variant;
98-
private final int percentage;
93+
private final int weight;
9994

10095
protected final void finalize() {
10196
// DO NOT REMOVE, spotbugs: CT_CONSTRUCTOR_THROW
@@ -108,23 +103,32 @@ protected final void finalize() {
108103

109104
final List<?> array = (List) from;
110105

111-
if (array.size() != 2) {
112-
throw new JsonLogicException("Fraction property does not have two elements");
106+
if (array.isEmpty()) {
107+
throw new JsonLogicException("Fraction property needs at least one element");
113108
}
114109

115110
// first must be a string
116111
if (!(array.get(0) instanceof String)) {
117112
throw new JsonLogicException("First element of the fraction property is not a string variant");
118113
}
119114

120-
// second element must be a number
121-
if (!(array.get(1) instanceof Number)) {
122-
throw new JsonLogicException("Second element of the fraction property is not a number");
123-
}
124-
125115
variant = (String) array.get(0);
126-
percentage = ((Number) array.get(1)).intValue();
116+
if(array.size() >= 2) {
117+
// second element must be a number
118+
if (!(array.get(1) instanceof Number)) {
119+
throw new JsonLogicException("Second element of the fraction property is not a number");
120+
}
121+
weight = ((Number) array.get(1)).intValue();
122+
} else {
123+
weight = 1;
124+
}
127125
}
128126

127+
float getPercentage(int totalWeight) {
128+
if (weight == 0) {
129+
return 0;
130+
}
131+
return (float) (weight * 100) / totalWeight;
132+
}
129133
}
130134
}

providers/flagd/src/test/java/dev/openfeature/contrib/providers/flagd/resolver/process/targeting/FractionalTest.java

+47-5
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ void targetingBackedFractional() throws JsonLogicEvaluationException {
151151

152152

153153
@Test
154-
void invalidRuleSumNot100() throws JsonLogicEvaluationException {
154+
void invalidRuleSumGreater100() throws JsonLogicEvaluationException {
155155
// given
156156
Fractional fractional = new Fractional();
157157

@@ -189,7 +189,49 @@ void invalidRuleSumNot100() throws JsonLogicEvaluationException {
189189
Object evaluate = fractional.evaluate(rule, data);
190190

191191
// then
192-
assertNull(evaluate);
192+
assertEquals("blue", evaluate);
193+
}
194+
195+
@Test
196+
void invalidRuleSumlower100() throws JsonLogicEvaluationException {
197+
// given
198+
Fractional fractional = new Fractional();
199+
200+
/* Rule
201+
* [
202+
* [
203+
* "blue",
204+
* 50
205+
* ],
206+
* [
207+
* "green",
208+
* 30
209+
* ]
210+
* ]
211+
* */
212+
213+
final List<Object> rule = new ArrayList<>();
214+
215+
final List<Object> bucket1 = new ArrayList<>();
216+
bucket1.add("blue");
217+
bucket1.add(50);
218+
219+
final List<Object> bucket2 = new ArrayList<>();
220+
bucket2.add("green");
221+
bucket2.add(70);
222+
223+
rule.add(bucket1);
224+
rule.add(bucket2);
225+
226+
Map<String, String> data = new HashMap<>();
227+
data.put(FLAG_KEY, "headerColor");
228+
data.put(TARGET_KEY, "[email protected]");
229+
230+
// when
231+
Object evaluate = fractional.evaluate(rule, data);
232+
233+
// then
234+
assertEquals("blue", evaluate);
193235
}
194236

195237
@Test
@@ -227,7 +269,7 @@ void notEnoughBuckets() throws JsonLogicEvaluationException {
227269

228270

229271
@Test
230-
void invalidRule() throws JsonLogicEvaluationException {
272+
void prefillingRuleWithPlaceHolderValue() throws JsonLogicEvaluationException {
231273
// given
232274
Fractional fractional = new Fractional();
233275

@@ -263,7 +305,7 @@ void invalidRule() throws JsonLogicEvaluationException {
263305
Object evaluate = fractional.evaluate(rule, data);
264306

265307
// then
266-
assertNull(evaluate);
308+
assertEquals("blue", evaluate);
267309
}
268310

269-
}
311+
}

0 commit comments

Comments
 (0)