mcrouter/tools/mcpiper/McPiperVisitor.h (272 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#pragma once
#include <cctype>
#include <type_traits>
#include <unordered_set>
#include <utility>
#include <folly/Conv.h>
#include <folly/Optional.h>
#include <folly/Range.h>
#include <thrift/lib/cpp2/FieldRef.h>
#include <thrift/lib/cpp2/protocol/Serializer.h>
#include <thrift/lib/cpp2/protocol/SimpleJSONProtocol.h>
#include "mcrouter/lib/carbon/CommonSerializationTraits.h"
#include "mcrouter/lib/carbon/Fields.h"
#include "mcrouter/lib/carbon/Keys.h"
#include "mcrouter/tools/mcpiper/PrettyFormat.h"
#include "mcrouter/tools/mcpiper/StyledString.h"
#include "mcrouter/tools/mcpiper/ValueFormatter.h"
namespace carbon {
namespace detail {
class McPiperVisitor {
public:
explicit McPiperVisitor(bool script, size_t indent = 0)
: indent_(indent), script_(script) {}
template <class T>
bool enterMixin(size_t /* id */, folly::StringPiece /* name */, const T&) {
return true;
}
bool leaveMixin() {
return true;
}
template <class T>
bool visitField(size_t /* id */, folly::StringPiece name, const T& t) {
if (kExcuseValues.find(name.str()) == kExcuseValues.end()) {
auto content = serialize(t);
if (!content.empty()) {
out_.append(prepareAndRenderHeader(name));
out_.append(content);
}
}
return true;
}
facebook::memcache::StyledString styled() && {
return std::move(out_);
}
private:
const std::unordered_set<std::string> kExcuseValues = {
"value",
"flags",
"result",
"key"};
facebook::memcache::StyledString out_;
size_t indent_{0};
const facebook::memcache::PrettyFormat format_{};
const bool script_{false};
bool commaNeeded_{false};
// key
template <class T>
facebook::memcache::StyledString serialize(const carbon::Keys<T>& value) {
facebook::memcache::StyledString out;
out.append(value.fullKey().str(), format_.dataValueColor);
return out;
}
// string-like
facebook::memcache::StyledString serialize(char value) {
facebook::memcache::StyledString out;
char mark = script_ ? '"' : '\'';
out.pushBack(mark);
out.pushBack(value, format_.dataValueColor);
out.pushBack(mark);
return out;
}
facebook::memcache::StyledString serialize(const std::string& value) {
return serializeString(value);
}
facebook::memcache::StyledString serialize(const folly::IOBuf& buf) {
auto buffer = buf;
auto strPiece = folly::StringPiece(buffer.coalesce());
return serializeString(strPiece);
}
// boolean
facebook::memcache::StyledString serialize(const bool& b) {
facebook::memcache::StyledString out;
out.append(b ? "true" : "false", format_.dataValueColor);
return out;
}
// numbers
template <class T>
std::enable_if_t<
std::is_arithmetic<T>::value,
facebook::memcache::StyledString>
serialize(const T& value) {
facebook::memcache::StyledString out;
out.append(folly::to<std::string>(value), format_.dataValueColor);
return out;
}
// enums
template <class T>
std::enable_if_t<std::is_enum<T>::value, facebook::memcache::StyledString>
serialize(const T& value) {
facebook::memcache::StyledString out;
out.append(
folly::to<std::string>(static_cast<std::underlying_type_t<T>>(value)),
format_.dataValueColor);
return out;
}
// optional
template <class T>
facebook::memcache::StyledString serialize(const folly::Optional<T>& opt) {
if (opt.hasValue()) {
return serialize(opt.value());
}
return facebook::memcache::StyledString{};
}
// optional_field_ref
template <class T>
facebook::memcache::StyledString serialize(
apache::thrift::optional_field_ref<T&> opt) {
if (opt.has_value()) {
return serialize(opt.value());
}
return facebook::memcache::StyledString{};
}
// linear containers
template <class T>
std::enable_if_t<
carbon::detail::IsLinearContainer<T>::value,
facebook::memcache::StyledString>
serialize(const T& values) {
facebook::memcache::StyledString out;
if (SerializationTraits<T>::size(values) == 0) {
out.append("[]");
return out;
}
out.pushBack('[');
incrementIndentation();
for (auto it = SerializationTraits<T>::begin(values);
it != SerializationTraits<T>::end(values);
++it) {
out.append(startNewLineAndIndent());
out.append(serialize(*it));
}
decrementIndentation();
out.append(startNewLineAndIndent());
out.pushBack(']');
return out;
}
// kv containers
template <class T>
std::enable_if_t<
carbon::detail::IsKVContainer<T>::value,
facebook::memcache::StyledString>
serialize(const T& values) {
facebook::memcache::StyledString out;
if (SerializationTraits<T>::size(values) == 0) {
out.append("{}");
return out;
}
out.pushBack('{');
incrementIndentation();
for (auto it = SerializationTraits<T>::begin(values);
it != SerializationTraits<T>::end(values);
++it) {
out.append(startNewLineAndIndent());
if (script_) {
out.append(serializeString(folly::to<std::string>(it->first)));
} else {
out.append(serialize(it->first));
}
out.append(": ");
out.append(serialize(it->second));
}
decrementIndentation();
out.append(startNewLineAndIndent());
out.pushBack('}');
return out;
}
// carbon structs and carbon unions
template <class T>
std::enable_if_t<
carbon::IsCarbonStruct<T>::value &&
!carbon::IsThriftWrapperStruct<T>::value,
facebook::memcache::StyledString>
serialize(const T& value) {
facebook::memcache::StyledString out;
McPiperVisitor printer(script_, indent_ + 1);
value.visitFields(printer);
auto content = std::move(printer).styled();
if (!content.empty()) {
out.pushBack('{');
incrementIndentation();
out.append(content);
decrementIndentation();
out.append(startNewLineAndIndent());
out.pushBack('}');
} else {
out.append("{}");
}
return out;
}
// thrift structs
template <class T>
std::enable_if_t<
carbon::IsThriftWrapperStruct<T>::value,
facebook::memcache::StyledString>
serialize(const T& t) {
facebook::memcache::StyledString out;
out.append(serializeString(
apache::thrift::SimpleJSONSerializer::serialize<std::string>(
t.getThriftStruct())));
return out;
}
// user type
template <class T>
std::enable_if_t<
detail::IsUserReadWriteDefined<T>::value,
facebook::memcache::StyledString>
serialize(const T& /* value */) {
facebook::memcache::StyledString out;
out.append(serializeString("<User type>"));
return out;
}
facebook::memcache::StyledString prepareAndRenderHeader(
folly::StringPiece name) {
facebook::memcache::StyledString out;
out.append(startNewLineAndIndent());
if (script_) {
out.append("\"");
out.append(name.str());
out.append("\": ");
} else {
out.append(name.str(), format_.msgAttrColor);
out.append(": ", format_.msgAttrColor);
}
return out;
}
facebook::memcache::StyledString serializeString(
folly::StringPiece str) const {
facebook::memcache::StyledString out;
if (script_) {
out.append("\"");
if (!std::all_of<folly::StringPiece::const_iterator, int(int)>(
str.begin(), str.end(), std::isprint)) {
/* JSON doesn't deal with arbitrary binary data - the input string
must be valid UTF-8. So we just hex encode the whole string. */
out.append(folly::hexlify(str));
} else {
out.append(folly::cEscape<std::string>(str));
}
out.append("\"");
} else {
out.append(folly::backslashify(str), format_.dataValueColor);
}
return out;
}
static std::string serializeIndentation(size_t indentLevel) {
constexpr size_t kIndentationUnit = 2;
return std::string(indentLevel * kIndentationUnit, ' ');
}
facebook::memcache::StyledString startNewLineAndIndent() {
facebook::memcache::StyledString out;
if (script_ && commaNeeded_) {
out.pushBack(',');
}
out.pushBack('\n');
out.append(serializeIndentation(indent_));
commaNeeded_ = true;
return out;
}
void incrementIndentation() {
++indent_;
commaNeeded_ = false;
}
void decrementIndentation() {
--indent_;
commaNeeded_ = false;
}
};
} // namespace detail
template <class R>
facebook::memcache::StyledString
print(const R& req, folly::StringPiece /* name */, bool script) {
detail::McPiperVisitor printer(script, 1 /* indentation */);
req.visitFields(printer);
return std::move(printer).styled();
}
} // namespace carbon