Thrift: Debug Protocol
Summary:
Added TDebugProtocol, a write-only Thrift protocol for C++
that produces human-readable representations of thrift structs.
Trac Bug: #
Blame Rev:
Reviewed By: mcslee
Test Plan:
Recompiled Thrift.
./test/TestDebugProto.* see compile instructions at the top.
Ran that, and it looked good.
Revert Plan:
grep TDebugProtocol <world>
grep ThriftDebugString <world>
Revert or comment out whatever you find so that they still compile.
Then svn revert.
Notes:
EImportant:
git-svn-id: https://svn.apache.org/repos/asf/incubator/thrift/trunk@665166 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/lib/cpp/src/protocol/TDebugProtocol.cpp b/lib/cpp/src/protocol/TDebugProtocol.cpp
new file mode 100644
index 0000000..5d28b78
--- /dev/null
+++ b/lib/cpp/src/protocol/TDebugProtocol.cpp
@@ -0,0 +1,315 @@
+// Copyright (c) 2006- Facebook
+// Distributed under the Thrift Software License
+//
+// See accompanying file LICENSE or visit the Thrift site at:
+// http://developers.facebook.com/thrift/
+
+#include "TDebugProtocol.h"
+
+#include <cassert>
+#include <cctype>
+#include <cstdio>
+#include <stdexcept>
+#include <boost/static_assert.hpp>
+#include <boost/lexical_cast.hpp>
+
+using std::string;
+
+
+static string byte_to_hex(const uint8_t byte) {
+ char buf[3];
+ int ret = std::sprintf(buf, "%02x", (int)byte);
+ assert(ret == 2);
+ assert(buf[2] == '\0');
+ return buf;
+}
+
+
+namespace facebook { namespace thrift { namespace protocol {
+
+string TDebugProtocol::fieldTypeName(TType type) {
+ switch (type) {
+ case T_STOP : return "stop" ;
+ case T_VOID : return "void" ;
+ case T_BOOL : return "bool" ;
+ case T_BYTE : return "byte" ;
+ case T_I16 : return "i16" ;
+ case T_I32 : return "i32" ;
+ case T_U64 : return "u64" ;
+ case T_I64 : return "i64" ;
+ case T_DOUBLE : return "double" ;
+ case T_STRING : return "string" ;
+ case T_STRUCT : return "struct" ;
+ case T_MAP : return "map" ;
+ case T_SET : return "set" ;
+ case T_LIST : return "list" ;
+ case T_UTF8 : return "utf8" ;
+ case T_UTF16 : return "utf16" ;
+ default: return "unknown";
+ }
+}
+
+void TDebugProtocol::indentUp() {
+ indent_str_ += string(indent_inc, ' ');
+}
+
+void TDebugProtocol::indentDown() {
+ if (indent_str_.length() < (string::size_type)indent_inc) {
+ throw TProtocolException(TProtocolException::INVALID_DATA);
+ }
+ indent_str_.erase(indent_str_.length() - indent_inc);
+}
+
+uint32_t TDebugProtocol::writePlain(const string& str) {
+ trans_->write((uint8_t*)str.data(), str.length());
+ return str.length();
+}
+
+uint32_t TDebugProtocol::writeIndented(const string& str) {
+ trans_->write((uint8_t*)indent_str_.data(), indent_str_.length());
+ trans_->write((uint8_t*)str.data(), str.length());
+ return indent_str_.length() + str.length();
+}
+
+uint32_t TDebugProtocol::startItem() {
+ uint32_t size;
+
+ switch (write_state_.back()) {
+ case UNINIT:
+ // XXX figure out what to do here.
+ //throw TProtocolException(TProtocolException::INVALID_DATA);
+ //return writeIndented(str);
+ return 0;
+ case STRUCT:
+ return 0;
+ case SET:
+ return writeIndented("");
+ case MAP_KEY:
+ return writeIndented("");
+ case MAP_VALUE:
+ return writePlain(" -> ");
+ case LIST:
+ size = writeIndented(
+ "[" + boost::lexical_cast<string>(list_idx_.back()) + "] = ");
+ list_idx_.back()++;
+ return size;
+ default:
+ throw std::logic_error("Invalid enum value.");
+ }
+}
+
+uint32_t TDebugProtocol::endItem() {
+ //uint32_t size;
+
+ switch (write_state_.back()) {
+ case UNINIT:
+ // XXX figure out what to do here.
+ //throw TProtocolException(TProtocolException::INVALID_DATA);
+ //return writeIndented(str);
+ return 0;
+ case STRUCT:
+ return writePlain(",\n");
+ case SET:
+ return writePlain(",\n");
+ case MAP_KEY:
+ write_state_.back() = MAP_VALUE;
+ return 0;
+ case MAP_VALUE:
+ write_state_.back() = MAP_KEY;
+ return writePlain(",\n");
+ case LIST:
+ return writePlain(",\n");
+ default:
+ throw std::logic_error("Invalid enum value.");
+ }
+}
+
+uint32_t TDebugProtocol::writeItem(const std::string& str) {
+ uint32_t size = 0;
+ size += startItem();
+ size += writePlain(str);
+ size += endItem();
+ return size;
+}
+
+uint32_t TDebugProtocol::writeMessageBegin(const std::string& name,
+ const TMessageType messageType,
+ const int32_t seqid) {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ "TDebugProtocol does not support messages (yet).");
+}
+
+uint32_t TDebugProtocol::writeMessageEnd() {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ "TDebugProtocol does not support messages (yet).");
+}
+
+uint32_t TDebugProtocol::writeStructBegin(const string& name) {
+ uint32_t size = 0;
+ size += startItem();
+ size += writePlain(name + " {\n");
+ indentUp();
+ write_state_.push_back(STRUCT);
+ return size;
+}
+
+uint32_t TDebugProtocol::writeStructEnd() {
+ indentDown();
+ write_state_.pop_back();
+ uint32_t size = 0;
+ size += writeIndented("}");
+ size += endItem();
+ return size;
+}
+
+uint32_t TDebugProtocol::writeFieldBegin(const string& name,
+ const TType fieldType,
+ const int16_t fieldId) {
+ // sprintf(id_str, "%02d", fieldId);
+ string id_str = boost::lexical_cast<string>(fieldId);
+ if (id_str.length() == 1) id_str = '0' + id_str;
+
+ return writeIndented(
+ id_str + ": " +
+ name + " (" +
+ fieldTypeName(fieldType) + ") = ");
+}
+
+uint32_t TDebugProtocol::writeFieldEnd() {
+ assert(write_state_.back() == STRUCT);
+ return 0;
+}
+
+uint32_t TDebugProtocol::writeFieldStop() {
+ return 0;
+ //writeIndented("***STOP***\n");
+}
+
+uint32_t TDebugProtocol::writeMapBegin(const TType keyType,
+ const TType valType,
+ const uint32_t size) {
+ // TODO(dreiss): Optimize short maps?
+ uint32_t bsize = 0;
+ bsize += startItem();
+ bsize += writePlain(
+ "map<" + fieldTypeName(keyType) + "," + fieldTypeName(valType) + ">"
+ "[" + boost::lexical_cast<string>(size) + "] {\n");
+ indentUp();
+ write_state_.push_back(MAP_KEY);
+ return bsize;
+}
+
+uint32_t TDebugProtocol::writeMapEnd() {
+ indentDown();
+ write_state_.pop_back();
+ uint32_t size = 0;
+ size += writeIndented("}");
+ size += endItem();
+ return size;
+}
+
+uint32_t TDebugProtocol::writeListBegin(const TType elemType,
+ const uint32_t size) {
+ // TODO(dreiss): Optimize short arrays.
+ uint32_t bsize = 0;
+ bsize += startItem();
+ bsize += writePlain(
+ "list<" + fieldTypeName(elemType) + ">"
+ "[" + boost::lexical_cast<string>(size) + "] {\n");
+ indentUp();
+ write_state_.push_back(LIST);
+ list_idx_.push_back(0);
+ return bsize;
+}
+
+uint32_t TDebugProtocol::writeListEnd() {
+ indentDown();
+ write_state_.pop_back();
+ list_idx_.pop_back();
+ uint32_t size = 0;
+ size += writeIndented("}");
+ size += endItem();
+ return size;
+}
+
+uint32_t TDebugProtocol::writeSetBegin(const TType elemType,
+ const uint32_t size) {
+ // TODO(dreiss): Optimize short sets.
+ uint32_t bsize = 0;
+ bsize += startItem();
+ bsize += writePlain(
+ "set<" + fieldTypeName(elemType) + ">"
+ "[" + boost::lexical_cast<string>(size) + "] {\n");
+ indentUp();
+ write_state_.push_back(SET);
+ return bsize;
+}
+
+uint32_t TDebugProtocol::writeSetEnd() {
+ indentDown();
+ write_state_.pop_back();
+ uint32_t size = 0;
+ size += writeIndented("}");
+ size += endItem();
+ return size;
+}
+
+uint32_t TDebugProtocol::writeBool(const bool value) {
+ return writeItem(value ? "true" : "false");
+}
+
+uint32_t TDebugProtocol::writeByte(const int8_t byte) {
+ return writeItem("0x" + byte_to_hex(byte));
+}
+
+uint32_t TDebugProtocol::writeI16(const int16_t i16) {
+ return writeItem(boost::lexical_cast<string>(i16));
+}
+
+uint32_t TDebugProtocol::writeI32(const int32_t i32) {
+ return writeItem(boost::lexical_cast<string>(i32));
+}
+
+uint32_t TDebugProtocol::writeI64(const int64_t i64) {
+ return writeItem(boost::lexical_cast<string>(i64));
+}
+
+uint32_t TDebugProtocol::writeDouble(const double dub) {
+ return writeItem(boost::lexical_cast<string>(dub));
+}
+
+
+uint32_t TDebugProtocol::writeString(const string& str) {
+ // XXX Raw/UTF-8?
+
+ string output = "\"";
+
+ for (string::const_iterator it = str.begin(); it != str.end(); ++it) {
+ if (*it == '\\') {
+ output += "\\";
+ } else if (*it == '"') {
+ output += "\\\"";
+ } else if (std::isprint(*it)) {
+ output += *it;
+ } else {
+ switch (*it) {
+ case '\"': output += "\\\""; break;
+ case '\a': output += "\\a"; break;
+ case '\b': output += "\\b"; break;
+ case '\f': output += "\\f"; break;
+ case '\n': output += "\\n"; break;
+ case '\r': output += "\\r"; break;
+ case '\t': output += "\\t"; break;
+ case '\v': output += "\\v"; break;
+ default:
+ output += "\\x";
+ output += byte_to_hex(*it);
+ }
+ }
+ }
+
+ output += '\"';
+ return writeItem(output);
+}
+
+}}} // facebook::thrift::protocol
diff --git a/lib/cpp/src/protocol/TDebugProtocol.h b/lib/cpp/src/protocol/TDebugProtocol.h
new file mode 100644
index 0000000..0dd60d8
--- /dev/null
+++ b/lib/cpp/src/protocol/TDebugProtocol.h
@@ -0,0 +1,194 @@
+// Copyright (c) 2006- Facebook
+// Distributed under the Thrift Software License
+//
+// See accompanying file LICENSE or visit the Thrift site at:
+// http://developers.facebook.com/thrift/
+
+#ifndef _THRIFT_PROTOCOL_TDEBUGPROTOCOL_H_
+#define _THRIFT_PROTOCOL_TDEBUGPROTOCOL_H_ 1
+
+#include "TProtocol.h"
+#include "TOneWayProtocol.h"
+
+#include <boost/shared_ptr.hpp>
+
+#include <transport/TTransportUtils.h>
+
+namespace facebook { namespace thrift { namespace protocol {
+
+/*
+
+!!! EXPERIMENTAL CODE !!!
+
+This protocol is very much a work in progress.
+It doesn't handle many cases properly.
+It throws exceptions in many cases.
+It probably segfaults in many cases.
+Bug reports and feature requests are welcome.
+Complaints are not. :R
+
+*/
+
+
+/**
+ * Protocol that prints the payload in a nice human-readable format.
+ * Reading from this protocol is not supported.
+ *
+ * @author David Reiss <dreiss@facebook.com>
+ */
+class TDebugProtocol : public TWriteOnlyProtocol {
+ private:
+ enum write_state_t {
+ UNINIT,
+ STRUCT,
+ LIST,
+ SET,
+ MAP_KEY,
+ MAP_VALUE,
+ };
+
+ public:
+ TDebugProtocol(boost::shared_ptr<TTransport> trans)
+ : TWriteOnlyProtocol(trans, "TDebugProtocol")
+ {
+ write_state_.push_back(UNINIT);
+ }
+
+
+ virtual uint32_t writeMessageBegin(const std::string& name,
+ const TMessageType messageType,
+ const int32_t seqid);
+
+ virtual uint32_t writeMessageEnd();
+
+
+ uint32_t writeStructBegin(const std::string& name);
+
+ uint32_t writeStructEnd();
+
+ uint32_t writeFieldBegin(const std::string& name,
+ const TType fieldType,
+ const int16_t fieldId);
+
+ uint32_t writeFieldEnd();
+
+ uint32_t writeFieldStop();
+
+ uint32_t writeMapBegin(const TType keyType,
+ const TType valType,
+ const uint32_t size);
+
+ uint32_t writeMapEnd();
+
+ uint32_t writeListBegin(const TType elemType,
+ const uint32_t size);
+
+ uint32_t writeListEnd();
+
+ uint32_t writeSetBegin(const TType elemType,
+ const uint32_t size);
+
+ uint32_t writeSetEnd();
+
+ uint32_t writeBool(const bool value);
+
+ uint32_t writeByte(const int8_t byte);
+
+ uint32_t writeI16(const int16_t i16);
+
+ uint32_t writeI32(const int32_t i32);
+
+ uint32_t writeI64(const int64_t i64);
+
+ uint32_t writeDouble(const double dub);
+
+ uint32_t writeString(const std::string& str);
+
+
+ private:
+ void indentUp();
+ void indentDown();
+ uint32_t writePlain(const std::string& str);
+ uint32_t writeIndented(const std::string& str);
+ uint32_t startItem();
+ uint32_t endItem();
+ uint32_t writeItem(const std::string& str);
+
+ static std::string fieldTypeName(TType type);
+
+ std::string indent_str_;
+ static const int indent_inc = 2;
+
+ std::vector<write_state_t> write_state_;
+ std::vector<int> list_idx_;
+};
+
+/**
+ * Constructs debug protocol handlers
+ */
+class TDebugProtocolFactory : public TProtocolFactory {
+ public:
+ TDebugProtocolFactory() {}
+ virtual ~TDebugProtocolFactory() {}
+
+ boost::shared_ptr<TProtocol> getProtocol(boost::shared_ptr<TTransport> trans) {
+ return boost::shared_ptr<TProtocol>(new TDebugProtocol(trans));
+ }
+
+};
+
+}}} // facebook::thrift::protocol
+
+
+namespace facebook { namespace thrift {
+
+template<typename ThriftStruct>
+std::string ThriftDebugString(const ThriftStruct& ts) {
+ using namespace facebook::thrift::transport;
+ using namespace facebook::thrift::protocol;
+ TMemoryBuffer* buffer = new TMemoryBuffer;
+ boost::shared_ptr<TTransport> trans(buffer);
+ TDebugProtocol protocol(trans);
+
+ ts.write(&protocol);
+
+ uint8_t* buf;
+ uint32_t size;
+ buffer->getBuffer(&buf, &size);
+ return std::string((char*)buf, (unsigned int)size);
+}
+
+// TODO(dreiss): This is badly broken. Don't use it unless you are me.
+#if 0
+template<typename Object>
+std::string DebugString(const std::vector<Object>& vec) {
+ using namespace facebook::thrift::transport;
+ using namespace facebook::thrift::protocol;
+ TMemoryBuffer* buffer = new TMemoryBuffer;
+ boost::shared_ptr<TTransport> trans(buffer);
+ TDebugProtocol protocol(trans);
+
+ // I am gross!
+ protocol.writeStructBegin("SomeRandomVector");
+
+ // TODO: Fix this with a trait.
+ protocol.writeListBegin((TType)99, vec.size());
+ typename std::vector<Object>::const_iterator it;
+ for (it = vec.begin(); it != vec.end(); ++it) {
+ it->write(&protocol);
+ }
+ protocol.writeListEnd();
+
+ uint8_t* buf;
+ uint32_t size;
+ buffer->getBuffer(&buf, &size);
+ return std::string((char*)buf, (unsigned int)size);
+}
+#endif // 0
+
+}} // facebook::thrift
+
+
+#endif // #ifndef _THRIFT_PROTOCOL_TDEBUGPROTOCOL_H_
+
+
diff --git a/lib/cpp/src/protocol/TOneWayProtocol.h b/lib/cpp/src/protocol/TOneWayProtocol.h
new file mode 100644
index 0000000..c928058
--- /dev/null
+++ b/lib/cpp/src/protocol/TOneWayProtocol.h
@@ -0,0 +1,147 @@
+// Copyright (c) 2006- Facebook
+// Distributed under the Thrift Software License
+//
+// See accompanying file LICENSE or visit the Thrift site at:
+// http://developers.facebook.com/thrift/
+
+#ifndef _THRIFT_PROTOCOL_TONEWAYPROTOCOL_H_
+#define _THRIFT_PROTOCOL_TONEWAYPROTOCOL_H_ 1
+
+#include "TProtocol.h"
+
+namespace facebook { namespace thrift { namespace protocol {
+
+/**
+ * Abstract class for implementing a protocol that can only be written,
+ * not read.
+ *
+ * @author David Reiss <dreiss@facebook.com>
+ */
+class TWriteOnlyProtocol : public TProtocol {
+ public:
+ /**
+ * @param subclass_name The name of the concrete subclass.
+ */
+ TWriteOnlyProtocol(boost::shared_ptr<TTransport> trans,
+ const std::string& subclass_name)
+ : TProtocol(trans)
+ , subclass_(subclass_name)
+ {}
+
+ // All writing functions remain abstract.
+
+ /**
+ * Reading functions all throw an exception.
+ */
+
+ uint32_t readMessageBegin(std::string& name,
+ TMessageType& messageType,
+ int32_t& seqid) {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ subclass_ + " does not support reading (yet).");
+ }
+
+ uint32_t readMessageEnd() {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ subclass_ + " does not support reading (yet).");
+ }
+
+ uint32_t readStructBegin(std::string& name) {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ subclass_ + " does not support reading (yet).");
+ }
+
+ uint32_t readStructEnd() {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ subclass_ + " does not support reading (yet).");
+ }
+
+ uint32_t readFieldBegin(std::string& name,
+ TType& fieldType,
+ int16_t& fieldId) {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ subclass_ + " does not support reading (yet).");
+ }
+
+ uint32_t readFieldEnd() {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ subclass_ + " does not support reading (yet).");
+ }
+
+ uint32_t readMapBegin(TType& keyType,
+ TType& valType,
+ uint32_t& size) {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ subclass_ + " does not support reading (yet).");
+ }
+
+ uint32_t readMapEnd() {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ subclass_ + " does not support reading (yet).");
+ }
+
+ uint32_t readListBegin(TType& elemType,
+ uint32_t& size) {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ subclass_ + " does not support reading (yet).");
+ }
+
+ uint32_t readListEnd() {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ subclass_ + " does not support reading (yet).");
+ }
+
+ uint32_t readSetBegin(TType& elemType,
+ uint32_t& size) {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ subclass_ + " does not support reading (yet).");
+ }
+
+ uint32_t readSetEnd() {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ subclass_ + " does not support reading (yet).");
+ }
+
+ uint32_t readBool(bool& value) {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ subclass_ + " does not support reading (yet).");
+ }
+
+ uint32_t readByte(int8_t& byte) {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ subclass_ + " does not support reading (yet).");
+ }
+
+ uint32_t readI16(int16_t& i16) {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ subclass_ + " does not support reading (yet).");
+ }
+
+ uint32_t readI32(int32_t& i32) {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ subclass_ + " does not support reading (yet).");
+ }
+
+ uint32_t readI64(int64_t& i64) {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ subclass_ + " does not support reading (yet).");
+ }
+
+ uint32_t readDouble(double& dub) {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ subclass_ + " does not support reading (yet).");
+ }
+
+ uint32_t readString(std::string& str) {
+ throw TProtocolException(TProtocolException::NOT_IMPLEMENTED,
+ subclass_ + " does not support reading (yet).");
+ }
+
+
+ private:
+ std::string subclass_;
+};
+
+}}} // facebook::thrift::protocol
+
+#endif // #ifndef _THRIFT_PROTOCOL_TBINARYPROTOCOL_H_
diff --git a/lib/cpp/src/protocol/TProtocolException.h b/lib/cpp/src/protocol/TProtocolException.h
index 50959b7..2d542a0 100644
--- a/lib/cpp/src/protocol/TProtocolException.h
+++ b/lib/cpp/src/protocol/TProtocolException.h
@@ -33,6 +33,7 @@
NEGATIVE_SIZE = 2,
SIZE_LIMIT = 3,
BAD_VERSION = 4,
+ NOT_IMPLEMENTED = 5,
};
TProtocolException() :