Skip to content

Commit caf6640

Browse files
Rename validation utils
AI-Session-Id: 32df18f1-ed0a-4979-bd0d-eb46df9d5c91 AI-Tool: claude-code AI-Model: unknown
1 parent 7f31beb commit caf6640

12 files changed

Lines changed: 217 additions & 218 deletions

File tree

src/sdkClient/clientInputValidation.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import {
44
validateEventValue,
55
validateEventProperties,
66
validateKey,
7-
validateSplit,
8-
validateSplits,
7+
validateDefinition,
8+
validateDefinitions,
99
validateTrafficType,
1010
validateIfNotDestroyed,
1111
validateIfReadyFromCache,
@@ -39,8 +39,8 @@ export function clientInputValidationDecorator<TClient extends SplitIO.IClient |
3939
const nameOrNames = methodName.indexOf('ByFlagSet') > -1 ?
4040
validateFlagSets(log, methodName, maybeNameOrNames as string[], settings.sync.__splitFiltersValidation.groupedFilters.bySet) :
4141
startsWith(methodName, GET_TREATMENTS) ?
42-
validateSplits(log, maybeNameOrNames, methodName) :
43-
validateSplit(log, maybeNameOrNames, methodName);
42+
validateDefinitions(log, maybeNameOrNames, methodName) :
43+
validateDefinition(log, maybeNameOrNames, methodName);
4444

4545
const attributes = validateAttributes(log, maybeAttributes, methodName);
4646
const isNotDestroyed = validateIfNotDestroyed(log, readinessManager, methodName);

src/sdkManager/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { objectAssign } from '../utils/lang/objectAssign';
22
import { thenable } from '../utils/promise/thenable';
33
import { find } from '../utils/lang';
4-
import { validateSplit, validateDefinitionExistence, validateIfOperational } from '../utils/inputValidation';
4+
import { validateDefinition, validateDefinitionExistence, validateIfOperational } from '../utils/inputValidation';
55
import { ISplitsCacheAsync, ISplitsCacheSync } from '../storages/types';
66
import { ISdkReadinessManager } from '../readiness/types';
77
import { IDefinition } from '../dtos/types';
@@ -65,7 +65,7 @@ export function sdkManagerFactory<TSplitCache extends ISplitsCacheSync | ISplits
6565
* Get the feature flag object corresponding to the given feature flag name if valid
6666
*/
6767
split(featureFlagName: string) {
68-
const splitName = validateSplit(log, featureFlagName, SPLIT_FN_LABEL);
68+
const splitName = validateDefinition(log, featureFlagName, SPLIT_FN_LABEL);
6969
if (!validateIfOperational(log, readinessManager, SPLIT_FN_LABEL) || !splitName) {
7070
return isAsync ? Promise.resolve(null) : null;
7171
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { ERROR_INVALID, ERROR_NULL, ERROR_EMPTY, WARN_TRIMMING } from '../../../logger/constants';
2+
import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock';
3+
4+
import { validateDefinition } from '../definition';
5+
6+
const invalidDefinitions = [
7+
{ definition: [], msg: ERROR_INVALID },
8+
{ definition: () => { }, msg: ERROR_INVALID },
9+
{ definition: Object.create({}), msg: ERROR_INVALID },
10+
{ definition: {}, msg: ERROR_INVALID },
11+
{ definition: true, msg: ERROR_INVALID },
12+
{ definition: false, msg: ERROR_INVALID },
13+
{ definition: 10, msg: ERROR_INVALID },
14+
{ definition: 0, msg: ERROR_INVALID },
15+
{ definition: NaN, msg: ERROR_INVALID },
16+
{ definition: Infinity, msg: ERROR_INVALID },
17+
{ definition: null, msg: ERROR_NULL },
18+
{ definition: undefined, msg: ERROR_NULL },
19+
{ definition: new Promise(res => res), msg: ERROR_INVALID },
20+
{ definition: Symbol('asd'), msg: ERROR_INVALID },
21+
{ definition: '', msg: ERROR_EMPTY }
22+
];
23+
24+
const trimmableDefinitions = [
25+
' splitName ',
26+
'split_name2 \n ',
27+
' split_name3'
28+
];
29+
30+
describe('INPUT VALIDATION for definition name', () => {
31+
32+
afterEach(() => { loggerMock.mockClear(); });
33+
34+
test('Should return the provided definition name if it is a valid string without logging any errors', () => {
35+
expect(validateDefinition(loggerMock, 'definitionName', 'some_method')).toBe('definitionName');
36+
expect(loggerMock.error.mock.calls[0]).not.toEqual('some_method');
37+
expect(validateDefinition(loggerMock, 'definition_name', 'some_method')).toBe('definition_name');
38+
expect(loggerMock.error.mock.calls[0]).not.toEqual('some_method');
39+
expect(validateDefinition(loggerMock, 'A_definition-name_29', 'some_method')).toBe('A_definition-name_29');
40+
expect(loggerMock.error.mock.calls[0]).not.toEqual('some_method');
41+
42+
expect(loggerMock.warn).not.toBeCalled();
43+
});
44+
45+
test('Should trim definition name if it is a valid string with trimmable spaces and log a warning', () => {
46+
for (let i = 0; i < trimmableDefinitions.length; i++) {
47+
const trimmableDefinition = trimmableDefinitions[i];
48+
expect(validateDefinition(loggerMock, trimmableDefinition, 'some_method')).toBe(trimmableDefinition.trim());
49+
expect(loggerMock.warn).toBeCalledWith(WARN_TRIMMING, ['some_method', 'feature flag name', trimmableDefinition]);
50+
51+
loggerMock.warn.mockClear();
52+
}
53+
54+
expect(loggerMock.error).not.toBeCalled();
55+
});
56+
57+
test('Should return false and log error if definition name is not a valid string', () => {
58+
for (let i = 0; i < invalidDefinitions.length; i++) {
59+
const invalidValue = invalidDefinitions[i]['definition'];
60+
// @ts-ignore
61+
const expectedLog = invalidDefinitions[i]['msg'];
62+
63+
expect(validateDefinition(loggerMock, invalidValue, 'test_method')).toBe(false);
64+
expect(loggerMock.error).toBeCalledWith(expectedLog, ['test_method', 'feature flag name']);
65+
66+
loggerMock.error.mockClear();
67+
}
68+
69+
expect(loggerMock.warn).not.toBeCalled();
70+
});
71+
});
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import uniq from 'lodash/uniq';
2+
import startsWith from 'lodash/startsWith';
3+
4+
// mocks sdkLogger
5+
import { loggerMock } from '../../../logger/__tests__/sdkLogger.mock';
6+
import { ERROR_EMPTY_ARRAY } from '../../../logger/constants';
7+
8+
// mocks validateDefinition
9+
jest.mock('../definition');
10+
import { validateDefinition } from '../definition';
11+
const validateDefinitionMock = validateDefinition as jest.Mock;
12+
validateDefinitionMock.mockImplementation((_, maybeDefinition) => maybeDefinition);
13+
14+
// test target
15+
import { validateDefinitions } from '../definitions';
16+
17+
const invalidDefinitions = [
18+
[],
19+
{},
20+
Object.create({}),
21+
() => { },
22+
false,
23+
true,
24+
5,
25+
'something',
26+
NaN,
27+
-Infinity,
28+
new Promise(res => res),
29+
Symbol('asd'),
30+
null,
31+
undefined,
32+
NaN
33+
];
34+
35+
describe('INPUT VALIDATION for definition names', () => {
36+
37+
afterEach(() => {
38+
loggerMock.mockClear();
39+
validateDefinitionMock.mockClear();
40+
});
41+
42+
test('Should return the provided array if it is a valid definition names array without logging any errors', () => {
43+
const validArr = ['definitionName1', 'definition_name_2', 'definition-name-3'];
44+
45+
expect(validateDefinitions(loggerMock, validArr, 'some_method')).toEqual(validArr);
46+
expect(validateDefinitionMock).toBeCalledTimes(validArr.length);
47+
expect(loggerMock.error).not.toBeCalled();
48+
49+
expect(loggerMock.warn).not.toBeCalled();
50+
});
51+
52+
test('Should return the provided array if it is a valid definition names array removing duplications, without logging any errors', () => {
53+
const validArr = ['definition_name', 'definition_name', 'definition-name'];
54+
55+
expect(validateDefinitions(loggerMock, validArr, 'some_method')).toEqual(uniq(validArr));
56+
expect(validateDefinitionMock).toBeCalledTimes(validArr.length);
57+
expect(loggerMock.error).not.toBeCalled();
58+
59+
expect(loggerMock.warn).not.toBeCalled();
60+
});
61+
62+
test('Should return false and log an error for the array if it is invalid', () => {
63+
for (let i = 0; i < invalidDefinitions.length; i++) {
64+
expect(validateDefinitions(loggerMock, invalidDefinitions[i], 'test_method')).toBe(false);
65+
expect(loggerMock.error).toBeCalledWith(ERROR_EMPTY_ARRAY, ['test_method', 'feature flag names']);
66+
expect(validateDefinitionMock).not.toBeCalled();
67+
68+
loggerMock.error.mockClear();
69+
}
70+
71+
expect(loggerMock.warn).not.toBeCalled();
72+
});
73+
74+
test('Should strip out any invalid value from the array', () => {
75+
validateDefinitionMock.mockImplementation((_, value) => startsWith(value, 'invalid') ? false : value);
76+
const myArr = ['valid_name', 'invalid_name', 'invalid_val_2', 'something_valid'];
77+
78+
expect(validateDefinitions(loggerMock, myArr, 'test_method')).toEqual(['valid_name', 'something_valid']);
79+
80+
for (let i = 0; i < myArr.length; i++) {
81+
expect(validateDefinitionMock.mock.calls[i]).toEqual([loggerMock, myArr[i], 'test_method', 'feature flag name']);
82+
}
83+
84+
expect(loggerMock.error).not.toBeCalled();
85+
expect(loggerMock.warn).not.toBeCalled();
86+
});
87+
});

src/utils/inputValidation/__tests__/split.spec.ts

Lines changed: 0 additions & 71 deletions
This file was deleted.

src/utils/inputValidation/__tests__/splits.spec.ts

Lines changed: 0 additions & 88 deletions
This file was deleted.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { ERROR_NULL, ERROR_INVALID, WARN_TRIMMING, ERROR_EMPTY } from '../../logger/constants';
2+
import { ILogger } from '../../logger/types';
3+
import { isString } from '../lang';
4+
5+
// include BOM and nbsp
6+
const TRIMMABLE_SPACES_REGEX = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/;
7+
8+
export function validateDefinition(log: ILogger, maybeDefinition: any, method: string, item = 'feature flag name'): string | false {
9+
if (maybeDefinition == undefined) { // eslint-disable-line eqeqeq
10+
log.error(ERROR_NULL, [method, item]);
11+
} else if (!isString(maybeDefinition)) {
12+
log.error(ERROR_INVALID, [method, item]);
13+
} else {
14+
if (TRIMMABLE_SPACES_REGEX.test(maybeDefinition)) {
15+
log.warn(WARN_TRIMMING, [method, item, maybeDefinition]);
16+
maybeDefinition = maybeDefinition.trim();
17+
}
18+
19+
if (maybeDefinition.length > 0) {
20+
return maybeDefinition;
21+
} else {
22+
log.error(ERROR_EMPTY, [method, item]);
23+
}
24+
}
25+
26+
return false;
27+
}

0 commit comments

Comments
 (0)