From 32c57da63096ed4430353761af138815b29c153a Mon Sep 17 00:00:00 2001 From: Vadim Laletin Date: Tue, 28 May 2024 12:01:27 +0200 Subject: [PATCH] fix: restore backward compatibility for /parse-template --- ts/server/src/app.controller.ts | 2 + ts/server/src/app.service.ts | 2 + ts/server/src/utils/extract.ts | 76 ++++++++++++++++++++++++++++++--- ts/server/test/app.e2e-spec.ts | 8 ++-- 4 files changed, 77 insertions(+), 11 deletions(-) diff --git a/ts/server/src/app.controller.ts b/ts/server/src/app.controller.ts index 623f4d2..ef76cd2 100644 --- a/ts/server/src/app.controller.ts +++ b/ts/server/src/app.controller.ts @@ -31,6 +31,7 @@ export class AppController { context, fhirpath_r4_model, strict, + true, ); } @@ -45,6 +46,7 @@ export class AppController { context, null, strict, + false ); } } diff --git a/ts/server/src/app.service.ts b/ts/server/src/app.service.ts index aa6340c..074aea8 100644 --- a/ts/server/src/app.service.ts +++ b/ts/server/src/app.service.ts @@ -10,6 +10,7 @@ export class AppService { context: Context, model?: Model, strict?: boolean, + dropNulls?: boolean, ): object { const options: FPOptions = { userInvocationTable: { @@ -42,6 +43,7 @@ export class AppService { model, options, strict, + dropNulls, ); } } diff --git a/ts/server/src/utils/extract.ts b/ts/server/src/utils/extract.ts index c994d1e..813298c 100644 --- a/ts/server/src/utils/extract.ts +++ b/ts/server/src/utils/extract.ts @@ -42,6 +42,8 @@ export function resolveTemplate( model?: Model, fpOptions?: FPOptions, strict?: boolean, + // TODO: it's fast hack for backward compatibility + dropNulls?: boolean, ): any { return resolveTemplateRecur( [], @@ -50,6 +52,7 @@ export function resolveTemplate( context, model, fpOptions, + dropNulls, ); } @@ -60,6 +63,7 @@ function resolveTemplateRecur( initialContext?: Context, model?: Model, fpOptions?: FPOptions, + dropNulls?: boolean, ): any { return iterateObject( startPath, @@ -74,6 +78,7 @@ function resolveTemplateRecur( context, model, fpOptions, + dropNulls, ); const matchers = [ processContextBlock, @@ -82,7 +87,15 @@ function resolveTemplateRecur( processIfBlock, ]; for (const matcher of matchers) { - const result = matcher(path, resource, newNode, newContext, model, fpOptions); + const result = matcher( + path, + resource, + newNode, + newContext, + model, + fpOptions, + dropNulls, + ); if (result) { return { node: result.node, context: newContext }; @@ -92,7 +105,15 @@ function resolveTemplateRecur( return { node: newNode, context: newContext }; } else if (typeof node === 'string') { return { - node: processTemplateString(path, resource, node, context, model, fpOptions), + node: processTemplateString( + path, + resource, + node, + context, + model, + fpOptions, + dropNulls, + ), context, }; } @@ -109,6 +130,7 @@ function processTemplateString( context: Context, model: Model, fpOptions: FPOptions, + dropNulls?: boolean, ) { const templateRegExp = /{{-?\s*([\s\S]+?)\s*-?}}/g; let match: @@ -126,7 +148,7 @@ function processTemplateString( return undefined; } - return null; + return dropNulls ? undefined : null; } if (match[0] === node) { @@ -146,6 +168,7 @@ function processAssignBlock( context: Context, model: Model, fpOptions: FPOptions, + dropNulls?: boolean, ): { node: any; context: Context } { const extendedContext = { ...context }; const keys = Object.keys(node); @@ -163,7 +186,15 @@ function processAssignBlock( } Object.entries( - resolveTemplateRecur(path, resource, obj, extendedContext, model, fpOptions), + resolveTemplateRecur( + path, + resource, + obj, + extendedContext, + model, + fpOptions, + dropNulls, + ), ).forEach(([key, value]) => { extendedContext[key] = value; }); @@ -183,6 +214,7 @@ function processAssignBlock( extendedContext, model, fpOptions, + dropNulls, ), ).forEach(([key, value]) => { extendedContext[key] = value; @@ -204,6 +236,7 @@ function processMergeBlock( context: Context, model: Model, fpOptions: FPOptions, + dropNulls?: boolean, ): { node: any } | undefined { const keys = Object.keys(node); @@ -221,6 +254,7 @@ function processMergeBlock( context, model, fpOptions, + dropNulls, ); if (!isPlainObject(result) && result !== null) { throw new FPMLValidationError('Merge block must contain object', path); @@ -241,6 +275,7 @@ function processForBlock( context: Context, model: Model, fpOptions: FPOptions, + dropNulls?: boolean, ): { node: any } | undefined { const keys = Object.keys(node); @@ -272,6 +307,7 @@ function processForBlock( }, model, fpOptions, + dropNulls, ), ), }; @@ -285,6 +321,7 @@ function processContextBlock( context: Context, model: Model, fpOptions: FPOptions, + dropNulls?: boolean, ): { node: any } | undefined { const keys = Object.keys(node); @@ -300,7 +337,15 @@ function processContextBlock( const answers = evaluateExpression(path, resource, expr, context, model, fpOptions); const result: any[] = answers.map((answer) => - resolveTemplateRecur(path, answer, node[contextKey], context, model, fpOptions), + resolveTemplateRecur( + path, + answer, + node[contextKey], + context, + model, + fpOptions, + dropNulls, + ), ); return { node: result }; @@ -314,6 +359,7 @@ function processIfBlock( context: Context, model: Model, fpOptions: FPOptions, + dropNulls?: boolean, ): { node: any } | undefined { const keys = Object.keys(node); @@ -353,9 +399,25 @@ function processIfBlock( )[0]; const newNode = answer - ? resolveTemplateRecur(path, resource, node[ifKey], context, model, fpOptions) + ? resolveTemplateRecur( + path, + resource, + node[ifKey], + context, + model, + fpOptions, + dropNulls, + ) : elseKey - ? resolveTemplateRecur(path, resource, node[elseKey], context, model, fpOptions) + ? resolveTemplateRecur( + path, + resource, + node[elseKey], + context, + model, + fpOptions, + dropNulls, + ) : null; const isMergeBehavior = keys.length !== (elseKey ? 2 : 1); diff --git a/ts/server/test/app.e2e-spec.ts b/ts/server/test/app.e2e-spec.ts index 016d727..fc21c39 100644 --- a/ts/server/test/app.e2e-spec.ts +++ b/ts/server/test/app.e2e-spec.ts @@ -23,7 +23,7 @@ describe('AppController (e2e)', () => { resourceType: 'Patient', id: 'foo', }, - template: { id: '{{ Patient.id }}' }, + template: { id: '{{ Patient.id }}', name: '{{ name }}' }, }) .expect(200) .expect({ id: 'foo' }); @@ -37,7 +37,7 @@ describe('AppController (e2e)', () => { resourceType: 'Patient', id: 'foo', }, - template: { id: '{{ Patient.id }}' }, + template: { id: '{{ Patient.id }}', name: '{{ name }}' }, }) .expect(200) .expect({ id: 'foo' }); @@ -51,10 +51,10 @@ describe('AppController (e2e)', () => { resourceType: 'Patient', id: 'foo', }, - template: { id: '{{ Patient.id }}' }, + template: { id: '{{ Patient.id }}', name: '{{ name }}' }, }) .expect(200) - .expect({ id: 'foo' }); + .expect({ id: 'foo', name: null }); }); it('$extract r4', () => {