From c8e01607aec4c09f510aa7e0d5df6a204e74189e Mon Sep 17 00:00:00 2001 From: Protobuf Team Bot Date: Thu, 30 Nov 2023 15:38:35 -0800 Subject: [PATCH] Support pretty printing proto2 Extensions inside of proto3 Anys in TextFormat.Printer. In Java, in order to include Extensions inside a proto message, the ExtensionRegistry for the extensions must be included during deserialization, otherwise they are treated as unknown fields. This change adds support for providing an ExtensionRegistry for TextFormat.Printer which is then used during Any deserialization. PiperOrigin-RevId: 586807633 --- .../java/com/google/protobuf/TextFormat.java | 30 +++++-- .../com/google/protobuf/TextFormatTest.java | 78 +++++++++++++++++++ 2 files changed, 103 insertions(+), 5 deletions(-) diff --git a/java/core/src/main/java/com/google/protobuf/TextFormat.java b/java/core/src/main/java/com/google/protobuf/TextFormat.java index a49919ef01..5fafdcc2ea 100644 --- a/java/core/src/main/java/com/google/protobuf/TextFormat.java +++ b/java/core/src/main/java/com/google/protobuf/TextFormat.java @@ -283,16 +283,23 @@ public final class TextFormat { public static final class Printer { // Printer instance which escapes non-ASCII characters. - private static final Printer DEFAULT = new Printer(true, TypeRegistry.getEmptyTypeRegistry()); + private static final Printer DEFAULT = + new Printer( + true, TypeRegistry.getEmptyTypeRegistry(), ExtensionRegistryLite.getEmptyRegistry()); /** Whether to escape non ASCII characters with backslash and octal. */ private final boolean escapeNonAscii; private final TypeRegistry typeRegistry; + private final ExtensionRegistryLite extensionRegistry; - private Printer(boolean escapeNonAscii, TypeRegistry typeRegistry) { + private Printer( + boolean escapeNonAscii, + TypeRegistry typeRegistry, + ExtensionRegistryLite extensionRegistry) { this.escapeNonAscii = escapeNonAscii; this.typeRegistry = typeRegistry; + this.extensionRegistry = extensionRegistry; } /** @@ -305,7 +312,7 @@ public final class TextFormat { * with the escape mode set to the given parameter. */ public Printer escapingNonAscii(boolean escapeNonAscii) { - return new Printer(escapeNonAscii, typeRegistry); + return new Printer(escapeNonAscii, typeRegistry, extensionRegistry); } /** @@ -318,7 +325,20 @@ public final class TextFormat { if (this.typeRegistry != TypeRegistry.getEmptyTypeRegistry()) { throw new IllegalArgumentException("Only one typeRegistry is allowed."); } - return new Printer(escapeNonAscii, typeRegistry); + return new Printer(escapeNonAscii, typeRegistry, extensionRegistry); + } + + /** + * Creates a new {@link Printer} using the given extensionRegistry. The new Printer clones all + * other configurations from the current {@link Printer}. + * + * @throws IllegalArgumentException if a registry is already set. + */ + public Printer usingExtensionRegistry(ExtensionRegistryLite extensionRegistry) { + if (this.extensionRegistry != ExtensionRegistryLite.getEmptyRegistry()) { + throw new IllegalArgumentException("Only one extensionRegistry is allowed."); + } + return new Printer(escapeNonAscii, typeRegistry, extensionRegistry); } /** @@ -377,7 +397,7 @@ public final class TextFormat { return false; } contentBuilder = DynamicMessage.getDefaultInstance(contentType).newBuilderForType(); - contentBuilder.mergeFrom((ByteString) value); + contentBuilder.mergeFrom((ByteString) value, extensionRegistry); } catch (InvalidProtocolBufferException e) { // The value of Any is malformed. We cannot print it out nicely, so fallback to printing out // the type_url and value as bytes. Note that we fail open here to be consistent with diff --git a/java/core/src/test/java/com/google/protobuf/TextFormatTest.java b/java/core/src/test/java/com/google/protobuf/TextFormatTest.java index d95ed4c8d9..695e3f58b3 100644 --- a/java/core/src/test/java/com/google/protobuf/TextFormatTest.java +++ b/java/core/src/test/java/com/google/protobuf/TextFormatTest.java @@ -11,6 +11,7 @@ import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static com.google.protobuf.TestUtil.TEST_REQUIRED_INITIALIZED; import static com.google.protobuf.TestUtil.TEST_REQUIRED_UNINITIALIZED; +import static protobuf_unittest.UnittestProto.optionalInt32Extension; import static org.junit.Assert.assertThrows; import com.google.protobuf.DescriptorProtos.DescriptorProto; @@ -623,6 +624,83 @@ public class TextFormatTest { assertThat(actual).isEqualTo(expected); } + @Test + public void testPrintAny_anyWithDynamicMessageContainingExtensionTreatedAsUnknown() + throws Exception { + Descriptor descriptor = + createDescriptorForAny( + FieldDescriptorProto.newBuilder() + .setName("type_url") + .setNumber(1) + .setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(FieldDescriptorProto.Type.TYPE_STRING) + .build(), + FieldDescriptorProto.newBuilder() + .setName("value") + .setNumber(2) + .setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(FieldDescriptorProto.Type.TYPE_BYTES) + .build()); + DynamicMessage testAny = + DynamicMessage.newBuilder(descriptor) + .setField( + descriptor.findFieldByNumber(1), + "type.googleapis.com/" + TestAllExtensions.getDescriptor().getFullName()) + .setField( + descriptor.findFieldByNumber(2), + TestAllExtensions.newBuilder() + .setExtension(optionalInt32Extension, 12345) + .build() + .toByteString()) + .build(); + String actual = + TextFormat.printer() + .usingTypeRegistry(TypeRegistry.newBuilder().add(TestAllTypes.getDescriptor()).build()) + .printToString(testAny); + String expected = "[type.googleapis.com/protobuf_unittest.TestAllExtensions] {\n 1: 12345\n}\n"; + assertThat(actual).isEqualTo(expected); + } + + @Test + public void testPrintAny_anyWithDynamicMessageContainingExtensionWithRegistry() throws Exception { + Descriptor descriptor = + createDescriptorForAny( + FieldDescriptorProto.newBuilder() + .setName("type_url") + .setNumber(1) + .setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(FieldDescriptorProto.Type.TYPE_STRING) + .build(), + FieldDescriptorProto.newBuilder() + .setName("value") + .setNumber(2) + .setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL) + .setType(FieldDescriptorProto.Type.TYPE_BYTES) + .build()); + DynamicMessage testAny = + DynamicMessage.newBuilder(descriptor) + .setField( + descriptor.findFieldByNumber(1), + "type.googleapis.com/" + TestAllExtensions.getDescriptor().getFullName()) + .setField( + descriptor.findFieldByNumber(2), + TestAllExtensions.newBuilder() + .setExtension(optionalInt32Extension, 12345) + .build() + .toByteString()) + .build(); + String actual = + TextFormat.printer() + .usingTypeRegistry(TypeRegistry.newBuilder().add(TestAllTypes.getDescriptor()).build()) + .usingExtensionRegistry(TestUtil.getFullExtensionRegistry()) + .printToString(testAny); + String expected = + "[type.googleapis.com/protobuf_unittest.TestAllExtensions] {\n" + + " [protobuf_unittest.optional_int32_extension]: 12345\n" + + "}\n"; + assertThat(actual).isEqualTo(expected); + } + @Test public void testPrintAny_anyFromWithNoValueField() throws Exception { Descriptor descriptor =