Skip to main content
Glama
functions.test.ts38.1 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import type { Patient } from '@medplum/fhirtypes'; import type { Atom, AtomContext } from '../fhirlexer/parse'; import type { TypedValue } from '../types'; import { PropertyType } from '../types'; import { createReference, getReferenceString } from '../utils'; import { LiteralAtom } from './atoms'; import type { FhirPathFunction } from './functions'; import { functions } from './functions'; import { booleanToTypedValue, toTypedValue } from './utils'; const context = { parent: undefined, variables: {}, }; const isEven: Atom = { eval: (context: AtomContext, num: TypedValue[]) => booleanToTypedValue((num[0].value as number) % 2 === 0), }; const TYPED_TRUE = toTypedValue(true); const TYPED_FALSE = toTypedValue(false); const TYPED_0 = toTypedValue(0); const TYPED_1 = toTypedValue(1); const TYPED_2 = toTypedValue(2); const TYPED_3 = toTypedValue(3); const TYPED_4 = toTypedValue(4); const TYPED_A = toTypedValue('a'); const TYPED_B = toTypedValue('b'); const TYPED_X = toTypedValue('x'); const TYPED_Y = toTypedValue('y'); const TYPED_Z = toTypedValue('z'); const TYPED_APPLE = toTypedValue('apple'); const TYPED_XYZ = toTypedValue('xyz'); const TYPED_EMPTY = toTypedValue({}); const LITERAL_TRUE = new LiteralAtom(TYPED_TRUE); const LITERAL_FALSE = new LiteralAtom(TYPED_FALSE); const LITERAL_X = new LiteralAtom(TYPED_X); const LITERAL_Y = new LiteralAtom(TYPED_Y); describe('FHIRPath functions', () => { // 5.1 Existence test('empty', () => { expect(functions.empty(context, [])).toStrictEqual([TYPED_TRUE]); expect(functions.empty(context, [TYPED_EMPTY])).toStrictEqual([TYPED_TRUE]); expect(functions.empty(context, [TYPED_1])).toStrictEqual([TYPED_FALSE]); expect(functions.empty(context, [TYPED_1, TYPED_2])).toStrictEqual([TYPED_FALSE]); }); test('hasValue', () => { expect(functions.hasValue(context, [])).toStrictEqual([TYPED_FALSE]); expect(functions.hasValue(context, [TYPED_1])).toStrictEqual([TYPED_TRUE]); expect(functions.hasValue(context, [TYPED_1, TYPED_2])).toStrictEqual([TYPED_TRUE]); }); test('exists', () => { expect(functions.exists(context, [])).toStrictEqual([TYPED_FALSE]); expect(functions.exists(context, [TYPED_EMPTY])).toStrictEqual([TYPED_FALSE]); expect(functions.exists(context, [TYPED_1])).toStrictEqual([TYPED_TRUE]); expect(functions.exists(context, [TYPED_1, TYPED_2])).toStrictEqual([TYPED_TRUE]); expect(functions.exists(context, [], isEven)).toStrictEqual([TYPED_FALSE]); expect(functions.exists(context, [TYPED_1], isEven)).toStrictEqual([TYPED_FALSE]); expect(functions.exists(context, [TYPED_1, TYPED_2], isEven)).toStrictEqual([TYPED_TRUE]); }); test('all', () => { expect(functions.all(context, [], isEven)).toStrictEqual([TYPED_TRUE]); expect(functions.all(context, [TYPED_1], isEven)).toStrictEqual([TYPED_FALSE]); expect(functions.all(context, [TYPED_2], isEven)).toStrictEqual([TYPED_TRUE]); expect(functions.all(context, [TYPED_1, TYPED_2], isEven)).toStrictEqual([TYPED_FALSE]); expect(functions.all(context, [TYPED_2, TYPED_4], isEven)).toStrictEqual([TYPED_TRUE]); }); test('allTrue', () => { expect(functions.allTrue(context, [])).toStrictEqual([TYPED_TRUE]); expect(functions.allTrue(context, [TYPED_TRUE])).toStrictEqual([TYPED_TRUE]); expect(functions.allTrue(context, [TYPED_FALSE])).toStrictEqual([TYPED_FALSE]); expect(functions.allTrue(context, [TYPED_TRUE, TYPED_FALSE])).toStrictEqual([TYPED_FALSE]); expect(functions.allTrue(context, [TYPED_TRUE, TYPED_TRUE])).toStrictEqual([TYPED_TRUE]); expect(functions.allTrue(context, [TYPED_FALSE, TYPED_FALSE])).toStrictEqual([TYPED_FALSE]); }); test('anyTrue', () => { expect(functions.anyTrue(context, [])).toStrictEqual([TYPED_FALSE]); expect(functions.anyTrue(context, [TYPED_TRUE])).toStrictEqual([TYPED_TRUE]); expect(functions.anyTrue(context, [TYPED_FALSE])).toStrictEqual([TYPED_FALSE]); expect(functions.anyTrue(context, [TYPED_TRUE, TYPED_FALSE])).toStrictEqual([TYPED_TRUE]); expect(functions.anyTrue(context, [TYPED_TRUE, TYPED_TRUE])).toStrictEqual([TYPED_TRUE]); expect(functions.anyTrue(context, [TYPED_FALSE, TYPED_FALSE])).toStrictEqual([TYPED_FALSE]); }); test('allFalse', () => { expect(functions.allFalse(context, [])).toStrictEqual([TYPED_TRUE]); expect(functions.allFalse(context, [TYPED_TRUE])).toStrictEqual([TYPED_FALSE]); expect(functions.allFalse(context, [TYPED_FALSE])).toStrictEqual([TYPED_TRUE]); expect(functions.allFalse(context, [TYPED_TRUE, TYPED_FALSE])).toStrictEqual([TYPED_FALSE]); expect(functions.allFalse(context, [TYPED_TRUE, TYPED_TRUE])).toStrictEqual([TYPED_FALSE]); expect(functions.allFalse(context, [TYPED_FALSE, TYPED_FALSE])).toStrictEqual([TYPED_TRUE]); }); test('anyFalse', () => { expect(functions.anyFalse(context, [])).toStrictEqual([TYPED_FALSE]); expect(functions.anyFalse(context, [TYPED_TRUE])).toStrictEqual([TYPED_FALSE]); expect(functions.anyFalse(context, [TYPED_FALSE])).toStrictEqual([TYPED_TRUE]); expect(functions.anyFalse(context, [TYPED_TRUE, TYPED_FALSE])).toStrictEqual([TYPED_TRUE]); expect(functions.anyFalse(context, [TYPED_TRUE, TYPED_TRUE])).toStrictEqual([TYPED_FALSE]); expect(functions.anyFalse(context, [TYPED_FALSE, TYPED_FALSE])).toStrictEqual([TYPED_TRUE]); }); test('count', () => { expect(functions.count(context, [])).toStrictEqual([TYPED_0]); expect(functions.count(context, [TYPED_1])).toStrictEqual([TYPED_1]); expect(functions.count(context, [TYPED_1, TYPED_2])).toStrictEqual([TYPED_2]); }); test('distinct', () => { expect(functions.distinct(context, [])).toStrictEqual([]); expect(functions.distinct(context, [TYPED_1])).toStrictEqual([TYPED_1]); expect(functions.distinct(context, [TYPED_1, TYPED_2])).toStrictEqual([TYPED_1, TYPED_2]); expect(functions.distinct(context, [TYPED_1, TYPED_1])).toStrictEqual([TYPED_1]); expect(functions.distinct(context, [TYPED_A])).toStrictEqual([TYPED_A]); expect(functions.distinct(context, [TYPED_A, TYPED_B])).toStrictEqual([TYPED_A, TYPED_B]); expect(functions.distinct(context, [TYPED_A, TYPED_A])).toStrictEqual([TYPED_A]); }); test('isDistinct', () => { expect(functions.isDistinct(context, [])).toStrictEqual([TYPED_TRUE]); expect(functions.isDistinct(context, [TYPED_1])).toStrictEqual([TYPED_TRUE]); expect(functions.isDistinct(context, [TYPED_1, TYPED_2])).toStrictEqual([TYPED_TRUE]); expect(functions.isDistinct(context, [TYPED_1, TYPED_1])).toStrictEqual([TYPED_FALSE]); expect(functions.isDistinct(context, [TYPED_A])).toStrictEqual([TYPED_TRUE]); expect(functions.isDistinct(context, [TYPED_A, TYPED_B])).toStrictEqual([TYPED_TRUE]); expect(functions.isDistinct(context, [TYPED_A, TYPED_A])).toStrictEqual([TYPED_FALSE]); }); // 5.2. Filtering and projection test('where', () => { expect(functions.where(context, [], isEven)).toStrictEqual([]); expect(functions.where(context, [TYPED_1], isEven)).toStrictEqual([]); expect(functions.where(context, [TYPED_1, TYPED_2], isEven)).toStrictEqual([TYPED_2]); expect(functions.where(context, [TYPED_1, TYPED_2, TYPED_3, TYPED_4], isEven)).toStrictEqual([TYPED_2, TYPED_4]); }); // 5.3 Subsetting test('single', () => { expect(functions.single(context, [])).toStrictEqual([]); expect(functions.single(context, [TYPED_1])).toStrictEqual([TYPED_1]); expect(() => functions.single(context, [TYPED_1, TYPED_2])).toThrow('Expected input length one for single()'); }); test('first', () => { expect(functions.first(context, [])).toStrictEqual([]); expect(functions.first(context, [TYPED_1])).toStrictEqual([TYPED_1]); expect(functions.first(context, [TYPED_1, TYPED_2])).toStrictEqual([TYPED_1]); expect(functions.first(context, [TYPED_1, TYPED_2, TYPED_3])).toStrictEqual([TYPED_1]); expect(functions.first(context, [TYPED_1, TYPED_2, TYPED_3, TYPED_4])).toStrictEqual([TYPED_1]); }); test('last', () => { expect(functions.last(context, [])).toStrictEqual([]); expect(functions.last(context, [TYPED_1])).toStrictEqual([TYPED_1]); expect(functions.last(context, [TYPED_1, TYPED_2])).toStrictEqual([TYPED_2]); expect(functions.last(context, [TYPED_1, TYPED_2, TYPED_3])).toStrictEqual([TYPED_3]); expect(functions.last(context, [TYPED_1, TYPED_2, TYPED_3, TYPED_4])).toStrictEqual([TYPED_4]); }); test('tail', () => { expect(functions.tail(context, [])).toStrictEqual([]); expect(functions.tail(context, [TYPED_1])).toStrictEqual([]); expect(functions.tail(context, [TYPED_1, TYPED_2])).toStrictEqual([TYPED_2]); expect(functions.tail(context, [TYPED_1, TYPED_2, TYPED_3])).toStrictEqual([TYPED_2, TYPED_3]); expect(functions.tail(context, [TYPED_1, TYPED_2, TYPED_3, TYPED_4])).toStrictEqual([TYPED_2, TYPED_3, TYPED_4]); }); test('skip', () => { const nonNumber: Atom = { eval: () => [TYPED_XYZ] }; expect(() => functions.skip(context, [TYPED_1, TYPED_2, TYPED_3], nonNumber)).toThrow( 'Expected a number for skip(num)' ); const num0: Atom = { eval: () => [TYPED_0] }; expect(functions.skip(context, [], num0)).toStrictEqual([]); expect(functions.skip(context, [TYPED_1], num0)).toStrictEqual([TYPED_1]); expect(functions.skip(context, [TYPED_1, TYPED_2], num0)).toStrictEqual([TYPED_1, TYPED_2]); expect(functions.skip(context, [TYPED_1, TYPED_2, TYPED_3], num0)).toStrictEqual([TYPED_1, TYPED_2, TYPED_3]); const num1: Atom = { eval: () => [TYPED_1] }; expect(functions.skip(context, [], num1)).toStrictEqual([]); expect(functions.skip(context, [TYPED_1], num1)).toStrictEqual([]); expect(functions.skip(context, [TYPED_1, TYPED_2], num1)).toStrictEqual([TYPED_2]); expect(functions.skip(context, [TYPED_1, TYPED_2, TYPED_3], num1)).toStrictEqual([TYPED_2, TYPED_3]); const num2: Atom = { eval: () => [TYPED_2] }; expect(functions.skip(context, [], num2)).toStrictEqual([]); expect(functions.skip(context, [TYPED_1], num2)).toStrictEqual([]); expect(functions.skip(context, [TYPED_1, TYPED_2], num2)).toStrictEqual([]); expect(functions.skip(context, [TYPED_1, TYPED_2, TYPED_3], num2)).toStrictEqual([TYPED_3]); }); test('take', () => { const nonNumber: Atom = { eval: () => [TYPED_XYZ] }; expect(() => functions.take(context, [TYPED_1, TYPED_2, TYPED_3], nonNumber)).toThrow( 'Expected a number for take(num)' ); const num0: Atom = { eval: () => [TYPED_0] }; expect(functions.take(context, [], num0)).toStrictEqual([]); expect(functions.take(context, [TYPED_1], num0)).toStrictEqual([]); expect(functions.take(context, [TYPED_1, TYPED_2], num0)).toStrictEqual([]); expect(functions.take(context, [TYPED_1, TYPED_2, TYPED_3], num0)).toStrictEqual([]); const num1: Atom = { eval: () => [TYPED_1] }; expect(functions.take(context, [], num1)).toStrictEqual([]); expect(functions.take(context, [TYPED_1], num1)).toStrictEqual([TYPED_1]); expect(functions.take(context, [TYPED_1, TYPED_2], num1)).toStrictEqual([TYPED_1]); expect(functions.take(context, [TYPED_1, TYPED_2, TYPED_3], num1)).toStrictEqual([TYPED_1]); const num2: Atom = { eval: () => [TYPED_2] }; expect(functions.take(context, [], num2)).toStrictEqual([]); expect(functions.take(context, [TYPED_1], num2)).toStrictEqual([TYPED_1]); expect(functions.take(context, [TYPED_1, TYPED_2], num2)).toStrictEqual([TYPED_1, TYPED_2]); expect(functions.take(context, [TYPED_1, TYPED_2, TYPED_3], num2)).toStrictEqual([TYPED_1, TYPED_2]); }); test('intersect', () => { expect(functions.intersect(context, [], undefined as unknown as Atom)).toStrictEqual([]); expect(functions.intersect(context, [], null as unknown as Atom)).toStrictEqual([]); const num1: Atom = { eval: () => [TYPED_1] }; expect(functions.intersect(context, [], num1)).toStrictEqual([]); expect(functions.intersect(context, [TYPED_1], num1)).toStrictEqual([TYPED_1]); expect(functions.intersect(context, [TYPED_1, TYPED_2], num1)).toStrictEqual([TYPED_1]); expect(functions.intersect(context, [TYPED_1, TYPED_1, TYPED_3], num1)).toStrictEqual([TYPED_1]); }); test('exclude', () => { expect(functions.exclude(context, [], undefined as unknown as Atom)).toStrictEqual([]); expect(functions.exclude(context, [], null as unknown as Atom)).toStrictEqual([]); const num1: Atom = { eval: () => [TYPED_1] }; expect(functions.exclude(context, [], num1)).toStrictEqual([]); expect(functions.exclude(context, [TYPED_1], num1)).toStrictEqual([]); expect(functions.exclude(context, [TYPED_1, TYPED_2], num1)).toStrictEqual([TYPED_2]); expect(functions.exclude(context, [TYPED_1, TYPED_2, TYPED_3], num1)).toStrictEqual([TYPED_2, TYPED_3]); }); // 5.4. Combining test('union', () => { expect(functions.union(context, [], undefined as unknown as Atom)).toStrictEqual([]); expect(functions.union(context, [], null as unknown as Atom)).toStrictEqual([]); const num1: Atom = { eval: () => [TYPED_1] }; expect(functions.union(context, [], num1)).toStrictEqual([TYPED_1]); expect(functions.union(context, [TYPED_1], num1)).toStrictEqual([TYPED_1]); expect(functions.union(context, [TYPED_1, TYPED_2], num1)).toStrictEqual([TYPED_1, TYPED_2]); expect(functions.union(context, [TYPED_1, TYPED_2, TYPED_3], num1)).toStrictEqual([TYPED_1, TYPED_2, TYPED_3]); }); test('combine', () => { expect(functions.combine(context, [], undefined as unknown as Atom)).toStrictEqual([]); expect(functions.combine(context, [], null as unknown as Atom)).toStrictEqual([]); const num1: Atom = { eval: () => [TYPED_1] }; expect(functions.combine(context, [], num1)).toStrictEqual([TYPED_1]); expect(functions.combine(context, [TYPED_1], num1)).toStrictEqual([TYPED_1, TYPED_1]); expect(functions.combine(context, [TYPED_1, TYPED_2], num1)).toStrictEqual([TYPED_1, TYPED_2, TYPED_1]); expect(functions.combine(context, [TYPED_1, TYPED_2, TYPED_3], num1)).toStrictEqual([ TYPED_1, TYPED_2, TYPED_3, TYPED_1, ]); }); // 5.5. Conversion test('iif', () => { expect(functions.iif(context, [], LITERAL_TRUE, LITERAL_X)).toStrictEqual([TYPED_X]); expect(functions.iif(context, [], LITERAL_FALSE, LITERAL_X)).toStrictEqual([]); expect(functions.iif(context, [], LITERAL_TRUE, LITERAL_X, LITERAL_Y)).toStrictEqual([TYPED_X]); expect(functions.iif(context, [], LITERAL_FALSE, LITERAL_X, LITERAL_Y)).toStrictEqual([TYPED_Y]); }); test('toBoolean', () => { expect(functions.toBoolean(context, [])).toStrictEqual([]); expect(functions.toBoolean(context, [TYPED_TRUE])).toStrictEqual([TYPED_TRUE]); expect(functions.toBoolean(context, [TYPED_FALSE])).toStrictEqual([TYPED_FALSE]); expect(functions.toBoolean(context, [TYPED_1])).toStrictEqual([TYPED_TRUE]); expect(() => functions.toBoolean(context, [TYPED_1, TYPED_2])).toThrow(); expect(functions.toBoolean(context, [toTypedValue('true')])).toStrictEqual([TYPED_TRUE]); expect(functions.toBoolean(context, [toTypedValue('false')])).toStrictEqual([TYPED_FALSE]); expect(functions.toBoolean(context, [toTypedValue('xyz')])).toStrictEqual([]); expect(functions.toBoolean(context, [toTypedValue({})])).toStrictEqual([]); }); test('convertsToBoolean', () => { expect(functions.convertsToBoolean(context, [])).toStrictEqual([]); expect(functions.convertsToBoolean(context, [TYPED_TRUE])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToBoolean(context, [TYPED_FALSE])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToBoolean(context, [TYPED_1])).toStrictEqual([TYPED_TRUE]); expect(() => functions.convertsToBoolean(context, [TYPED_1, TYPED_2])).toThrow(); expect(functions.convertsToBoolean(context, [toTypedValue('true')])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToBoolean(context, [toTypedValue('false')])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToBoolean(context, [toTypedValue('xyz')])).toStrictEqual([TYPED_FALSE]); expect(functions.convertsToBoolean(context, [toTypedValue({})])).toStrictEqual([TYPED_FALSE]); }); test('toInteger', () => { expect(functions.toInteger(context, [])).toStrictEqual([]); expect(functions.toInteger(context, [TYPED_TRUE])).toStrictEqual([TYPED_1]); expect(functions.toInteger(context, [TYPED_FALSE])).toStrictEqual([TYPED_0]); expect(functions.toInteger(context, [TYPED_0])).toStrictEqual([TYPED_0]); expect(functions.toInteger(context, [TYPED_1])).toStrictEqual([TYPED_1]); expect(() => functions.toInteger(context, [TYPED_1, TYPED_2])).toThrow(); expect(functions.toInteger(context, [toTypedValue('1')])).toStrictEqual([TYPED_1]); expect(functions.toInteger(context, [toTypedValue('true')])).toStrictEqual([]); expect(functions.toInteger(context, [toTypedValue('false')])).toStrictEqual([]); expect(functions.toInteger(context, [toTypedValue('xyz')])).toStrictEqual([]); expect(functions.toInteger(context, [toTypedValue({})])).toStrictEqual([]); }); test('convertsToInteger', () => { expect(functions.convertsToInteger(context, [])).toStrictEqual([]); expect(functions.convertsToInteger(context, [TYPED_TRUE])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToInteger(context, [TYPED_FALSE])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToInteger(context, [TYPED_0])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToInteger(context, [TYPED_1])).toStrictEqual([TYPED_TRUE]); expect(() => functions.convertsToInteger(context, [TYPED_1, TYPED_2])).toThrow(); expect(functions.convertsToInteger(context, [toTypedValue('1')])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToInteger(context, [toTypedValue('true')])).toStrictEqual([TYPED_FALSE]); expect(functions.convertsToInteger(context, [toTypedValue('false')])).toStrictEqual([TYPED_FALSE]); expect(functions.convertsToInteger(context, [toTypedValue('xyz')])).toStrictEqual([TYPED_FALSE]); expect(functions.convertsToInteger(context, [toTypedValue({})])).toStrictEqual([TYPED_FALSE]); }); test('toDate', () => { expect(functions.toDate(context, [])).toStrictEqual([]); expect(() => functions.toDate(context, [TYPED_1, TYPED_2])).toThrow(); expect(functions.toDate(context, [toTypedValue('2020-01-01')])).toStrictEqual([ { type: PropertyType.date, value: '2020-01-01' }, ]); expect(functions.toDate(context, [TYPED_1])).toStrictEqual([]); expect(functions.toDate(context, [TYPED_TRUE])).toStrictEqual([]); }); test('convertsToDate', () => { expect(functions.convertsToDate(context, [])).toStrictEqual([]); expect(() => functions.convertsToDate(context, [TYPED_1, TYPED_2])).toThrow(); expect(functions.convertsToDate(context, [toTypedValue('2020-01-01')])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToDate(context, [TYPED_1])).toStrictEqual([TYPED_FALSE]); expect(functions.convertsToDate(context, [TYPED_TRUE])).toStrictEqual([TYPED_FALSE]); }); test('toDateTime', () => { expect(functions.toDateTime(context, [])).toStrictEqual([]); expect(() => functions.toDateTime(context, [TYPED_1, TYPED_2])).toThrow(); expect(functions.toDateTime(context, [toTypedValue('2020-01-01')])).toStrictEqual([ { type: PropertyType.dateTime, value: '2020-01-01' }, ]); expect(functions.toDateTime(context, [toTypedValue('2020-01-01T12:00:00Z')])).toStrictEqual([ { type: PropertyType.dateTime, value: '2020-01-01T12:00:00.000Z' }, ]); expect(functions.toDateTime(context, [TYPED_1])).toStrictEqual([]); expect(functions.toDateTime(context, [TYPED_TRUE])).toStrictEqual([]); }); test('convertsToDateTime', () => { expect(functions.convertsToDateTime(context, [])).toStrictEqual([]); expect(() => functions.convertsToDateTime(context, [TYPED_1, TYPED_2])).toThrow(); expect(functions.convertsToDateTime(context, [toTypedValue('2020-01-01')])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToDateTime(context, [toTypedValue('2020-01-01T12:00:00Z')])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToDateTime(context, [TYPED_1])).toStrictEqual([TYPED_FALSE]); expect(functions.convertsToDateTime(context, [TYPED_TRUE])).toStrictEqual([TYPED_FALSE]); }); test('toDecimal', () => { expect(functions.toDecimal(context, [])).toStrictEqual([]); expect(functions.toDecimal(context, [TYPED_TRUE])).toStrictEqual([{ type: PropertyType.decimal, value: 1 }]); expect(functions.toDecimal(context, [TYPED_FALSE])).toStrictEqual([{ type: PropertyType.decimal, value: 0 }]); expect(functions.toDecimal(context, [TYPED_0])).toStrictEqual([{ type: PropertyType.decimal, value: 0 }]); expect(functions.toDecimal(context, [TYPED_1])).toStrictEqual([{ type: PropertyType.decimal, value: 1 }]); expect(() => functions.toDecimal(context, [TYPED_1, TYPED_2])).toThrow(); expect(functions.toDecimal(context, [toTypedValue('1')])).toStrictEqual([{ type: PropertyType.decimal, value: 1 }]); expect(functions.toDecimal(context, [toTypedValue('true')])).toStrictEqual([]); expect(functions.toDecimal(context, [toTypedValue('false')])).toStrictEqual([]); expect(functions.toDecimal(context, [toTypedValue('xyz')])).toStrictEqual([]); expect(functions.toDecimal(context, [toTypedValue({})])).toStrictEqual([]); }); test('convertsToDecimal', () => { expect(functions.convertsToDecimal(context, [])).toStrictEqual([]); expect(functions.convertsToDecimal(context, [TYPED_TRUE])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToDecimal(context, [TYPED_FALSE])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToDecimal(context, [TYPED_0])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToDecimal(context, [TYPED_1])).toStrictEqual([TYPED_TRUE]); expect(() => functions.convertsToDecimal(context, [TYPED_1, TYPED_2])).toThrow(); expect(functions.convertsToDecimal(context, [toTypedValue('1')])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToDecimal(context, [toTypedValue('true')])).toStrictEqual([TYPED_FALSE]); expect(functions.convertsToDecimal(context, [toTypedValue('false')])).toStrictEqual([TYPED_FALSE]); expect(functions.convertsToDecimal(context, [toTypedValue('xyz')])).toStrictEqual([TYPED_FALSE]); expect(functions.convertsToDecimal(context, [toTypedValue({})])).toStrictEqual([TYPED_FALSE]); }); test('toQuantity', () => { expect(functions.toQuantity(context, [])).toStrictEqual([]); expect(functions.toQuantity(context, [toTypedValue({ value: 123, unit: 'mg' })])).toStrictEqual([ toTypedValue({ value: 123, unit: 'mg' }), ]); expect(functions.toQuantity(context, [TYPED_TRUE])).toStrictEqual([toTypedValue({ value: 1, unit: '1' })]); expect(functions.toQuantity(context, [TYPED_FALSE])).toStrictEqual([toTypedValue({ value: 0, unit: '1' })]); expect(functions.toQuantity(context, [TYPED_0])).toStrictEqual([toTypedValue({ value: 0, unit: '1' })]); expect(functions.toQuantity(context, [TYPED_1])).toStrictEqual([toTypedValue({ value: 1, unit: '1' })]); expect(() => functions.toQuantity(context, [TYPED_1, TYPED_2])).toThrow(); expect(functions.toQuantity(context, [toTypedValue('1')])).toStrictEqual([toTypedValue({ value: 1, unit: '1' })]); expect(functions.toQuantity(context, [toTypedValue('true')])).toStrictEqual([]); expect(functions.toQuantity(context, [toTypedValue('false')])).toStrictEqual([]); expect(functions.toQuantity(context, [toTypedValue('xyz')])).toStrictEqual([]); expect(functions.toQuantity(context, [toTypedValue({})])).toStrictEqual([]); }); test('convertsToQuantity', () => { expect(functions.convertsToQuantity(context, [])).toStrictEqual([]); expect(functions.convertsToQuantity(context, [TYPED_TRUE])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToQuantity(context, [TYPED_FALSE])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToQuantity(context, [TYPED_0])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToQuantity(context, [TYPED_1])).toStrictEqual([TYPED_TRUE]); expect(() => functions.convertsToQuantity(context, [TYPED_1, TYPED_2])).toThrow(); expect(functions.convertsToQuantity(context, [toTypedValue('1')])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToQuantity(context, [toTypedValue('true')])).toStrictEqual([TYPED_FALSE]); expect(functions.convertsToQuantity(context, [toTypedValue('false')])).toStrictEqual([TYPED_FALSE]); expect(functions.convertsToQuantity(context, [toTypedValue('xyz')])).toStrictEqual([TYPED_FALSE]); expect(functions.convertsToQuantity(context, [toTypedValue({})])).toStrictEqual([TYPED_FALSE]); }); test('toString', () => { const fhirToString = functions.toString as unknown as FhirPathFunction; expect(fhirToString(context, [])).toStrictEqual([]); expect(() => fhirToString(context, [null as unknown as TypedValue])).toThrow(); expect(() => fhirToString(context, [undefined as unknown as TypedValue])).toThrow(); expect(() => fhirToString(context, [TYPED_1, TYPED_2])).toThrow(); expect(fhirToString(context, [TYPED_TRUE])).toStrictEqual([toTypedValue('true')]); expect(fhirToString(context, [TYPED_FALSE])).toStrictEqual([toTypedValue('false')]); expect(fhirToString(context, [TYPED_0])).toStrictEqual([toTypedValue('0')]); expect(fhirToString(context, [TYPED_1])).toStrictEqual([toTypedValue('1')]); expect(fhirToString(context, [toTypedValue(null)])).toStrictEqual([]); expect(fhirToString(context, [toTypedValue(undefined)])).toStrictEqual([]); expect(fhirToString(context, [toTypedValue('1')])).toStrictEqual([toTypedValue('1')]); expect(fhirToString(context, [toTypedValue('true')])).toStrictEqual([toTypedValue('true')]); expect(fhirToString(context, [toTypedValue('false')])).toStrictEqual([toTypedValue('false')]); expect(fhirToString(context, [toTypedValue('xyz')])).toStrictEqual([toTypedValue('xyz')]); }); test('convertsToString', () => { expect(functions.convertsToString(context, [])).toStrictEqual([]); expect(() => functions.convertsToString(context, [TYPED_1, TYPED_2])).toThrow(); expect(functions.convertsToString(context, [TYPED_TRUE])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToString(context, [TYPED_FALSE])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToString(context, [TYPED_0])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToString(context, [TYPED_1])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToString(context, [toTypedValue('1')])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToString(context, [toTypedValue('true')])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToString(context, [toTypedValue('false')])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToString(context, [toTypedValue('xyz')])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToString(context, [toTypedValue({})])).toStrictEqual([TYPED_TRUE]); }); test('toTime', () => { expect(functions.toTime(context, [])).toStrictEqual([]); expect(() => functions.toTime(context, [TYPED_1, TYPED_2])).toThrow(); expect(functions.toTime(context, [toTypedValue('12:00:00')])).toStrictEqual([ { type: PropertyType.time, value: 'T12:00:00.000Z' }, ]); expect(functions.toTime(context, [toTypedValue('T12:00:00')])).toStrictEqual([ { type: PropertyType.time, value: 'T12:00:00.000Z' }, ]); expect(functions.toTime(context, [toTypedValue('foo')])).toStrictEqual([]); expect(functions.toTime(context, [TYPED_1])).toStrictEqual([]); expect(functions.toTime(context, [TYPED_TRUE])).toStrictEqual([]); }); test('convertsToTime', () => { expect(functions.convertsToTime(context, [])).toStrictEqual([]); expect(() => functions.convertsToTime(context, [TYPED_1, TYPED_2])).toThrow(); expect(functions.convertsToTime(context, [toTypedValue('12:00:00')])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToTime(context, [toTypedValue('T12:00:00')])).toStrictEqual([TYPED_TRUE]); expect(functions.convertsToTime(context, [toTypedValue('foo')])).toStrictEqual([TYPED_FALSE]); expect(functions.convertsToTime(context, [TYPED_1])).toStrictEqual([TYPED_FALSE]); expect(functions.convertsToTime(context, [TYPED_TRUE])).toStrictEqual([TYPED_FALSE]); }); // 5.6. String Manipulation. test('indexOf', () => { expect(functions.indexOf(context, [TYPED_APPLE], new LiteralAtom(toTypedValue('a')))).toStrictEqual([TYPED_0]); }); test('substring', () => { expect(functions.substring(context, [], new LiteralAtom(toTypedValue(0)))).toStrictEqual([]); expect(() => functions.substring(context, [TYPED_1], new LiteralAtom(toTypedValue(0)))).toThrow(); expect(functions.substring(context, [TYPED_APPLE], new LiteralAtom(toTypedValue(-1)))).toStrictEqual([]); expect(functions.substring(context, [TYPED_APPLE], new LiteralAtom(toTypedValue(6)))).toStrictEqual([]); expect(functions.substring(context, [TYPED_APPLE], new LiteralAtom(toTypedValue(0)))).toStrictEqual([TYPED_APPLE]); expect(functions.substring(context, [TYPED_APPLE], new LiteralAtom(toTypedValue(2)))).toStrictEqual([ toTypedValue('ple'), ]); }); test('startsWith', () => { expect(functions.startsWith(context, [TYPED_APPLE], new LiteralAtom(toTypedValue('app')))).toStrictEqual([ TYPED_TRUE, ]); expect(functions.startsWith(context, [TYPED_APPLE], new LiteralAtom(toTypedValue('ple')))).toStrictEqual([ TYPED_FALSE, ]); }); test('endsWith', () => { expect(functions.endsWith(context, [TYPED_APPLE], new LiteralAtom(toTypedValue('app')))).toStrictEqual([ TYPED_FALSE, ]); expect(functions.endsWith(context, [TYPED_APPLE], new LiteralAtom(toTypedValue('ple')))).toStrictEqual([ TYPED_TRUE, ]); }); test('contains', () => { expect(functions.contains(context, [TYPED_APPLE], new LiteralAtom(toTypedValue('app')))).toStrictEqual([ TYPED_TRUE, ]); expect(functions.contains(context, [TYPED_APPLE], new LiteralAtom(toTypedValue('ple')))).toStrictEqual([ TYPED_TRUE, ]); expect(functions.contains(context, [TYPED_APPLE], new LiteralAtom(toTypedValue('ppl')))).toStrictEqual([ TYPED_TRUE, ]); expect(functions.contains(context, [TYPED_APPLE], new LiteralAtom(toTypedValue('xyz')))).toStrictEqual([ TYPED_FALSE, ]); }); test('upper', () => { expect(functions.upper(context, [toTypedValue('apple')])).toStrictEqual([toTypedValue('APPLE')]); expect(functions.upper(context, [toTypedValue('Apple')])).toStrictEqual([toTypedValue('APPLE')]); expect(functions.upper(context, [toTypedValue('APPLE')])).toStrictEqual([toTypedValue('APPLE')]); }); test('lower', () => { expect(functions.lower(context, [TYPED_APPLE])).toStrictEqual([TYPED_APPLE]); expect(functions.lower(context, [toTypedValue('Apple')])).toStrictEqual([TYPED_APPLE]); expect(functions.lower(context, [toTypedValue('APPLE')])).toStrictEqual([TYPED_APPLE]); }); test('replace', () => { expect( functions.replace( context, [toTypedValue('banana')], new LiteralAtom(toTypedValue('nana')), new LiteralAtom(toTypedValue('tman')) ) ).toStrictEqual([toTypedValue('batman')]); }); test('matches', () => { expect(functions.matches(context, [TYPED_APPLE], new LiteralAtom(TYPED_A))).toStrictEqual([TYPED_TRUE]); }); test('replaceMatches', () => { expect( functions.replaceMatches( context, [toTypedValue('banana')], new LiteralAtom(toTypedValue('(na)+')), new LiteralAtom(toTypedValue('tman')) ) ).toStrictEqual([toTypedValue('batman')]); }); test('length', () => { expect(functions.length(context, [toTypedValue('')])).toStrictEqual([TYPED_0]); expect(functions.length(context, [toTypedValue('x')])).toStrictEqual([TYPED_1]); expect(functions.length(context, [toTypedValue('xy')])).toStrictEqual([TYPED_2]); expect(functions.length(context, [toTypedValue('xyz')])).toStrictEqual([TYPED_3]); }); test('toChars', () => { expect(functions.toChars(context, [toTypedValue('')])).toStrictEqual([]); expect(functions.toChars(context, [toTypedValue('x')])).toStrictEqual([TYPED_X]); expect(functions.toChars(context, [toTypedValue('xy')])).toStrictEqual([TYPED_X, TYPED_Y]); expect(functions.toChars(context, [toTypedValue('xyz')])).toStrictEqual([TYPED_X, TYPED_Y, TYPED_Z]); }); // Additional string functions // STU Note: the contents of this section are Standard for Trial Use (STU) test('join', () => { expect(functions.join(context, [toTypedValue('')])).toStrictEqual([toTypedValue('')]); expect(functions.join(context, [toTypedValue('a'), toTypedValue('b'), toTypedValue('c')])).toStrictEqual([ toTypedValue('abc'), ]); expect( functions.join( context, [toTypedValue('a'), toTypedValue('b'), toTypedValue('c')], new LiteralAtom(toTypedValue(',')) ) ).toStrictEqual([toTypedValue('a,b,c')]); expect(() => functions.join(context, [toTypedValue('')], new LiteralAtom(toTypedValue(1)))).toThrow( 'Separator must be a string' ); }); // 5.7. Math test('abs', () => { expect(() => functions.abs(context, [toTypedValue('xyz')])).toThrow(); expect(functions.abs(context, [])).toStrictEqual([]); expect(functions.abs(context, [toTypedValue(-1)])).toStrictEqual([TYPED_1]); expect(functions.abs(context, [TYPED_0])).toStrictEqual([TYPED_0]); expect(functions.abs(context, [TYPED_1])).toStrictEqual([TYPED_1]); }); // 5.8. Tree navigation // 5.9. Utility functions test('now', () => { expect(functions.now(context, [])[0]).toBeDefined(); }); test('timeOfDay', () => { expect(functions.timeOfDay(context, [])[0]).toBeDefined(); }); test('today', () => { expect(functions.today(context, [])[0]).toBeDefined(); }); test('between', () => { expect( functions.between( context, [], new LiteralAtom(toTypedValue('2000-01-01')), new LiteralAtom(toTypedValue('2020-01-01')), new LiteralAtom(toTypedValue('years')) ) ).toStrictEqual([ { type: PropertyType.Quantity, value: { value: 20, unit: 'years' }, }, ]); expect(() => functions.between( context, [], new LiteralAtom(toTypedValue('xxxx-xx-xx')), new LiteralAtom(toTypedValue('2020-01-01')), new LiteralAtom(toTypedValue('years')) ) ).toThrow('Invalid start date'); expect(() => functions.between( context, [], new LiteralAtom(toTypedValue('2020-01-01')), new LiteralAtom(toTypedValue('xxxx-xx-xx')), new LiteralAtom(toTypedValue('years')) ) ).toThrow('Invalid end date'); expect(() => functions.between( context, [], new LiteralAtom(toTypedValue('2000-01-01')), new LiteralAtom(toTypedValue('2020-01-01')), new LiteralAtom(toTypedValue('xxxxx')) ) ).toThrow('Invalid units'); }); // Other test('resolve', () => { const patient: Patient = { resourceType: 'Patient', id: '123', identifier: [{ system: 'https://example.com', value: '123456' }], name: [{ family: 'Simpson', given: ['Homer'] }], }; // Canonical string resolves to patient-like with resourceType and id expect(functions.resolve(context, [toTypedValue(getReferenceString(patient))])).toStrictEqual([ toTypedValue({ resourceType: 'Patient', id: '123' }), ]); // Reference resolves to patient-like with resourceType and id expect(functions.resolve(context, [toTypedValue(createReference(patient))])).toStrictEqual([ toTypedValue({ resourceType: 'Patient', id: '123' }), ]); // Number resolves to nothing expect(functions.resolve(context, [toTypedValue(123)])).toStrictEqual([]); // Reference with embedded resource resolves to resource // We don't normally use embeded resources, except in GraphQL queries expect( functions.resolve(context, [toTypedValue({ reference: getReferenceString(patient), resource: patient })]) ).toStrictEqual([toTypedValue(patient)]); // Identifier references with a "type" should resolve to patient search reference expect( functions.resolve(context, [ { type: PropertyType.Reference, value: { type: 'Patient', identifier: patient.identifier?.[0] } }, ]) ).toStrictEqual([toTypedValue({ resourceType: 'Patient' })]); // Identifier references without a "type" resolves to nothing expect( functions.resolve(context, [{ type: PropertyType.Reference, value: { identifier: patient.identifier?.[0] } }]) ).toStrictEqual([]); }); test('as', () => { expect(functions.as(context, [toTypedValue({ resourceType: 'Patient', id: '123' })])).toStrictEqual([ toTypedValue({ resourceType: 'Patient', id: '123' }), ]); }); // 12. Formal Specifications test('type', () => { expect(functions.type(context, [TYPED_TRUE])).toStrictEqual([ toTypedValue({ namespace: 'System', name: 'Boolean' }), ]); expect(functions.type(context, [toTypedValue(123)])).toStrictEqual([ toTypedValue({ namespace: 'System', name: 'Integer' }), ]); expect(functions.type(context, [toTypedValue({ resourceType: 'Patient', id: '123' })])).toStrictEqual([ toTypedValue({ namespace: 'FHIR', name: 'Patient' }), ]); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server