thrift/lib/cpp2/protocol/Serializer.h (277 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed 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 CPP2_SERIALIZER_H
#define CPP2_SERIALIZER_H
#include <folly/io/IOBuf.h>
#include <folly/lang/Pretty.h>
#include <thrift/lib/cpp/TApplicationException.h>
#include <thrift/lib/cpp2/Thrift.h>
#include <thrift/lib/cpp2/protocol/BinaryProtocol.h>
#include <thrift/lib/cpp2/protocol/CompactProtocol.h>
#include <thrift/lib/cpp2/protocol/Cpp2Ops.h>
#include <thrift/lib/cpp2/protocol/JSONProtocol.h>
#include <thrift/lib/cpp2/protocol/NimbleProtocol.h>
#include <thrift/lib/cpp2/protocol/Protocol.h>
#include <thrift/lib/cpp2/protocol/SimpleJSONProtocol.h>
namespace apache {
namespace thrift {
template <typename Reader, typename Writer>
struct Serializer {
private:
template <typename T>
using is_thrift_class = folly::bool_constant<is_thrift_class_v<T>>;
template <typename T>
static void warn_unless(folly::tag_t<T>, const char* which, std::false_type) {
FB_LOG_ONCE(ERROR)
<< "Thrift serialization is only defined for structs and unions, not"
<< " containers thereof. Attemping to " << which << " a value of type `"
<< folly::pretty_name<T>() << "`.";
}
template <typename T>
static void warn_unless(folly::tag_t<T>, const char*, std::true_type) {}
public:
using ProtocolReader = Reader;
using ProtocolWriter = Writer;
template <class T>
static folly::io::Cursor deserialize(
const folly::io::Cursor& cursor,
T& obj,
ExternalBufferSharing sharing = COPY_EXTERNAL_BUFFER) {
return deserializeImpl(cursor, obj, sharing);
}
template <class T>
static size_t deserialize(
const folly::IOBuf* buf,
T& obj,
ExternalBufferSharing sharing = COPY_EXTERNAL_BUFFER) {
// Create Reader and assign/read Cursor object in the same method to avoid
// store-load forwarding block penalty vs calling deserializer from Cursor
// object directly.
return deserializeImpl(folly::io::Cursor{buf}, obj, sharing)
.getCurrentPosition();
}
template <class T>
static size_t deserialize(
folly::ByteRange range,
T& obj,
ExternalBufferSharing sharing = COPY_EXTERNAL_BUFFER) {
folly::IOBuf buf(folly::IOBuf::WRAP_BUFFER, range);
return deserialize(&buf, obj, sharing);
}
template <class T>
static size_t deserialize(
folly::StringPiece range,
T& obj,
ExternalBufferSharing sharing = COPY_EXTERNAL_BUFFER) {
return deserialize(folly::ByteRange(range), obj, sharing);
}
/**
* Deserialize an object from a folly::io::Cursor.
*
* When given a non-const Cursor reference we will update the input cursor
* to point at the end of the deserialized data.
*/
template <class T>
static T deserialize(folly::io::Cursor& cursor) {
return returning<T>([&](T& obj) { cursor = deserialize(cursor, obj); });
}
/**
* Deserialize an object from a folly::io::Cursor.
*
* When given a reference to a const Cursor we cannot return the size of data
* that was deserialized. Pass in a non-const Cursor to use the API above if
* you do need to determine the length of the deserialized data.
*/
template <class T>
static T deserialize(const folly::io::Cursor& cursor) {
return returning<T>([&](T& obj) { deserialize(cursor, obj); });
}
template <class T>
static T deserialize(const folly::IOBuf* buf, size_t* size = nullptr) {
return returning<T>([&](T& obj) { set(size, deserialize(buf, obj)); });
}
template <class T>
static T deserialize(
folly::ByteRange range,
size_t* size = nullptr,
ExternalBufferSharing sharing = COPY_EXTERNAL_BUFFER) {
return returning<T>(
[&](T& obj) { set(size, deserialize(range, obj, sharing)); });
}
template <class T>
static T deserialize(
folly::StringPiece range,
size_t* size = nullptr,
ExternalBufferSharing sharing = COPY_EXTERNAL_BUFFER) {
return deserialize<T>(folly::ByteRange(range), size, sharing);
}
template <class T>
static void serialize(
const T& obj,
folly::IOBufQueue* out,
ExternalBufferSharing sharing = COPY_EXTERNAL_BUFFER) {
warn_unless(folly::tag<T>, "serialize", is_thrift_class<T>{});
Writer writer(sharing);
writer.setOutput(out);
// This can be obj.write(&writer);
// if you don't need to support thrift1-compatibility types
apache::thrift::Cpp2Ops<T>::write(&writer, &obj);
}
template <class T>
static void serialize(
const T& obj,
folly::io::QueueAppender&& out,
ExternalBufferSharing sharing = COPY_EXTERNAL_BUFFER) {
Writer writer(sharing);
writer.setOutput(std::move(out));
// This can be obj.write(&writer);
// if you don't need to support thrift1-compatibility types
apache::thrift::Cpp2Ops<T>::write(&writer, &obj);
}
template <class T>
static void serialize(const T& obj, std::string* out) {
folly::IOBufQueue queue(folly::IOBufQueue::cacheChainLength());
// Okay to share any external buffers, as we'll copy them to *out
// immediately afterwards.
serialize(obj, &queue, SHARE_EXTERNAL_BUFFER);
queue.appendToString(*out);
}
template <class T>
static void serialize(const T& obj, folly::fbstring* out) {
folly::IOBufQueue queue(folly::IOBufQueue::cacheChainLength());
// Okay to share any external buffers, as we'll copy them to *out
// immediately afterwards.
if (!out->empty()) {
queue.append(out->data(), out->size());
}
serialize(obj, &queue, SHARE_EXTERNAL_BUFFER);
*out = queue.move()->moveToFbString();
}
template <class R, class T, typename... Args>
static R serialize(const T& obj, Args&&... args) {
R _return;
serialize(obj, &_return, std::forward<Args>(args)...);
return _return;
}
private:
template <typename T>
FOLLY_ALWAYS_INLINE static folly::io::Cursor deserializeImpl(
const folly::io::Cursor& cursor, T& obj, ExternalBufferSharing sharing) {
warn_unless(folly::tag<T>, "deserialize", is_thrift_class<T>{});
Reader reader(sharing);
reader.setInput(cursor);
// This can be obj.read(&reader);
// if you don't need to support thrift1-compatibility types
apache::thrift::Cpp2Ops<T>::read(&reader, &obj);
return reader.getCursor();
}
template <typename T>
static void set(T* t, T&& v) {
if (t != nullptr) {
*t = std::forward<T>(v);
}
}
template <typename R, typename F>
static R returning(F&& f) {
R _return;
f(_return);
return _return;
}
};
// template specialization for NimbleProtocol
template <>
struct Serializer<NimbleProtocolReader, NimbleProtocolWriter> {
template <class T>
static size_t deserialize(const folly::IOBuf* buf, T& obj) {
NimbleProtocolReader reader;
reader.setInput(folly::io::Cursor{buf});
obj.read(&reader);
return 0;
}
template <class T>
static void serialize(const T& obj, folly::IOBufQueue* out) {
NimbleProtocolWriter writer;
obj.write(&writer);
out->append(writer.finalize());
}
};
typedef Serializer<CompactProtocolReader, CompactProtocolWriter>
CompactSerializer;
typedef Serializer<BinaryProtocolReader, BinaryProtocolWriter> BinarySerializer;
typedef Serializer<JSONProtocolReader, JSONProtocolWriter> JSONSerializer;
typedef Serializer<SimpleJSONProtocolReader, SimpleJSONProtocolWriter>
SimpleJSONSerializer;
typedef Serializer<NimbleProtocolReader, NimbleProtocolWriter> NimbleSerializer;
// Serialization code specific to handling errors
template <typename ProtIn, typename ProtOut, bool includeEnvelope = true>
std::unique_ptr<folly::IOBuf> serializeErrorProtocol(
const TApplicationException& obj, folly::IOBuf* req) {
ProtOut prot;
folly::IOBufQueue queue;
size_t objSize = obj.serializedSizeZC(&prot);
if /*constexpr*/ (includeEnvelope) {
ProtIn iprot;
std::string fname;
apache::thrift::MessageType mtype;
int32_t protoSeqId = 0;
iprot.setInput(req);
iprot.readMessageBegin(fname, mtype, protoSeqId);
prot.setOutput(&queue, objSize + prot.serializedMessageSize(fname));
prot.writeMessageBegin(fname, MessageType::T_EXCEPTION, protoSeqId);
} else {
prot.setOutput(&queue, objSize);
}
obj.write(&prot);
prot.writeMessageEnd();
return queue.move();
}
template <typename ProtOut, bool includeEnvelope = true>
std::unique_ptr<folly::IOBuf> serializeErrorProtocol(
const TApplicationException& obj,
const std::string& fname,
int32_t protoSeqId) {
ProtOut prot;
folly::IOBufQueue queue;
std::size_t objSize = obj.serializedSizeZC(&prot);
if /*constexpr*/ (includeEnvelope) {
prot.setOutput(&queue, objSize + prot.serializedMessageSize(fname));
prot.writeMessageBegin(fname, MessageType::T_EXCEPTION, protoSeqId);
} else {
prot.setOutput(&queue, objSize);
}
obj.write(&prot);
prot.writeMessageEnd();
return queue.move();
}
template <bool includeEnvelope = true>
std::unique_ptr<folly::IOBuf> serializeError(
int protId, const TApplicationException& obj, folly::IOBuf* buf) {
switch (protId) {
case apache::thrift::protocol::T_BINARY_PROTOCOL: {
return serializeErrorProtocol<
BinaryProtocolReader,
BinaryProtocolWriter,
includeEnvelope>(obj, buf);
}
case apache::thrift::protocol::T_COMPACT_PROTOCOL: {
return serializeErrorProtocol<
CompactProtocolReader,
CompactProtocolWriter,
includeEnvelope>(obj, buf);
}
default: {
LOG(ERROR) << "Invalid protocol from client";
}
}
return nullptr;
}
template <bool includeEnvelope = true>
std::unique_ptr<folly::IOBuf> serializeError(
int protId,
const TApplicationException& obj,
const std::string& fname,
int32_t protoSeqId) {
switch (protId) {
case apache::thrift::protocol::T_BINARY_PROTOCOL: {
return serializeErrorProtocol<BinaryProtocolWriter, includeEnvelope>(
obj, fname, protoSeqId);
}
case apache::thrift::protocol::T_COMPACT_PROTOCOL: {
return serializeErrorProtocol<CompactProtocolWriter, includeEnvelope>(
obj, fname, protoSeqId);
}
default: {
LOG(ERROR) << "Invalid protocol from client";
}
}
return nullptr;
}
// For places where we can't currently invoke the templated version directly,
inline std::unique_ptr<folly::IOBuf> serializeErrorWithEnvelope(
int protId,
const TApplicationException& obj,
const std::string& fname,
int32_t protoSeqId) {
return serializeError<true>(protId, obj, fname, protoSeqId);
}
inline std::unique_ptr<folly::IOBuf> serializeErrorWithoutEnvelope(
int protId,
const TApplicationException& obj,
const std::string& fname,
int32_t protoSeqId) {
return serializeError<false>(protId, obj, fname, protoSeqId);
}
// serialize TApplicationException without a protocol message envelop
std::unique_ptr<folly::IOBuf> serializeErrorStruct(
protocol::PROTOCOL_TYPES protId, const TApplicationException& obj);
} // namespace thrift
} // namespace apache
#endif