use of au.csiro.pathling.fhirpath.function.NamedFunction in project pathling by aehrc.
the class SubsumesFunctionTest method throwsErrorIfArgumentTypeIsUnsupported.
@Test
void throwsErrorIfArgumentTypeIsUnsupported() {
final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).terminologyClientFactory(mock(TerminologyServiceFactory.class)).build();
final ElementPath input = new ElementPathBuilder(spark).fhirType(FHIRDefinedType.CODEABLECONCEPT).build();
final StringLiteralPath argument = StringLiteralPath.fromString("'str'", input);
final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, input, Collections.singletonList(argument));
final NamedFunction subsumesFunction = NamedFunction.getInstance("subsumes");
final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, () -> subsumesFunction.invoke(functionInput));
assertEquals("subsumes function accepts argument of type Coding or CodeableConcept", error.getMessage());
}
use of au.csiro.pathling.fhirpath.function.NamedFunction in project pathling by aehrc.
the class SubsumesFunctionTest method throwsErrorIfInputTypeIsUnsupported.
//
// Test for various validation errors
//
@Test
void throwsErrorIfInputTypeIsUnsupported() {
final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).terminologyClientFactory(mock(TerminologyServiceFactory.class)).build();
final ElementPath argument = new ElementPathBuilder(spark).fhirType(FHIRDefinedType.CODEABLECONCEPT).build();
final ElementPath input = new ElementPathBuilder(spark).fhirType(FHIRDefinedType.STRING).build();
final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, input, Collections.singletonList(argument));
final NamedFunction subsumesFunction = NamedFunction.getInstance("subsumedBy");
final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, () -> subsumesFunction.invoke(functionInput));
assertEquals("subsumedBy function accepts input of type Coding or CodeableConcept", error.getMessage());
}
use of au.csiro.pathling.fhirpath.function.NamedFunction in project pathling by aehrc.
the class SubsumesFunctionTest method throwsErrorIfMoreThanOneArgument.
@Test
void throwsErrorIfMoreThanOneArgument() {
final ParserContext parserContext = new ParserContextBuilder(spark, fhirContext).terminologyClientFactory(mock(TerminologyServiceFactory.class)).build();
final ElementPath input = new ElementPathBuilder(spark).fhirType(FHIRDefinedType.CODEABLECONCEPT).build();
final CodingLiteralPath argument1 = CodingLiteralPath.fromString(CODING_MEDIUM.getSystem() + "|" + CODING_MEDIUM.getCode(), input);
final CodingLiteralPath argument2 = CodingLiteralPath.fromString(CODING_MEDIUM.getSystem() + "|" + CODING_MEDIUM.getCode(), input);
final NamedFunctionInput functionInput = new NamedFunctionInput(parserContext, input, Arrays.asList(argument1, argument2));
final NamedFunction subsumesFunction = NamedFunction.getInstance("subsumes");
final InvalidUserInputError error = assertThrows(InvalidUserInputError.class, () -> subsumesFunction.invoke(functionInput));
assertEquals("subsumes function accepts one argument of type Coding or CodeableConcept", error.getMessage());
}
use of au.csiro.pathling.fhirpath.function.NamedFunction in project pathling by aehrc.
the class InvocationVisitor method visitFunctionInvocation.
/**
* This method gets called when a function call is on the right-hand side of an invocation
* expression.
*
* @param ctx The {@link FunctionInvocationContext}
* @return A {@link FhirPath} expression
*/
@Override
@Nonnull
public FhirPath visitFunctionInvocation(@Nullable final FunctionInvocationContext ctx) {
@Nullable final String functionIdentifier = checkNotNull(ctx).function().identifier().getText();
checkNotNull(functionIdentifier);
final NamedFunction function = NamedFunction.getInstance(functionIdentifier);
// If there is no invoker, we use either the input context or the this context, depending on
// whether we are in the context of function arguments.
final FhirPath input = invoker == null ? context.getThisContext().orElse(context.getInputContext()) : invoker;
// A literal cannot be used as a function input.
checkUserInput(input instanceof NonLiteralPath, "Literal expression cannot be used as input to a function invocation: " + input.getExpression());
final NonLiteralPath nonLiteral = (NonLiteralPath) input;
@Nullable final ParamListContext paramList = ctx.function().paramList();
final List<FhirPath> arguments = new ArrayList<>();
if (paramList != null) {
// The `$this` path will be the same as the input, but with a different expression, and it
// will be singular as it represents a single item.
// NOTE: This works because for $this the context for aggregation grouping on elements
// includes `id` and `this` columns.
// Create and alias the $this column.
final NonLiteralPath thisPath = nonLiteral.toThisPath();
// If the this context has an element ID, we need to add this to the grouping columns so that
// aggregations that occur within the arguments are in the context of an element. Otherwise,
// we add the resource ID column to the groupings.
final List<Column> argumentGroupings = new ArrayList<>(context.getGroupingColumns());
thisPath.getEidColumn().ifPresentOrElse(argumentGroupings::add, () -> argumentGroupings.add(thisPath.getIdColumn()));
// Create a new ParserContext, which includes information about how to evaluate the `$this`
// expression.
final ParserContext argumentContext = new ParserContext(context.getInputContext(), context.getFhirContext(), context.getSparkSession(), context.getDatabase(), context.getTerminologyServiceFactory(), argumentGroupings, context.getNodeIdColumns());
argumentContext.setThisContext(thisPath);
// Parse each of the expressions passed as arguments to the function.
arguments.addAll(paramList.expression().stream().map(expression -> new Visitor(argumentContext).visit(expression)).collect(Collectors.toList()));
}
final NamedFunctionInput functionInput = new NamedFunctionInput(context, nonLiteral, arguments);
return function.invoke(functionInput);
}
Aggregations