/* * 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. */ #include #include #include #include #include #include namespace apache::thrift::conformance { using type::kDisableUniversalHash; namespace { TEST(AnyRegistryTest, ShortType) { AnyRegistry registry; FollyToStringSerializer intCodec; EXPECT_TRUE(registry.registerType(shortThriftType(), {&intCodec})); // Should use the type uri because it is shorter than the id. Any any = registry.store(1, kFollyToStringProtocol); EXPECT_TRUE(any.type()); EXPECT_FALSE(any.typeHashPrefixSha2_256()); EXPECT_EQ(registry.load(any), 1); EXPECT_EQ(*registry.tryGetTypeId(any), typeid(int)); EXPECT_EQ(registry.getTypeId(any), typeid(int)); EXPECT_EQ(registry.getTypeUri(any), shortThriftType().uri().value()); } void checkLongType(int typeBytes, int expectedOutBytes) { AnyRegistry registry; FollyToStringSerializer intCodec; auto longType = longThriftType(); if (typeBytes == kTypeHashBytesNotSpecified) { longType.typeHashBytes().reset(); } else { longType.typeHashBytes_ref() = typeBytes; } EXPECT_TRUE(registry.registerType(longType, {&intCodec})); // Should use the type id because it is shorter than the uri. Any any = registry.store(1, kFollyToStringProtocol); if (expectedOutBytes == kDisableUniversalHash) { EXPECT_FALSE(any.typeHashPrefixSha2_256()); EXPECT_TRUE(any.type()); EXPECT_EQ( registry.getSerializerByUri(*any.type(), intCodec.getProtocol()), &intCodec); } else { EXPECT_FALSE(any.type()); ASSERT_TRUE(any.typeHashPrefixSha2_256()); EXPECT_EQ( any.typeHashPrefixSha2_256().value_unchecked().size(), expectedOutBytes); EXPECT_EQ( registry.getSerializerByHash( type::UniversalHashAlgorithm::Sha2_256, *any.typeHashPrefixSha2_256(), intCodec.getProtocol()), &intCodec); } EXPECT_EQ(registry.load(any), 1); EXPECT_EQ(*registry.tryGetTypeId(any), typeid(int)); EXPECT_EQ(registry.getTypeId(any), typeid(int)); EXPECT_EQ(registry.getTypeUri(any), longType.uri().value()); } TEST(AnyRegistryTest, LongType) { // Disabled is respected. THRIFT_SCOPED_CHECK( checkLongType(kDisableUniversalHash, kDisableUniversalHash)); // Unset uses default. THRIFT_SCOPED_CHECK( checkLongType(kTypeHashBytesNotSpecified, kDefaultTypeHashBytes)); // Type can increase bytes used. THRIFT_SCOPED_CHECK(checkLongType(24, 24)); THRIFT_SCOPED_CHECK(checkLongType(32, 32)); } TEST(AnyRegistryTest, ShortTypeHash) { AnyRegistry registry; FollyToStringSerializer intCodec; auto longType = longThriftType(); EXPECT_TRUE(registry.registerType(longType, {&intCodec})); Any any = registry.store(1, kFollyToStringProtocol); any.typeHashPrefixSha2_256_ref() = any.typeHashPrefixSha2_256()->substr(0, 7); EXPECT_THROW(registry.load(any), std::out_of_range); } TEST(AnyRegistryTest, TypeNotFound) { AnyRegistry registry; EXPECT_EQ(registry.getTypeUri(), ""); EXPECT_EQ((registry.getSerializer()), nullptr); EXPECT_THROW(registry.store(1), std::out_of_range); Any any; EXPECT_THROW(registry.load(any), std::invalid_argument); any.type_ref() = thriftType("int"); EXPECT_THROW(registry.load(any), std::out_of_range); any.protocol_ref() = StandardProtocol::Binary; EXPECT_THROW(registry.load(any), std::out_of_range); FollyToStringSerializer intCodec; EXPECT_THROW(registry.registerSerializer(&intCodec), std::out_of_range); } TEST(AnyRegistryTest, ProtocolNotFound) { AnyRegistry registry; EXPECT_TRUE(registry.registerType(testThriftType("int"))); EXPECT_EQ(registry.getTypeUri(), thriftType("int")); EXPECT_EQ((registry.getSerializer()), nullptr); EXPECT_THROW(registry.store(1), std::out_of_range); Any any; EXPECT_THROW(registry.load(any), std::invalid_argument); any.type_ref() = thriftType("int"); EXPECT_THROW(registry.load(any), std::out_of_range); any.protocol_ref() = StandardProtocol::Binary; EXPECT_THROW(registry.load(any), std::out_of_range); } TEST(AnyRegistryTest, TypeHashToShort) { AnyRegistry registry; FollyToStringSerializer intCodec; auto anyType = longThriftType(); anyType.typeHashBytes_ref() = 17; EXPECT_TRUE(registry.registerType(anyType, {&intCodec})); Any any = registry.store(1, intCodec.getProtocol()); ASSERT_TRUE(any.typeHashPrefixSha2_256()); EXPECT_EQ(any.typeHashPrefixSha2_256()->size(), 17); EXPECT_EQ(registry.load(any), 1); any.typeHashPrefixSha2_256_ref() = any.typeHashPrefixSha2_256()->substr(0, 8); EXPECT_EQ(registry.load(any), 1); any.typeHashPrefixSha2_256_ref() = any.typeHashPrefixSha2_256()->substr(0, 7); EXPECT_THROW(registry.load(any), std::out_of_range); } TEST(AnyRegistryTest, Behavior) { AnyRegistry registry; const AnyRegistry& cregistry = registry; EXPECT_EQ(cregistry.getTypeUri(typeid(int)), ""); EXPECT_EQ(cregistry.getSerializer(kFollyToStringProtocol), nullptr); FollyToStringSerializer intCodec; // Type must be registered first. EXPECT_THROW(registry.registerSerializer(&intCodec), std::out_of_range); // Empty string is rejected. EXPECT_THROW( registry.registerType(testThriftType("")), std::invalid_argument); EXPECT_TRUE(registry.registerType(testThriftType("int"))); EXPECT_EQ(cregistry.getTypeUri(typeid(int)), thriftType("int")); EXPECT_EQ(cregistry.getSerializer(kFollyToStringProtocol), nullptr); // Conflicting and duplicate registrations are rejected. EXPECT_FALSE(registry.registerType(testThriftType("int"))); EXPECT_FALSE(registry.registerType(testThriftType("other-int"))); EXPECT_FALSE(registry.registerType(testThriftType("int"))); EXPECT_TRUE(registry.registerSerializer(&intCodec)); EXPECT_EQ(cregistry.getTypeUri(), thriftType("int")); EXPECT_EQ(cregistry.getSerializer(kFollyToStringProtocol), &intCodec); // Duplicate registrations are rejected. EXPECT_FALSE(registry.registerSerializer(&intCodec)); Number1Serializer number1Codec; EXPECT_TRUE(registry.registerSerializer(&number1Codec)); EXPECT_TRUE(registry.registerType(testThriftType("double"))); // nullptr is rejected. EXPECT_FALSE(registry.registerSerializer(nullptr)); EXPECT_TRUE(registry.registerSerializer( std::make_unique>())); Any value = cregistry.store(3, kFollyToStringProtocol); EXPECT_EQ(value.type().value_or(""), thriftType("int")); EXPECT_FALSE(value.typeHashPrefixSha2_256().has_value()); EXPECT_EQ(toString(*value.data()), "3"); EXPECT_TRUE(hasProtocol(value, kFollyToStringProtocol)); EXPECT_EQ(std::any_cast(cregistry.load(value)), 3); EXPECT_EQ(cregistry.load(value), 3); // Storing an Any does nothing if the protocols match. Any original = value; value = cregistry.store(original, kFollyToStringProtocol); EXPECT_EQ(value.type().value_or(""), thriftType("int")); EXPECT_FALSE(value.typeHashPrefixSha2_256().has_value()); EXPECT_EQ(toString(*value.data()), "3"); EXPECT_TRUE(hasProtocol(value, kFollyToStringProtocol)); value = cregistry.store(std::any(std::move(original)), kFollyToStringProtocol); EXPECT_EQ(value.type().value_or(""), thriftType("int")); EXPECT_FALSE(value.typeHashPrefixSha2_256().has_value()); EXPECT_EQ(toString(*value.data()), "3"); EXPECT_TRUE(hasProtocol(value, kFollyToStringProtocol)); // Storing an Any with a different protocol does a conversion. original = value; value = cregistry.store(original, Number1Serializer::kProtocol); EXPECT_EQ(value.type().value_or(""), thriftType("int")); EXPECT_FALSE(value.typeHashPrefixSha2_256().has_value()); EXPECT_EQ(toString(*value.data()), "number 1!!"); EXPECT_TRUE(hasProtocol(value, Number1Serializer::kProtocol)); value = cregistry.store( std::any(std::move(original)), Number1Serializer::kProtocol); EXPECT_EQ(value.type().value_or(""), thriftType("int")); EXPECT_FALSE(value.typeHashPrefixSha2_256().has_value()); EXPECT_EQ(toString(*value.data()), "number 1!!"); EXPECT_TRUE(hasProtocol(value, Number1Serializer::kProtocol)); EXPECT_EQ(std::any_cast(cregistry.load(value)), 1); EXPECT_EQ(cregistry.load(value), 1); // Storing an unsupported type is an error. EXPECT_THROW( cregistry.store(2.5f, kFollyToStringProtocol), std::out_of_range); EXPECT_THROW( cregistry.store(std::any(2.5f), kFollyToStringProtocol), std::out_of_range); // Storing using an unsupported protocol throws an error EXPECT_THROW( cregistry.store(3, Protocol(StandardProtocol::Binary)), std::out_of_range); // Loading an empty Any value throws an error. value = {}; EXPECT_FALSE(value.type().has_value()); EXPECT_FALSE(value.typeHashPrefixSha2_256().has_value()); EXPECT_EQ(toString(*value.data()), ""); EXPECT_TRUE( hasProtocol(value, getStandardProtocol())); EXPECT_THROW(cregistry.load(value), std::invalid_argument); EXPECT_THROW(cregistry.load(value), std::invalid_argument); value.type_ref() = "foo"; EXPECT_THROW(cregistry.load(value), std::out_of_range); EXPECT_THROW(cregistry.load(value), std::out_of_range); value = cregistry.store(2.5, kFollyToStringProtocol); EXPECT_EQ(*value.type(), thriftType("double")); EXPECT_FALSE(value.typeHashPrefixSha2_256().has_value()); EXPECT_EQ(toString(*value.data()), "2.5"); EXPECT_TRUE(hasProtocol(value, kFollyToStringProtocol)); EXPECT_EQ(std::any_cast(cregistry.load(value)), 2.5); EXPECT_EQ(cregistry.load(value), 2.5); EXPECT_THROW(cregistry.load(value), std::bad_any_cast); EXPECT_EQ( cregistry.debugString(), "AnyRegistry[\n" " facebook.com/thrift/double (319b4d9a143e15bbf818e1fe4556f46a578cb58acda43d5f40cf9a22886dc9d8):\n" " facebook.com/thrift/FollyToString,\n" " facebook.com/thrift/int (3fc51d1641587f7d26c7ab0dcb97b69f6ea48f9ea15ea626ec34e6069fc4b136):\n" " facebook.com/thrift/FollyToString,\n" " facebook.com/thrift/Number1,\n" "]"); } TEST(AnyRegistryTest, Aliases) { AnyRegistry registry; const AnyRegistry& cregistry = registry; FollyToStringSerializer intCodec; Number1Serializer oneCodec; EXPECT_TRUE(registry.registerType( testThriftType({"int", "Int", "Integer"}), {&oneCodec, &intCodec})); EXPECT_EQ(registry.getTypeUri(), thriftType("int")); EXPECT_EQ( registry.getSerializerByUri(thriftType("int"), oneCodec.getProtocol()), &oneCodec); EXPECT_EQ( registry.getSerializerByUri(thriftType("Int"), oneCodec.getProtocol()), &oneCodec); EXPECT_EQ( registry.getSerializerByUri( thriftType("Integer"), oneCodec.getProtocol()), &oneCodec); auto any = cregistry.store(1, kFollyToStringProtocol); // Stored under the main type uri. EXPECT_EQ(any.type().value_or(""), thriftType("int")); EXPECT_EQ(cregistry.load(any), 1); any.type_ref() = thriftType("Int"); EXPECT_EQ(cregistry.load(any), 1); any.type_ref() = thriftType("Integer"); EXPECT_EQ(cregistry.load(any), 1); any.type_ref() = thriftType("Unknown"); EXPECT_THROW(cregistry.load(any), std::out_of_range); } TEST(AnyRegistryTest, ForwardCompat_Protocol) { AnyRegistry registry; Protocol invalidProtocol("invalid"); EXPECT_THROW(validateProtocol(invalidProtocol), std::invalid_argument); // Lookup does not throw. EXPECT_EQ(registry.getSerializer(invalidProtocol), nullptr); } TEST(AnyRegistryTest, ForwardCompat_Any) { AnyRegistry registry; const AnyRegistry& cregistry = registry; FollyToStringSerializer intCodec; EXPECT_TRUE(registry.registerType(testThriftType("int"))); EXPECT_TRUE(registry.registerSerializer(&intCodec)); Any any = cregistry.store(1, kFollyToStringProtocol); validateAny(any); any.type() = "invalid"; EXPECT_THROW(validateAny(any), std::invalid_argument); // Load does not throw std::invalid_argument. EXPECT_THROW(cregistry.load(any), std::out_of_range); } TEST(AnyRegistryTest, StdProtocol) { AnyRegistry registry; const AnyRegistry& cregistry = registry; registry.registerType< protocol::Value, StandardProtocol::Binary, StandardProtocol::Compact>(testThriftType("Value")); auto value = protocol::asValueStruct(1); auto any = cregistry.store(value); ASSERT_TRUE(any.type()); EXPECT_EQ(any.type().value_unchecked(), thriftType("Value")); EXPECT_EQ(cregistry.load(any), value); } TEST(AnyRegistryTest, Generated) { // Double register fails with a runtime error. EXPECT_THROW( detail::registerGeneratedStruct(), std::runtime_error); auto value = protocol::asValueStruct(1); auto any = AnyRegistry::generated().store(value); EXPECT_EQ(AnyRegistry::generated().load(any), value); EXPECT_THROW( AnyRegistry::generated().store(value), std::out_of_range); } TEST(AnyRegistryTest, ForceRegister) { AnyRegistry registry; EXPECT_TRUE(registry.forceRegisterType(typeid(protocol::Value), "va")); EXPECT_TRUE(registry.registerSerializer( &getAnyStandardSerializer())); protocol::Value expected; expected.boolValue_ref() = true; Any any = registry.store( expected, getStandardProtocol()); EXPECT_EQ(any.type(), "va"); protocol::Value actual = registry.load(any); EXPECT_EQ(actual, expected); } } // namespace } // namespace apache::thrift::conformance