From 626acc5b618a3e2af10bbeb46b3477be998591c2 Mon Sep 17 00:00:00 2001 From: Tanmay Ranjan Date: Tue, 12 Mar 2024 16:18:14 +0530 Subject: [PATCH] Fixes - 1. Handle Required with Variation 2.handle try catch in Evaluation --- .../featurevisor/sdk/Instance+Evaluation.kt | 798 +++++++++--------- .../sdk/serializers/Serializers.kt | 17 +- .../featurevisor/testRunner/TestExecuter.kt | 83 +- 3 files changed, 472 insertions(+), 426 deletions(-) diff --git a/src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt b/src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt index 6539442..dc65951 100644 --- a/src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt +++ b/src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt @@ -73,542 +73,586 @@ fun FeaturevisorInstance.isEnabled(featureKey: FeatureKey, context: Context = em return evaluation.enabled == true } +@Suppress("UNREACHABLE_CODE") fun FeaturevisorInstance.evaluateVariation(featureKey: FeatureKey, context: Context = emptyMap()): Evaluation { var evaluation: Evaluation - val flag = evaluateFlag(featureKey, context) - if (flag.enabled == false) { - evaluation = Evaluation( - featureKey = featureKey, - reason = DISABLED, - ) - - logger?.debug("feature is disabled", evaluation.toDictionary()) - return evaluation - } - - // sticky - stickyFeatures?.get(featureKey)?.variation?.let { variationValue -> - evaluation = Evaluation( - featureKey = featureKey, - reason = STICKY, - variationValue = variationValue, - ) - - logger?.debug("using sticky variation", evaluation.toDictionary()) - return evaluation - } - - // initial - if (statuses.ready.not() && initialFeatures?.get(featureKey)?.variation != null) { - val variationValue = initialFeatures[featureKey]?.variation - evaluation = Evaluation( - featureKey = featureKey, - reason = INITIAL, - variationValue = variationValue - ) + try { + val flag = evaluateFlag(featureKey, context) + if (flag.enabled == false) { + evaluation = Evaluation( + featureKey = featureKey, + reason = DISABLED, + ) - logger?.debug("using initial variation", evaluation.toDictionary()) - return evaluation - } + logger?.debug("feature is disabled", evaluation.toDictionary()) + return evaluation + } - val feature = getFeatureByKey(featureKey) - if (feature == null) { - // not found - evaluation = Evaluation( - featureKey = featureKey, - reason = NOT_FOUND - ) + // sticky + stickyFeatures?.get(featureKey)?.variation?.let { variationValue -> + evaluation = Evaluation( + featureKey = featureKey, + reason = STICKY, + variationValue = variationValue, + ) - logger?.warn("feature not found", evaluation.toDictionary()) - return evaluation - } + logger?.debug("using sticky variation", evaluation.toDictionary()) + return evaluation + } - if (feature.variations.isNullOrEmpty()) { - // no variations - evaluation = Evaluation( - featureKey = featureKey, - reason = NO_VARIATIONS - ) + // initial + if (statuses.ready.not() && initialFeatures?.get(featureKey)?.variation != null) { + val variationValue = initialFeatures[featureKey]?.variation + evaluation = Evaluation( + featureKey = featureKey, + reason = INITIAL, + variationValue = variationValue + ) - logger?.warn("no variations", evaluation.toDictionary()) - return evaluation - } + logger?.debug("using initial variation", evaluation.toDictionary()) + return evaluation + } - val finalContext = interceptContext?.invoke(context) ?: context + val feature = getFeatureByKey(featureKey) + if (feature == null) { + // not found + evaluation = Evaluation( + featureKey = featureKey, + reason = NOT_FOUND + ) - // forced - val force = findForceFromFeature(feature, context, datafileReader) - if (force != null) { - val variation = feature.variations.firstOrNull { it.value == force.variation } + logger?.warn("feature not found", evaluation.toDictionary()) + return evaluation + } - if (variation != null) { + if (feature.variations.isNullOrEmpty()) { + // no variations evaluation = Evaluation( - featureKey = feature.key, - reason = FORCED, - variation = variation + featureKey = featureKey, + reason = NO_VARIATIONS ) - logger?.debug("forced variation found", evaluation.toDictionary()) - + logger?.warn("no variations", evaluation.toDictionary()) return evaluation } - } - - // bucketing - val bucketValue = getBucketValue(feature, finalContext) - val matchedTrafficAndAllocation = getMatchedTrafficAndAllocation( - feature.traffic, - finalContext, - bucketValue, - datafileReader, - logger - ) + val finalContext = interceptContext?.invoke(context) ?: context - val matchedTraffic = matchedTrafficAndAllocation.matchedTraffic + // forced + val force = findForceFromFeature(feature, context, datafileReader) + if (force != null) { + val variation = feature.variations.firstOrNull { it.value == force.variation } - // override from rule - if (matchedTraffic?.variation != null) { - val variation = feature.variations.firstOrNull { it.value == matchedTraffic.variation } - if (variation != null) { - evaluation = Evaluation( - featureKey = feature.key, - reason = RULE, - bucketValue = bucketValue, - ruleKey = matchedTraffic.key, - variation = variation - ) + if (variation != null) { + evaluation = Evaluation( + featureKey = feature.key, + reason = FORCED, + variation = variation + ) - logger?.debug("override from rule", evaluation.toDictionary()) + logger?.debug("forced variation found", evaluation.toDictionary()) - return evaluation + return evaluation + } } - } - val matchedAllocation = matchedTrafficAndAllocation.matchedAllocation + // bucketing + val bucketValue = getBucketValue(feature, finalContext) - // regular allocation - if (matchedAllocation != null) { - val variation = feature.variations?.firstOrNull { it.value == matchedAllocation.variation } - if (variation != null) { - evaluation = Evaluation( - featureKey = feature.key, - reason = ALLOCATED, - bucketValue = bucketValue, - variation = variation - ) + val matchedTrafficAndAllocation = getMatchedTrafficAndAllocation( + feature.traffic, + finalContext, + bucketValue, + datafileReader, + logger + ) - logger?.debug("allocated variation", evaluation.toDictionary()) + val matchedTraffic = matchedTrafficAndAllocation.matchedTraffic - return evaluation - } - } + // override from rule + if (matchedTraffic?.variation != null) { + val variation = feature.variations.firstOrNull { it.value == matchedTraffic.variation } + if (variation != null) { + evaluation = Evaluation( + featureKey = feature.key, + reason = RULE, + bucketValue = bucketValue, + ruleKey = matchedTraffic.key, + variation = variation + ) - // nothing matched - evaluation = Evaluation( - featureKey = feature.key, - reason = ERROR, - bucketValue = bucketValue - ) + logger?.debug("override from rule", evaluation.toDictionary()) + + return evaluation + } + } - logger?.debug("no matched variation", evaluation.toDictionary()) + val matchedAllocation = matchedTrafficAndAllocation.matchedAllocation - return evaluation -} + // regular allocation + if (matchedAllocation != null) { + val variation = feature.variations?.firstOrNull { it.value == matchedAllocation.variation } + if (variation != null) { + evaluation = Evaluation( + featureKey = feature.key, + reason = ALLOCATED, + bucketValue = bucketValue, + variation = variation + ) -fun FeaturevisorInstance.evaluateFlag(featureKey: FeatureKey, context: Context = emptyMap()): Evaluation { - logger?.debug("evaluate flag: $featureKey") + logger?.debug("allocated variation", evaluation.toDictionary()) - val evaluation: Evaluation + return evaluation + } + } - // sticky - stickyFeatures?.get(featureKey)?.let { stickyFeature -> + // nothing matched evaluation = Evaluation( - featureKey = featureKey, - reason = STICKY, - enabled = stickyFeature.enabled, - sticky = stickyFeature + featureKey = feature.key, + reason = ERROR, + bucketValue = bucketValue ) - logger?.debug("using sticky enabled", evaluation.toDictionary()) + logger?.debug("no matched variation", evaluation.toDictionary()) return evaluation - } - - // initial - if (statuses.ready && initialFeatures?.get(featureKey) != null) { - val initialFeature = initialFeatures[featureKey] + }catch (e:Exception){ evaluation = Evaluation( featureKey = featureKey, - reason = INITIAL, - enabled = initialFeature?.enabled, - initial = initialFeature + reason = ERROR, + error(e) ) - logger?.debug("using initial enabled", evaluation.toDictionary()) + this.logger?.error("error", evaluation.toDictionary()) return evaluation } +} - val feature = getFeatureByKey(featureKey) - if (feature == null) { - // not found - evaluation = Evaluation( - featureKey = featureKey, - reason = NOT_FOUND - ) - logger?.warn("feature not found", evaluation.toDictionary()) - return evaluation - } +@Suppress("UNREACHABLE_CODE") +fun FeaturevisorInstance.evaluateFlag(featureKey: FeatureKey, context: Context = emptyMap()): Evaluation { - // deprecated - if (feature.deprecated == true) { - logger?.warn("feature is deprecated", mapOf("featureKey" to feature.key)) - } + var evaluation: Evaluation - val finalContext = interceptContext?.invoke(context) ?: context + try { + logger?.debug("evaluate flag: $featureKey") - // forced - findForceFromFeature(feature, context, datafileReader)?.let { force -> - if (force.enabled != null) { + // sticky + stickyFeatures?.get(featureKey)?.let { stickyFeature -> evaluation = Evaluation( featureKey = featureKey, - reason = FORCED, - enabled = force.enabled + reason = STICKY, + enabled = stickyFeature.enabled, + sticky = stickyFeature ) - logger?.debug("forced enabled found", evaluation.toDictionary()) + logger?.debug("using sticky enabled", evaluation.toDictionary()) return evaluation } - } - - // required - if (feature.required.isNullOrEmpty().not()) { - val requiredFeaturesAreEnabled = feature.required?.all { item -> - var requiredKey: FeatureKey? = null - var requiredVariation: VariationValue? = null - when (item) { - is Required.FeatureKey -> { - requiredKey = item.required - requiredVariation = null - } - is Required.WithVariation -> { - requiredKey = item.required.key - requiredVariation = item.required.variation - } - } - - val requiredIsEnabled = isEnabled(requiredKey, finalContext) - - if (requiredIsEnabled.not()) { - return@all false - } - - if (requiredVariation != null){ - val requiredVariationValue = getVariation(requiredKey, finalContext) + // initial + if (statuses.ready && initialFeatures?.get(featureKey) != null) { + val initialFeature = initialFeatures[featureKey] + evaluation = Evaluation( + featureKey = featureKey, + reason = INITIAL, + enabled = initialFeature?.enabled, + initial = initialFeature + ) - return@all requiredVariationValue == requiredVariation - } + logger?.debug("using initial enabled", evaluation.toDictionary()) - return@all true + return evaluation } - if ((requiredFeaturesAreEnabled == false)) { + val feature = getFeatureByKey(featureKey) + if (feature == null) { + // not found evaluation = Evaluation( - featureKey = feature.key, - reason = REQUIRED, - enabled = requiredFeaturesAreEnabled + featureKey = featureKey, + reason = NOT_FOUND ) + logger?.warn("feature not found", evaluation.toDictionary()) return evaluation } - } - // bucketing - val bucketValue = getBucketValue(feature = feature, context = finalContext) + // deprecated + if (feature.deprecated == true) { + logger?.warn("feature is deprecated", mapOf("featureKey" to feature.key)) + } - val matchedTraffic = getMatchedTraffic( - traffic = feature.traffic, - context = finalContext, - datafileReader = datafileReader, - ) + val finalContext = interceptContext?.invoke(context) ?: context - if (matchedTraffic != null) { + // forced + findForceFromFeature(feature, context, datafileReader)?.let { force -> + if (force.enabled != null) { + evaluation = Evaluation( + featureKey = featureKey, + reason = FORCED, + enabled = force.enabled + ) - if (feature.ranges.isNullOrEmpty().not()) { + logger?.debug("forced enabled found", evaluation.toDictionary()) - val matchedRange = feature.ranges!!.firstOrNull { range -> - bucketValue >= range.first() && bucketValue < range.last() + return evaluation } + } + + // required + if (feature.required.isNullOrEmpty().not()) { + val requiredFeaturesAreEnabled = feature.required?.all { item -> + var requiredKey: FeatureKey? = null + var requiredVariation: VariationValue? = null + when (item) { + is Required.FeatureKey -> { + requiredKey = item.required + requiredVariation = null + } + + is Required.WithVariation -> { + requiredKey = item.required.key + requiredVariation = item.required.variation + } + } + + val requiredIsEnabled = isEnabled(requiredKey, finalContext) + + if (requiredIsEnabled.not()) { + return@all false + } + + if (requiredVariation != null){ + val requiredVariationValue = getVariation(requiredKey, finalContext) - // matched - if (matchedRange != null) { + return@all requiredVariationValue == requiredVariation + } + + return@all true + } + + if ((requiredFeaturesAreEnabled == false)) { evaluation = Evaluation( featureKey = feature.key, - reason = ALLOCATED, - bucketValue = bucketValue, - enabled = matchedTraffic.enabled ?: true + reason = REQUIRED, + enabled = requiredFeaturesAreEnabled ) return evaluation } + } - // no match - evaluation = Evaluation( - featureKey = feature.key, - reason = OUT_OF_RANGE, - bucketValue = bucketValue, - enabled = false - ) + // bucketing + val bucketValue = getBucketValue(feature = feature, context = finalContext) - logger?.debug("not matched", evaluation.toDictionary()) + val matchedTraffic = getMatchedTraffic( + traffic = feature.traffic, + context = finalContext, + datafileReader = datafileReader, + ) - return evaluation - } + if (matchedTraffic != null) { - // override from rule - val matchedTrafficEnabled = matchedTraffic.enabled - if (matchedTrafficEnabled != null) { - evaluation = Evaluation( - featureKey = feature.key, - reason = OVERRIDE, - bucketValue = bucketValue, - ruleKey = matchedTraffic.key, - enabled = matchedTrafficEnabled, - traffic = matchedTraffic - ) + if (feature.ranges.isNullOrEmpty().not()) { - logger?.debug("override from rule", evaluation.toDictionary()) + val matchedRange = feature.ranges!!.firstOrNull { range -> + bucketValue >= range.first() && bucketValue < range.last() + } - return evaluation - } + // matched + if (matchedRange != null) { + evaluation = Evaluation( + featureKey = feature.key, + reason = ALLOCATED, + bucketValue = bucketValue, + enabled = matchedTraffic.enabled ?: true + ) - // treated as enabled because of matched traffic - if (bucketValue <= matchedTraffic.percentage) { - // @TODO: verify if range check should be inclusive or not - evaluation = Evaluation( - featureKey = feature.key, - reason = RULE, - bucketValue = bucketValue, - ruleKey = matchedTraffic.key, - enabled = true, - traffic = matchedTraffic - ) + return evaluation + } - return evaluation - } - } + // no match + evaluation = Evaluation( + featureKey = feature.key, + reason = OUT_OF_RANGE, + bucketValue = bucketValue, + enabled = false + ) - // nothing matched - evaluation = Evaluation( - featureKey = feature.key, - reason = ERROR, - bucketValue = bucketValue, - enabled = false - ) + logger?.debug("not matched", evaluation.toDictionary()) - return evaluation + return evaluation + } -} + // override from rule + val matchedTrafficEnabled = matchedTraffic.enabled + if (matchedTrafficEnabled != null) { + evaluation = Evaluation( + featureKey = feature.key, + reason = OVERRIDE, + bucketValue = bucketValue, + ruleKey = matchedTraffic.key, + enabled = matchedTrafficEnabled, + traffic = matchedTraffic + ) -fun FeaturevisorInstance.evaluateVariable( - featureKey: FeatureKey, - variableKey: VariableKey, - context: Context = emptyMap(), -): Evaluation { + logger?.debug("override from rule", evaluation.toDictionary()) - FeaturevisorInstance.companionLogger?.debug("evaluateVariable, featureKey: $featureKey, variableKey: $variableKey") - val evaluation: Evaluation - val flag = evaluateFlag(featureKey, context) - if (flag.enabled == false) { - evaluation = Evaluation(featureKey = featureKey, reason = DISABLED) - logger?.debug("feature is disabled", evaluation.toDictionary()) - return evaluation - } + return evaluation + } + + // treated as enabled because of matched traffic + if (bucketValue <= matchedTraffic.percentage) { + // @TODO: verify if range check should be inclusive or not + evaluation = Evaluation( + featureKey = feature.key, + reason = RULE, + bucketValue = bucketValue, + ruleKey = matchedTraffic.key, + enabled = true, + traffic = matchedTraffic + ) + + return evaluation + } + } - // sticky - stickyFeatures?.get(featureKey)?.variables?.get(variableKey)?.let { variableValue -> + // nothing matched evaluation = Evaluation( - featureKey = featureKey, - reason = STICKY, - variableKey = variableKey, - variableValue = variableValue + featureKey = feature.key, + reason = ERROR, + bucketValue = bucketValue, + enabled = false ) - logger?.debug("using sticky variable", evaluation.toDictionary()) return evaluation - } - // initial - if (!statuses.ready && initialFeatures?.get(featureKey)?.variables?.get(variableKey) != null) { - val variableValue = initialFeatures?.get(featureKey)?.variables?.get(variableKey) + } catch (e: Exception) { evaluation = Evaluation( featureKey = featureKey, - reason = INITIAL, - variableKey = variableKey, - variableValue = variableValue + reason = ERROR, + error(e) ) - logger?.debug("using initial variable", evaluation.toDictionary()) + this.logger?.error("error", evaluation.toDictionary()) + return evaluation } +} - getFeatureByKey(featureKey).let { feature -> - if (feature == null) { +@Suppress("UNREACHABLE_CODE") +fun FeaturevisorInstance.evaluateVariable( + featureKey: FeatureKey, + variableKey: VariableKey, + context: Context = emptyMap(), +): Evaluation { + + FeaturevisorInstance.companionLogger?.debug("evaluateVariable, featureKey: $featureKey, variableKey: $variableKey") + var evaluation: Evaluation + + try { + val flag = evaluateFlag(featureKey, context) + if (flag.enabled == false) { + evaluation = Evaluation(featureKey = featureKey, reason = DISABLED) + logger?.debug("feature is disabled", evaluation.toDictionary()) + return evaluation + } + + // sticky + stickyFeatures?.get(featureKey)?.variables?.get(variableKey)?.let { variableValue -> evaluation = Evaluation( featureKey = featureKey, - reason = NOT_FOUND, - variableKey = variableKey + reason = STICKY, + variableKey = variableKey, + variableValue = variableValue ) - logger?.warn("feature not found in datafile", evaluation.toDictionary()) + logger?.debug("using sticky variable", evaluation.toDictionary()) return evaluation } - val variableSchema = feature.variablesSchema?.firstOrNull { variableSchema -> - variableSchema.key == variableKey - } - - if (variableSchema == null) { + // initial + if (!statuses.ready && initialFeatures?.get(featureKey)?.variables?.get(variableKey) != null) { + val variableValue = initialFeatures?.get(featureKey)?.variables?.get(variableKey) evaluation = Evaluation( featureKey = featureKey, - reason = NOT_FOUND, - variableKey = variableKey + reason = INITIAL, + variableKey = variableKey, + variableValue = variableValue ) - logger?.warn("variable schema not found", evaluation.toDictionary()) + logger?.debug("using initial variable", evaluation.toDictionary()) return evaluation } - val finalContext = interceptContext?.invoke(context) ?: context - - // forced - val force = findForceFromFeature(feature, context, datafileReader) - - force?.let { - if (it.variables?.containsKey(variableKey) == true) { - val variableValue = it.variables[variableKey] + getFeatureByKey(featureKey).let { feature -> + if (feature == null) { evaluation = Evaluation( - featureKey = feature.key, - reason = FORCED, - variableKey = variableKey, - variableValue = variableValue, - variableSchema = variableSchema + featureKey = featureKey, + reason = NOT_FOUND, + variableKey = variableKey ) - logger?.debug("forced variable", evaluation.toDictionary()) + logger?.warn("feature not found in datafile", evaluation.toDictionary()) return evaluation } - } - - // bucketing - val bucketValue = getBucketValue(feature, finalContext) - val matchedTrafficAndAllocation = getMatchedTrafficAndAllocation( - traffic = feature.traffic, - context = finalContext, - bucketValue = bucketValue, - datafileReader = datafileReader, - logger = logger - ) + val variableSchema = feature.variablesSchema?.firstOrNull { variableSchema -> + variableSchema.key == variableKey + } - matchedTrafficAndAllocation.matchedTraffic?.let { matchedTraffic -> - // override from rule - matchedTraffic.variables?.get(variableKey)?.let { variableValue -> + if (variableSchema == null) { evaluation = Evaluation( - featureKey = feature.key, - reason = RULE, - bucketValue = bucketValue, - ruleKey = matchedTraffic.key, - variableKey = variableKey, - variableValue = variableValue, - variableSchema = variableSchema + featureKey = featureKey, + reason = NOT_FOUND, + variableKey = variableKey ) - logger?.debug("override from rule", evaluation.toDictionary()) - + logger?.warn("variable schema not found", evaluation.toDictionary()) return evaluation } - // regular allocation - matchedTrafficAndAllocation.matchedAllocation?.let { matchedAllocation -> - - val variationValue: String = if (force?.variation != null) { - force.variation - } else { - matchedAllocation.variation - } - - val variation = feature.variations?.firstOrNull { variation -> - variation.value == variationValue - } - - val variableFromVariation = variation?.variables?.firstOrNull { variable -> - variable.key == variableKey - } - - variableFromVariation?.overrides?.firstOrNull { override -> - if (override.conditions != null) { - return@firstOrNull allConditionsAreMatched(override.conditions, finalContext) - } + val finalContext = interceptContext?.invoke(context) ?: context - if (override.segments != null) { - return@firstOrNull allGroupSegmentsAreMatched( - override.segments, - finalContext, - datafileReader - ) - } + // forced + val force = findForceFromFeature(feature, context, datafileReader) - false - }?.let { override -> + force?.let { + if (it.variables?.containsKey(variableKey) == true) { + val variableValue = it.variables[variableKey] evaluation = Evaluation( featureKey = feature.key, - reason = OVERRIDE, - bucketValue = bucketValue, - ruleKey = matchedTraffic.key, + reason = FORCED, variableKey = variableKey, - variableValue = override.value, + variableValue = variableValue, variableSchema = variableSchema ) - logger?.debug("variable override", evaluation.toDictionary()) + logger?.debug("forced variable", evaluation.toDictionary()) return evaluation } + } + + // bucketing + val bucketValue = getBucketValue(feature, finalContext) + + val matchedTrafficAndAllocation = getMatchedTrafficAndAllocation( + traffic = feature.traffic, + context = finalContext, + bucketValue = bucketValue, + datafileReader = datafileReader, + logger = logger + ) - if (variableFromVariation?.value != null) { + matchedTrafficAndAllocation.matchedTraffic?.let { matchedTraffic -> + // override from rule + matchedTraffic.variables?.get(variableKey)?.let { variableValue -> evaluation = Evaluation( featureKey = feature.key, - reason = ALLOCATED, + reason = RULE, bucketValue = bucketValue, ruleKey = matchedTraffic.key, variableKey = variableKey, - variableValue = variableFromVariation.value, + variableValue = variableValue, variableSchema = variableSchema ) - logger?.debug("allocated variable", evaluation.toDictionary()) + logger?.debug("override from rule", evaluation.toDictionary()) + return evaluation } + + // regular allocation + matchedTrafficAndAllocation.matchedAllocation?.let { matchedAllocation -> + + val variationValue: String = if (force?.variation != null) { + force.variation + } else { + matchedAllocation.variation + } + + val variation = feature.variations?.firstOrNull { variation -> + variation.value == variationValue + } + + val variableFromVariation = variation?.variables?.firstOrNull { variable -> + variable.key == variableKey + } + + variableFromVariation?.overrides?.firstOrNull { override -> + if (override.conditions != null) { + return@firstOrNull allConditionsAreMatched(override.conditions, finalContext) + } + + if (override.segments != null) { + return@firstOrNull allGroupSegmentsAreMatched( + override.segments, + finalContext, + datafileReader + ) + } + + false + }?.let { override -> + evaluation = Evaluation( + featureKey = feature.key, + reason = OVERRIDE, + bucketValue = bucketValue, + ruleKey = matchedTraffic.key, + variableKey = variableKey, + variableValue = override.value, + variableSchema = variableSchema + ) + + logger?.debug("variable override", evaluation.toDictionary()) + return evaluation + } + + if (variableFromVariation?.value != null) { + evaluation = Evaluation( + featureKey = feature.key, + reason = ALLOCATED, + bucketValue = bucketValue, + ruleKey = matchedTraffic.key, + variableKey = variableKey, + variableValue = variableFromVariation.value, + variableSchema = variableSchema + ) + + logger?.debug("allocated variable", evaluation.toDictionary()) + return evaluation + } + } } - } - // fall back to default + // fall back to default + evaluation = Evaluation( + featureKey = feature.key, + reason = DEFAULTED, + bucketValue = bucketValue, + variableKey = variableKey, + variableValue = variableSchema.defaultValue, + variableSchema = variableSchema + ) + + logger?.debug("using default value", evaluation.toDictionary()) + return evaluation + } + }catch (e: Exception){ evaluation = Evaluation( - featureKey = feature.key, - reason = DEFAULTED, - bucketValue = bucketValue, - variableKey = variableKey, - variableValue = variableSchema.defaultValue, - variableSchema = variableSchema + featureKey = featureKey, + reason = ERROR, + error(e) ) - logger?.debug("using default value", evaluation.toDictionary()) + this.logger?.error("error", evaluation.toDictionary()) + return evaluation } + + } private fun FeaturevisorInstance.getBucketKey(feature: Feature, context: Context): BucketKey { diff --git a/src/main/kotlin/com/featurevisor/sdk/serializers/Serializers.kt b/src/main/kotlin/com/featurevisor/sdk/serializers/Serializers.kt index f760563..43456dc 100644 --- a/src/main/kotlin/com/featurevisor/sdk/serializers/Serializers.kt +++ b/src/main/kotlin/com/featurevisor/sdk/serializers/Serializers.kt @@ -1,15 +1,7 @@ package com.featurevisor.sdk.serializers import com.featurevisor.sdk.FeaturevisorInstance -import com.featurevisor.types.AndGroupSegment -import com.featurevisor.types.BucketBy -import com.featurevisor.types.Condition -import com.featurevisor.types.ConditionValue -import com.featurevisor.types.GroupSegment -import com.featurevisor.types.NotGroupSegment -import com.featurevisor.types.Operator -import com.featurevisor.types.OrGroupSegment -import com.featurevisor.types.VariableValue +import com.featurevisor.types.* import com.featurevisor.types.Required import kotlinx.serialization.* import kotlinx.serialization.descriptors.PolymorphicKind @@ -45,10 +37,13 @@ object RequiredSerializer: KSerializer{ Required.FeatureKey(tree.content) } is JsonArray -> { + // Never lies in JsonArray block Required.FeatureKey(tree.toString()) } - - else -> Required.FeatureKey("abc") + is JsonObject ->{ + val requiredWithVariation = RequiredWithVariation(tree["key"]?.jsonPrimitive?.content.orEmpty(),tree["variation"]?.jsonPrimitive?.content.orEmpty()) + Required.WithVariation(requiredWithVariation) + } } } } diff --git a/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt b/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt index db3d604..787c311 100644 --- a/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt +++ b/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt @@ -1,5 +1,4 @@ -@file:JvmName("TestExecuter") - +//@file:JvmName("TestExecuter") package com.featurevisor.testRunner import com.featurevisor.types.* @@ -24,7 +23,7 @@ fun main(args: Array) { } } -internal fun startTest(projectRootPath: String = "", testDirPath: String = "") { +fun startTest(projectRootPath: String = "", testDirPath: String = "") { val rootPath = projectRootPath.ifEmpty { getRootProjectDir() } @@ -78,57 +77,65 @@ internal fun getAllFilesInDirectory(projectRootPath: String, testDirPath: String } } -fun testSingleFeature(featureKey: String, projectRootPath: String) { - val test = parseTestFeatureAssertions("$projectRootPath/tests/$featureKey.spec.yml") +fun testSingleFeature(featureKey: String, projectRootPath: String = "", testDirPath: String = "") { + val rootPath = projectRootPath.ifEmpty { getRootProjectDir() } + val testDir = testDirPath.ifEmpty { "tests" } - val executionResult = ExecutionResult( - passed = false, - assertionsCount = AssertionsCount(0, 0) - ) + val test = parseTestFeatureAssertions("$rootPath/$testDir/$featureKey.feature.yml") - if (test == null) { - println("No File available") - return - } + test?.let { + val executionResult = ExecutionResult( + passed = false, + assertionsCount = AssertionsCount(0, 0) + ) - val testResult = testFeature(testFeature = (test as Test.Feature).value, projectRootPath) + val testResult = testFeature(testFeature = (test as Test.Feature).value, projectRootPath) - printTestResult(testResult) + printTestResult(testResult) - if (!testResult.passed) { - executionResult.passed = false + if (!testResult.passed) { + executionResult.passed = false - executionResult.assertionsCount.failed = testResult.assertions.count { !it.passed } - executionResult.assertionsCount.passed += testResult.assertions.size - executionResult.assertionsCount.failed - } else { - executionResult.assertionsCount.passed = testResult.assertions.size + executionResult.assertionsCount.failed = testResult.assertions.count { !it.passed } + executionResult.assertionsCount.passed += testResult.assertions.size - executionResult.assertionsCount.failed + } else { + executionResult.assertionsCount.passed = testResult.assertions.size + } + + printMessageInGreenColor("Test Assertion: ${executionResult.assertionsCount.passed} passed, ${executionResult.assertionsCount.failed} failed") } - printMessageInGreenColor("Test Assertion: ${executionResult.assertionsCount.passed} passed, ${executionResult.assertionsCount.failed} failed") } -fun testSingleSegment(featureKey: String, projectRootPath: String) { - val test = parseTestFeatureAssertions("$projectRootPath/tests/$featureKey.segment.yml") +fun testSingleSegment(segmentKey: String, projectRootPath: String = "", testDirPath: String = "") { - val executionResult = ExecutionResult( - passed = false, - assertionsCount = AssertionsCount(0, 0) - ) + val rootPath = projectRootPath.ifEmpty { getRootProjectDir() } + val testDir = testDirPath.ifEmpty { "tests" } - val testResult = testSegment(test = (test as Test.Segment).value, projectRootPath) + val test = parseTestFeatureAssertions("$rootPath/$testDir/$segmentKey.segment.yml") - printTestResult(testResult) + test?.let { + val executionResult = ExecutionResult( + passed = false, + assertionsCount = AssertionsCount(0, 0) + ) - if (!testResult.passed) { - executionResult.passed = false + val testResult = testSegment(test = (test as Test.Segment).value, projectRootPath) - executionResult.assertionsCount.failed = testResult.assertions.count { !it.passed } - executionResult.assertionsCount.passed += testResult.assertions.size - executionResult.assertionsCount.failed - } else { - executionResult.assertionsCount.passed = testResult.assertions.size - } + printTestResult(testResult) + + if (!testResult.passed) { + executionResult.passed = false - printMessageInGreenColor("Test Assertion: ${executionResult.assertionsCount.passed} passed, ${executionResult.assertionsCount.failed} failed") + executionResult.assertionsCount.failed = testResult.assertions.count { !it.passed } + executionResult.assertionsCount.passed += testResult.assertions.size - executionResult.assertionsCount.failed + } else { + executionResult.assertionsCount.passed = testResult.assertions.size + } + + printMessageInGreenColor("Test Assertion: ${executionResult.assertionsCount.passed} passed, ${executionResult.assertionsCount.failed} failed") + + } } private fun testAssertion(filePath: String, projectRootPath: String): ExecutionResult {