Add PrintTo() methods to BinaryHttpMessage and related classes

These are called by gUnit to produce human readable error messages.

Alternatively we could define operator<<, but it might be best to reserve the
operator for a machine-meaningful serialized output.  DebugString() output is
not intended for machine consumption.

PiperOrigin-RevId: 474864994
diff --git a/quiche/binary_http/binary_http_message.cc b/quiche/binary_http/binary_http_message.cc
index 1a6a149..93d25bf 100644
--- a/quiche/binary_http/binary_http_message.cc
+++ b/quiche/binary_http/binary_http_message.cc
@@ -13,6 +13,7 @@
 #include "absl/status/statusor.h"
 #include "absl/strings/ascii.h"
 #include "absl/strings/str_cat.h"
+#include "absl/strings/str_join.h"
 #include "absl/strings/string_view.h"
 #include "quiche/common/quiche_data_reader.h"
 #include "quiche/common/quiche_data_writer.h"
@@ -388,4 +389,48 @@
       absl::StrCat("Unsupported framing type ", framing));
 }
 
+std::string BinaryHttpMessage::DebugString() const {
+  std::vector<std::string> headers;
+  for (const auto& field : GetHeaderFields()) {
+    headers.emplace_back(field.DebugString());
+  }
+  return absl::StrCat("BinaryHttpMessage{Headers{",
+                      absl::StrJoin(headers, ";;"), "}Body{", body(), "}}");
+}
+
+std::string BinaryHttpMessage::Field::DebugString() const {
+  return absl::StrCat(name, "=", value);
+}
+
+std::string BinaryHttpResponse::InformationalResponse::DebugString() const {
+  std::vector<std::string> fs;
+  for (const auto& field : fields()) {
+    fs.emplace_back(field.DebugString());
+  }
+  return absl::StrCat("InformationalResponse{", absl::StrJoin(fs, ";;"), "}");
+}
+
+std::string BinaryHttpResponse::DebugString() const {
+  std::vector<std::string> irs;
+  for (const auto& ir : informational_responses()) {
+    irs.emplace_back(ir.DebugString());
+  }
+  return absl::StrCat("BinaryHttpResponse(", status_code_, "){",
+                      BinaryHttpMessage::DebugString(),
+                      absl::StrJoin(irs, ";;"), "}");
+}
+
+std::string BinaryHttpRequest::DebugString() const {
+  return absl::StrCat("BinaryHttpRequest{", BinaryHttpMessage::DebugString(),
+                      "}");
+}
+
+void PrintTo(const BinaryHttpRequest& msg, std::ostream* os) {
+  *os << msg.DebugString();
+}
+
+void PrintTo(const BinaryHttpResponse& msg, std::ostream* os) {
+  *os << msg.DebugString();
+}
+
 }  // namespace quiche
diff --git a/quiche/binary_http/binary_http_message.h b/quiche/binary_http/binary_http_message.h
index e49b6a9..88906e7 100644
--- a/quiche/binary_http/binary_http_message.h
+++ b/quiche/binary_http/binary_http_message.h
@@ -29,6 +29,8 @@
     bool operator==(const BinaryHttpMessage::Field& rhs) const {
       return name == rhs.name && value == rhs.value;
     }
+
+    std::string DebugString() const;
   };
   virtual ~BinaryHttpMessage() = default;
 
@@ -50,6 +52,9 @@
   virtual absl::StatusOr<std::string> Serialize() const = 0;
   // TODO(bschneider): Add AddTrailerField for chunked messages
   // TODO(bschneider): Add SetBodyCallback() for chunked messages
+
+  virtual std::string DebugString() const;
+
  protected:
   class Fields {
    public:
@@ -117,6 +122,8 @@
   absl::StatusOr<std::string> Serialize() const override;
   const ControlData& control_data() const { return control_data_; }
 
+  virtual std::string DebugString() const override;
+
  private:
   absl::Status EncodeControlData(quiche::QuicheDataWriter& writer) const;
 
@@ -130,6 +137,8 @@
   const ControlData control_data_;
 };
 
+void PrintTo(const BinaryHttpRequest& msg, std::ostream* os);
+
 class QUICHE_EXPORT_PRIVATE BinaryHttpResponse : public BinaryHttpMessage {
  public:
   // https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html#name-response-control-data
@@ -163,6 +172,8 @@
 
     uint16_t status_code() const { return status_code_; }
 
+    std::string DebugString() const;
+
    private:
     // Give BinaryHttpResponse access to Encoding functionality.
     friend class BinaryHttpResponse;
@@ -196,6 +207,8 @@
     return informational_response_control_data_;
   }
 
+  virtual std::string DebugString() const override;
+
  private:
   // Returns Binary Http known length request formatted response.
   absl::StatusOr<std::string> EncodeAsKnownLength() const;
@@ -205,6 +218,8 @@
   std::vector<InformationalResponse> informational_response_control_data_;
   const uint16_t status_code_;
 };
+
+void PrintTo(const BinaryHttpResponse& msg, std::ostream* os);
 }  // namespace quiche
 
 #endif  // QUICHE_BINARY_HTTP_BINARY_HTTP_MESSAGE_H_
diff --git a/quiche/binary_http/binary_http_message_test.cc b/quiche/binary_http/binary_http_message_test.cc
index 7d22bf3..c747505 100644
--- a/quiche/binary_http/binary_http_message_test.cc
+++ b/quiche/binary_http/binary_http_message_test.cc
@@ -10,6 +10,7 @@
 
 using ::testing::ContainerEq;
 using ::testing::FieldsAre;
+using ::testing::StrEq;
 
 namespace quiche {
 namespace {
@@ -20,6 +21,12 @@
        static_cast<uint8_t>(word >> 8), static_cast<uint8_t>(word)});
 }
 
+template <class T>
+void TestPrintTo(const T& resp) {
+  std::ostringstream os;
+  PrintTo(resp, &os);
+  EXPECT_EQ(os.str(), resp.DebugString());
+}
 }  // namespace
 // Test examples from
 // https://www.ietf.org/archive/id/draft-ietf-httpbis-binary-message-06.html
@@ -65,6 +72,13 @@
   const auto result = request.Serialize();
   ASSERT_TRUE(result.ok());
   ASSERT_EQ(*result, expected);
+  EXPECT_THAT(
+      request.DebugString(),
+      StrEq(
+          "BinaryHttpRequest{BinaryHttpMessage{Headers{user-agent=curl/7.16.3 "
+          "libcurl/7.16.3 OpenSSL/0.9.7l "
+          "zlib/1.2.3;;host=www.example.com;;accept-language=en, mi}Body{}}}"));
+  TestPrintTo(request);
 }
 
 TEST(BinaryHttpRequest, DecodeGetNoBody) {
@@ -90,6 +104,13 @@
       {"accept-language", "en, mi"}};
   ASSERT_THAT(request.GetHeaderFields(), ContainerEq(expected_fields));
   ASSERT_EQ(request.body(), "");
+  EXPECT_THAT(
+      request.DebugString(),
+      StrEq(
+          "BinaryHttpRequest{BinaryHttpMessage{Headers{user-agent=curl/7.16.3 "
+          "libcurl/7.16.3 OpenSSL/0.9.7l "
+          "zlib/1.2.3;;host=www.example.com;;accept-language=en, mi}Body{}}}"));
+  TestPrintTo(request);
 }
 
 TEST(BinaryHttpRequest, EncodeGetWithAuthority) {
@@ -128,6 +149,11 @@
   const auto result = request.Serialize();
   ASSERT_TRUE(result.ok());
   ASSERT_EQ(*result, expected);
+  EXPECT_THAT(
+      request.DebugString(),
+      StrEq("BinaryHttpRequest{BinaryHttpMessage{Headers{user-agent=curl/"
+            "7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l "
+            "zlib/1.2.3;;accept-language=en, mi}Body{}}}"));
 }
 
 TEST(BinaryHttpRequest, DecodeGetWithAuthority) {
@@ -152,6 +178,11 @@
       {"accept-language", "en, mi"}};
   ASSERT_THAT(request.GetHeaderFields(), ContainerEq(expected_fields));
   ASSERT_EQ(request.body(), "");
+  EXPECT_THAT(
+      request.DebugString(),
+      StrEq("BinaryHttpRequest{BinaryHttpMessage{Headers{user-agent=curl/"
+            "7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l "
+            "zlib/1.2.3;;accept-language=en, mi}Body{}}}"));
 }
 
 TEST(BinaryHttpRequest, EncodePostBody) {
@@ -194,6 +225,11 @@
   const auto result = request.Serialize();
   ASSERT_TRUE(result.ok());
   ASSERT_EQ(*result, expected);
+  EXPECT_THAT(
+      request.DebugString(),
+      StrEq("BinaryHttpRequest{BinaryHttpMessage{Headers{user-agent=not/"
+            "telling;;host=www.example.com;;accept-language=en}Body{Some "
+            "body that I used to post.\r\n}}}"));
 }
 
 TEST(BinaryHttpRequest, DecodePostBody) {
@@ -219,6 +255,11 @@
       {"accept-language", "en"}};
   ASSERT_THAT(request.GetHeaderFields(), ContainerEq(expected_fields));
   ASSERT_EQ(request.body(), "Some body that I used to post.\r\n");
+  EXPECT_THAT(
+      request.DebugString(),
+      StrEq("BinaryHttpRequest{BinaryHttpMessage{Headers{user-agent=not/"
+            "telling;;host=www.example.com;;accept-language=en}Body{Some "
+            "body that I used to post.\r\n}}}"));
 }
 
 TEST(BinaryHttpResponse, EncodeNoBody) {
@@ -243,6 +284,9 @@
   const auto result = response.Serialize();
   ASSERT_TRUE(result.ok());
   ASSERT_EQ(*result, expected);
+  EXPECT_THAT(response.DebugString(),
+              StrEq("BinaryHttpResponse(404){BinaryHttpMessage{Headers{server="
+                    "Apache}Body{}}}"));
 }
 
 TEST(BinaryHttpResponse, DecodeNoBody) {
@@ -265,6 +309,9 @@
   ASSERT_THAT(response.GetHeaderFields(), ContainerEq(expected_fields));
   ASSERT_EQ(response.body(), "");
   ASSERT_TRUE(response.informational_responses().empty());
+  EXPECT_THAT(response.DebugString(),
+              StrEq("BinaryHttpResponse(404){BinaryHttpMessage{Headers{server="
+                    "Apache}Body{}}}"));
 }
 
 TEST(BinaryHttpResponse, EncodeBody) {
@@ -295,6 +342,9 @@
   const auto result = response.Serialize();
   ASSERT_TRUE(result.ok());
   ASSERT_EQ(*result, expected);
+  EXPECT_THAT(response.DebugString(),
+              StrEq("BinaryHttpResponse(200){BinaryHttpMessage{Headers{server="
+                    "Apache}Body{Hello, world!\r\n}}}"));
 }
 
 TEST(BinaryHttpResponse, DecodeBody) {
@@ -319,6 +369,9 @@
   ASSERT_THAT(response.GetHeaderFields(), ContainerEq(expected_fields));
   ASSERT_EQ(response.body(), "Hello, world!\r\n");
   ASSERT_TRUE(response.informational_responses().empty());
+  EXPECT_THAT(response.DebugString(),
+              StrEq("BinaryHttpResponse(200){BinaryHttpMessage{Headers{server="
+                    "Apache}Body{Hello, world!\r\n}}}"));
 }
 
 TEST(BHttpResponse, AddBadInformationalResponseCode) {
@@ -416,6 +469,18 @@
   const auto result = response.Serialize();
   ASSERT_TRUE(result.ok());
   ASSERT_EQ(*result, expected);
+  EXPECT_THAT(
+      response.DebugString(),
+      StrEq("BinaryHttpResponse(200){BinaryHttpMessage{Headers{date=Mon, "
+            "27 Jul 2009 12:28:53 GMT;;server=Apache;;last-modified=Wed, "
+            "22 Jul 2009 19:15:56 "
+            "GMT;;etag=\"34aa387-d-1568eb00\";;accept-ranges=bytes;;"
+            "content-length=51;;vary=Accept-Encoding;;content-type=text/"
+            "plain}Body{Hello World! My content includes a trailing "
+            "CRLF.\r\n}}InformationalResponse{running=\"sleep "
+            "15\"};;InformationalResponse{link=</style.css>; rel=preload; "
+            "as=style;;link=</script.js>; rel=preload; as=script}}"));
+  TestPrintTo(response);
 }
 
 TEST(BinaryHttpResponse, DecodeMultiInformationalWithBody) {
@@ -484,6 +549,18 @@
       {102, header102}, {103, header103}};
   ASSERT_THAT(response.informational_responses(),
               ContainerEq(expected_control));
+  EXPECT_THAT(
+      response.DebugString(),
+      StrEq("BinaryHttpResponse(200){BinaryHttpMessage{Headers{date=Mon, "
+            "27 Jul 2009 12:28:53 GMT;;server=Apache;;last-modified=Wed, "
+            "22 Jul 2009 19:15:56 "
+            "GMT;;etag=\"34aa387-d-1568eb00\";;accept-ranges=bytes;;"
+            "content-length=51;;vary=Accept-Encoding;;content-type=text/"
+            "plain}Body{Hello World! My content includes a trailing "
+            "CRLF.\r\n}}InformationalResponse{running=\"sleep "
+            "15\"};;InformationalResponse{link=</style.css>; rel=preload; "
+            "as=style;;link=</script.js>; rel=preload; as=script}}"));
+  TestPrintTo(response);
 }
 
 }  // namespace quiche