UNPKG

@splitsoftware/splitio-commons

Version:
195 lines (194 loc) 8.37 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.validateFlagSets = exports.validateSplitFilters = void 0; var splits_1 = require("../inputValidation/splits"); var constants_1 = require("../../logger/constants"); var objectAssign_1 = require("../lang/objectAssign"); var lang_1 = require("../lang"); var mode_1 = require("./mode"); // Split filters metadata. // Ordered according to their precedency when forming the filter query string: `&names=<values>&prefixes=<values>` var FILTERS_METADATA = [ { type: 'bySet', maxLength: 50, queryParam: 'sets=' }, { type: 'byName', maxLength: 400, queryParam: 'names=' }, { type: 'byPrefix', maxLength: 50, queryParam: 'prefixes=' } ]; var VALID_FLAGSET_REGEX = /^[a-z0-9][_a-z0-9]{0,49}$/; var CAPITAL_LETTERS_REGEX = /[A-Z]/; /** * Validates that the given value is a valid filter type */ function validateFilterType(maybeFilterType) { return FILTERS_METADATA.some(function (filterMetadata) { return filterMetadata.type === maybeFilterType; }); } /** * Validate, deduplicate and sort a given list of filter values. * * @param type - filter type string used for log messages * @param values - list of values to validate, deduplicate and sort * @param maxLength - max length allowed for the list of values * @returns list of valid, unique and alphabetically sorted non-empty strings. The list is empty if `values` param is not a non-empty array or all its values are invalid. * * @throws Error if the sanitized list exceeds the length indicated by `maxLength` */ function validateSplitFilter(log, type, values, maxLength) { // validate and remove invalid and duplicated values var result = (0, splits_1.validateSplits)(log, values, constants_1.LOG_PREFIX_SETTINGS, type + " filter", type + " filter value"); if (result) { if (type === 'bySet') { result = sanitizeFlagSets(log, result, constants_1.LOG_PREFIX_SETTINGS); } // 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."); // sort values result.sort(); } return result || []; // returns empty array if `result` is `false` } /** * Returns a string representing the URL encoded query component of /splitChanges URL. * * The possible formats of the query string are: * - null: if all filters are empty * - '&names=<comma-separated-values>': if only `byPrefix` filter is undefined * - '&prefixes=<comma-separated-values>': if only `byName` filter is undefined * - '&names=<comma-separated-values>&prefixes=<comma-separated-values>': if no one is undefined * * @param groupedFilters - object of filters. Each filter must be a list of valid, unique and ordered string values. * @returns null or string with the `split filter query` component of the URL. */ function queryStringBuilder(groupedFilters) { var queryParams = []; FILTERS_METADATA.forEach(function (_a) { var type = _a.type, queryParam = _a.queryParam; var filter = groupedFilters[type]; if (filter.length > 0) queryParams.push(queryParam + filter.map(function (value) { return encodeURIComponent(value); }).join(',')); }); return queryParams.length > 0 ? '&' + queryParams.join('&') : null; } /** * Sanitizes set names list taking into account: * - It should be lowercase * - Must adhere the following regular expression `/^[a-z0-9][_a-z0-9]{0,49}$/` that means * - must start with a letter or number * - Be in lowercase * - Be alphanumeric * - have a max length of 50 characters * * @returns sanitized list of set names */ function sanitizeFlagSets(log, flagSets, method) { var sanitizedSets = flagSets .map(function (flagSet) { if (CAPITAL_LETTERS_REGEX.test(flagSet)) { log.warn(constants_1.WARN_LOWERCASE_FLAGSET, [method, flagSet]); flagSet = flagSet.toLowerCase(); } return flagSet; }) .filter(function (flagSet) { if (!VALID_FLAGSET_REGEX.test(flagSet)) { log.warn(constants_1.WARN_INVALID_FLAGSET, [method, flagSet, VALID_FLAGSET_REGEX, flagSet]); return false; } if (typeof flagSet !== 'string') return false; return true; }); return (0, lang_1.uniq)(sanitizedSets); } function configuredFilter(validFilters, filterType) { return (0, lang_1.find)(validFilters, function (filter) { return filter.type === filterType && filter.values.length > 0; }); } /** * Validates `splitFilters` configuration object and parses it into a query string for filtering splits on `/splitChanges` fetch. * * @param log - logger * @param maybeSplitFilters - split filters configuration param provided by the user * @param mode - settings mode * @returns it returns an object with the following properties: * - `validFilters`: the validated `splitFilters` configuration object defined by the user. * - `queryString`: the parsed split filter query. it is null if all filters are invalid or all values in filters are invalid. * - `groupedFilters`: list of values grouped by filter type. * * @throws Error if the some of the grouped list of values per filter exceeds the max allowed length */ function validateSplitFilters(log, maybeSplitFilters, mode) { // Validation result schema var res = { validFilters: [], queryString: null, groupedFilters: { bySet: [], byName: [], byPrefix: [] }, }; // do nothing if `splitFilters` param is not a non-empty array or mode is not STANDALONE if (!maybeSplitFilters) return res; // Warn depending on the mode if ((0, mode_1.isConsumerMode)(mode)) { log.warn(constants_1.WARN_SPLITS_FILTER_IGNORED); return res; } // Check collection type if (!Array.isArray(maybeSplitFilters) || maybeSplitFilters.length === 0) { log.warn(constants_1.WARN_SPLITS_FILTER_EMPTY); return res; } // Validate filters and group their values by filter type inside `groupedFilters` object res.validFilters = maybeSplitFilters.filter(function (filter, index) { if (filter && validateFilterType(filter.type) && Array.isArray(filter.values)) { res.groupedFilters[filter.type] = res.groupedFilters[filter.type].concat(filter.values); return true; } else { log.warn(constants_1.WARN_SPLITS_FILTER_INVALID, [index]); } return false; }); // By filter type, remove invalid and duplicated values and order them FILTERS_METADATA.forEach(function (_a) { var type = _a.type, maxLength = _a.maxLength; if (res.groupedFilters[type].length > 0) res.groupedFilters[type] = validateSplitFilter(log, type, res.groupedFilters[type], maxLength); }); var setFilter = configuredFilter(res.validFilters, 'bySet'); // Clean all filters if set filter is present if (setFilter) { if (configuredFilter(res.validFilters, 'byName') || configuredFilter(res.validFilters, 'byPrefix')) log.error(constants_1.ERROR_SETS_FILTER_EXCLUSIVE); (0, objectAssign_1.objectAssign)(res.groupedFilters, { byName: [], byPrefix: [] }); } // build query string res.queryString = queryStringBuilder(res.groupedFilters); log.debug(constants_1.SETTINGS_SPLITS_FILTER, [res.queryString]); return res; } exports.validateSplitFilters = validateSplitFilters; function validateFlagSets(log, method, flagSets, flagSetsInConfig) { var sets = (0, splits_1.validateSplits)(log, flagSets, method, 'flag sets', 'flag set'); var toReturn = sets ? sanitizeFlagSets(log, sets, method) : []; if (flagSetsInConfig.length > 0) { toReturn = toReturn.filter(function (flagSet) { if (flagSetsInConfig.indexOf(flagSet) > -1) { return true; } log.warn(constants_1.WARN_FLAGSET_NOT_CONFIGURED, [method, flagSet]); return false; }); } return toReturn.length ? toReturn : false; } exports.validateFlagSets = validateFlagSets;