/* * 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 namespace apache { namespace thrift { namespace ident { struct assign; } class BinaryProtocolReader; class BinaryProtocolWriter; class CompactProtocolReader; class CompactProtocolWriter; namespace op { namespace detail { template inline constexpr bool kProtocolSupportsDynamicPatch = std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v; /// A patch adapter that only supports 'assign', /// which is the minimum any patch should support. /// /// The `Patch` template parameter must be a Thrift struct with the following /// fields: /// * `optional T assign` /// /// If the assign only patch is deserialized from a dynamic patch, it might have /// other operations besides assign operation. template class AssignPatch : public BaseAssignPatch> { using Base = BaseAssignPatch; using T = typename Base::value_type; using Tag = get_type_tag; public: using Base::apply; using Base::Base; void assign(T a) { Base::assign(std::move(a)); dynPatch_.reset(); } auto& operator=(T a) { assign(std::move(a)); return *this; } void apply(T& val) const { if (dynPatch_) { auto value = protocol::asValueStruct(val); protocol::applyPatch(*dynPatch_, value); val = protocol::fromValueStruct(value); } else if (auto p = data_.assign()) { val = data_.assign().value(); } } void merge(AssignPatch other) { if (dynPatch_ && other.dynPatch_) { XLOG_EVERY_MS(CRITICAL, 10000) << "Merging dynamic patch is not implemented. " "The merged result will be incorrect.\n" "First Patch = " << folly::toPrettyJson( apache::thrift::protocol::toDynamic(*dynPatch_)) << "\nSecond Patch = " << folly::toPrettyJson( apache::thrift::protocol::toDynamic(*other.dynPatch_)); // Do nothing, which is the old behavior return; } if (!other.dynPatch_) { if (auto p = other.data_.assign()) { assign(std::move(*p)); } return; } if (auto p = data_.assign()) { other.apply(*p); } else { dynPatch_ = std::move(other.dynPatch_); DCHECK(dynPatch_.value().members()); } } template uint32_t encode(Protocol& prot) const { if (!kProtocolSupportsDynamicPatch || !dynPatch_) { return op::encode>(prot, data_); } return protocol::detail::serializeObject(prot, *dynPatch_); } template void decode(Protocol& prot) { if (!kProtocolSupportsDynamicPatch) { return op::decode>(prot, data_); } createFromObject( protocol::detail::parseValue(prot, TType::T_STRUCT).as_object()); } bool empty() const { return !dynPatch_.has_value() && Base::empty(); } void reset() { dynPatch_.reset(); Base::reset(); } private: using Base::data_; std::optional dynPatch_; void createFromObject(protocol::Object v) { data_ = protocol::fromObjectStruct>(v); if (data_.assign()) { dynPatch_.reset(); } else { dynPatch_ = std::move(v); Base::reset(); DCHECK(dynPatch_.value().members()); } } template friend struct protocol::detail::ProtocolValueToThriftValue; }; /// Patch for a Thrift bool. /// /// The `Patch` template parameter must be a Thrift struct with the following /// fields: /// * `optional T assign` /// * `terse bool clear` /// * `terse bool invert` template class BoolPatch : public BaseClearPatch> { using Base = BaseClearPatch; using T = typename Base::value_type; public: using Base::apply; using Base::Base; using Base::operator=; using Base::clear; /// Inverts the bool. void invert() { auto& val = assignOr(*data_.invert()); val = !val; } /// @copybrief AssignPatch::customVisit /// /// Users should provide a visitor with the following methods /// /// struct Visitor { /// void assign(bool); /// void clear(); /// void invert(); /// } /// /// For example: /// /// auto patch = BoolPatch::createClear(); /// patch = !patch; /// /// `patch.customVisit(v)` will invoke the following methods /// /// v.clear(); /// v.invert(); template void customVisit(Visitor&& v) const { if (false) { // Test whether the required methods exist in Visitor v.assign(T{}); v.clear(); v.invert(); } if (!Base::template customVisitAssignAndClear(v) && *data_.invert()) { std::forward(v).invert(); } } void apply(T& val) const { struct Visitor { T& v; void assign(T b) { v = b; } void clear() { v = false; } void invert() { v = !v; } }; return customVisit(Visitor{val}); } private: using Base::assignOr; using Base::data_; friend BoolPatch operator!(BoolPatch val) { return (val.invert(), val); } }; /// Patch for a numeric Thrift types. /// /// The `Patch` template parameter must be a Thrift struct with the following /// fields: /// * `optional T assign` /// * `terse bool clear` /// * `terse T add` template class NumberPatch : public BaseClearPatch> { using Base = BaseClearPatch; using T = typename Base::value_type; using Tag = type::infer_tag; public: using Base::apply; using Base::Base; using Base::operator=; using Base::clear; /// Increases the value. template void add(U&& val) { assignOr(*data_.add()) += std::forward(val); } /// Decreases the value. template void subtract(U&& val) { assignOr(*data_.add()) -= std::forward(val); } /// @copybrief AssignPatch::customVisit /// /// Users should provide a visitor with the following methods /// /// struct Visitor { /// void assign(Number); /// void clear(); /// void add(Number); /// } /// /// For example: /// /// auto patch = I32Patch::createClear(); /// patch += 10; /// /// `patch.customVisit(v)` will invoke the following methods /// /// v.clear(); /// v.add(10); template void customVisit(Visitor&& v) const { if (false) { // Test whether the required methods exist in Visitor v.assign(T{}); v.clear(); v.add(T{}); } if (!Base::template customVisitAssignAndClear(v)) { v.add(*data_.add()); } } void apply(T& val) const { struct Visitor { T& v; void assign(const T& t) { v = t; } void clear() { v = 0; } void add(const T& t) { v += t; } }; return customVisit(Visitor{val}); } /// Increases the value. template NumberPatch& operator+=(U&& val) { add(std::forward(val)); return *this; } /// Decreases the value. template NumberPatch& operator-=(U&& val) { subtract(std::forward(val)); return *this; } private: using Base::assignOr; using Base::data_; template friend NumberPatch operator+(NumberPatch lhs, U&& rhs) { lhs.add(std::forward(rhs)); return lhs; } template friend NumberPatch operator+(U&& lhs, NumberPatch rhs) { rhs.add(std::forward(lhs)); return rhs; } template friend NumberPatch operator-(NumberPatch lhs, U&& rhs) { lhs.subtract(std::forward(rhs)); return lhs; } }; /// Base class for string/binary patch types. /// /// The `Patch` template parameter must be a Thrift struct with the following /// fields: /// * `optional T assign` /// * `terse bool clear` /// * `terse T append` /// * `terse T prepend` template class BaseStringPatch : public BaseContainerPatch { using Base = BaseContainerPatch; using T = typename Base::value_type; public: using Base::Base; using Base::operator=; /// Appends a string. template Derived& operator+=(U&& val) { derived().append(std::forward(val)); return derived(); } /// @copybrief AssignPatch::customVisit /// /// Users should provide a visitor with the following methods /// /// struct Visitor { /// void assign(const String&); /// void clear(); /// void prepend(const String&); /// void append(const String&); /// } /// /// For example: /// /// auto patch = StringPatch::createPrepend("("); /// patch += ")"; /// /// `patch.customVisit(v)` will invoke the following methods /// /// v.prepend("("); /// v.append(")"); template void customVisit(Visitor&& v) const { if (false) { // Test whether the required methods exist in Visitor v.assign(T{}); v.clear(); v.prepend(T{}); v.append(T{}); } if (!Base::template customVisitAssignAndClear(std::forward(v))) { std::forward(v).prepend(*data_.prepend()); std::forward(v).append(*data_.append()); } } protected: using Base::assignOr; using Base::data_; using Base::derived; private: /// Concat two strings. template friend Derived operator+(Derived lhs, U&& rhs) { return lhs += std::forward(rhs); } /// Concat two strings. template friend Derived operator+(U&& lhs, Derived rhs) { rhs.prepend(std::forward(lhs)); return rhs; } }; /// Patch for a Thrift string. /// /// The `Patch` template parameter must be a Thrift struct with the following /// fields: /// * `optional string assign` /// * `terse bool clear` /// * `terse string append` /// * `terse string prepend` template class StringPatch : public BaseStringPatch> { using Base = BaseStringPatch; using T = typename Base::value_type; public: using Base::apply; using Base::Base; using Base::operator=; /// Appends a string. template void append(Args&&... args) { assignOr(*data_.append()).append(std::forward(args)...); } /// Prepends a string. template void prepend(U&& val) { T& cur = assignOr(*data_.prepend()); cur = std::forward(val) + std::move(cur); } void apply(T& val) const { struct Visitor { T& v; void assign(const T& t) { v = t; } void clear() { v = ""; } // TODO: Optimize this void prepend(const T& t) { v = t + v; } void append(const T& t) { v += t; } }; return Base::customVisit(Visitor{val}); } private: using Base::assignOr; using Base::data_; }; inline const folly::IOBuf& emptyIOBuf() { static const auto empty = folly::IOBuf::wrapBufferAsValue(folly::StringPiece("")); return empty; } /// Patch for a Thrift Binary. /// /// The `Patch` template parameter must be a Thrift struct with the following /// fields: /// * `optional standard.ByteBuffer assign` /// * `terse bool clear` /// * `terse standard.ByteBuffer append` /// * `terse standard.ByteBuffer prepend` template class BinaryPatch : public BaseStringPatch> { using Base = BaseStringPatch; using T = typename Base::value_type; public: using Base::apply; using Base::assign; using Base::Base; void assign(std::string s) { std::string* p = new std::string(std::move(s)); assign(folly::IOBuf( folly::IOBuf::TAKE_OWNERSHIP, p->data(), p->size(), [](void*, void* userData) { delete static_cast(userData); }, static_cast(p))); } template BinaryPatch& operator=(T&& other) { return assign(std::forward(other)), *this; } /// Appends a binary string. template void append(Args&&... args) { folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()}; auto& cur = assignOr(*data_.append()); queue.append(cur); queue.append(std::forward(args)...); cur = queue.moveAsValue(); } /// Prepends a binary string. template void prepend(U&& val) { auto& cur = assignOr(*data_.prepend()); folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()}; queue.append(std::forward(val)); queue.append(cur); cur = queue.moveAsValue(); } void apply(T& val) const { struct Visitor { T& v; std::deque bufs = {&v}; void assign(const T& t) { bufs = {&t}; } void clear() { bufs = {&emptyIOBuf()}; } void prepend(const T& t) { bufs.push_front(&t); } void append(const T& t) { bufs.push_back(&t); } ~Visitor() { folly::IOBufQueue queue{folly::IOBufQueue::cacheChainLength()}; for (auto buf : bufs) { queue.append(*buf); } v = queue.moveAsValue(); } }; return Base::customVisit(Visitor{val}); } void apply(std::string& val) const { folly::IOBuf buf; apply(buf); val = buf.to(); } private: using Base::assignOr; using Base::data_; }; } // namespace detail } // namespace op namespace protocol::detail { // When converting protocol::Object to AssignPatch, we need special logic here // to handle the case when protocol::Object is a dynamic patch and it might // contain operations other than assign. template struct ProtocolValueToThriftValue>, type::struct_t>> { bool operator()( const Object& obj, op::detail::AssignPatch& patch) { patch.createFromObject(obj); return true; } bool operator()( const Value& obj, op::detail::AssignPatch& patch) { if (auto p = obj.if_object()) { operator()(*p, patch); return true; } return false; } }; } // namespace protocol::detail } // namespace thrift } // namespace apache