diff --git a/lib/common_exports.ts b/lib/common_exports.ts index c043796df..583f1b455 100644 --- a/lib/common_exports.ts +++ b/lib/common_exports.ts @@ -14,6 +14,5 @@ * limitations under the License. */ -export { LOG_LEVEL } from './utils/enums'; export { createStaticProjectConfigManager } from './project_config/config_manager_factory'; export { PollingConfigManagerConfig } from './project_config/config_manager_factory'; diff --git a/lib/core/audience_evaluator/index.tests.js b/lib/core/audience_evaluator/index.tests.js index 6ab30ca08..bc725a428 100644 --- a/lib/core/audience_evaluator/index.tests.js +++ b/lib/core/audience_evaluator/index.tests.js @@ -21,7 +21,6 @@ import AudienceEvaluator, { createAudienceEvaluator } from './index'; import * as conditionTreeEvaluator from '../condition_tree_evaluator'; import * as customAttributeConditionEvaluator from '../custom_attribute_condition_evaluator'; import { AUDIENCE_EVALUATION_RESULT, EVALUATING_AUDIENCE } from '../../log_messages'; -// import { getEvaluator } from '../custom_attribute_condition_evaluator'; var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); var mockLogger = { diff --git a/lib/core/audience_evaluator/index.ts b/lib/core/audience_evaluator/index.ts index e110ab569..4ada47bbe 100644 --- a/lib/core/audience_evaluator/index.ts +++ b/lib/core/audience_evaluator/index.ts @@ -13,9 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - LOG_LEVEL, -} from '../../utils/enums'; import * as conditionTreeEvaluator from '../condition_tree_evaluator'; import * as customAttributeConditionEvaluator from '../custom_attribute_condition_evaluator'; import * as odpSegmentsConditionEvaluator from './odp_segment_condition_evaluator'; @@ -24,8 +21,6 @@ import { CONDITION_EVALUATOR_ERROR, UNKNOWN_CONDITION_TYPE } from '../../error_m import { AUDIENCE_EVALUATION_RESULT, EVALUATING_AUDIENCE} from '../../log_messages'; import { LoggerFacade } from '../../logging/logger'; -const MODULE_NAME = 'AUDIENCE_EVALUATOR'; - export class AudienceEvaluator { private logger?: LoggerFacade; diff --git a/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts index 4984dce51..d97ee9db5 100644 --- a/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts +++ b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts @@ -17,8 +17,6 @@ import { UNKNOWN_MATCH_TYPE } from '../../../error_messages'; import { LoggerFacade } from '../../../logging/logger'; import { Condition, OptimizelyUserContext } from '../../../shared_types'; -const MODULE_NAME = 'ODP_SEGMENT_CONDITION_EVALUATOR'; - const QUALIFIED_MATCH_TYPE = 'qualified'; const MATCH_TYPES = [ diff --git a/lib/core/bucketer/index.tests.js b/lib/core/bucketer/index.tests.js index c87bb35d4..12bea0d3a 100644 --- a/lib/core/bucketer/index.tests.js +++ b/lib/core/bucketer/index.tests.js @@ -29,6 +29,7 @@ import { USER_NOT_IN_ANY_EXPERIMENT, USER_ASSIGNED_TO_EXPERIMENT_BUCKET, } from '.'; +import { OptimizelyError } from '../../error/optimizly_error'; var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); var testData = getTestProjectConfig(); @@ -204,9 +205,11 @@ describe('lib/core/bucketer', function () { var bucketerParamsWithInvalidGroupId = cloneDeep(bucketerParams); bucketerParamsWithInvalidGroupId.experimentIdMap[configObj.experiments[4].id].groupId = '6969'; - assert.throws(function () { + const ex = assert.throws(function () { bucketer.bucket(bucketerParamsWithInvalidGroupId); - }, sprintf(INVALID_GROUP_ID, 'BUCKETER', '6969')); + }); + assert.equal(ex.baseMessage, INVALID_GROUP_ID); + assert.deepEqual(ex.params, ['6969']); }); }); @@ -343,10 +346,7 @@ describe('lib/core/bucketer', function () { const response = assert.throws(function() { bucketer._generateBucketValue(null); } ); - expect([ - sprintf(INVALID_BUCKETING_ID, 'BUCKETER', null, "Cannot read property 'length' of null"), // node v14 - sprintf(INVALID_BUCKETING_ID, 'BUCKETER', null, "Cannot read properties of null (reading \'length\')") // node v16 - ]).contain(response.message); + expect(response.baseMessage).to.equal(INVALID_BUCKETING_ID); }); }); diff --git a/lib/core/bucketer/index.ts b/lib/core/bucketer/index.ts index 88df2e818..6d23856e5 100644 --- a/lib/core/bucketer/index.ts +++ b/lib/core/bucketer/index.ts @@ -17,7 +17,6 @@ /** * Bucketer API for determining the variation id from the specified parameters */ -import { sprintf } from '../../utils/fns'; import murmurhash from 'murmurhash'; import { LoggerFacade } from '../../logging/logger'; import { @@ -27,8 +26,8 @@ import { Group, } from '../../shared_types'; -import { LOG_LEVEL } from '../../utils/enums'; import { INVALID_BUCKETING_ID, INVALID_GROUP_ID } from '../../error_messages'; +import { OptimizelyError } from '../../error/optimizly_error'; export const USER_NOT_IN_ANY_EXPERIMENT = 'User %s is not in any experiment of group %s.'; export const USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP = 'User %s is not in experiment %s of group %s.'; @@ -39,7 +38,6 @@ export const INVALID_VARIATION_ID = 'Bucketed into an invalid variation ID. Retu const HASH_SEED = 1; const MAX_HASH_VALUE = Math.pow(2, 32); const MAX_TRAFFIC_VALUE = 10000; -const MODULE_NAME = 'BUCKETER'; const RANDOM_POLICY = 'random'; /** @@ -66,7 +64,7 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse if (groupId) { const group = bucketerParams.groupIdMap[groupId]; if (!group) { - throw new Error(sprintf(INVALID_GROUP_ID, MODULE_NAME, groupId)); + throw new OptimizelyError(INVALID_GROUP_ID, groupId); } if (group.policy === RANDOM_POLICY) { const bucketedExperimentId = bucketUserIntoExperiment( @@ -85,7 +83,6 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse ); decideReasons.push([ USER_NOT_IN_ANY_EXPERIMENT, - MODULE_NAME, bucketerParams.userId, groupId, ]); @@ -105,7 +102,6 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse ); decideReasons.push([ USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, - MODULE_NAME, bucketerParams.userId, bucketerParams.experimentKey, groupId, @@ -125,7 +121,6 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse ); decideReasons.push([ USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, - MODULE_NAME, bucketerParams.userId, bucketerParams.experimentKey, groupId, @@ -142,7 +137,6 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse ); decideReasons.push([ USER_ASSIGNED_TO_EXPERIMENT_BUCKET, - MODULE_NAME, bucketValue, bucketerParams.userId, ]); @@ -151,8 +145,8 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse if (entityId !== null) { if (!bucketerParams.variationIdMap[entityId]) { if (entityId) { - bucketerParams.logger?.warn(INVALID_VARIATION_ID, MODULE_NAME); - decideReasons.push([INVALID_VARIATION_ID, MODULE_NAME]); + bucketerParams.logger?.warn(INVALID_VARIATION_ID); + decideReasons.push([INVALID_VARIATION_ID]); } return { result: null, @@ -228,7 +222,7 @@ export const _generateBucketValue = function(bucketingKey: string): number { const ratio = hashValue / MAX_HASH_VALUE; return Math.floor(ratio * MAX_TRAFFIC_VALUE); } catch (ex: any) { - throw new Error(sprintf(INVALID_BUCKETING_ID, MODULE_NAME, bucketingKey, ex.message)); + throw new OptimizelyError(INVALID_BUCKETING_ID, bucketingKey, ex.message); } }; diff --git a/lib/core/custom_attribute_condition_evaluator/index.ts b/lib/core/custom_attribute_condition_evaluator/index.ts index 0a3c0b0a6..c722c1837 100644 --- a/lib/core/custom_attribute_condition_evaluator/index.ts +++ b/lib/core/custom_attribute_condition_evaluator/index.ts @@ -29,8 +29,6 @@ import { } from '../../error_messages'; import { LoggerFacade } from '../../logging/logger'; -const MODULE_NAME = 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR'; - const EXACT_MATCH_TYPE = 'exact'; const EXISTS_MATCH_TYPE = 'exists'; const GREATER_OR_EQUAL_THAN_MATCH_TYPE = 'ge'; diff --git a/lib/core/decision_service/index.tests.js b/lib/core/decision_service/index.tests.js index 39d8889fd..470d998eb 100644 --- a/lib/core/decision_service/index.tests.js +++ b/lib/core/decision_service/index.tests.js @@ -39,27 +39,31 @@ import { getTestProjectConfig, getTestProjectConfigWithFeatures, } from '../../tests/test_data'; + import { - AUDIENCE_EVALUATION_RESULT_COMBINED, - EVALUATING_AUDIENCES_COMBINED, - USER_FORCED_IN_VARIATION, USER_HAS_NO_FORCED_VARIATION, - USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, - USER_NOT_IN_EXPERIMENT, + VALID_BUCKETING_ID, + SAVED_USER_VARIATION, + SAVED_VARIATION_NOT_FOUND, +} from '../../log_messages'; + +import { EXPERIMENT_NOT_RUNNING, RETURNING_STORED_VARIATION, + USER_NOT_IN_EXPERIMENT, + USER_FORCED_IN_VARIATION, + EVALUATING_AUDIENCES_COMBINED, + AUDIENCE_EVALUATION_RESULT_COMBINED, + USER_IN_ROLLOUT, + USER_NOT_IN_ROLLOUT, FEATURE_HAS_NO_EXPERIMENTS, - NO_ROLLOUT_EXISTS, + USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, + USER_NOT_BUCKETED_INTO_TARGETING_RULE, USER_BUCKETED_INTO_TARGETING_RULE, - USER_IN_ROLLOUT, + NO_ROLLOUT_EXISTS, USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, - USER_NOT_BUCKETED_INTO_TARGETING_RULE, - USER_NOT_IN_ROLLOUT, - VALID_BUCKETING_ID, - SAVED_USER_VARIATION, - SAVED_VARIATION_NOT_FOUND -} from '../../log_messages'; -import { mock } from 'node:test'; +} from '../decision_service/index'; + import { BUCKETING_ID_NOT_STRING, USER_PROFILE_LOOKUP_ERROR, USER_PROFILE_SAVE_ERROR } from '../../error_messages'; var testData = getTestProjectConfig(); diff --git a/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts index 7ce6a1c85..9867d9b19 100644 --- a/lib/core/decision_service/index.ts +++ b/lib/core/decision_service/index.ts @@ -14,15 +14,11 @@ * limitations under the License. */ import { LoggerFacade } from '../../logging/logger' -import { sprintf } from '../../utils/fns'; - -import fns from '../../utils/fns'; import { bucket } from '../bucketer'; import { AUDIENCE_EVALUATION_TYPES, CONTROL_ATTRIBUTES, DECISION_SOURCES, - LOG_LEVEL, } from '../../utils/enums'; import { getAudiencesById, @@ -53,51 +49,55 @@ import { Variation, } from '../../shared_types'; import { - IMPROPERLY_FORMATTED_EXPERIMENT, - INVALID_ROLLOUT_ID, INVALID_USER_ID, INVALID_VARIATION_KEY, NO_VARIATION_FOR_EXPERIMENT_KEY, USER_NOT_IN_FORCED_VARIATION, USER_PROFILE_LOOKUP_ERROR, USER_PROFILE_SAVE_ERROR, - FORCED_BUCKETING_FAILED, BUCKETING_ID_NOT_STRING, } from '../../error_messages'; import { - AUDIENCE_EVALUATION_RESULT_COMBINED, - EVALUATING_AUDIENCES_COMBINED, - EXPERIMENT_NOT_RUNNING, - FEATURE_HAS_NO_EXPERIMENTS, - NO_ROLLOUT_EXISTS, - RETURNING_STORED_VARIATION, - ROLLOUT_HAS_NO_EXPERIMENTS, SAVED_USER_VARIATION, SAVED_VARIATION_NOT_FOUND, - USER_BUCKETED_INTO_TARGETING_RULE, - USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, - USER_FORCED_IN_VARIATION, USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, - USER_HAS_FORCED_VARIATION, USER_HAS_NO_FORCED_VARIATION, - USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, - USER_HAS_NO_VARIATION, - USER_HAS_VARIATION, - USER_IN_ROLLOUT, USER_MAPPED_TO_FORCED_VARIATION, - USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, - USER_NOT_BUCKETED_INTO_TARGETING_RULE, - USER_NOT_IN_EXPERIMENT, - USER_NOT_IN_ROLLOUT, + USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, VALID_BUCKETING_ID, VARIATION_REMOVED_FOR_USER, } from '../../log_messages'; - -export const MODULE_NAME = 'DECISION_SERVICE'; +import { OptimizelyError } from '../../error/optimizly_error'; + +export const EXPERIMENT_NOT_RUNNING = 'Experiment %s is not running.'; +export const RETURNING_STORED_VARIATION = + 'Returning previously activated variation "%s" of experiment "%s" for user "%s" from user profile.'; +export const USER_NOT_IN_EXPERIMENT = 'User %s does not meet conditions to be in experiment %s.'; +export const USER_HAS_NO_VARIATION = 'User %s is in no variation of experiment %s.'; +export const USER_HAS_VARIATION = 'User %s is in variation %s of experiment %s.'; +export const USER_FORCED_IN_VARIATION = 'User %s is forced in variation %s.'; +export const FORCED_BUCKETING_FAILED = 'Variation key %s is not in datafile. Not activating user %s.'; +export const EVALUATING_AUDIENCES_COMBINED = 'Evaluating audiences for %s "%s": %s.'; +export const AUDIENCE_EVALUATION_RESULT_COMBINED = 'Audiences for %s %s collectively evaluated to %s.'; +export const USER_IN_ROLLOUT = 'User %s is in rollout of feature %s.'; +export const USER_NOT_IN_ROLLOUT = 'User %s is not in rollout of feature %s.'; +export const FEATURE_HAS_NO_EXPERIMENTS = 'Feature %s is not attached to any experiments.'; +export const USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE = + 'User %s does not meet conditions for targeting rule %s.'; +export const USER_NOT_BUCKETED_INTO_TARGETING_RULE = +'User %s not bucketed into targeting rule %s due to traffic allocation. Trying everyone rule.'; +export const USER_BUCKETED_INTO_TARGETING_RULE = 'User %s bucketed into targeting rule %s.'; +export const NO_ROLLOUT_EXISTS = 'There is no rollout of feature %s.'; +export const INVALID_ROLLOUT_ID = 'Invalid rollout ID %s attached to feature %s'; +export const ROLLOUT_HAS_NO_EXPERIMENTS = 'Rollout of feature %s has no experiments'; +export const IMPROPERLY_FORMATTED_EXPERIMENT = 'Experiment key %s is improperly formatted.'; +export const USER_HAS_FORCED_VARIATION = + 'Variation %s is mapped to experiment %s and user %s in the forced variation map.'; +export const USER_MEETS_CONDITIONS_FOR_TARGETING_RULE = 'User %s meets conditions for targeting rule %s.'; export interface DecisionObj { experiment: Experiment | null; @@ -172,7 +172,7 @@ export class DecisionService { const experimentKey = experiment.key; if (!this.checkIfExperimentIsActive(configObj, experimentKey)) { this.logger?.info(EXPERIMENT_NOT_RUNNING, experimentKey); - decideReasons.push([EXPERIMENT_NOT_RUNNING, MODULE_NAME, experimentKey]); + decideReasons.push([EXPERIMENT_NOT_RUNNING, experimentKey]); return { result: null, reasons: decideReasons, @@ -211,7 +211,6 @@ export class DecisionService { ); decideReasons.push([ RETURNING_STORED_VARIATION, - MODULE_NAME, variation.key, experimentKey, userId, @@ -240,7 +239,6 @@ export class DecisionService { ); decideReasons.push([ USER_NOT_IN_EXPERIMENT, - MODULE_NAME, userId, experimentKey, ]); @@ -265,7 +263,6 @@ export class DecisionService { ); decideReasons.push([ USER_HAS_NO_VARIATION, - MODULE_NAME, userId, experimentKey, ]); @@ -283,7 +280,6 @@ export class DecisionService { ); decideReasons.push([ USER_HAS_VARIATION, - MODULE_NAME, userId, variation.key, experimentKey, @@ -381,7 +377,6 @@ export class DecisionService { ); decideReasons.push([ USER_FORCED_IN_VARIATION, - MODULE_NAME, userId, forcedVariationKey, ]); @@ -392,13 +387,11 @@ export class DecisionService { } else { this.logger?.error( FORCED_BUCKETING_FAILED, - MODULE_NAME, forcedVariationKey, userId, ); decideReasons.push([ FORCED_BUCKETING_FAILED, - MODULE_NAME, forcedVariationKey, userId, ]); @@ -444,7 +437,6 @@ export class DecisionService { ); decideReasons.push([ EVALUATING_AUDIENCES_COMBINED, - MODULE_NAME, evaluationAttribute, loggingKey || experiment.key, JSON.stringify(experimentAudienceConditions), @@ -458,7 +450,6 @@ export class DecisionService { ); decideReasons.push([ AUDIENCE_EVALUATION_RESULT_COMBINED, - MODULE_NAME, evaluationAttribute, loggingKey || experiment.key, result.toString().toUpperCase(), @@ -653,10 +644,10 @@ export class DecisionService { if (rolloutDecision.variation) { this.logger?.debug(USER_IN_ROLLOUT, userId, feature.key); - decideReasons.push([USER_IN_ROLLOUT, MODULE_NAME, userId, feature.key]); + decideReasons.push([USER_IN_ROLLOUT, userId, feature.key]); } else { this.logger?.debug(USER_NOT_IN_ROLLOUT, userId, feature.key); - decideReasons.push([USER_NOT_IN_ROLLOUT, MODULE_NAME, userId, feature.key]); + decideReasons.push([USER_NOT_IN_ROLLOUT, userId, feature.key]); } decisions.push({ @@ -741,7 +732,7 @@ export class DecisionService { } } else { this.logger?.debug(FEATURE_HAS_NO_EXPERIMENTS, feature.key); - decideReasons.push([FEATURE_HAS_NO_EXPERIMENTS, MODULE_NAME, feature.key]); + decideReasons.push([FEATURE_HAS_NO_EXPERIMENTS, feature.key]); } variationForFeatureExperiment = { @@ -765,7 +756,7 @@ export class DecisionService { let decisionObj: DecisionObj; if (!feature.rolloutId) { this.logger?.debug(NO_ROLLOUT_EXISTS, feature.key); - decideReasons.push([NO_ROLLOUT_EXISTS, MODULE_NAME, feature.key]); + decideReasons.push([NO_ROLLOUT_EXISTS, feature.key]); decisionObj = { experiment: null, variation: null, @@ -785,7 +776,7 @@ export class DecisionService { feature.rolloutId, feature.key, ); - decideReasons.push([INVALID_ROLLOUT_ID, MODULE_NAME, feature.rolloutId, feature.key]); + decideReasons.push([INVALID_ROLLOUT_ID, feature.rolloutId, feature.key]); decisionObj = { experiment: null, variation: null, @@ -803,7 +794,7 @@ export class DecisionService { ROLLOUT_HAS_NO_EXPERIMENTS, feature.rolloutId, ); - decideReasons.push([ROLLOUT_HAS_NO_EXPERIMENTS, MODULE_NAME, feature.rolloutId]); + decideReasons.push([ROLLOUT_HAS_NO_EXPERIMENTS, feature.rolloutId]); decisionObj = { experiment: null, variation: null, @@ -975,7 +966,7 @@ export class DecisionService { */ removeForcedVariation(userId: string, experimentId: string, experimentKey: string): void { if (!userId) { - throw new Error(sprintf(INVALID_USER_ID, MODULE_NAME)); + throw new OptimizelyError(INVALID_USER_ID); } if (this.forcedVariationMap.hasOwnProperty(userId)) { @@ -986,7 +977,7 @@ export class DecisionService { userId, ); } else { - throw new Error(sprintf(USER_NOT_IN_FORCED_VARIATION, MODULE_NAME, userId)); + throw new OptimizelyError(USER_NOT_IN_FORCED_VARIATION, userId); } } @@ -1049,12 +1040,10 @@ export class DecisionService { // catching improperly formatted experiments this.logger?.error( IMPROPERLY_FORMATTED_EXPERIMENT, - MODULE_NAME, experimentKey, ); decideReasons.push([ IMPROPERLY_FORMATTED_EXPERIMENT, - MODULE_NAME, experimentKey, ]); @@ -1078,7 +1067,6 @@ export class DecisionService { if (!variationId) { this.logger?.debug( USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, - MODULE_NAME, experimentKey, userId, ); @@ -1098,7 +1086,6 @@ export class DecisionService { ); decideReasons.push([ USER_HAS_FORCED_VARIATION, - MODULE_NAME, variationKey, experimentKey, userId, @@ -1266,7 +1253,6 @@ export class DecisionService { ); decideReasons.push([ USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, - MODULE_NAME, userId, loggingKey ]); @@ -1286,7 +1272,6 @@ export class DecisionService { ); decideReasons.push([ USER_BUCKETED_INTO_TARGETING_RULE, - MODULE_NAME, userId, loggingKey]); } else if (!everyoneElse) { @@ -1298,7 +1283,6 @@ export class DecisionService { ); decideReasons.push([ USER_NOT_BUCKETED_INTO_TARGETING_RULE, - MODULE_NAME, userId, loggingKey ]); @@ -1314,7 +1298,6 @@ export class DecisionService { ); decideReasons.push([ USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, - MODULE_NAME, userId, loggingKey ]); diff --git a/lib/error_messages.ts b/lib/error_messages.ts index 75f869c2e..7ef14178d 100644 --- a/lib/error_messages.ts +++ b/lib/error_messages.ts @@ -18,33 +18,31 @@ export const BROWSER_ODP_MANAGER_INITIALIZATION_FAILED = '%s: Error initializing export const CONDITION_EVALUATOR_ERROR = 'Error evaluating audience condition of type %s: %s'; export const DATAFILE_AND_SDK_KEY_MISSING = '%s: You must provide at least one of sdkKey or datafile. Cannot start Optimizely'; -export const EXPERIMENT_KEY_NOT_IN_DATAFILE = '%s: Experiment key %s is not in datafile.'; +export const EXPERIMENT_KEY_NOT_IN_DATAFILE = 'Experiment key %s is not in datafile.'; export const FEATURE_NOT_IN_DATAFILE = 'Feature key %s is not in datafile.'; export const FETCH_SEGMENTS_FAILED_NETWORK_ERROR = '%s: Audience segments fetch failed. (network error)'; export const FETCH_SEGMENTS_FAILED_DECODE_ERROR = '%s: Audience segments fetch failed. (decode error)'; -export const IMPROPERLY_FORMATTED_EXPERIMENT = 'Experiment key %s is improperly formatted.'; -export const INVALID_ATTRIBUTES = '%s: Provided attributes are in an invalid format.'; -export const INVALID_BUCKETING_ID = '%s: Unable to generate hash for bucketing ID %s: %s'; -export const INVALID_DATAFILE = '%s: Datafile is invalid - property %s: %s'; -export const INVALID_DATAFILE_MALFORMED = '%s: Datafile is invalid because it is malformed.'; -export const INVALID_CONFIG = '%s: Provided Optimizely config is in an invalid format.'; -export const INVALID_JSON = '%s: JSON object is not valid.'; -export const INVALID_ERROR_HANDLER = '%s: Provided "errorHandler" is in an invalid format.'; -export const INVALID_EVENT_DISPATCHER = '%s: Provided "eventDispatcher" is in an invalid format.'; -export const INVALID_EVENT_TAGS = '%s: Provided event tags are in an invalid format.'; +export const INVALID_ATTRIBUTES = 'Provided attributes are in an invalid format.'; +export const INVALID_BUCKETING_ID = 'Unable to generate hash for bucketing ID %s: %s'; +export const INVALID_DATAFILE = 'Datafile is invalid - property %s: %s'; +export const INVALID_DATAFILE_MALFORMED = 'Datafile is invalid because it is malformed.'; +export const INVALID_CONFIG = 'Provided Optimizely config is in an invalid format.'; +export const INVALID_JSON = 'JSON object is not valid.'; +export const INVALID_ERROR_HANDLER = 'Provided "errorHandler" is in an invalid format.'; +export const INVALID_EVENT_DISPATCHER = 'Provided "eventDispatcher" is in an invalid format.'; +export const INVALID_EVENT_TAGS = 'Provided event tags are in an invalid format.'; export const INVALID_EXPERIMENT_KEY = 'Experiment key %s is not in datafile. It is either invalid, paused, or archived.'; export const INVALID_EXPERIMENT_ID = 'Experiment ID %s is not in datafile.'; -export const INVALID_GROUP_ID = '%s: Group ID %s is not in datafile.'; -export const INVALID_LOGGER = '%s: Provided "logger" is in an invalid format.'; -export const INVALID_ROLLOUT_ID = 'Invalid rollout ID %s attached to feature %s'; -export const INVALID_USER_ID = '%s: Provided user ID is in an invalid format.'; -export const INVALID_USER_PROFILE_SERVICE = '%s: Provided user profile service instance is in an invalid format: %s.'; +export const INVALID_GROUP_ID = 'Group ID %s is not in datafile.'; +export const INVALID_LOGGER = 'Provided "logger" is in an invalid format.'; +export const INVALID_USER_ID = 'Provided user ID is in an invalid format.'; +export const INVALID_USER_PROFILE_SERVICE = 'Provided user profile service instance is in an invalid format: %s.'; export const LOCAL_STORAGE_DOES_NOT_EXIST = 'Error accessing window localStorage.'; export const MISSING_INTEGRATION_KEY = - '%s: Integration key missing from datafile. All integrations should include a key.'; -export const NO_DATAFILE_SPECIFIED = '%s: No datafile specified. Cannot start optimizely.'; -export const NO_JSON_PROVIDED = '%s: No JSON object to validate against schema.'; + 'Integration key missing from datafile. All integrations should include a key.'; +export const NO_DATAFILE_SPECIFIED = 'No datafile specified. Cannot start optimizely.'; +export const NO_JSON_PROVIDED = 'No JSON object to validate against schema.'; export const NO_EVENT_PROCESSOR = 'No event processor is provided'; export const NO_VARIATION_FOR_EXPERIMENT_KEY = 'No variation key %s defined in datafile for experiment %s.'; export const ODP_CONFIG_NOT_AVAILABLE = '%s: ODP is not integrated to the project.'; @@ -79,21 +77,21 @@ export const ODP_VUID_INITIALIZATION_FAILED = '%s: ODP VUID initialization faile export const ODP_VUID_REGISTRATION_FAILED = '%s: ODP VUID failed to be registered.'; export const ODP_VUID_REGISTRATION_FAILED_EVENT_MANAGER_MISSING = '%s: ODP register vuid failed. (Event Manager not instantiated).'; -export const UNDEFINED_ATTRIBUTE = '%s: Provided attribute: %s has an undefined value.'; +export const UNDEFINED_ATTRIBUTE = 'Provided attribute: %s has an undefined value.'; export const UNRECOGNIZED_ATTRIBUTE = 'Unrecognized attribute %s provided. Pruning before sending event to Optimizely.'; export const UNABLE_TO_CAST_VALUE = 'Unable to cast value %s to type %s, returning null.'; export const USER_NOT_IN_FORCED_VARIATION = - '%s: User %s is not in the forced variation map. Cannot remove their forced variation.'; + 'User %s is not in the forced variation map. Cannot remove their forced variation.'; export const USER_PROFILE_LOOKUP_ERROR = 'Error while looking up user profile for user ID "%s": %s.'; export const USER_PROFILE_SAVE_ERROR = 'Error while saving user profile for user ID "%s": %s.'; export const VARIABLE_KEY_NOT_IN_DATAFILE = '%s: Variable with key "%s" associated with feature with key "%s" is not in datafile.'; export const VARIATION_ID_NOT_IN_DATAFILE = '%s: No variation ID %s defined in datafile for experiment %s.'; export const VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT = 'Variation ID %s is not in the datafile.'; -export const INVALID_INPUT_FORMAT = '%s: Provided %s is in an invalid format.'; +export const INVALID_INPUT_FORMAT = 'Provided %s is in an invalid format.'; export const INVALID_DATAFILE_VERSION = - '%s: This version of the JavaScript SDK does not support the given datafile version: %s'; + 'This version of the JavaScript SDK does not support the given datafile version: %s'; export const INVALID_VARIATION_KEY = 'Provided variation key is in an invalid format.'; export const UNABLE_TO_GET_VUID = 'Unable to get VUID - ODP Manager is not instantiated yet.'; export const ERROR_FETCHING_DATAFILE = 'Error fetching datafile: %s'; @@ -114,7 +112,6 @@ export const VARIABLE_REQUESTED_WITH_WRONG_TYPE = 'Requested variable type "%s", but variable is of type "%s". Use correct API to retrieve value. Returning None.'; export const UNEXPECTED_RESERVED_ATTRIBUTE_PREFIX = 'Attribute %s unexpectedly has reserved prefix %s; using attribute ID instead of reserved attribute name.'; -export const FORCED_BUCKETING_FAILED = 'Variation key %s is not in datafile. Not activating user %s.'; export const BUCKETING_ID_NOT_STRING = 'BucketingID attribute is not a string. Defaulted to userId'; export const UNEXPECTED_CONDITION_VALUE = 'Audience condition %s evaluated to UNKNOWN because the condition value is not supported.'; @@ -126,5 +123,25 @@ export const REQUEST_TIMEOUT = 'Request timeout'; export const REQUEST_ERROR = 'Request error'; export const NO_STATUS_CODE_IN_RESPONSE = 'No status code in response'; export const UNSUPPORTED_PROTOCOL = 'Unsupported protocol: %s'; +export const ONREADY_TIMEOUT = 'onReady timeout expired after %s ms'; +export const INSTANCE_CLOSED = 'Instance closed'; +export const DATAFILE_MANAGER_STOPPED = 'Datafile manager stopped before it could be started'; +export const FAILED_TO_FETCH_DATAFILE = 'Failed to fetch datafile'; +export const NO_SDKKEY_OR_DATAFILE = 'At least one of sdkKey or datafile must be provided'; +export const RETRY_CANCELLED = 'Retry cancelled'; +export const SERVICE_STOPPED_BEFORE_IT_WAS_STARTED = 'Service stopped before it was started'; +export const ONLY_POST_REQUESTS_ARE_SUPPORTED = 'Only POST requests are supported'; +export const SEND_BEACON_FAILED = 'sendBeacon failed'; +export const FAILED_TO_DISPATCH_EVENTS = 'Failed to dispatch events' +export const FAILED_TO_DISPATCH_EVENTS_WITH_ARG = 'Failed to dispatch events: %s'; +export const EVENT_PROCESSOR_STOPPED = 'Event processor stopped before it could be started'; +export const CANNOT_START_WITHOUT_ODP_CONFIG = 'cannot start without ODP config'; +export const START_CALLED_WHEN_ODP_IS_NOT_INTEGRATED = 'start() called when ODP is not integrated'; +export const ODP_ACTION_IS_NOT_VALID = 'ODP action is not valid (cannot be empty).'; +export const ODP_MANAGER_STOPPED_BEFORE_RUNNING = 'odp manager stopped before running'; +export const ODP_EVENT_MANAGER_STOPPED = "ODP event manager stopped before it could start"; +export const ONREADY_TIMEOUT_EXPIRED = 'onReady timeout expired after %s ms'; +export const DATAFILE_MANAGER_FAILED_TO_START = 'Datafile manager failed to start'; + export const messages: string[] = []; diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts index a6eee569c..dae605d88 100644 --- a/lib/event_processor/batch_event_processor.ts +++ b/lib/event_processor/batch_event_processor.ts @@ -27,8 +27,8 @@ import { isSuccessStatusCode } from "../utils/http_request_handler/http_util"; import { EventEmitter } from "../utils/event_emitter/event_emitter"; import { IdGenerator } from "../utils/id_generator"; import { areEventContextsEqual } from "./event_builder/user_event"; -import { EVENT_PROCESSOR_STOPPED, FAILED_TO_DISPATCH_EVENTS, FAILED_TO_DISPATCH_EVENTS_WITH_ARG } from "../exception_messages"; -import { sprintf } from "../utils/fns"; +import { EVENT_PROCESSOR_STOPPED, FAILED_TO_DISPATCH_EVENTS, FAILED_TO_DISPATCH_EVENTS_WITH_ARG } from "../error_messages"; +import { OptimizelyError } from "../error/optimizly_error"; export const DEFAULT_MIN_BACKOFF = 1000; export const DEFAULT_MAX_BACKOFF = 32000; @@ -165,7 +165,7 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { const dispatcher = closing && this.closingEventDispatcher ? this.closingEventDispatcher : this.eventDispatcher; return dispatcher.dispatchEvent(request).then((res) => { if (res.statusCode && !isSuccessStatusCode(res.statusCode)) { - return Promise.reject(new Error(sprintf(FAILED_TO_DISPATCH_EVENTS_WITH_ARG, res.statusCode))); + return Promise.reject(new OptimizelyError(FAILED_TO_DISPATCH_EVENTS_WITH_ARG, res.statusCode)); } return Promise.resolve(res); }); @@ -276,7 +276,7 @@ export class BatchEventProcessor extends BaseService implements EventProcessor { } if (this.isNew()) { - this.startPromise.reject(new Error(EVENT_PROCESSOR_STOPPED)); + this.startPromise.reject(new OptimizelyError(EVENT_PROCESSOR_STOPPED)); } this.state = ServiceState.Stopping; diff --git a/lib/event_processor/event_dispatcher/default_dispatcher.ts b/lib/event_processor/event_dispatcher/default_dispatcher.ts index 21c42bc5e..a812541cd 100644 --- a/lib/event_processor/event_dispatcher/default_dispatcher.ts +++ b/lib/event_processor/event_dispatcher/default_dispatcher.ts @@ -13,7 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ONLY_POST_REQUESTS_ARE_SUPPORTED } from '../../exception_messages'; +import { OptimizelyError } from '../../error/optimizly_error'; +import { ONLY_POST_REQUESTS_ARE_SUPPORTED } from '../../error_messages'; import { RequestHandler } from '../../utils/http_request_handler/http'; import { EventDispatcher, EventDispatcherResponse, LogEvent } from './event_dispatcher'; @@ -29,7 +30,7 @@ export class DefaultEventDispatcher implements EventDispatcher { ): Promise { // Non-POST requests not supported if (eventObj.httpVerb !== 'POST') { - return Promise.reject(new Error(ONLY_POST_REQUESTS_ARE_SUPPORTED)); + return Promise.reject(new OptimizelyError(ONLY_POST_REQUESTS_ARE_SUPPORTED)); } const dataString = JSON.stringify(eventObj.params); diff --git a/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts b/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts index 605bae2ef..d3130342a 100644 --- a/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts +++ b/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts @@ -14,7 +14,8 @@ * limitations under the License. */ -import { SEND_BEACON_FAILED } from '../../exception_messages'; +import { OptimizelyError } from '../../error/optimizly_error'; +import { SEND_BEACON_FAILED } from '../../error_messages'; import { EventDispatcher, EventDispatcherResponse } from './event_dispatcher'; export type Event = { @@ -42,7 +43,7 @@ export const dispatchEvent = function( if(success) { return Promise.resolve({}); } - return Promise.reject(new Error(SEND_BEACON_FAILED)); + return Promise.reject(new OptimizelyError(SEND_BEACON_FAILED)); } const eventDispatcher : EventDispatcher = { diff --git a/lib/event_processor/forwarding_event_processor.ts b/lib/event_processor/forwarding_event_processor.ts index dbbe7076c..8ac6f6631 100644 --- a/lib/event_processor/forwarding_event_processor.ts +++ b/lib/event_processor/forwarding_event_processor.ts @@ -23,7 +23,8 @@ import { buildLogEvent } from './event_builder/log_event'; import { BaseService, ServiceState } from '../service'; import { EventEmitter } from '../utils/event_emitter/event_emitter'; import { Consumer, Fn } from '../utils/type'; -import { SERVICE_STOPPED_BEFORE_IT_WAS_STARTED } from '../exception_messages'; +import { SERVICE_STOPPED_BEFORE_IT_WAS_STARTED } from '../error_messages'; +import { OptimizelyError } from '../error/optimizly_error'; class ForwardingEventProcessor extends BaseService implements EventProcessor { private dispatcher: EventDispatcher; private eventEmitter: EventEmitter<{ dispatch: LogEvent }>; @@ -55,7 +56,7 @@ class ForwardingEventProcessor extends BaseService implements EventProcessor { } if (this.isNew()) { - this.startPromise.reject(new Error(SERVICE_STOPPED_BEFORE_IT_WAS_STARTED)); + this.startPromise.reject(new OptimizelyError(SERVICE_STOPPED_BEFORE_IT_WAS_STARTED)); } this.state = ServiceState.Terminated; diff --git a/lib/exception_messages.ts b/lib/exception_messages.ts deleted file mode 100644 index aa743b905..000000000 --- a/lib/exception_messages.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright 2024, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export const FAILED_TO_DISPATCH_EVENTS = 'Failed to dispatch events' -export const FAILED_TO_DISPATCH_EVENTS_WITH_ARG = 'Failed to dispatch events: %s'; -export const EVENT_PROCESSOR_STOPPED = 'Event processor stopped before it could be started'; -export const SERVICE_STOPPED_BEFORE_IT_WAS_STARTED = 'Service stopped before it was started'; -export const ONLY_POST_REQUESTS_ARE_SUPPORTED = 'Only POST requests are supported'; -export const SEND_BEACON_FAILED = 'sendBeacon failed'; -export const CANNOT_START_WITHOUT_ODP_CONFIG = 'cannot start without ODP config'; -export const START_CALLED_WHEN_ODP_IS_NOT_INTEGRATED = 'start() called when ODP is not integrated'; -export const ODP_ACTION_IS_NOT_VALID = 'ODP action is not valid (cannot be empty).'; -export const ODP_MANAGER_STOPPED_BEFORE_RUNNING = 'odp manager stopped before running'; -export const ODP_EVENT_MANAGER_STOPPED = "ODP event manager stopped before it could start"; -export const ONREADY_TIMEOUT_EXPIRED = 'onReady timeout expired after %s ms'; -export const INSTANCE_CLOSED = 'Instance closed'; -export const DATAFILE_MANAGER_STOPPED = 'Datafile manager stopped before it could be started'; -export const DATAFILE_MANAGER_FAILED_TO_START = 'Datafile manager failed to start'; -export const FAILED_TO_FETCH_DATAFILE = 'Failed to fetch datafile'; -export const FAILED_TO_STOP = 'Failed to stop'; -export const YOU_MUST_PROVIDE_AT_LEAST_ONE_OF_SDKKEY_OR_DATAFILE = 'You must provide at least one of sdkKey or datafile'; -export const RETRY_CANCELLED = 'Retry cancelled'; -export const REQUEST_FAILED = 'Request failed'; -export const PROMISE_SHOULD_NOT_HAVE_RESOLVED = 'Promise should not have resolved'; -export const VUID_IS_NOT_SUPPORTED_IN_NODEJS= 'VUID is not supported in Node.js environment'; diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js index cc6c34cd0..5fb84a30f 100644 --- a/lib/index.browser.tests.js +++ b/lib/index.browser.tests.js @@ -22,8 +22,6 @@ import optimizelyFactory from './index.browser'; import configValidator from './utils/config_validator'; import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; import { createProjectConfig } from './project_config/project_config'; -import { INVALID_CONFIG_OR_SOMETHING } from './exception_messages'; - class MockLocalStorage { store = {}; @@ -134,7 +132,7 @@ describe('javascript-sdk (Browser)', function() { // }); it('should not throw if the provided config is not valid', function() { - configValidator.validate.throws(new Error(INVALID_CONFIG_OR_SOMETHING)); + configValidator.validate.throws(new Error('INVALID_CONFIG_OR_SOMETHING')); assert.doesNotThrow(function() { var optlyInstance = optimizelyFactory.createInstance({ projectConfigManager: getMockProjectConfigManager(), diff --git a/lib/index.browser.ts b/lib/index.browser.ts index 054c584d8..c25971393 100644 --- a/lib/index.browser.ts +++ b/lib/index.browser.ts @@ -19,7 +19,6 @@ import defaultErrorHandler from './plugins/error_handler'; import defaultEventDispatcher from './event_processor/event_dispatcher/default_dispatcher.browser'; import sendBeaconEventDispatcher from './event_processor/event_dispatcher/send_beacon_dispatcher.browser'; import * as enums from './utils/enums'; -import { createNotificationCenter } from './notification_center'; import { OptimizelyDecideOption, Client, Config, OptimizelyOptions } from './shared_types'; import Optimizely from './optimizely'; import { UserAgentParser } from './odp/ua_parser/user_agent_parser'; @@ -37,7 +36,6 @@ import { LoggerFacade } from './logging/logger'; import { Maybe } from './utils/type'; -const MODULE_NAME = 'INDEX_BROWSER'; const DEFAULT_EVENT_BATCH_SIZE = 10; const DEFAULT_EVENT_FLUSH_INTERVAL = 1000; // Unit is ms, default is 1s const DEFAULT_EVENT_MAX_QUEUE_SIZE = 10000; diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js index 6d2bba594..f35903418 100644 --- a/lib/index.node.tests.js +++ b/lib/index.node.tests.js @@ -21,7 +21,6 @@ import testData from './tests/test_data'; import optimizelyFactory from './index.node'; import configValidator from './utils/config_validator'; import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; -import { INVALID_CONFIG_OR_SOMETHING } from './exception_messages'; var createLogger = () => ({ debug: () => {}, @@ -70,7 +69,7 @@ describe('optimizelyFactory', function() { // }); it('should not throw if the provided config is not valid', function() { - configValidator.validate.throws(new Error(INVALID_CONFIG_OR_SOMETHING)); + configValidator.validate.throws(new Error('INVALID_CONFIG_OR_SOMETHING')); assert.doesNotThrow(function() { var optlyInstance = optimizelyFactory.createInstance({ projectConfigManager: getMockProjectConfigManager(), diff --git a/lib/log_messages.ts b/lib/log_messages.ts index d5830cba7..6123adc74 100644 --- a/lib/log_messages.ts +++ b/lib/log_messages.ts @@ -18,16 +18,13 @@ export const ACTIVATE_USER = '%s: Activating user %s in experiment %s.'; export const DISPATCH_CONVERSION_EVENT = '%s: Dispatching conversion event to URL %s with params %s.'; export const DISPATCH_IMPRESSION_EVENT = '%s: Dispatching impression event to URL %s with params %s.'; export const DEPRECATED_EVENT_VALUE = '%s: Event value is deprecated in %s call.'; -export const EXPERIMENT_NOT_RUNNING = 'Experiment %s is not running.'; export const FEATURE_ENABLED_FOR_USER = 'Feature %s is enabled for user %s.'; export const FEATURE_NOT_ENABLED_FOR_USER = 'Feature %s is not enabled for user %s.'; -export const FEATURE_HAS_NO_EXPERIMENTS = 'Feature %s is not attached to any experiments.'; export const FAILED_TO_PARSE_VALUE = '%s: Failed to parse event value "%s" from event tags.'; export const FAILED_TO_PARSE_REVENUE = 'Failed to parse revenue value "%s" from event tags.'; export const INVALID_CLIENT_ENGINE = 'Invalid client engine passed: %s. Defaulting to node-sdk.'; export const INVALID_DEFAULT_DECIDE_OPTIONS = '%s: Provided default decide options is not an array.'; export const INVALID_DECIDE_OPTIONS = 'Provided decide options is not an array. Using default decide options.'; -export const NO_ROLLOUT_EXISTS = 'There is no rollout of feature %s.'; export const NOT_ACTIVATING_USER = 'Not activating user %s for experiment %s.'; export const ODP_DISABLED = 'ODP Disabled.'; export const ODP_IDENTIFY_FAILED_ODP_DISABLED = '%s: ODP identify event for user %s is not dispatched (ODP disabled).'; @@ -37,9 +34,6 @@ export const ODP_SEND_EVENT_IDENTIFIER_CONVERSION_FAILED = '%s: sendOdpEvent failed to parse through and convert fs_user_id aliases'; export const PARSED_REVENUE_VALUE = 'Parsed revenue value "%s" from event tags.'; export const PARSED_NUMERIC_VALUE = 'Parsed event value "%s" from event tags.'; -export const RETURNING_STORED_VARIATION = - 'Returning previously activated variation "%s" of experiment "%s" for user "%s" from user profile.'; -export const ROLLOUT_HAS_NO_EXPERIMENTS = 'Rollout of feature %s has no experiments'; export const SAVED_USER_VARIATION = 'Saved user profile for user "%s".'; export const UPDATED_USER_VARIATION = '%s: Updated variation "%s" of experiment "%s" for user "%s".'; export const SAVED_VARIATION_NOT_FOUND = @@ -47,21 +41,12 @@ export const SAVED_VARIATION_NOT_FOUND = export const SHOULD_NOT_DISPATCH_ACTIVATE = 'Experiment %s is not in "Running" state. Not activating user.'; export const SKIPPING_JSON_VALIDATION = 'Skipping JSON schema validation.'; export const TRACK_EVENT = 'Tracking event %s for user %s.'; -export const USER_BUCKETED_INTO_TARGETING_RULE = 'User %s bucketed into targeting rule %s.'; export const USER_IN_FEATURE_EXPERIMENT = '%s: User %s is in variation %s of experiment %s on the feature %s.'; -export const USER_IN_ROLLOUT = 'User %s is in rollout of feature %s.'; export const USER_NOT_BUCKETED_INTO_EVERYONE_TARGETING_RULE = '%s: User %s not bucketed into everyone targeting rule due to traffic allocation.'; export const USER_NOT_BUCKETED_INTO_ANY_EXPERIMENT_IN_GROUP = '%s: User %s is not in any experiment of group %s.'; -export const USER_NOT_BUCKETED_INTO_TARGETING_RULE = - 'User %s not bucketed into targeting rule %s due to traffic allocation. Trying everyone rule.'; -export const USER_FORCED_IN_VARIATION = 'User %s is forced in variation %s.'; export const USER_MAPPED_TO_FORCED_VARIATION = 'Set variation %s for experiment %s and user %s in the forced variation map.'; -export const USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE = - 'User %s does not meet conditions for targeting rule %s.'; -export const USER_MEETS_CONDITIONS_FOR_TARGETING_RULE = 'User %s meets conditions for targeting rule %s.'; -export const USER_HAS_VARIATION = 'User %s is in variation %s of experiment %s.'; export const USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED = 'Variation (%s) is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.'; export const USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED = @@ -70,14 +55,7 @@ export const USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID = 'Invalid variation is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.'; export const USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID = 'Invalid variation is mapped to flag (%s) and user (%s) in the forced decision map.'; -export const USER_HAS_FORCED_VARIATION = - 'Variation %s is mapped to experiment %s and user %s in the forced variation map.'; -export const USER_HAS_NO_VARIATION = 'User %s is in no variation of experiment %s.'; export const USER_HAS_NO_FORCED_VARIATION = 'User %s is not in the forced variation map.'; -export const USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT = - 'No experiment %s mapped to user %s in the forced variation map.'; -export const USER_NOT_IN_EXPERIMENT = 'User %s does not meet conditions to be in experiment %s.'; -export const USER_NOT_IN_ROLLOUT = 'User %s is not in rollout of feature %s.'; export const USER_RECEIVED_DEFAULT_VARIABLE_VALUE = 'User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".'; export const FEATURE_NOT_ENABLED_RETURN_DEFAULT_VARIABLE_VALUE = @@ -91,9 +69,7 @@ export const VARIATION_REMOVED_FOR_USER = 'Variation mapped to experiment %s has export const VALID_BUCKETING_ID = 'BucketingId is valid: "%s"'; export const EVALUATING_AUDIENCE = 'Starting to evaluate audience "%s" with conditions: %s.'; -export const EVALUATING_AUDIENCES_COMBINED = 'Evaluating audiences for %s "%s": %s.'; export const AUDIENCE_EVALUATION_RESULT = 'Audience "%s" evaluated to %s.'; -export const AUDIENCE_EVALUATION_RESULT_COMBINED = 'Audiences for %s %s collectively evaluated to %s.'; export const MISSING_ATTRIBUTE_VALUE = 'Audience condition %s evaluated to UNKNOWN because no value was passed for user attribute "%s".'; export const UNEXPECTED_TYPE_NULL = @@ -104,6 +80,8 @@ export const UNABLE_TO_PARSE_AND_SKIPPED_HEADER = 'Unable to parse & skipped hea export const ADDING_AUTHORIZATION_HEADER_WITH_BEARER_TOKEN = 'Adding Authorization header with Bearer Token'; export const MAKING_DATAFILE_REQ_TO_URL_WITH_HEADERS = 'Making datafile request to url %s with headers: %s'; export const RESPONSE_STATUS_CODE = 'Response status code: %s'; -export const SAVED_LAST_MODIFIED_HEADER_VALUE_FROM_RESPONSE = 'Saved last modified header value from response: %s'; +export const SAVED_LAST_MODIFIED_HEADER_VALUE_FROM_RESPONSE = 'Saved last modified header value from response: %s'; +export const USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT = + 'No experiment %s mapped to user %s in the forced variation map.'; export const messages: string[] = []; diff --git a/lib/notification_center/index.ts b/lib/notification_center/index.ts index 15886fde3..2db4d36d7 100644 --- a/lib/notification_center/index.ts +++ b/lib/notification_center/index.ts @@ -14,13 +14,8 @@ * limitations under the License. */ import { LoggerFacade } from '../logging/logger'; -import { ErrorHandler } from '../error/error_handler'; import { objectValues } from '../utils/fns'; -import { - LOG_LEVEL, -} from '../utils/enums'; - import { NOTIFICATION_TYPES } from './type'; import { NotificationType, NotificationPayload } from './type'; import { Consumer, Fn } from '../utils/type'; @@ -29,8 +24,6 @@ import { NOTIFICATION_LISTENER_EXCEPTION } from '../error_messages'; import { ErrorReporter } from '../error/error_reporter'; import { ErrorNotifier } from '../error/error_notifier'; -const MODULE_NAME = 'NOTIFICATION_CENTER'; - interface NotificationCenterOptions { logger?: LoggerFacade; errorNotifier?: ErrorNotifier; diff --git a/lib/odp/event_manager/odp_event_api_manager.spec.ts b/lib/odp/event_manager/odp_event_api_manager.spec.ts index 55ec009e1..316787821 100644 --- a/lib/odp/event_manager/odp_event_api_manager.spec.ts +++ b/lib/odp/event_manager/odp_event_api_manager.spec.ts @@ -41,7 +41,6 @@ const PIXEL_URL = 'https://odp.pixel.com'; const odpConfig = new OdpConfig(API_KEY, API_HOST, PIXEL_URL, []); import { getMockRequestHandler } from '../../tests/mock/mock_request_handler'; -import { REQUEST_FAILED } from '../../exception_messages'; describe('DefaultOdpEventApiManager', () => { it('should generate the event request using the correct odp config and event', async () => { @@ -101,7 +100,7 @@ describe('DefaultOdpEventApiManager', () => { it('should return a promise that fails if the requestHandler response promise fails', async () => { const mockRequestHandler = getMockRequestHandler(); mockRequestHandler.makeRequest.mockReturnValue({ - responsePromise: Promise.reject(new Error(REQUEST_FAILED)), + responsePromise: Promise.reject(new Error('REQUEST_FAILED')), }); const requestGenerator = vi.fn().mockReturnValue({ method: 'PATCH', @@ -115,7 +114,7 @@ describe('DefaultOdpEventApiManager', () => { const manager = new DefaultOdpEventApiManager(mockRequestHandler, requestGenerator); const response = manager.sendEvents(odpConfig, ODP_EVENTS); - await expect(response).rejects.toThrow('Request failed'); + await expect(response).rejects.toThrow(); }); it('should return a promise that resolves with correct response code from the requestHandler', async () => { diff --git a/lib/odp/event_manager/odp_event_manager.spec.ts b/lib/odp/event_manager/odp_event_manager.spec.ts index ff7efa5cb..9d16273b9 100644 --- a/lib/odp/event_manager/odp_event_manager.spec.ts +++ b/lib/odp/event_manager/odp_event_manager.spec.ts @@ -23,7 +23,6 @@ import { OdpEvent } from './odp_event'; import { OdpConfig } from '../odp_config'; import { EventDispatchResponse } from './odp_event_api_manager'; import { advanceTimersByTime } from '../../tests/testUtils'; -import { FAILED_TO_DISPATCH_EVENTS } from '../../exception_messages'; const API_KEY = 'test-api-key'; const API_HOST = 'https://odp.example.com'; @@ -639,7 +638,7 @@ describe('DefaultOdpEventManager', () => { const repeater = getMockRepeater(); const apiManager = getMockApiManager(); - apiManager.sendEvents.mockReturnValue(Promise.reject(new Error(FAILED_TO_DISPATCH_EVENTS))); + apiManager.sendEvents.mockReturnValue(Promise.reject(new Error('FAILED_TO_DISPATCH_EVENTS'))); const backoffController = { backoff: vi.fn().mockReturnValue(666), @@ -741,7 +740,7 @@ describe('DefaultOdpEventManager', () => { const repeater = getMockRepeater(); const apiManager = getMockApiManager(); - apiManager.sendEvents.mockReturnValue(Promise.reject(new Error(FAILED_TO_DISPATCH_EVENTS))); + apiManager.sendEvents.mockReturnValue(Promise.reject(new Error('FAILED_TO_DISPATCH_EVENTS'))); const backoffController = { backoff: vi.fn().mockReturnValue(666), diff --git a/lib/odp/event_manager/odp_event_manager.ts b/lib/odp/event_manager/odp_event_manager.ts index 76aec79be..9bf107874 100644 --- a/lib/odp/event_manager/odp_event_manager.ts +++ b/lib/odp/event_manager/odp_event_manager.ts @@ -30,9 +30,10 @@ import { ODP_EVENT_MANAGER_IS_NOT_RUNNING, ODP_EVENTS_SHOULD_HAVE_ATLEAST_ONE_KEY_VALUE, ODP_NOT_INTEGRATED, + FAILED_TO_DISPATCH_EVENTS_WITH_ARG, + ODP_EVENT_MANAGER_STOPPED } from '../../error_messages'; -import { sprintf } from '../../utils/fns'; -import { FAILED_TO_DISPATCH_EVENTS_WITH_ARG, ODP_EVENT_MANAGER_STOPPED } from '../../exception_messages'; +import { OptimizelyError } from '../../error/optimizly_error'; export interface OdpEventManager extends Service { updateConfig(odpIntegrationConfig: OdpIntegrationConfig): void; @@ -75,7 +76,7 @@ export class DefaultOdpEventManager extends BaseService implements OdpEventManag private async executeDispatch(odpConfig: OdpConfig, batch: OdpEvent[]): Promise { const res = await this.apiManager.sendEvents(odpConfig, batch); if (res.statusCode && !isSuccessStatusCode(res.statusCode)) { - return Promise.reject(new Error(sprintf(FAILED_TO_DISPATCH_EVENTS_WITH_ARG, res.statusCode))); + return Promise.reject(new OptimizelyError(FAILED_TO_DISPATCH_EVENTS_WITH_ARG, res.statusCode)); } return await Promise.resolve(res); } @@ -153,7 +154,7 @@ export class DefaultOdpEventManager extends BaseService implements OdpEventManag } if (this.isNew()) { - this.startPromise.reject(new Error(ODP_EVENT_MANAGER_STOPPED)); + this.startPromise.reject(new OptimizelyError(ODP_EVENT_MANAGER_STOPPED)); } this.flush(); diff --git a/lib/odp/odp_manager.spec.ts b/lib/odp/odp_manager.spec.ts index 8ffc2721d..26c6e82e0 100644 --- a/lib/odp/odp_manager.spec.ts +++ b/lib/odp/odp_manager.spec.ts @@ -25,7 +25,6 @@ import { ODP_USER_KEY } from './constant'; import { OptimizelySegmentOption } from './segment_manager/optimizely_segment_option'; import { OdpEventManager } from './event_manager/odp_event_manager'; import { CLIENT_VERSION, JAVASCRIPT_CLIENT_ENGINE } from '../utils/enums'; -import { FAILED_TO_STOP } from '../exception_messages'; const keyA = 'key-a'; const hostA = 'host-a'; @@ -694,7 +693,7 @@ describe('DefaultOdpManager', () => { await exhaustMicrotasks(); expect(odpManager.getState()).toEqual(ServiceState.Stopping); - eventManagerTerminatedPromise.reject(new Error(FAILED_TO_STOP)); + eventManagerTerminatedPromise.reject(new Error('FAILED_TO_STOP')); await expect(odpManager.onTerminated()).rejects.toThrow(); }); diff --git a/lib/odp/odp_manager.ts b/lib/odp/odp_manager.ts index 3a7b4a62a..7b36c0eb9 100644 --- a/lib/odp/odp_manager.ts +++ b/lib/odp/odp_manager.ts @@ -29,7 +29,8 @@ import { CLIENT_VERSION, JAVASCRIPT_CLIENT_ENGINE } from '../utils/enums'; import { ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION, ODP_USER_KEY } from './constant'; import { isVuid } from '../vuid/vuid'; import { Maybe } from '../utils/type'; -import { ODP_MANAGER_STOPPED_BEFORE_RUNNING } from '../exception_messages'; +import { ODP_MANAGER_STOPPED_BEFORE_RUNNING } from '../error_messages'; +import { OptimizelyError } from '../error/optimizly_error'; export interface OdpManager extends Service { updateConfig(odpIntegrationConfig: OdpIntegrationConfig): boolean; @@ -137,7 +138,7 @@ export class DefaultOdpManager extends BaseService implements OdpManager { } if (!this.isRunning()) { - this.startPromise.reject(new Error(ODP_MANAGER_STOPPED_BEFORE_RUNNING)); + this.startPromise.reject(new OptimizelyError(ODP_MANAGER_STOPPED_BEFORE_RUNNING)); } this.state = ServiceState.Stopping; diff --git a/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js index d1468bced..30d67cd72 100644 --- a/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -36,27 +36,14 @@ import { createProjectConfig } from '../project_config/project_config'; import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; import { DECISION_NOTIFICATION_TYPES } from '../notification_center/type'; import { - AUDIENCE_EVALUATION_RESULT_COMBINED, - EXPERIMENT_NOT_RUNNING, - FEATURE_HAS_NO_EXPERIMENTS, FEATURE_NOT_ENABLED_FOR_USER, INVALID_CLIENT_ENGINE, INVALID_DEFAULT_DECIDE_OPTIONS, INVALID_OBJECT, NOT_ACTIVATING_USER, - RETURNING_STORED_VARIATION, - USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, - USER_FORCED_IN_VARIATION, - USER_HAS_FORCED_VARIATION, USER_HAS_NO_FORCED_VARIATION, USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, - USER_HAS_NO_VARIATION, - USER_HAS_VARIATION, - USER_IN_ROLLOUT, USER_MAPPED_TO_FORCED_VARIATION, - USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, - USER_NOT_BUCKETED_INTO_TARGETING_RULE, - USER_NOT_IN_EXPERIMENT, USER_RECEIVED_DEFAULT_VARIABLE_VALUE, VALID_USER_PROFILE_SERVICE, VARIATION_REMOVED_FOR_USER, @@ -70,11 +57,28 @@ import { INVALID_INPUT_FORMAT, NO_VARIATION_FOR_EXPERIMENT_KEY, USER_NOT_IN_FORCED_VARIATION, - FORCED_BUCKETING_FAILED, + INSTANCE_CLOSED, + ONREADY_TIMEOUT_EXPIRED, } from '../error_messages'; -import { FAILED_TO_STOP, ONREADY_TIMEOUT_EXPIRED, PROMISE_SHOULD_NOT_HAVE_RESOLVED } from '../exception_messages'; + +import { + AUDIENCE_EVALUATION_RESULT_COMBINED, + USER_NOT_IN_EXPERIMENT, + FEATURE_HAS_NO_EXPERIMENTS, + USER_HAS_NO_VARIATION, + USER_HAS_VARIATION, + USER_NOT_BUCKETED_INTO_TARGETING_RULE, + USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, + USER_IN_ROLLOUT, + USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, + FORCED_BUCKETING_FAILED, + USER_HAS_FORCED_VARIATION, + USER_FORCED_IN_VARIATION, + RETURNING_STORED_VARIATION, + EXPERIMENT_NOT_RUNNING, +} from '../core/decision_service'; + import { USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP } from '../core/bucketer'; -import { error } from 'console'; var LOG_LEVEL = enums.LOG_LEVEL; var DECISION_SOURCES = enums.DECISION_SOURCES; @@ -5067,7 +5071,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(EXPERIMENT_NOT_RUNNING, 'DECISION_SERVICE', 'exp_with_audience') + sprintf(EXPERIMENT_NOT_RUNNING, 'exp_with_audience') ); }); @@ -5112,7 +5116,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstanceWithUserProfile.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(RETURNING_STORED_VARIATION, 'DECISION_SERVICE', variationKey2, experimentKey, userId) + sprintf(RETURNING_STORED_VARIATION, variationKey2, experimentKey, userId) ); }); @@ -5128,7 +5132,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_FORCED_IN_VARIATION, 'DECISION_SERVICE', userId, variationKey) + sprintf(USER_FORCED_IN_VARIATION, userId, variationKey) ); }); @@ -5146,7 +5150,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_HAS_FORCED_VARIATION, 'DECISION_SERVICE', variationKey, experimentKey, userId) + sprintf(USER_HAS_FORCED_VARIATION, variationKey, experimentKey, userId) ); }); @@ -5163,7 +5167,7 @@ describe('lib/optimizely', function() { var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(FORCED_BUCKETING_FAILED, 'DECISION_SERVICE', variationKey, userId) + sprintf(FORCED_BUCKETING_FAILED, variationKey, userId) ); }); @@ -5176,7 +5180,7 @@ describe('lib/optimizely', function() { user.setAttribute('country', 'US'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, 'DECISION_SERVICE', userId, '1') + sprintf(USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, userId, '1') ); }); @@ -5189,7 +5193,7 @@ describe('lib/optimizely', function() { user.setAttribute('country', 'CA'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, 'DECISION_SERVICE', userId, '1') + sprintf(USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, userId, '1') ); }); @@ -5202,7 +5206,7 @@ describe('lib/optimizely', function() { user.setAttribute('country', 'US'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_IN_ROLLOUT, 'DECISION_SERVICE', userId, flagKey) + sprintf(USER_IN_ROLLOUT, userId, flagKey) ); }); @@ -5215,7 +5219,7 @@ describe('lib/optimizely', function() { user.setAttribute('country', 'KO'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, 'DECISION_SERVICE', userId, 'Everyone Else') + sprintf(USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, userId, 'Everyone Else') ); }); @@ -5228,7 +5232,7 @@ describe('lib/optimizely', function() { user.setAttribute('browser', 'safari'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_NOT_BUCKETED_INTO_TARGETING_RULE, 'DECISION_SERVICE', userId, '2') + sprintf(USER_NOT_BUCKETED_INTO_TARGETING_RULE, userId, '2') ); }); @@ -5242,7 +5246,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_HAS_VARIATION, 'DECISION_SERVICE', userId, variationKey, experimentKey) + sprintf(USER_HAS_VARIATION, userId, variationKey, experimentKey) ); }); @@ -5260,7 +5264,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_HAS_NO_VARIATION, 'DECISION_SERVICE', userId, experimentKey) + sprintf(USER_HAS_NO_VARIATION, userId, experimentKey) ); }); @@ -5278,7 +5282,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, 'BUCKETER', userId, experimentKey, groupId) + sprintf(USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, userId, experimentKey, groupId) ); }); @@ -5293,7 +5297,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(FEATURE_HAS_NO_EXPERIMENTS, 'DECISION_SERVICE', flagKey) + sprintf(FEATURE_HAS_NO_EXPERIMENTS, flagKey) ); }); @@ -5306,7 +5310,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf(USER_NOT_IN_EXPERIMENT, 'DECISION_SERVICE', userId, experimentKey) + sprintf(USER_NOT_IN_EXPERIMENT, userId, experimentKey) ); }); @@ -5326,7 +5330,6 @@ describe('lib/optimizely', function() { expect(decision.reasons).to.include( sprintf( AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', 'experiment', experimentKey, 'FALSE' @@ -5351,7 +5354,6 @@ describe('lib/optimizely', function() { expect(decision.reasons).to.include( sprintf( AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', 'experiment', experimentKey, 'FALSE' @@ -5376,7 +5378,6 @@ describe('lib/optimizely', function() { expect(decision.reasons).to.include( sprintf( AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', 'experiment', experimentKey, 'FALSE' @@ -5401,7 +5402,6 @@ describe('lib/optimizely', function() { expect(decision.reasons).to.include( sprintf( AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', 'experiment', experimentKey, 'FALSE' @@ -5426,7 +5426,6 @@ describe('lib/optimizely', function() { expect(decision.reasons).to.include( sprintf( AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', 'experiment', experimentKey, 'FALSE' @@ -5451,7 +5450,6 @@ describe('lib/optimizely', function() { expect(decision.reasons).to.include( sprintf( AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', 'experiment', experimentKey, 'FALSE' @@ -5476,7 +5474,6 @@ describe('lib/optimizely', function() { expect(decision.reasons).to.include( sprintf( AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', 'experiment', experimentKey, 'FALSE' @@ -5500,7 +5497,6 @@ describe('lib/optimizely', function() { expect(decision.reasons).to.include( sprintf( AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', 'experiment', experimentKey, 'FALSE' @@ -9286,7 +9282,7 @@ describe('lib/optimizely', function() { describe('when the event processor onTerminated() method returns a promise that rejects', function() { beforeEach(function() { - eventProcessorStopPromise = Promise.reject(new Error(FAILED_TO_STOP)); + eventProcessorStopPromise = Promise.reject(new Error('FAILED_TO_STOP')); eventProcessorStopPromise.catch(() => {}); mockEventProcessor.onTerminated.returns(eventProcessorStopPromise); const mockConfigManager = getMockProjectConfigManager({ @@ -9317,10 +9313,11 @@ describe('lib/optimizely', function() { it('returns a promise that fulfills with an unsuccessful result object', function() { return optlyInstance.close().then(function(result) { - assert.deepEqual(result, { - success: false, - reason: 'Error: Failed to stop', - }); + // assert.deepEqual(result, { + // success: false, + // reason: 'Error: Failed to stop', + // }); + assert.isFalse(result.success); }); }); }); @@ -9467,9 +9464,10 @@ describe('lib/optimizely', function() { var readyPromise = optlyInstance.onReady({ timeout: 500 }); clock.tick(501); return readyPromise.then(() => { - return Promise.reject(new Error(PROMISE_SHOULD_NOT_HAVE_RESOLVED)); + return Promise.reject(new Error('PROMISE_SHOULD_NOT_HAVE_RESOLVED')); }, (err) => { - assert.equal(err.message, sprintf(ONREADY_TIMEOUT_EXPIRED, 500)); + assert.equal(err.baseMessage, ONREADY_TIMEOUT_EXPIRED); + assert.deepEqual(err.params, [ 500 ]); }); }); @@ -9493,7 +9491,8 @@ describe('lib/optimizely', function() { return readyPromise.then(() => { return Promise.reject(new Error(PROMISE_SHOULD_NOT_HAVE_RESOLVED)); }, (err) => { - assert.equal(err.message, 'onReady timeout expired after 30000 ms') + assert.equal(err.baseMessage, ONREADY_TIMEOUT_EXPIRED); + assert.deepEqual(err.params, [ 30000 ]); }); }); @@ -9517,7 +9516,7 @@ describe('lib/optimizely', function() { return readyPromise.then(() => { return Promise.reject(new Error(PROMISE_SHOULD_NOT_HAVE_RESOLVED)); }, (err) => { - assert.equal(err.message, 'Instance closed') + assert.equal(err.baseMessage, INSTANCE_CLOSED); }); }); diff --git a/lib/optimizely/index.ts b/lib/optimizely/index.ts index 1d30e4fa1..87da57af7 100644 --- a/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -52,7 +52,6 @@ import * as stringValidator from '../utils/string_value_validator'; import * as decision from '../core/decision'; import { - LOG_LEVEL, DECISION_SOURCES, DECISION_MESSAGES, FEATURE_VARIABLE_TYPES, @@ -78,6 +77,8 @@ import { EVENT_KEY_NOT_FOUND, NOT_TRACKING_USER, VARIABLE_REQUESTED_WITH_WRONG_TYPE, + ONREADY_TIMEOUT, + INSTANCE_CLOSED } from '../error_messages'; import { @@ -96,11 +97,10 @@ import { VALID_USER_PROFILE_SERVICE, VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE, } from '../log_messages'; -import { INSTANCE_CLOSED } from '../exception_messages'; + import { ErrorNotifier } from '../error/error_notifier'; import { ErrorReporter } from '../error/error_reporter'; - -const MODULE_NAME = 'OPTIMIZELY'; +import { OptimizelyError } from '../error/optimizly_error'; const DEFAULT_ONREADY_TIMEOUT = 30000; @@ -375,7 +375,7 @@ export default class Optimizely implements Client { } if (!this.isValidInstance()) { - this.logger?.error(INVALID_OBJECT, MODULE_NAME, 'track'); + this.logger?.error(INVALID_OBJECT, 'track'); return; } @@ -549,14 +549,14 @@ export default class Optimizely implements Client { if (stringInputs.hasOwnProperty('user_id')) { const userId = stringInputs['user_id']; if (typeof userId !== 'string' || userId === null || userId === 'undefined') { - throw new Error(sprintf(INVALID_INPUT_FORMAT, MODULE_NAME, 'user_id')); + throw new OptimizelyError(INVALID_INPUT_FORMAT, 'user_id'); } delete stringInputs['user_id']; } Object.keys(stringInputs).forEach(key => { if (!stringValidator.validate(stringInputs[key as InputKey])) { - throw new Error(sprintf(INVALID_INPUT_FORMAT, MODULE_NAME, key)); + throw new OptimizelyError(INVALID_INPUT_FORMAT, key); } }); if (userAttributes) { @@ -1043,7 +1043,7 @@ export default class Optimizely implements Client { ): string | null { try { if (!this.isValidInstance()) { - this.logger?.error(INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableString'); + this.logger?.error(INVALID_OBJECT, 'getFeatureVariableString'); return null; } return this.getFeatureVariableForType( @@ -1335,14 +1335,12 @@ export default class Optimizely implements Client { const onReadyTimeout = () => { delete this.readyTimeouts[timeoutId]; - timeoutPromise.reject(new Error( - sprintf('onReady timeout expired after %s ms', timeoutValue) - )); + timeoutPromise.reject(new OptimizelyError(ONREADY_TIMEOUT, timeoutValue)); }; const readyTimeout = setTimeout(onReadyTimeout, timeoutValue); const onClose = function() { - timeoutPromise.reject(new Error(INSTANCE_CLOSED)); + timeoutPromise.reject(new OptimizelyError(INSTANCE_CLOSED)); }; this.readyTimeouts[timeoutId] = { @@ -1573,7 +1571,7 @@ export default class Optimizely implements Client { if (!feature) { this.logger?.error(FEATURE_NOT_IN_DATAFILE, key); decisionMap[key] = newErrorDecision(key, user, [sprintf(DECISION_MESSAGES.FLAG_KEY_INVALID, key)]); - continue + continue; } validKeys.push(key); @@ -1625,7 +1623,7 @@ export default class Optimizely implements Client { const configObj = this.projectConfigManager.getConfig(); const decisionMap: { [key: string]: OptimizelyDecision } = {}; if (!this.isValidInstance() || !configObj) { - this.logger?.error(INVALID_OBJECT, MODULE_NAME, 'decideAll'); + this.logger?.error(INVALID_OBJECT, 'decideAll'); return decisionMap; } diff --git a/lib/project_config/polling_datafile_manager.ts b/lib/project_config/polling_datafile_manager.ts index 62b17cfe4..1f7c62473 100644 --- a/lib/project_config/polling_datafile_manager.ts +++ b/lib/project_config/polling_datafile_manager.ts @@ -23,14 +23,19 @@ import { RequestHandler, AbortableRequest, Headers, Response } from '../utils/ht import { Repeater } from '../utils/repeater/repeater'; import { Consumer, Fn } from '../utils/type'; import { isSuccessStatusCode } from '../utils/http_request_handler/http_util'; -import { DATAFILE_MANAGER_STOPPED, FAILED_TO_FETCH_DATAFILE } from '../exception_messages'; -import { DATAFILE_FETCH_REQUEST_FAILED, ERROR_FETCHING_DATAFILE } from '../error_messages'; +import { + DATAFILE_MANAGER_STOPPED, + DATAFILE_FETCH_REQUEST_FAILED, + ERROR_FETCHING_DATAFILE, + FAILED_TO_FETCH_DATAFILE, +} from '../error_messages'; import { ADDING_AUTHORIZATION_HEADER_WITH_BEARER_TOKEN, MAKING_DATAFILE_REQ_TO_URL_WITH_HEADERS, RESPONSE_STATUS_CODE, SAVED_LAST_MODIFIED_HEADER_VALUE_FROM_RESPONSE, } from '../log_messages'; +import { OptimizelyError } from '../error/optimizly_error'; export class PollingDatafileManager extends BaseService implements DatafileManager { private requestHandler: RequestHandler; @@ -107,7 +112,7 @@ export class PollingDatafileManager extends BaseService implements DatafileManag } if (this.isNew() || this.isStarting()) { - this.startPromise.reject(new Error(DATAFILE_MANAGER_STOPPED)); + this.startPromise.reject(new OptimizelyError(DATAFILE_MANAGER_STOPPED)); } this.logger?.debug(DATAFILE_MANAGER_STOPPED); @@ -121,7 +126,7 @@ export class PollingDatafileManager extends BaseService implements DatafileManag private handleInitFailure(): void { this.state = ServiceState.Failed; this.repeater.stop(); - const error = new Error(FAILED_TO_FETCH_DATAFILE); + const error = new OptimizelyError(FAILED_TO_FETCH_DATAFILE); this.startPromise.reject(error); this.stopPromise.reject(error); } diff --git a/lib/project_config/project_config.tests.js b/lib/project_config/project_config.tests.js index e776ebf72..ff8e18624 100644 --- a/lib/project_config/project_config.tests.js +++ b/lib/project_config/project_config.tests.js @@ -312,9 +312,11 @@ describe('lib/core/project_config', function() { }); it('should throw error for invalid experiment key in getExperimentId', function() { - assert.throws(function() { + const ex = assert.throws(function() { projectConfig.getExperimentId(configObj, 'invalidExperimentKey'); - }, sprintf(INVALID_EXPERIMENT_KEY, 'PROJECT_CONFIG', 'invalidExperimentKey')); + }); + assert.equal(ex.baseMessage, INVALID_EXPERIMENT_KEY); + assert.deepEqual(ex.params, ['invalidExperimentKey']); }); it('should retrieve layer ID for valid experiment key in getLayerId', function() { @@ -322,9 +324,11 @@ describe('lib/core/project_config', function() { }); it('should throw error for invalid experiment key in getLayerId', function() { - assert.throws(function() { + const ex = assert.throws(function() { projectConfig.getLayerId(configObj, 'invalidExperimentKey'); - }, sprintf(INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentKey')); + }); + assert.equal(ex.baseMessage, INVALID_EXPERIMENT_ID); + assert.deepEqual(ex.params, ['invalidExperimentKey']); }); it('should retrieve attribute ID for valid attribute key in getAttributeId', function() { @@ -368,9 +372,11 @@ describe('lib/core/project_config', function() { }); it('should throw error for invalid experiment key in getExperimentStatus', function() { - assert.throws(function() { + const ex = assert.throws(function() { projectConfig.getExperimentStatus(configObj, 'invalidExperimentKey'); - }, sprintf(INVALID_EXPERIMENT_KEY, 'PROJECT_CONFIG', 'invalidExperimentKey')); + }); + assert.equal(ex.baseMessage, INVALID_EXPERIMENT_KEY); + assert.deepEqual(ex.params, ['invalidExperimentKey']); }); it('should return true if experiment status is set to Running in isActive', function() { @@ -404,9 +410,11 @@ describe('lib/core/project_config', function() { }); it('should throw error for invalid experient key in getTrafficAllocation', function() { - assert.throws(function() { + const ex = assert.throws(function() { projectConfig.getTrafficAllocation(configObj, 'invalidExperimentId'); - }, sprintf(INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentId')); + }); + assert.equal(ex.baseMessage, INVALID_EXPERIMENT_ID); + assert.deepEqual(ex.params, ['invalidExperimentId']); }); describe('#getVariationIdFromExperimentAndVariationKey', function() { @@ -686,9 +694,11 @@ describe('lib/core/project_config', function() { it('should throw error for invalid experiment key', function() { configObj = projectConfig.createProjectConfig(cloneDeep(testData)); - assert.throws(function() { + const ex = assert.throws(function() { projectConfig.getExperimentAudienceConditions(configObj, 'invalidExperimentId'); - }, sprintf(INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentId')); + }); + assert.equal(ex.baseMessage, INVALID_EXPERIMENT_ID); + assert.deepEqual(ex.params, ['invalidExperimentId']); }); it('should return experiment audienceIds if experiment has no audienceConditions', function() { diff --git a/lib/project_config/project_config.ts b/lib/project_config/project_config.ts index 3671928ac..1a7ad4313 100644 --- a/lib/project_config/project_config.ts +++ b/lib/project_config/project_config.ts @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { find, objectEntries, objectValues, sprintf, keyBy } from '../utils/fns'; +import { find, objectEntries, objectValues, keyBy } from '../utils/fns'; -import { LOG_LEVEL, FEATURE_VARIABLE_TYPES } from '../utils/enums'; +import { FEATURE_VARIABLE_TYPES } from '../utils/enums'; import configValidator from '../utils/config_validator'; import { LoggerFacade } from '../logging/logger'; @@ -50,6 +50,7 @@ import { VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT, } from '../error_messages'; import { SKIPPING_JSON_VALIDATION, VALID_DATAFILE } from '../log_messages'; +import { OptimizelyError } from '../error/optimizly_error'; interface TryCreatingProjectConfigConfig { // TODO[OASIS-6649]: Don't use object type @@ -109,7 +110,6 @@ export interface ProjectConfig { const EXPERIMENT_RUNNING_STATUS = 'Running'; const RESERVED_ATTRIBUTE_PREFIX = '$opt_'; -const MODULE_NAME = 'PROJECT_CONFIG'; // eslint-disable-next-line @typescript-eslint/no-explicit-any function createMutationSafeDatafileCopy(datafile: any): ProjectConfig { @@ -213,7 +213,7 @@ export const createProjectConfig = function(datafileObj?: JSON, datafileStr: str projectConfig.integrations.forEach(integration => { if (!('key' in integration)) { - throw new Error(sprintf(MISSING_INTEGRATION_KEY, MODULE_NAME)); + throw new OptimizelyError(MISSING_INTEGRATION_KEY); } if (integration.key === 'odp') { @@ -361,7 +361,7 @@ function isLogicalOperator(condition: string): boolean { export const getExperimentId = function(projectConfig: ProjectConfig, experimentKey: string): string { const experiment = projectConfig.experimentKeyMap[experimentKey]; if (!experiment) { - throw new Error(sprintf(INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey)); + throw new OptimizelyError(INVALID_EXPERIMENT_KEY, experimentKey); } return experiment.id; }; @@ -376,7 +376,7 @@ export const getExperimentId = function(projectConfig: ProjectConfig, experiment export const getLayerId = function(projectConfig: ProjectConfig, experimentId: string): string { const experiment = projectConfig.experimentIdMap[experimentId]; if (!experiment) { - throw new Error(sprintf(INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId)); + throw new OptimizelyError(INVALID_EXPERIMENT_ID, experimentId); } return experiment.layerId; }; @@ -436,7 +436,7 @@ export const getEventId = function(projectConfig: ProjectConfig, eventKey: strin export const getExperimentStatus = function(projectConfig: ProjectConfig, experimentKey: string): string { const experiment = projectConfig.experimentKeyMap[experimentKey]; if (!experiment) { - throw new Error(sprintf(INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey)); + throw new OptimizelyError(INVALID_EXPERIMENT_KEY, experimentKey); } return experiment.status; }; @@ -478,7 +478,7 @@ export const getExperimentAudienceConditions = function( ): Array { const experiment = projectConfig.experimentIdMap[experimentId]; if (!experiment) { - throw new Error(sprintf(INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId)); + throw new OptimizelyError(INVALID_EXPERIMENT_ID, experimentId); } return experiment.audienceConditions || experiment.audienceIds; @@ -547,7 +547,7 @@ export const getExperimentFromKey = function(projectConfig: ProjectConfig, exper } } - throw new Error(sprintf(EXPERIMENT_KEY_NOT_IN_DATAFILE, MODULE_NAME, experimentKey)); + throw new OptimizelyError(EXPERIMENT_KEY_NOT_IN_DATAFILE, experimentKey); }; /** @@ -560,7 +560,7 @@ export const getExperimentFromKey = function(projectConfig: ProjectConfig, exper export const getTrafficAllocation = function(projectConfig: ProjectConfig, experimentId: string): TrafficAllocation[] { const experiment = projectConfig.experimentIdMap[experimentId]; if (!experiment) { - throw new Error(sprintf(INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId)); + throw new OptimizelyError(INVALID_EXPERIMENT_ID, experimentId); } return experiment.trafficAllocation; }; @@ -836,7 +836,7 @@ export const tryCreatingProjectConfig = function( config.jsonSchemaValidator(newDatafileObj); config.logger?.info(VALID_DATAFILE); } else { - config.logger?.info(SKIPPING_JSON_VALIDATION, MODULE_NAME); + config.logger?.info(SKIPPING_JSON_VALIDATION); } const createProjectConfigArgs = [newDatafileObj]; diff --git a/lib/project_config/project_config_manager.ts b/lib/project_config/project_config_manager.ts index 4a851347e..985a0524e 100644 --- a/lib/project_config/project_config_manager.ts +++ b/lib/project_config/project_config_manager.ts @@ -22,11 +22,8 @@ import { scheduleMicrotask } from '../utils/microtask'; import { Service, ServiceState, BaseService } from '../service'; import { Consumer, Fn, Transformer } from '../utils/type'; import { EventEmitter } from '../utils/event_emitter/event_emitter'; -import { - DATAFILE_MANAGER_FAILED_TO_START, - DATAFILE_MANAGER_STOPPED, - YOU_MUST_PROVIDE_AT_LEAST_ONE_OF_SDKKEY_OR_DATAFILE, -} from '../exception_messages'; +import { DATAFILE_MANAGER_STOPPED, NO_SDKKEY_OR_DATAFILE, DATAFILE_MANAGER_FAILED_TO_START } from '../error_messages'; +import { OptimizelyError } from '../error/optimizly_error'; interface ProjectConfigManagerConfig { datafile?: string | Record; @@ -73,7 +70,7 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf this.state = ServiceState.Starting; if (!this.datafile && !this.datafileManager) { - this.handleInitError(new Error(YOU_MUST_PROVIDE_AT_LEAST_ONE_OF_SDKKEY_OR_DATAFILE)); + this.handleInitError(new OptimizelyError(NO_SDKKEY_OR_DATAFILE)); return; } @@ -197,7 +194,7 @@ export class ProjectConfigManagerImpl extends BaseService implements ProjectConf } if (this.isNew() || this.isStarting()) { - this.startPromise.reject(new Error(DATAFILE_MANAGER_STOPPED)); + this.startPromise.reject(new OptimizelyError(DATAFILE_MANAGER_STOPPED)); } this.state = ServiceState.Stopping; diff --git a/lib/utils/attributes_validator/index.tests.js b/lib/utils/attributes_validator/index.tests.js index ed79d9470..17daf68e8 100644 --- a/lib/utils/attributes_validator/index.tests.js +++ b/lib/utils/attributes_validator/index.tests.js @@ -28,24 +28,27 @@ describe('lib/utils/attributes_validator', function() { it('should throw an error if attributes is an array', function() { var attributesArray = ['notGonnaWork']; - assert.throws(function() { + const ex = assert.throws(function() { attributesValidator.validate(attributesArray); - }, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_ATTRIBUTES); }); it('should throw an error if attributes is null', function() { - assert.throws(function() { + const ex = assert.throws(function() { attributesValidator.validate(null); - }, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_ATTRIBUTES); }); it('should throw an error if attributes is a function', function() { function invalidInput() { console.log('This is an invalid input!'); } - assert.throws(function() { + const ex = assert.throws(function() { attributesValidator.validate(invalidInput); - }, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_ATTRIBUTES); }); it('should throw an error if attributes contains a key with an undefined value', function() { @@ -53,9 +56,11 @@ describe('lib/utils/attributes_validator', function() { var attributes = {}; attributes[attributeKey] = undefined; - assert.throws(function() { + const ex = assert.throws(function() { attributesValidator.validate(attributes); - }, sprintf(UNDEFINED_ATTRIBUTE, 'ATTRIBUTES_VALIDATOR', attributeKey)); + }); + assert.equal(ex.baseMessage, UNDEFINED_ATTRIBUTE); + assert.deepEqual(ex.params, [attributeKey]); }); }); diff --git a/lib/utils/attributes_validator/index.ts b/lib/utils/attributes_validator/index.ts index 255b99419..adbe70bdb 100644 --- a/lib/utils/attributes_validator/index.ts +++ b/lib/utils/attributes_validator/index.ts @@ -13,13 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { sprintf } from '../../utils/fns'; import { ObjectWithUnknownProperties } from '../../shared_types'; import fns from '../../utils/fns'; import { INVALID_ATTRIBUTES, UNDEFINED_ATTRIBUTE } from '../../error_messages'; - -const MODULE_NAME = 'ATTRIBUTES_VALIDATOR'; +import { OptimizelyError } from '../../error/optimizly_error'; /** * Validates user's provided attributes @@ -32,12 +30,12 @@ export function validate(attributes: unknown): boolean { if (typeof attributes === 'object' && !Array.isArray(attributes) && attributes !== null) { Object.keys(attributes).forEach(function(key) { if (typeof (attributes as ObjectWithUnknownProperties)[key] === 'undefined') { - throw new Error(sprintf(UNDEFINED_ATTRIBUTE, MODULE_NAME, key)); + throw new OptimizelyError(UNDEFINED_ATTRIBUTE, key); } }); return true; } else { - throw new Error(sprintf(INVALID_ATTRIBUTES, MODULE_NAME)); + throw new OptimizelyError(INVALID_ATTRIBUTES); } } diff --git a/lib/utils/config_validator/index.tests.js b/lib/utils/config_validator/index.tests.js index b7a8711c7..8ff6e7581 100644 --- a/lib/utils/config_validator/index.tests.js +++ b/lib/utils/config_validator/index.tests.js @@ -31,45 +31,52 @@ describe('lib/utils/config_validator', function() { describe('APIs', function() { describe('validate', function() { it('should complain if the provided error handler is invalid', function() { - assert.throws(function() { + const ex = assert.throws(function() { configValidator.validate({ errorHandler: {}, }); - }, sprintf(INVALID_ERROR_HANDLER, 'CONFIG_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_ERROR_HANDLER); }); it('should complain if the provided event dispatcher is invalid', function() { - assert.throws(function() { + const ex = assert.throws(function() { configValidator.validate({ eventDispatcher: {}, }); - }, sprintf(INVALID_EVENT_DISPATCHER, 'CONFIG_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_EVENT_DISPATCHER); }); it('should complain if the provided logger is invalid', function() { - assert.throws(function() { + const ex = assert.throws(function() { configValidator.validate({ logger: {}, }); - }, sprintf(INVALID_LOGGER, 'CONFIG_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_LOGGER); }); it('should complain if datafile is not provided', function() { - assert.throws(function() { + const ex = assert.throws(function() { configValidator.validateDatafile(); - }, sprintf(NO_DATAFILE_SPECIFIED, 'CONFIG_VALIDATOR')); + }); + assert.equal(ex.baseMessage, NO_DATAFILE_SPECIFIED); }); it('should complain if datafile is malformed', function() { - assert.throws(function() { + const ex = assert.throws(function() { configValidator.validateDatafile('abc'); - }, sprintf(INVALID_DATAFILE_MALFORMED, 'CONFIG_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_DATAFILE_MALFORMED); }); it('should complain if datafile version is not supported', function() { - assert.throws(function() { + const ex = assert.throws(function() { configValidator.validateDatafile(JSON.stringify(testData.getUnsupportedVersionConfig())); - }, sprintf(INVALID_DATAFILE_VERSION, 'CONFIG_VALIDATOR', '5')); + }); + assert.equal(ex.baseMessage, INVALID_DATAFILE_VERSION); + assert.deepEqual(ex.params, ['5']); }); it('should not complain if datafile is valid', function() { diff --git a/lib/utils/config_validator/index.ts b/lib/utils/config_validator/index.ts index f3c2eadfd..a61d4f1cf 100644 --- a/lib/utils/config_validator/index.ts +++ b/lib/utils/config_validator/index.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { sprintf } from '../../utils/fns'; import { ObjectWithUnknownProperties } from '../../shared_types'; import { @@ -28,8 +27,8 @@ import { INVALID_LOGGER, NO_DATAFILE_SPECIFIED, } from '../../error_messages'; +import { OptimizelyError } from '../../error/optimizly_error'; -const MODULE_NAME = 'CONFIG_VALIDATOR'; const SUPPORTED_VERSIONS = [DATAFILE_VERSIONS.V2, DATAFILE_VERSIONS.V3, DATAFILE_VERSIONS.V4]; /** @@ -48,17 +47,17 @@ export const validate = function(config: unknown): boolean { const eventDispatcher = configObj['eventDispatcher']; const logger = configObj['logger']; if (errorHandler && typeof (errorHandler as ObjectWithUnknownProperties)['handleError'] !== 'function') { - throw new Error(sprintf(INVALID_ERROR_HANDLER, MODULE_NAME)); + throw new OptimizelyError(INVALID_ERROR_HANDLER); } if (eventDispatcher && typeof (eventDispatcher as ObjectWithUnknownProperties)['dispatchEvent'] !== 'function') { - throw new Error(sprintf(INVALID_EVENT_DISPATCHER, MODULE_NAME)); + throw new OptimizelyError(INVALID_EVENT_DISPATCHER); } if (logger && typeof (logger as ObjectWithUnknownProperties)['info'] !== 'function') { - throw new Error(sprintf(INVALID_LOGGER, MODULE_NAME)); + throw new OptimizelyError(INVALID_LOGGER); } return true; } - throw new Error(sprintf(INVALID_CONFIG, MODULE_NAME)); + throw new OptimizelyError(INVALID_CONFIG); } /** @@ -73,19 +72,19 @@ export const validate = function(config: unknown): boolean { // eslint-disable-next-line export const validateDatafile = function(datafile: unknown): any { if (!datafile) { - throw new Error(sprintf(NO_DATAFILE_SPECIFIED, MODULE_NAME)); + throw new OptimizelyError(NO_DATAFILE_SPECIFIED); } if (typeof datafile === 'string') { // Attempt to parse the datafile string try { datafile = JSON.parse(datafile); } catch (ex) { - throw new Error(sprintf(INVALID_DATAFILE_MALFORMED, MODULE_NAME)); + throw new OptimizelyError(INVALID_DATAFILE_MALFORMED); } } if (typeof datafile === 'object' && !Array.isArray(datafile) && datafile !== null) { if (SUPPORTED_VERSIONS.indexOf(datafile['version' as keyof unknown]) === -1) { - throw new Error(sprintf(INVALID_DATAFILE_VERSION, MODULE_NAME, datafile['version' as keyof unknown])); + throw new OptimizelyError(INVALID_DATAFILE_VERSION, datafile['version' as keyof unknown]); } } diff --git a/lib/utils/event_tag_utils/index.ts b/lib/utils/event_tag_utils/index.ts index c8fc9835f..8819086a9 100644 --- a/lib/utils/event_tag_utils/index.ts +++ b/lib/utils/event_tag_utils/index.ts @@ -23,14 +23,12 @@ import { EventTags } from '../../event_processor/event_builder/user_event'; import { LoggerFacade } from '../../logging/logger'; import { - LOG_LEVEL, RESERVED_EVENT_KEYWORDS, } from '../enums'; /** * Provides utility method for parsing event tag values */ -const MODULE_NAME = 'EVENT_TAG_UTILS'; const REVENUE_EVENT_METRIC_NAME = RESERVED_EVENT_KEYWORDS.REVENUE; const VALUE_EVENT_METRIC_NAME = RESERVED_EVENT_KEYWORDS.VALUE; diff --git a/lib/utils/event_tags_validator/index.tests.js b/lib/utils/event_tags_validator/index.tests.js index fcf8d4bd3..a7ea58956 100644 --- a/lib/utils/event_tags_validator/index.tests.js +++ b/lib/utils/event_tags_validator/index.tests.js @@ -28,24 +28,27 @@ describe('lib/utils/event_tags_validator', function() { it('should throw an error if event tags is an array', function() { var eventTagsArray = ['notGonnaWork']; - assert.throws(function() { + const ex = assert.throws(function() { validate(eventTagsArray); - }, sprintf(INVALID_EVENT_TAGS, 'EVENT_TAGS_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_EVENT_TAGS); }); it('should throw an error if event tags is null', function() { - assert.throws(function() { + const ex = assert.throws(function() { validate(null); - }, sprintf(INVALID_EVENT_TAGS, 'EVENT_TAGS_VALIDATOR')); + }) + assert.equal(ex.baseMessage, INVALID_EVENT_TAGS); }); it('should throw an error if event tags is a function', function() { function invalidInput() { console.log('This is an invalid input!'); } - assert.throws(function() { + const ex = assert.throws(function() { validate(invalidInput); - }, sprintf(INVALID_EVENT_TAGS, 'EVENT_TAGS_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_EVENT_TAGS); }); }); }); diff --git a/lib/utils/event_tags_validator/index.ts b/lib/utils/event_tags_validator/index.ts index d898cc202..6dde8a045 100644 --- a/lib/utils/event_tags_validator/index.ts +++ b/lib/utils/event_tags_validator/index.ts @@ -17,10 +17,8 @@ /** * Provides utility method for validating that event tags user has provided are valid */ +import { OptimizelyError } from '../../error/optimizly_error'; import { INVALID_EVENT_TAGS } from '../../error_messages'; -import { sprintf } from '../../utils/fns'; - -const MODULE_NAME = 'EVENT_TAGS_VALIDATOR'; /** * Validates user's provided event tags @@ -32,6 +30,6 @@ export function validate(eventTags: unknown): boolean { if (typeof eventTags === 'object' && !Array.isArray(eventTags) && eventTags !== null) { return true; } else { - throw new Error(sprintf(INVALID_EVENT_TAGS, MODULE_NAME)); + throw new OptimizelyError(INVALID_EVENT_TAGS); } } diff --git a/lib/utils/executor/backoff_retry_runner.ts b/lib/utils/executor/backoff_retry_runner.ts index f939f9cc6..93b3ef748 100644 --- a/lib/utils/executor/backoff_retry_runner.ts +++ b/lib/utils/executor/backoff_retry_runner.ts @@ -1,4 +1,5 @@ -import { RETRY_CANCELLED } from "../../exception_messages"; +import { OptimizelyError } from "../../error/optimizly_error"; +import { RETRY_CANCELLED } from "../../error_messages"; import { resolvablePromise, ResolvablePromise } from "../promise/resolvablePromise"; import { BackoffController } from "../repeater/repeater"; import { AsyncProducer, Fn } from "../type"; @@ -27,7 +28,7 @@ const runTask = ( return; } if (cancelSignal.cancelled) { - returnPromise.reject(new Error(RETRY_CANCELLED)); + returnPromise.reject(new OptimizelyError(RETRY_CANCELLED)); return; } const delay = backoff?.backoff() ?? 0; diff --git a/lib/utils/http_request_handler/request_handler.browser.ts b/lib/utils/http_request_handler/request_handler.browser.ts index 88157e6a9..5ab8ce1cb 100644 --- a/lib/utils/http_request_handler/request_handler.browser.ts +++ b/lib/utils/http_request_handler/request_handler.browser.ts @@ -19,6 +19,7 @@ import { LoggerFacade, LogLevel } from '../../logging/logger'; import { REQUEST_TIMEOUT_MS } from '../enums'; import { REQUEST_ERROR, REQUEST_TIMEOUT } from '../../error_messages'; import { UNABLE_TO_PARSE_AND_SKIPPED_HEADER } from '../../log_messages'; +import { OptimizelyError } from '../../error/optimizly_error'; /** * Handles sending requests and receiving responses over HTTP via XMLHttpRequest @@ -52,7 +53,7 @@ export class BrowserRequestHandler implements RequestHandler { if (request.readyState === XMLHttpRequest.DONE) { const statusCode = request.status; if (statusCode === 0) { - reject(new Error(REQUEST_ERROR)); + reject(new OptimizelyError(REQUEST_ERROR)); return; } diff --git a/lib/utils/http_request_handler/request_handler.node.ts b/lib/utils/http_request_handler/request_handler.node.ts index 6626510e8..cf0a620db 100644 --- a/lib/utils/http_request_handler/request_handler.node.ts +++ b/lib/utils/http_request_handler/request_handler.node.ts @@ -20,8 +20,8 @@ import { AbortableRequest, Headers, RequestHandler, Response } from './http'; import decompressResponse from 'decompress-response'; import { LoggerFacade } from '../../logging/logger'; import { REQUEST_TIMEOUT_MS } from '../enums'; -import { sprintf } from '../fns'; import { NO_STATUS_CODE_IN_RESPONSE, REQUEST_ERROR, REQUEST_TIMEOUT, UNSUPPORTED_PROTOCOL } from '../../error_messages'; +import { OptimizelyError } from '../../error/optimizly_error'; /** * Handles sending requests and receiving responses over HTTP via NodeJS http module @@ -48,7 +48,7 @@ export class NodeRequestHandler implements RequestHandler { if (parsedUrl.protocol !== 'https:') { return { - responsePromise: Promise.reject(new Error(sprintf(UNSUPPORTED_PROTOCOL, parsedUrl.protocol))), + responsePromise: Promise.reject(new OptimizelyError(UNSUPPORTED_PROTOCOL, parsedUrl.protocol)), abort: () => {}, }; } @@ -130,7 +130,7 @@ export class NodeRequestHandler implements RequestHandler { request.on('timeout', () => { aborted = true; request.destroy(); - reject(new Error(REQUEST_TIMEOUT)); + reject(new OptimizelyError(REQUEST_TIMEOUT)); }); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -140,7 +140,7 @@ export class NodeRequestHandler implements RequestHandler { } else if (typeof err === 'string') { reject(new Error(err)); } else { - reject(new Error(REQUEST_ERROR)); + reject(new OptimizelyError(REQUEST_ERROR)); } }); @@ -166,7 +166,7 @@ export class NodeRequestHandler implements RequestHandler { } if (!incomingMessage.statusCode) { - reject(new Error(NO_STATUS_CODE_IN_RESPONSE)); + reject(new OptimizelyError(NO_STATUS_CODE_IN_RESPONSE)); return; } diff --git a/lib/utils/json_schema_validator/index.tests.js b/lib/utils/json_schema_validator/index.tests.js index d54bc39a4..aaee8dc27 100644 --- a/lib/utils/json_schema_validator/index.tests.js +++ b/lib/utils/json_schema_validator/index.tests.js @@ -31,9 +31,10 @@ describe('lib/utils/json_schema_validator', function() { }); it('should throw an error if no json object is passed in', function() { - assert.throws(function() { + const ex = assert.throws(function() { validate(); - }, sprintf(NO_JSON_PROVIDED, 'JSON_SCHEMA_VALIDATOR (Project Config JSON Schema)')); + }); + assert.equal(ex.baseMessage, NO_JSON_PROVIDED); }); it('should validate specified Optimizely datafile', function() { diff --git a/lib/utils/json_schema_validator/index.ts b/lib/utils/json_schema_validator/index.ts index a4bac5674..f5824931c 100644 --- a/lib/utils/json_schema_validator/index.ts +++ b/lib/utils/json_schema_validator/index.ts @@ -13,13 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { sprintf } from '../fns'; import { JSONSchema4, validate as jsonSchemaValidator } from 'json-schema'; import schema from '../../project_config/project_config_schema'; import { INVALID_DATAFILE, INVALID_JSON, NO_JSON_PROVIDED } from '../../error_messages'; - -const MODULE_NAME = 'JSON_SCHEMA_VALIDATOR'; +import { OptimizelyError } from '../../error/optimizly_error'; /** * Validate the given json object against the specified schema @@ -33,10 +31,8 @@ export function validate( validationSchema: JSONSchema4 = schema, shouldThrowOnError = true ): boolean { - const moduleTitle = `${MODULE_NAME} (${validationSchema.title})`; - if (typeof jsonObject !== 'object' || jsonObject === null) { - throw new Error(sprintf(NO_JSON_PROVIDED, moduleTitle)); + throw new OptimizelyError(NO_JSON_PROVIDED); } const result = jsonSchemaValidator(jsonObject, validationSchema); @@ -49,10 +45,10 @@ export function validate( } if (Array.isArray(result.errors)) { - throw new Error( - sprintf(INVALID_DATAFILE, moduleTitle, result.errors[0].property, result.errors[0].message) + throw new OptimizelyError( + INVALID_DATAFILE, result.errors[0].property, result.errors[0].message ); } - throw new Error(sprintf(INVALID_JSON, moduleTitle)); + throw new OptimizelyError(INVALID_JSON); } diff --git a/lib/utils/semantic_version/index.ts b/lib/utils/semantic_version/index.ts index ecd7bb804..2e5e02e47 100644 --- a/lib/utils/semantic_version/index.ts +++ b/lib/utils/semantic_version/index.ts @@ -17,8 +17,6 @@ import { UNKNOWN_MATCH_TYPE } from '../../error_messages'; import { LoggerFacade } from '../../logging/logger'; import { VERSION_TYPE } from '../enums'; -const MODULE_NAME = 'SEMANTIC VERSION'; - /** * Evaluate if provided string is number only * @param {unknown} content diff --git a/lib/utils/type.ts b/lib/utils/type.ts index ddf3871aa..0ddc6fc3c 100644 --- a/lib/utils/type.ts +++ b/lib/utils/type.ts @@ -26,3 +26,5 @@ export type Producer = () => T; export type AsyncProducer = () => Promise; export type Maybe = T | undefined; + +export type Either = { type: 'left', value: A } | { type: 'right', value: B }; diff --git a/lib/utils/user_profile_service_validator/index.tests.js b/lib/utils/user_profile_service_validator/index.tests.js index f12f790ea..8f2e50b28 100644 --- a/lib/utils/user_profile_service_validator/index.tests.js +++ b/lib/utils/user_profile_service_validator/index.tests.js @@ -26,13 +26,11 @@ describe('lib/utils/user_profile_service_validator', function() { var missingLookupFunction = { save: function() {}, }; - assert.throws(function() { + const ex = assert.throws(function() { validate(missingLookupFunction); - }, sprintf( - INVALID_USER_PROFILE_SERVICE, - 'USER_PROFILE_SERVICE_VALIDATOR', - "Missing function 'lookup'" - )); + }); + assert.equal(ex.baseMessage, INVALID_USER_PROFILE_SERVICE); + assert.deepEqual(ex.params, ["Missing function 'lookup'"]); }); it("should throw if 'lookup' is not a function", function() { @@ -40,26 +38,27 @@ describe('lib/utils/user_profile_service_validator', function() { save: function() {}, lookup: 'notGonnaWork', }; - assert.throws(function() { + const ex = assert.throws(function() { validate(lookupNotFunction); - }, sprintf( - INVALID_USER_PROFILE_SERVICE, - 'USER_PROFILE_SERVICE_VALIDATOR', - "Missing function 'lookup'" - )); + }); + assert.equal(ex.baseMessage, INVALID_USER_PROFILE_SERVICE); + assert.deepEqual(ex.params, ["Missing function 'lookup'"]); }); it("should throw if the instance does not provide a 'save' function", function() { var missingSaveFunction = { lookup: function() {}, }; - assert.throws(function() { + const ex = assert.throws(function() { validate(missingSaveFunction); - }, sprintf( - INVALID_USER_PROFILE_SERVICE, - 'USER_PROFILE_SERVICE_VALIDATOR', - "Missing function 'save'" - )); + }); + assert.equal(ex.baseMessage, INVALID_USER_PROFILE_SERVICE); + assert.deepEqual(ex.params, ["Missing function 'save'"]); + // , sprintf( + // INVALID_USER_PROFILE_SERVICE, + // 'USER_PROFILE_SERVICE_VALIDATOR', + // "Missing function 'save'" + // )); }); it("should throw if 'save' is not a function", function() { @@ -67,13 +66,11 @@ describe('lib/utils/user_profile_service_validator', function() { lookup: function() {}, save: 'notGonnaWork', }; - assert.throws(function() { + const ex = assert.throws(function() { validate(saveNotFunction); - }, sprintf( - INVALID_USER_PROFILE_SERVICE, - 'USER_PROFILE_SERVICE_VALIDATOR', - "Missing function 'save'" - )); + }); + assert.equal(ex.baseMessage, INVALID_USER_PROFILE_SERVICE); + assert.deepEqual(ex.params, ["Missing function 'save'"]); }); it('should return true if the instance is valid', function() { diff --git a/lib/utils/user_profile_service_validator/index.ts b/lib/utils/user_profile_service_validator/index.ts index 8f51fc137..cb7529dcb 100644 --- a/lib/utils/user_profile_service_validator/index.ts +++ b/lib/utils/user_profile_service_validator/index.ts @@ -18,11 +18,10 @@ * Provides utility method for validating that the given user profile service implementation is valid. */ -import { sprintf } from '../../utils/fns'; import { ObjectWithUnknownProperties } from '../../shared_types'; import { INVALID_USER_PROFILE_SERVICE } from '../../error_messages'; -const MODULE_NAME = 'USER_PROFILE_SERVICE_VALIDATOR'; +import { OptimizelyError } from '../../error/optimizly_error'; /** * Validates user's provided user profile service instance @@ -34,11 +33,11 @@ const MODULE_NAME = 'USER_PROFILE_SERVICE_VALIDATOR'; export function validate(userProfileServiceInstance: unknown): boolean { if (typeof userProfileServiceInstance === 'object' && userProfileServiceInstance !== null) { if (typeof (userProfileServiceInstance as ObjectWithUnknownProperties)['lookup'] !== 'function') { - throw new Error(sprintf(INVALID_USER_PROFILE_SERVICE, MODULE_NAME, "Missing function 'lookup'")); + throw new OptimizelyError(INVALID_USER_PROFILE_SERVICE, "Missing function 'lookup'"); } else if (typeof (userProfileServiceInstance as ObjectWithUnknownProperties)['save'] !== 'function') { - throw new Error(sprintf(INVALID_USER_PROFILE_SERVICE, MODULE_NAME, "Missing function 'save'")); + throw new OptimizelyError(INVALID_USER_PROFILE_SERVICE, "Missing function 'save'"); } return true; } - throw new Error(sprintf(INVALID_USER_PROFILE_SERVICE, MODULE_NAME)); + throw new OptimizelyError(INVALID_USER_PROFILE_SERVICE, 'Not an object'); } diff --git a/lib/vuid/vuid_manager_factory.node.spec.ts b/lib/vuid/vuid_manager_factory.node.spec.ts index 048704794..0d8a6af5b 100644 --- a/lib/vuid/vuid_manager_factory.node.spec.ts +++ b/lib/vuid/vuid_manager_factory.node.spec.ts @@ -17,7 +17,7 @@ import { vi, describe, expect, it } from 'vitest'; import { createVuidManager } from './vuid_manager_factory.node'; -import { VUID_IS_NOT_SUPPORTED_IN_NODEJS } from '../exception_messages'; +import { VUID_IS_NOT_SUPPORTED_IN_NODEJS } from './vuid_manager_factory.node'; describe('createVuidManager', () => { it('should throw an error', () => { diff --git a/lib/vuid/vuid_manager_factory.node.ts b/lib/vuid/vuid_manager_factory.node.ts index 6d194ce0b..ebc7fd373 100644 --- a/lib/vuid/vuid_manager_factory.node.ts +++ b/lib/vuid/vuid_manager_factory.node.ts @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { VUID_IS_NOT_SUPPORTED_IN_NODEJS } from '../exception_messages'; import { VuidManager } from './vuid_manager'; import { VuidManagerOptions } from './vuid_manager_factory'; +export const VUID_IS_NOT_SUPPORTED_IN_NODEJS= 'VUID is not supported in Node.js environment'; + export const createVuidManager = (options: VuidManagerOptions): VuidManager => { throw new Error(VUID_IS_NOT_SUPPORTED_IN_NODEJS); };