
com.launchdarkly.client.EvaluationReason Maven / Gradle / Ivy
package com.launchdarkly.client;
import java.util.Objects;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Describes the reason that a flag evaluation produced a particular value. This is returned by
* methods such as {@link LDClientInterface#boolVariationDetail(String, LDUser, boolean)}.
*
* Note that this is an enum-like class hierarchy rather than an enum, because some of the
* possible reasons have their own properties. However, directly referencing the subclasses is
* deprecated; in a future version only the {@link EvaluationReason} base class will be visible,
* and it has getter methods for all of the possible properties.
*
* @since 4.3.0
*/
public abstract class EvaluationReason {
/**
* Enumerated type defining the possible values of {@link EvaluationReason#getKind()}.
* @since 4.3.0
*/
public static enum Kind {
/**
* Indicates that the flag was off and therefore returned its configured off value.
*/
OFF,
/**
* Indicates that the flag was on but the user did not match any targets or rules.
*/
FALLTHROUGH,
/**
* Indicates that the user key was specifically targeted for this flag.
*/
TARGET_MATCH,
/**
* Indicates that the user matched one of the flag's rules.
*/
RULE_MATCH,
/**
* Indicates that the flag was considered off because it had at least one prerequisite flag
* that either was off or did not return the desired variation.
*/
PREREQUISITE_FAILED,
/**
* Indicates that the flag could not be evaluated, e.g. because it does not exist or due to an unexpected
* error. In this case the result value will be the default value that the caller passed to the client.
* Check the errorKind property for more details on the problem.
*/
ERROR;
}
/**
* Enumerated type defining the possible values of {@link EvaluationReason.Error#getErrorKind()}.
* @since 4.3.0
*/
public static enum ErrorKind {
/**
* Indicates that the caller tried to evaluate a flag before the client had successfully initialized.
*/
CLIENT_NOT_READY,
/**
* Indicates that the caller provided a flag key that did not match any known flag.
*/
FLAG_NOT_FOUND,
/**
* Indicates that there was an internal inconsistency in the flag data, e.g. a rule specified a nonexistent
* variation. An error message will always be logged in this case.
*/
MALFORMED_FLAG,
/**
* Indicates that the caller passed {@code null} for the user parameter, or the user lacked a key.
*/
USER_NOT_SPECIFIED,
/**
* Indicates that the result value was not of the requested type, e.g. you called
* {@link LDClientInterface#boolVariationDetail(String, LDUser, boolean)} but the value was an integer.
*/
WRONG_TYPE,
/**
* Indicates that an unexpected exception stopped flag evaluation. An error message will always be logged
* in this case, and the exception should be available via {@link EvaluationReason.Error#getException()}.
*/
EXCEPTION
}
// static instances to avoid repeatedly allocating reasons for the same errors
private static final Error ERROR_CLIENT_NOT_READY = new Error(ErrorKind.CLIENT_NOT_READY, null);
private static final Error ERROR_FLAG_NOT_FOUND = new Error(ErrorKind.FLAG_NOT_FOUND, null);
private static final Error ERROR_MALFORMED_FLAG = new Error(ErrorKind.MALFORMED_FLAG, null);
private static final Error ERROR_USER_NOT_SPECIFIED = new Error(ErrorKind.USER_NOT_SPECIFIED, null);
private static final Error ERROR_WRONG_TYPE = new Error(ErrorKind.WRONG_TYPE, null);
private static final Error ERROR_EXCEPTION = new Error(ErrorKind.EXCEPTION, null);
private final Kind kind;
/**
* Returns an enum indicating the general category of the reason.
* @return a {@link Kind} value
*/
public Kind getKind()
{
return kind;
}
/**
* The index of the rule that was matched (0 for the first rule in the feature flag),
* if the {@code kind} is {@link Kind#RULE_MATCH}. Otherwise this returns -1.
*
* @return the rule index or -1
*/
public int getRuleIndex() {
return -1;
}
/**
* The unique identifier of the rule that was matched, if the {@code kind} is
* {@link Kind#RULE_MATCH}. Otherwise {@code null}.
*
* Unlike the rule index, this identifier will not change if other rules are added or deleted.
*
* @return the rule identifier or null
*/
public String getRuleId() {
return null;
}
/**
* The key of the prerequisite flag that did not return the desired variation, if the
* {@code kind} is {@link Kind#PREREQUISITE_FAILED}. Otherwise {@code null}.
*
* @return the prerequisite flag key or null
*/
public String getPrerequisiteKey() {
return null;
}
/**
* An enumeration value indicating the general category of error, if the
* {@code kind} is {@link Kind#PREREQUISITE_FAILED}. Otherwise {@code null}.
*
* @return the error kind or null
*/
public ErrorKind getErrorKind() {
return null;
}
/**
* The exception that caused the error condition, if the {@code kind} is
* {@link EvaluationReason.Kind#ERROR} and the {@code errorKind} is {@link ErrorKind#EXCEPTION}.
* Otherwise {@code null}.
*
* @return the exception instance
* @since 4.11.0
*/
public Exception getException() {
return null;
}
@Override
public String toString() {
return getKind().name();
}
protected EvaluationReason(Kind kind)
{
this.kind = kind;
}
/**
* Returns an instance whose {@code kind} is {@link Kind#OFF}.
* @return a reason object
*/
public static Off off() {
return Off.instance;
}
/**
* Returns an instance whose {@code kind} is {@link Kind#TARGET_MATCH}.
* @return a reason object
*/
public static TargetMatch targetMatch() {
return TargetMatch.instance;
}
/**
* Returns an instance whose {@code kind} is {@link Kind#RULE_MATCH}.
* @param ruleIndex the rule index
* @param ruleId the rule identifier
* @return a reason object
*/
public static RuleMatch ruleMatch(int ruleIndex, String ruleId) {
return new RuleMatch(ruleIndex, ruleId);
}
/**
* Returns an instance whose {@code kind} is {@link Kind#PREREQUISITE_FAILED}.
* @param prerequisiteKey the flag key of the prerequisite that failed
* @return a reason object
*/
public static PrerequisiteFailed prerequisiteFailed(String prerequisiteKey) {
return new PrerequisiteFailed(prerequisiteKey);
}
/**
* Returns an instance whose {@code kind} is {@link Kind#FALLTHROUGH}.
* @return a reason object
*/
public static Fallthrough fallthrough() {
return Fallthrough.instance;
}
/**
* Returns an instance whose {@code kind} is {@link Kind#ERROR}.
* @param errorKind describes the type of error
* @return a reason object
*/
public static Error error(ErrorKind errorKind) {
switch (errorKind) {
case CLIENT_NOT_READY: return ERROR_CLIENT_NOT_READY;
case EXCEPTION: return ERROR_EXCEPTION;
case FLAG_NOT_FOUND: return ERROR_FLAG_NOT_FOUND;
case MALFORMED_FLAG: return ERROR_MALFORMED_FLAG;
case USER_NOT_SPECIFIED: return ERROR_USER_NOT_SPECIFIED;
case WRONG_TYPE: return ERROR_WRONG_TYPE;
default: return new Error(errorKind, null);
}
}
/**
* Returns an instance of {@link Error} with the kind {@link ErrorKind#EXCEPTION} and an exception instance.
* @param exception the exception that caused the error
* @return a reason object
* @since 4.11.0
*/
public static Error exception(Exception exception) {
return new Error(ErrorKind.EXCEPTION, exception);
}
/**
* Subclass of {@link EvaluationReason} that indicates that the flag was off and therefore returned
* its configured off value.
* @since 4.3.0
* @deprecated This type will be removed in a future version. Use {@link #getKind()} instead and check
* for the {@link Kind#OFF} value.
*/
@Deprecated
public static class Off extends EvaluationReason {
private Off() {
super(Kind.OFF);
}
private static final Off instance = new Off();
}
/**
* Subclass of {@link EvaluationReason} that indicates that the user key was specifically targeted
* for this flag.
* @since 4.3.0
* @deprecated This type will be removed in a future version. Use {@link #getKind()} instead and check
* for the {@link Kind#TARGET_MATCH} value.
*/
@Deprecated
public static class TargetMatch extends EvaluationReason {
private TargetMatch()
{
super(Kind.TARGET_MATCH);
}
private static final TargetMatch instance = new TargetMatch();
}
/**
* Subclass of {@link EvaluationReason} that indicates that the user matched one of the flag's rules.
* @since 4.3.0
* @deprecated This type will be removed in a future version. Use {@link #getKind()} instead and check
* for the {@link Kind#RULE_MATCH} value.
*/
@Deprecated
public static class RuleMatch extends EvaluationReason {
private final int ruleIndex;
private final String ruleId;
private RuleMatch(int ruleIndex, String ruleId) {
super(Kind.RULE_MATCH);
this.ruleIndex = ruleIndex;
this.ruleId = ruleId;
}
/**
* The index of the rule that was matched (0 for the first rule in the feature flag).
* @return the rule index
*/
@Override
public int getRuleIndex() {
return ruleIndex;
}
/**
* A unique string identifier for the matched rule, which will not change if other rules are added or deleted.
* @return the rule identifier
*/
@Override
public String getRuleId() {
return ruleId;
}
@Override
public boolean equals(Object other) {
if (other instanceof RuleMatch) {
RuleMatch o = (RuleMatch)other;
return ruleIndex == o.ruleIndex && Objects.equals(ruleId, o.ruleId);
}
return false;
}
@Override
public int hashCode() {
return Objects.hash(ruleIndex, ruleId);
}
@Override
public String toString() {
return getKind().name() + "(" + ruleIndex + (ruleId == null ? "" : ("," + ruleId)) + ")";
}
}
/**
* Subclass of {@link EvaluationReason} that indicates that the flag was considered off because it
* had at least one prerequisite flag that either was off or did not return the desired variation.
* @since 4.3.0
* @deprecated This type will be removed in a future version. Use {@link #getKind()} instead and check
* for the {@link Kind#PREREQUISITE_FAILED} value.
*/
@Deprecated
public static class PrerequisiteFailed extends EvaluationReason {
private final String prerequisiteKey;
private PrerequisiteFailed(String prerequisiteKey) {
super(Kind.PREREQUISITE_FAILED);
this.prerequisiteKey = checkNotNull(prerequisiteKey);
}
/**
* The key of the prerequisite flag that did not return the desired variation.
* @return the prerequisite flag key
*/
@Override
public String getPrerequisiteKey() {
return prerequisiteKey;
}
@Override
public boolean equals(Object other) {
return (other instanceof PrerequisiteFailed) &&
((PrerequisiteFailed)other).prerequisiteKey.equals(prerequisiteKey);
}
@Override
public int hashCode() {
return prerequisiteKey.hashCode();
}
@Override
public String toString() {
return getKind().name() + "(" + prerequisiteKey + ")";
}
}
/**
* Subclass of {@link EvaluationReason} that indicates that the flag was on but the user did not
* match any targets or rules.
* @since 4.3.0
* @deprecated This type will be removed in a future version. Use {@link #getKind()} instead and check
* for the {@link Kind#FALLTHROUGH} value.
*/
@Deprecated
public static class Fallthrough extends EvaluationReason {
private Fallthrough()
{
super(Kind.FALLTHROUGH);
}
private static final Fallthrough instance = new Fallthrough();
}
/**
* Subclass of {@link EvaluationReason} that indicates that the flag could not be evaluated.
* @since 4.3.0
* @deprecated This type will be removed in a future version. Use {@link #getKind()} instead and check
* for the {@link Kind#ERROR} value.
*/
@Deprecated
public static class Error extends EvaluationReason {
private final ErrorKind errorKind;
private transient final Exception exception;
// The exception field is transient because we don't want it to be included in the JSON representation that
// is used in analytics events; the LD event service wouldn't know what to do with it (and it would include
// a potentially large amount of stacktrace data).
private Error(ErrorKind errorKind, Exception exception) {
super(Kind.ERROR);
checkNotNull(errorKind);
this.errorKind = errorKind;
this.exception = exception;
}
/**
* An enumeration value indicating the general category of error.
* @return the error kind
*/
@Override
public ErrorKind getErrorKind() {
return errorKind;
}
/**
* Returns the exception that caused the error condition, if applicable.
*
* This is only set if {@link #getErrorKind()} is {@link ErrorKind#EXCEPTION}.
*
* @return the exception instance
* @since 4.11.0
*/
public Exception getException() {
return exception;
}
@Override
public boolean equals(Object other) {
return other instanceof Error && errorKind == ((Error) other).errorKind && Objects.equals(exception, ((Error) other).exception);
}
@Override
public int hashCode() {
return Objects.hash(errorKind, exception);
}
@Override
public String toString() {
return getKind().name() + "(" + errorKind.name() + (exception == null ? "" : ("," + exception)) + ")";
}
}
}