Convert presets preconditions to use expressions

This commit is contained in:
Shweelan Samson authored and Zaid Abdulla committed 2019-03-06 20:35:41 -08:00
1 parent 152d8f5283
commit edadfe54c8
7 files changed
+123 -174

No files matched your search

+6 -8
View File
@@ -25,7 +25,7 @@ import * as config from "./config";
import * as common from "./common";
import * as soap from "./soap";
import * as session from "./session";
import * as query from "./query";
import { evaluate, extractParams } from "./common/expression";
import * as device from "./device";
import * as cache from "./cache";
import * as localCache from "./local-cache";
@@ -279,7 +279,7 @@ function transferComplete(sessionContext, rpc): void {
);
}
// Append providions and remove duplicates
// Append provisions and remove duplicates
function appendProvisions(original, toAppend): boolean {
let modified = false;
const stringified = new WeakMap();
@@ -384,11 +384,8 @@ function applyPresets(sessionContext: SessionContext): void {
}
filteredPresets.push(preset);
for (const k of Object.keys(preset.precondition)) {
sessionContext.channels[preset.channel] = 0;
const p = k.split(/([^a-zA-Z0-9\-_.].*)/, 1)[0];
parameters[p] = Path.parse(p);
}
for (const k of extractParams(preset.precondition))
parameters[k] = Path.parse(k);
}
const declarations = Object.values(parameters).map(v => ({
@@ -429,8 +426,9 @@ function applyPresets(sessionContext: SessionContext): void {
session.addProvisions(sessionContext, whiteList, whiteListProvisions);
const appendProvisionsToFaults = {};
const now = Date.now();
for (const p of filteredPresets) {
if (query.testFilter(parameterValues, p.precondition)) {
if (evaluate(p.precondition, parameterValues, now)) {
if (blackList[p.channel] === 2) {
appendProvisionsToFaults[p.channel] = (
appendProvisionsToFaults[p.channel] || []
+10 -4
View File
@@ -22,7 +22,7 @@ import * as crypto from "crypto";
import * as config from "./config";
import * as db from "./db";
import * as cache from "./cache";
import * as query from "./query";
import { mongoQueryToFilter } from "./mongodb-functions";
import * as logger from "./logger";
import * as scheduling from "./scheduling";
import * as expression from "./common/expression";
@@ -164,9 +164,15 @@ function refresh(callback): void {
}
const events = preset.events || {};
const precondition = query.convertMongoQueryToFilters(
JSON.parse(preset.precondition)
);
let precondition;
try {
precondition = parse(preset.precondition);
} catch (error) {
precondition = mongoQueryToFilter(
JSON.parse(preset.precondition)
);
}
const _provisions = preset.provisions || [];
// Generate provisions from the old configuration format
@@ -1,7 +1,7 @@
import { ObjectID } from "mongodb";
import { map, parse, stringify } from "../common/expression-parser";
import { likePatternToRegExp, evaluate } from "../common/expression";
import { Expression, Fault, Task } from "../types";
import { map, parse, stringify } from "./common/expression-parser";
import { likePatternToRegExp, evaluate } from "./common/expression";
import { Expression, Fault, Task } from "./types";
const isArray = Array.isArray;
@@ -436,33 +436,44 @@ export function flattenTask(task): Task {
return t;
}
function mongoQueryToFilter(query): Expression {
const expressions = [];
function recursive(_query): void {
export function mongoQueryToFilter(query): Expression {
function recursive(_query): Expression {
const expressions: Expression[] = [];
for (const [k, v] of Object.entries(_query)) {
if (k[0] === "$") {
if (k === "$and") for (const vv of Object.values(v)) recursive(vv);
else throw new Error(`Operator ${k} not supported`);
if (k === "$and") {
const and: Expression = ["AND"];
for (const vv of Object.values(v)) and.push(recursive(vv));
expressions.push(and);
} else if (k === "$or") {
const or: Expression = ["OR"];
for (const vv of Object.values(v)) or.push(recursive(vv));
expressions.push(or);
} else {
throw new Error(`Operator ${k} not supported`);
}
} else if (k === "_tags") {
if (typeof v === "object") {
if (isArray(v)) throw new Error(`Invalid type`);
for (const [kk, vv] of Object.entries(v)) {
if (kk === "$ne")
expressions.push(["IS NULL", ["PARAM", `Tags.${vv}`]]);
else throw new Error(`Operator ${kk} not supported`);
}
if (v.hasOwnProperty("$ne"))
expressions.push(["IS NULL", ["PARAM", `Tags.${v["$ne"]}`]]);
else throw new Error(`Invalid tag query`);
} else {
expressions.push(["IS NOT NULL", ["PARAM", `Tags.${v}`]]);
}
} else if (k.startsWith("Tags.")) {
expressions.push([
v["$exists"] ? "IS NOT NULL" : "IS NULL",
["PARAM", k]
]);
let exists: boolean;
if (typeof v === "boolean") exists = v;
else if (v.hasOwnProperty("$ne")) exists = !v["$ne"];
else if (v.hasOwnProperty("$exists")) exists = !!v["$exists"];
else throw new Error(`Invalid tag query`);
expressions.push([exists ? "IS NOT NULL" : "IS NULL", ["PARAM", k]]);
} else if (typeof v === "object") {
if (isArray(v)) throw new Error(`Invalid type`);
const exps: Expression[] = [];
for (const [kk, vv] of Object.entries(v)) {
let op;
switch (kk) {
@@ -487,27 +498,36 @@ function mongoQueryToFilter(query): Expression {
default:
throw new Error(`Operator ${kk} not supported`);
}
expressions.push([op, ["PARAM", k], vv]);
exps.push([op, ["PARAM", k], vv]);
}
if (exps.length === 1) return exps[0];
const and: Expression = ["AND"];
expressions.push(and.concat(exps));
} else {
expressions.push(["=", ["PARAM", k], v]);
}
}
if (expressions.length === 1) return expressions[0];
const and: Expression = ["AND"];
return and.concat(expressions);
}
recursive(query);
if (expressions.length > 1) return ["AND"].concat(expressions);
// empty filter
if (!Object.keys(query).length) return true;
if (expressions.length === 1) return expressions[0];
return expressions;
return recursive(query);
}
export function flattenPreset(preset): {} {
const p = Object.assign({}, preset);
if (p.precondition) {
p.precondition = mongoQueryToFilter(JSON.parse(p.precondition));
p.precondition = p.precondition.length ? stringify(p.precondition) : "";
try {
// Try parse to check expression validity
parse(p.precondition);
} catch (error) {
p.precondition = mongoQueryToFilter(JSON.parse(p.precondition));
p.precondition = p.precondition.length ? stringify(p.precondition) : "";
}
}
if (p.events) {
@@ -546,10 +566,10 @@ export function flattenFile(file): {} {
export function preProcessPreset(data): {} {
const preset = Object.assign({}, data);
preset.precondition = preset.precondition
? filterToMongoQuery(parse(preset.precondition))
: {};
preset.precondition = JSON.stringify(preset.precondition);
if (!preset.precondition) preset.precondition = "";
// Try parse to check expression validity
parse(preset.precondition);
preset.weight = parseInt(preset.weight) || 0;
-129
View File
@@ -216,135 +216,6 @@ export function test(params, query): boolean {
return true;
}
function matchType(src, dst): string | number | boolean {
const type = typeof src;
if (type === "string") {
return String(dst);
} else if (type === "number") {
if (+dst === parseFloat(dst)) return +dst;
else if (!isNaN(Date.parse(dst))) return Date.parse(dst);
} else if (type === "boolean") {
const v = String(dst)
.trim()
.toLowerCase();
if (v === "true" || v === "1") return true;
else if (v === "false" || v === "0") return false;
}
return dst;
}
export function testFilter(obj, filter): boolean {
for (const [k, v] of Object.entries(filter)) {
const [param, op] = k.split(/([^a-zA-Z0-9\-_.].*)/, 2);
const val = matchType(obj[param], v);
switch (op) {
case "=":
case undefined:
if (obj[param] !== val) return false;
break;
case ">":
if (!(obj[param] > val)) return false;
break;
case ">=":
if (!(obj[param] >= val)) return false;
break;
case "<":
if (!(obj[param] < val)) return false;
break;
case "<=":
if (!(obj[param] <= val)) return false;
break;
case "!":
if (obj[param] === val) return false;
break;
default:
throw new Error(`Unrecognized operator ${op}`);
}
}
return true;
}
export function convertMongoQueryToFilters(query, filters = {}): {} {
for (let [k, v] of Object.entries(query)) {
if (k[0] === "$") {
if (k === "$and")
for (const vv of v as any[]) convertMongoQueryToFilters(vv, filters);
else throw new Error(`Operator ${k} not supported`);
} else if (k === "_tags") {
if (common.typeOf(v) === common.OBJECT_TYPE) {
for (let [kk, vv] of Object.entries(v)) {
vv = vv.replace(/[^a-zA-Z0-9-]+/g, "_");
if (kk === "$ne") filters[`Tags.${vv}!`] = true;
else throw new Error(`Operator ${kk} not supported`);
}
} else {
v = (v as string).replace(/[^a-zA-Z0-9-]+/g, "_");
filters[`Tags.${v}`] = true;
}
} else {
switch (k) {
case "_id":
k = "DeviceID.ID";
break;
case "_deviceId._Manufacturer":
k = "DeviceID.Manufacturer";
break;
case "_deviceId._OUI":
k = "DeviceID.OUI";
break;
case "_deviceId._ProductClass":
k = "DeviceID.ProductClass";
break;
case "_deviceId._SerialNumber":
k = "DeviceID.SerialNumber";
break;
case "_lastInform":
k = "Events.Inform";
break;
case "_lastBootstrap":
k = "Events.0_BOOTSTRAP";
break;
case "_lastBoot":
k = "Events.1_BOOT";
break;
case "_registered":
k = "Events.Registered";
}
if (common.typeOf(v) === common.OBJECT_TYPE) {
for (const [kk, vv] of Object.entries(v)) {
switch (kk) {
case "$eq":
filters[k] = vv;
break;
case "$ne":
filters[`${k}!`] = vv;
break;
case "$lt":
filters[`${k}<`] = vv;
break;
case "$lte":
filters[`${k}<=`] = vv;
break;
case "$gt":
filters[`${k}>`] = vv;
break;
case "$gte":
filters[`${k}>=`] = vv;
break;
default:
throw new Error(`Oprator ${kk} not supported`);
}
}
} else {
filters[k] = v;
}
}
}
return filters;
}
export function sanitizeQueryTypes(query, types): {} {
for (const [k, v] of Object.entries(query)) {
if (k[0] === "$") {
+2 -3
View File
@@ -1,8 +1,7 @@
import { MongoClient, GridFSBucket } from "mongodb";
import * as config from "../config";
import * as mongodbFunctions from "./mongodb-functions";
import * as mongodbFunctions from "../mongodb-functions";
import * as expression from "../common/expression";
import { preProcessPreset } from "./mongodb-functions";
import { QueryOptions } from "../types";
const CACHE_TTL = 300000;
@@ -257,7 +256,7 @@ function deleteResource(resource, id): Promise<void> {
}
export function putPreset(id, object): Promise<void> {
object = preProcessPreset(object);
object = mongodbFunctions.preProcessPreset(object);
return putResource("presets", id, object);
}