1 /*
2  * Copyright (c) Facebook, Inc. and its affiliates.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <thrift/conformance/cpp2/AnyRegistry.h>
18 
19 #include <folly/portability/GTest.h>
20 
21 #include <thrift/conformance/cpp2/Any.h>
22 #include <thrift/conformance/cpp2/Object.h>
23 #include <thrift/conformance/cpp2/Testing.h>
24 #include <thrift/lib/cpp2/type/UniversalName.h>
25 
26 namespace apache::thrift::conformance {
27 using type::kDisableUniversalHash;
28 
29 namespace {
30 
TEST(AnyRegistryTest,ShortType)31 TEST(AnyRegistryTest, ShortType) {
32   AnyRegistry registry;
33   FollyToStringSerializer<int> intCodec;
34   EXPECT_TRUE(registry.registerType<int>(shortThriftType(), {&intCodec}));
35 
36   // Should use the type uri because it is shorter than the id.
37   Any any = registry.store(1, kFollyToStringProtocol);
38   EXPECT_TRUE(any.type_ref());
39   EXPECT_FALSE(any.typeHashPrefixSha2_256_ref());
40   EXPECT_EQ(registry.load<int>(any), 1);
41   EXPECT_EQ(*registry.tryGetTypeId(any), typeid(int));
42   EXPECT_EQ(registry.getTypeId(any), typeid(int));
43   EXPECT_EQ(registry.getTypeUri(any), shortThriftType().uri_ref().value());
44 }
45 
checkLongType(int typeBytes,int expectedOutBytes)46 void checkLongType(int typeBytes, int expectedOutBytes) {
47   AnyRegistry registry;
48   FollyToStringSerializer<int> intCodec;
49   auto longType = longThriftType();
50   if (typeBytes == kTypeHashBytesNotSpecified) {
51     longType.typeHashBytes_ref().reset();
52   } else {
53     longType.set_typeHashBytes(typeBytes);
54   }
55   EXPECT_TRUE(registry.registerType<int>(longType, {&intCodec}));
56 
57   // Should use the type id because it is shorter than the uri.
58   Any any = registry.store(1, kFollyToStringProtocol);
59   if (expectedOutBytes == kDisableUniversalHash) {
60     EXPECT_FALSE(any.typeHashPrefixSha2_256_ref());
61     EXPECT_TRUE(any.type_ref());
62     EXPECT_EQ(
63         registry.getSerializerByUri(*any.get_type(), intCodec.getProtocol()),
64         &intCodec);
65   } else {
66     EXPECT_FALSE(any.type_ref());
67     ASSERT_TRUE(any.typeHashPrefixSha2_256_ref());
68     EXPECT_EQ(
69         any.typeHashPrefixSha2_256_ref().value_unchecked().size(),
70         expectedOutBytes);
71     EXPECT_EQ(
72         registry.getSerializerByHash(
73             type::UniversalHashAlgorithm::Sha2_256,
74             *any.get_typeHashPrefixSha2_256(),
75             intCodec.getProtocol()),
76         &intCodec);
77   }
78   EXPECT_EQ(registry.load<int>(any), 1);
79   EXPECT_EQ(*registry.tryGetTypeId(any), typeid(int));
80   EXPECT_EQ(registry.getTypeId(any), typeid(int));
81   EXPECT_EQ(registry.getTypeUri(any), longType.uri_ref().value());
82 }
83 
TEST(AnyRegistryTest,LongType)84 TEST(AnyRegistryTest, LongType) {
85   // Disabled is respected.
86   THRIFT_SCOPED_CHECK(
87       checkLongType(kDisableUniversalHash, kDisableUniversalHash));
88 
89   // Unset uses default.
90   THRIFT_SCOPED_CHECK(
91       checkLongType(kTypeHashBytesNotSpecified, kDefaultTypeHashBytes));
92 
93   // Type can increase bytes used.
94   THRIFT_SCOPED_CHECK(checkLongType(24, 24));
95   THRIFT_SCOPED_CHECK(checkLongType(32, 32));
96 }
97 
TEST(AnyRegistryTest,ShortTypeHash)98 TEST(AnyRegistryTest, ShortTypeHash) {
99   AnyRegistry registry;
100   FollyToStringSerializer<int> intCodec;
101   auto longType = longThriftType();
102   EXPECT_TRUE(registry.registerType<int>(longType, {&intCodec}));
103   Any any = registry.store(1, kFollyToStringProtocol);
104   any.set_typeHashPrefixSha2_256(
105       any.typeHashPrefixSha2_256_ref()->substr(0, 7));
106   EXPECT_THROW(registry.load<int>(any), std::out_of_range);
107 }
108 
TEST(AnyRegistryTest,TypeNotFound)109 TEST(AnyRegistryTest, TypeNotFound) {
110   AnyRegistry registry;
111   EXPECT_EQ(registry.getTypeUri<int>(), "");
112   EXPECT_EQ((registry.getSerializer<int, StandardProtocol::Binary>()), nullptr);
113 
114   EXPECT_THROW(registry.store<StandardProtocol::Binary>(1), std::out_of_range);
115 
116   Any any;
117   EXPECT_THROW(registry.load<int>(any), std::invalid_argument);
118   any.set_type(thriftType("int"));
119   EXPECT_THROW(registry.load<int>(any), std::out_of_range);
120   any.set_protocol(StandardProtocol::Binary);
121   EXPECT_THROW(registry.load<int>(any), std::out_of_range);
122 
123   FollyToStringSerializer<int> intCodec;
124   EXPECT_THROW(registry.registerSerializer<int>(&intCodec), std::out_of_range);
125 }
126 
TEST(AnyRegistryTest,ProtocolNotFound)127 TEST(AnyRegistryTest, ProtocolNotFound) {
128   AnyRegistry registry;
129   EXPECT_TRUE(registry.registerType<int>(testThriftType("int")));
130   EXPECT_EQ(registry.getTypeUri<int>(), thriftType("int"));
131   EXPECT_EQ((registry.getSerializer<int, StandardProtocol::Binary>()), nullptr);
132 
133   EXPECT_THROW(registry.store<StandardProtocol::Binary>(1), std::out_of_range);
134 
135   Any any;
136   EXPECT_THROW(registry.load<int>(any), std::invalid_argument);
137   any.set_type(thriftType("int"));
138   EXPECT_THROW(registry.load<int>(any), std::out_of_range);
139   any.set_protocol(StandardProtocol::Binary);
140   EXPECT_THROW(registry.load<int>(any), std::out_of_range);
141 }
142 
TEST(AnyRegistryTest,TypeHashToShort)143 TEST(AnyRegistryTest, TypeHashToShort) {
144   AnyRegistry registry;
145   FollyToStringSerializer<int> intCodec;
146   auto anyType = longThriftType();
147   anyType.set_typeHashBytes(17);
148   EXPECT_TRUE(registry.registerType<int>(anyType, {&intCodec}));
149   Any any = registry.store(1, intCodec.getProtocol());
150   ASSERT_TRUE(any.typeHashPrefixSha2_256_ref());
151   EXPECT_EQ(any.get_typeHashPrefixSha2_256()->size(), 17);
152   EXPECT_EQ(registry.load<int>(any), 1);
153 
154   any.set_typeHashPrefixSha2_256(
155       any.get_typeHashPrefixSha2_256()->substr(0, 8));
156   EXPECT_EQ(registry.load<int>(any), 1);
157 
158   any.set_typeHashPrefixSha2_256(
159       any.get_typeHashPrefixSha2_256()->substr(0, 7));
160   EXPECT_THROW(registry.load<int>(any), std::out_of_range);
161 }
162 
TEST(AnyRegistryTest,Behavior)163 TEST(AnyRegistryTest, Behavior) {
164   AnyRegistry registry;
165   const AnyRegistry& cregistry = registry;
166   EXPECT_EQ(cregistry.getTypeUri(typeid(int)), "");
167   EXPECT_EQ(cregistry.getSerializer<int>(kFollyToStringProtocol), nullptr);
168 
169   FollyToStringSerializer<int> intCodec;
170 
171   // Type must be registered first.
172   EXPECT_THROW(registry.registerSerializer<int>(&intCodec), std::out_of_range);
173 
174   // Empty string is rejected.
175   EXPECT_THROW(
176       registry.registerType<int>(testThriftType("")), std::invalid_argument);
177 
178   EXPECT_TRUE(registry.registerType<int>(testThriftType("int")));
179   EXPECT_EQ(cregistry.getTypeUri(typeid(int)), thriftType("int"));
180   EXPECT_EQ(cregistry.getSerializer<int>(kFollyToStringProtocol), nullptr);
181 
182   // Conflicting and duplicate registrations are rejected.
183   EXPECT_FALSE(registry.registerType<int>(testThriftType("int")));
184   EXPECT_FALSE(registry.registerType<int>(testThriftType("other-int")));
185   EXPECT_FALSE(registry.registerType<double>(testThriftType("int")));
186 
187   EXPECT_TRUE(registry.registerSerializer<int>(&intCodec));
188   EXPECT_EQ(cregistry.getTypeUri<int>(), thriftType("int"));
189   EXPECT_EQ(cregistry.getSerializer<int>(kFollyToStringProtocol), &intCodec);
190 
191   // Duplicate registrations are rejected.
192   EXPECT_FALSE(registry.registerSerializer<int>(&intCodec));
193 
194   Number1Serializer number1Codec;
195   EXPECT_TRUE(registry.registerSerializer<int>(&number1Codec));
196 
197   EXPECT_TRUE(registry.registerType<double>(testThriftType("double")));
198 
199   // nullptr is rejected.
200   EXPECT_FALSE(registry.registerSerializer<double>(nullptr));
201 
202   EXPECT_TRUE(registry.registerSerializer<double>(
203       std::make_unique<FollyToStringSerializer<double>>()));
204 
205   Any value = cregistry.store(3, kFollyToStringProtocol);
206   EXPECT_EQ(value.type_ref().value_or(""), thriftType("int"));
207   EXPECT_FALSE(value.typeHashPrefixSha2_256_ref().has_value());
208   EXPECT_EQ(toString(*value.data_ref()), "3");
209   EXPECT_TRUE(hasProtocol(value, kFollyToStringProtocol));
210   EXPECT_EQ(std::any_cast<int>(cregistry.load(value)), 3);
211   EXPECT_EQ(cregistry.load<int>(value), 3);
212 
213   // Storing an Any does nothing if the protocols match.
214   Any original = value;
215   value = cregistry.store(original, kFollyToStringProtocol);
216   EXPECT_EQ(value.type_ref().value_or(""), thriftType("int"));
217   EXPECT_FALSE(value.typeHashPrefixSha2_256_ref().has_value());
218   EXPECT_EQ(toString(*value.data_ref()), "3");
219   EXPECT_TRUE(hasProtocol(value, kFollyToStringProtocol));
220   value =
221       cregistry.store(std::any(std::move(original)), kFollyToStringProtocol);
222   EXPECT_EQ(value.type_ref().value_or(""), thriftType("int"));
223   EXPECT_FALSE(value.typeHashPrefixSha2_256_ref().has_value());
224   EXPECT_EQ(toString(*value.data_ref()), "3");
225   EXPECT_TRUE(hasProtocol(value, kFollyToStringProtocol));
226 
227   // Storing an Any with a different protocol does a conversion.
228   original = value;
229   value = cregistry.store(original, Number1Serializer::kProtocol);
230   EXPECT_EQ(value.type_ref().value_or(""), thriftType("int"));
231   EXPECT_FALSE(value.typeHashPrefixSha2_256_ref().has_value());
232   EXPECT_EQ(toString(*value.data_ref()), "number 1!!");
233   EXPECT_TRUE(hasProtocol(value, Number1Serializer::kProtocol));
234   value = cregistry.store(
235       std::any(std::move(original)), Number1Serializer::kProtocol);
236   EXPECT_EQ(value.type_ref().value_or(""), thriftType("int"));
237   EXPECT_FALSE(value.typeHashPrefixSha2_256_ref().has_value());
238   EXPECT_EQ(toString(*value.data_ref()), "number 1!!");
239   EXPECT_TRUE(hasProtocol(value, Number1Serializer::kProtocol));
240   EXPECT_EQ(std::any_cast<int>(cregistry.load(value)), 1);
241   EXPECT_EQ(cregistry.load<int>(value), 1);
242 
243   // Storing an unsupported type is an error.
244   EXPECT_THROW(
245       cregistry.store(2.5f, kFollyToStringProtocol), std::out_of_range);
246   EXPECT_THROW(
247       cregistry.store(std::any(2.5f), kFollyToStringProtocol),
248       std::out_of_range);
249 
250   // Storing using an unsupported protocol throws an error
251   EXPECT_THROW(
252       cregistry.store(3, Protocol(StandardProtocol::Binary)),
253       std::out_of_range);
254 
255   // Loading an empty Any value throws an error.
256   value = {};
257   EXPECT_FALSE(value.type_ref().has_value());
258   EXPECT_FALSE(value.typeHashPrefixSha2_256_ref().has_value());
259   EXPECT_EQ(toString(*value.data_ref()), "");
260   EXPECT_TRUE(
261       hasProtocol(value, getStandardProtocol<StandardProtocol::Compact>()));
262   EXPECT_THROW(cregistry.load(value), std::invalid_argument);
263   EXPECT_THROW(cregistry.load<float>(value), std::invalid_argument);
264   value.set_type("foo");
265   EXPECT_THROW(cregistry.load(value), std::out_of_range);
266   EXPECT_THROW(cregistry.load<float>(value), std::out_of_range);
267 
268   value = cregistry.store(2.5, kFollyToStringProtocol);
269   EXPECT_EQ(*value.type_ref(), thriftType("double"));
270   EXPECT_FALSE(value.typeHashPrefixSha2_256_ref().has_value());
271   EXPECT_EQ(toString(*value.data_ref()), "2.5");
272   EXPECT_TRUE(hasProtocol(value, kFollyToStringProtocol));
273   EXPECT_EQ(std::any_cast<double>(cregistry.load(value)), 2.5);
274   EXPECT_EQ(cregistry.load<double>(value), 2.5);
275   EXPECT_THROW(cregistry.load<int>(value), std::bad_any_cast);
276 
277   EXPECT_EQ(
278       cregistry.debugString(),
279       "AnyRegistry[\n"
280       "  facebook.com/thrift/double (319b4d9a143e15bbf818e1fe4556f46a578cb58acda43d5f40cf9a22886dc9d8):\n"
281       "    facebook.com/thrift/FollyToString,\n"
282       "  facebook.com/thrift/int (3fc51d1641587f7d26c7ab0dcb97b69f6ea48f9ea15ea626ec34e6069fc4b136):\n"
283       "    facebook.com/thrift/FollyToString,\n"
284       "    facebook.com/thrift/Number1,\n"
285       "]");
286 }
287 
TEST(AnyRegistryTest,Aliases)288 TEST(AnyRegistryTest, Aliases) {
289   AnyRegistry registry;
290   const AnyRegistry& cregistry = registry;
291   FollyToStringSerializer<int> intCodec;
292   Number1Serializer oneCodec;
293 
294   EXPECT_TRUE(registry.registerType<int>(
295       testThriftType({"int", "Int", "Integer"}), {&oneCodec, &intCodec}));
296   EXPECT_EQ(registry.getTypeUri<int>(), thriftType("int"));
297   EXPECT_EQ(
298       registry.getSerializerByUri(thriftType("int"), oneCodec.getProtocol()),
299       &oneCodec);
300   EXPECT_EQ(
301       registry.getSerializerByUri(thriftType("Int"), oneCodec.getProtocol()),
302       &oneCodec);
303   EXPECT_EQ(
304       registry.getSerializerByUri(
305           thriftType("Integer"), oneCodec.getProtocol()),
306       &oneCodec);
307 
308   auto any = cregistry.store(1, kFollyToStringProtocol);
309   // Stored under the main type uri.
310   EXPECT_EQ(any.type_ref().value_or(""), thriftType("int"));
311   EXPECT_EQ(cregistry.load<int>(any), 1);
312 
313   any.set_type(thriftType("Int"));
314   EXPECT_EQ(cregistry.load<int>(any), 1);
315 
316   any.set_type(thriftType("Integer"));
317   EXPECT_EQ(cregistry.load<int>(any), 1);
318 
319   any.set_type(thriftType("Unknown"));
320   EXPECT_THROW(cregistry.load<int>(any), std::out_of_range);
321 }
322 
TEST(AnyRegistryTest,ForwardCompat_Protocol)323 TEST(AnyRegistryTest, ForwardCompat_Protocol) {
324   AnyRegistry registry;
325   Protocol invalidProtocol("invalid");
326   EXPECT_THROW(validateProtocol(invalidProtocol), std::invalid_argument);
327   // Lookup does not throw.
328   EXPECT_EQ(registry.getSerializer<int>(invalidProtocol), nullptr);
329 }
330 
TEST(AnyRegistryTest,ForwardCompat_Any)331 TEST(AnyRegistryTest, ForwardCompat_Any) {
332   AnyRegistry registry;
333   const AnyRegistry& cregistry = registry;
334   FollyToStringSerializer<int> intCodec;
335 
336   EXPECT_TRUE(registry.registerType<int>(testThriftType("int")));
337   EXPECT_TRUE(registry.registerSerializer<int>(&intCodec));
338 
339   Any any = cregistry.store(1, kFollyToStringProtocol);
340 
341   validateAny(any);
342   any.type_ref() = "invalid";
343   EXPECT_THROW(validateAny(any), std::invalid_argument);
344   // Load does not throw std::invalid_argument.
345   EXPECT_THROW(cregistry.load(any), std::out_of_range);
346 }
347 
TEST(AnyRegistryTest,StdProtocol)348 TEST(AnyRegistryTest, StdProtocol) {
349   AnyRegistry registry;
350   const AnyRegistry& cregistry = registry;
351   registry
352       .registerType<Value, StandardProtocol::Binary, StandardProtocol::Compact>(
353           testThriftType("Value"));
354 
355   auto value = asValueStruct<type::i32_t>(1);
356   auto any = cregistry.store<StandardProtocol::Compact>(value);
357   ASSERT_TRUE(any.type_ref());
358   EXPECT_EQ(any.type_ref().value_unchecked(), thriftType("Value"));
359   EXPECT_EQ(cregistry.load<Value>(any), value);
360 }
361 
TEST(AnyRegistryTest,Generated)362 TEST(AnyRegistryTest, Generated) {
363   // Double register fails with a runtime error.
364   EXPECT_THROW(detail::registerGeneratedStruct<Value>(), std::runtime_error);
365 
366   auto value = asValueStruct<type::i32_t>(1);
367   auto any = AnyRegistry::generated().store<StandardProtocol::Compact>(value);
368   EXPECT_EQ(AnyRegistry::generated().load<Value>(any), value);
369   EXPECT_THROW(
370       AnyRegistry::generated().store<StandardProtocol::Json>(value),
371       std::out_of_range);
372 }
373 
TEST(AnyRegistryTest,ForceRegister)374 TEST(AnyRegistryTest, ForceRegister) {
375   AnyRegistry registry;
376   EXPECT_TRUE(registry.forceRegisterType(typeid(Value), "va"));
377   EXPECT_TRUE(registry.registerSerializer<Value>(
378       &getAnyStandardSerializer<Value, StandardProtocol::Compact>()));
379   Value expected;
380   expected.set_boolValue(true);
381   Any any = registry.store(
382       expected, getStandardProtocol<StandardProtocol::Compact>());
383   EXPECT_EQ(any.type_ref(), "va");
384   Value actual = registry.load<Value>(any);
385   EXPECT_EQ(actual, expected);
386 }
387 
388 } // namespace
389 } // namespace apache::thrift::conformance
390