@rudderstack/json-template-engine
Version:
A library for evaluating JSON template expressions.
356 lines (355 loc) • 15.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.convertToObjectMapping = void 0;
/* eslint-disable no-param-reassign */
const mapping_1 = require("../errors/mapping");
const types_1 = require("../types");
const common_1 = require("./common");
function createObjectExpression(props = []) {
return {
type: types_1.SyntaxType.OBJECT_EXPR,
props,
};
}
function createObjectPropExpressionWithSpread(value) {
return {
type: types_1.SyntaxType.OBJECT_PROP_EXPR,
value: {
type: types_1.SyntaxType.SPREAD_EXPR,
value,
},
};
}
function createObjectExpressionWithSpread(value) {
return createObjectExpression([createObjectPropExpressionWithSpread(value)]);
}
function findOrCreateObjectPropExpression(props, key) {
let match = props.find((prop) => prop.key === key);
if (!match) {
match = {
type: types_1.SyntaxType.OBJECT_PROP_EXPR,
key,
value: createObjectExpression(),
};
props.push(match);
}
return match;
}
function validateArrayIndexFilter(flatMapping, elements) {
for (let i = 0; i < elements.length; i++) {
if (elements[i] === undefined) {
throw new mapping_1.JsonTemplateMappingError(`Invalid mapping: missing a mapping at index#${i}; make sure to define all the mappings in order of indexes`, flatMapping.input, flatMapping.output);
}
}
}
function processArrayIndexFilter(flatMapping, currrentOutputPropAST, filter, isLastPart) {
const filterIndex = filter.indexes.elements[0].value;
if (currrentOutputPropAST.value.type !== types_1.SyntaxType.ARRAY_EXPR) {
const elements = [];
elements[filterIndex] = isLastPart ? flatMapping.inputExpr : currrentOutputPropAST.value;
currrentOutputPropAST.value = {
type: types_1.SyntaxType.ARRAY_EXPR,
elements,
};
}
else if (!currrentOutputPropAST.value.elements[filterIndex]) {
currrentOutputPropAST.value.elements[filterIndex] = isLastPart
? flatMapping.inputExpr
: createObjectExpression();
}
validateArrayIndexFilter(flatMapping, currrentOutputPropAST.value.elements);
const objectExpr = currrentOutputPropAST.value.elements[filterIndex];
if (!isLastPart && objectExpr?.type !== types_1.SyntaxType.OBJECT_EXPR) {
throw new mapping_1.JsonTemplateMappingError('Invalid mapping: invalid array index mapping', flatMapping.input, flatMapping.output);
}
return objectExpr;
}
function isPathWithEmptyPartsAndObjectRoot(expr) {
return (expr.type === types_1.SyntaxType.PATH &&
expr.parts.length === 0 &&
expr.root?.type === types_1.SyntaxType.OBJECT_EXPR);
}
function getPathExpressionForAllFilter(currentInputAST, root, parts = []) {
return {
type: types_1.SyntaxType.PATH,
root,
pathType: currentInputAST.pathType || types_1.PathType.UNKNOWN,
inferredPathType: currentInputAST.inferredPathType || types_1.PathType.UNKNOWN,
parts,
returnAsArray: true,
};
}
function validateResultOfAllFilter(objectExpr, flatMapping) {
if (!objectExpr?.props ||
objectExpr.type !== types_1.SyntaxType.OBJECT_EXPR ||
!Array.isArray(objectExpr.props)) {
throw new mapping_1.JsonTemplateMappingError('Invalid mapping: invalid array mapping', flatMapping.input, flatMapping.output);
}
}
function addToArrayToExpression(expr) {
return {
type: types_1.SyntaxType.PATH,
root: expr,
returnAsArray: true,
parts: [],
};
}
function mergeObjectFilterParts(existParts, newParts) {
for (let index = 0; index < newParts.length; index++) {
if (newParts[index].type === types_1.SyntaxType.OBJECT_FILTER_EXPR &&
existParts[index].type === types_1.SyntaxType.OBJECT_FILTER_EXPR) {
if (newParts[index].options?.index) {
existParts[index].options = { ...existParts[index].options, ...newParts[index].options };
}
if (newParts[index].filter.type !== types_1.SyntaxType.ALL_FILTER_EXPR) {
existParts[index].filter = newParts[index].filter;
}
}
}
}
function handleAllFilterIndexFound(currentInputAST, currentOutputPropAST, filterIndex, isLastPart) {
const matchedInputParts = currentInputAST.parts.splice(0, filterIndex + 1);
if (isPathWithEmptyPartsAndObjectRoot(currentOutputPropAST.value)) {
currentOutputPropAST.value = currentOutputPropAST.value.root;
}
if (currentOutputPropAST.value.type !== types_1.SyntaxType.PATH) {
matchedInputParts.push((0, common_1.createBlockExpression)(isLastPart ? currentInputAST : currentOutputPropAST.value));
currentOutputPropAST.value = getPathExpressionForAllFilter(currentInputAST, currentInputAST.root, matchedInputParts);
}
else {
mergeObjectFilterParts(currentOutputPropAST.value.parts, matchedInputParts);
}
currentInputAST.root = undefined;
}
function findAllFilterIndex(expr) {
let filterIndex = -1;
if (expr.type === types_1.SyntaxType.PATH) {
filterIndex = expr.parts.findIndex((part) => part.type === types_1.SyntaxType.OBJECT_FILTER_EXPR);
}
return filterIndex;
}
function handleAllFilterIndexNotFound(currentInputAST, currentOutputPropAST, isLastPart) {
if (currentOutputPropAST.value.type === types_1.SyntaxType.OBJECT_EXPR) {
const currObjectExpr = currentOutputPropAST.value;
currentOutputPropAST.value = isLastPart
? addToArrayToExpression(currentInputAST)
: getPathExpressionForAllFilter(currentInputAST, currObjectExpr);
return currObjectExpr;
}
if (isPathWithEmptyPartsAndObjectRoot(currentOutputPropAST.value)) {
return currentOutputPropAST.value.root;
}
}
function getNextObjectExpressionForAllFilter(flatMapping, currentOutputPropAST, isLastPart) {
const blockExpr = (0, common_1.getLastElement)(currentOutputPropAST.value.parts);
const objectExpr = isLastPart ? createObjectExpression() : blockExpr?.statements?.[0];
validateResultOfAllFilter(objectExpr, flatMapping);
return objectExpr;
}
function processAllFilter(flatMapping, currentOutputPropAST, isLastPart) {
const { inputExpr: currentInputAST } = flatMapping;
const filterIndex = findAllFilterIndex(currentInputAST);
if (filterIndex === -1) {
const objectExpr = handleAllFilterIndexNotFound(currentInputAST, currentOutputPropAST, isLastPart);
if (objectExpr) {
return objectExpr;
}
}
else {
handleAllFilterIndexFound(currentInputAST, currentOutputPropAST, filterIndex, isLastPart);
}
return getNextObjectExpressionForAllFilter(flatMapping, currentOutputPropAST, isLastPart);
}
function isWildcardSelector(expr) {
return expr.type === types_1.SyntaxType.SELECTOR && expr.prop?.value === '*';
}
function isOutputPartRegularSelector(outputPart) {
return (outputPart?.type === types_1.SyntaxType.SELECTOR &&
outputPart.prop?.value &&
outputPart.prop.value !== '*');
}
function processWildCardSelector(flatMapping, currentOutputPropAST, isLastPart = false) {
const currentInputAST = flatMapping.inputExpr;
const filterIndex = currentInputAST.parts.findIndex(isWildcardSelector);
if (filterIndex === -1) {
throw new mapping_1.JsonTemplateMappingError('Invalid mapping: input should have wildcard selector', flatMapping.input, flatMapping.output);
}
const matchedInputParts = currentInputAST.parts.splice(0, filterIndex);
currentInputAST.parts = currentInputAST.parts.slice(1);
if (currentOutputPropAST.value.type !== types_1.SyntaxType.PATH) {
matchedInputParts.push((0, common_1.createBlockExpression)(currentOutputPropAST.value));
currentOutputPropAST.value = {
type: types_1.SyntaxType.PATH,
root: currentInputAST.root,
pathType: currentInputAST.pathType,
inferredPathType: currentInputAST.inferredPathType,
parts: matchedInputParts,
};
}
currentInputAST.root = 'e.value';
const blockExpr = (0, common_1.getLastElement)(currentOutputPropAST.value.parts);
const blockObjectExpr = blockExpr.statements[0];
const objectExpr = createObjectExpression();
blockObjectExpr.props.push({
type: types_1.SyntaxType.OBJECT_PROP_EXPR,
key: {
type: types_1.SyntaxType.PATH,
root: 'e',
parts: [
{
type: types_1.SyntaxType.SELECTOR,
selector: '.',
prop: {
type: types_1.TokenType.ID,
value: 'key',
},
},
],
},
value: isLastPart ? currentInputAST : objectExpr,
contextVar: 'e',
});
return objectExpr;
}
function handleNextPart(flatMapping, partNum, currentOutputPropAST) {
const nextOutputPart = flatMapping.outputExpr.parts[partNum];
const prevOutputPart = flatMapping.outputExpr.parts[partNum - 1];
if (isOutputPartRegularSelector(prevOutputPart) &&
isOutputPartRegularSelector(nextOutputPart) &&
currentOutputPropAST.value.type !== types_1.SyntaxType.OBJECT_EXPR) {
currentOutputPropAST.value = createObjectExpressionWithSpread(currentOutputPropAST.value);
return currentOutputPropAST.value;
}
if (nextOutputPart.filter?.type === types_1.SyntaxType.ALL_FILTER_EXPR) {
const objectExpr = processAllFilter(flatMapping, currentOutputPropAST, partNum === flatMapping.outputExpr.parts.length - 1 && !nextOutputPart.options?.index);
if (nextOutputPart.options?.index) {
objectExpr.props.push({
type: types_1.SyntaxType.OBJECT_PROP_EXPR,
key: nextOutputPart.options?.index,
value: {
type: types_1.SyntaxType.PATH,
root: nextOutputPart.options?.index,
parts: [],
},
});
}
return objectExpr;
}
if (nextOutputPart.filter?.type === types_1.SyntaxType.ARRAY_INDEX_FILTER_EXPR) {
return processArrayIndexFilter(flatMapping, currentOutputPropAST, nextOutputPart.filter, partNum === flatMapping.outputExpr.parts.length - 1);
}
if (isWildcardSelector(nextOutputPart)) {
return processWildCardSelector(flatMapping, currentOutputPropAST, partNum === flatMapping.outputExpr.parts.length - 1);
}
}
function handleNextParts(flatMapping, partNum, currentOutputPropAST) {
let objectExpr = currentOutputPropAST.value;
let newPartNum = partNum;
while (newPartNum < flatMapping.outputExpr.parts.length) {
const nextObjectExpr = handleNextPart(flatMapping, newPartNum, currentOutputPropAST);
if (!nextObjectExpr) {
break;
}
if (isOutputPartRegularSelector(flatMapping.outputExpr.parts[newPartNum])) {
return nextObjectExpr;
}
newPartNum++;
objectExpr = nextObjectExpr;
}
return objectExpr;
}
function refineLeafOutputPropAST(inputExpr) {
if (inputExpr.type === types_1.SyntaxType.PATH &&
inputExpr.root === undefined &&
inputExpr.parts.length === 1 &&
inputExpr.parts[0].type === types_1.SyntaxType.BLOCK_EXPR) {
return inputExpr.parts[0].statements[0];
}
return inputExpr;
}
function handleLastOutputPart(flatMapping, currentOutputPropsAST, key) {
const outputPropAST = currentOutputPropsAST.find((prop) => prop.key === key);
if (!outputPropAST) {
currentOutputPropsAST.push({
type: types_1.SyntaxType.OBJECT_PROP_EXPR,
key,
value: refineLeafOutputPropAST(flatMapping.inputExpr),
});
}
else {
outputPropAST.value = {
type: types_1.SyntaxType.LOGICAL_OR_EXPR,
op: '||',
args: [outputPropAST.value, refineLeafOutputPropAST(flatMapping.inputExpr)],
};
}
}
function processFlatMappingPart(flatMapping, partNum, currentOutputPropsAST) {
const outputPart = flatMapping.outputExpr.parts[partNum];
if (!isOutputPartRegularSelector(outputPart)) {
return currentOutputPropsAST;
}
const key = outputPart.prop.value;
if (partNum === flatMapping.outputExpr.parts.length - 1) {
handleLastOutputPart(flatMapping, currentOutputPropsAST, key);
return currentOutputPropsAST;
}
const currentOutputPropAST = findOrCreateObjectPropExpression(currentOutputPropsAST, key);
const objectExpr = handleNextParts(flatMapping, partNum + 1, currentOutputPropAST);
return objectExpr.props;
}
function handleRootOnlyOutputMapping(flatMapping, outputAST) {
outputAST.props.push(createObjectPropExpressionWithSpread(flatMapping.inputExpr));
}
function validateMappingsForIndexVar(flatMapping, indexVar) {
if (flatMapping.inputExpr.type !== types_1.SyntaxType.PATH) {
throw new mapping_1.JsonTemplateMappingError('Invalid mapping: input should be path expression', flatMapping.input, flatMapping.output);
}
const foundIndexVar = flatMapping.inputExpr.parts.some((item) => item?.type === types_1.SyntaxType.OBJECT_FILTER_EXPR && item.options?.index === indexVar);
if (!foundIndexVar) {
throw new mapping_1.JsonTemplateMappingError(`Invalid mapping: index variable:${indexVar} not found in input path`, flatMapping.input, flatMapping.output);
}
}
function validateMapping(flatMapping) {
if (flatMapping.outputExpr.type !== types_1.SyntaxType.PATH) {
throw new mapping_1.JsonTemplateMappingError('Invalid mapping: output should be a path expression', flatMapping.input, flatMapping.output);
}
const lastPart = (0, common_1.getLastElement)(flatMapping.outputExpr.parts);
if (lastPart?.options?.index) {
validateMappingsForIndexVar(flatMapping, lastPart.options.index);
}
}
function processFlatMappingParts(flatMapping, objectExpr) {
let currentOutputPropsAST = objectExpr.props;
for (let i = 0; i < flatMapping.outputExpr.parts.length; i++) {
currentOutputPropsAST = processFlatMappingPart(flatMapping, i, currentOutputPropsAST);
}
}
/**
* Convert Flat to Object Mappings
*/
function convertToObjectMapping(flatMappingASTs) {
const outputAST = createObjectExpression();
let pathAST;
for (const flatMapping of flatMappingASTs) {
validateMapping(flatMapping);
let objectExpr = outputAST;
if (flatMapping.outputExpr.parts.length > 0) {
if (!isOutputPartRegularSelector(flatMapping.outputExpr.parts[0])) {
const objectPropExpr = {
type: types_1.SyntaxType.OBJECT_PROP_EXPR,
key: '',
value: pathAST || objectExpr,
};
objectExpr = handleNextParts(flatMapping, 0, objectPropExpr);
pathAST = objectPropExpr.value;
}
processFlatMappingParts(flatMapping, objectExpr);
}
else {
handleRootOnlyOutputMapping(flatMapping, outputAST);
}
}
return pathAST ?? outputAST;
}
exports.convertToObjectMapping = convertToObjectMapping;