Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions src/main/java/cloud/eppo/BanditEvaluator.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
import cloud.eppo.api.Attributes;
import cloud.eppo.api.DiscriminableAttributes;
import cloud.eppo.api.EppoValue;
import cloud.eppo.ufc.dto.*;
import cloud.eppo.api.IBanditAttributeCoefficients;
import cloud.eppo.api.IBanditCoefficients;
import cloud.eppo.api.IBanditModelData;
import java.util.*;
import java.util.stream.Collectors;

Expand All @@ -19,7 +21,7 @@ public static BanditEvaluationResult evaluateBandit(
String subjectKey,
DiscriminableAttributes subjectAttributes,
Actions actions,
BanditModelData modelData) {
IBanditModelData modelData) {
Map<String, Double> actionScores = scoreActions(subjectAttributes, actions, modelData);
Map<String, Double> actionWeights =
weighActions(actionScores, modelData.getGamma(), modelData.getActionProbabilityFloor());
Expand All @@ -43,7 +45,7 @@ public static BanditEvaluationResult evaluateBandit(
}

private static Map<String, Double> scoreActions(
DiscriminableAttributes subjectAttributes, Actions actions, BanditModelData modelData) {
DiscriminableAttributes subjectAttributes, Actions actions, IBanditModelData modelData) {
return actions.entrySet().stream()
.collect(
Collectors.toMap(
Expand All @@ -53,7 +55,7 @@ private static Map<String, Double> scoreActions(
DiscriminableAttributes actionAttributes = e.getValue();

// get all coefficients known to the model for this action
BanditCoefficients banditCoefficients =
IBanditCoefficients banditCoefficients =
modelData.getCoefficients().get(actionName);

if (banditCoefficients == null) {
Expand Down Expand Up @@ -85,11 +87,11 @@ private static Map<String, Double> scoreActions(
}

private static double scoreContextForCoefficients(
Attributes attributes, Map<String, ? extends BanditAttributeCoefficients> coefficients) {
Attributes attributes, Map<String, ? extends IBanditAttributeCoefficients> coefficients) {

double totalScore = 0.0;

for (BanditAttributeCoefficients attributeCoefficients : coefficients.values()) {
for (IBanditAttributeCoefficients attributeCoefficients : coefficients.values()) {
EppoValue contextValue = attributes.get(attributeCoefficients.getAttributeKey());
// The coefficient implementation knows how to score
double attributeScore = attributeCoefficients.scoreForAttributeValue(contextValue);
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/cloud/eppo/BaseEppoClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ protected EppoValue getTypedAssignment(

Configuration config = getConfiguration();

FlagConfig flag = config.getFlag(flagKey);
IFlagConfig flag = config.getFlag(flagKey);
if (flag == null) {
log.warn("no configuration found for key: {}", flagKey);
return defaultValue;
Expand All @@ -226,7 +226,7 @@ protected EppoValue getTypedAssignment(
FlagEvaluationResult evaluationResult =
FlagEvaluator.evaluateFlag(
flag, flagKey, subjectKey, subjectAttributes, config.isConfigObfuscated());
EppoValue assignedValue =
IEppoValue assignedValue =
evaluationResult.getVariation() != null ? evaluationResult.getVariation().getValue() : null;

if (assignedValue != null && !valueTypeMatchesExpected(expectedType, assignedValue)) {
Expand Down Expand Up @@ -278,10 +278,10 @@ protected EppoValue getTypedAssignment(
log.error("Error logging assignment: {}", e.getMessage(), e);
}
}
return assignedValue != null ? assignedValue : defaultValue;
return assignedValue != null ? (EppoValue) assignedValue : defaultValue;
}

private boolean valueTypeMatchesExpected(VariationType expectedType, EppoValue value) {
private boolean valueTypeMatchesExpected(VariationType expectedType, IEppoValue value) {
boolean typeMatch;
switch (expectedType) {
case BOOLEAN:
Expand Down Expand Up @@ -498,7 +498,7 @@ public BanditResult getBanditAction(

String banditKey = config.banditKeyForVariation(flagKey, assignedVariation);
if (banditKey != null && !actions.isEmpty()) {
BanditParameters banditParameters = config.getBanditParameters(banditKey);
IBanditParameters banditParameters = config.getBanditParameters(banditKey);
BanditEvaluationResult banditResult =
BanditEvaluator.evaluateBandit(
flagKey, subjectKey, subjectAttributes, actions, banditParameters.getModelData());
Expand Down
51 changes: 31 additions & 20 deletions src/main/java/cloud/eppo/FlagEvaluationResult.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cloud.eppo;

import cloud.eppo.api.Attributes;
import cloud.eppo.ufc.dto.Variation;
import cloud.eppo.api.IVariation;
import java.util.Map;
import java.util.Objects;

Expand All @@ -11,7 +11,7 @@ public class FlagEvaluationResult {
private final String subjectKey;
private final Attributes subjectAttributes;
private final String allocationKey;
private final Variation variation;
private final IVariation variation;
private final Map<String, String> extraLogging;
private final boolean doLog;

Expand All @@ -20,7 +20,7 @@ public FlagEvaluationResult(
String subjectKey,
Attributes subjectAttributes,
String allocationKey,
Variation variation,
IVariation variation,
Map<String, String> extraLogging,
boolean doLog) {
this.flagKey = flagKey;
Expand All @@ -34,33 +34,44 @@ public FlagEvaluationResult(

@Override
public String toString() {
return "FlagEvaluationResult{" +
"flagKey='" + flagKey + '\'' +
", subjectKey='" + subjectKey + '\'' +
", subjectAttributes=" + subjectAttributes +
", allocationKey='" + allocationKey + '\'' +
", variation=" + variation +
", extraLogging=" + extraLogging +
", doLog=" + doLog +
'}';
return "FlagEvaluationResult{"
+ "flagKey='"
+ flagKey
+ '\''
+ ", subjectKey='"
+ subjectKey
+ '\''
+ ", subjectAttributes="
+ subjectAttributes
+ ", allocationKey='"
+ allocationKey
+ '\''
+ ", variation="
+ variation
+ ", extraLogging="
+ extraLogging
+ ", doLog="
+ doLog
+ '}';
}

@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
FlagEvaluationResult that = (FlagEvaluationResult) o;
return doLog == that.doLog
&& Objects.equals(flagKey, that.flagKey)
&& Objects.equals(subjectKey, that.subjectKey)
&& Objects.equals(subjectAttributes, that.subjectAttributes)
&& Objects.equals(allocationKey, that.allocationKey)
&& Objects.equals(variation, that.variation)
&& Objects.equals(extraLogging, that.extraLogging);
&& Objects.equals(flagKey, that.flagKey)
&& Objects.equals(subjectKey, that.subjectKey)
&& Objects.equals(subjectAttributes, that.subjectAttributes)
&& Objects.equals(allocationKey, that.allocationKey)
&& Objects.equals(variation, that.variation)
&& Objects.equals(extraLogging, that.extraLogging);
}

@Override
public int hashCode() {
return Objects.hash(flagKey, subjectKey, subjectAttributes, allocationKey, variation, extraLogging, doLog);
return Objects.hash(
flagKey, subjectKey, subjectAttributes, allocationKey, variation, extraLogging, doLog);
}

public String getFlagKey() {
Expand All @@ -79,7 +90,7 @@ public String getAllocationKey() {
return allocationKey;
}

public Variation getVariation() {
public IVariation getVariation() {
return variation;
}

Expand Down
31 changes: 16 additions & 15 deletions src/main/java/cloud/eppo/FlagEvaluator.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@

import cloud.eppo.api.Attributes;
import cloud.eppo.api.EppoValue;
import cloud.eppo.model.ShardRange;
import cloud.eppo.ufc.dto.Allocation;
import cloud.eppo.ufc.dto.FlagConfig;
import cloud.eppo.ufc.dto.Shard;
import cloud.eppo.ufc.dto.Split;
import cloud.eppo.api.IAllocation;
import cloud.eppo.api.IFlagConfig;
import cloud.eppo.api.IShard;
import cloud.eppo.api.IShardRange;
import cloud.eppo.api.ISplit;
import cloud.eppo.api.IVariation;
import cloud.eppo.ufc.dto.Variation;
import java.util.Date;
import java.util.HashMap;
Expand All @@ -20,26 +21,26 @@
public class FlagEvaluator {

public static FlagEvaluationResult evaluateFlag(
FlagConfig flag,
IFlagConfig flag,
String flagKey,
String subjectKey,
Attributes subjectAttributes,
boolean isConfigObfuscated) {
Date now = new Date();

Variation variation = null;
IVariation variation = null;
String allocationKey = null;
Map<String, String> extraLogging = new HashMap<>();
boolean doLog = false;

// If flag is disabled; use an empty list of allocations so that the empty result is returned
// Note: this is a safety check; disabled flags should be filtered upstream
List<Allocation> allocationsToConsider =
List<? extends IAllocation> allocationsToConsider =
flag.isEnabled() && flag.getAllocations() != null
? flag.getAllocations()
? new LinkedList<>(flag.getAllocations())
: new LinkedList<>();

for (Allocation allocation : allocationsToConsider) {
for (IAllocation allocation : allocationsToConsider) {
if (allocation.getStartAt() != null && allocation.getStartAt().after(now)) {
// Allocation not yet active
continue;
Expand All @@ -66,7 +67,7 @@ public static FlagEvaluationResult evaluateFlag(
}

// This allocation has matched; find variation
for (Split split : allocation.getSplits()) {
for (ISplit split : allocation.getSplits()) {
if (allShardsMatch(split, subjectKey, flag.getTotalShards(), isConfigObfuscated)) {
// Variation and extra logging is determined by the relevant split
variation = flag.getVariations().get(split.getVariationKey());
Expand Down Expand Up @@ -140,13 +141,13 @@ public static FlagEvaluationResult evaluateFlag(
}

private static boolean allShardsMatch(
Split split, String subjectKey, int totalShards, boolean isObfuscated) {
ISplit split, String subjectKey, int totalShards, boolean isObfuscated) {
if (split.getShards() == null || split.getShards().isEmpty()) {
// Default to matching if no explicit shards
return true;
}

for (Shard shard : split.getShards()) {
for (IShard shard : split.getShards()) {
if (!matchesShard(shard, subjectKey, totalShards, isObfuscated)) {
return false;
}
Expand All @@ -157,14 +158,14 @@ private static boolean allShardsMatch(
}

private static boolean matchesShard(
Shard shard, String subjectKey, int totalShards, boolean isObfuscated) {
IShard shard, String subjectKey, int totalShards, boolean isObfuscated) {
String salt = shard.getSalt();
if (isObfuscated) {
salt = base64Decode(salt);
}
String hashKey = salt + "-" + subjectKey;
int assignedShard = getShard(hashKey, totalShards);
for (ShardRange range : shard.getRanges()) {
for (IShardRange range : shard.getRanges()) {
if (assignedShard >= range.getStart() && assignedShard < range.getEnd()) {
return true;
}
Expand Down
25 changes: 14 additions & 11 deletions src/main/java/cloud/eppo/RuleEvaluator.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@

import cloud.eppo.api.Attributes;
import cloud.eppo.api.EppoValue;
import cloud.eppo.api.IEppoValue;
import cloud.eppo.api.ITargetingCondition;
import cloud.eppo.api.ITargetingRule;
import cloud.eppo.ufc.dto.OperatorType;
import cloud.eppo.ufc.dto.TargetingCondition;
import cloud.eppo.ufc.dto.TargetingRule;
import com.github.zafarkhaja.semver.Version;
import java.util.Collections;
import java.util.Map;
Expand All @@ -16,9 +17,9 @@

public class RuleEvaluator {

public static TargetingRule findMatchingRule(
Attributes subjectAttributes, Set<TargetingRule> rules, boolean isObfuscated) {
for (TargetingRule rule : rules) {
public static ITargetingRule findMatchingRule(
Attributes subjectAttributes, Set<? extends ITargetingRule> rules, boolean isObfuscated) {
for (ITargetingRule rule : rules) {
if (allConditionsMatch(subjectAttributes, rule.getConditions(), isObfuscated)) {
return rule;
}
Expand All @@ -27,8 +28,10 @@ public static TargetingRule findMatchingRule(
}

private static boolean allConditionsMatch(
Attributes subjectAttributes, Set<TargetingCondition> conditions, boolean isObfuscated) {
for (TargetingCondition condition : conditions) {
Attributes subjectAttributes,
Set<? extends ITargetingCondition> conditions,
boolean isObfuscated) {
for (ITargetingCondition condition : conditions) {
if (!evaluateCondition(subjectAttributes, condition, isObfuscated)) {
return false;
}
Expand All @@ -37,10 +40,10 @@ private static boolean allConditionsMatch(
}

private static boolean evaluateCondition(
Attributes subjectAttributes, TargetingCondition condition, boolean isObfuscated) {
EppoValue conditionValue = condition.getValue();
Attributes subjectAttributes, ITargetingCondition condition, boolean isObfuscated) {
IEppoValue conditionValue = condition.getValue();
String attributeKey = condition.getAttribute();
EppoValue attributeValue = null;
IEppoValue attributeValue = null;
if (isObfuscated) {
// attribute names are hashed
for (Map.Entry<String, EppoValue> entry : subjectAttributes.entrySet()) {
Expand Down Expand Up @@ -187,7 +190,7 @@ private static boolean evaluateCondition(
* IN and NOT IN checks are not strongly typed, as the user is only entering in strings Thus we
* need to cast the attribute to a string before hashing and checking
*/
private static String castAttributeForListComparison(EppoValue attributeValue) {
private static String castAttributeForListComparison(IEppoValue attributeValue) {
if (attributeValue.isBoolean()) {
return Boolean.valueOf(attributeValue.booleanValue()).toString();
} else if (attributeValue.isNumeric()) {
Expand Down
Loading