/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // TODO: Remove this. Specify namespace explicitly instead. using namespace ::apache::thrift::conformance; using detail::protocol_reader_t; using detail::protocol_writer_t; namespace apache::thrift::protocol { namespace { namespace testset = apache::thrift::test::testset; template MaskedDecodeResult parseObjectWithTest( const folly::IOBuf& buf, Mask mask, bool string_to_binary = true) { auto ret = parseObject(buf, mask, string_to_binary); auto v = parseObjectWithoutExcludedData(buf, mask, string_to_binary); EXPECT_EQ(ret.included, v); return ret; } TEST(ObjectTest, Example) { using facebook::thrift::lib::test::Bar; Bar bar; bar.field_3() = {"foo", "bar", "baz"}; bar.field_4()->field_1() = 42; bar.field_4()->field_2() = "Everything"; auto serialized = CompactSerializer::serialize(bar).move(); // We can parse arbitrary serialized thrift blob into Protocol Object Object obj = parseObject(*serialized); // We can re-serialize it back to Protocol Object // Note: there is no guarantee that serialized data is byte-wise idential. serialized = serializeObject(obj); // Test round-trip. EXPECT_EQ(CompactSerializer::deserialize(serialized.get()), bar); // Test constructing the same Object manually Object foo; foo[FieldId{1}].ensure_i32() = 42; foo[FieldId{2}].ensure_binary() = *folly::IOBuf::copyBuffer("Everything"); Object obj2; obj2[FieldId{10}] = asValueStruct>(*bar.field_3()); obj2[FieldId{20}].ensure_object() = foo; EXPECT_EQ(obj, obj2); } TEST(ObjectTest, TypeEnforced) { // Always a bool when bool_t is used, without ambiguity. // Pointers implicitly converts to bools. Value value = asValueStruct(""); ASSERT_EQ(value.getType(), Value::Type::boolValue); EXPECT_TRUE(value.get_boolValue()); } TEST(ObjectTest, Empty) { Value value; value.emplace_object(); value.as_object().members().reset(); serializeValue(value); serializeObject(value.as_object()); } TEST(ObjectTest, Bool) { Value value = asValueStruct(20); ASSERT_EQ(value.getType(), Value::Type::boolValue); EXPECT_TRUE(value.get_boolValue()); value = asValueStruct(0); ASSERT_EQ(value.getType(), Value::Type::boolValue); EXPECT_FALSE(value.get_boolValue()); } TEST(ObjectTest, Byte) { Value value = asValueStruct(7u); ASSERT_EQ(value.getType(), Value::Type::byteValue); EXPECT_EQ(value.get_byteValue(), 7); } TEST(ObjectTest, I16) { Value value = asValueStruct(7u); ASSERT_EQ(value.getType(), Value::Type::i16Value); EXPECT_EQ(value.get_i16Value(), 7); } TEST(ObjectTest, I32) { Value value = asValueStruct(7u); ASSERT_EQ(value.getType(), Value::Type::i32Value); EXPECT_EQ(value.get_i32Value(), 7); } TEST(ObjectTest, I64) { Value value = asValueStruct(7u); ASSERT_EQ(value.getType(), Value::Type::i64Value); EXPECT_EQ(value.get_i64Value(), 7); } TEST(ObjectTest, Enum) { enum class MyEnum { kValue = 7 }; Value value = asValueStruct(MyEnum::kValue); ASSERT_EQ(value.getType(), Value::Type::i32Value); EXPECT_EQ(value.get_i32Value(), 7); value = asValueStruct(static_cast(2)); ASSERT_EQ(value.getType(), Value::Type::i32Value); EXPECT_EQ(value.get_i32Value(), 2); value = asValueStruct(21u); ASSERT_EQ(value.getType(), Value::Type::i32Value); EXPECT_EQ(value.get_i32Value(), 21); } TEST(ObjectTest, Float) { Value value = asValueStruct(1.5); ASSERT_EQ(value.getType(), Value::Type::floatValue); EXPECT_EQ(value.get_floatValue(), 1.5f); } TEST(ObjectTest, Double) { Value value = asValueStruct(1.5f); ASSERT_EQ(value.getType(), Value::Type::doubleValue); EXPECT_EQ(value.get_doubleValue(), 1.5); } TEST(ObjectTest, String) { Value value = asValueStruct("hi"); ASSERT_EQ(value.getType(), Value::Type::stringValue); EXPECT_EQ(value.get_stringValue(), "hi"); } TEST(ObjectTest, Binary) { Value value = asValueStruct("hi"); ASSERT_EQ(value.getType(), Value::Type::binaryValue); EXPECT_EQ(toString(value.get_binaryValue()), "hi"); } TEST(ObjectTest, List) { std::vector data = {1, 4, 2}; Value value = asValueStruct>(data); ASSERT_EQ(value.getType(), Value::Type::listValue); ASSERT_EQ(value.get_listValue().size(), data.size()); for (size_t i = 0; i < data.size(); ++i) { EXPECT_EQ(value.get_listValue()[i], asValueStruct(data[i])); } // Works with other containers std::set s(data.begin(), data.end()); value = asValueStruct>(s); std::sort(data.begin(), data.end()); ASSERT_EQ(value.getType(), Value::Type::listValue); ASSERT_EQ(value.get_listValue().size(), data.size()); for (size_t i = 0; i < data.size(); ++i) { EXPECT_EQ(value.get_listValue()[i], asValueStruct(data[i])); } // Works with cpp_type type tag Value value2 = asValueStruct, type::list>>(s); EXPECT_EQ(value, value2); } TEST(ObjectTest, Set) { std::set data = {1, 4, 2}; Value value = asValueStruct>(data); ASSERT_EQ(value.getType(), Value::Type::setValue); ASSERT_EQ(value.setValue_ref()->size(), data.size()); for (const Value& v : value.setValue_ref().value()) { ASSERT_TRUE(data.contains(v.as_i16())); } // Works with other containers value = asValueStruct>( std::vector(data.begin(), data.end())); ASSERT_EQ(value.getType(), Value::Type::setValue); ASSERT_EQ(value.setValue_ref()->size(), data.size()); for (const Value& v : value.setValue_ref().value()) { ASSERT_TRUE(data.contains(v.as_i16())); } } TEST(ObjectTest, Map) { std::map data = {{"one", 1}, {"four", 4}, {"two", 2}}; Value value = asValueStruct>(data); ASSERT_EQ(value.getType(), Value::Type::mapValue); ASSERT_EQ(value.mapValue_ref()->size(), data.size()); for (const auto& entry : data) { auto itr = value.mapValue_ref()->find(asValueStruct(entry.first)); ASSERT_NE(itr, value.mapValue_ref()->end()); EXPECT_EQ(itr->second, asValueStruct(entry.second)); } // Works with other containers. std::vector> otherData(data.begin(), data.end()); value = asValueStruct>(otherData); ASSERT_EQ(value.getType(), Value::Type::mapValue); ASSERT_EQ(value.mapValue_ref()->size(), data.size()); for (const auto& entry : data) { auto itr = value.mapValue_ref()->find(asValueStruct(entry.first)); ASSERT_NE(itr, value.mapValue_ref()->end()); EXPECT_EQ(itr->second, asValueStruct(entry.second)); } } TEST(ObjectTest, Struct) { // TODO(afuller): Use a struct that covers more cases. auto protocol = ::apache::thrift::conformance::Protocol("hi").asStruct(); Value value = asValueStruct(protocol); ASSERT_EQ(value.getType(), Value::Type::objectValue); const Object& object = value.get_objectValue(); EXPECT_EQ(object.members_ref()->size(), 2); EXPECT_EQ( object.members_ref()->at(1), asValueStruct( ::apache::thrift::conformance::StandardProtocol::Custom)); EXPECT_EQ(object.members_ref()->at(2), asValueStruct("hi")); } TEST(ObjectTest, StructWithList) { testset::struct_with> s; std::vector listValues = {1, 2, 3}; s.field_1_ref() = listValues; Value value = asValueStruct(s); ASSERT_EQ(value.getType(), Value::Type::objectValue); const Object& object = value.get_objectValue(); EXPECT_EQ(object.members_ref()->size(), 1); EXPECT_EQ( object.members_ref()->at(1), asValueStruct>(listValues)); } TEST(ObjectTest, StructWithMap) { testset::struct_with> s; std::map mapValues = {{"one", 1}, {"four", 4}, {"two", 2}}; s.field_1_ref() = mapValues; Value value = asValueStruct(s); ASSERT_EQ(value.getType(), Value::Type::objectValue); const Object& object = value.get_objectValue(); EXPECT_EQ(object.members_ref()->size(), 1); auto val = asValueStruct>(mapValues); EXPECT_EQ(object.members_ref()->at(1), val); } TEST(ObjectTest, StructWithSet) { testset::struct_with> s; std::set setValues = {1, 2, 3}; s.field_1_ref() = setValues; Value value = asValueStruct(s); ASSERT_EQ(value.getType(), Value::Type::objectValue); const Object& object = value.get_objectValue(); EXPECT_EQ(object.members_ref()->size(), 1); EXPECT_EQ( object.members_ref()->at(1), asValueStruct>(setValues)); } TEST(ObjectTest, DirectlyAdaptedStruct) { apache::thrift::test::basic::DirectlyAdaptedStruct s; s.value.data() = 42; using Tag = type::adapted< ::apache::thrift::test::TemplatedTestAdapter, type::struct_t< apache::thrift::test::basic::detail::DirectlyAdaptedStruct>>; Value value = asValueStruct(s); ASSERT_TRUE(value.objectValue_ref().has_value()); const Object& object = *value.objectValue_ref(); EXPECT_EQ(object.members_ref()->size(), 1); EXPECT_EQ(object.members_ref()->at(1), asValueStruct(42)); apache::thrift::test::basic::DirectlyAdaptedStruct s2; detail::ProtocolValueToThriftValue{}(value, s2); EXPECT_EQ(s, s2); } TEST(ObjectTest, parseObject) { folly::IOBufQueue iobufQueue; testset::struct_with> thriftStruct; std::set setValues = {1, 2, 3}; thriftStruct.field_1_ref() = setValues; BinarySerializer::serialize(thriftStruct, &iobufQueue); auto serialized = iobufQueue.move(); auto object = parseObject(*serialized); EXPECT_EQ(object.members_ref()->size(), 1); EXPECT_EQ( object.members_ref()->at(1), asValueStruct>(setValues)); } TEST(ObjectTest, serializeObject) { folly::IOBufQueue iobufQueue; testset::struct_with> thriftStruct; std::set setValues = {1, 2, 3}; thriftStruct.field_1_ref() = setValues; BinarySerializer::serialize(thriftStruct, &iobufQueue); auto expected = iobufQueue.move(); auto object = parseObject(*expected); auto actual = serializeObject(object); auto objectd = parseObject(*actual); EXPECT_EQ(objectd.members_ref()->size(), 1); EXPECT_EQ( objectd.members_ref()->at(1), asValueStruct>(setValues)); } TEST(ObjectTest, ValueUnionTypeMatch) { EXPECT_EQ( static_cast(Value::Type::boolValue), type::BaseType::Bool); EXPECT_EQ( static_cast(Value::Type::byteValue), type::BaseType::Byte); EXPECT_EQ( static_cast(Value::Type::i16Value), type::BaseType::I16); EXPECT_EQ( static_cast(Value::Type::i32Value), type::BaseType::I32); EXPECT_EQ( static_cast(Value::Type::i64Value), type::BaseType::I64); EXPECT_EQ( static_cast(Value::Type::floatValue), type::BaseType::Float); EXPECT_EQ( static_cast(Value::Type::doubleValue), type::BaseType::Double); EXPECT_EQ( static_cast(Value::Type::stringValue), type::BaseType::String); EXPECT_EQ( static_cast(Value::Type::binaryValue), type::BaseType::Binary); EXPECT_EQ( static_cast(Value::Type::listValue), type::BaseType::List); EXPECT_EQ( static_cast(Value::Type::setValue), type::BaseType::Set); EXPECT_EQ( static_cast(Value::Type::mapValue), type::BaseType::Map); EXPECT_EQ( static_cast(Value::Type::objectValue), type::BaseType::Struct); } template class TypedParseObjectTest : public testing::Test {}; template <::apache::thrift::conformance::StandardProtocol Protocol, typename T> std::unique_ptr serialize(T& s) { folly::IOBufQueue iobufQueue; protocol_writer_t writer; writer.setOutput(&iobufQueue); s.write(&writer); auto iobuf = iobufQueue.move(); return iobuf; } template < ::apache::thrift::conformance::StandardProtocol Protocol, typename Tag, typename T> void testParseObject() { T testsetValue; for (const auto& val : data::ValueGenerator::getKeyValues()) { SCOPED_TRACE(val.name); testsetValue.field_1_ref() = val.value; auto object = asObject(testsetValue); auto iobuf = serialize(testsetValue); auto objFromParseObject = parseObject>(*iobuf); EXPECT_EQ(objFromParseObject, object); } } type::StandardProtocol convertStandardProtocol( conformance::StandardProtocol prot) { return prot == conformance::StandardProtocol::Binary ? type::StandardProtocol::Binary : type::StandardProtocol::Compact; } template < ::apache::thrift::conformance::StandardProtocol Protocol, typename Tag, typename T> void testWithMask(bool testSerialize) { T testsetValue; for (const auto& val : data::ValueGenerator::getKeyValues()) { SCOPED_TRACE(val.name); testsetValue.field_1_ref() = val.value; auto object = asObject(testsetValue); auto reserialize = [&](MaskedDecodeResult& result) { auto reserialized = serializeObject>( result.included, result.excluded); Object finalObj = parseObject>(*reserialized); EXPECT_EQ(finalObj, object); }; auto iobuf = serialize(testsetValue); { // parseObject with allMask should parse the entire object. auto result = parseObjectWithTest>(*iobuf, allMask()); if (testSerialize) { reserialize(result); } else { // manually check the result EXPECT_EQ(result.included, object); MaskedProtocolData expected; expected.protocol() = convertStandardProtocol(Protocol); EXPECT_EQ(result.excluded, expected); } } { // parseObject with noneMask should parse nothing. auto result = parseObjectWithTest>(*iobuf, noneMask()); if (testSerialize) { reserialize(result); } else { // manually check the result EXPECT_EQ(result.included, Object{}); EXPECT_EQ( *result.excluded.protocol(), convertStandardProtocol(Protocol)); auto& values = *result.excluded.values(); auto& encodedValue = detail::getByValueId(values, *result.excluded.data()->full_ref()); auto objFromExcluded = parseObject>(*encodedValue.data()); EXPECT_EQ(objFromExcluded, object); } } { // parseObject with Mask = includes{1: allMask()} Mask mask; mask.includes_ref().emplace()[1] = allMask(); auto result = parseObjectWithTest>(*iobuf, mask); if (testSerialize) { reserialize(result); } else { // manually check the result EXPECT_EQ(result.included.size(), 1); EXPECT_EQ(result.included.at(FieldId{1}), object.at(FieldId{1})); EXPECT_EQ( *result.excluded.protocol(), convertStandardProtocol(Protocol)); } } } } template bool hasEmptyContainer(const type::standard_type& value) { if constexpr (type::is_a_v) { if (value.size() == 0) { return true; } } if constexpr (type::is_a_v>) { for (const auto& [mapkey, mapval] : value) { if (mapval.size() == 0) { return true; } } } return false; } // The tests cases to run. using ParseObjectTestCases = ::testing::Types< type::bool_t, type::byte_t, type::i16_t, type::i32_t, type::i64_t, type::float_t, type::double_t, type::binary_t, type::string_t, type::list, type::list, type::set, type::set, type::map, type::map, type::map>>; TYPED_TEST_SUITE(TypedParseObjectTest, ParseObjectTestCases); TYPED_TEST(TypedParseObjectTest, ParseSerializedSameAsDirectObject) { testParseObject< ::apache::thrift::conformance::StandardProtocol::Binary, TypeParam, testset::struct_with>(); testParseObject< ::apache::thrift::conformance::StandardProtocol::Compact, TypeParam, testset::struct_with>(); testParseObject< ::apache::thrift::conformance::StandardProtocol::Binary, TypeParam, testset::union_with>(); testParseObject< ::apache::thrift::conformance::StandardProtocol::Compact, TypeParam, testset::union_with>(); } TYPED_TEST(TypedParseObjectTest, ParseObjectWithMask) { testWithMask< ::apache::thrift::conformance::StandardProtocol::Binary, TypeParam, testset::struct_with>(false); testWithMask< ::apache::thrift::conformance::StandardProtocol::Compact, TypeParam, testset::struct_with>(false); } TYPED_TEST(TypedParseObjectTest, SerializeObjectWithMask) { testWithMask< ::apache::thrift::conformance::StandardProtocol::Binary, TypeParam, testset::struct_with>(true); testWithMask< ::apache::thrift::conformance::StandardProtocol::Compact, TypeParam, testset::struct_with>(true); } TEST(Object, invalid_object) { { Object obj; obj[FieldId{0}].ensure_list() = { asValueStruct(1), asValueStruct(1)}; EXPECT_THROW( serializeObject(obj), TProtocolException); } { Object obj; obj[FieldId{0}].ensure_set() = { asValueStruct(1), asValueStruct(1)}; EXPECT_THROW( serializeObject(obj), TProtocolException); } { Object obj; obj[FieldId{0}].ensure_map() = { {asValueStruct(1), asValueStruct(1)}, {asValueStruct(2), asValueStruct(1)}}; EXPECT_THROW( serializeObject(obj), TProtocolException); } { Object obj; obj[FieldId{0}].ensure_map() = { {asValueStruct(1), asValueStruct(1)}, {asValueStruct(1), asValueStruct(1)}}; EXPECT_THROW( serializeObject(obj), TProtocolException); } } TEST(Object, uri) { EXPECT_EQ(uri(), "facebook.com/thrift/protocol/Object"); EXPECT_EQ(uri(), "facebook.com/thrift/protocol/Value"); } TEST(Object, Wrapper) { Object object; EXPECT_TRUE(object.empty()); object[FieldId{0}]; EXPECT_FALSE(object.empty()); object[FieldId{2}]; EXPECT_EQ(object.size(), 2); EXPECT_EQ(&object[FieldId{0}], &object[FieldId{0}]); EXPECT_EQ(&object[FieldId{2}], &object[FieldId{2}]); EXPECT_EQ(&object.at(FieldId{0}), &object[FieldId{0}]); EXPECT_EQ(&object.at(FieldId{2}), &object[FieldId{2}]); EXPECT_EQ(object.if_contains(FieldId{0}), &object[FieldId{0}]); EXPECT_EQ(object.if_contains(FieldId{2}), &object[FieldId{2}]); EXPECT_EQ(object.contains(FieldId{0}), true); EXPECT_EQ(object.contains(FieldId{1}), false); EXPECT_EQ(object.contains(FieldId{2}), true); EXPECT_THROW(object.at(FieldId{1}), std::out_of_range); EXPECT_EQ(object.erase(FieldId{0}), 1); EXPECT_EQ(object.contains(FieldId{0}), false); EXPECT_EQ(object.contains(FieldId{2}), true); EXPECT_EQ(object.size(), 1); EXPECT_EQ(object.erase(FieldId{1}), 0); EXPECT_EQ(object.size(), 1); EXPECT_FALSE(object.empty()); EXPECT_EQ(object.erase(FieldId{2}), 1); EXPECT_EQ(object.size(), 0); EXPECT_TRUE(object.empty()); } TEST(Value, Wrapper) { Object obj; obj[FieldId{100}] = asValueStruct("200"); const std::vector l = { asValueStruct(10), asValueStruct(20)}; const folly::F14FastSet s = { asValueStruct(30), asValueStruct(40)}; const folly::F14FastMap m = { {asValueStruct(50), asValueStruct(60)}, {asValueStruct(70), asValueStruct(80)}}; Value value; #define FBTHRIFT_TEST_THRIFT_VALUE_TYPE(TYPE, VALUE) \ do { \ EXPECT_FALSE(value.is_##TYPE()); \ EXPECT_FALSE(value.TYPE##Value_ref()); \ EXPECT_THROW(value.as_##TYPE(), apache::thrift::bad_field_access); \ EXPECT_EQ(value.if_##TYPE(), nullptr); \ EXPECT_FALSE(value.TYPE##Value_ref()); \ value.ensure_##TYPE() = VALUE; \ EXPECT_TRUE(value.is_##TYPE()); \ value.emplace_##TYPE() = VALUE; \ EXPECT_TRUE(value.is_##TYPE()); \ value.emplace_##TYPE(VALUE); \ EXPECT_TRUE(value.is_##TYPE()); \ EXPECT_EQ(value.as_##TYPE(), VALUE); \ EXPECT_EQ(*value.if_##TYPE(), VALUE); \ EXPECT_EQ(value.TYPE##Value_ref(), VALUE); \ } while (false) FBTHRIFT_TEST_THRIFT_VALUE_TYPE(bool, true); FBTHRIFT_TEST_THRIFT_VALUE_TYPE(byte, 1); FBTHRIFT_TEST_THRIFT_VALUE_TYPE(i16, 2); FBTHRIFT_TEST_THRIFT_VALUE_TYPE(i32, 3); FBTHRIFT_TEST_THRIFT_VALUE_TYPE(i64, 4); FBTHRIFT_TEST_THRIFT_VALUE_TYPE(float, 5); FBTHRIFT_TEST_THRIFT_VALUE_TYPE(double, 6); FBTHRIFT_TEST_THRIFT_VALUE_TYPE(string, "7"); FBTHRIFT_TEST_THRIFT_VALUE_TYPE(object, obj); FBTHRIFT_TEST_THRIFT_VALUE_TYPE(list, l); FBTHRIFT_TEST_THRIFT_VALUE_TYPE(set, s); FBTHRIFT_TEST_THRIFT_VALUE_TYPE(map, m); #undef FBTHRIFT_VALUE_TEST_TYPE // `binary` type requires special code since IOBuf doesn't have operator== const auto buf = *folly::IOBuf::copyBuffer("90"); EXPECT_FALSE(value.is_binary()); EXPECT_FALSE(value.binaryValue_ref()); EXPECT_THROW(value.as_binary(), apache::thrift::bad_field_access); EXPECT_EQ(value.if_binary(), nullptr); EXPECT_FALSE(value.binaryValue_ref()); value.ensure_binary() = buf; EXPECT_TRUE(value.is_binary()); EXPECT_TRUE(folly::IOBufEqualTo{}(value.as_binary(), buf)); EXPECT_TRUE(folly::IOBufEqualTo{}(*value.if_binary(), buf)); EXPECT_TRUE(folly::IOBufEqualTo{}(value.binaryValue_ref().value(), buf)); } TEST(Value, IsIntrinsicDefaultTrue) { EXPECT_TRUE(isIntrinsicDefault(asValueStruct(false))); EXPECT_TRUE(isIntrinsicDefault(asValueStruct(0))); EXPECT_TRUE(isIntrinsicDefault(asValueStruct(0))); EXPECT_TRUE(isIntrinsicDefault(asValueStruct(0))); EXPECT_TRUE(isIntrinsicDefault(asValueStruct(0))); EXPECT_TRUE(isIntrinsicDefault(asValueStruct(0.0))); EXPECT_TRUE(isIntrinsicDefault(asValueStruct(0.0))); EXPECT_TRUE(isIntrinsicDefault(asValueStruct(""))); EXPECT_TRUE(isIntrinsicDefault(asValueStruct(""))); EXPECT_TRUE( isIntrinsicDefault(asValueStruct>({}))); EXPECT_TRUE(isIntrinsicDefault(asValueStruct>({}))); EXPECT_TRUE(isIntrinsicDefault( asValueStruct>({}))); testset::struct_with> s; s.field_1_ref() = std::map{}; Object obj = asObject(s); EXPECT_TRUE(isIntrinsicDefault(obj)); } TEST(Value, IsIntrinsicDefaultFalse) { EXPECT_FALSE(isIntrinsicDefault(asValueStruct(true))); EXPECT_FALSE(isIntrinsicDefault(asValueStruct(1))); EXPECT_FALSE(isIntrinsicDefault(asValueStruct(1))); EXPECT_FALSE(isIntrinsicDefault(asValueStruct(1))); EXPECT_FALSE(isIntrinsicDefault(asValueStruct(1))); EXPECT_FALSE(isIntrinsicDefault(asValueStruct(0.5))); EXPECT_FALSE(isIntrinsicDefault(asValueStruct(0.5))); EXPECT_FALSE(isIntrinsicDefault(asValueStruct("foo"))); EXPECT_FALSE(isIntrinsicDefault(asValueStruct("foo"))); EXPECT_FALSE( isIntrinsicDefault(asValueStruct>({"foo"}))); EXPECT_FALSE( isIntrinsicDefault(asValueStruct>({1, 2, 3}))); EXPECT_FALSE( isIntrinsicDefault(asValueStruct>( {{1, "foo"}, {2, "bar"}}))); testset::struct_with> s; s.field_1_ref() = std::map{{"foo", 1}, {"bar", 2}}; EXPECT_FALSE(isIntrinsicDefault(asObject(s))); } template Value parseValueFromEncodedValue(const EncodedValue& encodedValue) { auto baseType = type::detail::getBaseType(Tag{}); EXPECT_EQ(encodedValue.wireType().value(), baseType); return parseValue(encodedValue.data().value(), false); } // Tests parseObject (and serializeObject if testSerialize) with mask. template <::apache::thrift::conformance::StandardProtocol Protocol> void testParseObjectWithMask(bool testSerialize) { Object obj, foo, bar, expected; // obj{1: 3, // 2: {1: "foo"} // 3: {5: {1: "foo"}, // 6: true}} foo[FieldId{1}].emplace_string("foo"); bar[FieldId{5}].emplace_object(foo); bar[FieldId{6}].emplace_bool(true); obj[FieldId{1}].emplace_i16(3); obj[FieldId{2}].emplace_object(foo); obj[FieldId{3}].emplace_object(bar); // masks obj[2] and obj[3][6] Mask mask; auto& includes = mask.includes_ref().emplace(); includes[2] = allMask(); includes[3].includes_ref().emplace()[6] = allMask(); // expected{2: {1: "foo"} // 3: {6: true}} expected[FieldId{2}].emplace_object(foo); expected[FieldId{3}].ensure_object()[FieldId{6}].emplace_bool(true); // serialize the object and deserialize with mask auto serialized = protocol::serializeObject>(obj); MaskedDecodeResult result = parseObjectWithTest>( *serialized, mask, false); if (testSerialize) { // test serializeObject with mask auto reserialized = protocol::serializeObject>( result.included, result.excluded); Object finalObj = parseObject>(*reserialized, false); EXPECT_EQ(finalObj, obj); return; } // manually check the result EXPECT_EQ(*result.excluded.protocol(), convertStandardProtocol(Protocol)); EXPECT_EQ(result.included, expected); auto& values = *result.excluded.values(); EXPECT_EQ(values.size(), 2); // Excluded should contain obj[1] and obj[3][5]. auto& excludedFields = result.excluded.data()->fields_ref().value(); EXPECT_EQ(excludedFields.size(), 2); auto& i16Encoded = detail::getByValueId( values, excludedFields.at(FieldId{1}).full_ref().value()); { Value v = parseValueFromEncodedValue, type::i16_t>( i16Encoded); EXPECT_EQ(v.as_i16(), 3); } auto& nestedExcludedFields = excludedFields.at(FieldId{3}).fields_ref().value(); EXPECT_EQ(nestedExcludedFields.size(), 1); auto& objectEncoded = detail::getByValueId( values, nestedExcludedFields.at(FieldId{5}).full_ref().value()); { Value v = parseValueFromEncodedValue, type::struct_c>( objectEncoded); EXPECT_EQ(v.as_object(), foo); } } template <::apache::thrift::conformance::StandardProtocol Protocol> void testSerializeObjectWithMask() { Object obj, foo; // obj{1: {1: "foo", // 2: "bar"}, // 2: 2, // 3: 3} foo[FieldId{1}].emplace_string("foo"); foo[FieldId{2}].emplace_string("bar"); obj[FieldId{1}].emplace_object(foo); obj[FieldId{2}].emplace_i32(2); obj[FieldId{3}].emplace_i32(3); // masks obj[1][1] and obj[2] Mask mask; auto& includes = mask.includes_ref().emplace(); includes[1].includes_ref().emplace()[1] = allMask(); includes[2] = allMask(); // serialize the object and deserialize with mask auto serialized = protocol::serializeObject>(obj); MaskedDecodeResult result = parseObjectWithTest>( *serialized, mask, false); { Object expected, bar; // expected{1: {1: "foo"}, // 2: 2} bar[FieldId{1}].emplace_string("foo"); expected[FieldId{1}].emplace_object(bar); expected[FieldId{2}].emplace_i32(2); EXPECT_EQ(result.included, expected); } { // reserialize with the unmodified object auto reserialized = protocol::serializeObject>( result.included, result.excluded); Object finalObj = parseObject>(*reserialized, false); EXPECT_EQ(finalObj, obj); } { // reserialize with the modified object Object modified, baz; // modified{1: {3: "baz"}, // 4: 4} baz[FieldId{3}].emplace_string("baz"); modified[FieldId{1}].emplace_object(baz); modified[FieldId{4}].emplace_i32(4); Object expected, bar; // expected{1: {2: "bar", // 3: "baz"}, // 3: 3, // 4: 4} bar[FieldId{2}].emplace_string("bar"); bar[FieldId{3}].emplace_string("baz"); expected[FieldId{1}].emplace_object(bar); expected[FieldId{3}].emplace_i32(3); expected[FieldId{4}].emplace_i32(4); auto reserialized = protocol::serializeObject>( modified, result.excluded); Object finalObj = parseObject>(*reserialized, false); EXPECT_EQ(finalObj, expected); } } template <::apache::thrift::conformance::StandardProtocol Protocol> void testSerializeObjectWithMaskError() { Object obj, foo; // obj{1: {1: "foo"}} foo[FieldId{1}].emplace_string("foo"); obj[FieldId{1}].emplace_object(foo); { // MaskedData[1] is full, which should be fields. MaskedProtocolData protocolData; protocolData.protocol() = convertStandardProtocol(Protocol); MaskedData& maskedData = protocolData.data_ref().value(); maskedData.fields_ref().ensure()[FieldId{1}].full_ref() = type::ValueId{1}; EXPECT_THROW( protocol::serializeObject>( obj, protocolData), std::runtime_error); } { // MaskedData[2] is fields, which should be full. MaskedProtocolData protocolData; protocolData.protocol() = convertStandardProtocol(Protocol); MaskedData& maskedData = protocolData.data_ref().value(); maskedData.fields_ref().ensure()[FieldId{2}].fields_ref().ensure(); EXPECT_THROW( protocol::serializeObject>( obj, protocolData), std::runtime_error); } } TEST(Object, ParseObjectWithMask) { testParseObjectWithMask< ::apache::thrift::conformance::StandardProtocol::Compact>(false); testParseObjectWithMask< ::apache::thrift::conformance::StandardProtocol::Binary>(false); } TEST(Object, SerializeObjectWithMask) { testParseObjectWithMask< ::apache::thrift::conformance::StandardProtocol::Compact>(true); testParseObjectWithMask< ::apache::thrift::conformance::StandardProtocol::Binary>(true); testSerializeObjectWithMask< ::apache::thrift::conformance::StandardProtocol::Compact>(); testSerializeObjectWithMask< ::apache::thrift::conformance::StandardProtocol::Binary>(); testSerializeObjectWithMaskError< ::apache::thrift::conformance::StandardProtocol::Compact>(); testSerializeObjectWithMaskError< ::apache::thrift::conformance::StandardProtocol::Binary>(); } // called by testParseObjectWithMapMask when testSerialize=true template <::apache::thrift::conformance::StandardProtocol Protocol> void testSerializeObjectWithMapMask(MaskedDecodeResult& result, Object& obj) { { // test serializeObject with mask // reserialize with the unmodified object auto reserialized = protocol::serializeObject>( result.included, result.excluded); Object finalObj = parseObject>(*reserialized, false); EXPECT_EQ(finalObj, obj); } { // reserialize with the modified object Object modified; // modified{1: map{10: {}, // 30: {"foo": 1}}} // This tests when map is empty and types are determined from MaskedData. modified[FieldId{1}] = asValueStruct< type::map>>( {{10, {}}, {30, {{"foo", 1}}}}); Object expected; // expected{1: map{10: {"bar": 2}, // 20: {"baz": 3}, // 30: {"foo": 1}}} expected[FieldId{1}] = asValueStruct< type::map>>( {{10, {{"bar", 2}}}, {20, {{"baz", 3}}}, {30, {{"foo", 1}}}}); auto reserialized = protocol::serializeObject>( modified, result.excluded); Object finalObj = parseObject>(*reserialized, false); EXPECT_EQ(finalObj, expected); } } template <::apache::thrift::conformance::StandardProtocol Protocol> void testParseObjectWithMapMask(bool testSerialize) { Object obj; // obj{1: map{10: {"foo": 1, // "bar": 2}, // 20: {"baz": 3}}, // 2: set{1, 2, 3}} obj[FieldId{1}] = asValueStruct< type::map>>( {{10, {{"foo", 1}, {"bar", 2}}}, {20, {{"baz", 3}}}}); std::set set = {1, 2, 3}; obj[FieldId{2}] = asValueStruct>(set); auto serialized = protocol::serializeObject>(obj); // masks obj[1][10]["foo"] and obj[2] Mask mask; Value key10 = asValueStruct(10); Value key20 = asValueStruct(20); Value keyFoo = asValueStruct("foo"); Value keyBar = asValueStruct("bar"); auto& includes = mask.includes_ref().emplace(); includes[1] .includes_map_ref() .emplace()[(int64_t)&key10] .includes_map_ref() .emplace()[(int64_t)&keyFoo] = allMask(); // This is treated as allMask() as the type is set. It tests the edge case // that a set field may have a map mask, since extractMaskViewFromPatch cannot // determine if a patch is for map or set for some operators. includes[2].excludes_map_ref().emplace()[99] = allMask(); Object expected; // expected{1: map{10: {"foo": 1}}, // 2: set{1, 2, 3}} expected[FieldId{1}] = asValueStruct< type::map>>( {{10, {{"foo", 1}}}}); expected[FieldId{2}] = asValueStruct>(set); // serialize the object and deserialize with mask MaskedDecodeResult result = parseObjectWithTest>( *serialized, mask, false); if (testSerialize) { testSerializeObjectWithMapMask(result, obj); return; } // manually check the result EXPECT_EQ(result.included, expected); EXPECT_EQ(*result.excluded.protocol(), convertStandardProtocol(Protocol)); auto& values = *result.excluded.values(); EXPECT_EQ(values.size(), 2); // map[10]["bar"] and map[20] auto& keys = *result.excluded.keys(); EXPECT_EQ(keys.size(), 3); // 10, 20, and "bar" auto getKeyValueId = [&](Value& key) { auto it = std::find(keys.begin(), keys.end(), key); EXPECT_NE(it, keys.end()); // It should find the value. return type::ValueId{apache::thrift::util::i32ToZigzag(it - keys.begin())}; }; // Excluded should contain map[10]["bar"] and map[20] auto& excludedKeys = result.excluded.data()->fields_ref()->at(FieldId{1}).values_ref().value(); EXPECT_EQ(excludedKeys.size(), 2); // check map[20] { auto& mapEncoded = detail::getByValueId( values, excludedKeys.at(getKeyValueId(key20)).full_ref().value()); Value v = parseValueFromEncodedValue, type::map_c>( mapEncoded); EXPECT_EQ(v.as_map(), obj[FieldId{1}].as_map()[key20].as_map()); } // check map[10]["bar"] { auto& nestedExcludedKeys = excludedKeys.at(getKeyValueId(key10)).values_ref().value(); EXPECT_EQ(nestedExcludedKeys.size(), 1); auto& i32Encoded = detail::getByValueId( values, nestedExcludedKeys.at(getKeyValueId(keyBar)).full_ref().value()); Value v = parseValueFromEncodedValue, type::i32_t>( i32Encoded); EXPECT_EQ(v.as_i32(), 2); } } TEST(ObjectTest, ToDynamic) { Value v; v.ensure_bool() = true; EXPECT_EQ(toDynamic(v), true); v.ensure_byte() = 10; EXPECT_EQ(toDynamic(v), 10); v.ensure_i16() = 20; EXPECT_EQ(toDynamic(v), 20); v.ensure_i32() = 30; EXPECT_EQ(toDynamic(v), 30); v.ensure_i64() = 40; EXPECT_EQ(toDynamic(v), 40); v.ensure_float() = 50; EXPECT_EQ(toDynamic(v), float(50)); v.ensure_double() = 60; EXPECT_EQ(toDynamic(v), double(60)); v.ensure_string() = "70"; EXPECT_EQ(toDynamic(v), "70"); v = asValueStruct("80"); EXPECT_EQ(toDynamic(v), "80"); v.ensure_float() = NAN; EXPECT_TRUE(std::isnan(toDynamic(v).asDouble())); std::vector vec = {1, 4, 2}; v = asValueStruct>(vec); EXPECT_EQ(toDynamic(v), folly::dynamic::array(1, 4, 2)); v = asValueStruct>({1, 4, 2}); v = asValueStruct>( {{"1", "10"}, {"4", "40"}, {"2", "20"}}); EXPECT_EQ( toDynamic(v), folly::dynamic(folly::dynamic::object("4", "40")("1", "10")("2", "20"))); v.ensure_object(); v.as_object()[FieldId{10}].ensure_string() = "100"; v.as_object()[FieldId{40}].ensure_string() = "400"; v.as_object()[FieldId{20}].ensure_string() = "200"; EXPECT_EQ( toDynamic(v), folly::dynamic( folly::dynamic::object("40", "400")("10", "100")("20", "200"))); Value v2; v2.ensure_object()[FieldId{30}].ensure_string() = "300"; v2.as_object()[FieldId{50}] = v; EXPECT_EQ( toDynamic(v2), folly::dynamic(folly::dynamic::object("30", "300")("50", toDynamic(v)))); v = asValueStruct>(vec); v2.ensure_map()[v] = asValueStruct(10); EXPECT_THROW(toDynamic(v2), std::runtime_error); } template <::apache::thrift::conformance::StandardProtocol Protocol> void testSerializeObjectWithMapMaskError() { Object obj; // obj{1: map{1: "foo"}} obj[FieldId{1}] = asValueStruct>({{1, "foo"}}); { // MaskedData[1] is full, which should be values. MaskedProtocolData protocolData; protocolData.protocol() = convertStandardProtocol(Protocol); MaskedData& maskedData = protocolData.data_ref().value(); maskedData.fields_ref().ensure()[FieldId{1}].full_ref() = type::ValueId{1}; EXPECT_THROW( protocol::serializeObject>( obj, protocolData), std::runtime_error); } { // MaskedData[1][2] is values, which should be full. MaskedProtocolData protocolData; protocolData.protocol() = convertStandardProtocol(Protocol); MaskedData& maskedData = protocolData.data_ref().value(); auto& keys = protocolData.keys().ensure(); keys.push_back(asValueStruct(2)); type::ValueId keyValueId = type::ValueId{apache::thrift::util::i32ToZigzag(keys.size() - 1)}; maskedData.fields_ref() .ensure()[FieldId{1}] .values_ref() .ensure()[keyValueId] .values_ref() .ensure(); EXPECT_THROW( protocol::serializeObject>( obj, protocolData), std::runtime_error); } } TEST(Object, ParseObjectWithMapMask) { testParseObjectWithMapMask< ::apache::thrift::conformance::StandardProtocol::Compact>(false); testParseObjectWithMapMask< ::apache::thrift::conformance::StandardProtocol::Binary>(false); } TEST(Object, SerializeObjectWithMapMask) { testParseObjectWithMapMask< ::apache::thrift::conformance::StandardProtocol::Compact>(true); testParseObjectWithMapMask< ::apache::thrift::conformance::StandardProtocol::Binary>(true); testSerializeObjectWithMapMaskError< ::apache::thrift::conformance::StandardProtocol::Compact>(); testSerializeObjectWithMapMaskError< ::apache::thrift::conformance::StandardProtocol::Binary>(); } template <::apache::thrift::conformance::StandardProtocol Protocol> void testParseObjectWithTwoMasks() { Object obj, foo; // obj{1: {1: "foo", // 2: "bar"}, // 2: 2, // 3: 3, // 4: map{10: {"foo": 1, // "bar": 2}, // 20: {"baz": 3}}} foo[FieldId{1}].emplace_string("foo"); foo[FieldId{2}].emplace_string("bar"); obj[FieldId{1}].emplace_object(foo); obj[FieldId{2}].emplace_i32(2); obj[FieldId{3}].emplace_i32(3); obj[FieldId{4}] = asValueStruct< type::map>>( {{10, {{"foo", 1}, {"bar", 2}}}, {20, {{"baz", 3}}}}); Value key10 = asValueStruct(10); Value key20 = asValueStruct(20); Value keyFoo = asValueStruct("foo"); Value keyBaz = asValueStruct("baz"); // masks obj[2] and obj[4][10]["foo"] Mask readMask; { auto& includes = readMask.includes_ref().emplace(); includes[2] = allMask(); includes[4] .includes_map_ref() .emplace()[(int64_t)&key10] .includes_map_ref() .emplace()[(int64_t)&keyFoo] = allMask(); } // masks obj[1][1], obj[3], obj[4][10], and obj[4][20]["baz"] Mask writeMask; { auto& includes = writeMask.includes_ref().emplace(); includes[1].includes_ref().emplace()[1] = allMask(); includes[3] = allMask(); auto& includes_map = includes[4].includes_map_ref().emplace(); includes_map[(int64_t)&key10] = allMask(); includes_map[(int64_t)&key20] .includes_map_ref() .emplace()[(int64_t)&keyBaz] = allMask(); } // serialize the object and deserialize with mask auto serialized = protocol::serializeObject>(obj); MaskedDecodeResult result = parseObject>( *serialized, readMask, writeMask, false); { Object expected; // expected{1: {}, // 2: 2, // 4: map{10: {"foo": 1} // 20: {}}} expected[FieldId{1}].objectValue_ref().emplace(); expected[FieldId{2}].i32Value_ref() = 2; expected[FieldId{4}] = asValueStruct< type::map>>( {{10, {{"foo", 1}}}, {20, {}}}); EXPECT_EQ(result.included, expected); } { // reserialize with the object and MaskedData Object expected, bar; // expected{1: {2: "bar"}, // 2: 2, // 4: map{10: {"foo": 1}, // 20: {}}} bar[FieldId{2}].emplace_string("bar"); expected[FieldId{1}].emplace_object(bar); expected[FieldId{2}].emplace_i32(2); expected[FieldId{4}] = asValueStruct< type::map>>( {{10, {{"foo", 1}}}, {20, {}}}); auto reserialized = protocol::serializeObject>( result.included, result.excluded); Object finalObj = parseObject>(*reserialized, false); EXPECT_EQ(finalObj, expected); } } TEST(Object, ParseObjectWithTwoMasks) { testParseObjectWithTwoMasks< ::apache::thrift::conformance::StandardProtocol::Compact>(); testParseObjectWithTwoMasks< ::apache::thrift::conformance::StandardProtocol::Binary>(); } TEST(Object, ToType) { using namespace type; Value v; v.ensure_bool() = true; EXPECT_EQ(toType(v), Type::create()); v.ensure_byte() = 1; EXPECT_EQ(toType(v), Type::create()); v.ensure_i16() = 1; EXPECT_EQ(toType(v), Type::create()); v.ensure_i32() = 1; EXPECT_EQ(toType(v), Type::create()); v.ensure_i64() = 1; EXPECT_EQ(toType(v), Type::create()); v.ensure_float() = 1; EXPECT_EQ(toType(v), Type::create()); v.ensure_double() = 1; EXPECT_EQ(toType(v), Type::create()); v.ensure_string() = "1"; EXPECT_EQ(toType(v), Type::create()); v.ensure_binary(); EXPECT_EQ(toType(v), Type::create()); Value elem; elem.ensure_i32() = 20; v.ensure_list() = {elem}; EXPECT_EQ(toType(v), Type::create(Type::create())); v.ensure_list().clear(); EXPECT_EQ(toType(v), Type::create(Type{})); v.ensure_set() = {elem}; EXPECT_EQ(toType(v), Type::create(Type::create())); v.ensure_set().clear(); EXPECT_EQ(toType(v), Type::create(Type{})); Value key, value; key.ensure_i32() = 10; value.ensure_string() = "10"; v.ensure_map() = {{key, value}}; EXPECT_EQ( toType(v), Type::create(Type::create(), Type::create())); v.ensure_map().clear(); EXPECT_EQ(toType(v), Type::create(Type{}, Type{})); Value obj; obj.ensure_object(); obj.as_object().type() = "facebook.com/to/obj"; obj.as_object()[FieldId{1}] = elem; EXPECT_EQ(toType(obj), Type::create("facebook.com/to/obj")); } TEST(ToAnyTest, simple) { using facebook::thrift::lib::test::Bar; Bar bar; bar.field_3() = {"foo", "bar", "baz"}; bar.field_4()->field_1() = 42; bar.field_4()->field_2() = "Everything"; auto any = type::TypeRegistry::generated().store( bar, type::StandardProtocol::Compact); auto serialized = CompactSerializer::serialize(bar).move(); Value value; value.ensure_object() = parseObject(*serialized); auto newAny = toAny( value, type::Type::create>(), type::StandardProtocol::Compact); EXPECT_EQ(newAny.type(), any.type()); EXPECT_EQ(newAny.protocol(), type::StandardProtocol::Compact); EXPECT_EQ( parseObject(newAny.data()), value.as_object()); // TODO(dokwon): Enable this when we wrap Thrift Any with Adapter. // EXPECT_EQ(any, toAny(value)); } TEST(ObjectTest, FromValueStruct) { Value value; value.emplace_bool() = true; EXPECT_TRUE(fromValueStruct(value)); value.emplace_byte() = 10; EXPECT_EQ(fromValueStruct(value), 10); value.emplace_i16() = 20; EXPECT_EQ(fromValueStruct(value), 20); value.emplace_i32() = 30; EXPECT_EQ(fromValueStruct(value), 30); value.emplace_i64() = 40; EXPECT_EQ(fromValueStruct(value), 40); value.emplace_float() = 50; EXPECT_EQ(fromValueStruct(value), 50); value.emplace_double() = 60; EXPECT_EQ(fromValueStruct(value), 60); Value v1; v1.emplace_i32() = 10; Value v2; v2.emplace_i32() = 20; Value v3; v3.emplace_i32() = 30; Value v4; v4.emplace_i32() = 40; // List value.emplace_list() = {v1, v3, v2, v4}; EXPECT_EQ( fromValueStruct>(value), (std::vector{10, 30, 20, 40})); // Set value.emplace_set() = {v1, v3, v2, v4}; EXPECT_EQ( fromValueStruct>(value), (std::set{10, 20, 30, 40})); // Map value.emplace_map() = {{v1, v3}, {v2, v4}}; EXPECT_EQ( (fromValueStruct>(value)), (std::map{{10, 30}, {20, 40}})); // Struct using facebook::thrift::lib::test::Bar; using Tag = type::struct_t; Bar bar; bar.field_3() = {"foo", "bar", "baz"}; bar.field_4()->field_1() = 42; bar.field_4()->field_2() = "Everything"; EXPECT_EQ(fromValueStruct(asValueStruct(bar)), bar); EXPECT_EQ(fromObjectStruct(asValueStruct(bar).as_object()), bar); } template void testSerializeValue( const type::native_type& t, bool deterministic = true) { // Test whether serializeValue output the correct size. // If result is deterministic, also test whether result matches op::decode's folly::IOBufQueue queue1, queue2; CompactProtocolWriter writer1, writer2; writer1.setOutput(&queue1); writer2.setOutput(&queue2); auto size1 = detail::serializeValue(writer1, asValueStruct(t)); auto size2 = op::encode(writer2, t); auto buf1 = queue1.moveAsValue(); auto buf2 = queue2.moveAsValue(); EXPECT_EQ(size1, buf1.computeChainDataLength()); EXPECT_EQ(size2, buf2.computeChainDataLength()); CompactProtocolReader reader1, reader2; reader1.setInput(&buf1); reader2.setInput(&buf2); type::native_type t1, t2, t3; op::decode(reader1, t1); op::decode(reader2, t2); detail::ProtocolValueToThriftValue{}(asValueStruct(t), t3); EXPECT_EQ(t, t1); EXPECT_EQ(t, t2); EXPECT_EQ(t, t3); EXPECT_TRUE(!deterministic || folly::IOBufEqualTo{}(buf1, buf2)); } TEST(ObjectTest, SerializeValueSize) { testSerializeValue(false); testSerializeValue(true); testSerializeValue(10); testSerializeValue(20); testSerializeValue(30); testSerializeValue(40); testSerializeValue(50.0); testSerializeValue(60.0); testSerializeValue>({10, 20, 30, 40}); testSerializeValue>({10, 20, 30, 40}, false); testSerializeValue>( {{10, "10"}, {20, "20"}}, false); // Struct using facebook::thrift::lib::test::Bar; Bar bar; bar.field_3() = {"foo", "bar", "baz"}; bar.field_4()->field_1() = 42; bar.field_4()->field_2() = "Everything"; // Result is not deterministic since field id can be out of order testSerializeValue>(bar, false); } TEST(ObjectTest, Adapter) { using test::basic::AdaptTestStruct; AdaptTestStruct s; s.timeout() = std::chrono::seconds(1); s.data()->value = 10; s.indirectionString()->val = "20"; s.double_wrapped_integer()->value.value = 30; s.custom()->val = 40; using Tag = type::struct_t; EXPECT_EQ(fromObjectStruct(asValueStruct(s).as_object()), s); } TEST(ObjectTest, ToThriftStructTypeMismatch) { using facebook::thrift::lib::test::Bar; using Tag = type::struct_t; Bar bar; bar.field_3() = {"foo", "bar", "baz"}; bar.field_4()->field_1() = 42; bar.field_4()->field_2() = "Everything"; { auto obj = asValueStruct(bar).as_object(); obj[FieldId{10}].as_list()[2].emplace_i32(); auto bar2 = fromObjectStruct(obj); EXPECT_TRUE(bar2.field_3()->empty()); EXPECT_EQ(bar2.field_4(), bar.field_4()); } { auto obj = asValueStruct(bar).as_object(); obj[FieldId{20}].as_object()[FieldId{2}].emplace_i32(); auto bar2 = fromObjectStruct(obj); EXPECT_EQ(bar2.field_3(), bar.field_3()); EXPECT_EQ(bar2.field_4()->field_1(), 42); EXPECT_EQ(bar2.field_4()->field_2(), ""); } } } // namespace } // namespace apache::thrift::protocol