/* * 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. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace apache::thrift::protocol { namespace detail { template decltype(auto) forward_elem(T& elem) { if constexpr (std::is_lvalue_reference_v) { return elem; } else { return static_cast(elem); } } template struct ValueHelper { template static void set(Value& result, T&& value) { if constexpr (false) { } else if constexpr (type::base_type_v == type::BaseType::Bool) { result.emplace_bool(value); } else if constexpr (type::base_type_v == type::BaseType::Byte) { result.emplace_byte(value); } else if constexpr (type::base_type_v == type::BaseType::I16) { result.emplace_i16(value); } else if constexpr (type::base_type_v == type::BaseType::I32) { result.emplace_i32(value); } else if constexpr (type::base_type_v == type::BaseType::I64) { result.emplace_i64(value); } else if constexpr (type::base_type_v == type::BaseType::Enum) { result.emplace_i32(static_cast(value)); } else if constexpr (type::base_type_v == type::BaseType::Float) { result.emplace_float(value); } else if constexpr (type::base_type_v == type::BaseType::Double) { result.emplace_double(value); } else if constexpr (type::base_type_v == type::BaseType::String) { result.emplace_string(std::forward(value)); } else { static_assert(folly::always_false, "Unknown Type Tag."); } } }; template <> struct ValueHelper { static void set(Value& result, folly::IOBuf value) { result.emplace_binary(std::move(value)); } static void set(Value& result, std::string_view value) { result.emplace_binary( folly::IOBuf{folly::IOBuf::COPY_BUFFER, value.data(), value.size()}); } static void set(Value& result, folly::ByteRange value) { result.emplace_binary( folly::IOBuf{folly::IOBuf::COPY_BUFFER, value.data(), value.size()}); } }; template struct ValueHelper> { template static void set(Value& result, C&& value) { auto& result_list = result.ensure_list(); for (auto& elem : value) { ValueHelper::set(result_list.emplace_back(), forward_elem(elem)); } } }; template struct ValueHelper> { template static void set(Value& result, C&& value) { auto& result_set = result.ensure_set(); for (auto& elem : value) { Value elem_val; ValueHelper::set(elem_val, forward_elem(elem)); result_set.emplace(std::move(elem_val)); } } }; template struct ValueHelper> { template static void set(Value& result, C&& value) { auto& result_map = result.ensure_map(); for (auto& entry : value) { Value key; ValueHelper::set(key, entry.first); ValueHelper::set(result_map[key], forward_elem(entry.second)); } } }; template struct ValueHelper> : ValueHelper {}; template > Value asValueStruct(T&& value) { Value result; ValueHelper::set(result, std::forward(value)); return result; } class BaseObjectAdapter { public: static constexpr ProtocolType protocolType() { return {}; } static constexpr bool kUsesFieldNames() { return true; } static constexpr bool kOmitsContainerSizes() { return false; } static constexpr bool kSortKeys() { return false; } static constexpr bool kHasIndexSupport() { return false; } }; class ObjectWriter : public BaseObjectAdapter { public: explicit ObjectWriter(Value* target) { assert(target != nullptr); cur_.emplace(target); } uint32_t writeStructBegin(const char* /*name*/) { beginValue().ensure_object(); return 0; } uint32_t writeStructEnd() { return endValue(Value::Type::objectValue); } uint32_t writeFieldBegin( const char* /*name*/, TType /*fieldType*/, int16_t fieldId) { auto result = cur(Value::Type::objectValue) .as_object() .members() ->emplace(fieldId, Value()); assert(result.second); cur_.push(&result.first->second); return 0; } uint32_t writeFieldEnd() { return 0; } uint32_t writeFieldStop() { return 0; } uint32_t writeMapBegin( const TType /*keyType*/, TType /*valType*/, uint32_t size) { // We cannot push reference to map elements on stack without first inserting // map elements. So push reference to temporary buffer on stack instead. allocBufferPushOnStack((size_t)size * 2); return 0; } uint32_t writeMapEnd() { // insert elements from buffer into mapValue std::vector mapKeyAndValues = getBufferFromStack(); assert(mapKeyAndValues.size() % 2 == 0); auto& mapVal = cur().ensure_map(); mapVal.reserve(mapKeyAndValues.size() / 2); for (size_t i = 0; i < mapKeyAndValues.size(); i += 2) { mapVal.emplace( std::move(mapKeyAndValues[i]), std::move(mapKeyAndValues[i + 1])); } return endValue(Value::Type::mapValue); } uint32_t writeListBegin(TType /*elemType*/, uint32_t size) { allocBufferPushOnStack(size); return 0; } uint32_t writeListEnd() { return endValue(Value::Type::listValue); } uint32_t writeSetBegin(TType /*elemType*/, uint32_t size) { // We cannot push reference to set elements on stack without first inserting // set elements. So push reference to temporary buffer on stack instead. allocBufferPushOnStack(size); return 0; } uint32_t writeSetEnd() { // insert elements from buffer into setValue std::vector setValues = getBufferFromStack(); auto& setVal = cur().ensure_set(); setVal.reserve(setValues.size()); for (size_t i = 0; i < setValues.size(); i++) { setVal.emplace(std::move(setValues[i])); } return endValue(Value::Type::setValue); } uint32_t writeBool(bool value) { ValueHelper::set(beginValue(), value); return endValue(Value::Type::boolValue); } uint32_t writeByte(int8_t value) { ValueHelper::set(beginValue(), value); return endValue(Value::Type::byteValue); } uint32_t writeI16(int16_t value) { ValueHelper::set(beginValue(), value); return endValue(Value::Type::i16Value); } uint32_t writeI32(int32_t value) { ValueHelper::set(beginValue(), value); return endValue(Value::Type::i32Value); } uint32_t writeI64(int64_t value) { ValueHelper::set(beginValue(), value); return endValue(Value::Type::i64Value); } uint32_t writeFloat(float value) { ValueHelper::set(beginValue(), value); return endValue(Value::Type::floatValue); } int32_t writeDouble(double value) { ValueHelper::set(beginValue(), value); return endValue(Value::Type::doubleValue); } uint32_t writeString(folly::StringPiece value) { // TODO: set in stringValue if UTF8 return writeBinary(value); } uint32_t writeBinary(folly::ByteRange value) { ValueHelper::set(beginValue(), value); return endValue(Value::Type::binaryValue); } uint32_t writeBinary(const folly::IOBuf& value) { ValueHelper::set(beginValue(), value); return endValue(Value::Type::binaryValue); } uint32_t writeBinary(const std::unique_ptr& str) { assert(str != nullptr); if (!str) { return 0; } return writeBinary(*str); } uint32_t writeBinary(folly::StringPiece value) { return writeBinary(folly::ByteRange(value)); } protected: std::stack cur_; void checkCur(Value::Type required) { (void)required; assert(cur().getType() == required); } Value& cur(Value::Type required) { checkCur(required); return *cur_.top(); } Value& cur() { assert(!cur_.empty()); return *cur_.top(); } Value& beginValue() { checkCur(Value::Type::__EMPTY__); return cur(); } uint32_t endValue(Value::Type required) { checkCur(required); cur_.pop(); return 0; } // Allocated temporary buffer in cur() and pushes buffer references on stack void allocBufferPushOnStack(size_t n) { // using listVal as temporary buffer std::vector& listVal = beginValue().ensure_list(); listVal.resize(n); for (auto itr = listVal.rbegin(); itr != listVal.rend(); ++itr) { cur_.push(&*itr); } } // Get temporary buffer from cur() std::vector getBufferFromStack() { return std::move(cur(Value::Type::listValue).as_list()); } }; template struct ValueHelper> { template static void set(Value& result, T&& value) { ObjectWriter writer(&result); op::encode>(writer, std::forward(value)); } }; template inline constexpr bool kIsStructured = false; template inline constexpr bool kIsStructured> = true; template inline constexpr bool kIsStructured> = true; template inline constexpr bool kIsStructured> = true; template <> inline constexpr bool kIsStructured = true; template <> inline constexpr bool kIsStructured = true; template <> inline constexpr bool kIsStructured = true; // Specialization for all structured types. template struct ValueHelper>> { template static void set(Value& result, T&& value) { ObjectWriter writer(&result); op::encode>>( writer, std::forward(value)); } }; template Value parseValue(Protocol& prot, TType arg_type, bool string_to_binary = true); // Schemaless deserialization of thrift serialized data of specified // thrift type into conformance::Value // Protocol: protocol to use eg. apache::thrift::BinaryProtocolReader // TODO: handle jsonprotocol template void parseValueInplace( Protocol& prot, TType arg_type, Value& result, bool string_to_binary = true) { switch (arg_type) { case protocol::T_BOOL: { bool boolv; prot.readBool(boolv); result.emplace_bool(boolv); break; } case protocol::T_BYTE: { int8_t bytev = 0; prot.readByte(bytev); result.emplace_byte(bytev); break; } case protocol::T_I16: { int16_t i16; prot.readI16(i16); result.emplace_i16(i16); break; } case protocol::T_I32: { int32_t i32; prot.readI32(i32); result.emplace_i32(i32); break; } case protocol::T_I64: { int64_t i64; prot.readI64(i64); result.emplace_i64(i64); break; } case protocol::T_DOUBLE: { double dub; prot.readDouble(dub); result.emplace_double(dub); break; } case protocol::T_FLOAT: { float flt; prot.readFloat(flt); result.emplace_float(flt); break; } case protocol::T_STRING: { if (string_to_binary) { auto& binaryValue = result.ensure_binary(); prot.readBinary(binaryValue); break; } auto& stringValue = result.emplace_string(); prot.readString(stringValue); break; } case protocol::T_STRUCT: { std::string name; int16_t fid; TType ftype; auto& objectValue = result.ensure_object(); prot.readStructBegin(name); while (true) { prot.readFieldBegin(name, ftype, fid); if (ftype == protocol::T_STOP) { break; } parseValueInplace( prot, ftype, objectValue[FieldId{fid}], string_to_binary); prot.readFieldEnd(); } prot.readStructEnd(); break; } case protocol::T_MAP: { TType keyType; TType valType; uint32_t size; auto& mapValue = result.ensure_map(); prot.readMapBegin(keyType, valType, size); mapValue.reserve(size); for (uint32_t i = 0; i < size; i++) { parseValueInplace( prot, valType, mapValue[parseValue(prot, keyType, string_to_binary)], string_to_binary); } prot.readMapEnd(); break; } case protocol::T_SET: { TType elemType; uint32_t size; auto& setValue = result.ensure_set(); prot.readSetBegin(elemType, size); setValue.reserve(size); for (uint32_t i = 0; i < size; i++) { setValue.insert(parseValue(prot, elemType, string_to_binary)); } prot.readSetEnd(); break; } case protocol::T_LIST: { TType elemType; uint32_t size; prot.readListBegin(elemType, size); auto& listValue = result.ensure_list(); listValue.resize(size); for (auto& v : listValue) { parseValueInplace(prot, elemType, v, string_to_binary); } prot.readListEnd(); break; } default: { TProtocolException::throwInvalidSkipType(arg_type); } } } template Value parseValue(Protocol& prot, TType arg_type, bool string_to_binary) { Value result; parseValueInplace(prot, arg_type, result, string_to_binary); return result; } // Returns an element in the list by ValueId. template const T& getByValueId(const std::vector& values, type::ValueId id) { return values[apache::thrift::util::zigzagToI64(static_cast(id))]; } inline TType getTType(const Value& val) { auto type = toTType(static_cast(val.getType())); if (type == protocol::T_UTF7 || type == protocol::T_UTF8 || type == protocol::T_UTF16) { return protocol::T_STRING; } return type; } inline void ensureSameType(const Value& a, TType b) { if (getTType(a) != b) { TProtocolException::throwInvalidFieldData(); } } template uint32_t serializeValue(Protocol& prot, const Value& value) { switch (value.getType()) { case Value::Type::boolValue: return prot.writeBool(value.as_bool()); case Value::Type::byteValue: return prot.writeByte(value.as_byte()); case Value::Type::i16Value: return prot.writeI16(value.as_i16()); case Value::Type::i32Value: return prot.writeI32(value.as_i32()); case Value::Type::i64Value: return prot.writeI64(value.as_i64()); case Value::Type::floatValue: return prot.writeFloat(value.as_float()); case Value::Type::doubleValue: return prot.writeDouble(value.as_double()); case Value::Type::stringValue: return prot.writeString(value.as_string()); case Value::Type::binaryValue: return prot.writeBinary(value.as_binary()); case Value::Type::listValue: { TType elemType = protocol::T_I64; const auto& listVal = value.as_list(); const auto size = listVal.size(); if (size > 0) { elemType = getTType(listVal.at(0)); } auto serializedSize = prot.writeListBegin(elemType, size); for (const auto& val : listVal) { ensureSameType(val, elemType); serializedSize += serializeValue(prot, val); } serializedSize += prot.writeListEnd(); return serializedSize; } case Value::Type::mapValue: { TType keyType = protocol::T_STRING; TType valueType = protocol::T_I64; const auto& mapVal = value.as_map(); const auto size = mapVal.size(); if (size > 0) { keyType = getTType(mapVal.begin()->first); valueType = getTType(mapVal.begin()->second); } auto serializedSize = prot.writeMapBegin(keyType, valueType, size); for (const auto& [key, val] : mapVal) { ensureSameType(key, keyType); ensureSameType(val, valueType); serializedSize += serializeValue(prot, key); serializedSize += serializeValue(prot, val); } serializedSize += prot.writeMapEnd(); return serializedSize; } case Value::Type::setValue: { TType elemType = protocol::T_I64; const auto& setVal = value.as_set(); const auto size = setVal.size(); if (size > 0) { elemType = getTType(*setVal.begin()); } auto serializedSize = prot.writeSetBegin(elemType, size); for (const auto& val : setVal) { ensureSameType(val, elemType); serializedSize += serializeValue(prot, val); } serializedSize += prot.writeSetEnd(); return serializedSize; } case Value::Type::objectValue: { return serializeObject(prot, value.as_object()); } default: { TProtocolException::throwInvalidFieldData(); } } return 0; } template uint32_t serializeObject(Protocol& prot, const Object& obj) { uint32_t serializedSize = 0; serializedSize += prot.writeStructBegin(""); if (obj.members()) { for (const auto& [fieldID, fieldVal] : *obj.members()) { auto fieldType = getTType(fieldVal); serializedSize += prot.writeFieldBegin("", fieldType, fieldID); serializedSize += serializeValue(prot, fieldVal); serializedSize += prot.writeFieldEnd(); } } serializedSize += prot.writeFieldStop(); serializedSize += prot.writeStructEnd(); return serializedSize; } // Writes the field from raw data in MaskedData. template void writeRawField( Protocol& prot, FieldId fieldId, const MaskedProtocolData& protocolData, const MaskedData& maskedData) { const auto& nestedMaskedData = maskedData.fields_ref().value().at(fieldId); // When value doesn't exist in the object, maskedData should have full field. if (!nestedMaskedData.full_ref()) { throw std::runtime_error("incompatible value and maskedData"); } type::ValueId valueId = nestedMaskedData.full_ref().value(); const EncodedValue& value = getByValueId(*protocolData.values(), valueId); prot.writeFieldBegin( "", toTType(*value.wireType()), folly::to_underlying(fieldId)); prot.writeRaw(*value.data()); prot.writeFieldEnd(); } // Writes the map value from raw data in MaskedData. template void writeRawMapValue( Protocol& prot, TType valueType, const MaskedProtocolData& protocolData, const MaskedData& maskedData) { // When value doesn't exist in the object, maskedData should have full field. if (!maskedData.full_ref()) { throw std::runtime_error("incompatible value and maskedData"); } type::ValueId valueId = maskedData.full_ref().value(); const EncodedValue& value = getByValueId(*protocolData.values(), valueId); if (toTType(*value.wireType()) != valueType) { TProtocolException::throwInvalidFieldData(); } prot.writeRaw(*value.data()); } template void serializeObject( Protocol& prot, const Object& obj, const MaskedProtocolData& protocolData, const MaskedData& maskedData) { if (!maskedData.fields_ref()) { throw std::runtime_error("incompatible value and maskedData"); } prot.writeStructBegin(""); // It is more efficient to serialize with sorted field ids. std::set fieldIds{}; for (const auto& [fieldId, _] : obj) { fieldIds.insert(FieldId{fieldId}); } for (const auto& [fieldId, _] : *maskedData.fields_ref()) { fieldIds.insert(fieldId); } for (auto fieldId : fieldIds) { if (!obj.contains(fieldId)) { // no need to serialize the value writeRawField(prot, fieldId, protocolData, maskedData); continue; } // get type from value const auto& fieldVal = obj.at(fieldId); auto fieldType = getTType(fieldVal); prot.writeFieldBegin("", fieldType, folly::to_underlying(fieldId)); // just serialize the value if (folly::get_ptr(*maskedData.fields_ref(), fieldId) == nullptr) { serializeValue(prot, fieldVal); } else { // recursively serialize value with maskedData const auto& nextMaskedData = maskedData.fields_ref().value().at(fieldId); serializeValue(prot, fieldVal, protocolData, nextMaskedData); } prot.writeFieldEnd(); } prot.writeFieldStop(); prot.writeStructEnd(); return; } // We can assume that if value type is a struct, fields in maskedData is // active, and if value type is a map, values in maskedData is active. // It throws a runtime error if value and maskedData are incompatible. template void serializeValue( Protocol& prot, const Value& value, const MaskedProtocolData& protocolData, const MaskedData& maskedData) { switch (value.getType()) { case Value::Type::objectValue: { return serializeObject(prot, value.as_object(), protocolData, maskedData); } case Value::Type::mapValue: { if (!maskedData.values_ref()) { throw std::runtime_error("incompatible value and maskedData"); } TType keyType = protocol::T_STRING; TType valueType = protocol::T_I64; // compute size, keyType, and valueType const auto& mapVal = value.as_map(); auto size = mapVal.size(); if (size > 0) { keyType = getTType(mapVal.begin()->first); valueType = getTType(mapVal.begin()->second); } for (auto& [keyValueId, nestedMaskedData] : *maskedData.values_ref()) { const Value& key = getByValueId(*protocolData.keys(), keyValueId); if (size == 0) { // need to set keyType and valueType keyType = getTType(key); type::ValueId valueId = nestedMaskedData.full_ref().value(); valueType = toTType( *getByValueId(*protocolData.values(), valueId).wireType()); } if (folly::get_ptr(mapVal, key) == nullptr) { ++size; } } // Remember which keys are in the maskedData. Note that the ownership of // keys are managed by the maskedData. std::unordered_set< std::reference_wrapper, std::hash, std::equal_to> keys; prot.writeMapBegin(keyType, valueType, size); for (auto& [keyValueId, nestedMaskedData] : *maskedData.values_ref()) { const Value& key = getByValueId(*protocolData.keys(), keyValueId); keys.insert(key); ensureSameType(key, keyType); serializeValue(prot, key); // no need to serialize the value if (folly::get_ptr(mapVal, key) == nullptr) { writeRawMapValue(prot, valueType, protocolData, nestedMaskedData); continue; } // recursively serialize value with maskedData const Value& val = mapVal.at(key); ensureSameType(val, valueType); serializeValue(prot, val, protocolData, nestedMaskedData); } for (const auto& [key, val] : mapVal) { if (keys.find(key) != keys.end()) { // already serailized continue; } ensureSameType(key, keyType); ensureSameType(val, valueType); serializeValue(prot, key); serializeValue(prot, val); } prot.writeMapEnd(); return; } default: { serializeValue(prot, value); } } } template void serializeValue(const Value& val, folly::IOBufQueue& queue) { Protocol prot; prot.setOutput(&queue); serializeValue(prot, val); } template std::unique_ptr serializeValue(const Value& val) { folly::IOBufQueue queue(folly::IOBufQueue::cacheChainLength()); serializeValue(val, queue); return queue.move(); } type::Type toType(const protocol::Value& value); type::AnyData toAny( const Value& value, type::Type type, type::Protocol protocol); /** * Schemaless conversion from any -> value contained within * Currently supports only compact/binary protocols */ Value parseValueFromAny(const type::AnyStruct& any); Value parseValueFromAny(const type::AnyData& any); // anyObject's schema much match that of AnyStruct Value parseValueFromAnyObject(const Object& anyObject); template struct ProtocolValueToThriftValue; template <> struct ProtocolValueToThriftValue { // return whether conversion succeed template bool operator()(const Value& value, T& b) const { if (auto p = value.if_bool()) { b = *p; return true; } return false; } }; template <> struct ProtocolValueToThriftValue { template bool operator()(const Value& value, T& i) const { if (auto p = value.if_byte()) { i = *p; return true; } return false; } }; template <> struct ProtocolValueToThriftValue { template bool operator()(const Value& value, T& i) const { if (auto p = value.if_i16()) { i = *p; return true; } return false; } }; template <> struct ProtocolValueToThriftValue { template bool operator()(const Value& value, T& i) const { if (auto p = value.if_i32()) { i = *p; return true; } return false; } }; template <> struct ProtocolValueToThriftValue { template bool operator()(const Value& value, T& i) const { if (auto p = value.if_i64()) { i = *p; return true; } return false; } }; template <> struct ProtocolValueToThriftValue { template bool operator()(const Value& value, T& f) const { if (auto p = value.if_float()) { f = *p; return true; } return false; } }; template <> struct ProtocolValueToThriftValue { template bool operator()(const Value& value, T& d) const { if (auto p = value.if_double()) { d = *p; return true; } return false; } }; template <> struct ProtocolValueToThriftValue { template bool operator()(const Value& value, StrType& s) const { if (auto p = value.if_string()) { s = *p; return true; } if (auto p = value.if_binary()) { s.clear(); folly::io::Cursor cursor{&*p}; while (!cursor.isAtEnd()) { const auto buf = cursor.peekBytes(); s.append((const char*)buf.data(), buf.size()); cursor += buf.size(); } return true; } return false; } bool operator()(const Value& value, std::unique_ptr& s) const { if (auto p = value.if_string()) { s = folly::IOBuf::copyBuffer(p->data(), p->size()); return true; } if (auto p = value.if_binary()) { s = p->clone(); return true; } return false; } bool operator()(const Value& value, folly::IOBuf& s) const { std::unique_ptr buf; if (operator()(value, buf)) { s = *buf; return true; } return false; } }; // We can't distinguish string/binary type in binary/compact protocol, // Thus we need to handle them in the same way. template <> struct ProtocolValueToThriftValue : ProtocolValueToThriftValue {}; template struct ProtocolValueToThriftValue> { template bool operator()(const Value& value, U& t) const { if (auto p = value.if_i32()) { t = static_cast(*p); return true; } return false; } }; template struct ProtocolValueToThriftValue> { template bool operator()(const Value& value, ListType& list) const { auto p = value.if_list(); if (!p) { return false; } list.clear(); folly::reserve_if_available(list, p->size()); for (auto&& v : *p) { if (!ProtocolValueToThriftValue{}(v, list.emplace_back())) { return false; } } return true; } }; template struct ProtocolValueToThriftValue> { template bool operator()(const Value& value, SetType& set) const { type::native_type elem; auto p = value.if_set(); if (!p) { return false; } set.clear(); folly::reserve_if_available(set, p->size()); for (auto&& v : *p) { apache::thrift::op::clear(elem); if (!ProtocolValueToThriftValue{}(v, elem)) { return false; } set.emplace_hint(set.end(), std::move(elem)); } return true; } }; template struct ProtocolValueToThriftValue> { template bool operator()(const Value& value, MapType& map) const { type::native_type key; type::native_type val; auto p = value.if_map(); if (!p) { return false; } map.clear(); folly::reserve_if_available(map, p->size()); for (auto&& [k, v] : *p) { apache::thrift::op::clear(key); apache::thrift::op::clear(val); if (!ProtocolValueToThriftValue{}(k, key)) { return false; } if (!ProtocolValueToThriftValue{}(v, val)) { return false; } map.emplace_hint(map.end(), std::move(key), std::move(val)); } return true; } }; template struct ProtocolValueToThriftValue> : ProtocolValueToThriftValue {}; template struct ProtocolValueToThriftValue> { template bool operator()(const ObjectOrValue& value, U& m) const { // TODO: Optimize in-place adapter type::native_type orig; auto ret = ProtocolValueToThriftValue{}(value, orig); m = Adapter::fromThrift(std::move(orig)); return ret; } }; template struct ProtocolValueToThriftValue< type::field>> { template bool operator()(const ObjectOrValue& value, U& m, Struct&) const { return ProtocolValueToThriftValue{}(value, m); } }; template struct ProtocolValueToThriftValue< type::field, FieldContext>> { using field_adapted_tag = type::field, FieldContext>; static_assert(type::is_concrete_v); template constexpr adapt_detail:: if_not_field_adapter, Struct, bool> operator()(const ObjectOrValue& value, U& m, Struct&) const { return ProtocolValueToThriftValue>{}(value, m); } template constexpr adapt_detail:: if_field_adapter, Struct, bool> operator()(const ObjectOrValue& value, U& m, Struct& strct) const { // TODO: Optimize in-place adapter type::native_type orig; bool ret = ProtocolValueToThriftValue{}(value, orig); m = adapt_detail::fromThriftField(std::move(orig), strct); return ret; } }; template struct ProtocolValueToThriftValueStructure { bool operator()(const Object& obj, T& s) const { for (auto&& kv : obj) { op::invoke_by_field_id( static_cast(kv.first), [&](auto id) { using Id = decltype(id); op::get_native_type t; if (ProtocolValueToThriftValue>{}( kv.second, t, s)) { using Ref = op::get_field_ref; if constexpr (apache::thrift::detail::is_shared_or_unique_ptr_v< Ref>) { op::get(s) = std::make_unique>(std::move(t)); } else { op::get(s) = std::move(t); } } }, [] {}); } return true; } bool operator()(const Value& value, T& s) const { if (auto p = value.if_object()) { operator()(*p, s); return true; } return false; } }; template struct ProtocolValueToThriftValue> : ProtocolValueToThriftValueStructure {}; template struct ProtocolValueToThriftValue> : ProtocolValueToThriftValueStructure {}; template struct ProtocolValueToThriftValue> : ProtocolValueToThriftValueStructure {}; } // namespace detail } // namespace apache::thrift::protocol