UNPKG

rapiq

Version:

A tiny library which provides utility types/functions for request and response query handling.

1,511 lines (1,466 loc) 58.1 kB
import { createMerger, isObject as isObject$1, distinctArray } from 'smob'; /* * 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. */ // ----------------------------------------------------------- var Parameter = /*#__PURE__*/ function(Parameter) { Parameter["FILTERS"] = "filters"; Parameter["FIELDS"] = "fields"; Parameter["PAGINATION"] = "pagination"; Parameter["RELATIONS"] = "relations"; Parameter["SORT"] = "sort"; return Parameter; }({}); // ----------------------------------------------------------- var URLParameter = /*#__PURE__*/ function(URLParameter) { URLParameter["FILTERS"] = "filter"; URLParameter["FIELDS"] = "fields"; URLParameter["PAGINATION"] = "page"; URLParameter["RELATIONS"] = "include"; URLParameter["SORT"] = "sort"; return URLParameter; }({}); // ----------------------------------------------------------- const DEFAULT_ID = '__DEFAULT__'; /* * 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 extendObject(target, source) { const keys = Object.keys(source); for(let i = 0; i < keys.length; i++){ target[keys[i]] = source[keys[i]]; } return target; } function hasOwnProperty(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); } function toFlatObject(data, options = {}) { const prefixParts = options.prefixParts || []; let output = {}; const keys = Object.keys(data); for(let i = 0; i < keys.length; i++){ const key = keys[i]; if (isObject(data[key])) { output = { ...output, ...toFlatObject(data[key], { ...options, prefixParts: [ ...prefixParts, key ] }) }; continue; } const destinationKey = [ ...prefixParts, key ].join('.'); if (options.transformer) { const result = options.transformer(data[key], output, destinationKey); if (result) { continue; } } if (options.validator) { const result = options.validator(data[key], destinationKey); if (!result) { continue; } } output[destinationKey] = data[key]; } return output; } function buildKeyPath(key, prefix) { if (typeof prefix === 'string') { return `${prefix}.${key}`; } return key; } function toKeyPathArray(input, options, prefix) { options = options || {}; const output = []; if (options.transformer) { const result = options.transformer(input, output, prefix); if (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 (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 (isObject(input[i])) { const keys = Object.keys(input[i]); for(let j = 0; j < keys.length; j++){ const value = buildKeyPath(keys[j], prefix); const data = toKeyPathArray(input[i][keys[j]], options, value); if (data.length === 0) { output.push(value); } else { output.push(...data); } } } } return output; } if (isObject(input)) { const keys = Object.keys(input); for(let i = 0; i < keys.length; i++){ const value = buildKeyPath(keys[i], prefix); const data = toKeyPathArray(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; } 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 = 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 (isObject$1(name)) { details = name; } else { details = { name, path }; } return details.path || path ? `${details.path || path}.${details.name}` : details.name; } function serializeAsURI(data, options = {}) { // Loop through the data object const keys = Object.keys(data); if (keys.length === 0) { return ''; } const prefixParts = options.prefixParts || []; // Create a query array to hold the key/value pairs const query = []; for(let i = 0; i < keys.length; i++){ let value = data[keys[i]]; if (isObject$1(value)) { query.push(...serializeAsURI(value, { ...options, prefixParts: [ ...prefixParts, keys[i] ] })); continue; } if (Array.isArray(value)) { value = value.map((el)=>`${el}`).filter(Boolean).join(','); } if (value) { const destinationKey = [ ...prefixParts, keys[i] ].reduce((acc, curr)=>`${acc}[${curr}]`, ''); // Encode each key and value, concatenate them into a string, and push them to the array query.push(`${encodeURIComponent(destinationKey)}=${encodeURIComponent(value)}`); } } return query.join('&'); } function flattenParseAllowedOption(input) { if (typeof input === 'undefined') { return []; } return toKeyPathArray(input); } function isPathCoveredByParseAllowedOption(input, path) { const paths = Array.isArray(path) ? path : [ path ]; const items = toKeyPathArray(input); for(let i = 0; i < items.length; i++){ if (paths.indexOf(items[i]) !== -1) { return true; } } return false; } class FieldsOptionsContainer { initDefault() { if (typeof this.options.default === 'undefined') { this.default = {}; this.defaultIsUndefined = true; return; } this.default = groupArrayByKeyPath(flattenParseAllowedOption(this.options.default)); this.defaultIsUndefined = false; } initAllowed() { if (typeof this.options.allowed === 'undefined') { if (typeof this.options.default !== 'undefined') { const items = toFlatObject(this.options.default, { validator (input) { if (!Array.isArray(input)) { return false; } return !input.some((el)=>typeof el !== 'string'); } }); if (items.length > 0) { this.allowed = items; this.allowedIsUndefined = false; return; } } this.allowed = {}; this.allowedIsUndefined = true; return; } this.allowed = groupArrayByKeyPath(flattenParseAllowedOption(this.options.allowed)); this.allowedIsUndefined = false; } initItems() { this.items = merge(this.default || {}, this.allowed || {}); this.keys = Object.keys(this.items); } initReverseMapping() { if (typeof this.options.mapping === 'undefined') { return; } this.reverseMapping = this.buildReverseRecord(this.options.mapping); } 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; } constructor(input = {}){ this.options = input; this.allowed = {}; this.allowedIsUndefined = true; this.default = {}; this.defaultIsUndefined = true; this.items = {}; this.keys = []; this.reverseMapping = {}; this.initDefault(); this.initAllowed(); this.initItems(); this.initReverseMapping(); } } /* * 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. */ var FieldOperator = /*#__PURE__*/ function(FieldOperator) { FieldOperator["INCLUDE"] = "+"; FieldOperator["EXCLUDE"] = "-"; return 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. */ var ErrorCode = /*#__PURE__*/ 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"; return ErrorCode; }({}); class BaseError extends Error { constructor(input){ if (typeof input === 'string') { super(input); this.code = ErrorCode.NONE; } else { super(input.message); this.code = input.code || 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: ErrorCode.INPUT_INVALID }); } static keyNotAllowed(name) { return new this({ message: `The key ${name} is not covered by allowed/default options.`, code: ErrorCode.KEY_NOT_ALLOWED }); } static keyInvalid(key) { return new this({ message: `The key ${key} is invalid.`, code: ErrorCode.KEY_INVALID }); } static keyPathInvalid(key) { return new this({ message: `The key path ${key} is invalid.`, code: ErrorCode.KEY_PATH_INVALID }); } static keyValueInvalid(key) { return new this({ message: `The value of the key ${key} is invalid.`, code: 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 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 parseQueryFields(input, options) { let container; if (options instanceof FieldsOptionsContainer) { container = options; } else { container = new FieldsOptionsContainer(options); } // If it is an empty array nothing is allowed if ((!container.allowedIsUndefined || !container.defaultIsUndefined) && container.keys.length === 0) { return []; } let data = { [DEFAULT_ID]: [] }; if (isObject$1(input)) { data = input; } else if (typeof input === 'string' || Array.isArray(input)) { data = { [DEFAULT_ID]: input }; } else if (container.options.throwOnFailure) { throw FieldsParseError.inputInvalid(); } let { keys } = container; if (keys.length > 0 && hasOwnProperty(data, DEFAULT_ID)) { data = { [keys[0]]: data[DEFAULT_ID] }; } else { keys = 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, container.options.relations)) { if (container.options.throwOnFailure) { throw FieldsParseError.keyPathInvalid(path); } continue; } let fields = []; if (hasOwnProperty(data, path)) { fields = parseFieldsInput(data[path]); } else if (hasOwnProperty(container.reverseMapping, path) && hasOwnProperty(data, container.reverseMapping[path])) { fields = parseFieldsInput(data[container.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 === FieldOperator.INCLUDE) { operator = FieldOperator.INCLUDE; } else if (character === FieldOperator.EXCLUDE) { operator = FieldOperator.EXCLUDE; } if (operator) { fields[j] = fields[j].substring(1); } fields[j] = applyMapping(fields[j], container.options.mapping, true); let isValid; if (hasOwnProperty(container.items, path)) { isValid = container.items[path].indexOf(fields[j]) !== -1; } else { isValid = isValidFieldName(fields[j]); } if (!isValid) { if (container.options.throwOnFailure) { throw FieldsParseError.keyNotAllowed(fields[j]); } continue; } if (operator === FieldOperator.INCLUDE) { transformed.included.push(fields[j]); } else if (operator === FieldOperator.EXCLUDE) { transformed.excluded.push(fields[j]); } else { transformed.default.push(fields[j]); } } } if (transformed.default.length === 0 && hasOwnProperty(container.default, path)) { transformed.default = container.default[path]; } if (transformed.included.length === 0 && transformed.default.length === 0 && hasOwnProperty(container.allowed, path)) { transformed.default = container.allowed[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 (container.options.defaultPath) { destPath = container.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. */ var FilterComparisonOperator = /*#__PURE__*/ 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"; return FilterComparisonOperator; }({}); var FilterInputOperatorValue = /*#__PURE__*/ function(FilterInputOperatorValue) { FilterInputOperatorValue["NEGATION"] = "!"; FilterInputOperatorValue["LIKE"] = "~"; FilterInputOperatorValue["LESS_THAN_EQUAL"] = "<="; FilterInputOperatorValue["LESS_THAN"] = "<"; FilterInputOperatorValue["MORE_THAN_EQUAL"] = ">="; FilterInputOperatorValue["MORE_THAN"] = ">"; FilterInputOperatorValue["IN"] = ","; return FilterInputOperatorValue; }({}); /* * 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(FilterInputOperatorValue.IN)) { input = input.split(FilterInputOperatorValue.IN); } let negation = false; let value = matchOperator(FilterInputOperatorValue.NEGATION, input, 'start'); if (typeof value !== 'undefined') { negation = true; input = value; } if (Array.isArray(input)) { return { value: input, operator: negation ? FilterComparisonOperator.NOT_IN : FilterComparisonOperator.IN }; } value = matchOperator(FilterInputOperatorValue.LIKE, input, 'start'); if (typeof value !== 'undefined') { return { value, operator: negation ? FilterComparisonOperator.NOT_LIKE : FilterComparisonOperator.LIKE }; } value = matchOperator(FilterInputOperatorValue.LESS_THAN_EQUAL, input, 'start'); if (typeof value !== 'undefined') { return { value, operator: FilterComparisonOperator.LESS_THAN_EQUAL }; } value = matchOperator(FilterInputOperatorValue.LESS_THAN, input, 'start'); if (typeof value !== 'undefined') { return { value, operator: FilterComparisonOperator.LESS_THAN }; } value = matchOperator(FilterInputOperatorValue.MORE_THAN_EQUAL, input, 'start'); if (typeof value !== 'undefined') { return { value, operator: FilterComparisonOperator.GREATER_THAN_EQUAL }; } value = matchOperator(FilterInputOperatorValue.MORE_THAN, input, 'start'); if (typeof value !== 'undefined') { return { value, operator: FilterComparisonOperator.GREATER_THAN }; } return { value: input, operator: negation ? FilterComparisonOperator.NOT_EQUAL : FilterComparisonOperator.EQUAL }; } class FiltersOptionsContainer { initDefault() { if (!this.options.default) { this.default = {}; this.defaultKeys = []; this.defaultOutput = this.buildParseOutput(); return; } this.default = toFlatObject(this.options.default); this.defaultKeys = Object.keys(this.default); this.defaultOutput = this.buildParseOutput(); } initAllowed() { if (typeof this.options.allowed === 'undefined') { if (typeof this.options.default !== 'undefined') { const flatten = toFlatObject(this.options.default); const allowed = Object.keys(flatten); if (allowed.length > 0) { this.allowed = allowed; this.allowedIsUndefined = false; return; } } this.allowed = []; this.allowedIsUndefined = true; return; } this.allowed = flattenParseAllowedOption(this.options.allowed); this.allowedIsUndefined = false; } buildParseOutput(input = {}) { const inputKeys = Object.keys(input || {}); if (!this.options.defaultByElement && inputKeys.length > 0) { return Object.values(input); } if (this.defaultKeys.length > 0) { const output = []; for(let i = 0; i < this.defaultKeys.length; i++){ const keyDetails = parseKey(this.defaultKeys[i]); if (this.options.defaultByElement && inputKeys.length > 0) { const keyWithPath = buildKeyWithPath(keyDetails); if (hasOwnProperty(input, keyWithPath)) { continue; } } if (this.options.defaultByElement || inputKeys.length === 0) { let path; if (keyDetails.path) { path = keyDetails.path; } else if (this.options.defaultPath) { path = this.options.defaultPath; } output.push(this.transformParseOutputElement({ ...path ? { path } : {}, key: keyDetails.name, value: this.default[this.defaultKeys[i]] })); } } return input ? [ ...Object.values(input), ...output ] : output; } return input ? Object.values(input) : []; } // ^([0-9]+(?:\.[0-9]+)*){0,1}([a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]+)*){0,1}$ transformParseOutputElement(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 = FilterComparisonOperator.EQUAL; } element.value = transformFilterValue(element.value); return element; } constructor(input = {}){ this.options = input; this.allowed = []; this.allowedIsUndefined = true; this.default = {}; this.defaultKeys = []; this.defaultOutput = []; this.initDefault(); this.initAllowed(); } } function transformFiltersBuildInput(input) { return toFlatObject(input, { 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; } if (isObject(input)) { const tmp = transformFiltersBuildInput(input); extendObject(output, tmp); } return undefined; } }); } class FiltersBuildError extends BuildError { } class FiltersParseError extends ParseError { } function parseQueryFilters(data, options) { let container; if (options instanceof FiltersOptionsContainer) { container = options; } else { container = new FiltersOptionsContainer(options); } // If it is an empty array nothing is allowed if (!container.allowedIsUndefined && container.allowed.length === 0) { return container.defaultOutput; } /* istanbul ignore next */ if (!isObject(data)) { if (container.options.throwOnFailure) { throw FiltersParseError.inputInvalid(); } return container.defaultOutput; } const { length } = Object.keys(data); if (length === 0) { return container.defaultOutput; } 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 (container.options.throwOnFailure) { throw FiltersParseError.keyValueInvalid(keys[i]); } continue; } keys[i] = applyMapping(keys[i], container.options.mapping); const fieldDetails = parseKey(keys[i]); if (container.allowedIsUndefined && !isValidFieldName(fieldDetails.name)) { if (container.options.throwOnFailure) { throw FiltersParseError.keyInvalid(fieldDetails.name); } continue; } if (typeof fieldDetails.path !== 'undefined' && !isPathAllowedByRelations(fieldDetails.path, container.options.relations)) { if (container.options.throwOnFailure) { throw FiltersParseError.keyPathInvalid(fieldDetails.path); } continue; } const fullKey = buildKeyWithPath(fieldDetails); if (!container.allowedIsUndefined && container.allowed && !isPathCoveredByParseAllowedOption(container.allowed, [ keys[i], fullKey ])) { if (container.options.throwOnFailure) { throw FiltersParseError.keyInvalid(fieldDetails.name); } continue; } const filter = container.transformParseOutputElement({ key: fieldDetails.name, value: value }); if (container.options.validate) { if (Array.isArray(filter.value)) { const output = []; for(let j = 0; j < filter.value.length; j++){ if (container.options.validate(filter.key, filter.value[j])) { output.push(filter.value[j]); } else if (container.options.throwOnFailure) { throw FiltersParseError.keyValueInvalid(fieldDetails.name); } } filter.value = output; if (filter.value.length === 0) { continue; } } else if (!container.options.validate(filter.key, filter.value)) { if (container.options.throwOnFailure) { throw FiltersParseError.keyValueInvalid(fieldDetails.name); } continue; } } if (typeof filter.value === 'string' && filter.value.length === 0) { if (container.options.throwOnFailure) { throw FiltersParseError.keyValueInvalid(fieldDetails.name); } continue; } if (Array.isArray(filter.value) && filter.value.length === 0) { if (container.options.throwOnFailure) { throw FiltersParseError.keyValueInvalid(fieldDetails.name); } continue; } if (fieldDetails.path || container.options.defaultPath) { filter.path = fieldDetails.path || container.options.defaultPath; } items[fullKey] = filter; } return container.buildParseOutput(items); } 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 = {}) { const output = {}; if (!isObject$1(data)) { if (options.throwOnFailure) { throw PaginationParseError.inputInvalid(); } return finalizePagination(output, options); } let { limit, offset } = data; if (typeof limit !== 'undefined') { limit = parseInt(limit, 10); if (!Number.isNaN(limit) && limit > 0) { output.limit = limit; } else if (options.throwOnFailure) { throw PaginationParseError.keyValueInvalid('limit'); } } if (typeof offset !== 'undefined') { offset = parseInt(offset, 10); if (!Number.isNaN(offset) && offset >= 0) { output.offset = offset; } else if (options.throwOnFailure) { throw PaginationParseError.keyValueInvalid('offset'); } } return finalizePagination(output, options); } 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 = {}) { // 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. */ var SortDirection = /*#__PURE__*/ function(SortDirection) { SortDirection["ASC"] = "ASC"; SortDirection["DESC"] = "DESC"; return SortDirection; }({}); function transformSortBuildInput(input) { if (typeof input === 'undefined') { return {}; } if (typeof input === 'string') { return input.split(',').reduce((acc, curr)=>{ if (curr.startsWith('-')) { acc[curr.slice(1)] = SortDirection.DESC; } else { acc[curr] = SortDirection.ASC; } return acc; }, {}); } if (Array.isArray(input)) { let output = {}; for(let i = 0; i < input.length; i++){ output = { ...output, ...transformSortBuildInput(input[i]) }; } return output; } if (isObject(input)) { return toFlatObject(input, { transformer: (input, output)=>{ if (isObject(input)) { const tmp = transformSortBuildInput(input); extendObject(output, tmp); return true; } return undefined; } }); } return {}; } class SortOptionsContainer { buildDefaultDomainFields() { if (!this.options.default) { this.default = {}; this.defaultKeys = []; this.defaultOutput = this.buildParseOutput(); return; } this.default = toFlatObject(this.options.default); this.defaultKeys = Object.keys(this.default); this.defaultOutput = this.buildParseOutput(); } buildAllowedDomainFields() { if (typeof this.options.allowed === 'undefined') { if (typeof this.options.default !== 'undefined') { const flatten = toFlatObject(this.options.default); const allowed = Object.keys(flatten); if (allowed.length > 0) { this.allowed = allowed; this.allowedIsUndefined = false; return; } } this.allowed = []; this.allowedIsUndefined = true; return; } this.allowed = flattenParseAllowedOption(this.options.allowed); this.allowedIsUndefined = false; } buildParseOutput() { if (this.default) { const output = []; const flatten = toFlatObject(this.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 (this.options.defaultPath) { path = this.options.defaultPath; } output.push({ key: fieldDetails.name, ...path ? { path } : {}, value: flatten[keys[i]] }); } return output; } return []; } constructor(input = {}){ this.options = input; this.allowed = []; this.allowedIsUndefined = true; this.default = {}; this.defaultKeys = []; this.defaultOutput = []; this.buildDefaultDomainFields(); this.buildAllowedDomainFields(); } } class SortBuildError extends BuildError { } class SortParseError extends ParseError { } function parseSortValue(value) { let direction = SortDirection.ASC; if (value.substring(0, 1) === '-') { direction = 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]); } /** * Transform sort data to appreciate data format. * @param data * @param options */ function parseQuerySort(data, options) { let container; if (options instanceof SortOptionsContainer) { container = options; } else { container = new SortOptionsContainer(options); } // If it is an empty array nothing is allowed if (!container.allowedIsUndefined && container.allowed.length === 0) { return container.defaultOutput; } /* istanbul ignore next */ if (typeof data !== 'string' && !Array.isArray(data) && !isObject$1(data)) { if (container.options.throwOnFailure) { throw SortParseError.inputInvalid(); } return container.defaultOutput; } let parts = []; if (typeof data === 'string') { parts = data.split(','); } if (Array.isArray(data)) { parts = data.filter((item)=>typeof item === 'string'); } if (isObject$1(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 (container.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], container.options.mapping); const fieldDetails = parseKey(key); if (container.allowedIsUndefined && !isValidFieldName(fieldDetails.name)) { if (container.options.throwOnFailure) { throw SortParseError.keyInvalid(fieldDetails.name); } continue; } if (!isPathAllowedByRelations(fieldDetails.path, container.options.relations) && typeof fieldDetails.path !== 'undefined') { if (container.options.throwOnFailure) { throw SortParseError.keyPathInvalid(fieldDetails.path); } continue; } const keyWithAlias = buildKeyWithPath(fieldDetails); if (!container.allowedIsUndefined && container.allowed && !isMultiDimensionalArray(container.options.allowed) && !isPathCoveredByParseAllowedOption(container.allowed, [ key, keyWithAlias ])) { if (container.options.throwOnFailure) { throw SortParseError.keyNotAllowed(fieldDetails.name); } continue; } matched = true; let path; if (fieldDetails.path) { path = fieldDetails.path; } else if (container.options.defaultPath) { path = container.options.defaultPath; } items[keyWithAlias] = { key: fieldDetails.name, ...path ? { path } : {}, value: direction }; } if (!matched) { return container.defaultOutput; } if (isMultiDimensionalArray(container.options.allowed)) { // eslint-disable-next-line no-labels,no-restricted-syntax outerLoop: for(let i = 0; i < container.allowed.length; i++){ const temp = []; const keyPaths = flattenParseAllowedOption(container.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, container.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); } class QueryBuilder { // -------------------------------------------------- add(input) { if (typeof input[Parameter.FIELDS] !== 'undefined') { this.addFields(input[Parameter.FIELDS]); } if (typeof input[URLParameter.FIELDS] !== 'undefined') { this.addFields(input[URLParameter.FIELDS]); } if (typeof input[Parameter.FILTERS] !== 'undefined') { this.addFilters(input[Parameter.FILTERS]); } if (typeof input[URLParameter.FILTERS] !== 'undefined') { this.addFilters(input[URLParameter.FILTERS]); } if (typeof input[Parameter.PAGINATION] !== 'undefined') { this.addPagination(input[Parameter.PAGINATION]); } if (typeof input[URLParameter.PAGINATION] !== 'undefined') { this.addPagination(input[URLParameter.PAGINATION]); } if (typeof input[Parameter.RELATIONS] !== 'undefined') { this.addRelations(input[Parameter.RELATIONS]); } if (typeof input[URLParameter.RELATIONS] !== 'undefined') { this.addRelations(input[URLParameter.RELATIONS]); } if (typeof input[Parameter.SORT] !== 'undefined') { this.addSort(input[Parameter