Skip to content

Commit bf4885a

Browse files
Mizuchimeta-codesync[bot]
authored andcommitted
Add helper function to compare two structured annotation list whether they are equal
Summary: ``` Anno1{set_field = [1, 2, 3]} Anno2 struct Foo {} Anno2 Anno1{set_field = [3, 2, 1]} struct Bar {} ``` `Foo` and `Bar` have the same list of structured annotations that we cannot distinguish in `schema.thrift`. However, we can't compare them directly (due to ordering of annotations, as well as ordering in `set`/`map` field). We need a helper function to do this. Reviewed By: praihan Differential Revision: D85978390 fbshipit-source-id: af2107026970081ca82b89aaab33c9f831370406
1 parent 23a7cec commit bf4885a

File tree

3 files changed

+170
-0
lines changed

3 files changed

+170
-0
lines changed

third-party/thrift/src/thrift/lib/cpp2/gen/module_metadata_cpp.cpp

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,4 +140,75 @@ metadata::ThriftService genServiceMetadata(
140140
return ret;
141141
}
142142

143+
namespace {
144+
// In ThriftConstValue, `set`/`map` are stored as list.
145+
// This function sort `set`/`map` so that we can do equality comparison.
146+
void normalizeThriftConstValue(
147+
ThriftConstValue& t, const syntax_graph::TypeRef& type);
148+
149+
void normalizeThriftConstStruct(
150+
ThriftConstStruct& t, const syntax_graph::TypeRef& type) {
151+
std::unordered_map<std::string, syntax_graph::TypeRef> fieldType;
152+
for (auto& field : type.asStructured().fields()) {
153+
fieldType.emplace(field.name(), field.type());
154+
}
155+
for (auto& [name, value] : *t.fields()) {
156+
normalizeThriftConstValue(value, fieldType.at(name));
157+
}
158+
}
159+
void normalizeThriftConstValue(
160+
ThriftConstValue& t, const syntax_graph::TypeRef& type) {
161+
if (type.isList()) {
162+
for (auto& i : *t.cv_list()) {
163+
normalizeThriftConstValue(i, type.asList().elementType());
164+
}
165+
}
166+
167+
if (type.isSet()) {
168+
for (auto& i : *t.cv_list()) {
169+
normalizeThriftConstValue(i, type.asSet().elementType());
170+
}
171+
std::sort(t.cv_list()->begin(), t.cv_list()->end());
172+
}
173+
174+
if (type.isMap()) {
175+
auto keyType = type.asMap().keyType();
176+
auto valueType = type.asMap().valueType();
177+
for (auto& i : *t.cv_map()) {
178+
normalizeThriftConstValue(*i.key(), keyType);
179+
normalizeThriftConstValue(*i.value(), valueType);
180+
}
181+
std::sort(t.cv_map()->begin(), t.cv_map()->end());
182+
}
183+
184+
if (type.isStructured()) {
185+
normalizeThriftConstStruct(*t.cv_struct(), type);
186+
}
187+
}
188+
189+
// This function will sort structured annotations, as well as sorting
190+
// `set`/`map` inside annotations so that we can do equality comparison.
191+
std::vector<ThriftConstStruct> normalizeStructuredAnnotations(
192+
std::vector<ThriftConstStruct> annotations,
193+
const std::unordered_map<std::string, syntax_graph::TypeRef>& nameToType) {
194+
for (auto& i : annotations) {
195+
normalizeThriftConstStruct(i, nameToType.at(*i.type()->name()));
196+
}
197+
std::sort(annotations.begin(), annotations.end());
198+
return annotations;
199+
}
200+
} // namespace
201+
202+
bool structuredAnnotationsEquality(
203+
std::vector<ThriftConstStruct> lhsAnnotations,
204+
std::vector<ThriftConstStruct> rhsAnnotations,
205+
const std::vector<syntax_graph::TypeRef>& annotationTypes) {
206+
std::unordered_map<std::string, syntax_graph::TypeRef> nameToType;
207+
for (const auto& i : annotationTypes) {
208+
nameToType.emplace(getName(i.asStructured()), i);
209+
}
210+
return normalizeStructuredAnnotations(
211+
std::move(lhsAnnotations), nameToType) ==
212+
normalizeStructuredAnnotations(std::move(rhsAnnotations), nameToType);
213+
}
143214
} // namespace apache::thrift::detail::md

third-party/thrift/src/thrift/lib/cpp2/gen/module_metadata_cpp.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,4 +310,12 @@ metadata::ThriftService genServiceMetadata() {
310310
return genServiceMetadata(getDefinitionNodeWithLock<Tag>().asService());
311311
}
312312

313+
// A Helper function to check whether two list of structured annotations have
314+
// same data. We can not rely on `std::vector::operator==` directly since
315+
// Annotations' order, as well as the order of `set`/`map` in the annotation
316+
// fields might not be preserved.
317+
bool structuredAnnotationsEquality(
318+
std::vector<ThriftConstStruct> lhsAnnotations,
319+
std::vector<ThriftConstStruct> rhsAnnotations,
320+
const std::vector<syntax_graph::TypeRef>& annotationTypes);
313321
} // namespace apache::thrift::detail::md

third-party/thrift/src/thrift/lib/cpp2/test/metadata/annotations_test.cpp

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
*/
1616

1717
#include <gtest/gtest.h>
18+
#include <thrift/lib/cpp2/gen/module_metadata_cpp.h>
19+
#include <thrift/lib/cpp2/runtime/SchemaRegistry.h>
1820
#include <thrift/lib/cpp2/test/metadata/gen-cpp2/annotations_metadata.h>
21+
#include <thrift/lib/cpp2/util/DebugTree.h>
1922

2023
namespace apache::thrift::test {
2124

@@ -85,4 +88,92 @@ TEST(Annotations, Enum) {
8588
EXPECT_EQ(md.enums()["annotations.TestEnum"], expectedEnum());
8689
}
8790

91+
TEST(Annotations, Normalization) {
92+
auto annotationType =
93+
syntax_graph::TypeRef::of(SchemaRegistry::get().getNode<Annotation>());
94+
auto fooType =
95+
syntax_graph::TypeRef::of(SchemaRegistry::get().getNode<Foo>());
96+
{
97+
std::vector<metadata::ThriftConstStruct> lhs;
98+
lhs.emplace_back().type()->name() = "annotations.Annotation";
99+
lhs.emplace_back().type()->name() = "annotations.Foo";
100+
std::vector<metadata::ThriftConstStruct> rhs = {lhs[1], lhs[0]};
101+
std::vector<syntax_graph::TypeRef> types = {annotationType, fooType};
102+
103+
EXPECT_TRUE(detail::md::structuredAnnotationsEquality(lhs, rhs, types));
104+
}
105+
{
106+
metadata::ThriftConstStruct lhs;
107+
lhs.type()->name() = "annotations.Annotation";
108+
lhs.fields()["boolField"].cv_bool() = false;
109+
110+
metadata::ThriftConstStruct rhs;
111+
rhs.type()->name() = "annotations.Annotation";
112+
rhs.fields()["boolField"].cv_bool() = true;
113+
114+
EXPECT_FALSE(
115+
detail::md::structuredAnnotationsEquality(
116+
{lhs}, {rhs}, {annotationType}));
117+
}
118+
{
119+
metadata::ThriftConstStruct lhs;
120+
lhs.type()->name() = "annotations.Annotation";
121+
lhs.fields()["listField"].cv_list().emplace();
122+
lhs.fields()["listField"].cv_list()->emplace_back().cv_integer() = 2;
123+
lhs.fields()["listField"].cv_list()->emplace_back().cv_integer() = 1;
124+
125+
metadata::ThriftConstStruct rhs;
126+
rhs.type()->name() = "annotations.Annotation";
127+
rhs.fields()["listField"].cv_list().emplace();
128+
rhs.fields()["listField"].cv_list()->emplace_back().cv_integer() = 1;
129+
rhs.fields()["listField"].cv_list()->emplace_back().cv_integer() = 2;
130+
131+
EXPECT_FALSE(
132+
detail::md::structuredAnnotationsEquality(
133+
{lhs}, {rhs}, {annotationType}));
134+
}
135+
{
136+
metadata::ThriftConstStruct lhs;
137+
lhs.type()->name() = "annotations.Annotation";
138+
lhs.fields()["setField"].cv_list().emplace();
139+
lhs.fields()["setField"].cv_list()->emplace_back().cv_integer() = 2;
140+
lhs.fields()["setField"].cv_list()->emplace_back().cv_integer() = 1;
141+
142+
metadata::ThriftConstStruct rhs;
143+
rhs.type()->name() = "annotations.Annotation";
144+
rhs.fields()["setField"].cv_list().emplace();
145+
rhs.fields()["setField"].cv_list()->emplace_back().cv_integer() = 1;
146+
rhs.fields()["setField"].cv_list()->emplace_back().cv_integer() = 2;
147+
148+
EXPECT_TRUE(
149+
detail::md::structuredAnnotationsEquality(
150+
{lhs}, {rhs}, {annotationType}));
151+
}
152+
{
153+
metadata::ThriftConstStruct lhs;
154+
lhs.type()->name() = "annotations.Annotation";
155+
lhs.fields()["mapField"].cv_map().emplace();
156+
lhs.fields()["mapField"].cv_map()->emplace_back();
157+
lhs.fields()["mapField"].cv_map()->back().key()->cv_integer() = 2;
158+
lhs.fields()["mapField"].cv_map()->back().value()->cv_string() = "20";
159+
lhs.fields()["mapField"].cv_map()->emplace_back();
160+
lhs.fields()["mapField"].cv_map()->back().key()->cv_integer() = 1;
161+
lhs.fields()["mapField"].cv_map()->back().value()->cv_string() = "10";
162+
163+
metadata::ThriftConstStruct rhs;
164+
rhs.type()->name() = "annotations.Annotation";
165+
rhs.fields()["mapField"].cv_map().emplace();
166+
rhs.fields()["mapField"].cv_map()->emplace_back();
167+
rhs.fields()["mapField"].cv_map()->back().key()->cv_integer() = 1;
168+
rhs.fields()["mapField"].cv_map()->back().value()->cv_string() = "10";
169+
rhs.fields()["mapField"].cv_map()->emplace_back();
170+
rhs.fields()["mapField"].cv_map()->back().key()->cv_integer() = 2;
171+
rhs.fields()["mapField"].cv_map()->back().value()->cv_string() = "20";
172+
173+
EXPECT_TRUE(
174+
detail::md::structuredAnnotationsEquality(
175+
{lhs}, {rhs}, {annotationType}));
176+
}
177+
}
178+
88179
} // namespace apache::thrift::test

0 commit comments

Comments
 (0)