Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/evaluator/parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function parser(log: ILogger, conditions: ISplitCondition[], storage: ISt
const matcher = matcherFactory(log, matcherDto, storage);

// Evaluator function.
return (key: string, attributes: SplitIO.Attributes, splitEvaluator: ISplitEvaluator) => {
return (key: string, attributes: SplitIO.Attributes | undefined, splitEvaluator: ISplitEvaluator) => {
const value = sanitizeValue(log, key, matcherDto, attributes);
const result = value !== undefined && matcher ? matcher(value, splitEvaluator) : false;

Expand Down
4 changes: 2 additions & 2 deletions src/evaluator/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ILogger } from '../logger/types';

export interface IDependencyMatcherValue {
key: SplitIO.SplitKey,
attributes: SplitIO.Attributes
attributes?: SplitIO.Attributes
}

export interface IMatcherDto {
Expand All @@ -27,7 +27,7 @@ export interface IEvaluation {

export type IEvaluationResult = IEvaluation & { treatment: string }

export type ISplitEvaluator = (log: ILogger, key: SplitIO.SplitKey, splitName: string, attributes: SplitIO.Attributes, storage: IStorageSync | IStorageAsync) => MaybeThenable<IEvaluation>
export type ISplitEvaluator = (log: ILogger, key: SplitIO.SplitKey, splitName: string, attributes: SplitIO.Attributes | undefined, storage: IStorageSync | IStorageAsync) => MaybeThenable<IEvaluation>

export type IEvaluator = (key: SplitIO.SplitKey, seed: number, trafficAllocation?: number, trafficAllocationSeed?: number, attributes?: SplitIO.Attributes, splitEvaluator?: ISplitEvaluator) => MaybeThenable<IEvaluation | undefined>

Expand Down
4 changes: 2 additions & 2 deletions src/evaluator/value/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ILogger } from '../../logger/types';
import { sanitize } from './sanitize';
import { ENGINE_VALUE, ENGINE_VALUE_NO_ATTRIBUTES, ENGINE_VALUE_INVALID } from '../../logger/constants';

function parseValue(log: ILogger, key: string, attributeName: string | null, attributes: SplitIO.Attributes) {
function parseValue(log: ILogger, key: string, attributeName: string | null, attributes?: SplitIO.Attributes) {
let value = undefined;
if (attributeName) {
if (attributes) {
Expand All @@ -23,7 +23,7 @@ function parseValue(log: ILogger, key: string, attributeName: string | null, att
/**
* Defines value to be matched (key / attribute).
*/
export function sanitizeValue(log: ILogger, key: string, matcherDto: IMatcherDto, attributes: SplitIO.Attributes) {
export function sanitizeValue(log: ILogger, key: string, matcherDto: IMatcherDto, attributes?: SplitIO.Attributes) {
const attributeName = matcherDto.attribute;
const valueToMatch = parseValue(log, key, attributeName, attributes);
const sanitizedValue = sanitize(log, matcherDto.type, valueToMatch, matcherDto.dataType, attributes);
Expand Down
4 changes: 2 additions & 2 deletions src/evaluator/value/sanitize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ function sanitizeBoolean(val: any): boolean | undefined {
return undefined;
}

function dependencyProcessor(sanitizedValue: string, attributes: SplitIO.Attributes): IDependencyMatcherValue {
function dependencyProcessor(sanitizedValue: string, attributes?: SplitIO.Attributes): IDependencyMatcherValue {
return {
key: sanitizedValue,
attributes
Expand Down Expand Up @@ -69,7 +69,7 @@ function getProcessingFunction(matcherTypeID: number, dataType: string) {
/**
* Sanitize matcher value
*/
export function sanitize(log: ILogger, matcherTypeID: number, value: string | number | boolean | Array<string | number> | undefined, dataType: string, attributes: SplitIO.Attributes) {
export function sanitize(log: ILogger, matcherTypeID: number, value: string | number | boolean | Array<string | number> | undefined, dataType: string, attributes?: SplitIO.Attributes) {
const processor = getProcessingFunction(matcherTypeID, dataType);
let sanitizedValue: string | number | boolean | Array<string | number> | IDependencyMatcherValue | undefined;

Expand Down
3 changes: 1 addition & 2 deletions src/utils/inputValidation/attributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { ERROR_NOT_PLAIN_OBJECT } from '../../logger/constants';

export function validateAttributes(log: ILogger, maybeAttrs: any, method: string): SplitIO.Attributes | undefined | false {
// Attributes are optional
if (isObject(maybeAttrs) || maybeAttrs == undefined) // eslint-disable-line eqeqeq
if (maybeAttrs == undefined || isObject(maybeAttrs)) // eslint-disable-line eqeqeq
return maybeAttrs;

log.error(ERROR_NOT_PLAIN_OBJECT, [method, 'attributes']);
Expand All @@ -23,5 +23,4 @@ export function validateAttributesDeep(log: ILogger, maybeAttributes: Record<str
});

return result;

}
20 changes: 20 additions & 0 deletions src/utils/lang/__tests__/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// @ts-nocheck
import vm from 'vm';
import {
startsWith,
endsWith,
Expand Down Expand Up @@ -252,6 +253,25 @@ test('LANG UTILS / isObject', () => {
// Object.create(null) creates an object with no prototype which may be tricky to handle. Filtering that out too.
expect(isObject(Object.create(null))).toBe(false); // Should return false for anything that is not a plain object.

// validate on a different VM context
const ctx = vm.createContext({ isObject });
vm.runInContext(`
var positives =
isObject({}) &&
isObject({ a: true }) &&
isObject(new Object()) &&
isObject(Object.create({})) &&
isObject(Object.create(Object.prototype));
var negatives =
isObject([]) ||
isObject(() => { }) ||
isObject(null) ||
isObject(undefined) ||
isObject(new Promise(res => res())) ||
isObject(Object.create(null));
`, ctx);
expect(ctx.positives).toBe(true);
expect(ctx.negatives).toBe(false);
});

test('LANG UTILS / uniqueId', () => {
Expand Down
32 changes: 6 additions & 26 deletions src/utils/lang/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,35 +150,15 @@ export function isNaNNumber(val: any): boolean {
return val !== val;
}

function _isObject(o: any) {
return Object.prototype.toString.call(o) === '[object Object]';
}

/**
* Validates if a value is an object created by the Object constructor (plain object).
* Adapted from:
*
* is-plain-object <https://github.com/jonschlinkert/is-plain-object>
*
* Copyright (c) 2014-2017, Jon Schlinkert.
* Released under the MIT License.
* It checks `constructor.name` to avoid false negatives when validating values on a separate VM context, which has its own global built-ins.
*/
export function isObject(o: any): boolean {
if (_isObject(o) === false) return false;

// If has modified constructor
const ctor = o.constructor;
if (ctor === undefined) return false; // `Object.create(null)`

// If has modified prototype
const prot = ctor.prototype;
if (_isObject(prot) === false) return false;

// If constructor does not have an Object-specific method
if (Object.prototype.hasOwnProperty.call(prot, 'isPrototypeOf') === false) return false;

// Most likely a plain Object
return true;
export function isObject(obj: any) {
return obj !== null && typeof obj === 'object' && (
obj.constructor === Object ||
(obj.constructor != null && obj.constructor.name === 'Object')
);
}

/**
Expand Down