예제 #1
0
 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));
 }
예제 #2
0
 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));
 }
예제 #3
0
 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
     };
 }
예제 #4
0
        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));
        }
예제 #5
0
        /// <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());
        }
예제 #6
0
        // 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);
        }
예제 #7
0
 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;
 }
예제 #8
0
        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));
        }
예제 #9
0
        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));
        }
예제 #10
0
        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;
        }
예제 #11
0
 // 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));
        }
예제 #15
0
 // 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;
 }
예제 #17
0
        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));
            }
        }
예제 #18
0
 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);
        }