Skip to content

Commit

Permalink
extended path visitor: Map
Browse files Browse the repository at this point in the history
Summary: implement map type for extended path visitor.

Differential Revision:
D65767417

Privacy Context Container: L1125642

fbshipit-source-id: 2bf125b8cbdc05b981f9c14df82337388b3ad52d
  • Loading branch information
Wei-Cheng Lin authored and facebook-github-bot committed Nov 16, 2024
1 parent 50d09a8 commit 2e4b4a0
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 12 deletions.
99 changes: 94 additions & 5 deletions fboss/thrift_cow/visitors/ExtendedPathVisitor.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#include <type_traits>

#include <fboss/thrift_cow/nodes/NodeUtils.h>
#include <fboss/thrift_cow/nodes/Serializer.h>
#include <fboss/thrift_cow/visitors/VisitorUtils.h>
#include <re2/re2.h>
#include <thrift/lib/cpp/util/EnumUtils.h>
Expand Down Expand Up @@ -156,7 +158,20 @@ struct ExtendedPathVisitor<apache::thrift::type_class::set<ValueTypeClass>> {
Func&& f)
requires(std::is_same_v<typename Node::CowType, HybridNodeType>)
{
// TODO: implement specialization for HybridNode
throw std::runtime_error("Set: not implemented yet");
}

template <typename Node, typename Func>
static void visit(
std::vector<std::string>& path,
Node& node,
epv_detail::ExtPathIter begin,
epv_detail::ExtPathIter end,
const ExtPathVisitorOptions& options,
Func&& f)
requires(!is_cow_type_v<Node> && !is_field_type_v<Node>)
{
throw std::runtime_error("Set: not implemented yet");
}

template <typename Fields, typename Func>
Expand Down Expand Up @@ -215,7 +230,20 @@ struct ExtendedPathVisitor<apache::thrift::type_class::list<ValueTypeClass>> {
Func&& f)
requires(std::is_same_v<typename Node::CowType, HybridNodeType>)
{
// TODO: implement specialization for HybridNode
throw std::runtime_error("List: not implemented yet");
}

template <typename Node, typename Func>
static void visit(
std::vector<std::string>& path,
Node& node,
epv_detail::ExtPathIter begin,
epv_detail::ExtPathIter end,
const ExtPathVisitorOptions& options,
Func&& f)
requires(!is_cow_type_v<Node> && !is_field_type_v<Node>)
{
throw std::runtime_error("List: not implemented yet");
}

template <typename Fields, typename Func>
Expand Down Expand Up @@ -271,6 +299,30 @@ struct ExtendedPathVisitor<
path, node, begin, end, options, std::forward<Func>(f));
}

template <typename Node, typename Func>
static void visit(
std::vector<std::string>& path,
Node& node,
epv_detail::ExtPathIter begin,
epv_detail::ExtPathIter end,
const ExtPathVisitorOptions& options,
Func&& f)
requires(!is_cow_type_v<Node> && !is_field_type_v<Node>)
{
const auto& elem = *begin++;
for (auto& [key, val] : node) {
auto matching = epv_detail::matchingToken<KeyTypeClass>(key, elem);
if (matching) {
path.push_back(*matching);

ExtendedPathVisitor<MappedTypeClass>::visit(
path, val, begin, end, options, std::forward<Func>(f));

path.pop_back();
}
}
}

template <typename Node, typename Func>
static void visit(
std::vector<std::string>& path,
Expand All @@ -281,7 +333,18 @@ struct ExtendedPathVisitor<
Func&& f)
requires(std::is_same_v<typename Node::CowType, HybridNodeType>)
{
// TODO: implement specialization for HybridNode
const auto& elem = *begin++;
for (auto& [key, val] : node.ref()) {
auto matching = epv_detail::matchingToken<KeyTypeClass>(key, elem);
if (matching) {
path.push_back(*matching);

ExtendedPathVisitor<MappedTypeClass>::visit(
path, val, begin, end, options, std::forward<Func>(f));

path.pop_back();
}
}
}

template <typename Fields, typename Func>
Expand Down Expand Up @@ -348,7 +411,20 @@ struct ExtendedPathVisitor<apache::thrift::type_class::variant> {
Func&& f)
requires(std::is_same_v<typename Node::CowType, HybridNodeType>)
{
// TODO: implement specialization for HybridNode
throw std::runtime_error("Variant: not implemented yet");
}

template <typename Node, typename Func>
static void visit(
std::vector<std::string>& path,
Node& node,
epv_detail::ExtPathIter begin,
epv_detail::ExtPathIter end,
const ExtPathVisitorOptions& options,
Func&& f)
requires(!is_cow_type_v<Node> && !is_field_type_v<Node>)
{
throw std::runtime_error("Variant: not implemented yet");
}

template <typename Fields, typename Func>
Expand Down Expand Up @@ -437,6 +513,19 @@ struct ExtendedPathVisitor<apache::thrift::type_class::structure> {
path, node, begin, end, options, std::forward<Func>(f));
}

template <typename Node, typename Func>
static void visit(
std::vector<std::string>& path,
Node& node,
epv_detail::ExtPathIter begin,
epv_detail::ExtPathIter end,
const ExtPathVisitorOptions& options,
Func&& f)
requires(!is_cow_type_v<Node> && !is_field_type_v<Node>)
{
throw std::runtime_error("Sructure: not implemented yet");
}

template <typename Node, typename Func>
static void visit(
std::vector<std::string>& path,
Expand All @@ -447,7 +536,7 @@ struct ExtendedPathVisitor<apache::thrift::type_class::structure> {
Func&& f)
requires(std::is_same_v<typename Node::CowType, HybridNodeType>)
{
// TODO: implement specialization for HybridNode
throw std::runtime_error("Sructure: not implemented yet");
}

template <typename Fields, typename Func>
Expand Down
109 changes: 102 additions & 7 deletions fboss/thrift_cow/visitors/tests/ExtendedPathVisitorTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ TestStruct createTestStruct() {
dynamic::object("3", dynamic::object("min", 100)("max", 200)))(
"mapOfStringToI32", dynamic::object)(
"listOfPrimitives", dynamic::array())("setOfI32", dynamic::array())(
"mapOfI32ToListOfStructs", dynamic::object());
"mapOfI32ToListOfStructs", dynamic::object())(
"hybridMapOfMap", dynamic::object());

for (int i = 0; i <= 20; ++i) {
testDyn["mapOfStringToI32"][fmt::format("test{}", i)] = i;
Expand All @@ -43,6 +44,9 @@ TestStruct createTestStruct() {
dynamic::array(
dynamic::object("min", 1)("max", 2),
dynamic::object("min", 3)("max", 4));
dynamic innerMap = dynamic::object();
innerMap[i * 10] = i * 100;
testDyn["hybridMapOfMap"][i] = std::move(innerMap);
}

return facebook::thrift::from_dynamic<TestStruct>(
Expand Down Expand Up @@ -130,13 +134,44 @@ TEST(ExtendedPathVisitorTests, AccessOptional) {
EXPECT_TRUE(got.empty());
}

TEST(ExtendedPathVisitorTests, AccessRegexMap) {
template <bool EnableHybridStorage>
struct TestParams {
static constexpr auto hybridStorage = EnableHybridStorage;
};

using StorageTestTypes = ::testing::Types<TestParams<false>, TestParams<true>>;

template <typename TestParams>
class ExtendedPathVisitorTests : public ::testing::Test {
public:
auto initNode(auto val) {
using RootType = std::remove_cvref_t<decltype(val)>;
return std::make_shared<ThriftStructNode<
RootType,
ThriftStructResolver<RootType, TestParams::hybridStorage>,
TestParams::hybridStorage>>(val);
}
bool isHybridStorage() {
return TestParams::hybridStorage;
}
};

TYPED_TEST_SUITE(ExtendedPathVisitorTests, StorageTestTypes);

TYPED_TEST(ExtendedPathVisitorTests, AccessRegexMap) {
auto structA = createTestStruct();
auto nodeA = std::make_shared<ThriftStructNode<TestStruct>>(structA);
auto nodeA = this->initNode(structA);

std::set<std::pair<std::vector<std::string>, folly::dynamic>> visited;
auto processPath = [&visited](auto&& path, auto&& node) {
visited.emplace(std::make_pair(path, node.toFollyDynamic()));
if constexpr (is_cow_type_v<decltype(node)>) {
visited.emplace(std::make_pair(path, node.toFollyDynamic()));
} else {
folly::dynamic out;
facebook::thrift::to_dynamic(
out, node, facebook::thrift::dynamic_format::JSON_1);
visited.emplace(std::make_pair(path, out));
}
};

auto path = ext_path_builder::raw("mapOfStringToI32").regex("test1.*").get();
Expand All @@ -159,6 +194,11 @@ TEST(ExtendedPathVisitorTests, AccessRegexMap) {
*nodeA, path.path()->begin(), path.path()->end(), options, processPath);
EXPECT_THAT(visited, ::testing::ContainerEq(expected));

if (this->isHybridStorage()) {
// TODO: list and struct not implemented yet for hybrid storage
return;
}

auto path2 = ext_path_builder::raw("mapOfI32ToListOfStructs")
.regex("1.*")
.regex(".*")
Expand Down Expand Up @@ -195,13 +235,20 @@ TEST(ExtendedPathVisitorTests, AccessRegexMap) {
EXPECT_THAT(visited, ::testing::ContainerEq(expected2));
}

TEST(ExtendedPathVisitorTests, AccessAnyMap) {
TYPED_TEST(ExtendedPathVisitorTests, AccessAnyMap) {
auto structA = createTestStruct();
auto nodeA = std::make_shared<ThriftStructNode<TestStruct>>(structA);
auto nodeA = this->initNode(structA);

std::set<std::pair<std::vector<std::string>, folly::dynamic>> visited;
auto processPath = [&visited](auto&& path, auto&& node) {
visited.emplace(std::make_pair(path, node.toFollyDynamic()));
if constexpr (is_cow_type_v<decltype(node)>) {
visited.emplace(std::make_pair(path, node.toFollyDynamic()));
} else {
folly::dynamic out;
facebook::thrift::to_dynamic(
out, node, facebook::thrift::dynamic_format::JSON_1);
visited.emplace(std::make_pair(path, out));
}
};

auto path = ext_path_builder::raw("mapOfStringToI32").any().get();
Expand Down Expand Up @@ -235,6 +282,54 @@ TEST(ExtendedPathVisitorTests, AccessAnyMap) {
EXPECT_THAT(visited, ::testing::ContainerEq(expected));
}

TYPED_TEST(ExtendedPathVisitorTests, AccessDeepMap) {
auto structA = createTestStruct();
auto nodeA = this->initNode(structA);

std::set<std::pair<std::vector<std::string>, folly::dynamic>> visited;
auto processPath = [&visited](auto&& path, auto&& node) {
if constexpr (is_cow_type_v<decltype(node)>) {
visited.emplace(std::make_pair(path, node.toFollyDynamic()));
} else {
folly::dynamic out;
facebook::thrift::to_dynamic(
out, node, facebook::thrift::dynamic_format::JSON_1);
visited.emplace(std::make_pair(path, out));
}
};

auto path = ext_path_builder::raw("hybridMapOfMap").any().any().get();
std::set<std::pair<std::vector<std::string>, folly::dynamic>> expected = {
{{"hybridMapOfMap", "0", "0"}, 0},
{{"hybridMapOfMap", "1", "10"}, 100},
{{"hybridMapOfMap", "2", "20"}, 200},
{{"hybridMapOfMap", "3", "30"}, 300},
{{"hybridMapOfMap", "4", "40"}, 400},
{{"hybridMapOfMap", "5", "50"}, 500},
{{"hybridMapOfMap", "6", "60"}, 600},
{{"hybridMapOfMap", "7", "70"}, 700},
{{"hybridMapOfMap", "8", "80"}, 800},
{{"hybridMapOfMap", "9", "90"}, 900},
{{"hybridMapOfMap", "10", "100"}, 1000},
{{"hybridMapOfMap", "11", "110"}, 1100},
{{"hybridMapOfMap", "12", "120"}, 1200},
{{"hybridMapOfMap", "13", "130"}, 1300},
{{"hybridMapOfMap", "14", "140"}, 1400},
{{"hybridMapOfMap", "15", "150"}, 1500},
{{"hybridMapOfMap", "16", "160"}, 1600},
{{"hybridMapOfMap", "17", "170"}, 1700},
{{"hybridMapOfMap", "18", "180"}, 1800},
{{"hybridMapOfMap", "19", "190"}, 1900},
{{"hybridMapOfMap", "20", "200"}, 2000},

};

ExtPathVisitorOptions options;
RootExtendedPathVisitor::visit(
*nodeA, path.path()->begin(), path.path()->end(), options, processPath);
EXPECT_THAT(visited, ::testing::ContainerEq(expected));
}

TEST(ExtendedPathVisitorTests, AccessRegexList) {
auto structA = createTestStruct();
auto nodeA = std::make_shared<ThriftStructNode<TestStruct>>(structA);
Expand Down

0 comments on commit 2e4b4a0

Please sign in to comment.