@eppo/js-client-sdk-common
Advanced tools
Comparing version 4.0.1 to 4.0.2
@@ -80,3 +80,6 @@ "use strict"; | ||
actionScoreEntries.forEach(([actionKey, actionScore]) => { | ||
if (currTopScore === null || actionScore > currTopScore) { | ||
if (currTopScore === null || | ||
currTopAction === null || | ||
actionScore > currTopScore || | ||
(actionScore === currTopScore && actionKey < currTopAction)) { | ||
currTopScore = actionScore; | ||
@@ -83,0 +86,0 @@ currTopAction = actionKey; |
@@ -169,2 +169,3 @@ import { IAssignmentLogger } from '../assignment-logger'; | ||
private getAssignmentVariation; | ||
private parseVariationWithDetails; | ||
private rethrowIfNotGraceful; | ||
@@ -171,0 +172,0 @@ /** |
@@ -14,2 +14,3 @@ "use strict"; | ||
const flag_evaluation_details_builder_1 = require("../flag-evaluation-details-builder"); | ||
const flag_evaluation_error_1 = require("../flag-evaluation-error"); | ||
const http_client_1 = require("../http-client"); | ||
@@ -413,4 +414,25 @@ const interfaces_1 = require("../interfaces"); | ||
const result = this.getAssignmentDetail(flagKey, subjectKey, subjectAttributes, expectedVariationType); | ||
if (!result.variation) { | ||
return this.parseVariationWithDetails(result, defaultValue, expectedVariationType); | ||
} | ||
catch (error) { | ||
const eppoValue = this.rethrowIfNotGraceful(error, defaultValue); | ||
if (error instanceof flag_evaluation_error_1.FlagEvaluationError && error.flagEvaluationDetails) { | ||
return { | ||
eppoValue, | ||
flagEvaluationDetails: error.flagEvaluationDetails, | ||
}; | ||
} | ||
else { | ||
const flagEvaluationDetails = new flag_evaluation_details_builder_1.FlagEvaluationDetailsBuilder('', [], '', '').buildForNoneResult('ASSIGNMENT_ERROR', `Assignment Error: ${error.message}`); | ||
return { | ||
eppoValue, | ||
flagEvaluationDetails, | ||
}; | ||
} | ||
} | ||
} | ||
parseVariationWithDetails(result, defaultValue, expectedVariationType) { | ||
try { | ||
if (!result.variation || result.flagEvaluationDetails.flagEvaluationCode !== 'MATCH') { | ||
return { | ||
eppoValue: defaultValue, | ||
@@ -427,6 +449,5 @@ flagEvaluationDetails: result.flagEvaluationDetails, | ||
const eppoValue = this.rethrowIfNotGraceful(error, defaultValue); | ||
const flagEvaluationDetails = new flag_evaluation_details_builder_1.FlagEvaluationDetailsBuilder('', [], '', '').buildForNoneResult('ASSIGNMENT_ERROR', `Assignment Error: ${error.message}`); | ||
return { | ||
eppoValue, | ||
flagEvaluationDetails, | ||
flagEvaluationDetails: result.flagEvaluationDetails, | ||
}; | ||
@@ -467,3 +488,8 @@ } | ||
if (!checkTypeMatch(expectedVariationType, flag.variationType)) { | ||
throw new TypeError(`Variation value does not have the correct type. Found: ${flag.variationType} != ${expectedVariationType} for flag ${flagKey}`); | ||
const errorMessage = `Variation value does not have the correct type. Found ${flag.variationType}, but expected ${expectedVariationType} for flag ${flagKey}`; | ||
if (this.isGracefulFailureMode) { | ||
const flagEvaluationDetails = flagEvaluationDetailsBuilder.buildForNoneResult('TYPE_MISMATCH', errorMessage); | ||
return (0, evaluator_1.noneResult)(flagKey, subjectKey, subjectAttributes, flagEvaluationDetails); | ||
} | ||
throw new TypeError(errorMessage); | ||
} | ||
@@ -481,8 +507,2 @@ if (!flag.enabled) { | ||
} | ||
if ((result === null || result === void 0 ? void 0 : result.variation) && !checkValueTypeMatch(expectedVariationType, result.variation.value)) { | ||
const { key: vKey, value: vValue } = result.variation; | ||
const reason = `Expected variation type ${expectedVariationType} does not match for variation '${vKey}' with value ${vValue}`; | ||
const flagEvaluationDetails = flagEvaluationDetailsBuilder.buildForNoneResult('TYPE_MISMATCH', reason); | ||
return (0, evaluator_1.noneResult)(flagKey, subjectKey, subjectAttributes, flagEvaluationDetails); | ||
} | ||
try { | ||
@@ -489,0 +509,0 @@ if (result === null || result === void 0 ? void 0 : result.doLog) { |
@@ -21,3 +21,3 @@ import { IFlagEvaluationDetails } from './flag-evaluation-details-builder'; | ||
matchesShard(shard: Shard, subjectKey: string, totalShards: number): boolean; | ||
private getMatchedEvaluationDetailsMessage; | ||
private getMatchedEvaluationCodeAndDescription; | ||
} | ||
@@ -24,0 +24,0 @@ export declare function isInShardRange(shard: number, range: Range): boolean; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.matchesRules = exports.noneResult = exports.hashKey = exports.isInShardRange = exports.Evaluator = void 0; | ||
const eppo_client_1 = require("./client/eppo-client"); | ||
const flag_evaluation_details_builder_1 = require("./flag-evaluation-details-builder"); | ||
const flag_evaluation_error_1 = require("./flag-evaluation-error"); | ||
const rules_1 = require("./rules"); | ||
@@ -9,4 +11,11 @@ const sharders_1 = require("./sharders"); | ||
constructor(sharder) { | ||
this.getMatchedEvaluationDetailsMessage = (allocation, split, subjectKey) => { | ||
this.getMatchedEvaluationCodeAndDescription = (variation, allocation, split, subjectKey, expectedVariationType) => { | ||
var _a; | ||
if (!(0, eppo_client_1.checkValueTypeMatch)(expectedVariationType, variation.value)) { | ||
const { key: vKey, value: vValue } = variation; | ||
return { | ||
flagEvaluationCode: 'ASSIGNMENT_ERROR', | ||
flagEvaluationDescription: `Variation (${vKey}) is configured for type ${expectedVariationType}, but is set to incompatible value (${vValue})`, | ||
}; | ||
} | ||
const hasDefinedRules = !!((_a = allocation.rules) === null || _a === void 0 ? void 0 : _a.length); | ||
@@ -17,8 +26,17 @@ const isExperiment = allocation.splits.length > 1; | ||
if (hasDefinedRules && isExperimentOrPartialRollout) { | ||
return `Supplied attributes match rules defined in allocation "${allocation.key}" and ${subjectKey} belongs to the range of traffic assigned to "${split.variationKey}".`; | ||
return { | ||
flagEvaluationCode: 'MATCH', | ||
flagEvaluationDescription: `Supplied attributes match rules defined in allocation "${allocation.key}" and ${subjectKey} belongs to the range of traffic assigned to "${split.variationKey}".`, | ||
}; | ||
} | ||
if (hasDefinedRules && !isExperimentOrPartialRollout) { | ||
return `Supplied attributes match rules defined in allocation "${allocation.key}".`; | ||
return { | ||
flagEvaluationCode: 'MATCH', | ||
flagEvaluationDescription: `Supplied attributes match rules defined in allocation "${allocation.key}".`, | ||
}; | ||
} | ||
return `${subjectKey} belongs to the range of traffic assigned to "${split.variationKey}" defined in allocation "${allocation.key}".`; | ||
return { | ||
flagEvaluationCode: 'MATCH', | ||
flagEvaluationDescription: `${subjectKey} belongs to the range of traffic assigned to "${split.variationKey}" defined in allocation "${allocation.key}".`, | ||
}; | ||
}; | ||
@@ -30,54 +48,63 @@ this.sharder = sharder !== null && sharder !== void 0 ? sharder : new sharders_1.MD5Sharder(); | ||
const flagEvaluationDetailsBuilder = new flag_evaluation_details_builder_1.FlagEvaluationDetailsBuilder(configDetails.configEnvironment.name, flag.allocations, configDetails.configFetchedAt, configDetails.configPublishedAt); | ||
if (!flag.enabled) { | ||
return noneResult(flag.key, subjectKey, subjectAttributes, flagEvaluationDetailsBuilder.buildForNoneResult('FLAG_UNRECOGNIZED_OR_DISABLED', `Unrecognized or disabled flag: ${flag.key}`)); | ||
} | ||
const now = new Date(); | ||
const unmatchedAllocations = []; | ||
for (let i = 0; i < flag.allocations.length; i++) { | ||
const allocation = flag.allocations[i]; | ||
const addUnmatchedAllocation = (code) => { | ||
unmatchedAllocations.push({ | ||
key: allocation.key, | ||
allocationEvaluationCode: code, | ||
orderPosition: i + 1, | ||
}); | ||
}; | ||
if (allocation.startAt && now < new Date(allocation.startAt)) { | ||
addUnmatchedAllocation(flag_evaluation_details_builder_1.AllocationEvaluationCode.BEFORE_START_TIME); | ||
continue; | ||
try { | ||
if (!flag.enabled) { | ||
return noneResult(flag.key, subjectKey, subjectAttributes, flagEvaluationDetailsBuilder.buildForNoneResult('FLAG_UNRECOGNIZED_OR_DISABLED', `Unrecognized or disabled flag: ${flag.key}`)); | ||
} | ||
if (allocation.endAt && now > new Date(allocation.endAt)) { | ||
addUnmatchedAllocation(flag_evaluation_details_builder_1.AllocationEvaluationCode.AFTER_END_TIME); | ||
continue; | ||
} | ||
const { matched, matchedRule } = matchesRules((_a = allocation === null || allocation === void 0 ? void 0 : allocation.rules) !== null && _a !== void 0 ? _a : [], Object.assign({ id: subjectKey }, subjectAttributes), obfuscated); | ||
if (matched) { | ||
for (const split of allocation.splits) { | ||
if (split.shards.every((shard) => this.matchesShard(shard, subjectKey, flag.totalShards))) { | ||
const variation = flag.variations[split.variationKey]; | ||
const flagEvaluationDetails = flagEvaluationDetailsBuilder | ||
.setMatch(i, variation, allocation, matchedRule, unmatchedAllocations, expectedVariationType) | ||
.build('MATCH', this.getMatchedEvaluationDetailsMessage(allocation, split, subjectKey)); | ||
return { | ||
flagKey: flag.key, | ||
subjectKey, | ||
subjectAttributes, | ||
allocationKey: allocation.key, | ||
variation, | ||
extraLogging: (_b = split.extraLogging) !== null && _b !== void 0 ? _b : {}, | ||
doLog: allocation.doLog, | ||
flagEvaluationDetails, | ||
}; | ||
const now = new Date(); | ||
for (let i = 0; i < flag.allocations.length; i++) { | ||
const allocation = flag.allocations[i]; | ||
const addUnmatchedAllocation = (code) => { | ||
flagEvaluationDetailsBuilder.addUnmatchedAllocation({ | ||
key: allocation.key, | ||
allocationEvaluationCode: code, | ||
orderPosition: i + 1, | ||
}); | ||
}; | ||
if (allocation.startAt && now < new Date(allocation.startAt)) { | ||
addUnmatchedAllocation(flag_evaluation_details_builder_1.AllocationEvaluationCode.BEFORE_START_TIME); | ||
continue; | ||
} | ||
if (allocation.endAt && now > new Date(allocation.endAt)) { | ||
addUnmatchedAllocation(flag_evaluation_details_builder_1.AllocationEvaluationCode.AFTER_END_TIME); | ||
continue; | ||
} | ||
const { matched, matchedRule } = matchesRules((_a = allocation === null || allocation === void 0 ? void 0 : allocation.rules) !== null && _a !== void 0 ? _a : [], Object.assign({ id: subjectKey }, subjectAttributes), obfuscated); | ||
if (matched) { | ||
for (const split of allocation.splits) { | ||
if (split.shards.every((shard) => this.matchesShard(shard, subjectKey, flag.totalShards))) { | ||
const variation = flag.variations[split.variationKey]; | ||
const { flagEvaluationCode, flagEvaluationDescription } = this.getMatchedEvaluationCodeAndDescription(variation, allocation, split, subjectKey, expectedVariationType); | ||
const flagEvaluationDetails = flagEvaluationDetailsBuilder | ||
.setMatch(i, variation, allocation, matchedRule, expectedVariationType) | ||
.build(flagEvaluationCode, flagEvaluationDescription); | ||
return { | ||
flagKey: flag.key, | ||
subjectKey, | ||
subjectAttributes, | ||
allocationKey: allocation.key, | ||
variation, | ||
extraLogging: (_b = split.extraLogging) !== null && _b !== void 0 ? _b : {}, | ||
doLog: allocation.doLog, | ||
flagEvaluationDetails, | ||
}; | ||
} | ||
} | ||
// matched, but does not fall within split range | ||
addUnmatchedAllocation(flag_evaluation_details_builder_1.AllocationEvaluationCode.TRAFFIC_EXPOSURE_MISS); | ||
} | ||
// matched, but does not fall within split range | ||
addUnmatchedAllocation(flag_evaluation_details_builder_1.AllocationEvaluationCode.TRAFFIC_EXPOSURE_MISS); | ||
else { | ||
addUnmatchedAllocation(flag_evaluation_details_builder_1.AllocationEvaluationCode.FAILING_RULE); | ||
} | ||
} | ||
else { | ||
addUnmatchedAllocation(flag_evaluation_details_builder_1.AllocationEvaluationCode.FAILING_RULE); | ||
return noneResult(flag.key, subjectKey, subjectAttributes, flagEvaluationDetailsBuilder.buildForNoneResult('DEFAULT_ALLOCATION_NULL', 'No allocations matched. Falling back to "Default Allocation", serving NULL')); | ||
} | ||
catch (err) { | ||
const flagEvaluationDetails = flagEvaluationDetailsBuilder.gracefulBuild('ASSIGNMENT_ERROR', `Assignment Error: ${err.message}`); | ||
if (flagEvaluationDetails) { | ||
const flagEvaluationError = new flag_evaluation_error_1.FlagEvaluationError(err.message); | ||
flagEvaluationError.flagEvaluationDetails = flagEvaluationDetails; | ||
throw flagEvaluationError; | ||
} | ||
throw err; | ||
} | ||
return noneResult(flag.key, subjectKey, subjectAttributes, flagEvaluationDetailsBuilder | ||
.setNoMatchFound(unmatchedAllocations) | ||
.build('DEFAULT_ALLOCATION_NULL', 'No allocations matched. Falling back to "Default Allocation", serving NULL')); | ||
} | ||
@@ -84,0 +111,0 @@ matchesShard(shard, subjectKey, totalShards) { |
@@ -42,11 +42,13 @@ import { Allocation, Variation, VariationType } from './interfaces'; | ||
private matchedAllocation; | ||
private unmatchedAllocations; | ||
private unevaluatedAllocations; | ||
private readonly unmatchedAllocations; | ||
private readonly unevaluatedAllocations; | ||
constructor(environmentName: string, allocations: Allocation[], configFetchedAt: string, configPublishedAt: string); | ||
addUnmatchedAllocation: (allocationEvaluation: AllocationEvaluation) => void; | ||
setNone: () => FlagEvaluationDetailsBuilder; | ||
setNoMatchFound: (unmatchedAllocations?: Array<AllocationEvaluation>) => FlagEvaluationDetailsBuilder; | ||
setMatch: (indexPosition: number, variation: Variation, allocation: Allocation, matchedRule: Rule | null, unmatchedAllocations: Array<AllocationEvaluation>, expectedVariationType: VariationType | undefined) => FlagEvaluationDetailsBuilder; | ||
setMatch: (indexPosition: number, variation: Variation, allocation: Allocation, matchedRule: Rule | null, expectedVariationType: VariationType | undefined) => FlagEvaluationDetailsBuilder; | ||
buildForNoneResult: (flagEvaluationCode: FlagEvaluationCode, flagEvaluationDescription: string) => IFlagEvaluationDetails; | ||
build: (flagEvaluationCode: FlagEvaluationCode, flagEvaluationDescription: string) => IFlagEvaluationDetails; | ||
gracefulBuild: (flagEvaluationCode: FlagEvaluationCode, flagEvaluationDescription: string) => IFlagEvaluationDetails | null; | ||
private calculateUnevaluatedAllocations; | ||
} | ||
//# sourceMappingURL=flag-evaluation-details-builder.d.ts.map |
@@ -35,2 +35,5 @@ "use strict"; | ||
this.unevaluatedAllocations = []; | ||
this.addUnmatchedAllocation = (allocationEvaluation) => { | ||
this.unmatchedAllocations.push(allocationEvaluation); | ||
}; | ||
this.setNone = () => { | ||
@@ -41,20 +44,5 @@ this.variationKey = null; | ||
this.matchedAllocation = null; | ||
this.unmatchedAllocations = []; | ||
this.unevaluatedAllocations = this.allocations.map((allocation, i) => ({ | ||
key: allocation.key, | ||
allocationEvaluationCode: AllocationEvaluationCode.UNEVALUATED, | ||
orderPosition: i + 1, | ||
})); | ||
return this; | ||
}; | ||
this.setNoMatchFound = (unmatchedAllocations = []) => { | ||
this.variationKey = null; | ||
this.variationValue = null; | ||
this.matchedAllocation = null; | ||
this.matchedRule = null; | ||
this.unmatchedAllocations = unmatchedAllocations; | ||
this.unevaluatedAllocations = []; | ||
return this; | ||
}; | ||
this.setMatch = (indexPosition, variation, allocation, matchedRule, unmatchedAllocations, expectedVariationType) => { | ||
this.setMatch = (indexPosition, variation, allocation, matchedRule, expectedVariationType) => { | ||
this.variationKey = variation.key; | ||
@@ -73,10 +61,2 @@ // variation.value needs to be parsed into a JSON object if the variation type is JSON | ||
}; | ||
this.unmatchedAllocations = unmatchedAllocations; | ||
const unevaluatedStartIndex = indexPosition + 1; | ||
const unevaluatedStartOrderPosition = unevaluatedStartIndex + 1; // orderPosition is 1-indexed to match UI | ||
this.unevaluatedAllocations = this.allocations.slice(unevaluatedStartIndex).map((allocation, i) => ({ | ||
key: allocation.key, | ||
allocationEvaluationCode: AllocationEvaluationCode.UNEVALUATED, | ||
orderPosition: unevaluatedStartOrderPosition + i, | ||
})); | ||
return this; | ||
@@ -98,4 +78,23 @@ }; | ||
unmatchedAllocations: this.unmatchedAllocations, | ||
unevaluatedAllocations: this.unevaluatedAllocations, | ||
unevaluatedAllocations: this.calculateUnevaluatedAllocations(), | ||
}); | ||
this.gracefulBuild = (flagEvaluationCode, flagEvaluationDescription) => { | ||
try { | ||
return this.build(flagEvaluationCode, flagEvaluationDescription); | ||
} | ||
catch (err) { | ||
return null; | ||
} | ||
}; | ||
this.calculateUnevaluatedAllocations = () => { | ||
const unevaluatedStartIndex = this.matchedAllocation | ||
? this.unmatchedAllocations.length + 1 | ||
: this.unmatchedAllocations.length; | ||
const unevaluatedStartOrderPosition = unevaluatedStartIndex + 1; // orderPosition is 1-indexed to match UI | ||
return this.allocations.slice(unevaluatedStartIndex).map((allocation, i) => ({ | ||
key: allocation.key, | ||
allocationEvaluationCode: AllocationEvaluationCode.UNEVALUATED, | ||
orderPosition: unevaluatedStartOrderPosition + i, | ||
})); | ||
}; | ||
this.setNone(); | ||
@@ -102,0 +101,0 @@ } |
{ | ||
"name": "@eppo/js-client-sdk-common", | ||
"version": "4.0.1", | ||
"version": "4.0.2", | ||
"description": "Eppo SDK for client-side JavaScript applications (base for both web and react native)", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -147,3 +147,8 @@ import { BANDIT_ASSIGNMENT_SHARDS } from './constants'; | ||
actionScoreEntries.forEach(([actionKey, actionScore]) => { | ||
if (currTopScore === null || actionScore > currTopScore) { | ||
if ( | ||
currTopScore === null || | ||
currTopAction === null || | ||
actionScore > currTopScore || | ||
(actionScore === currTopScore && actionKey < currTopAction) | ||
) { | ||
currTopScore = actionScore; | ||
@@ -150,0 +155,0 @@ currTopAction = actionKey; |
@@ -27,2 +27,3 @@ import ApiEndpoints from '../api-endpoints'; | ||
} from '../flag-evaluation-details-builder'; | ||
import { FlagEvaluationError } from '../flag-evaluation-error'; | ||
import FetchHttpClient from '../http-client'; | ||
@@ -683,4 +684,32 @@ import { | ||
); | ||
return this.parseVariationWithDetails(result, defaultValue, expectedVariationType); | ||
} catch (error) { | ||
const eppoValue = this.rethrowIfNotGraceful(error, defaultValue); | ||
if (error instanceof FlagEvaluationError && error.flagEvaluationDetails) { | ||
return { | ||
eppoValue, | ||
flagEvaluationDetails: error.flagEvaluationDetails, | ||
}; | ||
} else { | ||
const flagEvaluationDetails = new FlagEvaluationDetailsBuilder( | ||
'', | ||
[], | ||
'', | ||
'', | ||
).buildForNoneResult('ASSIGNMENT_ERROR', `Assignment Error: ${error.message}`); | ||
return { | ||
eppoValue, | ||
flagEvaluationDetails, | ||
}; | ||
} | ||
} | ||
} | ||
if (!result.variation) { | ||
private parseVariationWithDetails( | ||
result: FlagEvaluation, | ||
defaultValue: EppoValue, | ||
expectedVariationType: VariationType, | ||
): { eppoValue: EppoValue; flagEvaluationDetails: IFlagEvaluationDetails } { | ||
try { | ||
if (!result.variation || result.flagEvaluationDetails.flagEvaluationCode !== 'MATCH') { | ||
return { | ||
@@ -691,3 +720,2 @@ eppoValue: defaultValue, | ||
} | ||
return { | ||
@@ -699,11 +727,5 @@ eppoValue: EppoValue.valueOf(result.variation.value, expectedVariationType), | ||
const eppoValue = this.rethrowIfNotGraceful(error, defaultValue); | ||
const flagEvaluationDetails = new FlagEvaluationDetailsBuilder( | ||
'', | ||
[], | ||
'', | ||
'', | ||
).buildForNoneResult('ASSIGNMENT_ERROR', `Assignment Error: ${error.message}`); | ||
return { | ||
eppoValue, | ||
flagEvaluationDetails, | ||
flagEvaluationDetails: result.flagEvaluationDetails, | ||
}; | ||
@@ -757,5 +779,11 @@ } | ||
if (!checkTypeMatch(expectedVariationType, flag.variationType)) { | ||
throw new TypeError( | ||
`Variation value does not have the correct type. Found: ${flag.variationType} != ${expectedVariationType} for flag ${flagKey}`, | ||
); | ||
const errorMessage = `Variation value does not have the correct type. Found ${flag.variationType}, but expected ${expectedVariationType} for flag ${flagKey}`; | ||
if (this.isGracefulFailureMode) { | ||
const flagEvaluationDetails = flagEvaluationDetailsBuilder.buildForNoneResult( | ||
'TYPE_MISMATCH', | ||
errorMessage, | ||
); | ||
return noneResult(flagKey, subjectKey, subjectAttributes, flagEvaluationDetails); | ||
} | ||
throw new TypeError(errorMessage); | ||
} | ||
@@ -786,12 +814,2 @@ | ||
if (result?.variation && !checkValueTypeMatch(expectedVariationType, result.variation.value)) { | ||
const { key: vKey, value: vValue } = result.variation; | ||
const reason = `Expected variation type ${expectedVariationType} does not match for variation '${vKey}' with value ${vValue}`; | ||
const flagEvaluationDetails = flagEvaluationDetailsBuilder.buildForNoneResult( | ||
'TYPE_MISMATCH', | ||
reason, | ||
); | ||
return noneResult(flagKey, subjectKey, subjectAttributes, flagEvaluationDetails); | ||
} | ||
try { | ||
@@ -798,0 +816,0 @@ if (result?.doLog) { |
@@ -0,7 +1,9 @@ | ||
import { checkValueTypeMatch } from './client/eppo-client'; | ||
import { | ||
AllocationEvaluation, | ||
AllocationEvaluationCode, | ||
IFlagEvaluationDetails, | ||
FlagEvaluationDetailsBuilder, | ||
FlagEvaluationCode, | ||
} from './flag-evaluation-details-builder'; | ||
import { FlagEvaluationError } from './flag-evaluation-error'; | ||
import { | ||
@@ -53,4 +55,74 @@ Flag, | ||
); | ||
try { | ||
if (!flag.enabled) { | ||
return noneResult( | ||
flag.key, | ||
subjectKey, | ||
subjectAttributes, | ||
flagEvaluationDetailsBuilder.buildForNoneResult( | ||
'FLAG_UNRECOGNIZED_OR_DISABLED', | ||
`Unrecognized or disabled flag: ${flag.key}`, | ||
), | ||
); | ||
} | ||
if (!flag.enabled) { | ||
const now = new Date(); | ||
for (let i = 0; i < flag.allocations.length; i++) { | ||
const allocation = flag.allocations[i]; | ||
const addUnmatchedAllocation = (code: AllocationEvaluationCode) => { | ||
flagEvaluationDetailsBuilder.addUnmatchedAllocation({ | ||
key: allocation.key, | ||
allocationEvaluationCode: code, | ||
orderPosition: i + 1, | ||
}); | ||
}; | ||
if (allocation.startAt && now < new Date(allocation.startAt)) { | ||
addUnmatchedAllocation(AllocationEvaluationCode.BEFORE_START_TIME); | ||
continue; | ||
} | ||
if (allocation.endAt && now > new Date(allocation.endAt)) { | ||
addUnmatchedAllocation(AllocationEvaluationCode.AFTER_END_TIME); | ||
continue; | ||
} | ||
const { matched, matchedRule } = matchesRules( | ||
allocation?.rules ?? [], | ||
{ id: subjectKey, ...subjectAttributes }, | ||
obfuscated, | ||
); | ||
if (matched) { | ||
for (const split of allocation.splits) { | ||
if ( | ||
split.shards.every((shard) => this.matchesShard(shard, subjectKey, flag.totalShards)) | ||
) { | ||
const variation = flag.variations[split.variationKey]; | ||
const { flagEvaluationCode, flagEvaluationDescription } = | ||
this.getMatchedEvaluationCodeAndDescription( | ||
variation, | ||
allocation, | ||
split, | ||
subjectKey, | ||
expectedVariationType, | ||
); | ||
const flagEvaluationDetails = flagEvaluationDetailsBuilder | ||
.setMatch(i, variation, allocation, matchedRule, expectedVariationType) | ||
.build(flagEvaluationCode, flagEvaluationDescription); | ||
return { | ||
flagKey: flag.key, | ||
subjectKey, | ||
subjectAttributes, | ||
allocationKey: allocation.key, | ||
variation, | ||
extraLogging: split.extraLogging ?? {}, | ||
doLog: allocation.doLog, | ||
flagEvaluationDetails, | ||
}; | ||
} | ||
} | ||
// matched, but does not fall within split range | ||
addUnmatchedAllocation(AllocationEvaluationCode.TRAFFIC_EXPOSURE_MISS); | ||
} else { | ||
addUnmatchedAllocation(AllocationEvaluationCode.FAILING_RULE); | ||
} | ||
} | ||
return noneResult( | ||
@@ -61,81 +133,18 @@ flag.key, | ||
flagEvaluationDetailsBuilder.buildForNoneResult( | ||
'FLAG_UNRECOGNIZED_OR_DISABLED', | ||
`Unrecognized or disabled flag: ${flag.key}`, | ||
'DEFAULT_ALLOCATION_NULL', | ||
'No allocations matched. Falling back to "Default Allocation", serving NULL', | ||
), | ||
); | ||
} | ||
const now = new Date(); | ||
const unmatchedAllocations: Array<AllocationEvaluation> = []; | ||
for (let i = 0; i < flag.allocations.length; i++) { | ||
const allocation = flag.allocations[i]; | ||
const addUnmatchedAllocation = (code: AllocationEvaluationCode) => { | ||
unmatchedAllocations.push({ | ||
key: allocation.key, | ||
allocationEvaluationCode: code, | ||
orderPosition: i + 1, | ||
}); | ||
}; | ||
if (allocation.startAt && now < new Date(allocation.startAt)) { | ||
addUnmatchedAllocation(AllocationEvaluationCode.BEFORE_START_TIME); | ||
continue; | ||
} | ||
if (allocation.endAt && now > new Date(allocation.endAt)) { | ||
addUnmatchedAllocation(AllocationEvaluationCode.AFTER_END_TIME); | ||
continue; | ||
} | ||
const { matched, matchedRule } = matchesRules( | ||
allocation?.rules ?? [], | ||
{ id: subjectKey, ...subjectAttributes }, | ||
obfuscated, | ||
} catch (err) { | ||
const flagEvaluationDetails = flagEvaluationDetailsBuilder.gracefulBuild( | ||
'ASSIGNMENT_ERROR', | ||
`Assignment Error: ${err.message}`, | ||
); | ||
if (matched) { | ||
for (const split of allocation.splits) { | ||
if ( | ||
split.shards.every((shard) => this.matchesShard(shard, subjectKey, flag.totalShards)) | ||
) { | ||
const variation = flag.variations[split.variationKey]; | ||
const flagEvaluationDetails = flagEvaluationDetailsBuilder | ||
.setMatch( | ||
i, | ||
variation, | ||
allocation, | ||
matchedRule, | ||
unmatchedAllocations, | ||
expectedVariationType, | ||
) | ||
.build( | ||
'MATCH', | ||
this.getMatchedEvaluationDetailsMessage(allocation, split, subjectKey), | ||
); | ||
return { | ||
flagKey: flag.key, | ||
subjectKey, | ||
subjectAttributes, | ||
allocationKey: allocation.key, | ||
variation, | ||
extraLogging: split.extraLogging ?? {}, | ||
doLog: allocation.doLog, | ||
flagEvaluationDetails, | ||
}; | ||
} | ||
} | ||
// matched, but does not fall within split range | ||
addUnmatchedAllocation(AllocationEvaluationCode.TRAFFIC_EXPOSURE_MISS); | ||
} else { | ||
addUnmatchedAllocation(AllocationEvaluationCode.FAILING_RULE); | ||
if (flagEvaluationDetails) { | ||
const flagEvaluationError = new FlagEvaluationError(err.message); | ||
flagEvaluationError.flagEvaluationDetails = flagEvaluationDetails; | ||
throw flagEvaluationError; | ||
} | ||
throw err; | ||
} | ||
return noneResult( | ||
flag.key, | ||
subjectKey, | ||
subjectAttributes, | ||
flagEvaluationDetailsBuilder | ||
.setNoMatchFound(unmatchedAllocations) | ||
.build( | ||
'DEFAULT_ALLOCATION_NULL', | ||
'No allocations matched. Falling back to "Default Allocation", serving NULL', | ||
), | ||
); | ||
} | ||
@@ -148,7 +157,16 @@ | ||
private getMatchedEvaluationDetailsMessage = ( | ||
private getMatchedEvaluationCodeAndDescription = ( | ||
variation: Variation, | ||
allocation: Allocation, | ||
split: Split, | ||
subjectKey: string, | ||
): string => { | ||
expectedVariationType: VariationType | undefined, | ||
): { flagEvaluationCode: FlagEvaluationCode; flagEvaluationDescription: string } => { | ||
if (!checkValueTypeMatch(expectedVariationType, variation.value)) { | ||
const { key: vKey, value: vValue } = variation; | ||
return { | ||
flagEvaluationCode: 'ASSIGNMENT_ERROR', | ||
flagEvaluationDescription: `Variation (${vKey}) is configured for type ${expectedVariationType}, but is set to incompatible value (${vValue})`, | ||
}; | ||
} | ||
const hasDefinedRules = !!allocation.rules?.length; | ||
@@ -160,8 +178,17 @@ const isExperiment = allocation.splits.length > 1; | ||
if (hasDefinedRules && isExperimentOrPartialRollout) { | ||
return `Supplied attributes match rules defined in allocation "${allocation.key}" and ${subjectKey} belongs to the range of traffic assigned to "${split.variationKey}".`; | ||
return { | ||
flagEvaluationCode: 'MATCH', | ||
flagEvaluationDescription: `Supplied attributes match rules defined in allocation "${allocation.key}" and ${subjectKey} belongs to the range of traffic assigned to "${split.variationKey}".`, | ||
}; | ||
} | ||
if (hasDefinedRules && !isExperimentOrPartialRollout) { | ||
return `Supplied attributes match rules defined in allocation "${allocation.key}".`; | ||
return { | ||
flagEvaluationCode: 'MATCH', | ||
flagEvaluationDescription: `Supplied attributes match rules defined in allocation "${allocation.key}".`, | ||
}; | ||
} | ||
return `${subjectKey} belongs to the range of traffic assigned to "${split.variationKey}" defined in allocation "${allocation.key}".`; | ||
return { | ||
flagEvaluationCode: 'MATCH', | ||
flagEvaluationDescription: `${subjectKey} belongs to the range of traffic assigned to "${split.variationKey}" defined in allocation "${allocation.key}".`, | ||
}; | ||
}; | ||
@@ -168,0 +195,0 @@ } |
@@ -52,4 +52,4 @@ import { Allocation, Variation, VariationType } from './interfaces'; | ||
private matchedAllocation: IFlagEvaluationDetails['matchedAllocation'] = null; | ||
private unmatchedAllocations: IFlagEvaluationDetails['unmatchedAllocations'] = []; | ||
private unevaluatedAllocations: IFlagEvaluationDetails['unevaluatedAllocations'] = []; | ||
private readonly unmatchedAllocations: IFlagEvaluationDetails['unmatchedAllocations'] = []; | ||
private readonly unevaluatedAllocations: IFlagEvaluationDetails['unevaluatedAllocations'] = []; | ||
@@ -65,2 +65,6 @@ constructor( | ||
addUnmatchedAllocation = (allocationEvaluation: AllocationEvaluation) => { | ||
this.unmatchedAllocations.push(allocationEvaluation); | ||
}; | ||
setNone = (): FlagEvaluationDetailsBuilder => { | ||
@@ -71,25 +75,5 @@ this.variationKey = null; | ||
this.matchedAllocation = null; | ||
this.unmatchedAllocations = []; | ||
this.unevaluatedAllocations = this.allocations.map( | ||
(allocation, i): AllocationEvaluation => ({ | ||
key: allocation.key, | ||
allocationEvaluationCode: AllocationEvaluationCode.UNEVALUATED, | ||
orderPosition: i + 1, | ||
}), | ||
); | ||
return this; | ||
}; | ||
setNoMatchFound = ( | ||
unmatchedAllocations: Array<AllocationEvaluation> = [], | ||
): FlagEvaluationDetailsBuilder => { | ||
this.variationKey = null; | ||
this.variationValue = null; | ||
this.matchedAllocation = null; | ||
this.matchedRule = null; | ||
this.unmatchedAllocations = unmatchedAllocations; | ||
this.unevaluatedAllocations = []; | ||
return this; | ||
}; | ||
setMatch = ( | ||
@@ -100,3 +84,2 @@ indexPosition: number, | ||
matchedRule: Rule | null, | ||
unmatchedAllocations: Array<AllocationEvaluation>, | ||
expectedVariationType: VariationType | undefined, | ||
@@ -117,13 +100,2 @@ ): FlagEvaluationDetailsBuilder => { | ||
}; | ||
this.unmatchedAllocations = unmatchedAllocations; | ||
const unevaluatedStartIndex = indexPosition + 1; | ||
const unevaluatedStartOrderPosition = unevaluatedStartIndex + 1; // orderPosition is 1-indexed to match UI | ||
this.unevaluatedAllocations = this.allocations.slice(unevaluatedStartIndex).map( | ||
(allocation, i) => | ||
({ | ||
key: allocation.key, | ||
allocationEvaluationCode: AllocationEvaluationCode.UNEVALUATED, | ||
orderPosition: unevaluatedStartOrderPosition + i, | ||
} as AllocationEvaluation), | ||
); | ||
return this; | ||
@@ -153,4 +125,30 @@ }; | ||
unmatchedAllocations: this.unmatchedAllocations, | ||
unevaluatedAllocations: this.unevaluatedAllocations, | ||
unevaluatedAllocations: this.calculateUnevaluatedAllocations(), | ||
}); | ||
gracefulBuild = ( | ||
flagEvaluationCode: FlagEvaluationCode, | ||
flagEvaluationDescription: string, | ||
): IFlagEvaluationDetails | null => { | ||
try { | ||
return this.build(flagEvaluationCode, flagEvaluationDescription); | ||
} catch (err) { | ||
return null; | ||
} | ||
}; | ||
private calculateUnevaluatedAllocations = (): Array<AllocationEvaluation> => { | ||
const unevaluatedStartIndex = this.matchedAllocation | ||
? this.unmatchedAllocations.length + 1 | ||
: this.unmatchedAllocations.length; | ||
const unevaluatedStartOrderPosition = unevaluatedStartIndex + 1; // orderPosition is 1-indexed to match UI | ||
return this.allocations.slice(unevaluatedStartIndex).map( | ||
(allocation, i) => | ||
({ | ||
key: allocation.key, | ||
allocationEvaluationCode: AllocationEvaluationCode.UNEVALUATED, | ||
orderPosition: unevaluatedStartOrderPosition + i, | ||
} as AllocationEvaluation), | ||
); | ||
}; | ||
} |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
801706
156
6526