internal EvaluationDetail <JToken> GetOffValue(EvaluationReason reason) { if (OffVariation == null) // off variation unspecified - return default value { return(new EvaluationDetail <JToken>(null, null, reason)); } return(GetVariation(OffVariation.Value, reason)); }
internal EvaluationDetail <JToken> GetVariation(int variation, EvaluationReason reason) { if (variation < 0 || variation >= Variations.Count) { Log.ErrorFormat("Data inconsistency in feature flag \"{0}\": invalid variation index", Key); return(ErrorResult(EvaluationErrorKind.MALFORMED_FLAG)); } return(new EvaluationDetail <JToken>(Variations[variation], variation, reason)); }
internal void AddFlag(FeatureFlag flag, JToken value, int?variation, EvaluationReason reason) { _flagValues[flag.Key] = value; _flagMetadata[flag.Key] = new FlagMetadata { Variation = variation, Version = flag.Version, TrackEvents = flag.TrackEvents, DebugEventsUntilDate = flag.DebugEventsUntilDate, Reason = reason }; }
internal EvaluationDetail <JToken> GetValueForVariationOrRollout(VariationOrRollout vr, User user, EvaluationReason reason) { var index = vr.VariationIndexForUser(user, Key, Salt); if (index == null) { Log.ErrorFormat("Data inconsistency in feature flag \"{0}\": variation/rollout object with no variation or rollout", Key); return(ErrorResult(EvaluationErrorKind.MALFORMED_FLAG)); } return(GetVariation(index.Value, reason)); }
/// <inheritdoc/> public FeatureFlagsState AllFlagsState(User user, params FlagsStateOption[] options) { if (IsOffline()) { Log.Warn("AllFlagsState() was called when client is in offline mode. Returning empty state."); return(new FeatureFlagsState(false)); } if (!Initialized()) { if (_featureStore.Initialized()) { Log.Warn("AllFlagsState() called before client initialized; using last known values from feature store"); } else { Log.Warn("AllFlagsState() called before client initialized; feature store unavailable, returning empty state"); return(new FeatureFlagsState(false)); } } if (user == null || user.Key == null) { Log.Warn("AllFlagsState() called with null user or null user key. Returning empty state"); return(new FeatureFlagsState(false)); } var builder = new FeatureFlagsStateBuilder(options); var clientSideOnly = FlagsStateOption.HasOption(options, FlagsStateOption.ClientSideOnly); IDictionary <string, FeatureFlag> flags = _featureStore.All(VersionedDataKind.Features); foreach (KeyValuePair <string, FeatureFlag> pair in flags) { var flag = pair.Value; if (clientSideOnly && !flag.ClientSide) { continue; } try { FeatureFlag.EvalResult result = flag.Evaluate(user, _featureStore, EventFactory.Default); builder.AddFlag(flag.Key, result.Result.Value.InnerValue, result.Result.VariationIndex, result.Result.Reason, flag.Version, flag.TrackEvents, flag.DebugEventsUntilDate); } catch (Exception e) { Log.ErrorFormat("Exception caught for feature flag \"{0}\" when evaluating all flags: {1}", flag.Key, Util.ExceptionMessage(e)); Log.Debug(e.ToString(), e); EvaluationReason reason = EvaluationReason.ErrorReason(EvaluationErrorKind.EXCEPTION); builder.AddFlag(flag.Key, null, null, reason, flag.Version, flag.TrackEvents, flag.DebugEventsUntilDate); } } return(builder.Build()); }
// This method is called by EventFactory to determine if extra tracking should be // enabled for an event, based on the evaluation reason. bool IFlagEventProperties.IsExperiment(EvaluationReason reason) { switch (reason.Kind) { case EvaluationReasonKind.FALLTHROUGH: return(TrackEventsFallthrough); case EvaluationReasonKind.RULE_MATCH: return(reason.RuleIndex >= 0 && Rules != null && reason.RuleIndex < Rules.Count && Rules[reason.RuleIndex].TrackEvents); } return(false); }
internal FeatureRequestEvent(long creationDate, string key, User user, int?variation, LdValue value, LdValue defaultValue, int?version, string prereqOf, bool trackEvents, long?debugEventsUntilDate, bool debug, EvaluationReason reason) : base(creationDate, key, user) { Variation = variation; LdValue = value; LdValueDefault = defaultValue; Version = version; PrereqOf = prereqOf; TrackEvents = trackEvents; DebugEventsUntilDate = debugEventsUntilDate; Debug = debug; Reason = reason; }
internal EvalResult Evaluate(User user, IFeatureStore featureStore, EventFactory eventFactory) { IList <FeatureRequestEvent> prereqEvents = new List <FeatureRequestEvent>(); if (user == null || user.Key == null) { Log.WarnFormat("User or user key is null when evaluating flag: {0} returning null", Key); return(new EvalResult( new EvaluationDetail <LdValue>(LdValue.Null, null, EvaluationReason.ErrorReason(EvaluationErrorKind.USER_NOT_SPECIFIED)), prereqEvents)); } var details = Evaluate(user, featureStore, prereqEvents, eventFactory); return(new EvalResult(details, prereqEvents)); }
private EvaluationDetail <LdValue> Evaluate(User user, IFeatureStore featureStore, IList <FeatureRequestEvent> events, EventFactory eventFactory) { if (!On) { return(GetOffValue(EvaluationReason.OffReason)); } var prereqFailureReason = CheckPrerequisites(user, featureStore, events, eventFactory); if (prereqFailureReason != null) { return(GetOffValue(prereqFailureReason)); } // Check to see if targets match if (Targets != null) { foreach (var target in Targets) { foreach (var v in target.Values) { if (user.Key == v) { return(GetVariation(target.Variation, EvaluationReason.TargetMatchReason)); } } } } // Now walk through the rules and see if any match if (Rules != null) { for (int i = 0; i < Rules.Count; i++) { Rule rule = Rules[i]; if (rule.MatchesUser(user, featureStore)) { return(GetValueForVariationOrRollout(rule, user, EvaluationReason.RuleMatchReason(i, rule.Id))); } } } // Walk through the fallthrough and see if it matches return(GetValueForVariationOrRollout(Fallthrough, user, EvaluationReason.FallthroughReason)); }
internal void AddFlag(FeatureFlag flag, JToken value, int?variation, EvaluationReason reason, bool detailsOnlyIfTracked) { _flagValues[flag.Key] = value; var meta = new FlagMetadata { Variation = variation, DebugEventsUntilDate = flag.DebugEventsUntilDate }; if (!detailsOnlyIfTracked || flag.TrackEvents || flag.DebugEventsUntilDate != null) { meta.Version = flag.Version; meta.Reason = reason; } if (flag.TrackEvents) { meta.TrackEvents = true; } _flagMetadata[flag.Key] = meta; }
// Checks prerequisites if any; returns null if successful, or an EvaluationReason if we have to // short-circuit due to a prerequisite failure. private EvaluationReason CheckPrerequisites(User user, IFeatureStore featureStore, IList <FeatureRequestEvent> events, EventFactory eventFactory) { if (Prerequisites == null || Prerequisites.Count == 0) { return(null); } foreach (var prereq in Prerequisites) { var prereqOk = true; var prereqFeatureFlag = featureStore.Get(VersionedDataKind.Features, prereq.Key); if (prereqFeatureFlag == null) { Log.ErrorFormat("Could not retrieve prerequisite flag \"{0}\" when evaluating \"{1}\"", prereq.Key, Key); prereqOk = false; } else { var prereqEvalResult = prereqFeatureFlag.Evaluate(user, featureStore, events, eventFactory); // Note that if the prerequisite flag is off, we don't consider it a match no matter // what its off variation was. But we still need to evaluate it in order to generate // an event. if (!prereqFeatureFlag.On || prereqEvalResult.VariationIndex == null || prereqEvalResult.VariationIndex.Value != prereq.Variation) { prereqOk = false; } events.Add(eventFactory.NewPrerequisiteFeatureRequestEvent(prereqFeatureFlag, user, prereqEvalResult, this)); } if (!prereqOk) { return(EvaluationReason.PrerequisiteFailedReason(prereq.Key)); } } return(null); }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { LdValue o = (LdValue)LdValueSerializer.Instance.ReadJson(reader, typeof(LdValue), LdValue.Null, serializer); if (o.IsNull) { return(null); } EvaluationReasonKind kind = (EvaluationReasonKind)Enum.Parse(typeof(EvaluationReasonKind), o.Get("kind").AsString); switch (kind) { case EvaluationReasonKind.OFF: return(EvaluationReason.OffReason); case EvaluationReasonKind.FALLTHROUGH: return(EvaluationReason.FallthroughReason); case EvaluationReasonKind.TARGET_MATCH: return(EvaluationReason.TargetMatchReason); case EvaluationReasonKind.RULE_MATCH: var index = o.Get("ruleIndex").AsInt; var id = o.Get("ruleId").AsString; return(EvaluationReason.RuleMatchReason(index, id)); case EvaluationReasonKind.PREREQUISITE_FAILED: var key = o.Get("prerequisiteKey").AsString; return(EvaluationReason.PrerequisiteFailedReason(key)); case EvaluationReasonKind.ERROR: var errorKind = (EvaluationErrorKind)Enum.Parse(typeof(EvaluationErrorKind), o.Get("errorKind").AsString); return(EvaluationReason.ErrorReason(errorKind)); } throw new ArgumentException(); }
public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { JObject o = serializer.Deserialize <JObject>(reader); if (o == null) { return(null); } EvaluationReasonKind kind = o.GetValue("kind").ToObject <EvaluationReasonKind>(); switch (kind) { case EvaluationReasonKind.OFF: return(EvaluationReason.OffReason); case EvaluationReasonKind.FALLTHROUGH: return(EvaluationReason.FallthroughReason); case EvaluationReasonKind.TARGET_MATCH: return(EvaluationReason.TargetMatchReason); case EvaluationReasonKind.RULE_MATCH: var index = (int)o.GetValue("ruleIndex"); var id = (string)o.GetValue("ruleId"); return(EvaluationReason.RuleMatchReason(index, id)); case EvaluationReasonKind.PREREQUISITE_FAILED: var key = (string)o.GetValue("prerequisiteKey"); return(EvaluationReason.PrerequisiteFailedReason(key)); case EvaluationReasonKind.ERROR: var errorKind = o.GetValue("errorKind").ToObject <EvaluationErrorKind>(); return(EvaluationReason.ErrorReason(errorKind)); } throw new ArgumentException(); }
/// <summary> /// Equivalent to <see cref="ILdClient.StringVariationDetail(string, User, string)"/>, but converts the /// flag's string value to an enum value. /// </summary> /// <remarks> /// <para> /// If the flag has a value that is not one of the allowed enum value names, or is not a string, /// <c>defaultValue</c> is returned. /// </para> /// <para> /// Note that there is no type constraint to guarantee that T really is an enum type, because that is /// a C# 7.3 feature that is unavailable in older versions of .NET Standard. If you try to use a /// non-enum type, you will simply receive the default value back. /// </para> /// </remarks> /// <typeparam name="T">the enum type</typeparam> /// <param name="client">the client instance</param> /// <param name="key">the unique feature key for the feature flag</param> /// <param name="user">the end user requesting the flag</param> /// <param name="defaultValue">the default value of the flag (as an enum value)</param> /// <returns>an <see cref="EvaluationDetail{T}"/> object</returns> public static EvaluationDetail <T> EnumVariationDetail <T>(this ILdClient client, string key, User user, T defaultValue) { var stringDetail = client.StringVariationDetail(key, user, defaultValue.ToString()); if (stringDetail.Value != null) { try { var enumValue = (T)System.Enum.Parse(typeof(T), stringDetail.Value, true); return(new EvaluationDetail <T>(enumValue, stringDetail.VariationIndex, stringDetail.Reason)); } catch (System.ArgumentException) { return(new EvaluationDetail <T>(defaultValue, stringDetail.VariationIndex, EvaluationReason.ErrorReason(EvaluationErrorKind.WRONG_TYPE))); } } return(new EvaluationDetail <T>(defaultValue, stringDetail.VariationIndex, stringDetail.Reason)); }
// This method is called by EventFactory to determine if extra tracking should be // enabled for an event, based on the evaluation reason. It is not enabled yet. bool IFlagEventProperties.IsExperiment(EvaluationReason reason) { return(false); }
/// <summary> /// Constructs a new EvaluationDetail insetance. /// </summary> /// <param name="value">the flag value</param> /// <param name="variationIndex">the variation index</param> /// <param name="reason">the evaluation reason</param> public EvaluationDetail(T value, int?variationIndex, EvaluationReason reason) { _value = value; _variationIndex = variationIndex; _reason = reason; }
private EvaluationDetail <T> Evaluate <T>(string featureKey, User user, LdValue defaultValue, LdValue.Converter <T> converter, bool checkType, EventFactory eventFactory) { T defaultValueOfType = converter.ToType(defaultValue); if (!Initialized()) { if (_featureStore.Initialized()) { Log.Warn("Flag evaluation before client initialized; using last known values from feature store"); } else { Log.Warn("Flag evaluation before client initialized; feature store unavailable, returning default value"); return(new EvaluationDetail <T>(defaultValueOfType, null, EvaluationReason.ErrorReason(EvaluationErrorKind.CLIENT_NOT_READY))); } } FeatureFlag featureFlag = null; try { featureFlag = _featureStore.Get(VersionedDataKind.Features, featureKey); if (featureFlag == null) { Log.InfoFormat("Unknown feature flag {0}; returning default value", featureKey); _eventProcessor.SendEvent(eventFactory.NewUnknownFeatureRequestEvent(featureKey, user, defaultValue, EvaluationErrorKind.FLAG_NOT_FOUND)); return(new EvaluationDetail <T>(defaultValueOfType, null, EvaluationReason.ErrorReason(EvaluationErrorKind.FLAG_NOT_FOUND))); } if (user == null || user.Key == null) { Log.Warn("Feature flag evaluation called with null user or null user key. Returning default"); _eventProcessor.SendEvent(eventFactory.NewDefaultFeatureRequestEvent(featureFlag, user, defaultValue, EvaluationErrorKind.USER_NOT_SPECIFIED)); return(new EvaluationDetail <T>(defaultValueOfType, null, EvaluationReason.ErrorReason(EvaluationErrorKind.USER_NOT_SPECIFIED))); } FeatureFlag.EvalResult evalResult = featureFlag.Evaluate(user, _featureStore, eventFactory); if (!IsOffline()) { foreach (var prereqEvent in evalResult.PrerequisiteEvents) { _eventProcessor.SendEvent(prereqEvent); } } var evalDetail = evalResult.Result; EvaluationDetail <T> returnDetail; if (evalDetail.VariationIndex == null) { returnDetail = new EvaluationDetail <T>(defaultValueOfType, null, evalDetail.Reason); evalDetail = new EvaluationDetail <LdValue>(defaultValue, null, evalDetail.Reason); } else { if (checkType && !defaultValue.IsNull && evalDetail.Value.Type != defaultValue.Type) { Log.ErrorFormat("Expected type: {0} but got {1} when evaluating FeatureFlag: {2}. Returning default", defaultValue.Type, evalDetail.Value.Type, featureKey); _eventProcessor.SendEvent(eventFactory.NewDefaultFeatureRequestEvent(featureFlag, user, defaultValue, EvaluationErrorKind.WRONG_TYPE)); return(new EvaluationDetail <T>(defaultValueOfType, null, EvaluationReason.ErrorReason(EvaluationErrorKind.WRONG_TYPE))); } returnDetail = new EvaluationDetail <T>(converter.ToType(evalDetail.Value), evalDetail.VariationIndex, evalDetail.Reason); } _eventProcessor.SendEvent(eventFactory.NewFeatureRequestEvent(featureFlag, user, evalDetail, defaultValue)); return(returnDetail); } catch (Exception e) { Log.ErrorFormat("Encountered exception in LaunchDarkly client: {0} when evaluating feature key: {1} for user key: {2}", Util.ExceptionMessage(e), featureKey, user.Key); Log.Debug(e.ToString(), e); var reason = EvaluationReason.ErrorReason(EvaluationErrorKind.EXCEPTION); if (featureFlag == null) { _eventProcessor.SendEvent(eventFactory.NewUnknownFeatureRequestEvent(featureKey, user, defaultValue, EvaluationErrorKind.EXCEPTION)); } else { _eventProcessor.SendEvent(eventFactory.NewFeatureRequestEvent(featureFlag, user, new EvaluationDetail <LdValue>(defaultValue, null, reason), defaultValue)); } return(new EvaluationDetail <T>(defaultValueOfType, null, reason)); } }
internal EvaluationDetail <LdValue> ErrorResult(EvaluationErrorKind kind) { return(new EvaluationDetail <LdValue>(LdValue.Null, null, EvaluationReason.ErrorReason(kind))); }
// This method is defined with internal scope because 1. we don't want application code to use JToken any more and 2. extra // metadata fields like trackEvents aren't relevant to the main external use case for the builder (testing server-side code) internal FeatureFlagsStateBuilder AddFlag(string flagKey, JToken value, int?variationIndex, EvaluationReason reason, int flagVersion, bool flagTrackEvents, long?flagDebugEventsUntilDate) { _flagValues[flagKey] = value; var meta = new FlagMetadata { Variation = variationIndex, DebugEventsUntilDate = flagDebugEventsUntilDate }; if (!_detailsOnlyIfTracked || flagTrackEvents || flagDebugEventsUntilDate != null) { meta.Version = flagVersion; meta.Reason = _withReasons ? reason : null; } if (flagTrackEvents) { meta.TrackEvents = true; } _flagMetadata[flagKey] = meta; return(this); }