UNPKG

@opra/common

Version:
151 lines (150 loc) 6.37 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FilterRules = void 0; require("../polifils/array-find-last.js"); const objects_1 = require("@jsopen/objects"); const valgen_1 = require("valgen"); const index_js_1 = require("../exception/index.js"); const index_js_2 = require("../helpers/index.js"); const index_js_3 = require("./ast/index.js"); const parse_js_1 = require("./parse.js"); class FilterRules { constructor(rules, options) { this._rules = new index_js_2.ResponsiveMap(); this._decoderCache = new WeakMap(); Object.defineProperty(this, '_rules', { value: new index_js_2.ResponsiveMap(null, { caseSensitive: options?.caseSensitive }), enumerable: false, }); if (rules) { for (const [k, v] of Object.entries(rules)) { this.set(k, v); } } } set(fieldName, options) { const operators = typeof options?.operators === 'string' ? options.operators.split(/\s*[,| ]\s*/) : options?.operators; this._rules.set(fieldName, (0, objects_1.omitUndefined)({ ...options, operators, })); } normalizeFilter(filter, currentType, scope) { const ast = typeof filter === 'string' ? (0, parse_js_1.parse)(filter) : filter; return this.normalizeFilterAst(ast, [], currentType, scope); } normalizeFilterAst(ast, stack, currentType, scope) { if (ast instanceof index_js_3.ComparisonExpression) { stack.push(ast); this.normalizeFilterAst(ast.left, stack, currentType); if (!(ast.left instanceof index_js_3.QualifiedIdentifier && ast.left.field)) { throw new TypeError(`Invalid filter query. Left side should be a data field.`); } // Check if filtering accepted for given field const rule = this._rules.get(ast.left.value); if (!rule) { throw new index_js_1.OpraException({ message: `Field '${ast.left.value}' is not available for filter operation`, code: 'UNACCEPTED_FILTER_FIELD', details: { field: ast.left.value, }, }); } // Check if filtering endpoint accepted for given field if (rule.operators && !rule.operators.includes(ast.op)) { throw new index_js_1.OpraException({ message: `'${ast.left.value}' field do not accept '${ast.op}' filter operator`, code: 'UNACCEPTED_FILTER_OPERATION', details: { field: ast.left.value, operator: ast.op, }, }); } if (rule.mappedField) ast.left.value = rule.mappedField; if (rule.prepare) ast.prepare = rule.prepare; this.normalizeFilterAst(ast.right, stack, currentType); stack.pop(); return ast; } if (ast instanceof index_js_3.LogicalExpression) { stack.push(ast); ast.items.forEach(item => this.normalizeFilterAst(item, stack, currentType)); stack.pop(); return ast; } if (ast instanceof index_js_3.ArithmeticExpression) { stack.push(ast); ast.items.forEach(item => this.normalizeFilterAst(item.expression, stack, currentType)); stack.pop(); return ast; } if (ast instanceof index_js_3.ArrayExpression) { stack.push(ast); ast.items.forEach(item => this.normalizeFilterAst(item, stack, currentType)); stack.pop(); return ast; } if (ast instanceof index_js_3.ParenthesizedExpression) { stack.push(ast); this.normalizeFilterAst(ast.expression, stack, currentType); stack.pop(); return ast; } if (ast instanceof index_js_3.QualifiedIdentifier && currentType) { ast.value = currentType.normalizeFieldPath(ast.value, { scope }); ast.field = currentType.getField(ast.value, scope); ast.dataType = ast.field.type; return ast; } if (ast instanceof index_js_3.Literal) { /** Check if comparison expression has in stack */ const compIdx = stack.findLastIndex(x => x instanceof index_js_3.ComparisonExpression); if (compIdx >= 0) { const comp = stack[compIdx]; /** If calling for right side of comparison */ if (ast === comp.right || stack[compIdx + 1] === comp.right) { /** Check if comparison expression left side is a field */ if (comp && comp.left instanceof index_js_3.QualifiedIdentifier && comp.left.field) { if (ast.value == null && !comp.left.field.required) return ast.value; let decoder; if (comp.op === 'like' || comp.op === '!like' || comp.op === 'ilike' || comp.op === '!ilike') { decoder = valgen_1.isString; } else decoder = this._decoderCache.get(comp.left.field); if (!decoder) { decoder = comp.left.field.type.generateCodec('decode', { scope, projection: '*', ignoreWriteonlyFields: true, coerce: true, }); this._decoderCache.set(comp.left.field, decoder); } ast.value = decoder(ast.value); } } } } return ast; } toJSON() { return this._rules.toObject(); } [Symbol.iterator]() { return this._rules.entries(); } } exports.FilterRules = FilterRules;