Skip to content

Commit

Permalink
feat: add merge directive
Browse files Browse the repository at this point in the history
  • Loading branch information
ruscoder committed May 28, 2024
1 parent f401de6 commit 6d4ec47
Show file tree
Hide file tree
Showing 2 changed files with 147 additions and 4 deletions.
84 changes: 84 additions & 0 deletions ts/server/src/utils/extract.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -510,4 +510,88 @@ describe('If block', () => {
}),
).toThrow(FPMLValidationError);
});

test('fails on multiple if blocks', () => {
expect(() =>
resolveTemplate(resource, {
result: {
myKey: 1,
"{% if key != 'value' %}": {},
"{% if key = 'value' %}": {},
},
}),
).toThrow(FPMLValidationError);
});

test('fails on multiple else blocks', () => {
expect(() =>
resolveTemplate(resource, {
result: {
myKey: 1,
"{% if key != 'value' %}": {},
'{% else %}': {},
'{% else %}': {},
},
}),
).toThrow(FPMLValidationError);
});

test('fails on else block without if block', () => {
expect(() =>
resolveTemplate(resource, {
result: {
myKey: 1,
'{% else %}': {},
},
}),
).toThrow(FPMLValidationError);
});
});

describe('Merge block', () => {
const resource: any = {
key: 'value',
};

test('implicitly merges into the node', () => {
expect(
resolveTemplate(resource, {
b: 1,
'{% merge %}': { a: 1 },
}),
).toStrictEqual({
b: 1,
a: 1,
});
});

test('merges multiple blocks within order', () => {
expect(
resolveTemplate(resource, {
'{% merge %}': [{ a: 1 }, { b: 2 }, { a: 3 }],
}),
).toStrictEqual({
a: 3,
b: 2,
});
});

test('merges multiple blocks containing nulls', () => {
expect(
resolveTemplate(resource, {
'{% merge %}': [{ a: 1 }, null, { b: 2 }],
}),
).toStrictEqual({
a: 1,
b: 2,
});
});

test('fails on merge with non-object', () => {
expect(() =>
resolveTemplate(resource, {
'{% merge %}': [1, 2],
}),
).toThrow(FPMLValidationError);
});
});
67 changes: 63 additions & 4 deletions ts/server/src/utils/extract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,12 @@ function resolveTemplateRecur(
model,
fpOptions,
);
const matchers = [processContextBlock, processForBlock, processIfBlock];
const matchers = [
processContextBlock,
processMergeBlock,
processForBlock,
processIfBlock,
];
for (const matcher of matchers) {
const result = matcher(path, resource, newNode, newContext, model, fpOptions);

Expand Down Expand Up @@ -192,6 +197,43 @@ function processAssignBlock(
return { node, context };
}

function processMergeBlock(
path: Path,
resource: Resource,
node: any,
context: Context,
model: Model,
fpOptions: FPOptions,
): { node: any } | undefined {
const keys = Object.keys(node);

const mergeRegExp = /{%\s*merge\s*%}/;
const mergeKey = keys.find((k) => k.match(mergeRegExp));

if (mergeKey) {
return {
node: (Array.isArray(node[mergeKey]) ? node[mergeKey] : [node[mergeKey]]).reduce(
(mergeAcc, nodeValue) => {
const result = resolveTemplateRecur(
path,
resource,
nodeValue,
context,
model,
fpOptions,
);
if (!isPlainObject(result) && result !== null) {
throw new FPMLValidationError('Merge block must contain object', path);
}

return { ...mergeAcc, ...(result || {}) };
},
omitKey(node, mergeKey),
),
};
}
}

function processForBlock(
path: Path,
resource: Resource,
Expand All @@ -204,6 +246,7 @@ function processForBlock(

const forRegExp = /{%\s*for\s+(?:(\w+?)\s*,\s*)?(\w+?)\s+in\s+(.+?)\s*%}/;
const forKey = keys.find((k) => k.match(forRegExp));

if (forKey) {
const matches = forKey.match(forRegExp);
const hasIndexKey = matches.length === 4;
Expand Down Expand Up @@ -276,11 +319,27 @@ function processIfBlock(

const ifRegExp = /{%\s*if\s+(.+?)\s*%}/;
const elseRegExp = /{%\s*else\s*%}/;
const ifKey = keys.find((k) => k.match(ifRegExp));

if (ifKey) {
const elseKey = keys.find((k) => k.match(elseRegExp));
const ifKeys = keys.filter((k) => k.match(ifRegExp));
if (ifKeys.length > 1) {
throw new FPMLValidationError('If block must be presented once', path);
}
const ifKey = ifKeys[0];

const elseKeys = keys.filter((k) => k.match(elseRegExp));
if (elseKeys.length > 1) {
throw new FPMLValidationError('Else block must be presented once', path);
}
const elseKey = elseKeys[0];

if (elseKey && !ifKey) {
throw new FPMLValidationError(
'Else block must be presented only when if block is presented',
path,
);
}

if (ifKey) {
const matches = ifKey.match(ifRegExp);
const expr = matches[1];

Expand Down

0 comments on commit 6d4ec47

Please sign in to comment.