object-introspection/include/oi/exporters/Json.h
Jake Hillion 40af807d8b tbv2: support capture-thrift-isset
Support the capture-thrift-isset feature with TreeBuilder-v2. Fairly minor
changes here except the type of the Enum in a template parameter now matters.

We follow the previous behaviour of capturing a value for each field in a
struct that has an `isset_bitset`. This value is a VarInt captured before the
C++ contents of the member. It has 3 values: 0 (not set), 1 (set), and 2
(unavailable). These are handled by the processor and represented in the output
as `false`, `true`, and `std::nullopt_t` respectively.

Changes:
- Add a simple Thrift isset processor before any fields that have Thrift isset.
- Store the fully qualified names of enum types in DrgnParser - it already
  worked out this information anyway for naming the values and this is
  consistent with classes.
- Forward all enum template parameters under their input name under the
  assumption that they will all be policy type things like `IssetBitsetOption`.
  This could turn out to be wrong.

Test plan:
- CI (doesn't test thrift changes but covers other regressions)
- Updated Thrift enum tests for new format.
- `FILTER='OilIntegration.*' make test` - Thrift tests failed before, succeed
  after.
2024-01-16 19:09:46 +00:00

202 lines
6.4 KiB
C++

/*
* 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 INCLUDED_OI_EXPORTERS_JSON_H
#define INCLUDED_OI_EXPORTERS_JSON_H 1
#include <oi/IntrospectionResult.h>
#include <oi/result/SizedResult.h>
#include <ostream>
#include <string_view>
namespace oi::exporters {
class Json {
public:
Json(std::ostream& out);
template <typename Res>
void print(const Res& r) {
auto begin = r.begin();
auto end = r.end();
return print(begin, end);
}
template <typename It>
void print(It& it, const It& end);
void setPretty(bool pretty) {
pretty_ = pretty;
}
private:
std::string_view tab() const;
std::string_view space() const;
std::string_view endl() const;
static std::string makeIndent(size_t depth);
void printStringField(std::string_view name,
std::string_view value,
std::string_view indent);
void printBoolField(std::string_view name,
bool value,
std::string_view indent);
void printUnsignedField(std::string_view name,
uint64_t value,
std::string_view indent);
void printPointerField(std::string_view name,
uintptr_t value,
std::string_view indent);
template <typename Rng>
void printListField(std::string_view name,
const Rng& range,
std::string_view indent);
void printFields(const result::Element&, std::string_view indent);
template <typename El>
void printFields(const result::SizedElement<El>&, std::string_view indent);
bool pretty_ = false;
std::ostream& out_;
};
inline Json::Json(std::ostream& out) : out_(out) {
}
inline std::string_view Json::tab() const {
return pretty_ ? " " : "";
}
inline std::string_view Json::space() const {
return pretty_ ? " " : "";
}
inline std::string_view Json::endl() const {
return pretty_ ? "\n" : "";
}
inline std::string Json::makeIndent(size_t depth) {
depth = std::max(depth, 1UL);
return std::string((depth - 1) * 4, ' ');
}
inline void Json::printStringField(std::string_view name,
std::string_view value,
std::string_view indent) {
out_ << tab() << '"' << name << '"' << ':' << space() << "\"" << value
<< "\"," << endl() << indent;
}
inline void Json::printBoolField(std::string_view name,
bool value,
std::string_view indent) {
out_ << tab() << '"' << name << "\":" << space() << (value ? "true" : "false")
<< ',' << endl() << indent;
}
inline void Json::printUnsignedField(std::string_view name,
uint64_t value,
std::string_view indent) {
out_ << tab() << '"' << name << "\":" << space() << value << ',' << endl()
<< indent;
}
inline void Json::printPointerField(std::string_view name,
uintptr_t value,
std::string_view indent) {
out_ << tab() << '"' << name << "\":" << space() << "\"0x" << std::hex
<< value << std::dec << "\"," << endl() << indent;
}
template <typename Rng>
void Json::printListField(std::string_view name,
const Rng& range,
std::string_view indent) {
out_ << tab() << '"' << name << '"' << ':' << space() << '[';
bool first = true;
for (const auto& el : range) {
if (!std::exchange(first, false))
out_ << ',' << space();
out_ << '"' << el << '"';
}
out_ << "]," << endl() << indent;
}
template <typename El>
void Json::printFields(const result::SizedElement<El>& el,
std::string_view indent) {
printUnsignedField("size", el.size, indent);
printFields(el.inner(), indent);
}
inline void Json::printFields(const result::Element& el,
std::string_view indent) {
printStringField("name", el.name, indent);
printListField("typePath", el.type_path, indent);
printListField("typeNames", el.type_names, indent);
printUnsignedField("staticSize", el.static_size, indent);
printUnsignedField("exclusiveSize", el.exclusive_size, indent);
if (el.pointer.has_value())
printUnsignedField("pointer", *el.pointer, indent);
if (const auto* s = std::get_if<result::Element::Scalar>(&el.data)) {
printUnsignedField("data", s->n, indent);
} else if (const auto* p = std::get_if<result::Element::Pointer>(&el.data)) {
printPointerField("data", p->p, indent);
} else if (const auto* str = std::get_if<std::string>(&el.data)) {
printStringField("data", *str, indent);
}
if (el.container_stats.has_value()) {
printUnsignedField("length", el.container_stats->length, indent);
printUnsignedField("capacity", el.container_stats->capacity, indent);
}
if (el.is_set_stats.has_value())
printBoolField("is_set", el.is_set_stats->is_set, indent);
printBoolField("is_primitive", el.is_primitive, indent);
}
template <typename It>
void Json::print(It& it, const It& end) {
const auto depth = it->type_path.size();
const auto thisIndent = pretty_ ? makeIndent(depth) : "";
const auto lastIndent = pretty_ ? makeIndent(depth - 1) : "";
out_ << '[' << endl() << thisIndent;
bool first = true;
while (it != end && it->type_path.size() >= depth) {
if (!std::exchange(first, false))
out_ << ',' << endl() << thisIndent;
out_ << '{' << endl() << thisIndent;
printFields(*it, thisIndent);
out_ << tab() << "\"members\":" << space();
if (++it != end && it->type_path.size() > depth) {
print(it, end);
} else {
out_ << "[]" << endl();
}
out_ << thisIndent << "}";
}
if (depth == 1) {
out_ << endl() << ']' << endl();
} else {
out_ << endl() << lastIndent << tab() << ']' << endl();
}
}
} // namespace oi::exporters
#endif