Skip to content

Commit c8e0160

Browse files
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
1 parent de57b67 commit c8e0160

2 files changed

Lines changed: 103 additions & 5 deletions

File tree

java/core/src/main/java/com/google/protobuf/TextFormat.java

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -283,16 +283,23 @@ public static Printer printer() {
283283
public static final class Printer {
284284

285285
// Printer instance which escapes non-ASCII characters.
286-
private static final Printer DEFAULT = new Printer(true, TypeRegistry.getEmptyTypeRegistry());
286+
private static final Printer DEFAULT =
287+
new Printer(
288+
true, TypeRegistry.getEmptyTypeRegistry(), ExtensionRegistryLite.getEmptyRegistry());
287289

288290
/** Whether to escape non ASCII characters with backslash and octal. */
289291
private final boolean escapeNonAscii;
290292

291293
private final TypeRegistry typeRegistry;
294+
private final ExtensionRegistryLite extensionRegistry;
292295

293-
private Printer(boolean escapeNonAscii, TypeRegistry typeRegistry) {
296+
private Printer(
297+
boolean escapeNonAscii,
298+
TypeRegistry typeRegistry,
299+
ExtensionRegistryLite extensionRegistry) {
294300
this.escapeNonAscii = escapeNonAscii;
295301
this.typeRegistry = typeRegistry;
302+
this.extensionRegistry = extensionRegistry;
296303
}
297304

298305
/**
@@ -305,7 +312,7 @@ private Printer(boolean escapeNonAscii, TypeRegistry typeRegistry) {
305312
* with the escape mode set to the given parameter.
306313
*/
307314
public Printer escapingNonAscii(boolean escapeNonAscii) {
308-
return new Printer(escapeNonAscii, typeRegistry);
315+
return new Printer(escapeNonAscii, typeRegistry, extensionRegistry);
309316
}
310317

311318
/**
@@ -318,7 +325,20 @@ public Printer usingTypeRegistry(TypeRegistry typeRegistry) {
318325
if (this.typeRegistry != TypeRegistry.getEmptyTypeRegistry()) {
319326
throw new IllegalArgumentException("Only one typeRegistry is allowed.");
320327
}
321-
return new Printer(escapeNonAscii, typeRegistry);
328+
return new Printer(escapeNonAscii, typeRegistry, extensionRegistry);
329+
}
330+
331+
/**
332+
* Creates a new {@link Printer} using the given extensionRegistry. The new Printer clones all
333+
* other configurations from the current {@link Printer}.
334+
*
335+
* @throws IllegalArgumentException if a registry is already set.
336+
*/
337+
public Printer usingExtensionRegistry(ExtensionRegistryLite extensionRegistry) {
338+
if (this.extensionRegistry != ExtensionRegistryLite.getEmptyRegistry()) {
339+
throw new IllegalArgumentException("Only one extensionRegistry is allowed.");
340+
}
341+
return new Printer(escapeNonAscii, typeRegistry, extensionRegistry);
322342
}
323343

324344
/**
@@ -377,7 +397,7 @@ private boolean printAny(final MessageOrBuilder message, final TextGenerator gen
377397
return false;
378398
}
379399
contentBuilder = DynamicMessage.getDefaultInstance(contentType).newBuilderForType();
380-
contentBuilder.mergeFrom((ByteString) value);
400+
contentBuilder.mergeFrom((ByteString) value, extensionRegistry);
381401
} catch (InvalidProtocolBufferException e) {
382402
// The value of Any is malformed. We cannot print it out nicely, so fallback to printing out
383403
// the type_url and value as bytes. Note that we fail open here to be consistent with

java/core/src/test/java/com/google/protobuf/TextFormatTest.java

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import static com.google.common.truth.Truth.assertWithMessage;
1212
import static com.google.protobuf.TestUtil.TEST_REQUIRED_INITIALIZED;
1313
import static com.google.protobuf.TestUtil.TEST_REQUIRED_UNINITIALIZED;
14+
import static protobuf_unittest.UnittestProto.optionalInt32Extension;
1415
import static org.junit.Assert.assertThrows;
1516

1617
import com.google.protobuf.DescriptorProtos.DescriptorProto;
@@ -623,6 +624,83 @@ public void testPrintAny_anyWithDynamicMessage() throws Exception {
623624
assertThat(actual).isEqualTo(expected);
624625
}
625626

627+
@Test
628+
public void testPrintAny_anyWithDynamicMessageContainingExtensionTreatedAsUnknown()
629+
throws Exception {
630+
Descriptor descriptor =
631+
createDescriptorForAny(
632+
FieldDescriptorProto.newBuilder()
633+
.setName("type_url")
634+
.setNumber(1)
635+
.setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL)
636+
.setType(FieldDescriptorProto.Type.TYPE_STRING)
637+
.build(),
638+
FieldDescriptorProto.newBuilder()
639+
.setName("value")
640+
.setNumber(2)
641+
.setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL)
642+
.setType(FieldDescriptorProto.Type.TYPE_BYTES)
643+
.build());
644+
DynamicMessage testAny =
645+
DynamicMessage.newBuilder(descriptor)
646+
.setField(
647+
descriptor.findFieldByNumber(1),
648+
"type.googleapis.com/" + TestAllExtensions.getDescriptor().getFullName())
649+
.setField(
650+
descriptor.findFieldByNumber(2),
651+
TestAllExtensions.newBuilder()
652+
.setExtension(optionalInt32Extension, 12345)
653+
.build()
654+
.toByteString())
655+
.build();
656+
String actual =
657+
TextFormat.printer()
658+
.usingTypeRegistry(TypeRegistry.newBuilder().add(TestAllTypes.getDescriptor()).build())
659+
.printToString(testAny);
660+
String expected = "[type.googleapis.com/protobuf_unittest.TestAllExtensions] {\n 1: 12345\n}\n";
661+
assertThat(actual).isEqualTo(expected);
662+
}
663+
664+
@Test
665+
public void testPrintAny_anyWithDynamicMessageContainingExtensionWithRegistry() throws Exception {
666+
Descriptor descriptor =
667+
createDescriptorForAny(
668+
FieldDescriptorProto.newBuilder()
669+
.setName("type_url")
670+
.setNumber(1)
671+
.setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL)
672+
.setType(FieldDescriptorProto.Type.TYPE_STRING)
673+
.build(),
674+
FieldDescriptorProto.newBuilder()
675+
.setName("value")
676+
.setNumber(2)
677+
.setLabel(FieldDescriptorProto.Label.LABEL_OPTIONAL)
678+
.setType(FieldDescriptorProto.Type.TYPE_BYTES)
679+
.build());
680+
DynamicMessage testAny =
681+
DynamicMessage.newBuilder(descriptor)
682+
.setField(
683+
descriptor.findFieldByNumber(1),
684+
"type.googleapis.com/" + TestAllExtensions.getDescriptor().getFullName())
685+
.setField(
686+
descriptor.findFieldByNumber(2),
687+
TestAllExtensions.newBuilder()
688+
.setExtension(optionalInt32Extension, 12345)
689+
.build()
690+
.toByteString())
691+
.build();
692+
String actual =
693+
TextFormat.printer()
694+
.usingTypeRegistry(TypeRegistry.newBuilder().add(TestAllTypes.getDescriptor()).build())
695+
.usingExtensionRegistry(TestUtil.getFullExtensionRegistry())
696+
.printToString(testAny);
697+
String expected =
698+
"[type.googleapis.com/protobuf_unittest.TestAllExtensions] {\n"
699+
+ " [protobuf_unittest.optional_int32_extension]: 12345\n"
700+
+ "}\n";
701+
assertThat(actual).isEqualTo(expected);
702+
}
703+
626704
@Test
627705
public void testPrintAny_anyFromWithNoValueField() throws Exception {
628706
Descriptor descriptor =

0 commit comments

Comments
 (0)