Skip to content

Commit 5d3e04f

Browse files
Merge branch 'bugfix/LF-3005/resource-in-environment-var' into 'master'
Fixed an issue with evaluating an expression for a resource passed through an environment variable See merge request lfor/fhirpath.js!12
2 parents e0fe2b5 + 7273e8f commit 5d3e04f

File tree

14 files changed

+188
-92
lines changed

14 files changed

+188
-92
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33
This log documents significant changes for each release. This project follows
44
[Semantic Versioning](http://semver.org/).
55

6+
## [3.13.2] - 2024-05-15
7+
### Fixed
8+
- an issue with evaluating an expression for a resource passed through an
9+
environment variable.
10+
- an issue with "statusShift" during performance tests.
11+
612
## [3.13.1] - 2024-04-25
713
### Fixed
814
- Added flag 'u' for regular expressions in the specification's `matches` and

fhir-context/dstu2/index.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const updateWithGeneratedData = require('../general-additions');
12
/**
23
* Exports the FHIR model data for DSTU2. This is an internal structure that
34
* will likely evolve as more FHIR specific processing is added.
@@ -24,10 +25,7 @@ const modelInfo = {
2425
path2Type: require('./path2Type.json')
2526
};
2627

27-
// Generate a set of available data types
28-
modelInfo.availableTypes = new Set();
29-
// IE11 probably doesn't support `new Set(iterable)`
30-
Object.keys(modelInfo.type2Parent).forEach(i => modelInfo.availableTypes.add(i));
31-
Object.values(modelInfo.type2Parent).forEach(i => modelInfo.availableTypes.add(i));
28+
// Update with generated data
29+
updateWithGeneratedData(modelInfo)
3230

3331
module.exports = modelInfo;

fhir-context/general-additions.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Update the model with generated data
2+
module.exports = (modelInfo) => {
3+
// Generate a set of available data types
4+
modelInfo.availableTypes = new Set();
5+
// IE11 probably doesn't support `new Set(iterable)`
6+
Object.keys(modelInfo.type2Parent).forEach(i => modelInfo.availableTypes.add(i));
7+
Object.values(modelInfo.type2Parent).forEach(i => modelInfo.availableTypes.add(i));
8+
9+
// Generate a hash map to map paths to data types excluding "BackboneElement" and "Element".
10+
modelInfo.path2TypeWithoutElements = {};
11+
for(let i in modelInfo.path2Type) {
12+
if (modelInfo.path2Type[i] === 'Element' || modelInfo.path2Type[i] === 'BackboneElement') {
13+
continue;
14+
}
15+
modelInfo.path2TypeWithoutElements[i] = modelInfo.path2Type[i];
16+
}
17+
};

fhir-context/r4/index.js

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
const updateWithGeneratedData = require('../general-additions');
2+
13
/**
24
* Exports the FHIR model data for R4. This is an internal structure that
35
* will likely evolve as more FHIR specific processing is added.
@@ -24,10 +26,7 @@ const modelInfo = {
2426
path2Type: require('./path2Type.json')
2527
};
2628

27-
// Generate a set of available data types
28-
modelInfo.availableTypes = new Set();
29-
// IE11 probably doesn't support `new Set(iterable)`
30-
Object.keys(modelInfo.type2Parent).forEach(i => modelInfo.availableTypes.add(i));
31-
Object.values(modelInfo.type2Parent).forEach(i => modelInfo.availableTypes.add(i));
29+
// Update with generated data
30+
updateWithGeneratedData(modelInfo)
3231

3332
module.exports = modelInfo;

fhir-context/r5/index.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const updateWithGeneratedData = require('../general-additions');
12
/**
23
* Exports the FHIR model data for R5. This is an internal structure that
34
* will likely evolve as more FHIR specific processing is added.
@@ -24,10 +25,7 @@ const modelInfo = {
2425
path2Type: require('./path2Type.json')
2526
};
2627

27-
// Generate a set of available data types
28-
modelInfo.availableTypes = new Set();
29-
// IE11 probably doesn't support `new Set(iterable)`
30-
Object.keys(modelInfo.type2Parent).forEach(i => modelInfo.availableTypes.add(i));
31-
Object.values(modelInfo.type2Parent).forEach(i => modelInfo.availableTypes.add(i));
28+
// Update with generated data
29+
updateWithGeneratedData(modelInfo)
3230

3331
module.exports = modelInfo;

fhir-context/stu3/index.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const updateWithGeneratedData = require('../general-additions');
12
/**
23
* Exports the FHIR model data for STU3. This is an internal structure that
34
* will likely evolve as more FHIR specific processing is added.
@@ -24,11 +25,8 @@ const modelInfo = {
2425
path2Type: require('./path2Type.json')
2526
};
2627

27-
// Generate a set of available data types
28-
modelInfo.availableTypes = new Set();
29-
// IE11 probably doesn't support `new Set(iterable)`
30-
Object.keys(modelInfo.type2Parent).forEach(i => modelInfo.availableTypes.add(i));
31-
Object.values(modelInfo.type2Parent).forEach(i => modelInfo.availableTypes.add(i));
28+
// Update with generated data
29+
updateWithGeneratedData(modelInfo)
3230

3331
module.exports = modelInfo;
3432

package-lock.json

Lines changed: 10 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "fhirpath",
3-
"version": "3.13.1",
3+
"version": "3.13.2",
44
"description": "A FHIRPath engine",
55
"main": "src/fhirpath.js",
66
"dependencies": {
@@ -15,7 +15,7 @@
1515
"@babel/eslint-parser": "^7.17.0",
1616
"@babel/preset-env": "^7.16.11",
1717
"babel-loader": "^8.2.3",
18-
"benny": "^3.7.1",
18+
"benny": "github:caderek/benny#0ad058d3c7ef0b488a8fe9ae3519159fc7f36bb6",
1919
"bestzip": "^2.2.0",
2020
"copy-webpack-plugin": "^6.0.3",
2121
"cypress": "^13.7.2",
@@ -41,6 +41,7 @@
4141
"node": ">=8.9.0"
4242
},
4343
"scripts": {
44+
"postinstall": "echo \"Building the Benny package based on a pull request which fixes an issue with 'statusShift'... \" && (cd node_modules/benny && npm i && npm run build > /dev/null) || echo \"Building the Benny package is completed.\"",
4445
"generateParser": "cd src/parser; rimraf ./generated/*; java -Xmx500M -cp \"../../antlr-4.9.3-complete.jar:$CLASSPATH\" org.antlr.v4.Tool -o generated -Dlanguage=JavaScript FHIRPath.g4; grunt updateParserRequirements",
4546
"build": "cd browser-build && webpack && rimraf fhirpath.zip && bestzip fhirpath.zip LICENSE.md fhirpath.min.js fhirpath.r5.min.js fhirpath.r4.min.js fhirpath.stu3.min.js fhirpath.dstu2.min.js && rimraf LICENSE.md",
4647
"test:unit": "node --use_strict node_modules/.bin/jest && TZ=America/New_York node --use_strict node_modules/.bin/jest && TZ=Europe/Paris node --use_strict node_modules/.bin/jest",

src/fhirpath.js

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -251,14 +251,44 @@ engine.ExternalConstantTerm = function(ctx, parentData, node) {
251251
var identifier = extConstant.children[0];
252252
var varName = engine.Identifier(ctx, parentData, identifier)[0];
253253

254-
var value = ctx.vars[varName];
255-
if (!(varName in ctx.vars)) {
256-
if (ctx.definedVars && varName in ctx.definedVars)
257-
value = ctx.definedVars[varName];
258-
else
259-
throw new Error(
260-
"Attempting to access an undefined environment variable: " + varName
261-
);
254+
var value;
255+
// Check the user-defined environment variables first as the user can override
256+
// the "context" variable like we do in unit tests. In this case, the user
257+
// environment variable can replace the system environment variable in "processedVars".
258+
if (varName in ctx.vars) {
259+
// Restore the ResourceNodes for the top-level objects of the environment
260+
// variables. The nested objects will be converted to ResourceNodes
261+
// in the MemberInvocation method.
262+
value = ctx.vars[varName];
263+
if (Array.isArray(value)) {
264+
value = value.map(
265+
i => i?.__path__
266+
? makeResNode(i, i.__path__.path || null, null,
267+
i.__path__.fhirNodeDataType || null)
268+
: i?.resourceType
269+
? makeResNode(i, null, null)
270+
: i );
271+
} else {
272+
value = value?.__path__
273+
? makeResNode(value, value.__path__.path || null, null,
274+
value.__path__.fhirNodeDataType || null)
275+
: value?.resourceType
276+
? makeResNode(value, null, null)
277+
: value;
278+
}
279+
ctx.processedVars[varName] = value;
280+
delete ctx.vars[varName];
281+
} else if (varName in ctx.processedVars) {
282+
// "processedVars" are variables with ready-to-use values that have already
283+
// been converted to ResourceNodes if necessary.
284+
value = ctx.processedVars[varName];
285+
} else if (ctx.definedVars && varName in ctx.definedVars) {
286+
// "definedVars" are variables defined with the "defineVariable" function.
287+
value = ctx.definedVars[varName];
288+
} else {
289+
throw new Error(
290+
"Attempting to access an undefined environment variable: " + varName
291+
);
262292
}
263293
// For convenience, we all variable values to be passed in without their array
264294
// wrapper. However, when evaluating, we need to put the array back in.
@@ -656,27 +686,7 @@ function applyParsedPath(resource, parsedPath, context, model, options) {
656686
// Set up default standard variables, and allow override from the variables.
657687
// However, we'll keep our own copy of dataRoot for internal processing.
658688
let vars = {context: dataRoot, ucum: 'http://unitsofmeasure.org'};
659-
// Restore the ResourceNodes for the top-level objects of the context
660-
// variables. The nested objects will be converted to ResourceNodes
661-
// in the MemberInvocation method.
662-
if (context) {
663-
context = Object.keys(context).reduce((restoredContext, key) => {
664-
if (Array.isArray(context[key])) {
665-
restoredContext[key] = context[key].map(
666-
i => i?.__path__
667-
? makeResNode(i, i.__path__.path || null, null,
668-
i.__path__.fhirNodeDataType || null)
669-
: i );
670-
} else {
671-
restoredContext[key] = context[key]?.__path__
672-
? makeResNode(context[key], context[key].__path__.path || null, null,
673-
context[key].__path__.fhirNodeDataType || null)
674-
: context[key];
675-
}
676-
return restoredContext;
677-
}, {});
678-
}
679-
let ctx = {dataRoot, vars: Object.assign(vars, context), model};
689+
let ctx = {dataRoot, processedVars: vars, vars: context || {}, model};
680690
if (options.traceFn) {
681691
ctx.customTraceFn = options.traceFn;
682692
}

src/misc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ engine.defineVariable = function (x, label, expr) {
5555
// Just in time initialization of definedVars
5656
if (!this.definedVars) this.definedVars = {};
5757

58-
if (Object.keys(this.vars).includes(label)) {
58+
if (label in this.vars || label in this.processedVars) {
5959
throw new Error("Environment Variable %" + label + " already defined");
6060
}
6161

0 commit comments

Comments
 (0)