From ae1038abafebb92328776e5ca122352c1a40f4b3 Mon Sep 17 00:00:00 2001 From: Lee Surprenant Date: Tue, 6 Apr 2021 21:42:57 -0400 Subject: [PATCH] issue #2207 - consistently throw FHIRPatchException for bad patches Previously, which exception you got was dependent on what was wrong with the patch. And prior to the model changes for this same issue, we actually allowed invalid patches to be applied (in the case of adding, inserting, and replacing on a list). Signed-off-by: Lee Surprenant --- .../fhir/model/visitor/CopyingVisitor.java | 30 ++-- .../ibm/fhir/path/patch/FHIRPathPatch.java | 40 ++--- .../com/ibm/fhir/path/util/AddingVisitor.java | 18 ++- .../com/ibm/fhir/path/util/FHIRPathUtil.java | 46 +++--- .../ibm/fhir/path/util/InsertingVisitor.java | 6 +- .../ibm/fhir/path/util/ReplacingVisitor.java | 9 +- .../patch/test/FHIRPathPatchBuilderTest.java | 147 ++++++++++++++++-- .../path/patch/test/FHIRPathUtilTest.java | 48 ++++++ 8 files changed, 261 insertions(+), 83 deletions(-) create mode 100644 fhir-path/src/test/java/com/ibm/fhir/path/patch/test/FHIRPathUtilTest.java diff --git a/fhir-model/src/main/java/com/ibm/fhir/model/visitor/CopyingVisitor.java b/fhir-model/src/main/java/com/ibm/fhir/model/visitor/CopyingVisitor.java index 7b400a9bea9..4879b2a65c7 100644 --- a/fhir-model/src/main/java/com/ibm/fhir/model/visitor/CopyingVisitor.java +++ b/fhir-model/src/main/java/com/ibm/fhir/model/visitor/CopyingVisitor.java @@ -20,8 +20,6 @@ import javax.lang.model.SourceVersion; import com.ibm.fhir.model.builder.Builder; -import com.ibm.fhir.model.resource.Bundle; -import com.ibm.fhir.model.resource.Parameters; import com.ibm.fhir.model.resource.Resource; import com.ibm.fhir.model.type.Code; import com.ibm.fhir.model.type.Element; @@ -171,16 +169,14 @@ private void _visitEnd(java.lang.String elementName, int index, Visitable visite Builder parentBuilder = parent.getBuilder(); Object obj = wrapper.getBuilder().build(); - MethodHandle methodHandle; + Class expectedType = ModelSupport.getElementType(parentBuilder.getClass().getEnclosingClass(), elementName); + if (obj != null && !expectedType.isInstance(obj)) { + throw new IllegalStateException("Expected argument of type " + expectedType + " but found " + obj.getClass()); + } + try { - MethodType mt; - if ((visited instanceof Element && ModelSupport.isChoiceElement(parentBuilder.getClass().getEnclosingClass(), elementName)) - || (visited instanceof Resource && isResourceContainer(parentBuilder, elementName))) { - mt = MethodType.methodType(parentBuilder.getClass(), elementOrResource); - } else { - mt = MethodType.methodType(parentBuilder.getClass(), visited.getClass()); - } - methodHandle = MethodHandles.publicLookup().findVirtual(parentBuilder.getClass(), setterName(elementName), mt); + MethodType mt = MethodType.methodType(parentBuilder.getClass(), expectedType); + MethodHandle methodHandle = MethodHandles.publicLookup().findVirtual(parentBuilder.getClass(), setterName(elementName), mt); methodHandle.invoke(parentBuilder, obj); } catch (Throwable t) { throw new RuntimeException("Unexpected error while visiting " + parentBuilder.getClass() + "." + elementName, t); @@ -191,12 +187,6 @@ private void _visitEnd(java.lang.String elementName, int index, Visitable visite } } - private boolean isResourceContainer(Builder parentBuilder, String elementName) { - return (parentBuilder instanceof Bundle.Entry.Builder && "resource".equals(elementName)) || - (parentBuilder instanceof Bundle.Entry.Response.Builder && "outcome".equals(elementName)) || - (parentBuilder instanceof Parameters.Parameter.Builder && "resource".equals(elementName)); - } - /** * Subclasses may override doVisitListStart */ @@ -214,10 +204,16 @@ public void visitEnd(String elementName, List visitables, C doVisitListEnd(elementName, visitables, type); ListWrapper listWrapper = listStack.pop(); if (listWrapper.isDirty()) { + for (Visitable obj : listWrapper.getList()) { + if (!type.isInstance(obj)) { + throw new IllegalStateException("Expected argument of type " + type + " but found " + obj.getClass()); + } + } BuilderWrapper parent = builderStack.peek(); parent.dirty(true); Builder parentBuilder = parent.getBuilder(); MethodHandles.Lookup lookup = MethodHandles.publicLookup(); + MethodType mt = MethodType.methodType(parentBuilder.getClass(), java.util.Collection.class); MethodHandle methodHandle; try { diff --git a/fhir-path/src/main/java/com/ibm/fhir/path/patch/FHIRPathPatch.java b/fhir-path/src/main/java/com/ibm/fhir/path/patch/FHIRPathPatch.java index fc8934306f7..0272f511207 100644 --- a/fhir-path/src/main/java/com/ibm/fhir/path/patch/FHIRPathPatch.java +++ b/fhir-path/src/main/java/com/ibm/fhir/path/patch/FHIRPathPatch.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2020 + * (C) Copyright IBM Corp. 2020, 2021 * * SPDX-License-Identifier: Apache-2.0 */ @@ -26,7 +26,7 @@ public class FHIRPathPatch implements FHIRPatch { private FHIRPathPatch(Builder builder) { this.operations = Collections.unmodifiableList(builder.operations); } - + @Override public T apply(T resource) throws FHIRPatchException { for (FHIRPathPatchOperation fhirPathPatchOperation : operations) { @@ -34,7 +34,7 @@ public T apply(T resource) throws FHIRPatchException { } return resource; } - + /** * Convert the FHIRPathPatch to a FHIR Parameters resource */ @@ -45,22 +45,22 @@ public Parameters toParameters() { } return builder.build(); } - + public Builder toBuilder() { return new Builder().from(this); } - + public static Builder builder() { return new Builder(); } - - public static class Builder { + + public static class Builder { private List operations = new ArrayList<>(3); - + private Builder() { // hidden constructor } - + /** * Add an add operation to the FHIRPathPatch */ @@ -68,7 +68,7 @@ public Builder add(String path, String elementName, Element element) { operations.add(new FHIRPathPatchAdd(path, elementName, element)); return this; } - + /** * Add a delete operation to the FHIRPathPatch */ @@ -76,7 +76,7 @@ public Builder delete(String path) { operations.add(new FHIRPathPatchDelete(path)); return this; } - + /** * Add an insert operation to the FHIRPathPatch */ @@ -84,7 +84,7 @@ public Builder insert(String path, Element element, Integer index) { operations.add(new FHIRPathPatchInsert(path, element, index)); return this; } - + /** * Add a move operation to the FHIRPathPatch */ @@ -92,7 +92,7 @@ public Builder move(String path, Integer source, Integer destination) { operations.add(new FHIRPathPatchMove(path, source, destination)); return this; } - + /** * Add an add operation to the FHIRPathPatch */ @@ -100,7 +100,7 @@ public Builder replace(String path, Element element) { operations.add(new FHIRPathPatchReplace(path, element)); return this; } - + /** * Add all patch operations from the passed FHIRPathPatch */ @@ -108,10 +108,10 @@ public Builder from(FHIRPathPatch patch) { operations.addAll(patch.operations); return this; } - + /** * Build the {@link FHIRPathPatch} - * + * * @return * An immutable object of type {@link FHIRPathPatch} */ @@ -119,10 +119,10 @@ public FHIRPathPatch build() { return new FHIRPathPatch(this); } } - + /** * Parse a FHIRPathPatch from a FHIR Parameters resource - * + * * @throws IllegalArgumentException if the Parameters object does not satisfy the requirements of a FHIRPath Patch */ public static FHIRPathPatch from(Parameters params) { @@ -138,10 +138,10 @@ public static FHIRPathPatch from(Parameters params) { return builder.build(); } - + /** * Parse the passed Parameter and add it to the builder - * + * * @throws IllegalArgumentException if the Parameter object does not represent a valid FHIRPath Patch operation */ private static void addOperation(Builder builder, Parameter operation) { diff --git a/fhir-path/src/main/java/com/ibm/fhir/path/util/AddingVisitor.java b/fhir-path/src/main/java/com/ibm/fhir/path/util/AddingVisitor.java index b8ca43ba39e..1ff11aac37a 100644 --- a/fhir-path/src/main/java/com/ibm/fhir/path/util/AddingVisitor.java +++ b/fhir-path/src/main/java/com/ibm/fhir/path/util/AddingVisitor.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2020 + * (C) Copyright IBM Corp. 2020, 2021 * * SPDX-License-Identifier: Apache-2.0 */ @@ -26,6 +26,7 @@ class AddingVisitor extends CopyingVisitor { * @param parentPath a "simple" FHIRPath path to the parent of the element being added * @param elementName the name of the element to add * @param value the element to add + * @throws IllegalArgumentException */ public AddingVisitor(Visitable parent, String parentPath, String elementName, Visitable value) { this.path = Objects.requireNonNull(parentPath); @@ -34,17 +35,18 @@ public AddingVisitor(Visitable parent, String parentPath, String elementName, Vi this.value = Objects.requireNonNull(value) instanceof Code ? convertToCodeSubtype(parent, elementName, (Code)value) : value; } - + @Override protected void doVisitListEnd(String elementName, List visitables, Class type) { - if (getPath().equals(path) && - type.isAssignableFrom(value.getClass()) && - elementName.equals(this.elementNameToAdd)) { + if (getPath().equals(path) && elementName.equals(this.elementNameToAdd)) { + if (!type.isAssignableFrom(value.getClass())) { + throw new IllegalStateException("target " + type + " is not assignable from " + value.getClass()); + } getList().add(value); markListDirty(); } } - + @Override public boolean visit(String elementName, int index, Visitable value) { if (!isRepeatingElement) { @@ -56,7 +58,7 @@ public boolean visit(String elementName, int index, Visitable value) { } return true; } - + @Override protected void doVisitEnd(String elementName, int elementIndex, Resource resource) { if (!isRepeatingElement) { @@ -65,7 +67,7 @@ protected void doVisitEnd(String elementName, int elementIndex, Resource resourc } } } - + @Override protected void doVisitEnd(String elementName, int elementIndex, Element element) { if (!isRepeatingElement) { diff --git a/fhir-path/src/main/java/com/ibm/fhir/path/util/FHIRPathUtil.java b/fhir-path/src/main/java/com/ibm/fhir/path/util/FHIRPathUtil.java index 4ccac896d1b..61950c5d19e 100644 --- a/fhir-path/src/main/java/com/ibm/fhir/path/util/FHIRPathUtil.java +++ b/fhir-path/src/main/java/com/ibm/fhir/path/util/FHIRPathUtil.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2019, 2020 + * (C) Copyright IBM Corp. 2019, 2021 * * SPDX-License-Identifier: Apache-2.0 */ @@ -865,14 +865,13 @@ public static T add(T elementOrResource, String fhirPath, Visitable parent = node.isResourceNode() ? node.asResourceNode().resource() : node.asElementNode().element(); - AddingVisitor addingVisitor = new AddingVisitor<>(parent, node.path(), elementName, value); - try { + AddingVisitor addingVisitor = new AddingVisitor<>(parent, node.path(), elementName, value); elementOrResource.accept(addingVisitor); - } catch (IllegalStateException e) { + return addingVisitor.getResult(); + } catch (IllegalArgumentException | IllegalStateException e) { throw new FHIRPatchException("An error occurred while adding the value", fhirPath, e); } - return addingVisitor.getResult(); } /** @@ -883,14 +882,14 @@ public static T add(T elementOrResource, String fhirPath, */ public static T delete(T elementOrResource, String fhirPath) throws FHIRPathException, FHIRPatchException { FHIRPathNode node = evaluateToSingle(elementOrResource, fhirPath); - DeletingVisitor deletingVisitor = new DeletingVisitor(node.path()); try { + DeletingVisitor deletingVisitor = new DeletingVisitor(node.path()); elementOrResource.accept(deletingVisitor); - } catch (IllegalStateException e) { + return deletingVisitor.getResult(); + } catch (IllegalArgumentException | IllegalStateException e) { throw new FHIRPatchException("An error occurred while deleting the value", fhirPath, e); } - return deletingVisitor.getResult(); } /** @@ -909,16 +908,22 @@ public static T replace(T elementOrResource, String fhirPa Visitable parent = parentNode.isResourceNode() ? parentNode.asResourceNode().resource() : parentNode.asElementNode().element(); - ReplacingVisitor replacingVisitor = new ReplacingVisitor(parent, elementName, node.path(), value); - try { + ReplacingVisitor replacingVisitor = new ReplacingVisitor(parent, elementName, node.path(), value); elementOrResource.accept(replacingVisitor); - } catch (IllegalStateException e) { + return replacingVisitor.getResult(); + } catch (IllegalArgumentException | IllegalStateException e) { throw new FHIRPatchException("An error occurred while replacing the value", fhirPath, e); } - return replacingVisitor.getResult(); } + /** + * @param elementOrResource + * @param fhirPath + * @return + * @throws FHIRPathException + * @throws FHIRPatchException if the fhirPath does not evaluate to a single node + */ private static FHIRPathNode evaluateToSingle(Visitable elementOrResource, String fhirPath) throws FHIRPathException, FHIRPatchException { /* * 1. The FHIRPath statement must return a single element. @@ -933,6 +938,9 @@ private static FHIRPathNode evaluateToSingle(Visitable elementOrResource, String * 5. Except for the delete operation, it is an error if no element matches the specified path. */ Collection nodes = evaluator.evaluate(elementOrResource, fhirPath); + if (!isSingleton(nodes)) { + throw new FHIRPatchException("Expected a singleton but instead found " + nodes.size() + " nodes", fhirPath); + } return getSingleton(nodes); } @@ -959,14 +967,13 @@ public static T insert(T elementOrResource, String fhirPat Visitable parent = parentNode.isResourceNode() ? parentNode.asResourceNode().resource() : parentNode.asElementNode().element(); - InsertingVisitor insertingVisitor = new InsertingVisitor(parent, parentNode.path(), elementName, index, value); - try { + InsertingVisitor insertingVisitor = new InsertingVisitor(parent, parentNode.path(), elementName, index, value); elementOrResource.accept(insertingVisitor); - } catch (IllegalStateException e) { + return insertingVisitor.getResult(); + } catch (IllegalArgumentException | IllegalStateException e) { throw new FHIRPatchException("An error occurred while inserting the value", fhirPath, e); } - return insertingVisitor.getResult(); } /** @@ -990,14 +997,13 @@ public static T move(T elementOrResource, String fhirPath, FHIRPathTree tree = evaluator.getEvaluationContext().getTree(); FHIRPathNode parent = getCommonParent(fhirPath, nodes, tree); - MovingVisitor movingVisitor = new MovingVisitor(parent.path(), elementName, source, target); - try { + MovingVisitor movingVisitor = new MovingVisitor(parent.path(), elementName, source, target); elementOrResource.accept(movingVisitor); - } catch (IllegalStateException e) { + return movingVisitor.getResult(); + } catch (IllegalArgumentException | IllegalStateException e) { throw new FHIRPatchException("An error occurred while moving the value", fhirPath, e); } - return movingVisitor.getResult(); } /** diff --git a/fhir-path/src/main/java/com/ibm/fhir/path/util/InsertingVisitor.java b/fhir-path/src/main/java/com/ibm/fhir/path/util/InsertingVisitor.java index 3861f72464e..00cc51f289b 100644 --- a/fhir-path/src/main/java/com/ibm/fhir/path/util/InsertingVisitor.java +++ b/fhir-path/src/main/java/com/ibm/fhir/path/util/InsertingVisitor.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2020 + * (C) Copyright IBM Corp. 2020, 2021 * * SPDX-License-Identifier: Apache-2.0 */ @@ -23,6 +23,7 @@ class InsertingVisitor extends CopyingVisitor { * @param elementName * @param index * @param value + * @throws IllegalArgumentException */ public InsertingVisitor(Visitable parent, String parentPath, String elementName, int index, Visitable value) { this.parentPath = Objects.requireNonNull(parentPath); @@ -35,6 +36,9 @@ public InsertingVisitor(Visitable parent, String parentPath, String elementName, @Override protected void doVisitListEnd(String elementName, List visitables, Class type) { if (getPath().equals(parentPath) && elementName.equals(this.elementNameToInsert)) { + if (!type.isInstance(value)) { + throw new IllegalStateException("target " + type + " is not assignable from " + value.getClass()); + } getList().add(index, value); markListDirty(); } diff --git a/fhir-path/src/main/java/com/ibm/fhir/path/util/ReplacingVisitor.java b/fhir-path/src/main/java/com/ibm/fhir/path/util/ReplacingVisitor.java index a5ac5196039..ee818794bc5 100644 --- a/fhir-path/src/main/java/com/ibm/fhir/path/util/ReplacingVisitor.java +++ b/fhir-path/src/main/java/com/ibm/fhir/path/util/ReplacingVisitor.java @@ -1,5 +1,5 @@ /* - * (C) Copyright IBM Corp. 2020 + * (C) Copyright IBM Corp. 2020, 2021 * * SPDX-License-Identifier: Apache-2.0 */ @@ -17,6 +17,13 @@ class ReplacingVisitor extends CopyingVisitor { private String pathToReplace; private Visitable newValue; + /** + * @param parent + * @param elementName + * @param pathToReplace + * @param newValue + * @throws IllegalArgumentException + */ public ReplacingVisitor(Visitable parent, String elementName, String pathToReplace, Visitable newValue) { this.pathToReplace = Objects.requireNonNull(pathToReplace); this.newValue = Objects.requireNonNull(newValue) instanceof Code ? diff --git a/fhir-path/src/test/java/com/ibm/fhir/path/patch/test/FHIRPathPatchBuilderTest.java b/fhir-path/src/test/java/com/ibm/fhir/path/patch/test/FHIRPathPatchBuilderTest.java index e3688f4d2ee..ecfb1936a14 100644 --- a/fhir-path/src/test/java/com/ibm/fhir/path/patch/test/FHIRPathPatchBuilderTest.java +++ b/fhir-path/src/test/java/com/ibm/fhir/path/patch/test/FHIRPathPatchBuilderTest.java @@ -1,6 +1,6 @@ /* - * (C) Copyright IBM Corp. 2019, 2020 - * + * (C) Copyright IBM Corp. 2019, 2021 + * * SPDX-License-Identifier: Apache-2.0 */ @@ -17,43 +17,152 @@ import com.ibm.fhir.model.patch.exception.FHIRPatchException; import com.ibm.fhir.model.resource.Patient; +import com.ibm.fhir.model.resource.Patient.Communication; +import com.ibm.fhir.model.resource.Patient.Contact; +import com.ibm.fhir.model.type.Code; +import com.ibm.fhir.model.type.CodeableConcept; +import com.ibm.fhir.model.type.Coding; +import com.ibm.fhir.model.type.Date; +import com.ibm.fhir.model.type.DateTime; import com.ibm.fhir.model.type.Extension; import com.ibm.fhir.model.type.HumanName; import com.ibm.fhir.model.type.Identifier; import com.ibm.fhir.model.type.Time; import com.ibm.fhir.model.type.Uri; +import com.ibm.fhir.model.type.code.AccountStatus; +import com.ibm.fhir.model.type.code.AdministrativeGender; +import com.ibm.fhir.model.type.code.DataAbsentReason; import com.ibm.fhir.path.patch.FHIRPathPatch; public class FHIRPathPatchBuilderTest { @Test private void patchBuilderTestIncremental() throws Exception { Patient patient = Patient.builder().id("test").build(); - + Patient patchedPatient = buildAdd().apply(patient); patient = addViaBuilder(patient); assertEquals(patchedPatient, patient); - + patchedPatient = buildDelete().apply(patient); patient = deleteViaBuilder(patient); assertEquals(patchedPatient, patient); - + patchedPatient = buildInsert().apply(patient); patient = insertViaBuilder(patient); assertEquals(patchedPatient, patient); - + patchedPatient = buildMove().apply(patient); patient = moveViaBuilder(patient); assertEquals(patchedPatient, patient); - + patchedPatient = buildReplace().apply(patient); patient = replaceViaBuilder(patient); assertEquals(patchedPatient, patient); } - + + @Test(expectedExceptions = FHIRPatchException.class) + private void patchBuilderTestBadAddList() throws Exception { + Patient patient = Patient.builder().id("test").build(); + Patient modifiedPatient = FHIRPathPatch.builder() + .add("Patient", "communication", Contact.builder().name(HumanName.builder().family(string("Last")).build()).build()) + .build() + .apply(patient); + System.out.println(modifiedPatient); + } + + @Test(expectedExceptions = FHIRPatchException.class) + private void patchBuilderTestBadAddSingle() throws Exception { + Patient patient = Patient.builder().id("test").build(); + Patient modifiedPatient = FHIRPathPatch.builder() + .add("Patient", "birthDate", DateTime.now()) + .build() + .apply(patient); + System.out.println(modifiedPatient); + } + + @Test(expectedExceptions = FHIRPatchException.class) + private void patchBuilderTestBadAddSingleCode() throws Exception { + Patient patient = Patient.builder().id("test").build(); + Patient modifiedPatient = FHIRPathPatch.builder() + .add("Patient", "active", AccountStatus.UNKNOWN) + .build() + .apply(patient); + System.out.println(modifiedPatient); + } + + @Test(expectedExceptions = FHIRPatchException.class) + private void patchBuilderTestBadAddDeepCode() throws Exception { + Patient patient = Patient.builder().id("test").build(); + Patient modifiedPatient = FHIRPathPatch.builder() + .add("Patient", "contact", Contact.builder() + .name(HumanName.builder().family(string("Last")).build()) + .build()) + .add("Patient.contact", "gender", DataAbsentReason.MASKED) + .build() + .apply(patient); + System.out.println(modifiedPatient); + } + + @Test(expectedExceptions = FHIRPatchException.class) + private void patchBuilderTestBadInsert() throws Exception { + Patient patient = Patient.builder().id("test").build(); + Patient modifiedPatient = FHIRPathPatch.builder() + .add("Patient", "communication", Communication.builder() + .language(CodeableConcept.builder() + .coding(Coding.builder() + .system(Uri.of("urn:ietf:bcp:47")) + .code(Code.of("en")) + .build()) + .build()) + .build()) + .insert("Patient.communication", Contact.builder().name(HumanName.builder().family(string("Last")).build()).build(), 1) + .build() + .apply(patient); + System.out.println(modifiedPatient); + } + + @Test(expectedExceptions = FHIRPatchException.class) + private void patchBuilderTestBadReplaceList() throws Exception { + Patient patient = Patient.builder().id("test").build(); + Patient modifiedPatient = FHIRPathPatch.builder() + .add("Patient", "communication", Communication.builder() + .language(CodeableConcept.builder() + .coding(Coding.builder() + .system(Uri.of("urn:ietf:bcp:47")) + .code(Code.of("en")) + .build()) + .build()) + .build()) + .replace("Patient.communication[0]", Contact.builder().name(HumanName.builder().family(string("Last")).build()).build()) + .build() + .apply(patient); + System.out.println(modifiedPatient); + } + + @Test(expectedExceptions = FHIRPatchException.class) + private void patchBuilderTestBadReplaceSingle() throws Exception { + Patient patient = Patient.builder().id("test").build(); + FHIRPathPatch.builder() + .add("Patient", "birthDate", Date.of("2021")) + .replace("Patient.birthDate", DateTime.now()) + .build() + .apply(patient); + } + + @Test(expectedExceptions = FHIRPatchException.class) + private void patchBuilderTestBadReplaceSingleCode() throws Exception { + Patient patient = Patient.builder().id("test").build(); + FHIRPathPatch.builder() + .add("Patient", "active", com.ibm.fhir.model.type.Boolean.TRUE) + .replace("Patient.active", Code.of("false")) + .build() + .apply(patient); + } + @Test private void patchBuilderTestAll() throws Exception { Patient patient = Patient.builder().id("test").build(); - + FHIRPathPatch allPatch = FHIRPathPatch.builder() .from(buildAdd()) .from(buildDelete()) @@ -61,15 +170,15 @@ private void patchBuilderTestAll() throws Exception { .from(buildMove()) .from(buildReplace()) .build(); - + Patient patchedPatient = allPatch.apply(patient); - + patient = addViaBuilder(patient); patient = deleteViaBuilder(patient); patient = insertViaBuilder(patient); patient = moveViaBuilder(patient); patient = replaceViaBuilder(patient); - + assertEquals(patchedPatient, patient); } @@ -91,6 +200,10 @@ private Patient addViaBuilder(Patient patient) { .build()) .build(), HumanName.builder().family(string("Last")).build()) + .contact(Contact.builder() + .name(HumanName.builder().family(string("Last")).build()) + .gender(AdministrativeGender.FEMALE) + .build()) .active(com.ibm.fhir.model.type.Boolean.TRUE) .build(); } @@ -101,6 +214,8 @@ private FHIRPathPatch buildAdd() throws FHIRPatchException { .add("Patient.identifier", "value", string("it-me")) .add("Patient", "name", HumanName.builder().family(string("Last")).build()) .add("Patient", "name", HumanName.builder().family(string("Last")).build()) + .add("Patient", "contact", Contact.builder().name(HumanName.builder().family(string("Last")).build()).build()) + .add("Patient.contact", "gender", AdministrativeGender.FEMALE) .add("Patient.name[0]", "given", string("First")) .add("Patient.name[0]", "extension", Extension.builder().url("myExtension").build()) .add("Patient.name[0].extension", "extension", Extension.builder().url("lunchTime").build()) @@ -108,7 +223,7 @@ private FHIRPathPatch buildAdd() throws FHIRPatchException { .add("Patient", "active", com.ibm.fhir.model.type.Boolean.TRUE) .build(); } - + private Patient deleteViaBuilder(Patient patient) { return patient.toBuilder() .identifier(Collections.emptySet()) @@ -126,11 +241,11 @@ private FHIRPathPatch buildDelete() throws FHIRPatchException { private Patient insertViaBuilder(Patient patient) { List names = new ArrayList<>(patient.getName()); names.add(2, HumanName.builder().family(string("Inserted")).build()); - + List name1extensions = new ArrayList<>(names.get(0).getExtension()); name1extensions.add(0, Extension.builder().url("inserted").value(string("value")).build()); names.set(0, names.get(0).toBuilder().extension(name1extensions).build()); - + return patient.toBuilder() .name(names) .build(); @@ -139,7 +254,7 @@ private Patient insertViaBuilder(Patient patient) { private FHIRPathPatch buildInsert() throws FHIRPatchException { return FHIRPathPatch.builder() .insert("Patient.name", HumanName.builder().family(string("Inserted")).build(), 2) - .insert("Patient.name[0].extension", + .insert("Patient.name[0].extension", Extension.builder().url("inserted").value(string("value")).build(), 0) .build(); } diff --git a/fhir-path/src/test/java/com/ibm/fhir/path/patch/test/FHIRPathUtilTest.java b/fhir-path/src/test/java/com/ibm/fhir/path/patch/test/FHIRPathUtilTest.java new file mode 100644 index 00000000000..c620ae41f96 --- /dev/null +++ b/fhir-path/src/test/java/com/ibm/fhir/path/patch/test/FHIRPathUtilTest.java @@ -0,0 +1,48 @@ +/* + * (C) Copyright IBM Corp. 2021 + * + * SPDX-License-Identifier: Apache-2.0 + */ +package com.ibm.fhir.path.patch.test; + +import static org.testng.Assert.assertEquals; + +import org.testng.annotations.Test; + +import com.ibm.fhir.model.patch.exception.FHIRPatchException; +import com.ibm.fhir.model.resource.Bundle; +import com.ibm.fhir.model.resource.Bundle.Entry; +import com.ibm.fhir.model.resource.Patient; +import com.ibm.fhir.model.resource.Practitioner; +import com.ibm.fhir.model.type.Uri; +import com.ibm.fhir.model.type.code.BundleType; +import com.ibm.fhir.path.exception.FHIRPathException; +import com.ibm.fhir.path.util.FHIRPathUtil; + +/** + * Tests against the FHIRPathPatch helper methods in FHIRPathUtil + */ +public class FHIRPathUtilTest { + Bundle bundle = Bundle.builder().type(BundleType.COLLECTION).build(); + Patient patient = Patient.builder().id("test").build(); + Practitioner practitioner = Practitioner.builder().id("test").build(); + + @Test + private void testAddListResource() throws FHIRPathException, FHIRPatchException { + Patient modifiedPatient = FHIRPathUtil.add(patient, "Patient", "contained", practitioner); + assertEquals(modifiedPatient.getContained().get(0), practitioner); + } + + @Test + private void testAddSingleResource() throws FHIRPathException, FHIRPatchException { + Entry emptyEntry = Entry.builder().fullUrl(Uri.of("test")).build(); + + Bundle modifiedBundle = FHIRPathUtil.add(bundle, "Bundle", "entry", emptyEntry); + modifiedBundle = FHIRPathUtil.add(modifiedBundle, "Bundle", "entry", emptyEntry); + modifiedBundle = FHIRPathUtil.add(modifiedBundle, "Bundle.entry[0]", "resource", patient); + modifiedBundle = FHIRPathUtil.add(modifiedBundle, "Bundle.entry[1]", "resource", practitioner); + + assertEquals(modifiedBundle.getEntry().get(0).getResource(), patient); + assertEquals(modifiedBundle.getEntry().get(1).getResource(), practitioner); + } +}