diff --git a/bazel/protogen.bzl b/bazel/protogen.bzl index 23f73fb65b..4ca05ad5da 100644 --- a/bazel/protogen.bzl +++ b/bazel/protogen.bzl @@ -75,6 +75,7 @@ def gen_fhir_protos( package_deps = [], additional_proto_imports = [], disable_test = False, + edition = "", golden_java_proto_rules = []): """Generates a proto file from a fhir_package @@ -119,6 +120,9 @@ def gen_fhir_protos( flags.append("--directory_in_source " + src_dir) + if edition != "": + flags.append("--edition " + edition) + all_fhir_pkgs = package_deps + [ package, R4_PACKAGE_DEP, @@ -178,6 +182,7 @@ def gen_fhir_definitions_and_protos( additional_proto_imports = [], disable_test = False, golden_java_proto_rules = [], + edition = "", package_json = None): """Generates structure definitions and protos based on Extensions and Profiles protos. @@ -285,6 +290,7 @@ def gen_fhir_definitions_and_protos( additional_proto_imports = additional_proto_imports, disable_test = disable_test, golden_java_proto_rules = golden_java_proto_rules, + edition = edition, ) def _get_zip_for_pkg(pkg): diff --git a/java/com/google/fhir/protogen/ProtoFilePrinter.java b/java/com/google/fhir/protogen/ProtoFilePrinter.java index 9bc285220b..7b07daeae4 100644 --- a/java/com/google/fhir/protogen/ProtoFilePrinter.java +++ b/java/com/google/fhir/protogen/ProtoFilePrinter.java @@ -29,6 +29,7 @@ import com.google.fhir.proto.ProtoGeneratorAnnotations; import com.google.fhir.proto.ProtogenConfig; import com.google.protobuf.DescriptorProtos.DescriptorProto; +import com.google.protobuf.DescriptorProtos.Edition; import com.google.protobuf.DescriptorProtos.EnumDescriptorProto; import com.google.protobuf.DescriptorProtos.EnumOptions; import com.google.protobuf.DescriptorProtos.EnumValueDescriptorProto; @@ -50,12 +51,6 @@ /** A utility to turn protocol message descriptors into .proto files. */ public class ProtoFilePrinter { - /** Enum represting the proto Syntax */ - public static enum Syntax { - PROTO2, - PROTO3 - }; - private static final String APACHE_LICENSE = "// Copyright %1$s Google Inc.\n" + "//\n" @@ -77,8 +72,6 @@ public static enum Syntax { private final PackageInfo.License license; private final String licenseDate; - private final Syntax syntax; - // All Message options in this list will appear in this order, before any options not in this // list. The remainder will be alphabetized. // This list exists largely to maintain backwards compatibility in ordering. @@ -105,27 +98,19 @@ public static enum Syntax { /** Creates a ProtoFilePrinter with default parameters. */ public ProtoFilePrinter(PackageInfo packageInfo) { - this(packageInfo, Syntax.PROTO3); - } - - /** Creates a ProtoFilePrinter with default parameters. */ - public ProtoFilePrinter(PackageInfo packageInfo, Syntax syntax) { license = packageInfo.getLicense(); licenseDate = packageInfo.getLicenseDate(); - this.syntax = syntax; } /** Creates a ProtoFilePrinter with default parameters. */ public ProtoFilePrinter(ProtogenConfig protogenConfig) { license = PackageInfo.License.APACHE; licenseDate = protogenConfig.getLicenseDate(); - this.syntax = Syntax.PROTO3; } public ProtoFilePrinter(String licenseDate) { license = PackageInfo.License.APACHE; this.licenseDate = licenseDate; - this.syntax = Syntax.PROTO3; } /** Generate a .proto file corresponding to the provided FileDescriptorProto. */ @@ -151,7 +136,15 @@ public String print(FileDescriptorProto fileDescriptor) { private String printHeader(FileDescriptorProto fileDescriptor) { StringBuilder header = new StringBuilder(); if (fileDescriptor.hasSyntax()) { - header.append("syntax = \"").append(fileDescriptor.getSyntax()).append("\";\n\n"); + if (fileDescriptor.getSyntax().equals("editions")) { + if (fileDescriptor.getEdition().equals(Edition.EDITION_2023)) { + header.append("edition = \"2023\";\n\n"); + } else { + header.append("edition = \"unknown\";\n\n"); + } + } else { + header.append("syntax = \"").append(fileDescriptor.getSyntax()).append("\";\n\n"); + } } if (fileDescriptor.hasPackage()) { header.append("package ").append(fileDescriptor.getPackage()).append(";\n"); @@ -170,6 +163,9 @@ private String printImports(FileDescriptorProto fileDescriptor) { private String printOptions(FileDescriptorProto fileDescriptor, String packageName) { StringBuilder options = new StringBuilder(); FileOptions fileOptions = fileDescriptor.getOptions(); + if (fileDescriptor.getSyntax().equals("editions")) { + options.append("option features.field_presence = IMPLICIT;\n"); + } if (fileOptions.hasJavaMultipleFiles()) { options .append("option java_multiple_files = ") @@ -279,7 +275,7 @@ private String printMessage(DescriptorProto descriptor, String typePrefix, Strin descriptor, field, typePrefix, packageName, printedNestedTypeDefinitions)); message.append( printField( - fieldBuilder.build(), fullName, fieldIndent, packageName, /* inOneof= */ false)); + fieldBuilder.build(), fullName, fieldIndent, packageName)); if (i != descriptor.getFieldCount() - 1) { message.append("\n"); } @@ -327,7 +323,7 @@ private String printMessage(DescriptorProto descriptor, String typePrefix, Strin } message.append( printField( - fieldBuilder.build(), fullName, oneofIndent, packageName, /* inOneof= */ true)); + fieldBuilder.build(), fullName, oneofIndent, packageName)); // If this oneof field had a description, and is not the last field, add a newline after. if (field.getOptions().hasExtension(ProtoGeneratorAnnotations.fieldDescription) && i != descriptor.getFieldCount() - 1) { @@ -441,20 +437,14 @@ private String printField( FieldDescriptorProto field, String containingType, String indent, - String packageName, - boolean inOneof) { + String packageName) { StringBuilder message = new StringBuilder(); message.append(indent); - // Add the "repeated" or "optional" keywords, if necessary. + // Add the "repeated" keyword, if necessary. if (field.getLabel() == FieldDescriptorProto.Label.LABEL_REPEATED) { message.append("repeated "); } - if (!inOneof - && field.getLabel() == FieldDescriptorProto.Label.LABEL_OPTIONAL - && syntax == Syntax.PROTO2) { - message.append("optional "); - } // Add the type of the field. if ((field.getType() == FieldDescriptorProto.Type.TYPE_MESSAGE diff --git a/java/com/google/fhir/protogen/ProtoGenerator.java b/java/com/google/fhir/protogen/ProtoGenerator.java index 4138978e1c..1e4c02ca27 100644 --- a/java/com/google/fhir/protogen/ProtoGenerator.java +++ b/java/com/google/fhir/protogen/ProtoGenerator.java @@ -59,6 +59,7 @@ import com.google.fhir.r4.core.StructureDefinitionKindCode; import com.google.fhir.r4.core.TypeDerivationRuleCode; import com.google.protobuf.DescriptorProtos.DescriptorProto; +import com.google.protobuf.DescriptorProtos.Edition; import com.google.protobuf.DescriptorProtos.EnumDescriptorProto; import com.google.protobuf.DescriptorProtos.EnumValueDescriptorProto; import com.google.protobuf.DescriptorProtos.FieldDescriptorProto; @@ -214,6 +215,8 @@ public class ProtoGenerator { private final ImmutableMap> coreTypeDefinitionsByFile; + private final String edition; + private static Set getTypesDefinedInFile(FileDescriptor file) { return file.getMessageTypes().stream() .flatMap(desc -> getTypesDefinedInType(desc).stream()) @@ -261,19 +264,21 @@ private static class StructureDefinitionData { public ProtoGenerator( PackageInfo packageInfo, String codesProtoImport, Set fhirPackages) throws InvalidFhirException { - this(packageInfo, codesProtoImport, fhirPackages, null); + this(packageInfo, codesProtoImport, fhirPackages, null, ""); } public ProtoGenerator( PackageInfo packageInfo, String codesProtoImport, Set fhirPackages, - ValueSetGenerator valueSetGenerator) + ValueSetGenerator valueSetGenerator, + String edition) throws InvalidFhirException { this.packageInfo = packageInfo; this.codesProtoImport = codesProtoImport; this.fhirVersion = FhirVersion.fromAnnotation(packageInfo.getFhirVersion()); this.valueSetGenerator = valueSetGenerator; + this.edition = edition; ImmutableMap.Builder> coreTypeBuilder = new ImmutableMap.Builder<>(); for (Map.Entry entry : fhirVersion.coreTypeMap.entrySet()) { @@ -1834,7 +1839,13 @@ public FileDescriptorProto generateFileDescriptor(List defs public FileDescriptorProto generateFileDescriptor( List defs, List additionalImports) throws InvalidFhirException { FileDescriptorProto.Builder builder = FileDescriptorProto.newBuilder(); - builder.setPackage(packageInfo.getProtoPackage()).setSyntax("proto3"); + builder.setPackage(packageInfo.getProtoPackage()); + if (this.edition.equals("2023")) { + builder.setSyntax("editions"); + builder.setEdition(Edition.EDITION_2023); + } else { + builder.setSyntax("proto3"); + } FileOptions.Builder options = FileOptions.newBuilder(); if (!packageInfo.getJavaProtoPackage().isEmpty()) { options.setJavaPackage(packageInfo.getJavaProtoPackage()).setJavaMultipleFiles(true); diff --git a/java/com/google/fhir/protogen/ProtoGeneratorMain.java b/java/com/google/fhir/protogen/ProtoGeneratorMain.java index 886023d15f..765537b22a 100644 --- a/java/com/google/fhir/protogen/ProtoGeneratorMain.java +++ b/java/com/google/fhir/protogen/ProtoGeneratorMain.java @@ -109,6 +109,11 @@ private static class Args { + " will output ${output_name}_extensions.proto.") private String outputName = "output"; + @Parameter( + names = {"--edition"}, + description = "proto edition") + private String edition = ""; + @Parameter( names = {"--input_package"}, description = "Input FHIR package", @@ -160,13 +165,15 @@ void run() throws IOException, InvalidFhirException { // Generate the proto file. System.out.println("Generating proto descriptors..."); - ValueSetGenerator valueSetGenerator = new ValueSetGenerator(packageInfo, fhirPackages); + ValueSetGenerator valueSetGenerator = + new ValueSetGenerator(packageInfo, fhirPackages, args.edition); ProtoGenerator generator = new ProtoGenerator( packageInfo, args.directoryInSource + "/" + args.outputName + "_codes.proto", fhirPackages, - valueSetGenerator); + valueSetGenerator, + args.edition); ProtoFilePrinter printer = new ProtoFilePrinter(packageInfo); try (ZipOutputStream zipOutputStream = diff --git a/java/com/google/fhir/protogen/ValueSetGenerator.java b/java/com/google/fhir/protogen/ValueSetGenerator.java index 78dd267dd3..c159395ba2 100644 --- a/java/com/google/fhir/protogen/ValueSetGenerator.java +++ b/java/com/google/fhir/protogen/ValueSetGenerator.java @@ -40,6 +40,7 @@ import com.google.fhir.r4.core.ValueSet; import com.google.fhir.r4.core.ValueSet.Compose.ConceptSet.Filter; import com.google.protobuf.DescriptorProtos.DescriptorProto; +import com.google.protobuf.DescriptorProtos.Edition; import com.google.protobuf.DescriptorProtos.EnumDescriptorProto; import com.google.protobuf.DescriptorProtos.EnumValueDescriptorProto; import com.google.protobuf.DescriptorProtos.FieldDescriptorProto; @@ -67,9 +68,11 @@ public class ValueSetGenerator { private final Map codeSystemsByUrl; private final Map valueSetsByUrl; private final Map protoTypesByUrl; + private final String edition; - public ValueSetGenerator(PackageInfo packageInfo, Set fhirPackages) { + public ValueSetGenerator(PackageInfo packageInfo, Set fhirPackages, String edition) { this.packageInfo = packageInfo; + this.edition = edition; this.fhirVersion = FhirVersion.fromAnnotation(packageInfo.getFhirVersion()); this.codeSystemsByUrl = @@ -161,7 +164,13 @@ public FileDescriptorProto generateCodeSystemFile(FhirPackage fhirPackage) { private FileDescriptorProto generateCodeSystemFile(Collection codeSystemsToGenerate) { FileDescriptorProto.Builder builder = FileDescriptorProto.newBuilder(); - builder.setPackage(packageInfo.getProtoPackage()).setSyntax("proto3"); + builder.setPackage(packageInfo.getProtoPackage()); + if (this.edition.equals("2023")) { + builder.setSyntax("editions"); + builder.setEdition(Edition.EDITION_2023); + } else { + builder.setSyntax("proto3"); + } builder.addDependency(new File(GeneratorUtils.ANNOTATION_PATH, "annotations.proto").toString()); FileOptions.Builder options = FileOptions.newBuilder(); if (!packageInfo.getJavaProtoPackage().isEmpty()) { diff --git a/javatests/com/google/fhir/protogen/GeneratedProtoTest.java b/javatests/com/google/fhir/protogen/GeneratedProtoTest.java index fbd7685a11..b060e890a6 100644 --- a/javatests/com/google/fhir/protogen/GeneratedProtoTest.java +++ b/javatests/com/google/fhir/protogen/GeneratedProtoTest.java @@ -66,6 +66,9 @@ public void testGeneratedProto() throws Exception { } for (String filename : generatedContentsByFilename.keySet()) { + if (filename.startsWith(".")) { + continue; + } assertThat(goldenContentsByFilename).containsKey(filename); assertThat(cleanProtoFile(generatedContentsByFilename.get(filename))) .isEqualTo(cleanProtoFile(goldenContentsByFilename.get(filename))); diff --git a/javatests/com/google/fhir/protogen/ProtoGeneratorTest.java b/javatests/com/google/fhir/protogen/ProtoGeneratorTest.java index 91024a1f36..ef52676304 100644 --- a/javatests/com/google/fhir/protogen/ProtoGeneratorTest.java +++ b/javatests/com/google/fhir/protogen/ProtoGeneratorTest.java @@ -79,7 +79,8 @@ private static ProtoGenerator makeProtoGenerator( packageInfo, codesProtoImport, ImmutableSet.copyOf(packages), - new ValueSetGenerator(packageInfo, packages)); + new ValueSetGenerator(packageInfo, packages, "2023"), + "2023"); } public ProtoGeneratorTest() throws Exception {