encoding: add Format helper function and method

The Format function and MarshalOptions.Format method are helper
functions for directly obtaining the formatted string for a message
without having to deal with errors or convert a []byte to string.
It is only intended for human consumption (e.g., debugging or logging).

We also add a MarshalOptions.Multiline option to specify that the output
should use some default indentation in a multiline output.

This assists in the v1 to v2 migration where:
	protoV1.CompactTextString(m) => prototext.MarshalOptions{}.Format(m)
	protoV1.MarshalTextString(m) => prototext.Format(m)

At Google, there are approximately 10x more usages of MarshalTextString than
CompactTextString, so it makes sense that the top-level Format function
does multiline expansion by default.

Fixes #850

Change-Id: I149c9e190a6d99b985d3884df675499a3313e9b3
Reviewed-on: https://go-review.googlesource.com/c/protobuf/+/213460
Reviewed-by: Damien Neil <dneil@google.com>
Reviewed-by: Herbie Ong <herbie@google.com>
diff --git a/encoding/protojson/encode.go b/encoding/protojson/encode.go
index 9b2592d..02723c0 100644
--- a/encoding/protojson/encode.go
+++ b/encoding/protojson/encode.go
@@ -19,6 +19,16 @@
 	"google.golang.org/protobuf/reflect/protoregistry"
 )
 
+const defaultIndent = "  "
+
+// Format formats the message as a multiline string.
+// This function is only intended for human consumption and ignores errors.
+// Do not depend on the output being stable. It may change over time across
+// different versions of the program.
+func Format(m proto.Message) string {
+	return MarshalOptions{Multiline: true}.Format(m)
+}
+
 // Marshal writes the given proto.Message in JSON format using default options.
 // Do not depend on the output being stable. It may change over time across
 // different versions of the program.
@@ -59,9 +69,15 @@
 	//  ╚═══════╧════════════════════════════╝
 	EmitUnpopulated bool
 
-	// If Indent is a non-empty string, it causes entries for an Array or Object
-	// to be preceded by the indent and trailed by a newline. Indent can only be
-	// composed of space or tab characters.
+	// Multiline specifies whether the marshaler should format the output in
+	// indented-form with every textual element on a new line.
+	// If Indent is an empty string, then an arbitrary indent is chosen.
+	Multiline bool
+
+	// Indent specifies the set of indentation characters to use in a multiline
+	// formatted output such that every entry is preceded by Indent and
+	// terminated by a newline. If non-empty, then Multiline is treated as true.
+	// Indent can only be composed of space or tab characters.
 	Indent string
 
 	// Resolver is used for looking up types when expanding google.protobuf.Any
@@ -72,18 +88,35 @@
 	}
 }
 
+// Format formats the message as a string.
+// This method is only intended for human consumption and ignores errors.
+// Do not depend on the output being stable. It may change over time across
+// different versions of the program.
+func (o MarshalOptions) Format(m proto.Message) string {
+	if m == nil || !m.ProtoReflect().IsValid() {
+		return "<nil>" // invalid syntax, but okay since this is for debugging
+	}
+	o.AllowPartial = true
+	b, _ := o.Marshal(m)
+	return string(b)
+}
+
 // Marshal marshals the given proto.Message in the JSON format using options in
 // MarshalOptions. Do not depend on the output being stable. It may change over
 // time across different versions of the program.
 func (o MarshalOptions) Marshal(m proto.Message) ([]byte, error) {
+	if o.Multiline && o.Indent == "" {
+		o.Indent = defaultIndent
+	}
+	if o.Resolver == nil {
+		o.Resolver = protoregistry.GlobalTypes
+	}
+
 	var err error
 	o.encoder, err = json.NewEncoder(o.Indent)
 	if err != nil {
 		return nil, err
 	}
-	if o.Resolver == nil {
-		o.Resolver = protoregistry.GlobalTypes
-	}
 
 	err = o.marshalMessage(m.ProtoReflect())
 	if err != nil {