Skip to content

Commit

Permalink
Implement a mechanism to control the output format of Message.toStrin…
Browse files Browse the repository at this point in the history
…g within a Runnable instance.

PiperOrigin-RevId: 665539414
  • Loading branch information
protobuf-github-bot authored and copybara-github committed Aug 20, 2024
1 parent 8bb789e commit 737803e
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 9 deletions.
1 change: 1 addition & 0 deletions java/core/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,7 @@ LITE_TEST_EXCLUSIONS = [
"src/test/java/com/google/protobuf/Proto2ExtensionLookupSchemaTest.java",
"src/test/java/com/google/protobuf/Proto2SchemaTest.java",
"src/test/java/com/google/protobuf/Proto2UnknownEnumValueTest.java",
"src/test/java/com/google/protobuf/ProtobufToStringOutputTest.java",
"src/test/java/com/google/protobuf/RepeatedFieldBuilderTest.java",
"src/test/java/com/google/protobuf/ServiceTest.java",
"src/test/java/com/google/protobuf/SingleFieldBuilderTest.java",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,11 @@ public FieldDescriptor getOneofFieldDescriptor(OneofDescriptor oneof) {

@Override
public final String toString() {
return TextFormat.printer()
.printToString(this, TextFormat.Printer.FieldReporterLevel.ABSTRACT_TO_STRING);
TextFormat.Printer printer =
ProtobufToStringOutput.shouldOutputDebugFormat()
? TextFormat.debugFormatPrinter()
: TextFormat.printer();
return printer.printToString(this, TextFormat.Printer.FieldReporterLevel.ABSTRACT_TO_STRING);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.google.protobuf;

/**
* ProtobufToStringOutput controls the output format of {@link Message#toString()}. Specifically, for
* the Runnable object passed to `callWithDebugFormat` and `callWithTextFormat`, Message.toString()
* will always output the specified format unless ProtobufToStringOutput is used again to change the
* output format.
*/
public final class ProtobufToStringOutput {
private enum OutputMode {
DEBUG_FORMAT,
TEXT_FORMAT
}

private static final ThreadLocal<OutputMode> outputMode =
new ThreadLocal<OutputMode>() {
@Override
protected OutputMode initialValue() {
return OutputMode.TEXT_FORMAT;
}
};

private ProtobufToStringOutput() {}

@CanIgnoreReturnValue
private static OutputMode setOutputMode(OutputMode newMode) {
OutputMode oldMode = outputMode.get();
outputMode.set(newMode);
return oldMode;
}

private static void callWithSpecificFormat(Runnable impl, OutputMode mode) {
OutputMode oldMode = setOutputMode(mode);
try {
impl.run();
} finally {
OutputMode unused = setOutputMode(oldMode);
}
}

public static void callWithDebugFormat(Runnable impl) {
callWithSpecificFormat(impl, OutputMode.DEBUG_FORMAT);
}

public static void callWithTextFormat(Runnable impl) {
callWithSpecificFormat(impl, OutputMode.TEXT_FORMAT);
}

public static boolean shouldOutputDebugFormat() {
return outputMode.get() == OutputMode.DEBUG_FORMAT;
}
}
20 changes: 17 additions & 3 deletions java/core/src/main/java/com/google/protobuf/TextFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -111,21 +111,35 @@ private static void printUnknownFieldValue(

/** Printer instance which escapes non-ASCII characters. */
public static Printer printer() {
return Printer.DEFAULT;
return Printer.DEFAULT_TEXT_FORMAT;
}

/** Printer instance which escapes non-ASCII characters and prints in the debug format. */
public static Printer debugFormatPrinter() {
return Printer.DEFAULT_DEBUG_FORMAT;
}

/** Helper class for converting protobufs to text. */
public static final class Printer {

// Printer instance which escapes non-ASCII characters.
private static final Printer DEFAULT =
// Printer instance which escapes non-ASCII characters and prints in the text format.
private static final Printer DEFAULT_TEXT_FORMAT =
new Printer(
true,
TypeRegistry.getEmptyTypeRegistry(),
ExtensionRegistryLite.getEmptyRegistry(),
false,
false);

// Printer instance which escapes non-ASCII characters and prints in the debug format.
private static final Printer DEFAULT_DEBUG_FORMAT =
new Printer(
true,
TypeRegistry.getEmptyTypeRegistry(),
ExtensionRegistryLite.getEmptyRegistry(),
true,
false);

/**
* A list of the public APIs that output human-readable text from a message. A higher-level API
* must be larger than any lower-level APIs it calls under the hood, e.g
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public final class DebugFormatTest {
public class DebugFormatTest {

private static final String REDACTED_REGEX = "\\[REDACTED\\]";
private static final String UNSTABLE_PREFIX_SINGLE_LINE = getUnstablePrefix(true);
private static final String UNSTABLE_PREFIX_MULTILINE = getUnstablePrefix(false);
static final String REDACTED_REGEX = "\\[REDACTED\\]";
static final String UNSTABLE_PREFIX_SINGLE_LINE = getUnstablePrefix(true);
static final String UNSTABLE_PREFIX_MULTILINE = getUnstablePrefix(false);

private static String getUnstablePrefix(boolean singleLine) {
return "";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package com.google.protobuf;

import static com.google.common.truth.Truth.assertThat;

import protobuf_unittest.UnittestProto.RedactedFields;
import protobuf_unittest.UnittestProto.TestNestedMessageRedaction;
import java.util.ArrayList;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public final class ProtobufToStringOutputTest extends DebugFormatTest {
RedactedFields message;

@Before
public void setupTest() {
message =
RedactedFields.newBuilder()
.setOptionalUnredactedString("foo")
.setOptionalRedactedString("bar")
.setOptionalRedactedMessage(
TestNestedMessageRedaction.newBuilder().setOptionalUnredactedNestedString("foobar"))
.build();
}

@Test
public void toStringFormat_defaultFormat() {
assertThat(message.toString())
.matches(
"optional_redacted_string: \"bar\"\n"
+ "optional_unredacted_string: \"foo\"\n"
+ "optional_redacted_message \\{\n"
+ " optional_unredacted_nested_string: \"foobar\"\n"
+ "\\}\n");
}

@Test
public void toStringFormat_testDebugFormat() {
ProtobufToStringOutput.callWithDebugFormat(
() ->
assertThat(message.toString())
.matches(
String.format(
"%soptional_redacted_string: %s\n"
+ "optional_unredacted_string: \"foo\"\n"
+ "optional_redacted_message \\{\n"
+ " %s\n"
+ "\\}\n",
UNSTABLE_PREFIX_MULTILINE, REDACTED_REGEX, REDACTED_REGEX)));
}

@Test
public void toStringFormat_testTextFormat() {
ProtobufToStringOutput.callWithTextFormat(
() -> {
assertThat(message.toString())
.matches(
"optional_redacted_string: \"bar\"\n"
+ "optional_unredacted_string: \"foo\"\n"
+ "optional_redacted_message \\{\n"
+ " optional_unredacted_nested_string: \"foobar\"\n"
+ "\\}\n");
});
}

@Test
public void toStringFormat_testProtoWrapperWithDebugFormat() {
ProtobufToStringOutput.callWithDebugFormat(
() -> {
ArrayList<RedactedFields> list = new ArrayList<>();
list.add(message);
assertThat(list.toString())
.matches(
String.format(
"\\[%soptional_redacted_string: %s\n"
+ "optional_unredacted_string: \"foo\"\n"
+ "optional_redacted_message \\{\n"
+ " %s\n"
+ "\\}\n"
+ "\\]",
UNSTABLE_PREFIX_MULTILINE, REDACTED_REGEX, REDACTED_REGEX));
});
}

@Test
public void toStringFormat_testProtoWrapperWithTextFormat() {
ProtobufToStringOutput.callWithTextFormat(
() -> {
ArrayList<RedactedFields> list = new ArrayList<>();
list.add(message);
assertThat(list.toString())
.matches(
"\\[optional_redacted_string: \"bar\"\n"
+ "optional_unredacted_string: \"foo\"\n"
+ "optional_redacted_message \\{\n"
+ " optional_unredacted_nested_string: \"foobar\"\n"
+ "\\}\n"
+ "\\]");
});
}
}

0 comments on commit 737803e

Please sign in to comment.