Skip to content

Commit

Permalink
Adds ignoringUnrecognizedFields mode to Java JsonFormat and FhirPacka…
Browse files Browse the repository at this point in the history
…ge classes. This allows parsing new versions of the spec using older versions of the spec, as long as the resource in question is normative (such as StructureDefinition).

PiperOrigin-RevId: 559911204
  • Loading branch information
nickgeorge authored and copybara-github committed Aug 25, 2023
1 parent 434d9e3 commit 9bf0deb
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 18 deletions.
45 changes: 31 additions & 14 deletions java/com/google/fhir/common/JsonFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -596,10 +596,15 @@ public static Parser getParser() {
public static final class Parser {
private final ProtoGenTransformer protoGenTransformer;
private final ZoneId defaultTimeZone;
private final boolean ignoreUnrecognizedFields;

private Parser(ZoneId defaultTimeZone, ProtoGenTransformer protoGenTransformer) {
private Parser(
ZoneId defaultTimeZone,
ProtoGenTransformer protoGenTransformer,
boolean ignoreUnrecognizedFields) {
this.protoGenTransformer = protoGenTransformer;
this.defaultTimeZone = defaultTimeZone;
this.ignoreUnrecognizedFields = ignoreUnrecognizedFields;
}

public static Parser withDefaultTimeZone(ZoneId defaultTimeZone) {
Expand All @@ -612,9 +617,10 @@ public static Builder newBuilder() {
}

/** Builder that can be used to obtain new instances of {@link Parser}. */
static final class Builder {
public static final class Builder {
private ZoneId defaultTimeZone;
private ProtoGenTransformer protoGenTransformer;
private boolean ignoreUnrecognizedFields = false;

Builder(ZoneId defaultTimeZone) {
this.defaultTimeZone = defaultTimeZone;
Expand Down Expand Up @@ -642,8 +648,14 @@ Builder withProtoGenTransformer(ProtoGenTransformer protoGenTransformer) {
return this;
}

Parser build() {
return new Parser(defaultTimeZone, protoGenTransformer);
@CanIgnoreReturnValue
public Builder ignoreUnrecognizedFields(boolean ignoreUnrecognizedFields) {
this.ignoreUnrecognizedFields = ignoreUnrecognizedFields;
return this;
}

public Parser build() {
return new Parser(defaultTimeZone, protoGenTransformer, ignoreUnrecognizedFields);
}
}

Expand Down Expand Up @@ -754,17 +766,22 @@ private void mergeMessage(JsonObject json, Message.Builder builder)
+ descriptor.getFullName());
}
} else {
String names = "";
for (Map.Entry<String, FieldDescriptor> e : nameToDescriptorMap.entrySet()) {
names = names + " " + e.getKey();
if (ignoreUnrecognizedFields) {
continue;
} else {
String names = "";
for (Map.Entry<String, FieldDescriptor> e : nameToDescriptorMap.entrySet()) {
names = names + " " + e.getKey();
}

throw new InvalidFhirException(
"Unknown field "
+ fieldName
+ " in input of expected type "
+ builder.getDescriptorForType().getFullName()
+ ", known fields: "
+ names);
}
throw new InvalidFhirException(
"Unknown field "
+ fieldName
+ " in input of expected type "
+ builder.getDescriptorForType().getFullName()
+ ", known fields: "
+ names);
}
}
if (AnnotationUtils.isReference(builder.getDescriptorForType())) {
Expand Down
35 changes: 31 additions & 4 deletions java/com/google/fhir/protogen/FhirPackage.java
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,31 @@ public static FhirPackage load(String archiveFilePath) throws IOException, Inval
*/
public static FhirPackage load(String archiveFilePath, PackageInfo packageInfo)
throws IOException, InvalidFhirException {
return load(archiveFilePath, packageInfo, /* ignoreUnrecognizedFields= */ false);
}

/**
* Loads a package ZIP into a FhirPackage.
*
* <p>Parses all defining resources that are part of the spec.
*
* <p>To read PackageInfo out of the ZIP itself, use the version of this that does not accept a
* PackageProto argument.
*
* @param archiveFilePath The absolute path to the archive file (ZIP or TAR) that is loaded.
* Expected to end with ".zip" "tar.gz" or ".tgz".
* @param packageInfo The package information to load. This package information is used,
* irrespective of whether the ZIP contains one.
* @param ignoreUnrecognizedFields If true, will not fail if unrecognized fields are encountered
* while parsing JSON. This allows for loading a FhirPackage of a new version of the Core FHIR
* spec using a FhirPackage binary build with the older version of the spec. For instance, you
* could parse an R5 StructureDefinition into an R4 StructureDefinition proto, since
* StructureDefinition was made normative in R4, since normative implies that fields can be
* added but not removed.
*/
public static FhirPackage load(
String archiveFilePath, PackageInfo packageInfo, boolean ignoreUnrecognizedFields)
throws IOException, InvalidFhirException {
PackageInfo extractedPackageInfo = null;
try (ArchiveInputStream archiveEntries = getZipOrTarInputStream(archiveFilePath)) {
List<JsonFile> jsonFiles = new ArrayList<>();
Expand Down Expand Up @@ -178,7 +203,9 @@ public static FhirPackage load(String archiveFilePath, PackageInfo packageInfo)
}

return makeFromJsonAndPackageInfo(
jsonFiles, packageInfo == null ? extractedPackageInfo : packageInfo);
jsonFiles,
packageInfo == null ? extractedPackageInfo : packageInfo,
ignoreUnrecognizedFields);
}
}

Expand Down Expand Up @@ -327,7 +354,7 @@ private static void buildResourceCollections(
}

private static FhirPackage makeFromJsonAndPackageInfo(
List<JsonFile> jsonFiles, PackageInfo packageInfo) {
List<JsonFile> jsonFiles, PackageInfo packageInfo, boolean ignoreUnrecognizedFields) {
if (packageInfo != null) {
if (packageInfo.getProtoPackage().isEmpty()) {
throw new IllegalArgumentException(
Expand All @@ -338,8 +365,8 @@ private static FhirPackage makeFromJsonAndPackageInfo(
"When PackageInfo is provided, must specify `fhir_version`.");
}
}

JsonFormat.Parser parser = JsonFormat.getParser();
JsonFormat.Parser parser =
JsonFormat.Parser.newBuilder().ignoreUnrecognizedFields(ignoreUnrecognizedFields).build();

ResourceCollections resourceCollections = new ResourceCollections();
resourceCollections.structureDefinitions =
Expand Down
38 changes: 38 additions & 0 deletions javatests/com/google/fhir/protogen/FhirPackageTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,44 @@ public void isCorePackage_withNonCorePackage() {
assertThat(FhirPackage.isCorePackage(packageInfo)).isFalse();
}

@Test
public void load_ignoringUnrecognizedFields() throws IOException, InvalidFhirException {
ImmutableList<PackageFile> files =
ImmutableList.of(
new PackageFile() {
{
fileName = "foo_package_info.prototxt";
fileContents =
"proto_package: \"google.foo\""
+ "\njava_proto_package: \"com.google.foo\""
+ "\nfhir_version: R4"
+ "\nlicense: APACHE"
+ "\nlicense_date: \"2019\""
+ "\nlocal_contained_resource: true"
+ "\nfile_splitting_behavior: SPLIT_RESOURCES";
}
},
new PackageFile() {
{
fileName = "myStructDef.json";
fileContents =
""
+ "{ "
+ " \"resourceType\": \"StructureDefinition\", "
+ " \"id\": \"Patient\", "
+ " \"url\": \"the-url.com\", "
+ " \"garbage\": true"
+ "}";
}
});
String zipFile = createFhirPackageInfoZip("foo_package", files);
FhirPackage fhirPackage = FhirPackage.load(zipFile, null, /* ignoreUnrecognizedFields= */ true);

assertThat(fhirPackage.structureDefinitions()).hasSize(1);
assertThat(fhirPackage.structureDefinitions().iterator().next().getId().getValue())
.isEqualTo("Patient");
}

@Test
public void isCorePackage_withLoadedCorePackage() throws IOException, InvalidFhirException {
ImmutableList<PackageFile> files =
Expand Down
4 changes: 4 additions & 0 deletions javatests/com/google/fhir/r4/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ java_test(
test_class = "com.google.fhir.r4.JsonFormatTest",
deps = [
"//java/com/google/fhir/common:exceptions",
"//java/com/google/fhir/common:json_format",
"//java/com/google/fhir/testing:json_format_test_base",
"//proto/google/fhir/proto/r4/core:datatypes_java_proto",
"//proto/google/fhir/proto/r4/core/resources:account_java_proto",
"//proto/google/fhir/proto/r4/core/resources:activity_definition_java_proto",
"//proto/google/fhir/proto/r4/core/resources:adverse_event_java_proto",
Expand Down Expand Up @@ -140,6 +142,7 @@ java_test(
"//proto/google/fhir/proto/r4/core/resources:slot_java_proto",
"//proto/google/fhir/proto/r4/core/resources:specimen_definition_java_proto",
"//proto/google/fhir/proto/r4/core/resources:specimen_java_proto",
"//proto/google/fhir/proto/r4/core/resources:structure_definition_java_proto",
"//proto/google/fhir/proto/r4/core/resources:structure_map_java_proto",
"//proto/google/fhir/proto/r4/core/resources:subscription_java_proto",
"//proto/google/fhir/proto/r4/core/resources:substance_java_proto",
Expand All @@ -154,6 +157,7 @@ java_test(
"//proto/google/fhir/proto/r4/core/resources:vision_prescription_java_proto",
"@com_google_protobuf//:protobuf_java",
"@maven//:com_google_guava_guava",
"@maven//:com_google_truth_extensions_truth_proto_extension",
"@maven//:junit_junit",
],
)
26 changes: 26 additions & 0 deletions javatests/com/google/fhir/r4/JsonFormatTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@

package com.google.fhir.r4;

import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static org.junit.Assert.assertThrows;

import com.google.common.io.Files;
import com.google.fhir.common.InvalidFhirException;
import com.google.fhir.common.JsonFormat;
import com.google.fhir.r4.core.Account;
import com.google.fhir.r4.core.ActivityDefinition;
import com.google.fhir.r4.core.AdverseEvent;
Expand Down Expand Up @@ -75,6 +79,7 @@
import com.google.fhir.r4.core.Group;
import com.google.fhir.r4.core.GuidanceResponse;
import com.google.fhir.r4.core.HealthcareService;
import com.google.fhir.r4.core.Id;
import com.google.fhir.r4.core.ImagingStudy;
import com.google.fhir.r4.core.Immunization;
import com.google.fhir.r4.core.ImmunizationEvaluation;
Expand Down Expand Up @@ -140,6 +145,7 @@
import com.google.fhir.r4.core.Slot;
import com.google.fhir.r4.core.Specimen;
import com.google.fhir.r4.core.SpecimenDefinition;
import com.google.fhir.r4.core.StructureDefinition;
import com.google.fhir.r4.core.StructureMap;
import com.google.fhir.r4.core.Subscription;
import com.google.fhir.r4.core.Substance;
Expand Down Expand Up @@ -202,6 +208,25 @@ private void testOrGenerate(String[] fileNames, Message.Builder type)
}
}

@Test
public void testIgnoringUnrecognizedFields_false_failsOnUnrecognizedField() throws Exception {
JsonFormat.Parser parser = JsonFormat.Parser.newBuilder().build();
StructureDefinition.Builder builder = StructureDefinition.newBuilder();
assertThrows(
InvalidFhirException.class,
() -> parser.merge("{ \"id\": \"123\", \"garbage\": true}", builder));
}

@Test
public void testIgnoringUnrecognizedFields_true_succeedsOnUnrecognizedField() throws Exception {
JsonFormat.Parser parser =
JsonFormat.Parser.newBuilder().ignoreUnrecognizedFields(true).build();
StructureDefinition.Builder builder = StructureDefinition.newBuilder();
parser.merge("{ \"id\": \"123\", \"garbage\": true}", builder);
assertThat(builder.build())
.isEqualTo(StructureDefinition.newBuilder().setId(Id.newBuilder().setValue("123")).build());
}

/** Test the analytics output format. */
@Test
public void convertForAnalytics() throws Exception {
Expand Down Expand Up @@ -348,6 +373,7 @@ public void testBundlePt2() throws Exception {
public void testBundlePt4() throws Exception {
testOrGenerate(new String[] {"Bundle-conceptmaps", "Bundle-dataelements"}, Bundle.newBuilder());
}

// TODO(b/244184211): These tests don't seem to ever finish for some reason - while other large
// files finish in at most a couple seconds, these time out even at 15 minutes.
// Seems to be a problem with the printer. Figure out why, and reneable these tests.
Expand Down

0 comments on commit 9bf0deb

Please sign in to comment.