Skip to content

Commit

Permalink
[SDKS-7561] Input validation for evaluationcd
Browse files Browse the repository at this point in the history
  • Loading branch information
Emmanuel Zamora committed Sep 27, 2023
1 parent fb7e99f commit 2d9a3a9
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 12 deletions.
1 change: 1 addition & 0 deletions src/logger/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export const STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2 = 223;
export const STREAMING_PARSING_SPLIT_UPDATE = 224;
export const WARN_SPLITS_FILTER_INVALID_SET = 225;
export const WARN_SPLITS_FILTER_LOWERCASE_SET = 226;
export const WARN_FLAGSET_NOT_CONFIGURED = 227;

export const ERROR_ENGINE_COMBINER_IFELSEIF = 300;
export const ERROR_LOGLEVEL_INVALID = 301;
Expand Down
20 changes: 11 additions & 9 deletions src/sdkClient/clientInputValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { IReadinessManager } from '../readiness/types';
import { MaybeThenable } from '../dtos/types';
import { ISettings, SplitIO } from '../types';
import { isStorageSync } from '../trackers/impressionObserver/utils';
import { flagSetsAreValid } from '../utils/settingsValidation/splitFilters';

/**
* Decorator that validates the input before actually executing the client methods.
Expand All @@ -30,21 +31,22 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
/**
* Avoid repeating this validations code
*/
function validateEvaluationParams(maybeKey: SplitIO.SplitKey, maybeFeatureFlagNameOrNames: string | string[] | undefined, maybeAttributes: SplitIO.Attributes | undefined, methodName: string, maybeFlagSetNameOrNames?: string | string[] | undefined) {
function validateEvaluationParams(maybeKey: SplitIO.SplitKey, maybeFeatureFlagNameOrNames: string | string[] | undefined, maybeAttributes: SplitIO.Attributes | undefined, methodName: string, maybeFlagSetNameOrNames?: string[] | undefined) {
const multi = startsWith(methodName, 'getTreatments');
const key = validateKey(log, maybeKey, methodName);
let splitOrSplits: string | string[] | false = false;
let flagSetOrFlagSets: string[] = [];
if (maybeFeatureFlagNameOrNames) {
splitOrSplits = multi ? validateSplits(log, maybeFeatureFlagNameOrNames, methodName) : validateSplit(log, maybeFeatureFlagNameOrNames, methodName);
}
const attributes = validateAttributes(log, maybeAttributes, methodName);
const isNotDestroyed = validateIfNotDestroyed(log, readinessManager, methodName);
// @todo maybeFlagSets validation method
const flagSetOrFlagSets = maybeFlagSetNameOrNames;

if (maybeFlagSetNameOrNames) {
flagSetOrFlagSets = flagSetsAreValid(log, methodName, maybeFlagSetNameOrNames, settings.sync.__splitFiltersValidation.groupedFilters.bySet);
}
validateIfOperational(log, readinessManager, methodName);

const valid = isNotDestroyed && key && (splitOrSplits || flagSetOrFlagSets) && attributes !== false;
const valid = isNotDestroyed && key && (splitOrSplits || flagSetOrFlagSets.length > 0) && attributes !== false;

return {
valid,
Expand Down Expand Up @@ -126,20 +128,20 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
}

function getTreatmentsByFlagSet(maybeKey: SplitIO.SplitKey, maybeFlagSet: string, maybeAttributes?: SplitIO.Attributes) {
const params = validateEvaluationParams(maybeKey, undefined, maybeAttributes, 'getTreatmentsByFlagSet', maybeFlagSet);
const params = validateEvaluationParams(maybeKey, undefined, maybeAttributes, 'getTreatmentsByFlagSet', [maybeFlagSet]);

if (params.valid) {
return client.getTreatmentsByFlagSet(params.key as SplitIO.SplitKey, params.flagSetOrFlagSets as string, params.attributes as SplitIO.Attributes | undefined);
return client.getTreatmentsByFlagSet(params.key as SplitIO.SplitKey, params.flagSetOrFlagSets[0] as string, params.attributes as SplitIO.Attributes | undefined);
} else {
return wrapResult({});
}
}

function getTreatmentsWithConfigByFlagSet(maybeKey: SplitIO.SplitKey, maybeFlagSet: string, maybeAttributes?: SplitIO.Attributes) {
const params = validateEvaluationParams(maybeKey, undefined, maybeAttributes, 'getTreatmentsWithConfigByFlagSet', maybeFlagSet);
const params = validateEvaluationParams(maybeKey, undefined, maybeAttributes, 'getTreatmentsWithConfigByFlagSet', [maybeFlagSet]);

if (params.valid) {
return client.getTreatmentsWithConfigByFlagSet(params.key as SplitIO.SplitKey, params.flagSetOrFlagSets as string, params.attributes as SplitIO.Attributes | undefined);
return client.getTreatmentsWithConfigByFlagSet(params.key as SplitIO.SplitKey, params.flagSetOrFlagSets[0] as string, params.attributes as SplitIO.Attributes | undefined);
} else {
return wrapResult({});
}
Expand Down
44 changes: 42 additions & 2 deletions src/utils/settingsValidation/__tests__/splitFilters.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { STANDALONE_MODE, CONSUMER_MODE } from '../../constants';
import { splitFilters, queryStrings, groupedFilters } from '../../../__tests__/mocks/fetchSpecificSplits';

// Test target
import { validateSplitFilters } from '../splitFilters';
import { SETTINGS_SPLITS_FILTER, ERROR_INVALID, ERROR_EMPTY_ARRAY, WARN_SPLITS_FILTER_IGNORED, WARN_SPLITS_FILTER_INVALID, WARN_SPLITS_FILTER_EMPTY, WARN_TRIMMING, ERROR_SPLITS_FILTER_NAME_AND_SET, WARN_SPLITS_FILTER_INVALID_SET, WARN_SPLITS_FILTER_LOWERCASE_SET } from '../../../logger/constants';
import { flagSetsAreValid, validateSplitFilters } from '../splitFilters';
import { SETTINGS_SPLITS_FILTER, ERROR_INVALID, ERROR_EMPTY_ARRAY, WARN_SPLITS_FILTER_IGNORED, WARN_SPLITS_FILTER_INVALID, WARN_SPLITS_FILTER_EMPTY, WARN_TRIMMING, ERROR_SPLITS_FILTER_NAME_AND_SET, WARN_SPLITS_FILTER_INVALID_SET, WARN_SPLITS_FILTER_LOWERCASE_SET, WARN_FLAGSET_NOT_CONFIGURED } from '../../../logger/constants';

describe('validateSplitFilters', () => {

Expand Down Expand Up @@ -128,4 +128,44 @@ describe('validateSplitFilters', () => {
expect(loggerMock.warn.mock.calls.length).toEqual(12);
expect(loggerMock.error.mock.calls.length).toEqual(3);
});

test('flagSetsAreValid - Flag set validation for evaluations', () => {

let flagSetsFilter = ['set_1', 'set_2'];

// empty array
expect(flagSetsAreValid(loggerMock, 'test_method', [], flagSetsFilter)).toEqual([]);

// must start with a letter or number
expect(flagSetsAreValid(loggerMock, 'test_method', ['_set_1'], flagSetsFilter)).toEqual([]);
expect(loggerMock.warn.mock.calls[0]).toEqual([WARN_SPLITS_FILTER_INVALID_SET, ['_set_1', regexp, '_set_1']]);

// can contain _a-z0-9
expect(flagSetsAreValid(loggerMock, 'test_method', ['set*1'], flagSetsFilter)).toEqual([]);
expect(loggerMock.warn.mock.calls[1]).toEqual([WARN_SPLITS_FILTER_INVALID_SET, ['set*1', regexp, 'set*1']]);

// have a max length of 50 characters
const longName = '1234567890_1234567890_1234567890_1234567890_1234567890';
expect(flagSetsAreValid(loggerMock, 'test_method', [longName], flagSetsFilter)).toEqual([]);
expect(loggerMock.warn.mock.calls[2]).toEqual([WARN_SPLITS_FILTER_INVALID_SET, [longName, regexp, longName]]);

// both set names invalid -> empty list & warn
expect(flagSetsAreValid(loggerMock, 'test_method', ['set*1','set*3'], flagSetsFilter)).toEqual([]);
expect(loggerMock.warn.mock.calls[3]).toEqual([WARN_SPLITS_FILTER_INVALID_SET, ['set*1', regexp, 'set*1']]);
expect(loggerMock.warn.mock.calls[4]).toEqual([WARN_SPLITS_FILTER_INVALID_SET, ['set*3', regexp, 'set*3']]);

// only set_1 is valid => [set_1] & warn
expect(flagSetsAreValid(loggerMock, 'test_method', ['set_1','set*3'], flagSetsFilter)).toEqual(['set_1']);
expect(loggerMock.warn.mock.calls[5]).toEqual([WARN_SPLITS_FILTER_INVALID_SET, ['set*3', regexp, 'set*3']]);

// set_3 not included in configuration but set_1 included => [set_1] & warn
expect(flagSetsAreValid(loggerMock, 'test_method', ['set_1','set_3'], flagSetsFilter)).toEqual(['set_1']);
expect(loggerMock.warn.mock.calls[6]).toEqual([WARN_FLAGSET_NOT_CONFIGURED, ['set_3']]);

// set_3 not included in configuration => [] & warn
expect(flagSetsAreValid(loggerMock, 'test_method', ['set_3'], flagSetsFilter)).toEqual([]);
expect(loggerMock.warn.mock.calls[7]).toEqual([WARN_FLAGSET_NOT_CONFIGURED, ['set_3']]);

});

});
20 changes: 19 additions & 1 deletion src/utils/settingsValidation/splitFilters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { validateSplits } from '../inputValidation/splits';
import { ISplitFiltersValidation } from '../../dtos/types';
import { SplitIO } from '../../types';
import { ILogger } from '../../logger/types';
import { WARN_SPLITS_FILTER_IGNORED, WARN_SPLITS_FILTER_EMPTY, WARN_SPLITS_FILTER_INVALID, SETTINGS_SPLITS_FILTER, LOG_PREFIX_SETTINGS, ERROR_SPLITS_FILTER_NAME_AND_SET, WARN_SPLITS_FILTER_LOWERCASE_SET, WARN_SPLITS_FILTER_INVALID_SET } from '../../logger/constants';
import { WARN_SPLITS_FILTER_IGNORED, WARN_SPLITS_FILTER_EMPTY, WARN_SPLITS_FILTER_INVALID, SETTINGS_SPLITS_FILTER, LOG_PREFIX_SETTINGS, ERROR_SPLITS_FILTER_NAME_AND_SET, WARN_SPLITS_FILTER_LOWERCASE_SET, WARN_SPLITS_FILTER_INVALID_SET, ERROR_EMPTY_ARRAY, WARN_FLAGSET_NOT_CONFIGURED } from '../../logger/constants';
import { objectAssign } from '../lang/objectAssign';
import { find, uniq } from '../lang';

Expand Down Expand Up @@ -187,3 +187,21 @@ export function validateSplitFilters(log: ILogger, maybeSplitFilters: any, mode:

return res;
}

export function flagSetsAreValid(log: ILogger, method: string, flagSets: string[], flagSetsInConfig: string[]): string[] {
let toReturn: string[] = [];
if (flagSets.length === 0) {
log.error(ERROR_EMPTY_ARRAY, [method, 'flagSets']);
return toReturn;
}
const sets = validateSplits(log, flagSets, method, 'flag sets', 'flag set');
toReturn = sets ? sanitizeFlagSets(log, sets) : [];
return toReturn.filter(flagSet => {
if (flagSetsInConfig.indexOf(flagSet) > -1) {
return true;
}
log.warn(WARN_FLAGSET_NOT_CONFIGURED, [flagSet]);
return false;
});

}

0 comments on commit 2d9a3a9

Please sign in to comment.