From 7eaaac8fb272c5ddf95c79c203face6999381830 Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Thu, 24 Aug 2023 21:21:15 -0300 Subject: [PATCH 1/2] [SDKS-7463] Add new filter --- package-lock.json | 4 +- package.json | 2 +- src/__tests__/mocks/fetchSpecificSplits.ts | 82 +++++++++++++++---- src/logger/constants.ts | 3 + src/logger/messages/warn.ts | 5 +- .../inLocalStorage/SplitsCacheInLocal.ts | 2 +- src/types.ts | 2 +- .../__tests__/settings.mocks.ts | 2 +- .../__tests__/splitFilters.spec.ts | 59 +++++++++++-- src/utils/settingsValidation/splitFilters.ts | 64 ++++++++++++++- 10 files changed, 194 insertions(+), 31 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8b4a82b5..b345dd22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@splitsoftware/splitio-commons", - "version": "1.9.0", + "version": "1.9.1-rc.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@splitsoftware/splitio-commons", - "version": "1.9.0", + "version": "1.9.1-rc.0", "license": "Apache-2.0", "dependencies": { "tslib": "^2.3.1" diff --git a/package.json b/package.json index 1e0e5d5a..543c6582 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@splitsoftware/splitio-commons", - "version": "1.9.0", + "version": "1.9.1-rc.0", "description": "Split Javascript SDK common components", "main": "cjs/index.js", "module": "esm/index.js", diff --git a/src/__tests__/mocks/fetchSpecificSplits.ts b/src/__tests__/mocks/fetchSpecificSplits.ts index 8f9098c3..852f768e 100644 --- a/src/__tests__/mocks/fetchSpecificSplits.ts +++ b/src/__tests__/mocks/fetchSpecificSplits.ts @@ -10,6 +10,13 @@ const valuesExamples = [ ['p0', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9', 'p10', 'p11', 'p12', 'p13', 'p14', 'p15', 'p16', 'p17', 'p18', 'p19', 'p20', 'p21', 'p22', 'p23', 'p24', 'p25', 'p26', 'p27', 'p28', 'p29', 'p30', 'p31', 'p32', 'p33', 'p34', 'p35', 'p36', 'p37', 'p38', 'p39', 'p40', 'p41', 'p42', 'p43', 'p44', 'p45', 'p46', 'p47', 'p48', 'p49', 'p50'], ['__ш', '__a', '%', '%25', ' __ш ', '% '], // to test that we order before encoding: '__a' < '__ш' but encodeURIComponent('__a') > encodeURIComponent('__ш') ['%', '%25', '__a', '__ш'], // [7] ordered and deduplicated + // flagSets examples + [' set_1','set_3 ',' set_a ','set_2','set_c','set_b'], // [9] trim + ['set_1','set_2','set_3','set_a','set_b','set_c'], // [10] sanitized [9] + ['set_ 1','set _3','3set_a','_set_2','seT_c','set_B','set_1234567890_1234567890_234567890_1234567890_1234567890','set_a','set_2'], // [11] lowercase & regexp + ['set_2','set_a','set_b','set_c'], // [12] sanitized [11] + ['set_2','set_a','SET_2','set_a','set_b','set_B','set_1','set_3!'], // [13] dedupe, dedupe with case sensitive + ['set_1','set_2','set_a','set_b'], // [14] sanitized [13] ]; export const splitFilters: SplitIO.SplitFilter[][] = [ @@ -41,39 +48,86 @@ export const splitFilters: SplitIO.SplitFilter[][] = [ ], [ { type: 'byName', values: valuesExamples[7] } - ] + ], + // FlagSet filters + [ // [6] + { type: 'byPrefix', values: valuesExamples[1] }, + { type: 'bySet', values: valuesExamples[9] }, + { type: 'byName', values: valuesExamples[1] } + ], + [ // [7] + { type: 'bySet', values: valuesExamples[11] }, + { type: 'byPrefix', values: [] }, + { type: 'byName', values: valuesExamples[6] } + ], + [ // [8] + { type: 'byPrefix', values: [] }, + { type: 'byName', values: valuesExamples[6] }, + { type: 'bySet', values: valuesExamples[13] } + ], ]; // each entry corresponds to the queryString or exception message of each splitFilters entry export const queryStrings = [ - '&names=abc%C8%A3,abc%C8%A3asd,ausgef%C3%BCllt,%C8%A3abc', - '&prefixes=abc%C8%A3,abc%C8%A3asd,ausgef%C3%BCllt,%C8%A3abc', - '&names=abc%C8%A3,abc%C8%A3asd,ausgef%C3%BCllt,%C8%A3abc&prefixes=abc%C8%A3,abc%C8%A3asd,ausgef%C3%BCllt,%C8%A3abc', - "400 unique values can be specified at most for 'byName' filter. You passed 401. Please consider reducing the amount or using other filter.", - "50 unique values can be specified at most for 'byPrefix' filter. You passed 51. Please consider reducing the amount or using other filter.", - '&names=%25,%2525,__a,__%D1%88', + '&names=abc%C8%A3,abc%C8%A3asd,ausgef%C3%BCllt,%C8%A3abc', // [0] + '&prefixes=abc%C8%A3,abc%C8%A3asd,ausgef%C3%BCllt,%C8%A3abc', // [1] + '&names=abc%C8%A3,abc%C8%A3asd,ausgef%C3%BCllt,%C8%A3abc&prefixes=abc%C8%A3,abc%C8%A3asd,ausgef%C3%BCllt,%C8%A3abc', // [2] + "400 unique values can be specified at most for 'byName' filter. You passed 401. Please consider reducing the amount or using other filter.", // [3] + "50 unique values can be specified at most for 'byPrefix' filter. You passed 51. Please consider reducing the amount or using other filter.", // [4] + '&names=%25,%2525,__a,__%D1%88', // [5] + // FlagSet filters + '&sets=set_1,set_2,set_3,set_a,set_b,set_c', // [6] + '&sets=set_2,set_a,set_b,set_c', // [7] + '&sets=set_1,set_2,set_a,set_b', // [8] ]; // each entry corresponds to a `groupedFilter` object returned by `validateSplitFilter` for each `splitFilters` input. // `groupedFilter` contains valid, unique and ordered values per filter type. // An `undefined` value means that `validateSplitFilter` throws an exception which message value is at `queryStrings`. export const groupedFilters = [ - { + { // [0] + bySet: [], byName: valuesExamples[2], byPrefix: [] }, - { + { // [1] + bySet: [], byName: [], byPrefix: valuesExamples[2] }, - { + { // [2] + bySet: [], byName: valuesExamples[2], byPrefix: valuesExamples[2] }, - undefined, - undefined, - { + undefined, // [3] + undefined, // [4] + { // [5] + bySet: [], byName: valuesExamples[8], byPrefix: [] - } + }, + // FlagSet filters + { // [6] + byName: [], + bySet: valuesExamples[10], + byPrefix: [] + }, + { // [7] + byName: [], + bySet: valuesExamples[12], + byPrefix: [] + }, + { // [8] + byName: [], + bySet: valuesExamples[14], + byPrefix: [] + }, +]; + +export const flagSetValidFilters = [ + undefined, undefined, undefined, undefined, undefined, undefined, + [{ type: 'bySet', values: valuesExamples[9] }], + [{ type: 'bySet', values: valuesExamples[11] }], + [{ type: 'bySet', values: valuesExamples[13] }], ]; diff --git a/src/logger/constants.ts b/src/logger/constants.ts index 75f5fe1c..fcebb0e5 100644 --- a/src/logger/constants.ts +++ b/src/logger/constants.ts @@ -97,6 +97,9 @@ export const WARN_SPLITS_FILTER_EMPTY = 221; export const WARN_SDK_KEY = 222; export const STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2 = 223; export const STREAMING_PARSING_SPLIT_UPDATE = 224; +export const WARN_SPLITS_FILTER_NAME_AND_SET = 225; +export const WARN_SPLITS_FILTER_INVALID_SET = 226; +export const WARN_SPLITS_FILTER_LOWERCASE_SET = 227; export const ERROR_ENGINE_COMBINER_IFELSEIF = 300; export const ERROR_LOGLEVEL_INVALID = 301; diff --git a/src/logger/messages/warn.ts b/src/logger/messages/warn.ts index 9917f862..69aa40ca 100644 --- a/src/logger/messages/warn.ts +++ b/src/logger/messages/warn.ts @@ -27,10 +27,13 @@ export const codesWarn: [number, string][] = codesError.concat([ // initialization / settings validation [c.WARN_INTEGRATION_INVALID, c.LOG_PREFIX_SETTINGS+': %s integration item(s) at settings is invalid. %s'], [c.WARN_SPLITS_FILTER_IGNORED, c.LOG_PREFIX_SETTINGS+': feature flag filters have been configured but will have no effect if mode is not "%s", since synchronization is being deferred to an external tool.'], - [c.WARN_SPLITS_FILTER_INVALID, c.LOG_PREFIX_SETTINGS+': feature flag filter at position %s is invalid. It must be an object with a valid filter type ("byName" or "byPrefix") and a list of "values".'], + [c.WARN_SPLITS_FILTER_INVALID, c.LOG_PREFIX_SETTINGS+': feature flag filter at position %s is invalid. It must be an object with a valid filter type ("bySet", "byName" or "byPrefix") and a list of "values".'], [c.WARN_SPLITS_FILTER_EMPTY, c.LOG_PREFIX_SETTINGS+': feature flag filter configuration must be a non-empty array of filter objects.'], [c.WARN_SDK_KEY, c.LOG_PREFIX_SETTINGS+': You already have %s. We recommend keeping only one instance of the factory at all times (Singleton pattern) and reusing it throughout your application'], [c.STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching MySegments due to an error processing %s notification: %s'], [c.STREAMING_PARSING_SPLIT_UPDATE, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching SplitChanges due to an error processing SPLIT_UPDATE notification: %s'], + [c.WARN_SPLITS_FILTER_NAME_AND_SET, c.LOG_PREFIX_SETTINGS+': names and sets filter cannot be used at the same time. The sdk will proceed using sets filter.'], + [c.WARN_SPLITS_FILTER_INVALID_SET, c.LOG_PREFIX_SETTINGS+': you passed %s, Flag Set must adhere to the regular expressions %s. This means a Flag Set must start with a letter, be in lowercase, alphanumeric and have a max length of 50 characteres. %s was discarded.'], + [c.WARN_SPLITS_FILTER_LOWERCASE_SET, c.LOG_PREFIX_SETTINGS+': Flag Set name %s should be all lowercase - converting string to lowercase.'], ]); diff --git a/src/storages/inLocalStorage/SplitsCacheInLocal.ts b/src/storages/inLocalStorage/SplitsCacheInLocal.ts index 44f4664c..5d827177 100644 --- a/src/storages/inLocalStorage/SplitsCacheInLocal.ts +++ b/src/storages/inLocalStorage/SplitsCacheInLocal.ts @@ -21,7 +21,7 @@ export class SplitsCacheInLocal extends AbstractSplitsCacheSync { * @param {number | undefined} expirationTimestamp * @param {ISplitFiltersValidation} splitFiltersValidation */ - constructor(private readonly log: ILogger, keys: KeyBuilderCS, expirationTimestamp?: number, splitFiltersValidation: ISplitFiltersValidation = { queryString: null, groupedFilters: { byName: [], byPrefix: [] }, validFilters: [] }) { + constructor(private readonly log: ILogger, keys: KeyBuilderCS, expirationTimestamp?: number, splitFiltersValidation: ISplitFiltersValidation = { queryString: null, groupedFilters: { bySet: [], byName: [], byPrefix: [] }, validFilters: [] }) { super(); this.keys = keys; this.splitFiltersValidation = splitFiltersValidation; diff --git a/src/types.ts b/src/types.ts index 4c215404..61daaab5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -709,7 +709,7 @@ export namespace SplitIO { * SplitFilter type. * @typedef {string} SplitFilterType */ - export type SplitFilterType = 'byName' | 'byPrefix'; + export type SplitFilterType = 'byName' | 'byPrefix' | 'bySet'; /** * Defines a feature flag filter, described by a type and list of values. */ diff --git a/src/utils/settingsValidation/__tests__/settings.mocks.ts b/src/utils/settingsValidation/__tests__/settings.mocks.ts index ad8d812a..6a1f1c82 100644 --- a/src/utils/settingsValidation/__tests__/settings.mocks.ts +++ b/src/utils/settingsValidation/__tests__/settings.mocks.ts @@ -77,7 +77,7 @@ export const fullSettings: ISettings = { __splitFiltersValidation: { validFilters: [], queryString: null, - groupedFilters: { byName: [], byPrefix: [] } + groupedFilters: { bySet: [], byName: [], byPrefix: [] } }, enabled: true }, diff --git a/src/utils/settingsValidation/__tests__/splitFilters.spec.ts b/src/utils/settingsValidation/__tests__/splitFilters.spec.ts index 1e23b752..a5a66387 100644 --- a/src/utils/settingsValidation/__tests__/splitFilters.spec.ts +++ b/src/utils/settingsValidation/__tests__/splitFilters.spec.ts @@ -3,18 +3,18 @@ import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock'; import { STANDALONE_MODE, CONSUMER_MODE } from '../../constants'; // Split filter and QueryStrings examples -import { splitFilters, queryStrings, groupedFilters } from '../../../__tests__/mocks/fetchSpecificSplits'; +import { splitFilters, queryStrings, groupedFilters, flagSetValidFilters } 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 } from '../../../logger/constants'; +import { SETTINGS_SPLITS_FILTER, ERROR_INVALID, ERROR_EMPTY_ARRAY, WARN_SPLITS_FILTER_IGNORED, WARN_SPLITS_FILTER_INVALID, WARN_SPLITS_FILTER_EMPTY, WARN_TRIMMING, WARN_SPLITS_FILTER_NAME_AND_SET, WARN_SPLITS_FILTER_INVALID_SET, WARN_SPLITS_FILTER_LOWERCASE_SET } from '../../../logger/constants'; describe('validateSplitFilters', () => { const defaultOutput = { validFilters: [], queryString: null, - groupedFilters: { byName: [], byPrefix: [] } + groupedFilters: { bySet: [], byName: [], byPrefix: [] } }; afterEach(() => { loggerMock.mockClear(); }); @@ -39,13 +39,14 @@ describe('validateSplitFilters', () => { test('Returns object with null queryString, if `splitFilters` contains invalid filters or contains filters with no values or invalid values', () => { const splitFilters: any[] = [ + { type: 'bySet', values: [] }, { type: 'byName', values: [] }, { type: 'byName', values: [] }, { type: 'byPrefix', values: [] }]; const output = { validFilters: [...splitFilters], queryString: null, - groupedFilters: { byName: [], byPrefix: [] } + groupedFilters: { bySet: [], byName: [], byPrefix: [] } }; expect(validateSplitFilters(loggerMock, splitFilters, STANDALONE_MODE)).toEqual(output); // filters without values expect(loggerMock.debug).toBeCalledWith(SETTINGS_SPLITS_FILTER, [null]); @@ -60,9 +61,9 @@ describe('validateSplitFilters', () => { expect(validateSplitFilters(loggerMock, splitFilters, STANDALONE_MODE)).toEqual(output); // some filters are invalid expect(loggerMock.debug.mock.calls).toEqual([[SETTINGS_SPLITS_FILTER, [null]]]); expect(loggerMock.warn.mock.calls).toEqual([ - [WARN_SPLITS_FILTER_INVALID, [3]], // invalid value of `type` property - [WARN_SPLITS_FILTER_INVALID, [4]], // invalid type of `values` property - [WARN_SPLITS_FILTER_INVALID, [5]] // invalid type of `type` property + [WARN_SPLITS_FILTER_INVALID, [4]], // invalid value of `type` property + [WARN_SPLITS_FILTER_INVALID, [5]], // invalid type of `values` property + [WARN_SPLITS_FILTER_INVALID, [6]] // invalid type of `type` property ]); expect(loggerMock.error.mock.calls).toEqual([ @@ -73,7 +74,7 @@ describe('validateSplitFilters', () => { test('Returns object with a queryString, if `splitFilters` contains at least a valid `byName` or `byPrefix` filter with at least a valid value', () => { - for (let i = 0; i < splitFilters.length; i++) { + for (let i = 0; i < 6; i++) { if (groupedFilters[i]) { // tests where validateSplitFilters executes normally const output = { @@ -91,3 +92,45 @@ describe('validateSplitFilters', () => { }); }); + + +describe('validateSetFilter', () => { + + const getOutput = (testIndex: number) => { + return { + // @ts-ignore + validFilters: [...flagSetValidFilters[testIndex]], + queryString: queryStrings[testIndex], + groupedFilters: groupedFilters[testIndex] + }; + }; + + const regexp = /^[a-z][_a-z0-9]{0,49}$/; + + test('Config validations', () => { + // extra spaces trimmed and sorted query output + expect(validateSplitFilters(loggerMock, splitFilters[6], STANDALONE_MODE)).toEqual(getOutput(6)); // trim & sort + expect(loggerMock.warn.mock.calls[0]).toEqual([WARN_TRIMMING, ['settings', 'bySet filter value', ' set_1']]); + expect(loggerMock.warn.mock.calls[1]).toEqual([WARN_TRIMMING, ['settings', 'bySet filter value', 'set_3 ']]); + expect(loggerMock.warn.mock.calls[2]).toEqual([WARN_TRIMMING, ['settings', 'bySet filter value', ' set_a ']]); + expect(loggerMock.warn.mock.calls[3]).toEqual([WARN_SPLITS_FILTER_NAME_AND_SET]); + + expect(validateSplitFilters(loggerMock, splitFilters[7], STANDALONE_MODE)).toEqual(getOutput(7)); // lowercase and regexp + expect(loggerMock.warn.mock.calls[4]).toEqual([WARN_SPLITS_FILTER_LOWERCASE_SET, ['seT_c']]); // lowercase + expect(loggerMock.warn.mock.calls[5]).toEqual([WARN_SPLITS_FILTER_LOWERCASE_SET, ['set_B']]); // lowercase + expect(loggerMock.warn.mock.calls[6]).toEqual([WARN_SPLITS_FILTER_INVALID_SET, ['set_ 1', regexp, 'set_ 1']]); // empty spaces + expect(loggerMock.warn.mock.calls[7]).toEqual([WARN_SPLITS_FILTER_INVALID_SET, ['set _3', regexp, 'set _3']]); // empty spaces + expect(loggerMock.warn.mock.calls[8]).toEqual([WARN_SPLITS_FILTER_INVALID_SET, ['3set_a', regexp, '3set_a']]); // start with a letter + expect(loggerMock.warn.mock.calls[9]).toEqual([WARN_SPLITS_FILTER_INVALID_SET, ['_set_2', regexp, '_set_2']]); // start with a letter + expect(loggerMock.warn.mock.calls[10]).toEqual([WARN_SPLITS_FILTER_INVALID_SET, ['set_1234567890_1234567890_234567890_1234567890_1234567890', regexp, 'set_1234567890_1234567890_234567890_1234567890_1234567890']]); // max of 50 characters + expect(loggerMock.warn.mock.calls[11]).toEqual([WARN_SPLITS_FILTER_NAME_AND_SET]); + + expect(validateSplitFilters(loggerMock, splitFilters[8], STANDALONE_MODE)).toEqual(getOutput(8)); // lowercase and dedupe + expect(loggerMock.warn.mock.calls[12]).toEqual([WARN_SPLITS_FILTER_LOWERCASE_SET, ['SET_2']]); // lowercase + expect(loggerMock.warn.mock.calls[13]).toEqual([WARN_SPLITS_FILTER_LOWERCASE_SET, ['set_B']]); // lowercase + expect(loggerMock.warn.mock.calls[14]).toEqual([WARN_SPLITS_FILTER_INVALID_SET, ['set_3!', regexp, 'set_3!']]); // special character + expect(loggerMock.warn.mock.calls[15]).toEqual([WARN_SPLITS_FILTER_NAME_AND_SET]); + + expect(loggerMock.warn.mock.calls.length).toEqual(16); + }); +}); diff --git a/src/utils/settingsValidation/splitFilters.ts b/src/utils/settingsValidation/splitFilters.ts index eb7b0371..c7f1e957 100644 --- a/src/utils/settingsValidation/splitFilters.ts +++ b/src/utils/settingsValidation/splitFilters.ts @@ -3,11 +3,18 @@ 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 } from '../../logger/constants'; +import { WARN_SPLITS_FILTER_IGNORED, WARN_SPLITS_FILTER_EMPTY, WARN_SPLITS_FILTER_INVALID, SETTINGS_SPLITS_FILTER, LOG_PREFIX_SETTINGS, WARN_SPLITS_FILTER_NAME_AND_SET, WARN_SPLITS_FILTER_LOWERCASE_SET, WARN_SPLITS_FILTER_INVALID_SET } from '../../logger/constants'; +import { objectAssign } from '../lang/objectAssign'; +import { uniq } from '../lang'; // Split filters metadata. // Ordered according to their precedency when forming the filter query string: `&names=&prefixes=` const FILTERS_METADATA = [ + { + type: 'bySet' as SplitIO.SplitFilterType, + maxLength: 50, + queryParam: 'sets=' + }, { type: 'byName' as SplitIO.SplitFilterType, maxLength: 400, @@ -20,6 +27,9 @@ const FILTERS_METADATA = [ } ]; +const VALID_FLAGSET_REGEX = /^[a-z][_a-z0-9]{0,49}$/; +const CAPITAL_LETTERS_REGEX = /[A-Z]/; + /** * Validates that the given value is a valid filter type */ @@ -42,6 +52,11 @@ function validateSplitFilter(log: ILogger, type: SplitIO.SplitFilterType, values let result = validateSplits(log, values, LOG_PREFIX_SETTINGS, `${type} filter`, `${type} filter value`); if (result) { + + if (type === 'bySet') { + result = sanitizeFlagSets(log, result); + } + // check max length if (result.length > maxLength) throw new Error(`${maxLength} unique values can be specified at most for '${type}' filter. You passed ${result.length}. Please consider reducing the amount or using other filter.`); @@ -72,6 +87,43 @@ function queryStringBuilder(groupedFilters: Record 0 ? '&' + queryParams.join('&') : null; } +/** + * Sanitizes set names list taking in account: + * - It should be lowercase + * - Must adhere the following regular expression /^[a-z][_a-z0-9]{0,49}$/ that means + * - must start with a letter + * - Be in lowercase + * - Be alphanumeric + * - have a max length of 50 characteres + * + * @param {ILogger} log + * @param {string[]} flagsets + * @returns sanitized list of set names + */ +function sanitizeFlagSets(log: ILogger, flagsets: string[]) { + let sanitizedSets = flagsets + .map(flagSet => { + if (CAPITAL_LETTERS_REGEX.test(flagSet)){ + log.warn(WARN_SPLITS_FILTER_LOWERCASE_SET,[flagSet]); + flagSet = flagSet.toLowerCase(); + } + return flagSet; + }) + .filter(flagSet => { + if (!VALID_FLAGSET_REGEX.test(flagSet)){ + log.warn(WARN_SPLITS_FILTER_INVALID_SET, [flagSet,VALID_FLAGSET_REGEX,flagSet]); + return false; + } + if (typeof flagSet !== 'string') return false; + return true; + }); + return uniq(sanitizedSets); +} + +function configuredFilter(validFilters: SplitIO.SplitFilter[], filterType: SplitIO.SplitFilterType) { + return validFilters.find(filter => filter.type === filterType && filter.values.length > 0); +} + /** * Validates `splitFilters` configuration object and parses it into a query string for filtering splits on `/splitChanges` fetch. * @@ -90,7 +142,7 @@ export function validateSplitFilters(log: ILogger, maybeSplitFilters: any, mode: const res = { validFilters: [], queryString: null, - groupedFilters: { byName: [], byPrefix: [] } + groupedFilters: { bySet: [], byName: [], byPrefix: [] } } as ISplitFiltersValidation; // do nothing if `splitFilters` param is not a non-empty array or mode is not STANDALONE @@ -122,6 +174,14 @@ export function validateSplitFilters(log: ILogger, maybeSplitFilters: any, mode: if (res.groupedFilters[type].length > 0) res.groupedFilters[type] = validateSplitFilter(log, type, res.groupedFilters[type], maxLength); }); + const setFilter = configuredFilter(res.validFilters, 'bySet'); + // Clean all filters if set filter is present + if (setFilter) { + if (configuredFilter(res.validFilters, 'byName')) log.warn(WARN_SPLITS_FILTER_NAME_AND_SET); + objectAssign(res.groupedFilters, { byName: [], byPrefix: [] }); + res.validFilters = [setFilter]; + } + // build query string res.queryString = queryStringBuilder(res.groupedFilters); log.debug(SETTINGS_SPLITS_FILTER, [res.queryString]); From a42693cd1742d83b2291c23557c78bc1295c6696 Mon Sep 17 00:00:00 2001 From: Emmanuel Zamora Date: Wed, 30 Aug 2023 12:12:40 -0300 Subject: [PATCH 2/2] Fix typo and styles --- src/logger/messages/warn.ts | 4 +-- .../__tests__/splitFilters.spec.ts | 29 ++++++++----------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/logger/messages/warn.ts b/src/logger/messages/warn.ts index 69aa40ca..1a29dd66 100644 --- a/src/logger/messages/warn.ts +++ b/src/logger/messages/warn.ts @@ -34,6 +34,6 @@ export const codesWarn: [number, string][] = codesError.concat([ [c.STREAMING_PARSING_MY_SEGMENTS_UPDATE_V2, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching MySegments due to an error processing %s notification: %s'], [c.STREAMING_PARSING_SPLIT_UPDATE, c.LOG_PREFIX_SYNC_STREAMING + 'Fetching SplitChanges due to an error processing SPLIT_UPDATE notification: %s'], [c.WARN_SPLITS_FILTER_NAME_AND_SET, c.LOG_PREFIX_SETTINGS+': names and sets filter cannot be used at the same time. The sdk will proceed using sets filter.'], - [c.WARN_SPLITS_FILTER_INVALID_SET, c.LOG_PREFIX_SETTINGS+': you passed %s, Flag Set must adhere to the regular expressions %s. This means a Flag Set must start with a letter, be in lowercase, alphanumeric and have a max length of 50 characteres. %s was discarded.'], - [c.WARN_SPLITS_FILTER_LOWERCASE_SET, c.LOG_PREFIX_SETTINGS+': Flag Set name %s should be all lowercase - converting string to lowercase.'], + [c.WARN_SPLITS_FILTER_INVALID_SET, c.LOG_PREFIX_SETTINGS+': you passed %s, flag set must adhere to the regular expressions %s. This means a flag set must start with a letter, be in lowercase, alphanumeric and have a max length of 50 characteres. %s was discarded.'], + [c.WARN_SPLITS_FILTER_LOWERCASE_SET, c.LOG_PREFIX_SETTINGS+': flag set %s should be all lowercase - converting string to lowercase.'], ]); diff --git a/src/utils/settingsValidation/__tests__/splitFilters.spec.ts b/src/utils/settingsValidation/__tests__/splitFilters.spec.ts index a5a66387..73938186 100644 --- a/src/utils/settingsValidation/__tests__/splitFilters.spec.ts +++ b/src/utils/settingsValidation/__tests__/splitFilters.spec.ts @@ -17,6 +17,17 @@ describe('validateSplitFilters', () => { groupedFilters: { bySet: [], byName: [], byPrefix: [] } }; + const getOutput = (testIndex: number) => { + return { + // @ts-ignore + validFilters: [...flagSetValidFilters[testIndex]], + queryString: queryStrings[testIndex], + groupedFilters: groupedFilters[testIndex] + }; + }; + + const regexp = /^[a-z][_a-z0-9]{0,49}$/; + afterEach(() => { loggerMock.mockClear(); }); test('Returns default output with empty values if `splitFilters` is an invalid object or `mode` is not \'standalone\'', () => { @@ -91,23 +102,7 @@ describe('validateSplitFilters', () => { } }); -}); - - -describe('validateSetFilter', () => { - - const getOutput = (testIndex: number) => { - return { - // @ts-ignore - validFilters: [...flagSetValidFilters[testIndex]], - queryString: queryStrings[testIndex], - groupedFilters: groupedFilters[testIndex] - }; - }; - - const regexp = /^[a-z][_a-z0-9]{0,49}$/; - - test('Config validations', () => { + test('Validates flag set filters', () => { // extra spaces trimmed and sorted query output expect(validateSplitFilters(loggerMock, splitFilters[6], STANDALONE_MODE)).toEqual(getOutput(6)); // trim & sort expect(loggerMock.warn.mock.calls[0]).toEqual([WARN_TRIMMING, ['settings', 'bySet filter value', ' set_1']]);