Skip to content

Commit

Permalink
Merge branch 'bugfix/LF-2881/handling-of-empty-values-coming-from-nul…
Browse files Browse the repository at this point in the history
…l' into 'master'

Handling of empty values that came from nulls

See merge request lfor/fhirpath.js!8
  • Loading branch information
yuriy-sedinkin committed Mar 25, 2024
2 parents 5428ef8 + 555d715 commit eab5df2
Show file tree
Hide file tree
Showing 32 changed files with 836 additions and 191 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
This log documents significant changes for each release. This project follows
[Semantic Versioning](http://semver.org/).

## [3.10.5] - 2024-03-25
### Fixed
- Handling of empty values that came from nulls.

## [3.10.4] - 2024-03-13
### Fixed
- hasValue() function previously only checked the data type of an input
Expand Down
24 changes: 12 additions & 12 deletions demo/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fhirpath",
"version": "3.10.4",
"version": "3.10.5",
"description": "A FHIRPath engine",
"main": "src/fhirpath.js",
"dependencies": {
Expand Down
58 changes: 39 additions & 19 deletions src/aggregate.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,38 +24,58 @@ engine.countFn = function(x) {
// Shortcut for "value.tail().aggregate($this+$total, value.first())" `
engine.sumFn = function(data) {
return engine.aggregateMacro.apply(this, [data.slice(1), ($this) => {
return math.plus(util.arraify($this), util.arraify(this.$total));
let x = util.arraify($this).filter(i => util.valData(i) != null);
let y = util.arraify(this.$total).filter(i => util.valData(i) != null);
if (x.length === 0 || y.length === 0) {
return [];
}
return math.plus(x, y);
}, data[0]]);
};

/**
* Shortcut for "[source collection].aggregate(iif($total.empty(), $this, iif($this [operator] $total, $this, $total)))"
* Used for functions min() and max().
* @param {Array} data - source collection
* @param {Function} fn - operator function
* @return {Array}
*/
function minMaxShortcutTemplate(data, fn) {
let $total;
if (data.length === 0 || util.valData(data[0]) == null) {
$total = [];
} else {
$total = [data[0]];
for (let i = 1; i < data.length; i++) {
if (util.valData(data[i]) == null) {
$total = [];
break;
}
const $this = [data[i]];
$total = util.isTrue(fn($this, $total)) ? $this : $total;
}
}
return $total;
}

// Shortcut for "value.aggregate(iif($total.empty(), $this, iif($this < $total, $this, $total)))"
engine.minFn = function (data) {
return engine.aggregateMacro.apply(this, [data, (curr) => {
const $this = util.arraify(curr);
const $total = util.arraify(this.$total);
return util.isEmpty($total)
? $this
: equality.lt($this, $total) ? $this : $total;
}]);
return minMaxShortcutTemplate(data, equality.lt);
};

// Shortcut for "value.aggregate(iif($total.empty(), $this, iif($this > $total, $this, $total)))"
engine.maxFn = function (data) {
return engine.aggregateMacro.apply(this, [data, (curr) => {
const $this = util.arraify(curr);
const $total = util.arraify(this.$total);
return util.isEmpty($total)
? $this
: equality.gt($this, $total) ? $this : $total;
}]);
return minMaxShortcutTemplate(data, equality.gt);
};

// Shortcut for "value.sum()/value.count()"
engine.avgFn = function (data) {
return math.div(
util.arraify(engine.sumFn(data)),
util.arraify(engine.countFn(data))
);
const x = util.arraify(engine.sumFn(data));
const y = util.arraify(engine.countFn(data));
if (x.length === 0 || y.length === 0) {
return [];
}
return math.div(x, y);
};

module.exports = engine;
37 changes: 23 additions & 14 deletions src/equality.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,32 +38,35 @@ engine.unequival = function(a, b){

/**
* Checks that the types of a and b are suitable for comparison in an
* inequality expression. It is assumed that a check has already been made
* that there is at least one value in a and b.
* inequality expression.
* @param a the left side of the inequality expression (which should be an array of
* one value).
* @param b the right side of the inequality expression (which should be an array of
* one value).
* @return the singleton values of the arrays a, and b. If one was an FP_Type
* and the other was convertible, the coverted value will be retureed.
* and the other was convertible, the converted value will be returned.
*/
function typecheck(a, b){
util.assertAtMostOne(a, "Singleton was expected");
util.assertAtMostOne(b, "Singleton was expected");
util.assertOnlyOne(a, "Singleton was expected");
util.assertOnlyOne(b, "Singleton was expected");
a = util.valDataConverted(a[0]);
b = util.valDataConverted(b[0]);
let lClass = a instanceof FP_DateTime ? FP_DateTime : a.constructor;
let rClass = b instanceof FP_DateTime ? FP_DateTime : b.constructor;
if (lClass !== rClass) {
util.raiseError('Type of "'+a+'" ('+lClass.name+') did not match type of "'+
b+'" ('+rClass.name+')', 'InequalityExpression');
if (a != null && b != null) {
let lClass = a instanceof FP_DateTime ? FP_DateTime : a.constructor;
let rClass = b instanceof FP_DateTime ? FP_DateTime : b.constructor;
if (lClass !== rClass) {
util.raiseError('Type of "' + a + '" (' + lClass.name + ') did not match type of "' +
b + '" (' + rClass.name + ')', 'InequalityExpression');
}
}
return [a, b];
}

engine.lt = function(a, b){
if (!a.length || !b.length) return [];
const [a0, b0] = typecheck(a,b);
if (a0 == null || b0 == null) {
return [];
}
if (a0 instanceof FP_Type) {
const compare = a0.compare(b0);
return compare === null ? [] : compare < 0;
Expand All @@ -72,8 +75,10 @@ engine.lt = function(a, b){
};

engine.gt = function(a, b){
if (!a.length || !b.length) return [];
const [a0, b0] = typecheck(a,b);
if (a0 == null || b0 == null) {
return [];
}
if (a0 instanceof FP_Type) {
const compare = a0.compare(b0);
return compare === null ? [] : compare > 0;
Expand All @@ -82,8 +87,10 @@ engine.gt = function(a, b){
};

engine.lte = function(a, b){
if (!a.length || !b.length) return [];
const [a0, b0] = typecheck(a,b);
if (a0 == null || b0 == null) {
return [];
}
if (a0 instanceof FP_Type) {
const compare = a0.compare(b0);
return compare === null ? [] : compare <= 0;
Expand All @@ -92,8 +99,10 @@ engine.lte = function(a, b){
};

engine.gte = function(a, b){
if (!a.length || !b.length) return [];
const [a0, b0] = typecheck(a,b);
if (a0 == null || b0 == null) {
return [];
}
if (a0 instanceof FP_Type) {
const compare = a0.compare(b0);
return compare === null ? [] : compare >= 0;
Expand Down
9 changes: 5 additions & 4 deletions src/fhirpath.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ engine.invocationTable = {
where: {fn: filtering.whereMacro, arity: {1: ["Expr"]}},
extension: {fn: filtering.extension, arity: {1: ["String"]}},
select: {fn: filtering.selectMacro, arity: {1: ["Expr"]}},
aggregate: {fn: aggregate.aggregateMacro, arity: {1: ["Expr"], 2: ["Expr", "Any"]}},
aggregate: {fn: aggregate.aggregateMacro, arity: {1: ["Expr"], 2: ["Expr", "AnyAtRoot"]}},
sum: {fn: aggregate.sumFn},
min: {fn: aggregate.minFn},
max: {fn: aggregate.maxFn},
Expand Down Expand Up @@ -107,6 +107,7 @@ engine.invocationTable = {
toTime: {fn: misc.toTime},
toBoolean: {fn: misc.toBoolean},
toQuantity: {fn: misc.toQuantity, arity: {0: [], 1: ["String"]}},
// TODO: The hasValue function should be taken into account in a separate request
hasValue: {fn: misc.hasValueFn},
convertsToBoolean: {fn: misc.createConvertsToFn(misc.toBoolean, 'boolean')},
convertsToInteger: {fn: misc.createConvertsToFn(misc.toInteger, 'number')},
Expand Down Expand Up @@ -143,7 +144,7 @@ engine.invocationTable = {
ln: {fn: math.ln},
log: {fn: math.log, arity: {1: ["Number"]}, nullable: true},
power: {fn: math.power, arity: {1: ["Number"]}, nullable: true},
round: {fn: math.round, arity: {1: ["Number"]}},
round: {fn: math.round, arity: {0: [], 1: ["Number"]}},
sqrt: {fn: math.sqrt},
truncate: {fn: math.truncate},

Expand Down Expand Up @@ -414,7 +415,7 @@ function makeParam(ctx, parentData, type, param) {
return engine.TypeSpecifier(ctx, parentData, param);
}

const res = engine.doEval(ctx, parentData, param);
let res = engine.doEval(ctx, parentData, param);
if(type === "Any") {
return res;
}
Expand All @@ -434,7 +435,7 @@ function doInvoke(ctx, fnName, data, rawParams){
if(invoc) {
if(!invoc.arity){
if(!rawParams){
res = invoc.fn.call(ctx, util.arraify(data));
res = invoc.fn.call(ctx, data);
return util.arraify(res);
} else {
throw new Error(fnName + " expects no params");
Expand Down
3 changes: 1 addition & 2 deletions src/filtering.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,7 @@ engine.singleFn = function(x) {
} else if (x.length == 0) {
return [];
} else {
//TODO: should throw error?
return {$status: "error", $error: "Expected single"};
throw new Error("Expected single");
}
};

Expand Down
Loading

0 comments on commit eab5df2

Please sign in to comment.