@coocoon/react-awesome-query-builder
Version:
User-friendly query builder for React. Demo: https://ukrbublik.github.io/react-awesome-query-builder
1,091 lines (1,034 loc) • 31 kB
JavaScript
import { SpelExpressionEvaluator } from "spel2js";
import uuid from "../utils/uuid";
import {
getFieldConfig,
extendConfig,
normalizeField,
} from "../utils/configUtils";
import { getWidgetForFieldOp } from "../utils/ruleUtils";
import { loadTree } from "./tree";
import {
defaultConjunction,
defaultGroupConjunction,
} from "../utils/defaultUtils";
import { logger } from "../utils/stuff";
import moment from "moment";
import _ from "lodash";
export const loadFromSpel = (logicTree, config) => {
return _loadFromSpel(logicTree, config, true);
};
export const _loadFromSpel = (spelStr, config, returnErrors = true) => {
//meta is mutable
let meta = {
errors: [],
};
const extendedConfig = extendConfig(config);
const conv = buildConv(extendedConfig);
let compiledExpression;
let convertedObj;
let jsTree = undefined;
try {
const compileRes = SpelExpressionEvaluator.compile(spelStr);
compiledExpression = compileRes._compiledExpression;
} catch (e) {
meta.errors.push(e);
}
if (compiledExpression) {
logger.debug("compiledExpression:", compiledExpression);
convertedObj = convertCompiled(compiledExpression, meta);
logger.debug("convertedObj:", convertedObj, meta);
jsTree = convertToTree(convertedObj, conv, extendedConfig, meta);
if (jsTree && jsTree.type != "group" && jsTree.type != "switch_group") {
jsTree = wrapInDefaultConj(jsTree, extendedConfig);
}
logger.debug("jsTree:", jsTree);
}
const immTree = jsTree ? loadTree(jsTree) : undefined;
if (returnErrors) {
return [immTree, meta.errors];
} else {
if (meta.errors.length)
console.warn("Errors while importing from SpEL:", meta.errors);
return immTree;
}
};
const convertCompiled = (expr, meta, parentExpr = null) => {
const type = expr.getType();
let children = expr
.getChildren()
.map((child) => convertCompiled(child, meta, expr));
// flatize OR/AND
if (type == "op-or" || type == "op-and") {
children = children.reduce((acc, child) => {
const canFlatize = child.type == type && !child.not;
const flat = canFlatize ? child.children : [child];
return [...acc, ...flat];
}, []);
}
// unwrap NOT
if (type == "op-not") {
if (children.length != 1) {
meta.errors.push(
`Operator NOT should have 1 child, but got ${children.length}}`
);
}
return {
...children[0],
not: !(children[0].not || false),
};
}
if (type == "compound") {
// remove `.?[true]`
children = children.filter((child) => {
const isListFix =
child.type == "selection" &&
child.children.length == 1 &&
child.children[0].type == "boolean" &&
child.children[0].val == true;
return !isListFix;
});
// aggregation
// eg. `results.?[product == 'abc'].length`
const selection = children.find((child) => child.type == "selection");
if (selection && selection.children.length != 1) {
meta.errors.push(
`Selection should have 1 child, but got ${selection.children.length}`
);
}
const filter = selection ? selection.children[0] : null;
const lastChild = children[children.length - 1];
const isSize =
(lastChild.type == "method" && lastChild.val.methodName == "size") ||
(lastChild.type == "!func" && lastChild.methodName == "size");
const isLength = lastChild.type == "property" && lastChild.val == "length";
const sourceParts = children.filter(
(child) => child !== selection && child !== lastChild
);
const source = {
type: "compound",
children: sourceParts,
};
if (isSize || isLength) {
return {
type: "!aggr",
filter,
source,
};
}
// remove `#this`, `#root`
children = children.filter((child) => {
const isThis = child.type == "variable" && child.val == "this";
const isRoot = child.type == "variable" && child.val == "root";
return !(isThis || isRoot);
});
// indexer
children = children.map((child) => {
if (child.type == "indexer" && child.children.length == 1) {
return {
type: "indexer",
val: child.children[0].val,
itype: child.children[0].type,
};
} else {
return child;
}
});
// method
if (lastChild.type == "method") {
const obj = children.filter((child) => child !== lastChild);
return {
type: "!func",
obj,
methodName: lastChild.val.methodName,
args: lastChild.val.args,
};
}
// !func
if (lastChild.type == "!func") {
const obj = children.filter((child) => child !== lastChild);
return {
...lastChild,
obj,
};
}
}
// getRaw || getValue
let val;
try {
if (expr.getRaw) {
// use my fork
val = expr.getRaw();
} else if (expr.getValue.length == 0) {
// getValue not requires context arg -> can use
val = expr.getValue();
}
} catch (e) {
logger.error("[spel2js] Error in getValue()", e);
}
// ternary
if (type == "ternary") {
val = flatizeTernary(children);
}
// convert method/function args
if (typeof val === "object" && val !== null) {
if (val.methodName || val.functionName) {
val.args = val.args.map((child) => convertCompiled(child, meta, expr));
}
}
// convert list
if (type == "list") {
val = val.map((item) => convertCompiled(item, meta, expr));
// fix whole expression wrapped in `{}`
if (!parentExpr && val.length == 1) {
return val[0];
}
}
// convert constructor
if (type == "constructorref") {
const qid = children.find((child) => child.type == "qualifiedidentifier");
const cls = qid?.val;
if (!cls) {
meta.errors.push(
`Can't find qualifiedidentifier in constructorref children: ${JSON.stringify(
children
)}`
);
return undefined;
}
const args = children.filter(
(child) => child.type != "qualifiedidentifier"
);
return {
type: "!new",
cls,
args,
};
}
// convert type
if (type == "typeref") {
const qid = children.find((child) => child.type == "qualifiedidentifier");
const cls = qid?.val;
if (!cls) {
meta.errors.push(
`Can't find qualifiedidentifier in typeref children: ${JSON.stringify(
children
)}`
);
return undefined;
}
const _args = children.filter(
(child) => child.type != "qualifiedidentifier"
);
return {
type: "!type",
cls,
};
}
// convert function/method
if (type == "function" || type == "method") {
// `foo()` is method, `#foo()` is function
// let's use common property `methodName` and just add `isVar` for function
const { functionName, methodName, args } = val;
return {
type: "!func",
methodName: functionName || methodName,
isVar: type == "function",
args,
};
}
return {
type,
children,
val,
};
};
const flatizeTernary = (children) => {
let flat = [];
function _processTernaryChildren(tern) {
let [cond, if_val, else_val] = tern;
flat.push([cond, if_val]);
if (else_val?.type == "ternary") {
_processTernaryChildren(else_val.children);
} else {
flat.push([undefined, else_val]);
}
}
_processTernaryChildren(children);
return flat;
};
const buildConv = (config) => {
let operators = {};
for (let opKey in config.operators) {
const opConfig = config.operators[opKey];
if (opConfig.spelOps) {
// examples: "==", "eq", ".contains", "matches" (can be used for starts_with, ends_with)
opConfig.spelOps.forEach((spelOp) => {
const opk = spelOp; // + "/" + defaultValue(opConfig.cardinality, 1);
if (!operators[opk]) operators[opk] = [];
operators[opk].push(opKey);
});
} else if (opConfig.spelOp) {
const opk = opConfig.spelOp; // + "/" + defaultValue(opConfig.cardinality, 1);
if (!operators[opk]) operators[opk] = [];
operators[opk].push(opKey);
} else {
logger.log(`[spel] No spelOp for operator ${opKey}`);
}
}
let conjunctions = {};
for (let conjKey in config.conjunctions) {
const conjunctionDefinition = config.conjunctions[conjKey];
const ck = conjunctionDefinition.spelConj || conjKey.toLowerCase();
conjunctions[ck] = conjKey;
}
let funcs = {};
for (let funcKey in config.funcs) {
const funcConfig = config.funcs[funcKey];
let fk;
if (typeof funcConfig.spelFunc == "string") {
fk = funcConfig.spelFunc;
}
if (fk) {
if (!funcs[fk]) funcs[fk] = [];
funcs[fk].push(funcKey);
}
}
return {
operators,
conjunctions,
funcs,
};
};
const convertPath = (parts, meta) => {
let isError = false;
const res = parts.map((c) => {
if (
c.type == "variable" ||
c.type == "property" ||
(c.type == "indexer" && c.itype == "string")
) {
return c.val;
} else {
isError = true;
meta.errors.push(`Unexpected item in compound: ${JSON.stringify(c)}`);
}
});
return !isError ? res : undefined;
};
const convertArg = (spel, conv, config, meta, parentSpel) => {
if (spel == undefined) return undefined;
const { fieldSeparator } = config.settings;
const literalTypes = {
number: "number",
string: "text",
boolean: "boolean",
null: "null", // should not be
};
const groupFieldParts = parentSpel?._groupField
? [parentSpel?._groupField]
: [];
if (spel.type == "compound") {
// complex field
const parts = convertPath(spel.children, meta);
if (!parts) {
return undefined;
}
const fullParts = [...groupFieldParts, ...parts];
const isVariable = spel.children?.[0]?.type == "variable";
return {
valueSrc: "field",
//valueType: todo
isVariable,
value: fullParts.join(fieldSeparator),
};
} else if (spel.type == "variable" || spel.type == "property") {
// normal field
const fullParts = [...groupFieldParts, spel.val];
const isVariable = spel.type == "variable";
return {
valueSrc: "field",
//valueType: todo
isVariable,
value: fullParts.join(fieldSeparator),
};
} else if (literalTypes[spel.type]) {
let value = spel.val;
let valueType = literalTypes[spel.type];
if (parentSpel?.isUnary) {
value = -value;
}
return {
valueSrc: "value",
valueType,
value,
};
} else if (spel.type == "list") {
const values = spel.val.map((v) => convertArg(v, conv, config, meta, spel));
const _itemType = values.length ? values[0]?.valueType : null;
const value = values.map((v) => v?.value);
const valueType = "multiselect";
return {
valueSrc: "value",
valueType,
value,
};
} else if (spel.type == "!func") {
const { obj, methodName, args, isVar } = spel;
// todo: get from conv
const funcToOpMap = {
[".contains"]: "like",
[".startsWith"]: "starts_with",
[".endsWith"]: "ends_with",
["$contains"]: "select_any_in",
[".isInIpRange"]: "is_in_ip_range",
[".cidrs"]: "is_in_cidrs",
};
const convertedArgs = args.map((v) =>
convertArg(v, conv, config, meta, {
...spel,
_groupField: parentSpel?._groupField,
})
);
//todo: make dynamic: use funcToOpMap and check obj type in basic config
if (methodName == "contains" && obj && obj[0].type == "list") {
const convertedObj = obj.map((v) =>
convertArg(v, conv, config, meta, spel)
);
// {'yellow', 'green'}.?[true].contains(color)
if (
!(convertedArgs.length == 1 && convertedArgs[0].valueSrc == "field")
) {
meta.errors.push(
`Expected arg to method ${methodName} to be field but got: ${JSON.stringify(
convertedArgs
)}`
);
return undefined;
}
const field = convertedArgs[0].value;
if (
!(
convertedObj.length == 1 && convertedObj[0].valueType == "multiselect"
)
) {
meta.errors.push(
`Expected object of method ${methodName} to be inline list but got: ${JSON.stringify(
convertedObj
)}`
);
return undefined;
}
const opKey = funcToOpMap["$" + methodName];
const list = convertedObj[0];
return buildRule(config, meta, field, opKey, [list]);
} else if (funcToOpMap["." + methodName]) {
// user.login.startsWith('gg')
const opKey = funcToOpMap["." + methodName];
const parts = convertPath(obj, meta);
if (parts && convertedArgs.length == 1) {
const fullParts = [...groupFieldParts, ...parts];
const field = fullParts.join(fieldSeparator);
return buildRule(config, meta, field, opKey, convertedArgs);
}
} else if (
methodName == "parse" &&
obj &&
obj[0].type == "!new" &&
obj[0].cls.at(-1) == "SimpleDateFormat"
) {
// new java.text.SimpleDateFormat('yyyy-MM-dd').parse('2022-01-15')
const args = obj[0].args.map((v) =>
convertArg(v, conv, config, meta, {
...spel,
_groupField: parentSpel?._groupField,
})
);
if (!(args.length == 1 && args[0].valueType == "text")) {
meta.errors.push(
`Expected args of ${obj[0].cls.join(
"."
)}.${methodName} to be 1 string but got: ${JSON.stringify(args)}`
);
return undefined;
}
if (
!(convertedArgs.length == 1 && convertedArgs[0].valueType == "text")
) {
meta.errors.push(
`Expected args of ${obj[0].cls.join(
"."
)} to be 1 string but got: ${JSON.stringify(convertedArgs)}`
);
return undefined;
}
const dateFormat = args[0].value;
const dateString = convertedArgs[0].value;
const valueType = dateFormat.includes(" ") ? "datetime" : "date";
const field = null; // todo
const widget = valueType;
const fieldConfig = getFieldConfig(config, field);
const widgetConfig = config.widgets[widget || fieldConfig?.mainWidget];
const valueFormat = widgetConfig.valueFormat;
const dateVal = moment(dateString, moment.ISO_8601);
const value = dateVal.isValid() ? dateVal.format(valueFormat) : undefined;
return {
valueSrc: "value",
valueType,
value,
};
} else if (
methodName == "parse" &&
obj &&
obj[0].type == "!type" &&
obj[0].cls.at(-1) == "LocalTime"
) {
// time == T(java.time.LocalTime).parse('02:03:00')
if (
!(convertedArgs.length == 1 && convertedArgs[0].valueType == "text")
) {
meta.errors.push(
`Expected args of ${obj[0].cls.join(
"."
)} to be 1 string but got: ${JSON.stringify(convertedArgs)}`
);
return undefined;
}
const timeString = convertedArgs[0].value;
const valueType = "time";
const field = null; // todo
const widget = valueType;
const fieldConfig = getFieldConfig(config, field);
const widgetConfig = config.widgets[widget || fieldConfig?.mainWidget];
const valueFormat = widgetConfig.valueFormat;
const dateVal = moment(timeString, "HH:mm:ss");
const value = dateVal.isValid() ? dateVal.format(valueFormat) : undefined;
return {
valueSrc: "value",
valueType,
value,
};
} else if (config?.funcs?.[methodName]) {
var method = config.funcs[methodName];
const { returnType, label, args } = method;
if (_.size(args) != _.size(convertedArgs)) {
meta.errors.push(`Args size is not match ${_.size(args)}`);
return undefined;
}
for (let index = 0; index < convertedArgs.length; index++) {
const convertedArg = convertedArgs[index];
const agrName = Object.keys(args)[index];
const arg = args[agrName];
if (convertedArg.valueType !== "null" && arg.type !== "text") {
if (convertedArg.valueType !== arg.type) {
meta.errors.push(
`${arg.label}(args[${index}]) should be ${arg.type}`
);
return undefined;
}
}
}
// `foo()` is method, `#foo()` is function
// let's use common property `methodName` and just add `isVar` for function
let res = {
type: "!func",
methodName: methodName,
isVar: false,
args: convertedArgs,
};
return res;
} else {
meta.errors.push(`Unsupported method ${methodName}`);
}
} else if (spel.type == "op-plus" && parentSpel?.type == "ternary") {
return buildCaseValueConcat(spel, conv, config, meta);
} else {
meta.errors.push(`Can't convert arg of type ${spel.type}`);
}
return undefined;
};
const getOpkey = (spel, operators, defaultOperator) => {
if (!spel) {
return defaultOperator;
}
const { type } = spel;
if (!type) {
return defaultOperator;
}
const key = type.startsWith("op-") ? type.substring(3) : type;
for (var operator in operators) {
const { spelOp, spelOps } = operators[operator];
if (spelOp === key) {
return operator;
}
if (_.indexOf(spelOps, key) >= 0) {
return operator;
}
}
return defaultOperator;
};
const buildRule = (config, meta, field, opKey, convertedArgs) => {
if (convertedArgs.filter((v) => v === undefined).length) {
return undefined;
}
const fieldConfig = getFieldConfig(config, field);
if (!fieldConfig) {
meta.errors.push(`No config for field ${field}`);
return undefined;
}
const widget = getWidgetForFieldOp(config, field, opKey);
const widgetConfig = config.widgets[widget || fieldConfig.mainWidget];
const asyncListValuesArr = convertedArgs
.map((v) => v.asyncListValues)
.filter((v) => v != undefined);
const asyncListValues = asyncListValuesArr.length
? asyncListValuesArr[0]
: undefined;
let res = {
type: "rule",
id: uuid(),
properties: {
field: field,
operator: opKey,
value: convertedArgs.map((v) => v.value),
valueSrc: convertedArgs.map((v) => v.valueSrc),
valueType: convertedArgs.map((v) => {
if (v.valueSrc == "value") {
return widgetConfig?.type || fieldConfig?.type || v.valueType;
}
return v.valueType;
}),
asyncListValues,
},
};
return res;
};
const buildRuleGroup = (
{ groupFilter, groupFieldValue },
opKey,
convertedArgs,
config,
meta
) => {
if (groupFieldValue.valueSrc != "field")
throw `Bad groupFieldValue: ${JSON.stringify(groupFieldValue)}`;
const groupField = groupFieldValue.value;
let groupOpRule = buildRule(config, meta, groupField, opKey, convertedArgs);
if (!groupOpRule) return undefined;
const fieldConfig = getFieldConfig(config, groupField);
const mode = fieldConfig?.mode;
let res = {
...(groupFilter || {}),
type: "rule_group",
properties: {
...groupOpRule.properties,
...(groupFilter?.properties || {}),
mode,
},
};
if (!res.id) res.id = uuid();
return res;
};
const compareArgs = (
left,
right,
spel,
conv,
config,
meta,
parentSpel = null
) => {
if (left.type == right.type) {
if (left.type == "!aggr") {
const [leftSource, rightSource] = [left.source, right.source].map((v) =>
convertArg(v, conv, config, meta, {
...spel,
_groupField: parentSpel?._groupField,
})
);
//todo: check same filter
return leftSource.value == rightSource.value;
} else {
const [leftVal, rightVal] = [left, right].map((v) =>
convertArg(v, conv, config, meta, {
...spel,
_groupField: parentSpel?._groupField,
})
);
return leftVal.value == rightVal.value;
}
}
return false;
};
const convertToTree = (spel, conv, config, meta, parentSpel = null) => {
if (!spel) return undefined;
let res;
if (spel.type.indexOf("op-") == 0) {
let op = spel.type.slice("op-".length);
// unary
const isUnary =
(op == "minus" || op == "plus") && spel.children.length == 1;
if (isUnary) {
spel.isUnary = true;
return convertToTree(spel.children[0], conv, config, meta, spel);
}
// between
let isBetweenNormal =
op == "and" &&
spel.children.length == 2 &&
spel.children[0].type == "op-ge" &&
spel.children[1].type == "op-le";
let isBetweenRev =
op == "or" &&
spel.children.length == 2 &&
spel.children[0].type == "op-lt" &&
spel.children[1].type == "op-gt";
let isBetween = isBetweenNormal || isBetweenRev;
if (isBetween) {
const [left, from] = spel.children[0].children;
const [right, to] = spel.children[1].children;
const isNumbers = from.type == "number" && to.type == "number";
const isSameSource = compareArgs(
left,
right,
spel,
conv,
config,
meta,
parentSpel
);
if (isNumbers && isSameSource) {
const _fromValue = from.val;
const _toValue = to.val;
const oneSpel = {
type: "op-between",
children: [left, from, to],
};
return convertToTree(oneSpel, conv, config, meta, parentSpel);
}
}
// find op
let opKeys = conv.operators[op];
let opKey;
// todo: make dynamic, use basic config
if (op == "eq" && spel.children[1].type == "null") {
opKey = "is_null";
} else if (op == "ne" && spel.children[1].type == "null") {
opKey = "is_not_null";
} else if (
op == "le" &&
spel.children[1].type == "string" &&
spel.children[1].val == ""
) {
opKey = "is_empty";
} else if (
op == "gt" &&
spel.children[1].type == "string" &&
spel.children[1].val == ""
) {
opKey = "is_not_empty";
} else if (op == "between") {
opKey = "between";
opKeys = ["between"];
}
// convert children
const convertChildren = () =>
spel.children.map((child) =>
convertToTree(child, conv, config, meta, {
...spel,
_groupField: parentSpel?._groupField,
})
);
if (op == "and" || op == "or") {
const children1 = {};
const vals = convertChildren();
vals.forEach((v) => {
if (v) {
const id = uuid();
v.id = id;
if (v.type != undefined) {
if(v.type==='rule' && v?.properties?.operator &&v?.properties?.value.length > 0 &&v?.properties?.value[0] === undefined){
// not support funaction equeals, for example: getMetric('deba_count_max') == getMetric('deba_count_min')
meta.errors.push(`Unsupported function operates: ${JSON.stringify(v)}`);
}else{
children1[id] = v;
}
} else {
meta.errors.push(`Bad item in AND/OR: ${JSON.stringify(v)}`);
}
}
});
res = {
type: "group",
id: uuid(),
children1,
properties: {
conjunction: conv.conjunctions[op],
not: spel.not,
},
};
} else if (opKeys) {
const vals = convertChildren();
const fieldObj = vals[0];
let convertedArgs = vals.slice(1);
if (!opKey) {
opKey = opKeys[0];
}
if (!fieldObj) {
// LHS can't be parsed
} else if (fieldObj.groupFieldValue) {
// 1. group
if (fieldObj.groupFieldValue.valueSrc != "field") {
meta.errors.push(`Expected group field ${JSON.stringify(fieldObj)}`);
}
const groupField = fieldObj.groupFieldValue.value;
// some/all/none
const opArg = convertedArgs[0];
if (
opArg &&
opArg.groupFieldValue &&
opArg.groupFieldValue.valueSrc == "field" &&
opArg.groupFieldValue.value == groupField
) {
// group.?[...].size() == group.size()
opKey = "all";
convertedArgs = [];
} else if (
opKey == "equal" &&
opArg.valueSrc == "value" &&
opArg.valueType == "number" &&
opArg.value == 0
) {
opKey = "none";
convertedArgs = [];
} else if (
opKey == "greater" &&
opArg.valueSrc == "value" &&
opArg.valueType == "number" &&
opArg.value == 0
) {
opKey = "some";
convertedArgs = [];
}
res = buildRuleGroup(fieldObj, opKey, convertedArgs, config, meta);
} else if (fieldObj.type === "!func") {
//func
let res = {
type: "rule",
id: uuid(),
properties: {
field: fieldObj.methodName,
isFunc: true,
operator: opKey,
value: convertedArgs.map((v) => v.value),
valueSrc: convertedArgs.map((v) => v.valueSrc),
valueType: convertedArgs.map((v) => {
return v.valueType;
}),
args: fieldObj.args,
},
};
return res;
} else {
// 2. not group
if (fieldObj.valueSrc != "field") {
meta.errors.push(`Expected field ${JSON.stringify(fieldObj)}`);
}
const field = fieldObj.value;
if (opKeys.length > 1) {
logger.warn(`[spel] Spel operator ${op} can be mapped to ${opKeys}`);
//todo: it's naive
const widgets = opKeys.map((op) => ({
op,
widget: getWidgetForFieldOp(config, field, op),
}));
if (op == "eq" && opKey !== "is_null") {
const ws = widgets.find(({ op, widget }) => widget != "field");
opKey = ws.op;
}
}
res = buildRule(config, meta, field, opKey, convertedArgs);
}
} else {
if (!parentSpel) {
// try to parse whole `"str" + prop + #var` as ternary
res = buildSimpleSwitch(spel, conv, config, meta);
}
if (!res) {
meta.errors.push(`Can't convert op ${op}`);
}
}
} else if (spel.type == "!aggr") {
const groupFieldValue = convertToTree(spel.source, conv, config, meta, {
...spel,
_groupField: parentSpel?._groupField,
});
let groupFilter = convertToTree(spel.filter, conv, config, meta, {
...spel,
_groupField: groupFieldValue?.value,
});
if (groupFilter?.type == "rule") {
groupFilter = wrapInDefaultConj(groupFilter, config);
}
res = {
groupFilter,
groupFieldValue,
};
if (!parentSpel) {
// !aggr can't be in root, it should be compared with something
res = undefined;
meta.errors.push("Unexpected !aggr in root");
}
} else if (spel.type == "ternary") {
const children1 = {};
spel.val.forEach((v) => {
const [cond, val] = v;
const caseI = buildCase(cond, val, conv, config, meta, spel);
if (caseI) {
children1[caseI.id] = caseI;
}
});
res = {
type: "switch_group",
id: uuid(),
children1,
properties: {},
};
} else {
res = convertArg(spel, conv, config, meta, parentSpel);
if (res && !res.type && !parentSpel) {
// try to parse whole `"1"` as ternary
const sw = buildSimpleSwitch(spel, conv, config, meta);
if (sw) {
res = sw;
} else {
res = undefined;
meta.errors.push(
`Can't convert rule of type ${spel.type}, it looks like var/literal`
);
}
}
}
return res;
};
const buildSimpleSwitch = (val, conv, config, meta) => {
let children1 = {};
const cond = null;
const caseI = buildCase(cond, val, conv, config, meta);
if (caseI) {
children1[caseI.id] = caseI;
}
let res = {
type: "switch_group",
id: uuid(),
children1,
properties: {},
};
return res;
};
const buildCase = (cond, val, conv, config, meta, spel = null) => {
const valProperties = buildCaseValProperties(config, meta, conv, val, spel);
let caseI;
if (cond) {
caseI = convertToTree(cond, conv, config, meta, spel);
if (caseI && caseI.type) {
if (caseI.type != "group") {
caseI = wrapInDefaultConj(caseI, config);
}
caseI.type = "case_group";
} else {
meta.errors.push(`Unexpected case: ${JSON.stringify(caseI)}`);
caseI = undefined;
}
} else {
caseI = {
id: uuid(),
type: "case_group",
properties: {},
};
}
if (caseI) {
caseI.properties = {
...caseI.properties,
...valProperties,
};
}
return caseI;
};
const buildCaseValueConcat = (spel, conv, config, meta) => {
let flat = [];
function _processConcatChildren(children) {
children.map((child) => {
if (child.type == "op-plus") {
_processConcatChildren(child.children);
} else {
const convertedChild = convertArg(child, conv, config, meta, spel);
if (convertedChild) {
flat.push(convertedChild);
} else {
meta.errors.push(`Can't convert ${child.type} in concatenation`);
}
}
});
}
_processConcatChildren(spel.children);
return {
valueSrc: "value",
valueType: "case_value",
value: flat,
};
};
const buildCaseValProperties = (config, meta, conv, val, spel = null) => {
let valProperties = {};
let convVal;
if (val?.type == "op-plus") {
convVal = buildCaseValueConcat(val, conv, config, meta);
} else {
convVal = convertArg(val, conv, config, meta, spel);
}
const widgetDef = config.widgets["case_value"];
const importCaseValue = widgetDef?.spelImportValue;
if (importCaseValue) {
const [normVal, normErrors] = importCaseValue(convVal);
normErrors.map((e) => meta.errors.push(e));
if (normVal) {
valProperties = {
value: [normVal],
valueSrc: ["value"],
valueType: ["case_value"],
};
}
} else {
meta.errors.push("No fucntion to import case value");
}
return valProperties;
};
const wrapInDefaultConjRuleGroup = (
rule,
parentField,
parentFieldConfig,
config,
conj
) => {
if (!rule) return undefined;
return {
type: "rule_group",
id: uuid(),
children1: { [rule.id]: rule },
properties: {
conjunction: conj || defaultGroupConjunction(config, parentFieldConfig),
not: false,
field: parentField,
},
};
};
const wrapInDefaultConj = (rule, config, not = false) => {
return {
type: "group",
id: uuid(),
children1: { [rule.id]: rule },
properties: {
conjunction: defaultConjunction(config),
not: not,
},
};
};