'use strict'; var smob = require('smob'); var ebec = require('ebec'); /* * Copyright (c) 2021-2022. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ // ----------------------------------------------------------- exports.Parameter = void 0; (function(Parameter) { Parameter["FILTERS"] = 'filters'; Parameter["FIELDS"] = 'fields'; Parameter["PAGINATION"] = 'pagination'; Parameter["RELATIONS"] = 'relations'; Parameter["SORT"] = 'sort'; })(exports.Parameter || (exports.Parameter = {})); exports.URLParameter = void 0; (function(URLParameter) { URLParameter["FILTERS"] = 'filter'; URLParameter["FIELDS"] = 'fields'; URLParameter["PAGINATION"] = 'page'; URLParameter["RELATIONS"] = 'include'; URLParameter["SORT"] = 'sort'; })(exports.URLParameter || (exports.URLParameter = {})); // ----------------------------------------------------------- const DEFAULT_ID = '__DEFAULT__'; function buildKeyPath(key, prefix) { if (typeof prefix === 'string') { return `${prefix}.${key}`; } return key; } function flattenToKeyPathArray(input, options, prefix) { options = options || {}; const output = []; if (options.transformer) { const result = options.transformer(input, output, prefix); if (typeof result !== 'undefined' && !!result) { return output; } } if (Array.isArray(input)) { for(let i = 0; i < input.length; i++){ if (options.transformer) { const result = options.transformer(input[i], output, prefix); if (typeof result !== 'undefined' && !!result) { return output; } } if (Array.isArray(input[i])) { for(let j = 0; j < input[i].length; j++){ const key = buildKeyPath(input[i][j], prefix); output.push(key); } continue; } if (typeof input[i] === 'string') { output.push(buildKeyPath(input[i], prefix)); continue; } if (typeof input[i] === 'object') { const keys = Object.keys(input[i]); for(let j = 0; j < keys.length; j++){ const value = buildKeyPath(keys[j], prefix); const data = flattenToKeyPathArray(input[i][keys[j]], options, value); if (data.length === 0) { output.push(value); } else { output.push(...data); } } } } return output; } if (typeof input === 'object' && input !== null) { const keys = Object.keys(input); for(let i = 0; i < keys.length; i++){ const value = buildKeyPath(keys[i], prefix); const data = flattenToKeyPathArray(input[keys[i]], options, value); if (data.length === 0) { output.push(value); } else { output.push(...data); } } return output; } if (typeof input === 'string') { const value = buildKeyPath(input, prefix); output.push(value); return output; } return output; } function groupArrayByKeyPath(input) { const pathItems = {}; for(let i = 0; i < input.length; i++){ const parts = input[i].split('.'); let key; let name; if (parts.length === 1) { key = DEFAULT_ID; name = input[i]; } else { name = parts.pop(); key = parts.join('.'); } if (!Object.prototype.hasOwnProperty.call(pathItems, key)) { pathItems[key] = []; } pathItems[key].push(name); } return pathItems; } /* * Copyright (c) 2021-2022. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ function isObject(item) { return !!item && typeof item === 'object' && !Array.isArray(item); } function hasOwnProperty(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); } function flattenNestedObject(data, options, prefixParts) { options = options || {}; prefixParts = prefixParts || []; let output = {}; if (options.transformer) { const result = options.transformer(data, output, prefixParts.join('.')); if (typeof result !== 'undefined' && !!result) { return output; } } const keys = Object.keys(data); for(let i = 0; i < keys.length; i++){ const key = keys[i]; if (options.transformer) { const result = options.transformer(data[key], output, [ ...prefixParts, key ].join('.')); if (typeof result !== 'undefined' && !!result) { continue; } } if (typeof data[key] === 'object' && data[key]) { output = { ...output, ...flattenNestedObject(data[key], options, [ ...prefixParts, key ]) }; continue; } const destinationKey = [ ...prefixParts, key ].join('.'); if (typeof data[key] === 'boolean' || typeof data[key] === 'string' || typeof data[key] === 'number' || typeof data[key] === 'undefined' || data[key] === null || Array.isArray(data[key])) { output[destinationKey] = data[key]; } } return output; } function applyMapping(name, map, onlyKey) { if (typeof map === 'undefined') { return name; } const keys = Object.keys(map); if (keys.length === 0) { return name; } let parts = name.split('.'); const output = []; let run = true; while(run){ const value = parts.shift(); if (typeof value === 'undefined') { run = false; break; } if (hasOwnProperty(map, value)) { output.push(map[value]); } else { let found = false; const rest = []; const copy = [ ...parts ]; while(copy.length > 0){ const key = [ value, ...copy ].join('.'); if (hasOwnProperty(map, key)) { output.push(map[key]); found = true; break; } else { const last = copy.pop(); if (last) { rest.unshift(last); } } } if (found) { parts = rest; } else { output.push(value); } } } if (onlyKey) { return output.pop() || name; } if (output.length === 0) { return name; } return output.join('.'); } /* * Copyright (c) 2022. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ function parseKey(field) { const parts = field.split('.'); const name = parts.pop(); return { name, path: parts.length > 0 ? parts.join('.') : undefined }; } const merge = smob.createMerger({ clone: true, inPlace: false, array: true, arrayDistinct: true }); function isPathAllowedByRelations(path, includes) { if (typeof path === 'undefined' || typeof includes === 'undefined') { return true; } return includes.some((include)=>include.key === path); } function buildKeyWithPath(name, path) { let details; if (smob.isObject(name)) { details = name; } else { details = { name, path }; } return details.path || path ? `${details.path || path}.${details.name}` : details.name; } function buildURLQueryString(data, withQuestionMark = true) { if (typeof data === 'undefined' || data === null) return ''; // If the data is already a string, return it as-is if (typeof data === 'string') return data; // Create a query array to hold the key/value pairs const query = []; // Loop through the data object const keys = Object.keys(data); for(let i = 0; i < keys.length; i++){ let value = data[keys[i]]; if (smob.isObject(value)) { const valueKeys = Object.keys(value); for(let j = 0; j < valueKeys.length; j++){ let v = value[valueKeys[j]]; if (Array.isArray(v)) { v = v.join(','); } query.push(`${encodeURIComponent(`${keys[i]}[${valueKeys[j]}]`)}=${encodeURIComponent(v)}`); } continue; } if (Array.isArray(value)) { value = value.join(','); } // Encode each key and value, concatenate them into a string, and push them to the array query.push(`${encodeURIComponent(keys[i])}=${encodeURIComponent(value)}`); } // Join each item in the array with a `&` and return the resulting string return (withQuestionMark ? '?' : '') + query.join('&'); } function buildQueryFields(input) { if (typeof input === 'undefined') { return []; } const data = groupArrayByKeyPath(flattenToKeyPathArray(input)); const keys = Object.keys(data); if (keys.length === 1) { return data[keys[0]]; } return data; } function mergeQueryFields(target, source) { if (Array.isArray(target)) { target = groupArrayByKeyPath(target); } if (Array.isArray(source)) { source = groupArrayByKeyPath(source); } const data = merge(target, source); const keys = Object.keys(data); if (keys.length === 1) { return data[keys[0]]; } return data; } /* * Copyright (c) 2022. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ exports.FieldOperator = void 0; (function(FieldOperator) { FieldOperator["INCLUDE"] = '+'; FieldOperator["EXCLUDE"] = '-'; })(exports.FieldOperator || (exports.FieldOperator = {})); /* * Copyright (c) 2023. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ exports.ErrorCode = void 0; (function(ErrorCode) { ErrorCode["NONE"] = 'none'; ErrorCode["INPUT_INVALID"] = 'inputInvalid'; ErrorCode["KEY_INVALID"] = 'keyInvalid'; ErrorCode["KEY_PATH_INVALID"] = 'keyPathInvalid'; ErrorCode["KEY_NOT_ALLOWED"] = 'keyNotAllowed'; ErrorCode["KEY_VALUE_INVALID"] = 'keyValueInvalid'; })(exports.ErrorCode || (exports.ErrorCode = {})); class BaseError extends ebec.BaseError { get code() { return this.getOption('code') || exports.ErrorCode.NONE; } } class BuildError extends BaseError { constructor(message){ if (isObject(message)) { message.message = 'A building error has occurred.'; } super(message || 'A building error has occurred.'); } } class ParseError extends BaseError { static inputInvalid() { return new this({ message: 'The shape of the input is not valid.', code: exports.ErrorCode.INPUT_INVALID }); } static keyNotAllowed(name) { return new this({ message: `The key ${name} is not covered by allowed/default options.`, code: exports.ErrorCode.KEY_NOT_ALLOWED }); } static keyInvalid(key) { return new this({ message: `The key ${key} is invalid.`, code: exports.ErrorCode.KEY_INVALID }); } static keyPathInvalid(key) { return new this({ message: `The key path ${key} is invalid.`, code: exports.ErrorCode.KEY_PATH_INVALID }); } static keyValueInvalid(key) { return new this({ message: `The value of the key ${key} is invalid.`, code: exports.ErrorCode.KEY_VALUE_INVALID }); } constructor(message){ if (isObject(message)) { message.message = message.message || 'A parsing error has occurred.'; } super(message || 'A parsing error has occurred.'); } } class FieldsBuildError extends BuildError { } class FieldsParseError extends ParseError { } function flattenParseAllowedOption(input) { if (typeof input === 'undefined') { return []; } return flattenToKeyPathArray(input); } function isPathCoveredByParseAllowedOption(input, path) { const paths = Array.isArray(path) ? path : [ path ]; const items = flattenToKeyPathArray(input); for(let i = 0; i < items.length; i++){ if (paths.indexOf(items[i]) !== -1) { return true; } } return false; } function buildFieldDomainRecords(data) { if (typeof data === 'undefined') { return {}; } let domainFields = {}; if (Array.isArray(data)) { domainFields[DEFAULT_ID] = data; } else { domainFields = data; } return domainFields; } /* * Copyright (c) 2022. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ function parseFieldsInput(input) { let output = []; if (typeof input === 'string') { output = input.split(','); } else if (Array.isArray(input)) { for(let i = 0; i < input.length; i++){ if (typeof input[i] === 'string') { output.push(input[i]); } } } return output; } /* * Copyright (c) 2022. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ function isValidFieldName(input) { return /^[a-zA-Z_][a-zA-Z0-9_]*$/gu.test(input); } // -------------------------------------------------- function buildReverseRecord(record) { const keys = Object.keys(record); const output = {}; for(let i = 0; i < keys.length; i++){ output[record[keys[i]]] = keys[i]; } return output; } function parseQueryFields(input, options) { options = options || {}; const defaultDomainFields = groupArrayByKeyPath(flattenParseAllowedOption(options.default)); const allowedDomainFields = groupArrayByKeyPath(flattenParseAllowedOption(options.allowed)); const domainFields = merge(defaultDomainFields, allowedDomainFields); let keys = Object.keys(domainFields); // If it is an empty array nothing is allowed if ((typeof options.default !== 'undefined' || typeof options.allowed !== 'undefined') && keys.length === 0) { return []; } let data = { [DEFAULT_ID]: [] }; if (smob.isObject(input)) { data = input; } else if (typeof input === 'string' || Array.isArray(input)) { data = { [DEFAULT_ID]: input }; } else if (options.throwOnFailure) { throw FieldsParseError.inputInvalid(); } options.mapping = options.mapping || {}; const reverseMapping = buildReverseRecord(options.mapping); if (keys.length > 0 && hasOwnProperty(data, DEFAULT_ID)) { data = { [keys[0]]: data[DEFAULT_ID] }; } else { keys = smob.distinctArray([ ...keys, ...Object.keys(data) ]); } const output = []; for(let i = 0; i < keys.length; i++){ const path = keys[i]; if (path !== DEFAULT_ID && !isPathAllowedByRelations(path, options.relations)) { if (options.throwOnFailure) { throw FieldsParseError.keyPathInvalid(path); } continue; } let fields = []; if (hasOwnProperty(data, path)) { fields = parseFieldsInput(data[path]); } else if (hasOwnProperty(reverseMapping, path) && hasOwnProperty(data, reverseMapping[path])) { fields = parseFieldsInput(data[reverseMapping[path]]); } const transformed = { default: [], included: [], excluded: [] }; if (fields.length > 0) { for(let j = 0; j < fields.length; j++){ let operator; const character = fields[j].substring(0, 1); if (character === exports.FieldOperator.INCLUDE) { operator = exports.FieldOperator.INCLUDE; } else if (character === exports.FieldOperator.EXCLUDE) { operator = exports.FieldOperator.EXCLUDE; } if (operator) { fields[j] = fields[j].substring(1); } fields[j] = applyMapping(fields[j], options.mapping, true); let isValid; if (hasOwnProperty(domainFields, path)) { isValid = domainFields[path].indexOf(fields[j]) !== -1; } else { isValid = isValidFieldName(fields[j]); } if (!isValid) { if (options.throwOnFailure) { throw FieldsParseError.keyNotAllowed(fields[j]); } continue; } if (operator === exports.FieldOperator.INCLUDE) { transformed.included.push(fields[j]); } else if (operator === exports.FieldOperator.EXCLUDE) { transformed.excluded.push(fields[j]); } else { transformed.default.push(fields[j]); } } } if (transformed.default.length === 0 && hasOwnProperty(defaultDomainFields, path)) { transformed.default = defaultDomainFields[path]; } if (transformed.included.length === 0 && transformed.default.length === 0 && hasOwnProperty(allowedDomainFields, path)) { transformed.default = allowedDomainFields[path]; } transformed.default = Array.from(new Set([ ...transformed.default, ...transformed.included ])); for(let j = 0; j < transformed.excluded.length; j++){ const index = transformed.default.indexOf(transformed.excluded[j]); if (index !== -1) { transformed.default.splice(index, 1); } } if (transformed.default.length > 0) { for(let j = 0; j < transformed.default.length; j++){ let destPath; if (path !== DEFAULT_ID) { destPath = path; } else if (options.defaultPath) { destPath = options.defaultPath; } output.push({ key: transformed.default[j], ...destPath ? { path: destPath } : {} }); } } } return output; } /* * Copyright (c) 2022-2022. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ exports.FilterComparisonOperator = void 0; (function(FilterComparisonOperator) { FilterComparisonOperator["EQUAL"] = '$eq'; FilterComparisonOperator["NOT_EQUAL"] = '$ne'; FilterComparisonOperator["LIKE"] = '$l'; FilterComparisonOperator["NOT_LIKE"] = '$nl'; FilterComparisonOperator["LESS_THAN_EQUAL"] = '$lte'; FilterComparisonOperator["LESS_THAN"] = '$lt'; FilterComparisonOperator["GREATER_THAN_EQUAL"] = '$gte'; FilterComparisonOperator["GREATER_THAN"] = '$gt'; FilterComparisonOperator["IN"] = '$in'; FilterComparisonOperator["NOT_IN"] = '$nin'; })(exports.FilterComparisonOperator || (exports.FilterComparisonOperator = {})); exports.FilterInputOperatorValue = void 0; (function(FilterInputOperatorValue) { FilterInputOperatorValue["NEGATION"] = '!'; FilterInputOperatorValue["LIKE"] = '~'; FilterInputOperatorValue["LESS_THAN_EQUAL"] = '<='; FilterInputOperatorValue["LESS_THAN"] = '<'; FilterInputOperatorValue["MORE_THAN_EQUAL"] = '>='; FilterInputOperatorValue["MORE_THAN"] = '>'; FilterInputOperatorValue["IN"] = ','; })(exports.FilterInputOperatorValue || (exports.FilterInputOperatorValue = {})); function buildQueryFilters(data) { if (typeof data === 'undefined') { return {}; } return flattenNestedObject(data, { transformer: (input, output, key)=>{ if (typeof input === 'undefined') { output[key] = null; return true; } if (Array.isArray(input)) { // preserve null values const data = []; for(let i = 0; i < input.length; i++){ if (input[i] === null) { input[i] = 'null'; } if (typeof input[i] === 'number') { input[i] = `${input[i]}`; } if (typeof input[i] === 'string') { data.push(input[i]); } } output[key] = data.join(','); return true; } return undefined; } }); } function mergeQueryFilters(target, source) { return merge(target || {}, source || {}); } class FiltersBuildError extends BuildError { } class FiltersParseError extends ParseError { } /* * Copyright (c) 2022. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ function transformFilterValue(input) { if (typeof input === 'string') { input = input.trim(); const lower = input.toLowerCase(); if (lower === 'true') { return true; } if (lower === 'false') { return false; } if (lower === 'null') { return null; } if (input.length === 0) { return input; } const num = Number(input); if (!Number.isNaN(num)) { return num; } const parts = input.split(','); if (parts.length > 1) { return transformFilterValue(parts); } } if (Array.isArray(input)) { for(let i = 0; i < input.length; i++){ input[i] = transformFilterValue(input[i]); } return input.filter((n)=>n === 0 || n === null || !!n); } if (typeof input === 'undefined' || input === null) { return null; } return input; } function matchOperator(key, value, position) { if (typeof value === 'string') { switch(position){ case 'start': { if (value.substring(0, key.length) === key) { return value.substring(key.length); } break; } case 'end': { if (value.substring(0 - key.length) === key) { return value.substring(0, value.length - key.length - 1); } break; } } return undefined; } if (Array.isArray(value)) { let match = false; for(let i = 0; i < value.length; i++){ const output = matchOperator(key, value[i], position); if (typeof output !== 'undefined') { match = true; value[i] = output; } } if (match) { return value; } } return undefined; } function parseFilterValue(input) { if (typeof input === 'string' && input.includes(exports.FilterInputOperatorValue.IN)) { input = input.split(exports.FilterInputOperatorValue.IN); } let negation = false; let value = matchOperator(exports.FilterInputOperatorValue.NEGATION, input, 'start'); if (typeof value !== 'undefined') { negation = true; input = value; } if (Array.isArray(input)) { return { value: input, operator: negation ? exports.FilterComparisonOperator.NOT_IN : exports.FilterComparisonOperator.IN }; } value = matchOperator(exports.FilterInputOperatorValue.LIKE, input, 'start'); if (typeof value !== 'undefined') { return { value, operator: negation ? exports.FilterComparisonOperator.NOT_LIKE : exports.FilterComparisonOperator.LIKE }; } value = matchOperator(exports.FilterInputOperatorValue.LESS_THAN_EQUAL, input, 'start'); if (typeof value !== 'undefined') { return { value, operator: exports.FilterComparisonOperator.LESS_THAN_EQUAL }; } value = matchOperator(exports.FilterInputOperatorValue.LESS_THAN, input, 'start'); if (typeof value !== 'undefined') { return { value, operator: exports.FilterComparisonOperator.LESS_THAN }; } value = matchOperator(exports.FilterInputOperatorValue.MORE_THAN_EQUAL, input, 'start'); if (typeof value !== 'undefined') { return { value, operator: exports.FilterComparisonOperator.GREATER_THAN_EQUAL }; } value = matchOperator(exports.FilterInputOperatorValue.MORE_THAN, input, 'start'); if (typeof value !== 'undefined') { return { value, operator: exports.FilterComparisonOperator.GREATER_THAN }; } return { value: input, operator: negation ? exports.FilterComparisonOperator.NOT_EQUAL : exports.FilterComparisonOperator.EQUAL }; } // -------------------------------------------------- // ^([0-9]+(?:\.[0-9]+)*){0,1}([a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]+)*){0,1}$ function transformFiltersParseOutputElement(element) { if (hasOwnProperty(element, 'path') && (typeof element.path === 'undefined' || element.path === null)) { delete element.path; } if (element.operator) { return element; } if (typeof element.value === 'string') { element = { ...element, ...parseFilterValue(element.value) }; } else { element.operator = exports.FilterComparisonOperator.EQUAL; } element.value = transformFilterValue(element.value); return element; } function buildDefaultFiltersParseOutput(options, input = {}) { const inputKeys = Object.keys(input || {}); if (!options.defaultByElement && inputKeys.length > 0) { return Object.values(input); } if (options.default) { const flatten = flattenNestedObject(options.default); const keys = Object.keys(flatten); const output = []; for(let i = 0; i < keys.length; i++){ const keyDetails = parseKey(keys[i]); if (options.defaultByElement && inputKeys.length > 0) { const keyWithPath = buildKeyWithPath(keyDetails); if (hasOwnProperty(input, keyWithPath)) { continue; } } if (options.defaultByElement || inputKeys.length === 0) { let path; if (keyDetails.path) { path = keyDetails.path; } else if (options.defaultPath) { path = options.defaultPath; } output.push(transformFiltersParseOutputElement({ ...path ? { path } : {}, key: keyDetails.name, value: flatten[keys[i]] })); } } return input ? [ ...Object.values(input), ...output ] : output; } return input ? Object.values(input) : []; } function parseQueryFilters(data, options) { options = options || {}; options.mapping = options.mapping || {}; options.relations = options.relations || []; // If it is an empty array nothing is allowed if (typeof options.allowed !== 'undefined') { options.allowed = flattenParseAllowedOption(options.allowed); if (options.allowed.length === 0) { return buildDefaultFiltersParseOutput(options); } } /* istanbul ignore next */ if (!isObject(data)) { if (options.throwOnFailure) { throw FiltersParseError.inputInvalid(); } return buildDefaultFiltersParseOutput(options); } const { length } = Object.keys(data); if (length === 0) { return buildDefaultFiltersParseOutput(options); } if ((typeof options.allowed === 'undefined' || options.allowed.length === 0) && options.default) { const flatten = flattenNestedObject(options.default); options.allowed = Object.keys(flatten); } const items = {}; // transform to appreciate data format & validate input const keys = Object.keys(data); for(let i = 0; i < keys.length; i++){ const value = data[keys[i]]; if (typeof value !== 'string' && typeof value !== 'number' && typeof value !== 'boolean' && typeof value !== 'undefined' && value !== null && !Array.isArray(value)) { if (options.throwOnFailure) { throw FiltersParseError.keyValueInvalid(keys[i]); } continue; } keys[i] = applyMapping(keys[i], options.mapping); const fieldDetails = parseKey(keys[i]); if (typeof options.allowed === 'undefined' && !isValidFieldName(fieldDetails.name)) { if (options.throwOnFailure) { throw FiltersParseError.keyInvalid(fieldDetails.name); } continue; } if (typeof fieldDetails.path !== 'undefined' && !isPathAllowedByRelations(fieldDetails.path, options.relations)) { if (options.throwOnFailure) { throw FiltersParseError.keyPathInvalid(fieldDetails.path); } continue; } const fullKey = buildKeyWithPath(fieldDetails); if (options.allowed && !isPathCoveredByParseAllowedOption(options.allowed, [ keys[i], fullKey ])) { if (options.throwOnFailure) { throw FiltersParseError.keyInvalid(fieldDetails.name); } continue; } const filter = transformFiltersParseOutputElement({ key: fieldDetails.name, value: value }); if (options.validate) { if (Array.isArray(filter.value)) { const output = []; for(let j = 0; j < filter.value.length; j++){ if (options.validate(filter.key, filter.value[j])) { output.push(filter.value[j]); } else if (options.throwOnFailure) { throw FiltersParseError.keyValueInvalid(fieldDetails.name); } } filter.value = output; if (filter.value.length === 0) { continue; } } else if (!options.validate(filter.key, filter.value)) { if (options.throwOnFailure) { throw FiltersParseError.keyValueInvalid(fieldDetails.name); } continue; } } if (typeof filter.value === 'string' && filter.value.length === 0) { if (options.throwOnFailure) { throw FiltersParseError.keyValueInvalid(fieldDetails.name); } continue; } if (Array.isArray(filter.value) && filter.value.length === 0) { if (options.throwOnFailure) { throw FiltersParseError.keyValueInvalid(fieldDetails.name); } continue; } if (fieldDetails.path || options.defaultPath) { filter.path = fieldDetails.path || options.defaultPath; } items[fullKey] = filter; } return buildDefaultFiltersParseOutput(options, items); } function mergeQueryPagination(target, source) { return merge(target || {}, source || {}); } class PaginationBuildError extends BuildError { } class PaginationParseError extends ParseError { static limitExceeded(limit) { return new this({ message: `The pagination limit must not exceed the value of ${limit}.` }); } } // -------------------------------------------------- function finalizePagination(data, options) { if (typeof options.maxLimit !== 'undefined') { if (typeof data.limit === 'undefined' || data.limit > options.maxLimit) { if (options.throwOnFailure) { throw PaginationParseError.limitExceeded(options.maxLimit); } data.limit = options.maxLimit; } } if (typeof data.limit !== 'undefined' && typeof data.offset === 'undefined') { data.offset = 0; } return data; } /** * Transform pagination data to an appreciate data format. * * @param data * @param options */ function parseQueryPagination(data, options) { options = options || {}; const pagination = {}; if (!smob.isObject(data)) { if (options.throwOnFailure) { throw PaginationParseError.inputInvalid(); } return finalizePagination(pagination, options); } let { limit, offset } = data; if (typeof limit !== 'undefined') { limit = parseInt(limit, 10); if (!Number.isNaN(limit) && limit > 0) { pagination.limit = limit; } else if (options.throwOnFailure) { throw PaginationParseError.keyValueInvalid('limit'); } } if (typeof offset !== 'undefined') { offset = parseInt(offset, 10); if (!Number.isNaN(offset) && offset >= 0) { pagination.offset = offset; } else if (options.throwOnFailure) { throw PaginationParseError.keyValueInvalid('offset'); } } return finalizePagination(pagination, options); } function buildQueryRelations(input) { if (typeof input === 'undefined') { return []; } return flattenToKeyPathArray(input); } function mergeQueryRelations(target, source) { return merge(target || [], source || []); } class RelationsBuildError extends BuildError { } class RelationsParseError extends ParseError { } /* * Copyright (c) 2022. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ function includeParents(data) { for(let i = 0; i < data.length; i++){ const parts = data[i].split('.'); while(parts.length > 0){ parts.pop(); if (parts.length > 0) { const value = parts.join('.'); if (data.indexOf(value) === -1) { data.unshift(value); } } } } return data; } /* * Copyright (c) 2022. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ function isValidRelationPath(input) { return /^[a-zA-Z0-9_-]+([.]*[a-zA-Z0-9_-])*$/gu.test(input); } // -------------------------------------------------- function parseQueryRelations(input, options = {}) { options = options || {}; // If it is an empty array nothing is allowed if (Array.isArray(options.allowed) && options.allowed.length === 0) { return []; } options.mapping = options.mapping || {}; options.pathMapping = options.pathMapping || {}; if (typeof options.includeParents === 'undefined') { options.includeParents = true; } let items = []; if (typeof input === 'string') { items = input.split(','); } else if (Array.isArray(input)) { for(let i = 0; i < input.length; i++){ if (typeof input[i] === 'string') { items.push(input[i]); } else { throw RelationsParseError.inputInvalid(); } } } else if (options.throwOnFailure) { throw RelationsParseError.inputInvalid(); } if (items.length === 0) { return []; } const mappingKeys = Object.keys(options.mapping); if (mappingKeys.length > 0) { for(let i = 0; i < items.length; i++){ items[i] = applyMapping(items[i], options.mapping); } } for(let j = items.length - 1; j >= 0; j--){ let isValid; if (options.allowed) { isValid = isPathCoveredByParseAllowedOption(options.allowed, items[j]); } else { isValid = isValidRelationPath(items[j]); } if (!isValid) { if (options.throwOnFailure) { throw RelationsParseError.keyInvalid(items[j]); } items.splice(j, 1); } } if (options.includeParents) { if (Array.isArray(options.includeParents)) { const parentIncludes = items.filter((item)=>item.includes('.') && options.includeParents.filter((parent)=>item.startsWith(parent)).length > 0); items.unshift(...includeParents(parentIncludes)); } else { items = includeParents(items); } } items = Array.from(new Set(items)); return items.map((key)=>{ const parts = key.split('.'); let value; if (options.pathMapping && hasOwnProperty(options.pathMapping, key)) { value = options.pathMapping[key]; } else { value = parts.pop(); } return { key, value }; }); } /* * Copyright (c) 2021-2022. * Author Peter Placzek (tada5hi) * For the full copyright and license information, * view the LICENSE file that was distributed with this source code. */ exports.SortDirection = void 0; (function(SortDirection) { SortDirection["ASC"] = "ASC"; SortDirection["DESC"] = "DESC"; })(exports.SortDirection || (exports.SortDirection = {})); function buildQuerySort(data) { if (typeof data === 'undefined') { return []; } if (typeof data === 'string') { return [ data ]; } return flattenToKeyPathArray(data, { transformer: (input, output, path)=>{ if (typeof input === 'string' && path && (input === exports.SortDirection.ASC || input === exports.SortDirection.DESC)) { if (input === exports.SortDirection.DESC) { output.push(`-${path}`); } else { output.push(path); } return true; } return undefined; } }); } function mergeQuerySort(target, source) { return merge(target || [], source || []); } class SortBuildError extends BuildError { } class SortParseError extends ParseError { } function parseSortValue(value) { let direction = exports.SortDirection.ASC; if (value.substring(0, 1) === '-') { direction = exports.SortDirection.DESC; value = value.substring(1); } return { direction, value }; } // -------------------------------------------------- function isMultiDimensionalArray(arr) { if (!Array.isArray(arr)) { return false; } return arr.length > 0 && Array.isArray(arr[0]); } function buildDefaultSortParseOutput(options) { if (options.default) { const output = []; const flatten = flattenNestedObject(options.default); const keys = Object.keys(flatten); for(let i = 0; i < keys.length; i++){ const fieldDetails = parseKey(keys[i]); let path; if (fieldDetails.path) { path = fieldDetails.path; } else if (options.defaultPath) { path = options.defaultPath; } output.push({ key: fieldDetails.name, ...path ? { path } : {}, value: flatten[keys[i]] }); } return output; } return []; } /** * Transform sort data to appreciate data format. * @param data * @param options */ function parseQuerySort(data, options) { options = options || {}; // If it is an empty array nothing is allowed if (typeof options.allowed !== 'undefined') { const allowed = flattenParseAllowedOption(options.allowed); if (allowed.length === 0) { return buildDefaultSortParseOutput(options); } } options.mapping = options.mapping || {}; /* istanbul ignore next */ if (typeof data !== 'string' && !Array.isArray(data) && !smob.isObject(data)) { if (options.throwOnFailure) { throw SortParseError.inputInvalid(); } return buildDefaultSortParseOutput(options); } if (typeof options.allowed === 'undefined' && options.default) { const flatten = flattenNestedObject(options.default); options.allowed = Object.keys(flatten); } let parts = []; if (typeof data === 'string') { parts = data.split(','); } if (Array.isArray(data)) { parts = data.filter((item)=>typeof item === 'string'); } if (smob.isObject(data)) { const keys = Object.keys(data); for(let i = 0; i < keys.length; i++){ /* istanbul ignore next */ if (!hasOwnProperty(data, keys[i]) || typeof keys[i] !== 'string' || typeof data[keys[i]] !== 'string') { if (options.throwOnFailure) { throw SortParseError.keyValueInvalid(keys[i]); } continue; } const fieldPrefix = data[keys[i]].toLowerCase() === 'desc' ? '-' : ''; parts.push(fieldPrefix + keys[i]); } } const items = {}; let matched = false; for(let i = 0; i < parts.length; i++){ const { value, direction } = parseSortValue(parts[i]); parts[i] = value; const key = applyMapping(parts[i], options.mapping); const fieldDetails = parseKey(key); if (typeof options.allowed === 'undefined' && !isValidFieldName(fieldDetails.name)) { if (options.throwOnFailure) { throw SortParseError.keyInvalid(fieldDetails.name); } continue; } if (!isPathAllowedByRelations(fieldDetails.path, options.relations) && typeof fieldDetails.path !== 'undefined') { if (options.throwOnFailure) { throw SortParseError.keyPathInvalid(fieldDetails.path); } continue; } const keyWithAlias = buildKeyWithPath(fieldDetails); if (typeof options.allowed !== 'undefined' && !isMultiDimensionalArray(options.allowed) && !isPathCoveredByParseAllowedOption(options.allowed, [ key, keyWithAlias ])) { if (options.throwOnFailure) { throw SortParseError.keyNotAllowed(fieldDetails.name); } continue; } matched = true; let path; if (fieldDetails.path) { path = fieldDetails.path; } else if (options.defaultPath) { path = options.defaultPath; } items[keyWithAlias] = { key: fieldDetails.name, ...path ? { path } : {}, value: direction }; } if (!matched) { return buildDefaultSortParseOutput(options); } if (isMultiDimensionalArray(options.allowed)) { // eslint-disable-next-line no-labels,no-restricted-syntax outerLoop: for(let i = 0; i < options.allowed.length; i++){ const temp = []; const keyPaths = flattenParseAllowedOption(options.allowed[i]); for(let j = 0; j < keyPaths.length; j++){ let keyWithAlias = keyPaths[j]; let key; const parts = keyWithAlias.split('.'); if (parts.length > 1) { key = parts.pop(); } else { key = keyWithAlias; keyWithAlias = buildKeyPath(key, options.defaultPath); } if (hasOwnProperty(items, key) || hasOwnProperty(items, keyWithAlias)) { const item = hasOwnProperty(items, key) ? items[key] : items[keyWithAlias]; temp.push(item); } else { continue outerLoop; } } return temp; } // if we get no match, the sort data is invalid. return []; } return Object.values(items); } function buildQuery(input) { if (!input) { return ''; } const query = {}; if (typeof input[exports.Parameter.FIELDS] !== 'undefined' || typeof input[exports.URLParameter.FIELDS] !== 'undefined') { query[exports.URLParameter.FIELDS] = mergeQueryFields(buildQueryFields(input[exports.Parameter.FIELDS]), buildQueryFields(input[exports.URLParameter.FIELDS])); } if (typeof input[exports.Parameter.FILTERS] !== 'undefined' || typeof input[exports.URLParameter.FILTERS] !== 'undefined') { query[exports.URLParameter.FILTERS] = mergeQueryFilters(buildQueryFilters(input[exports.Parameter.FILTERS]), buildQueryFilters(input[exports.URLParameter.FILTERS])); } if (typeof input[exports.Parameter.PAGINATION] !== 'undefined' || typeof input[exports.URLParameter.PAGINATION] !== 'undefined') { query[exports.URLParameter.PAGINATION] = mergeQueryPagination(input[exports.Parameter.PAGINATION], input[exports.URLParameter.PAGINATION]); } if (typeof input[exports.Parameter.RELATIONS] !== 'undefined' || typeof input[exports.URLParameter.RELATIONS] !== 'undefined') { query[exports.URLParameter.RELATIONS] = mergeQueryRelations(buildQueryRelations(input[exports.Parameter.RELATIONS]), buildQueryRelations(input[exports.URLParameter.RELATIONS])); } if (typeof input[exports.Parameter.SORT] !== 'undefined' || typeof input[exports.URLParameter.SORT] !== 'undefined') { query[exports.URLParameter.SORT] = mergeQuerySort(buildQuerySort(input[exports.Parameter.SORT]), buildQuerySort(input[exports.URLParameter.SORT])); } return buildURLQueryString(query); } function parseQueryParameter(key, data, options, relations) { switch(key){ case exports.Parameter.FIELDS: case exports.URLParameter.FIELDS: return parseQueryFields(data, { ...invalidToEmptyObject(options), ...relations ? { relations } : {} }); case exports.Parameter.FILTERS: case exports.URLParameter.FILTERS: return parseQueryFilters(data, { ...invalidToEmptyObject(options), ...relations ? { relations } : {} }); case exports.Parameter.PAGINATION: case exports.URLParameter.PAGINATION: return parseQueryPagination(data, { ...invalidToEmptyObject(options) }); case exports.Parameter.RELATIONS: case exports.URLParameter.RELATIONS: return parseQueryRelations(data, { ...invalidToEmptyObject(options) }); default: return parseQuerySort(data, { ...invalidToEmptyObject(options), ...relations ? { relations } : {} }); } } function invalidToEmptyObject(value) { return typeof value === 'boolean' || typeof value === 'undefined' ? {} : value; } function buildQueryParameterOptions(input) { if (smob.isObject(input)) { return input; } return {}; } function isQueryParameterEnabled(context) { if (typeof context.options === 'boolean') { return context.options; } if (typeof context.data !== 'undefined' && typeof context.options === 'undefined') { return true; } if (smob.isObject(context.options)) { if (typeof context.options.default !== 'undefined') { return true; } return typeof context.data !== 'undefined'; } return false; } function parseQuery(input, options = {}) { options = options || {}; const mergeWithGlobalOptions = (data)=>{ if (typeof data !== 'undefined') { if (typeof data.defaultPath === 'undefined') { data.defaultPath = options.defaultPath; } if (typeof data.throwOnError === 'undefined') { data.throwOnError = options.throwOnFailure; } } return data || {}; }; const output = {}; if (options.defaultPath) { output.defaultPath = options.defaultPath; } let relations; const keys = [ // relations must be first parameter exports.Parameter.RELATIONS, exports.Parameter.FIELDS, exports.Parameter.FILTERS, exports.Parameter.PAGINATION, exports.Parameter.SORT ]; for(let i = 0; i < keys.length; i++){ const key = keys[i]; switch(key){ case exports.Parameter.RELATIONS: { const value = input[exports.Parameter.RELATIONS] || input[exports.URLParameter.RELATIONS]; if (isQueryParameterEnabled({ data: value, options: options[exports.Parameter.RELATIONS] })) { relations = parseQueryParameter(key, value, buildQueryParameterOptions(options[exports.Parameter.RELATIONS])); output[exports.Parameter.RELATIONS] = relations; } break; } case exports.Parameter.FIELDS: { const value = input[exports.Parameter.FIELDS] || input[exports.URLParameter.FIELDS]; if (isQueryParameterEnabled({ data: value, options: options[exports.Parameter.FIELDS] })) { output[exports.Parameter.FIELDS] = parseQueryParameter(key, value, mergeWithGlobalOptions(buildQueryParameterOptions(options[exports.Parameter.FIELDS])), relations); } break; } case exports.Parameter.FILTERS: { const value = input[exports.Parameter.FILTERS] || input[exports.URLParameter.FILTERS]; if (isQueryParameterEnabled({ data: value, options: options[exports.Parameter.FILTERS] })) { output[exports.Parameter.FILTERS] = parseQueryParameter(key, value, mergeWithGlobalOptions(buildQueryParameterOptions(options[exports.Parameter.FILTERS])), relations); } break; } case exports.Parameter.PAGINATION: { const value = input[exports.Parameter.PAGINATION] || input[exports.URLParameter.PAGINATION]; if (isQueryParameterEnabled({ data: value, options: options[exports.Parameter.PAGINATION] })) { output[exports.Parameter.PAGINATION] = parseQueryParameter(key, value, buildQueryParameterOptions(options[exports.Parameter.PAGINATION]), relations); } break; } case exports.Parameter.SORT: { const value = input[exports.Parameter.SORT] || input[exports.URLParameter.SORT]; if (isQueryParameterEnabled({ data: value, options: options[exports.Parameter.SORT] })) { output[exports.Parameter.SORT] = parseQueryParameter(key, value, mergeWithGlobalOptions(buildQueryParameterOptions(options[exports.Parameter.SORT])), relations); } break; } } } return output; } exports.BaseError = BaseError; exports.BuildError = BuildError; exports.DEFAULT_ID = DEFAULT_ID; exports.FieldsBuildError = FieldsBuildError; exports.FieldsParseError = FieldsParseError; exports.FiltersBuildError = FiltersBuildError; exports.FiltersParseError = FiltersParseError; exports.PaginationBuildError = PaginationBuildError; exports.PaginationParseError = PaginationParseError; exports.ParseError = ParseError; exports.RelationsBuildError = RelationsBuildError; exports.RelationsParseError = RelationsParseError; exports.SortBuildError = SortBuildError; exports.SortParseError = SortParseError; exports.buildFieldDomainRecords = buildFieldDomainRecords; exports.buildQuery = buildQuery; exports.buildQueryFields = buildQueryFields; exports.buildQueryFilters = buildQueryFilters; exports.buildQueryParameterOptions = buildQueryParameterOptions; exports.buildQueryRelations = buildQueryRelations; exports.buildQuerySort = buildQuerySort; exports.flattenParseAllowedOption = flattenParseAllowedOption; exports.isPathCoveredByParseAllowedOption = isPathCoveredByParseAllowedOption; exports.isQueryParameterEnabled = isQueryParameterEnabled; exports.isValidFieldName = isValidFieldName; exports.mergeQueryFields = mergeQueryFields; exports.mergeQueryFilters = mergeQueryFilters; exports.mergeQueryPagination = mergeQueryPagination; exports.mergeQueryRelations = mergeQueryRelations; exports.mergeQuerySort = mergeQuerySort; exports.parseFieldsInput = parseFieldsInput; exports.parseFilterValue = parseFilterValue; exports.parseQuery = parseQuery; exports.parseQueryFields = parseQueryFields; exports.parseQueryFilters = parseQueryFilters; exports.parseQueryPagination = parseQueryPagination; exports.parseQueryParameter = parseQueryParameter; exports.parseQueryRelations = parseQueryRelations; exports.parseQuerySort = parseQuerySort; exports.transformFilterValue = transformFilterValue; //# sourceMappingURL=index.cjs.map