Skip to content

Commit

Permalink
feat: add support for context var index
Browse files Browse the repository at this point in the history
  • Loading branch information
koladilip committed Jun 28, 2024
1 parent 048f031 commit 32299b3
Show file tree
Hide file tree
Showing 11 changed files with 257 additions and 67 deletions.
8 changes: 6 additions & 2 deletions src/engine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,13 @@ export class JsonTemplateEngine {
return JsonTemplateEngine.translateExpression(JsonTemplateEngine.parse(template, options));
}

static reverseTranslate(expr: Expression, options?: EngineOptions): string {
static reverseTranslate(expr: Expression | FlatMappingPaths[], options?: EngineOptions): string {
const translator = new JsonTemplateReverseTranslator(options);
return translator.translate(expr);
let newExpr = expr;
if (Array.isArray(expr)) {
newExpr = JsonTemplateEngine.parseMappingPaths(expr as FlatMappingPaths[], options);
}
return translator.translate(newExpr as Expression);
}

static convertMappingsToTemplate(mappings: FlatMappingPaths[], options?: EngineOptions): string {
Expand Down
14 changes: 9 additions & 5 deletions src/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1443,9 +1443,9 @@ export class JsonTemplateParser {
return this.lexer.throwUnexpectedToken();
}

private static pathContainsVariables(parts: Expression[]): boolean {
private static shouldPathBeConvertedAsBlock(parts: Expression[]): boolean {
return parts
.filter((part) => part.type === SyntaxType.PATH_OPTIONS)
.filter((part, index) => part.type === SyntaxType.PATH_OPTIONS && index < parts.length - 1)

Check warning on line 1448 in src/parser.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
.some((part) => part.options?.index ?? part.options?.item);
}

Expand Down Expand Up @@ -1550,17 +1550,21 @@ export class JsonTemplateParser {
newPathExpr.parts.shift();
}

const shouldConvertAsBlock = JsonTemplateParser.pathContainsVariables(newPathExpr.parts);
const shouldConvertAsBlock = JsonTemplateParser.shouldPathBeConvertedAsBlock(newPathExpr.parts);
let lastPart = getLastElement(newPathExpr.parts);
let fnExpr: FunctionCallExpression | undefined;
if (lastPart?.type === SyntaxType.FUNCTION_CALL_EXPR) {
fnExpr = newPathExpr.parts.pop() as FunctionCallExpression;
}

lastPart = getLastElement(newPathExpr.parts);
if (lastPart?.type === SyntaxType.PATH_OPTIONS) {
newPathExpr.parts.pop();
if (lastPart?.type === SyntaxType.PATH_OPTIONS && lastPart.options?.toArray) {

Check warning on line 1561 in src/parser.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
newPathExpr.returnAsArray = lastPart.options?.toArray;
if (!lastPart.options.item && !lastPart.options.index) {

Check warning on line 1563 in src/parser.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
newPathExpr.parts.pop();

Check warning on line 1564 in src/parser.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
} else {

Check warning on line 1565 in src/parser.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
lastPart.options.toArray = false;

Check warning on line 1566 in src/parser.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 1567 in src/parser.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 1567 in src/parser.ts

View check run for this annotation

Codecov / codecov/patch

src/parser.ts#L1563-L1567

Added lines #L1563 - L1567 were not covered by tests
}

Check warning on line 1568 in src/parser.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
newPathExpr.parts = JsonTemplateParser.combinePathOptionParts(newPathExpr.parts);

Expand Down
8 changes: 5 additions & 3 deletions src/reverse_translator.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { JsonTemplateEngine } from './engine';
import { PathType } from './types';

describe('reverse_translator', () => {
it('should reverse translate with indentation', () => {
Expand All @@ -12,7 +13,7 @@ describe('reverse_translator', () => {

it('should reverse translate json mappings', () => {
const template = JsonTemplateEngine.reverseTranslate(
JsonTemplateEngine.parse([
[
{
input: '$.userId',
output: '$.user.id',
Expand Down Expand Up @@ -41,10 +42,11 @@ describe('reverse_translator', () => {
input: '$.products[?(@.category)].(@.price * @.quantity * (1 - $.discount / 100)).sum()',
output: '$.events[0].revenue',
},
]),
],
{ defaultPathType: PathType.JSON },
);
expect(template).toEqual(
'{\n "user": {\n "id": $.userId\n },\n "events": [{\n "items": $.products{.category}.({\n "discount": $.discount,\n "product_id": .id,\n "options": .variations[*].({\n "s": .size\n })[],\n "value": .price * .quantity * (1 - $.discount / 100)\n })[],\n "name": $.events[0],\n "revenue": $.products{.category}.(.price * .quantity * (1 - $.discount / 100)).sum()\n }]\n}',
'{\n "user": {\n "id": $.userId\n },\n "events": [{\n "items": $.products[?(@.category)].({\n "discount": $.discount,\n "product_id": @.id,\n "options": @.variations[*].({\n "s": @.size\n })[],\n "value": @.price * @.quantity * (1 - $.discount / 100)\n })[],\n "name": $.events[0],\n "revenue": $.products[?(@.category)].(@.price * @.quantity * (1 - $.discount / 100)).sum()\n }]\n}',
);
});
});
27 changes: 17 additions & 10 deletions src/translator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,17 +310,21 @@ export class JsonTemplateTranslator {
code.push(JsonTemplateTranslator.covertToArrayValue(data));
if (
JsonTemplateTranslator.isArrayFilterExpr(expr.parts[partNum]) ||
JsonTemplateTranslator.isAllFilterExpr(expr.parts[partNum]) ||
JsonTemplateTranslator.isToArray(expr, partNum)
) {
code.push(`${data} = [${data}];`);
}
return code.join('');
}

static isAllFilterExpr(expr: Expression): boolean {
return (
expr.type === SyntaxType.OBJECT_FILTER_EXPR && expr.filter.type === SyntaxType.ALL_FILTER_EXPR
);
}

private translatePathParts(expr: PathExpression, dest: string): string {
if (!expr.parts.length) {
return '';
}
const { parts } = expr;
const code: string[] = [];
const numParts = parts.length;
Expand Down Expand Up @@ -384,8 +388,9 @@ export class JsonTemplateTranslator {
}
const code: string[] = [];
code.push(this.translatePathRoot(expr, dest, ctx));
code.push(this.translatePathParts(expr, dest));
if (expr.returnAsArray && expr.parts.length === 0) {
if (expr.parts.length > 0) {
code.push(this.translatePathParts(expr, dest));
} else if (expr.returnAsArray) {
code.push(JsonTemplateTranslator.covertToArrayValue(dest));
}
return code.join('');
Expand Down Expand Up @@ -786,11 +791,13 @@ export class JsonTemplateTranslator {
ctx: string,
): string {
const code: string[] = [];
const condition = this.acquireVar();
code.push(JsonTemplateTranslator.generateAssignmentCode(condition, 'true'));
code.push(this.translateExpr(expr.filter, condition, ctx));
code.push(`if(!${condition}) {${dest} = undefined;}`);
this.releaseVars(condition);
if (expr.filter.type !== SyntaxType.ALL_FILTER_EXPR) {
const condition = this.acquireVar();
code.push(JsonTemplateTranslator.generateAssignmentCode(condition, 'true'));
code.push(this.translateExpr(expr.filter, condition, ctx));
code.push(`if(!${condition}) {${dest} = undefined;}`);
this.releaseVars(condition);
}
return code.join('');
}

Expand Down
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ export type FlatMappingPaths = {
};

export type FlatMappingAST = FlatMappingPaths & {
inputExpr: PathExpression;
inputExpr: Expression;
outputExpr: PathExpression;
};

Expand Down
154 changes: 114 additions & 40 deletions src/utils/converter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
/* eslint-disable no-param-reassign */
import { JsonTemplateMappingError } from '../errors/mapping';
import { EMPTY_EXPR } from '../constants';
import {
SyntaxType,
PathExpression,
Expand All @@ -13,6 +12,7 @@ import {
BlockExpression,
TokenType,
BinaryExpression,
PathType,
} from '../types';
import { createBlockExpression, getLastElement } from './common';

Expand Down Expand Up @@ -40,20 +40,23 @@ function findOrCreateObjectPropExpression(
}

function processArrayIndexFilter(
flatMapping: FlatMappingAST,
currrentOutputPropAST: ObjectPropExpression,
filter: IndexFilterExpression,
isLastPart: boolean,
): ObjectExpression {
const filterIndex = filter.indexes.elements[0].value;
if (currrentOutputPropAST.value.type !== SyntaxType.ARRAY_EXPR) {
const elements: Expression[] = [];
elements[filterIndex] = currrentOutputPropAST.value;
elements[filterIndex] = isLastPart ? flatMapping.inputExpr : currrentOutputPropAST.value;

Check warning on line 51 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
currrentOutputPropAST.value = {
type: SyntaxType.ARRAY_EXPR,
elements,
};
} else if (!currrentOutputPropAST.value.elements[filterIndex]) {
(currrentOutputPropAST.value as ArrayExpression).elements[filterIndex] =
createObjectExpression();
(currrentOutputPropAST.value as ArrayExpression).elements[filterIndex] = isLastPart

Check warning on line 57 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
? flatMapping.inputExpr

Check warning on line 58 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
: createObjectExpression();

Check warning on line 59 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 59 in src/utils/converter.ts

View check run for this annotation

Codecov / codecov/patch

src/utils/converter.ts#L57-L59

Added lines #L57 - L59 were not covered by tests
}

Check warning on line 60 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
return currrentOutputPropAST.value.elements[filterIndex];
}
Expand All @@ -67,24 +70,27 @@ function isPathWithEmptyPartsAndObjectRoot(expr: Expression) {
}

function getPathExpressionForAllFilter(
currentInputAST: PathExpression,
currentInputAST: Expression,
root: any,
parts: Expression[] = [],
): PathExpression {
return {
type: SyntaxType.PATH,
root,
pathType: currentInputAST.pathType,
inferredPathType: currentInputAST.inferredPathType,
pathType: currentInputAST.pathType || PathType.UNKNOWN,

Check warning on line 80 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
inferredPathType: currentInputAST.inferredPathType || PathType.UNKNOWN,

Check warning on line 81 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
parts,
returnAsArray: true,
} as PathExpression;
}

function validateResultOfAllFilter(objectExpr: Expression, flatMapping: FlatMappingAST) {
function validateResultOfAllFilter(
objectExpr: Expression | undefined,
flatMapping: FlatMappingAST,
) {
if (
!objectExpr?.props ||
objectExpr.type !== SyntaxType.OBJECT_EXPR ||
!objectExpr.props ||
!Array.isArray(objectExpr.props)
) {
throw new JsonTemplateMappingError(
Expand All @@ -95,45 +101,95 @@ function validateResultOfAllFilter(objectExpr: Expression, flatMapping: FlatMapp
}
}

function addToArrayToExpression(expr: Expression) {
return {
type: SyntaxType.PATH,
root: expr,
returnAsArray: true,
parts: [],
};
}

function handleAllFilterIndexFound(
currentInputAST: Expression,
currentOutputPropAST: ObjectPropExpression,
filterIndex: number,
isLastPart: boolean,
) {
const matchedInputParts = currentInputAST.parts.splice(0, filterIndex + 1);
if (isPathWithEmptyPartsAndObjectRoot(currentOutputPropAST.value)) {
currentOutputPropAST.value = currentOutputPropAST.value.root;
}

if (currentOutputPropAST.value.type !== SyntaxType.PATH) {
matchedInputParts.push(
createBlockExpression(isLastPart ? currentInputAST : currentOutputPropAST.value),

Check warning on line 126 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
);
currentOutputPropAST.value = getPathExpressionForAllFilter(
currentInputAST,
currentInputAST.root,
matchedInputParts,
);
}
currentInputAST.root = undefined;
}

function findAllFilterIndex(expr: Expression): number {
let filterIndex = -1;
if (expr.type === SyntaxType.PATH) {
filterIndex = expr.parts.findIndex((part) => part.type === SyntaxType.OBJECT_FILTER_EXPR);
}
return filterIndex;
}

function handleAllFilterIndexNotFound(
currentInputAST: Expression,
currentOutputPropAST: ObjectPropExpression,
isLastPart: boolean,
): ObjectExpression | undefined {
if (currentOutputPropAST.value.type === SyntaxType.OBJECT_EXPR) {
const currObjectExpr = currentOutputPropAST.value as ObjectExpression;
currentOutputPropAST.value = isLastPart
? addToArrayToExpression(currentInputAST)

Check warning on line 153 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
: getPathExpressionForAllFilter(currentInputAST, currObjectExpr);
return currObjectExpr;
}
if (isPathWithEmptyPartsAndObjectRoot(currentOutputPropAST.value)) {
return currentOutputPropAST.value.root as ObjectExpression;

Check warning on line 158 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 159 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 159 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 159 in src/utils/converter.ts

View check run for this annotation

Codecov / codecov/patch

src/utils/converter.ts#L158-L159

Added lines #L158 - L159 were not covered by tests
}

function getNextObjectExpressionForAllFilter(
flatMapping: FlatMappingAST,
currentOutputPropAST: ObjectPropExpression,
isLastPart: boolean,
) {
const blockExpr = getLastElement(currentOutputPropAST.value.parts) as Expression;
const objectExpr = isLastPart ? createObjectExpression() : blockExpr?.statements?.[0];

Check warning on line 168 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
validateResultOfAllFilter(objectExpr, flatMapping);
return objectExpr;
}

function processAllFilter(
flatMapping: FlatMappingAST,
currentOutputPropAST: ObjectPropExpression,
isLastPart: boolean,
): ObjectExpression {
const { inputExpr: currentInputAST } = flatMapping;
const filterIndex = currentInputAST.parts.findIndex(
(part) => part.type === SyntaxType.OBJECT_FILTER_EXPR,
);

const filterIndex = findAllFilterIndex(currentInputAST);
if (filterIndex === -1) {
if (currentOutputPropAST.value.type === SyntaxType.OBJECT_EXPR) {
const currObjectExpr = currentOutputPropAST.value as ObjectExpression;
currentOutputPropAST.value = getPathExpressionForAllFilter(currentInputAST, currObjectExpr);
return currObjectExpr;
}
if (isPathWithEmptyPartsAndObjectRoot(currentOutputPropAST.value)) {
return currentOutputPropAST.value.root as ObjectExpression;
const objectExpr = handleAllFilterIndexNotFound(
currentInputAST,
currentOutputPropAST,
isLastPart,
);
if (objectExpr) {
return objectExpr;
}
} else {
const matchedInputParts = currentInputAST.parts.splice(0, filterIndex + 1);
if (isPathWithEmptyPartsAndObjectRoot(currentOutputPropAST.value)) {
currentOutputPropAST.value = currentOutputPropAST.value.root;
}

if (currentOutputPropAST.value.type !== SyntaxType.PATH) {
matchedInputParts.push(createBlockExpression(currentOutputPropAST.value));
currentOutputPropAST.value = getPathExpressionForAllFilter(
currentInputAST,
currentInputAST.root,
matchedInputParts,
);
}
currentInputAST.root = undefined;
handleAllFilterIndexFound(currentInputAST, currentOutputPropAST, filterIndex, isLastPart);
}

const blockExpr = getLastElement(currentOutputPropAST.value.parts) as Expression;
const objectExpr = blockExpr?.statements?.[0] || EMPTY_EXPR;
validateResultOfAllFilter(objectExpr, flatMapping);
return objectExpr;
return getNextObjectExpressionForAllFilter(flatMapping, currentOutputPropAST, isLastPart);
}

function isWildcardSelector(expr: Expression): boolean {
Expand Down Expand Up @@ -201,12 +257,30 @@ function handleNextPart(
): ObjectExpression | undefined {
const nextOutputPart = flatMapping.outputExpr.parts[partNum];
if (nextOutputPart.filter?.type === SyntaxType.ALL_FILTER_EXPR) {
return processAllFilter(flatMapping, currentOutputPropAST);
const objectExpr = processAllFilter(
flatMapping,
currentOutputPropAST,
partNum === flatMapping.outputExpr.parts.length - 1 && !nextOutputPart.options?.index,

Check warning on line 263 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
);
if (nextOutputPart.options?.index) {

Check warning on line 265 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
objectExpr.props.push({

Check warning on line 266 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
type: SyntaxType.OBJECT_PROP_EXPR,

Check warning on line 267 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
key: nextOutputPart.options?.index,

Check warning on line 268 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
value: {

Check warning on line 269 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
type: SyntaxType.PATH,

Check warning on line 270 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
root: nextOutputPart.options?.index,

Check warning on line 271 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
parts: [],

Check warning on line 272 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
},

Check warning on line 273 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
});

Check warning on line 274 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

Check warning on line 275 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement

Check warning on line 275 in src/utils/converter.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

Check warning on line 275 in src/utils/converter.ts

View check run for this annotation

Codecov / codecov/patch

src/utils/converter.ts#L266-L275

Added lines #L266 - L275 were not covered by tests
return objectExpr;
}
if (nextOutputPart.filter?.type === SyntaxType.ARRAY_INDEX_FILTER_EXPR) {
return processArrayIndexFilter(
flatMapping,
currentOutputPropAST,
nextOutputPart.filter as IndexFilterExpression,
partNum === flatMapping.outputExpr.parts.length - 1,
);
}
if (isWildcardSelector(nextOutputPart)) {
Expand Down
Loading

0 comments on commit 32299b3

Please sign in to comment.