UNPKG

@c8y/client

Version:

Client application programming interface to access the Cumulocity IoT-Platform REST services.

272 lines 11.2 kB
export class QueriesUtil { constructor() { this.operatorFns = { __not: operand => { return `not(${this.buildQueryFilter(operand, null)})`; }, __and: operand => { return this.buildQueryFilter(operand, null, 'and'); }, __or: operand => { return this.buildQueryFilter(operand, null, 'or'); }, __eq: (operand, contextKey) => { if (typeof operand === 'object' && operand !== null) { return this.buildQueryFilter(operand, contextKey); } return `${contextKey} eq ${this.quoteString(operand)}`; }, __gt: (operand, contextKey) => { return `${contextKey} gt ${this.useAsFloat(operand)}`; }, __ge: (operand, contextKey) => { return `${contextKey} ge ${this.useAsFloat(operand)}`; }, __lt: (operand, contextKey) => { return `${contextKey} lt ${this.useAsFloat(operand)}`; }, __le: (operand, contextKey) => { return `${contextKey} le ${this.useAsFloat(operand)}`; }, __in: (operand, contextKey) => { const statements = operand .filter(op => !!op) .map(op => { return `${contextKey} eq ${this.quoteString(op)}`; }); return this.glue(statements, 'or'); }, __bygroupid: (operand) => { return `bygroupid(${operand})`; }, __has: (operand) => { return `has(${operand})`; }, __isinhierarchyof: (operand) => { return `isinhierarchyof(${operand})`; }, __hasany: (operand) => { return `hasany(${operand})`; }, __useFilterQueryString: (queryString) => { var _a; // match everything inside the most exterior parentheses, including them const query = (_a = queryString.match(/\(.*\)/)) === null || _a === void 0 ? void 0 : _a[0]; // get rid of the most exterior parentheses return query === null || query === void 0 ? void 0 : query.substring(1, query.length - 1); } }; } /** * Builds query string from provided query object. * * @param query Object containing filters and sort order for querying managed objects. Supported filters are: * - **__and** - Specifies conditions, e.g. `{__and: [{__has: 'c8y_IsDevice'}, {'count': {__gt: 0}}]}`. * - **__or** - Specifies alternative conditions, e.g. `{__or: [{__bygroupid: 10300}, {__bygroupid: 10400}]}`. * - **__eq** - Specified fragment must be equal to given value, e.g. `{'status': 'AVAILABLE'}` (no nested object required). * - **__lt** - Specified fragment must be less then given value, e.g. `{'count': {__lt: 10}}`. * - **__gt** - Specified fragment must be greater then given value, e.g. `{'count': {__gt: 0}}`. * - **__in** - Specified fragment must be equal to one of values in the list, e.g. `{'status': {__in: ['AVAILABLE', 'UNAVAILABLE']}}`. * - **__not** - Negates condition, e.g. `{__not: {'status': 'AVAILABLE'}}`. * - **__bygroupid** - True if filtered managed object is assigned to given group, e.g. `{__bygroupid: 10300}`. * - **__has** - Specified fragment must have a value defined, e.g. `{__has: 'c8y_IsDevice'}`. * - **__hasany** - Matches objects having at least one of the fragments defined, e.g. `{__has: ['c8y_IsDevice', 'c8y_Dashboard']}`. * - **__isinhierarchyof** - Matches objects that are in hierarchy of given managed object, e.g. `{__isinhierarchyof: '12345'}`. * - **__useFilterQueryString** - Gets rid of the `$filter=()… $orderby=…` parts of a query and keeps only what's between the most * exterior parentheses of the $filter. * EXAMPLE: takes a query of the form * `$filter=(name eq 'RaspPi*') $orderby=name asc` * and turns it into * `name eq 'RaspPi*'` * This is necessary for searching for smart groups, which are identified by their own query * that needs to be passed through. * * Note: if you want to specify the order, you need to wrap your filters within `__filter` property and then add `__orderby` with the array of field paths and sort directions (1 for ascending, -1 for descending), for example: * - `{ __filter: { ... }, __orderby: [{ 'creationTime': -1 }, { 'name': 1 }] }` * * @returns {string} Returns a query string ready to be sent in request params to backend. * * **Example** * ```typescript * const query = { * __filter: { * 'name': 'My Device*', * 'c8y_Availability.status': { * __in: ['AVAILABLE', 'UNAVAILABLE'] * }, * 'creationTime': { * __lt: '2015-11-30T13:28:123Z' * }, * 'c8y_ActiveAlarmsStatus.critical': { * __gt: 0 * }, * __or: [ * {__not: {__has: 'c8y_ActiveAlarmsStatus.major'}}, * { * __or: [ * {__bygroupid: 10300}, * {__bygroupid: 10400} * ] * } * ] * }, * __orderby: [ * {'name': 1}, * {'creationTime': -1}, * {'c8y_ActiveAlarmsStatus.critical': -1} * ] * }; * * const params = { * query: queriesUtil.buildQuery(query) * }; * ``` */ buildQuery(query) { const q = []; const queryObjectRoot = query.__filter || query; const filter = this.buildQueryFilter(queryObjectRoot); const orderBy = this.buildQueryOrderby(query.__orderby); if (filter) { q.push(`$filter=(${filter})`); } if (orderBy) { q.push(`$orderby=${orderBy}`); } return q.join(' '); } buildQueryFilter(queryFilter, _queryKey, _glueType) { const queryKey = _queryKey || null; const glueType = _glueType || 'and'; const q = new Array(); if (Array.isArray(queryFilter)) { for (const qFilter of queryFilter) { const _q = this.buildQueryFilter(qFilter, null, glueType); if (_q) { q.push(_q); } } } else { for (const k of Object.keys(queryFilter)) { if (this.operatorFns[k] !== undefined) { const _q = this.operatorFns[k](queryFilter[k], queryKey); if (_q) { q.push(_q); } } else { const _q = this.operatorFns.__eq(queryFilter[k], k); if (_q) { q.push(_q); } } } } return this.glue(q, glueType); } buildQueryOrderby(queryOrderbys) { const o = []; if (queryOrderbys) { for (const q of queryOrderbys) { for (const k of Object.keys(q)) { if (q[k] !== 0) { o.push(`${k} ${q[k] > 0 ? 'asc' : 'desc'}`); } } } } return o.join(','); } addAndFilter(query, filter) { return this.addFilter(query, filter, 'and'); } addOrFilter(query, filter) { return this.addFilter(query, filter, 'or'); } addFilter(query, filter, operator) { const oldFilter = query.__orderby ? (query.__filter || {}) : (query.__filter || query); const newFilter = { [`__${operator}`]: this.skipEmptyObjects([oldFilter, filter]) }; if (!query.__filter && !query.__orderby) { return newFilter; } query.__filter = newFilter; return query; } prependOrderbys(query, orderbys) { return this.addOrderbys(query, orderbys, 'prepend'); } appendOrderbys(query, orderbys) { return this.addOrderbys(query, orderbys, 'append'); } addOrderbys(query, orderbys, how) { const oldFilter = query.__orderby ? (query.__filter || {}) : (query.__filter || query); const oldOrderbys = query.__orderby || []; const newOrderbys = how === 'prepend' ? [...orderbys, ...oldOrderbys] : [...oldOrderbys, ...orderbys]; const newQuery = { __orderby: this.skipEmptyObjects(newOrderbys) }; if (!this.isEmptyObject(oldFilter)) { newQuery.__filter = oldFilter; } return newQuery; } extractAndMergeOrderBys(queries) { if ((queries === null || queries === void 0 ? void 0 : queries.length) > 0) { const orderByQuery = queries .map(query => { const token = '$orderby='; const tokenIndex = query.lastIndexOf(token); return tokenIndex !== -1 ? query.substring(tokenIndex + token.length) : null; }) .filter(orderBy => orderBy !== null) .join(','); return orderByQuery ? `$orderby=${orderByQuery}` : ''; } } glue(statements, type) { return statements.length > 1 ? `(${statements.join(`) ${type} (`)})` : statements[0]; } useAsFloat(operand) { operand = this.quoteString(operand); if (typeof operand === 'number') { return `${operand}f`; } return operand; } quoteString(s) { if (typeof s === 'string') { return `'${this.escapeString(s)}'`; } return s; } skipEmptyObjects(objs) { return objs.filter(obj => !this.isEmptyObject(obj)); } isEmptyObject(obj) { return Object.keys(obj).length === 0; } /** * Escapes a string to be used in OData query. * * - OData does not support single quotes in the query. We need to replace all single quotes with double quotes. * - OData uses parentheses for grouping expressions and functions. We need to escape any parentheses in the string. * - OData uses backslash as an escape character. We need to escape any backslashes in the string. * * Spec: http://docs.oasis-open.org/odata/odata/v4.01/cs01/part2-url-conventions/odata-v4.01-cs01-part2-url-conventions.html#sec_URLComponents * * @param s String to be escaped. * @returns Escaped string. */ escapeString(s) { return s .replace(/'/g, "''") .replace(/\\/g, '\\\\') .replace(/(\[|\{|\(|\]|\}|\))/g, '\\$1'); } } //# sourceMappingURL=QueriesUtil.js.map