THRIFT-5772: UUID support for c++ #2952
Client: cpp
Patch: CJCombrink

This closes #2952
diff --git a/lib/cpp/CMakeLists.txt b/lib/cpp/CMakeLists.txt
index 6a66e5a..c2f15dd 100644
--- a/lib/cpp/CMakeLists.txt
+++ b/lib/cpp/CMakeLists.txt
@@ -43,6 +43,7 @@
    src/thrift/protocol/TJSONProtocol.cpp
    src/thrift/protocol/TMultiplexedProtocol.cpp
    src/thrift/protocol/TProtocol.cpp
+   src/thrift/protocol/TUuidUtils.cpp
    src/thrift/transport/TTransportException.cpp
    src/thrift/transport/TFDTransport.cpp
    src/thrift/transport/TSimpleFileTransport.cpp
diff --git a/lib/cpp/Makefile.am b/lib/cpp/Makefile.am
index 2499fdb..12b8d82 100644
--- a/lib/cpp/Makefile.am
+++ b/lib/cpp/Makefile.am
@@ -69,6 +69,7 @@
                        src/thrift/protocol/TBase64Utils.cpp \
                        src/thrift/protocol/TMultiplexedProtocol.cpp \
                        src/thrift/protocol/TProtocol.cpp \
+                       src/thrift/protocol/TUuidUtils.cpp \
                        src/thrift/transport/TTransportException.cpp \
                        src/thrift/transport/TFDTransport.cpp \
                        src/thrift/transport/TFileTransport.cpp \
diff --git a/lib/cpp/libthrift.vcxproj b/lib/cpp/libthrift.vcxproj
index 0b5e16d..1b413f8 100644
--- a/lib/cpp/libthrift.vcxproj
+++ b/lib/cpp/libthrift.vcxproj
@@ -49,6 +49,7 @@
     <ClCompile Include="src\thrift\protocol\TJSONProtocol.cpp" />
     <ClCompile Include="src\thrift\protocol\TMultiplexedProtocol.cpp" />
     <ClCompile Include="src\thrift\protocol\TProtocol.cpp" />
+    <ClCompile Include="src\thrift\protocol\TUuidUtils.cpp" />
     <ClCompile Include="src\thrift\server\TConnectedClient.cpp" />
     <ClCompile Include="src\thrift\server\TServer.cpp" />
     <ClCompile Include="src\thrift\server\TServerFramework.cpp" />
diff --git a/lib/cpp/libthrift.vcxproj.filters b/lib/cpp/libthrift.vcxproj.filters
index 98426fa..fb94f60 100644
--- a/lib/cpp/libthrift.vcxproj.filters
+++ b/lib/cpp/libthrift.vcxproj.filters
@@ -30,6 +30,9 @@
     <ClCompile Include="src\thrift\protocol\TMultiplexedProtocol.cpp">
       <Filter>protocol</Filter>
     </ClCompile>
+    <ClCompile Include="src\thrift\protocol\TUuidUtils.cpp">
+      <Filter>protocol</Filter>
+    </ClCompile>
     <ClCompile Include="src\thrift\transport\TFDTransport.cpp">
       <Filter>transport</Filter>
     </ClCompile>
diff --git a/lib/cpp/src/thrift/protocol/TBinaryProtocol.h b/lib/cpp/src/thrift/protocol/TBinaryProtocol.h
index 7b829c7..24e51f7 100644
--- a/lib/cpp/src/thrift/protocol/TBinaryProtocol.h
+++ b/lib/cpp/src/thrift/protocol/TBinaryProtocol.h
@@ -119,6 +119,8 @@
 
   inline uint32_t writeBinary(const std::string& str);
 
+  inline uint32_t writeUUID(const std::string& str);
+
   /**
    * Reading functions
    */
@@ -166,6 +168,8 @@
 
   inline uint32_t readBinary(std::string& str);
 
+  inline uint32_t readUUID(std::string& str);
+
   int getMinSerializedSize(TType type) override;
 
   void checkReadBytesAvailable(TSet& set) override
diff --git a/lib/cpp/src/thrift/protocol/TBinaryProtocol.tcc b/lib/cpp/src/thrift/protocol/TBinaryProtocol.tcc
index c448e77..6a54ece 100644
--- a/lib/cpp/src/thrift/protocol/TBinaryProtocol.tcc
+++ b/lib/cpp/src/thrift/protocol/TBinaryProtocol.tcc
@@ -21,6 +21,7 @@
 #define _THRIFT_PROTOCOL_TBINARYPROTOCOL_TCC_ 1
 
 #include <thrift/protocol/TBinaryProtocol.h>
+#include <thrift/protocol/TUuidUtils.hpp>
 #include <thrift/transport/TTransportException.h>
 
 #include <limits>
@@ -193,6 +194,20 @@
   return TBinaryProtocolT<Transport_, ByteOrder_>::writeString(str);
 }
 
+template <class Transport_, class ByteOrder_>
+uint32_t TBinaryProtocolT<Transport_, ByteOrder_>::writeUUID(const std::string& str) {
+  std::string out;
+  const bool encoded = uuid_encode(str, out);
+  if(!encoded)
+    throw TProtocolException(TProtocolException::INVALID_DATA);
+  // This should not happen, but check for now
+  if(out.size() != 16)
+    throw TProtocolException(TProtocolException::UNKNOWN);
+  // TODO: Consider endian swapping, see lib/delphi/src/Thrift.Utils.pas:377
+  this->trans_->write((uint8_t*)out.data(), 16);
+  return 16;
+}
+
 /**
  * Reading functions
  */
@@ -286,7 +301,7 @@
     throw TProtocolException(TProtocolException::SIZE_LIMIT);
   }
   size = (uint32_t)sizei;
-  
+
   TMap map(keyType, valType, size);
   checkReadBytesAvailable(map);
 
@@ -429,6 +444,14 @@
 }
 
 template <class Transport_, class ByteOrder_>
+uint32_t TBinaryProtocolT<Transport_, ByteOrder_>::readUUID(std::string& str) {
+  std::string in;
+  readStringBody(in, 16);
+  uuid_decode(in, str);
+  return 16;
+}
+
+template <class Transport_, class ByteOrder_>
 template <typename StrType>
 uint32_t TBinaryProtocolT<Transport_, ByteOrder_>::readStringBody(StrType& str, int32_t size) {
   uint32_t result = 0;
diff --git a/lib/cpp/src/thrift/protocol/TDebugProtocol.cpp b/lib/cpp/src/thrift/protocol/TDebugProtocol.cpp
index 0e6d4a2..0a2eaed 100644
--- a/lib/cpp/src/thrift/protocol/TDebugProtocol.cpp
+++ b/lib/cpp/src/thrift/protocol/TDebugProtocol.cpp
@@ -18,6 +18,7 @@
  */
 
 #include <thrift/protocol/TDebugProtocol.h>
+#include <thrift/protocol/TUuidUtils.hpp>
 
 #include <thrift/TToString.h>
 #include <cassert>
@@ -70,10 +71,8 @@
     return "set";
   case T_LIST:
     return "list";
-  case T_UTF8:
-    return "utf8";
-  case T_UTF16:
-    return "utf16";
+  case T_UUID:
+    return "uuid";
   default:
     return "unknown";
   }
@@ -388,6 +387,24 @@
   // XXX Hex?
   return TDebugProtocol::writeString(str);
 }
+
+uint32_t TDebugProtocol::writeUUID(const string& str) {
+  std::string out_raw;
+  uuid_encode(str, out_raw);
+
+  std::string out_encoded;
+  uuid_decode(out_raw, out_encoded);
+
+  size_t size = writePlain("{\n");
+  indentUp();
+  size += writeIndented("[in ] = \"" + str + "\",\n");
+  size += writeIndented("[raw] = ");
+  size += writeString(out_raw);
+  size += writeIndented("[enc] = \"" + out_encoded + "\"\n");
+  indentDown();
+  size += writeIndented("}\n");
+  return size;
+}
 }
 }
 } // apache::thrift::protocol
diff --git a/lib/cpp/src/thrift/protocol/TDebugProtocol.h b/lib/cpp/src/thrift/protocol/TDebugProtocol.h
index 41bb0d4..af89cc5 100644
--- a/lib/cpp/src/thrift/protocol/TDebugProtocol.h
+++ b/lib/cpp/src/thrift/protocol/TDebugProtocol.h
@@ -110,6 +110,8 @@
 
   uint32_t writeBinary(const std::string& str);
 
+  uint32_t writeUUID(const std::string& str);
+
 private:
   void indentUp();
   void indentDown();
diff --git a/lib/cpp/src/thrift/protocol/TEnum.h b/lib/cpp/src/thrift/protocol/TEnum.h
index 9636785..bbd1247 100644
--- a/lib/cpp/src/thrift/protocol/TEnum.h
+++ b/lib/cpp/src/thrift/protocol/TEnum.h
@@ -18,7 +18,7 @@
  */
 
 #ifndef _THRIFT_ENUM_H_
-#define _THRIFT_ENUM_H_ 
+#define _THRIFT_ENUM_H_
 
 namespace apache {
 namespace thrift {
@@ -46,8 +46,7 @@
   T_MAP        = 13,
   T_SET        = 14,
   T_LIST       = 15,
-  T_UTF8       = 16,
-  T_UTF16      = 17
+  T_UUID       = 16,
 };
 
 /**
@@ -63,4 +62,4 @@
 
 }}} // apache::thrift::protocol
 
-#endif // #define _THRIFT_ENUM_H_ 
+#endif // #define _THRIFT_ENUM_H_
diff --git a/lib/cpp/src/thrift/protocol/TJSONProtocol.cpp b/lib/cpp/src/thrift/protocol/TJSONProtocol.cpp
index 6e4e8ef..0899f00 100644
--- a/lib/cpp/src/thrift/protocol/TJSONProtocol.cpp
+++ b/lib/cpp/src/thrift/protocol/TJSONProtocol.cpp
@@ -18,6 +18,7 @@
  */
 
 #include <thrift/protocol/TJSONProtocol.h>
+#include <thrift/protocol/TUuidUtils.hpp>
 
 #include <boost/locale.hpp>
 
@@ -68,6 +69,7 @@
 static const std::string kTypeNameMap("map");
 static const std::string kTypeNameList("lst");
 static const std::string kTypeNameSet("set");
+static const std::string kTypeNameUuid("uid");
 
 static const std::string& getTypeNameForTypeID(TType typeID) {
   switch (typeID) {
@@ -93,6 +95,8 @@
     return kTypeNameSet;
   case T_LIST:
     return kTypeNameList;
+  case T_UUID:
+    return kTypeNameUuid;
   default:
     throw TProtocolException(TProtocolException::NOT_IMPLEMENTED, "Unrecognized type");
   }
@@ -140,6 +144,9 @@
     case 't':
       result = T_BOOL;
       break;
+    case 'u':
+      result = T_UUID;
+      break;
     }
   }
   if (result == T_STOP) {
@@ -710,6 +717,16 @@
   return writeJSONBase64(str);
 }
 
+uint32_t TJSONProtocol::writeUUID(const std::string& str) {
+  std::string out_raw;
+  uuid_encode(str, out_raw);
+
+  std::string out_encoded;
+  uuid_decode(out_raw, out_encoded);
+
+  return writeJSONString(out_encoded);
+}
+
 /**
  * Reading functions
  */
@@ -1106,6 +1123,10 @@
   return readJSONBase64(str);
 }
 
+uint32_t TJSONProtocol::readUUID(std::string& str) {
+  return readJSONString(str);
+}
+
 // Return the minimum number of bytes a type will consume on the wire
 int TJSONProtocol::getMinSerializedSize(TType type)
 {
@@ -1113,7 +1134,7 @@
   {
     case T_STOP: return 0;
     case T_VOID: return 0;
-    case T_BOOL: return 1;  // written as int  
+    case T_BOOL: return 1;  // written as int
     case T_BYTE: return 1;
     case T_DOUBLE: return 1;
     case T_I16: return 1;
@@ -1124,6 +1145,7 @@
     case T_MAP: return 2;  // empty map
     case T_SET: return 2;  // empty set
     case T_LIST: return 2;  // empty list
+    case T_UUID: return 16;  // empty UUID
     default: throw TProtocolException(TProtocolException::UNKNOWN, "unrecognized type code");
   }
 }
diff --git a/lib/cpp/src/thrift/protocol/TJSONProtocol.h b/lib/cpp/src/thrift/protocol/TJSONProtocol.h
index d01bdf8..069a990 100644
--- a/lib/cpp/src/thrift/protocol/TJSONProtocol.h
+++ b/lib/cpp/src/thrift/protocol/TJSONProtocol.h
@@ -198,6 +198,8 @@
 
   uint32_t writeBinary(const std::string& str);
 
+  uint32_t writeUUID(const std::string& str);
+
   /**
    * Reading functions
    */
@@ -245,6 +247,8 @@
 
   uint32_t readBinary(std::string& str);
 
+  uint32_t readUUID(std::string& str);
+
   int getMinSerializedSize(TType type) override;
 
   void checkReadBytesAvailable(TSet& set) override
diff --git a/lib/cpp/src/thrift/protocol/TProtocol.h b/lib/cpp/src/thrift/protocol/TProtocol.h
index 237c1e5..d29df1c 100644
--- a/lib/cpp/src/thrift/protocol/TProtocol.h
+++ b/lib/cpp/src/thrift/protocol/TProtocol.h
@@ -275,6 +275,8 @@
 
   virtual uint32_t writeBinary_virt(const std::string& str) = 0;
 
+  virtual uint32_t writeUUID_virt(const std::string& str) = 0;
+
   uint32_t writeMessageBegin(const std::string& name,
                              const TMessageType messageType,
                              const int32_t seqid) {
@@ -382,6 +384,11 @@
     return writeBinary_virt(str);
   }
 
+  uint32_t writeUUID(const std::string& str) {
+    T_VIRTUAL_CALL();
+    return writeUUID_virt(str);
+  }
+
   /**
    * Reading functions
    */
@@ -430,6 +437,8 @@
 
   virtual uint32_t readBinary_virt(std::string& str) = 0;
 
+  virtual uint32_t readUUID_virt(std::string& str) = 0;
+
   uint32_t readMessageBegin(std::string& name, TMessageType& messageType, int32_t& seqid) {
     T_VIRTUAL_CALL();
     return readMessageBegin_virt(name, messageType, seqid);
@@ -530,6 +539,11 @@
     return readBinary_virt(str);
   }
 
+  uint32_t readUUID(std::string& str) {
+    T_VIRTUAL_CALL();
+    return readUUID_virt(str);
+  }
+
   /*
    * std::vector is specialized for bool, and its elements are individual bits
    * rather than bools.   We need to define a different version of readBool()
diff --git a/lib/cpp/src/thrift/protocol/TProtocolDecorator.h b/lib/cpp/src/thrift/protocol/TProtocolDecorator.h
index 5258159..9eb1566 100644
--- a/lib/cpp/src/thrift/protocol/TProtocolDecorator.h
+++ b/lib/cpp/src/thrift/protocol/TProtocolDecorator.h
@@ -92,6 +92,7 @@
   uint32_t writeDouble_virt(const double dub) override { return protocol->writeDouble(dub); }
   uint32_t writeString_virt(const std::string& str) override { return protocol->writeString(str); }
   uint32_t writeBinary_virt(const std::string& str) override { return protocol->writeBinary(str); }
+  uint32_t writeUUID_virt(const std::string& str) override { return protocol->writeUUID(str); }
 
   uint32_t readMessageBegin_virt(std::string& name,
                                          TMessageType& messageType,
@@ -140,6 +141,7 @@
 
   uint32_t readString_virt(std::string& str) override { return protocol->readString(str); }
   uint32_t readBinary_virt(std::string& str) override { return protocol->readBinary(str); }
+  uint32_t readUUID_virt(std::string& str) override { return protocol->readUUID(str); }
 
 private:
   shared_ptr<TProtocol> protocol;
diff --git a/lib/cpp/src/thrift/protocol/TUuidUtils.cpp b/lib/cpp/src/thrift/protocol/TUuidUtils.cpp
new file mode 100644
index 0000000..e1eab22
--- /dev/null
+++ b/lib/cpp/src/thrift/protocol/TUuidUtils.cpp
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <thrift/protocol/TUuidUtils.hpp>
+
+#include <boost/uuid/string_generator.hpp>
+#include <boost/uuid/uuid.hpp>
+#include <boost/uuid/uuid_io.hpp>
+
+namespace apache {
+namespace thrift {
+namespace protocol {
+
+bool uuid_encode(const std::string& in, std::string& out) {
+  static const boost::uuids::string_generator gen;
+  static const std::string empty_uuid(boost::uuids::uuid::static_size(), '\0');
+  out = empty_uuid;
+  if (in.empty()) {
+    return true;
+  }
+  try {
+    const boost::uuids::uuid uuid{gen(in)};
+    std::copy(uuid.begin(), uuid.end(), out.begin());
+    return true;
+  } catch (const std::runtime_error&) {
+    // Invalid string most probably
+    return false;
+  }
+}
+
+void uuid_decode(const std::string& in, std::string& out) {
+  boost::uuids::uuid uuid{};
+  const size_t to_copy = std::min(in.size(), uuid.size());
+  std::copy(in.begin(), in.begin() + to_copy, uuid.begin());
+  out = boost::uuids::to_string(uuid);
+}
+
+}
+}
+} // apache::thrift::protocol
diff --git a/lib/cpp/src/thrift/protocol/TUuidUtils.hpp b/lib/cpp/src/thrift/protocol/TUuidUtils.hpp
new file mode 100644
index 0000000..583147f
--- /dev/null
+++ b/lib/cpp/src/thrift/protocol/TUuidUtils.hpp
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#ifndef _THRIFT_PROTOCOL_TUUIDUTILS_H_
+#define _THRIFT_PROTOCOL_TUUIDUTILS_H_
+
+#include <string>
+
+namespace apache {
+namespace thrift {
+namespace protocol {
+
+// Encode canonical UUID string to a 16 char representation
+// Supported formats for in:
+//   - "hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh"
+//   - "{hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh}"
+//   - "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh"
+//   - "{hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh}"
+// Returns false if the string was invalid and the value was not encoded.
+bool uuid_encode(const std::string& in, std::string& out);
+
+// Decode 16 char UUID buffer to 36 characted string
+void uuid_decode(const std::string& in, std::string& out);
+
+}
+}
+} // apache::thrift::protocol
+
+#endif // #define _THRIFT_PROTOCOL_TUUIDUTILS_H_
diff --git a/lib/cpp/src/thrift/protocol/TVirtualProtocol.h b/lib/cpp/src/thrift/protocol/TVirtualProtocol.h
index b7fe929..4698081 100644
--- a/lib/cpp/src/thrift/protocol/TVirtualProtocol.h
+++ b/lib/cpp/src/thrift/protocol/TVirtualProtocol.h
@@ -393,6 +393,10 @@
     return static_cast<Protocol_*>(this)->writeBinary(str);
   }
 
+  uint32_t writeUUID_virt(const std::string& str) override {
+    return static_cast<Protocol_*>(this)->writeUUID(str);
+  }
+
   /**
    * Reading functions
    */
@@ -471,6 +475,10 @@
     return static_cast<Protocol_*>(this)->readBinary(str);
   }
 
+  uint32_t readUUID_virt(std::string& str) override {
+    return static_cast<Protocol_*>(this)->readUUID(str);
+  }
+
   uint32_t skip_virt(TType type) override { return static_cast<Protocol_*>(this)->skip(type); }
 
   /*
diff --git a/lib/cpp/test/Benchmark.cpp b/lib/cpp/test/Benchmark.cpp
index 56adac0..97a5317 100644
--- a/lib/cpp/test/Benchmark.cpp
+++ b/lib/cpp/test/Benchmark.cpp
@@ -66,6 +66,7 @@
   ooe.some_characters = "JSON THIS! \"\1";
   ooe.zomg_unicode = "\xd7\n\a\t";
   ooe.base64 = "\1\2\3\255";
+  ooe.rfc4122_uuid = "{5e2ab188-1726-4e75-a04f-1ed9a6a89c4c}";
 
   int num = 100000;
   std::shared_ptr<TMemoryBuffer> buf(new TMemoryBuffer(num*1000));
diff --git a/lib/cpp/test/CMakeLists.txt b/lib/cpp/test/CMakeLists.txt
index 1117cd9..87ed109 100644
--- a/lib/cpp/test/CMakeLists.txt
+++ b/lib/cpp/test/CMakeLists.txt
@@ -360,7 +360,7 @@
 )
 
 add_custom_command(OUTPUT gen-cpp/DebugProtoTest_types.cpp gen-cpp/DebugProtoTest_types.h gen-cpp/EmptyService.cpp gen-cpp/EmptyService.h
-    COMMAND ${THRIFT_COMPILER} --gen cpp ${PROJECT_SOURCE_DIR}/test/v0.16/DebugProtoTest.thrift
+    COMMAND ${THRIFT_COMPILER} --gen cpp ${PROJECT_SOURCE_DIR}/test/DebugProtoTest.thrift
 )
 
 add_custom_command(OUTPUT gen-cpp/EnumTest_types.cpp gen-cpp/EnumTest_types.h
@@ -384,7 +384,7 @@
 )
 
 add_custom_command(OUTPUT gen-cpp/SecondService.cpp gen-cpp/ThriftTest_constants.cpp gen-cpp/ThriftTest.cpp gen-cpp/ThriftTest_types.cpp gen-cpp/ThriftTest_types.h
-    COMMAND ${THRIFT_COMPILER} --gen cpp ${PROJECT_SOURCE_DIR}/test/v0.16/ThriftTest.thrift
+    COMMAND ${THRIFT_COMPILER} --gen cpp ${PROJECT_SOURCE_DIR}/test/ThriftTest.thrift
 )
 
 add_custom_command(OUTPUT gen-cpp/OneWayService.cpp gen-cpp/OneWayTest_types.h gen-cpp/OneWayService.h
diff --git a/lib/cpp/test/DebugProtoTest.cpp b/lib/cpp/test/DebugProtoTest.cpp
index 060f354..cc4e5ff 100644
--- a/lib/cpp/test/DebugProtoTest.cpp
+++ b/lib/cpp/test/DebugProtoTest.cpp
@@ -74,6 +74,11 @@
     "    [1] = 2,\n"
     "    [2] = 3,\n"
     "  },\n"
+    "  15: rfc4122_uuid (uuid) = {\n"
+    "    [in ] = \"\",\n"
+    "    [raw] = \"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\",\n"
+    "    [enc] = \"00000000-0000-0000-0000-000000000000\"\n"
+    "  }\n"
     "}");
   const std::string result(apache::thrift::ThriftDebugString(*ooe));
 
@@ -98,6 +103,7 @@
                                "\xb0\xcf\x81\xe2\x84\x8e\x20\xce\x91\x74\x74"
                                "\xce\xb1\xe2\x85\xbd\xce\xba\xc7\x83\xe2\x80"
                                "\xbc";
+  n->my_ooe.rfc4122_uuid = "{5e2ab188-1726-4e75-a04f-1ed9a6a89c4c}";
   n->my_bonk.type = 31337;
   n->my_bonk.message = "I am a bonk... xor!";
 }
@@ -141,6 +147,11 @@
     "      [1] = 2,\n"
     "      [2] = 3,\n"
     "    },\n"
+    "    15: rfc4122_uuid (uuid) = {\n"
+    "      [in ] = \"{5e2ab188-1726-4e75-a04f-1ed9a6a89c4c}\",\n"
+    "      [raw] = \"^*\\xb1\\x88\\x17&Nu\\xa0O\\x1e\\xd9\\xa6\\xa8\\x9cL\",\n"
+    "      [enc] = \"5e2ab188-1726-4e75-a04f-1ed9a6a89c4c\"\n"
+    "    }\n"
     "  },\n"
     "}");
   const std::string result(apache::thrift::ThriftDebugString(*n));
@@ -228,6 +239,11 @@
     "        [1] = 2,\n"
     "        [2] = 3,\n"
     "      },\n"
+    "      15: rfc4122_uuid (uuid) = {\n"
+    "        [in ] = \"\",\n"
+    "        [raw] = \"\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\",\n"
+    "        [enc] = \"00000000-0000-0000-0000-000000000000\"\n"
+    "      }\n"
     "    },\n"
     "    [1] = OneOfEach {\n"
     "      01: im_true (bool) = true,\n"
@@ -259,6 +275,11 @@
     "        [1] = 2,\n"
     "        [2] = 3,\n"
     "      },\n"
+    "      15: rfc4122_uuid (uuid) = {\n"
+    "        [in ] = \"{5e2ab188-1726-4e75-a04f-1ed9a6a89c4c}\",\n"
+    "        [raw] = \"^*\\xb1\\x88\\x17&Nu\\xa0O\\x1e\\xd9\\xa6\\xa8\\x9cL\",\n"
+    "        [enc] = \"5e2ab188-1726-4e75-a04f-1ed9a6a89c4c\"\n"
+    "      }\n"
     "    },\n"
     "  },\n"
     "  02: contain (set) = set<list>[3] {\n"
diff --git a/lib/cpp/test/JSONProtoTest.cpp b/lib/cpp/test/JSONProtoTest.cpp
index 082c8a2..fedf99e 100644
--- a/lib/cpp/test/JSONProtoTest.cpp
+++ b/lib/cpp/test/JSONProtoTest.cpp
@@ -48,6 +48,7 @@
   ooe->some_characters = "JSON THIS! \"\1";
   ooe->zomg_unicode = "\xd7\n\a\t";
   ooe->base64 = "\1\2\3\255";
+  ooe->rfc4122_uuid = "00000000-0000-0000-0000-000000000000";
 }
 
 BOOST_AUTO_TEST_CASE(test_json_proto_1) {
@@ -59,7 +60,7 @@
   "535897931},\"8\":{\"str\":\"JSON THIS! \\\"\\u0001\"},\"9\":{\"str\":\"\xd7\\"
   "n\\u0007\\t\"},\"10\":{\"tf\":0},\"11\":{\"str\":\"AQIDrQ\"},\"12\":{\"lst\""
   ":[\"i8\",3,1,2,3]},\"13\":{\"lst\":[\"i16\",3,1,2,3]},\"14\":{\"lst\":[\"i64"
-  "\",3,1,2,3]}}");
+  "\",3,1,2,3]},\"15\":{\"uid\":\"00000000-0000-0000-0000-000000000000\"}}");
 
   const std::string result(apache::thrift::ThriftJSONString(*ooe));
 
@@ -84,6 +85,7 @@
                                "\xb0\xcf\x81\xe2\x84\x8e\x20\xce\x91\x74\x74"
                                "\xce\xb1\xe2\x85\xbd\xce\xba\xc7\x83\xe2\x80"
                                "\xbc";
+  n->my_ooe.rfc4122_uuid = "5e2ab188-1726-4e75-a04f-1ed9a6a89c4c";
   n->my_bonk.type = 31337;
   n->my_bonk.message = "I am a bonk... xor!";
 }
@@ -98,7 +100,8 @@
     "1.6180339887498949},\"8\":{\"str\":\":R (me going \\\"rrrr\\\")\"},\"9\":{"
     "\"str\":\"ӀⅮΝ Нοⅿоɡгаρℎ Αttαⅽκǃ‼\"},\"10\":{\"tf\":0},\"11\":{\"str\":\""
     "AQIDrQ\"},\"12\":{\"lst\":[\"i8\",3,1,2,3]},\"13\":{\"lst\":[\"i16\",3,1,2"
-    ",3]},\"14\":{\"lst\":[\"i64\",3,1,2,3]}}}}"
+    ",3]},\"14\":{\"lst\":[\"i64\",3,1,2,3]},\"15\":{\"uid\":\"5e2ab188-1726-"
+    "4e75-a04f-1ed9a6a89c4c\"}}}}"
   );
 
   const std::string result(apache::thrift::ThriftJSONString(*n));
@@ -162,12 +165,14 @@
   "},\"7\":{\"dbl\":3.1415926535897931},\"8\":{\"str\":\"JSON THIS! \\\"\\u0001"
   "\"},\"9\":{\"str\":\"\xd7\\n\\u0007\\t\"},\"10\":{\"tf\":0},\"11\":{\"str\":"
   "\"AQIDrQ\"},\"12\":{\"lst\":[\"i8\",3,1,2,3]},\"13\":{\"lst\":[\"i16\",3,1,2"
-  ",3]},\"14\":{\"lst\":[\"i64\",3,1,2,3]}},{\"1\":{\"tf\":1},\"2\":{\"tf\":0},"
+  ",3]},\"14\":{\"lst\":[\"i64\",3,1,2,3]},\"15\":{\"uid\":\"00000000-0000-0000"
+  "-0000-000000000000\"}},{\"1\":{\"tf\":1},\"2\":{\"tf\":0},"
   "\"3\":{\"i8\":51},\"4\":{\"i16\":16},\"5\":{\"i32\":32},\"6\":{\"i64\":64},"
   "\"7\":{\"dbl\":1.6180339887498949},\"8\":{\"str\":\":R (me going \\\"rrrr\\\""
   ")\"},\"9\":{\"str\":\"ӀⅮΝ Нοⅿоɡгаρℎ Αttαⅽκǃ‼\"},\"10\":{\"tf\":0},\"11\":{"
   "\"str\":\"AQIDrQ\"},\"12\":{\"lst\":[\"i8\",3,1,2,3]},\"13\":{\"lst\":[\"i16"
-  "\",3,1,2,3]},\"14\":{\"lst\":[\"i64\",3,1,2,3]}}]},\"2\":{\"set\":[\"lst\",3"
+  "\",3,1,2,3]},\"14\":{\"lst\":[\"i64\",3,1,2,3]},\"15\":{\"uid\":\"5e2ab188-"
+  "1726-4e75-a04f-1ed9a6a89c4c\"}}]},\"2\":{\"set\":[\"lst\",3"
   ",[\"str\",0],[\"str\",2,\"and a one\",\"and a two\"],[\"str\",3,\"then a one"
   ", two\",\"three!\",\"FOUR!!\"]]},\"3\":{\"map\":[\"str\",\"lst\",3,{\"nothin"
   "g\":[\"rec\",0],\"poe\":[\"rec\",3,{\"1\":{\"i32\":3},\"2\":{\"str\":\"quoth"
@@ -192,6 +197,8 @@
   OneOfEach ooe2;
   ooe2.read(proto.get());
 
+  BOOST_TEST_INFO("written: " << *ooe);
+  BOOST_TEST_INFO("read   : " << ooe2);
   BOOST_CHECK(*ooe == ooe2);
 }
 
@@ -205,6 +212,8 @@
   HolyMoley hm2;
   hm2.read(proto.get());
 
+  BOOST_TEST_INFO("written: " << *hm);
+  BOOST_TEST_INFO("read   : " << hm2);
   BOOST_CHECK(*hm == hm2);
 
   hm2.big[0].a_bite = 0x00;
@@ -230,14 +239,14 @@
   );
 
   std::shared_ptr<TMemoryBuffer> buffer(new TMemoryBuffer());
-  std::shared_ptr<TJSONProtocol> proto(new TJSONProtocol(buffer)); 
+  std::shared_ptr<TJSONProtocol> proto(new TJSONProtocol(buffer));
   dub.write(proto.get());
   Doubles dub_1;
   dub_1.read(proto.get());
 
   const std::string result(apache::thrift::ThriftJSONString(dub));
   const std::string result_1(apache::thrift::ThriftJSONString(dub_1));
-  
+
   BOOST_CHECK_MESSAGE(!expected_result.compare(result),
     "Expected:\n" << expected_result << "\nGotten:\n" << result);
   BOOST_CHECK_MESSAGE(!expected_result.compare(result_1),
diff --git a/lib/cpp/test/SpecializationTest.cpp b/lib/cpp/test/SpecializationTest.cpp
index 008837d..0976112 100644
--- a/lib/cpp/test/SpecializationTest.cpp
+++ b/lib/cpp/test/SpecializationTest.cpp
@@ -26,6 +26,7 @@
   ooe.some_characters = "JSON THIS! \"\1";
   ooe.zomg_unicode = "\xd7\n\a\t";
   ooe.base64 = "\1\2\3\255";
+  ooe.rfc4122_uuid = "00000000-0000-0000-0000-000000000000";
 
   Nesting n;
   n.my_ooe = ooe;
@@ -89,6 +90,8 @@
   OneOfEach ooe2;
   ooe2.read(proto.get());
 
+  BOOST_TEST_INFO("Write: " << ooe);
+  BOOST_TEST_INFO("Read : " << ooe2);
   BOOST_CHECK(ooe == ooe2);
 
   hm.write(proto.get());
diff --git a/lib/cpp/test/ToStringTest.cpp b/lib/cpp/test/ToStringTest.cpp
index 736b33c..68c82ad 100644
--- a/lib/cpp/test/ToStringTest.cpp
+++ b/lib/cpp/test/ToStringTest.cpp
@@ -160,4 +160,13 @@
                     "ListBonks(bonk=[Bonk(message=a, type=0), Bonk(message=b, type=0)])");
 }
 
+BOOST_AUTO_TEST_CASE(generated_uuid_to_string) {
+  thrift::test::CrazyNesting l;
+  l.uuid_field = "{4b686716-5f20-4deb-8ce0-9eaf379e8a3d}";
+
+  BOOST_CHECK_EQUAL(to_string(l),
+                    "CrazyNesting(string_field=, set_field=<null>, list_field=[], binary_field=, "
+                    "uuid_field={4b686716-5f20-4deb-8ce0-9eaf379e8a3d})");
+}
+
 BOOST_AUTO_TEST_SUITE_END()