diff options
author | arkady-e1ppa <arkady-e1ppa@yandex-team.com> | 2024-10-22 18:15:25 +0300 |
---|---|---|
committer | arkady-e1ppa <arkady-e1ppa@yandex-team.com> | 2024-10-22 19:13:37 +0300 |
commit | 477bb1cd2d7d44f218454a8a4137a849eb7e64fa (patch) | |
tree | a9a2acf38b06ab47ecc6a377c82e5ec494ab588b | |
parent | 0bd28574f1df329ac2b07bf9081184f739bcb483 (diff) | |
download | ydb-477bb1cd2d7d44f218454a8a4137a849eb7e64fa.tar.gz |
YT-21233: Introduce TErrorAttributes as an opaque type for attributes to hide dependencies on IAttributeDictionary's API
Plan:
1) Remove `IAttributedDictionary` type from the public API. \+
2) Remove `Set` method from public API in favor of `operator<<=`. \+
3) Adopt `ConvertTo<T>` (or other name) CPO with proper extension into `NYT::NYson::ConvertTo` from `yt/core`.
4) Use CPO from (3) to eliminate direct dependency on `yt/core` of `Get/Find` methods from attributes API.
5) Adopt `ConvertToYsonString` (or other name) CPO with proper extension into `yt/core` customisations.
6) Use CPO from (5) to eliminate direct dependency on `yt/core` of `TErrorAttribute` ctor.
7) Swap attributes implementation to the one which doesn’t use `IAttributeDictionary`.
8) At this point `stripped_error*` can be moved to library/cpp/yt and so can recursively dependant on THROW macro methods `Get/Find/…`.
9) Adjust CPO’s to work with `std::string` instead of `TYsonString` assuming text format to be used (maybe `TString` for now).
10) Remove dep of `library/cpp/yt/error` on `yson` entirely.
This pull request covers steps 1 and 2 laying foundation to 3-4 being implemented in the next one
commit_hash:e899bccdef4ebed321ea2eb93215282694c261ac
-rw-r--r-- | library/cpp/yt/error/error_attributes-inl.h | 72 | ||||
-rw-r--r-- | library/cpp/yt/error/error_attributes.cpp | 62 | ||||
-rw-r--r-- | library/cpp/yt/error/error_attributes.h | 105 | ||||
-rw-r--r-- | library/cpp/yt/error/mergeable_dictionary.h | 57 | ||||
-rw-r--r-- | library/cpp/yt/error/public.h | 17 | ||||
-rw-r--r-- | library/cpp/yt/error/ya.make | 2 | ||||
-rw-r--r-- | yt/yt/client/api/security_client.cpp | 16 | ||||
-rw-r--r-- | yt/yt/client/tablet_client/table_mount_cache_detail.cpp | 4 | ||||
-rw-r--r-- | yt/yt/core/misc/error.cpp | 42 | ||||
-rw-r--r-- | yt/yt/core/misc/stripped_error-inl.h | 14 | ||||
-rw-r--r-- | yt/yt/core/misc/stripped_error.cpp | 132 | ||||
-rw-r--r-- | yt/yt/core/misc/stripped_error.h | 13 | ||||
-rw-r--r-- | yt/yt/core/misc/unittests/error_ut.cpp | 50 | ||||
-rw-r--r-- | yt/yt/core/rpc/helpers.cpp | 4 | ||||
-rw-r--r-- | yt/yt/core/ytree/attributes-inl.h | 58 | ||||
-rw-r--r-- | yt/yt/core/ytree/exception_helpers.cpp | 2 |
16 files changed, 558 insertions, 92 deletions
diff --git a/library/cpp/yt/error/error_attributes-inl.h b/library/cpp/yt/error/error_attributes-inl.h new file mode 100644 index 0000000000..a05e62d0e9 --- /dev/null +++ b/library/cpp/yt/error/error_attributes-inl.h @@ -0,0 +1,72 @@ +#ifndef ERROR_ATTRIBUTES_INL_H_ +#error "Direct inclusion of this file is not allowed, include error_attributes.h" +// For the sake of sane code completion. +#include "error_attributes.h" +#endif + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +T TErrorAttributes::GetAndRemove(const TString& key) +{ + auto result = Get<T>(key); + Remove(key); + return result; +} + +template <class T> +T TErrorAttributes::Get(TStringBuf key, const T& defaultValue) const +{ + return Find<T>(key).value_or(defaultValue); +} + +template <class T> +T TErrorAttributes::GetAndRemove(const TString& key, const T& defaultValue) +{ + auto result = Find<T>(key); + if (result) { + Remove(key); + return *result; + } else { + return defaultValue; + } +} + +template <class T> +typename TOptionalTraits<T>::TOptional TErrorAttributes::FindAndRemove(const TString& key) +{ + auto result = Find<T>(key); + if (result) { + Remove(key); + } + return result; +} + +template <CMergeableDictionary TDictionary> +void TErrorAttributes::MergeFrom(const TDictionary& dict) +{ + using TTraits = TMergeDictionariesTraits<TDictionary>; + + for (const auto& [key, value] : TTraits::MakeIterableView(dict)) { + SetYson(key, value); + } +} + +//////////////////////////////////////////////////////////////////////////////// + +template <> +struct TMergeDictionariesTraits<TErrorAttributes> +{ + static auto MakeIterableView(const TErrorAttributes& attributes) + { + return attributes.ListPairs(); + } +}; + +static_assert(CMergeableDictionary<TErrorAttributes>); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/error/error_attributes.cpp b/library/cpp/yt/error/error_attributes.cpp new file mode 100644 index 0000000000..2c1b542314 --- /dev/null +++ b/library/cpp/yt/error/error_attributes.cpp @@ -0,0 +1,62 @@ +#include "error_attributes.h" + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +TErrorAttributes::TErrorAttributes(void* attributes) + : Attributes_(attributes) +{ } + +void TErrorAttributes::Clear() +{ + for (const auto& key : ListKeys()) { + Remove(key); + } +} + +NYson::TYsonString TErrorAttributes::GetYsonAndRemove(const TString& key) +{ + auto result = GetYson(key); + Remove(key); + return result; +} + +bool TErrorAttributes::Contains(TStringBuf key) const +{ + return FindYson(key).operator bool(); +} + +bool operator == (const TErrorAttributes& lhs, const TErrorAttributes& rhs) +{ + auto lhsPairs = lhs.ListPairs(); + auto rhsPairs = rhs.ListPairs(); + if (lhsPairs.size() != rhsPairs.size()) { + return false; + } + + std::sort(lhsPairs.begin(), lhsPairs.end(), [] (const auto& lhs, const auto& rhs) { + return lhs.first < rhs.first; + }); + std::sort(rhsPairs.begin(), rhsPairs.end(), [] (const auto& lhs, const auto& rhs) { + return lhs.first < rhs.first; + }); + + for (auto index = 0; index < std::ssize(lhsPairs); ++index) { + if (lhsPairs[index].first != rhsPairs[index].first) { + return false; + } + } + + for (auto index = 0; index < std::ssize(lhsPairs); ++index) { + if (lhsPairs[index].second != rhsPairs[index].second) { + return false; + } + } + + return true; +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/error/error_attributes.h b/library/cpp/yt/error/error_attributes.h new file mode 100644 index 0000000000..88771f8360 --- /dev/null +++ b/library/cpp/yt/error/error_attributes.h @@ -0,0 +1,105 @@ +#pragma once + +#include "mergeable_dictionary.h" + +#include <library/cpp/yt/misc/optional.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +// For now this is just an opaque handle to error attributes +// used to remove dependency on IAttributeDictionary in public API. +// Eventually it would be a simple hash map. +// NB(arkady-e1ppa): For now most methods are defined in yt/yt/core/misc/stripped_error.cpp +// eventually they will be moved here. +class TErrorAttributes +{ +public: + using TKey = TString; + using TValue = NYson::TYsonString; + using TKeyValuePair = std::pair<TKey, TValue>; + + //! Returns the list of all keys in the dictionary. + std::vector<TString> ListKeys() const; + + //! Returns the list of all key-value pairs in the dictionary. + std::vector<TKeyValuePair> ListPairs() const; + + //! Returns the value of the attribute (null indicates that the attribute is not found). + NYson::TYsonString FindYson(TStringBuf key) const; + + //! Sets the value of the attribute. + void SetYson(const TString& key, const NYson::TYsonString& value); + + //! Removes the attribute. + //! Returns |true| if the attribute was removed or |false| if there is no attribute with this key. + bool Remove(const TString& key); + + //! Removes all attributes. + void Clear(); + + //! Returns the value of the attribute (throws an exception if the attribute is not found). + NYson::TYsonString GetYson(TStringBuf key) const; + + //! Same as #GetYson but removes the value. + NYson::TYsonString GetYsonAndRemove(const TString& key); + + //! Returns |true| iff the given key is present. + bool Contains(TStringBuf key) const; + + // TODO(arkady-e1ppa): By default deserialization is located at yt/core + // consider using deserialization of some default types (guid, string, int, double) + // to be supported and everything else not supported without inclusion of yt/core. + //! Finds the attribute and deserializes its value. + //! Throws if no such value is found. + template <class T> + T Get(TStringBuf key) const; + + //! Same as #Get but removes the value. + template <class T> + T GetAndRemove(const TString& key); + + //! Finds the attribute and deserializes its value. + //! Uses default value if no such attribute is found. + template <class T> + T Get(TStringBuf key, const T& defaultValue) const; + + //! Same as #Get but removes the value if it exists. + template <class T> + T GetAndRemove(const TString& key, const T& defaultValue); + + //! Finds the attribute and deserializes its value. + //! Returns null if no such attribute is found. + template <class T> + typename TOptionalTraits<T>::TOptional Find(TStringBuf key) const; + + //! Same as #Find but removes the value if it exists. + template <class T> + typename TOptionalTraits<T>::TOptional FindAndRemove(const TString& key); + + template <CMergeableDictionary TDictionary> + void MergeFrom(const TDictionary& dict); + +private: + void* Attributes_; // IAttributesDictionary* + + friend class TErrorOr<void>; + explicit TErrorAttributes(void* attributes); + + TErrorAttributes(const TErrorAttributes& other) = default; + TErrorAttributes& operator= (const TErrorAttributes& other) = default; + + TErrorAttributes(TErrorAttributes&& other) = default; + TErrorAttributes& operator= (TErrorAttributes&& other) = default; +}; + +bool operator == (const TErrorAttributes& lhs, const TErrorAttributes& rhs); + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT + +#define ERROR_ATTRIBUTES_INL_H_ +#include "error_attributes-inl.h" +#undef ERROR_ATTRIBUTES_INL_H_ diff --git a/library/cpp/yt/error/mergeable_dictionary.h b/library/cpp/yt/error/mergeable_dictionary.h new file mode 100644 index 0000000000..90597059e4 --- /dev/null +++ b/library/cpp/yt/error/mergeable_dictionary.h @@ -0,0 +1,57 @@ +#pragma once + +#include "public.h" + +#include <ranges> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +// Can be specialized to make your dictionary satisfy CMergeableDictionary. +template <class TDictionary> +struct TMergeDictionariesTraits +{ + static auto MakeIterableView(const TDictionary& dict) + requires false; +}; + +//////////////////////////////////////////////////////////////////////////////// + +namespace NDetail { + +template <class T> +struct TMergeableDictionaryImpl +{ + using TView = std::invoke_result_t<decltype(&TMergeDictionariesTraits<T>::MakeIterableView), const T&>; + using TIterator = std::ranges::iterator_t<TView>; + using TValue = typename std::iterator_traits<TIterator>::value_type; + + static constexpr bool ValidSize = requires { + { std::tuple_size<TValue>::value } -> std::same_as<const size_t&>; + } && (std::tuple_size<TValue>::value == 2); + + static constexpr bool CorrectTupleElements = requires { + typename std::tuple_element<0, TValue>::type; + std::same_as<typename std::tuple_element<0, TValue>::type, TString>; + + typename std::tuple_element<1, TValue>::type; + std::same_as<typename std::tuple_element<1, TValue>::type, NYson::TYsonString>; + }; +}; + +} // namespace NDetail + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +concept CMergeableDictionary = + requires (const T& dict) { + TMergeDictionariesTraits<T>::MakeIterableView(dict); + } && + NDetail::TMergeableDictionaryImpl<T>::ValidSize && + NDetail::TMergeableDictionaryImpl<T>::CorrectTupleElements; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/error/public.h b/library/cpp/yt/error/public.h new file mode 100644 index 0000000000..63b95497c7 --- /dev/null +++ b/library/cpp/yt/error/public.h @@ -0,0 +1,17 @@ +#pragma once + +#include <library/cpp/yt/yson_string/string.h> + +namespace NYT { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> +class TErrorOr; + +class TErrorAttributes; +struct TOriginAttributes; + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/library/cpp/yt/error/ya.make b/library/cpp/yt/error/ya.make index f5bd946199..60d3e9a92f 100644 --- a/library/cpp/yt/error/ya.make +++ b/library/cpp/yt/error/ya.make @@ -9,9 +9,11 @@ PEERDIR( library/cpp/yt/misc library/cpp/yt/threading library/cpp/yt/string + library/cpp/yt/yson_string # TODO(arkady-e1ppa): eliminate ) SRCS( + error_attributes.cpp origin_attributes.cpp ) diff --git a/yt/yt/client/api/security_client.cpp b/yt/yt/client/api/security_client.cpp index b6a178f1d0..2b0d0f675c 100644 --- a/yt/yt/client/api/security_client.cpp +++ b/yt/yt/client/api/security_client.cpp @@ -32,16 +32,16 @@ TError TCheckPermissionResult::ToError( user, permission); } - error.MutableAttributes()->Set("user", user); - error.MutableAttributes()->Set("permission", permission); + error <<= TErrorAttribute("user", user); + error <<= TErrorAttribute("permission", permission); if (ObjectId) { - error.MutableAttributes()->Set("denied_by", ObjectId); + error <<= TErrorAttribute("denied_by", ObjectId); } if (SubjectId) { - error.MutableAttributes()->Set("denied_for", SubjectId); + error <<= TErrorAttribute("denied_for", SubjectId); } if (column) { - error.MutableAttributes()->Set("column", *column); + error <<= TErrorAttribute("column", *column); } return error; } @@ -73,10 +73,10 @@ TError TCheckPermissionByAclResult::ToError(const std::string& user, EPermission user, permission); } - error.MutableAttributes()->Set("user", user); - error.MutableAttributes()->Set("permission", permission); + error <<= TErrorAttribute("user", user); + error <<= TErrorAttribute("permission", permission); if (SubjectId) { - error.MutableAttributes()->Set("denied_for", SubjectId); + error <<= TErrorAttribute("denied_for", SubjectId); } return error; } diff --git a/yt/yt/client/tablet_client/table_mount_cache_detail.cpp b/yt/yt/client/tablet_client/table_mount_cache_detail.cpp index 2c37652904..248e19de7e 100644 --- a/yt/yt/client/tablet_client/table_mount_cache_detail.cpp +++ b/yt/yt/client/tablet_client/table_mount_cache_detail.cpp @@ -256,8 +256,8 @@ auto TTableMountCacheBase::TryHandleServantNotActiveError(const TError& error) return {}; } - if (auto siblingCellDescriptor = attributes.ToMap()->FindChild("sibling_servant_cell_descriptor")) { - RegisterCell(std::move(siblingCellDescriptor)); + if (auto siblingCellDescriptor = attributes.FindYson("sibling_servant_cell_descriptor")) { + RegisterCell(ConvertToNode(siblingCellDescriptor)); } else { return {}; } diff --git a/yt/yt/core/misc/error.cpp b/yt/yt/core/misc/error.cpp index 950a7a0f02..15add4a3d1 100644 --- a/yt/yt/core/misc/error.cpp +++ b/yt/yt/core/misc/error.cpp @@ -396,9 +396,14 @@ void Deserialize(TError& error, const NYTree::INodePtr& node) error.SetMessage(mapNode->GetChildValueOrThrow<TString>(MessageKey)); static const TString AttributesKey("attributes"); - auto attributes = IAttributeDictionary::FromMap(mapNode->GetChildOrThrow(AttributesKey)->AsMap()); + auto children = mapNode->GetChildOrThrow(AttributesKey)->AsMap()->GetChildren(); - error.SetAttributes(std::move(attributes)); + for (const auto& [key, value] : children) { + // TODO(babenko): migrate to std::string + error <<= TErrorAttribute(TString(key), ConvertToYsonString(value)); + } + + error.UpdateOriginAttributes(); static const TString InnerErrorsKey("inner_errors"); if (auto innerErrorsNode = mapNode->FindChild(InnerErrorsKey)) { @@ -428,7 +433,19 @@ void ToProto(NYT::NProto::TError* protoError, const TError& error) protoError->clear_attributes(); if (error.HasAttributes()) { - ToProto(protoError->mutable_attributes(), error.Attributes()); + auto* protoAttributes = protoError->mutable_attributes(); + + protoAttributes->Clear(); + auto pairs = error.Attributes().ListPairs(); + std::sort(pairs.begin(), pairs.end(), [] (const auto& lhs, const auto& rhs) { + return lhs.first < rhs.first; + }); + protoAttributes->mutable_attributes()->Reserve(pairs.size()); + for (const auto& [key, value] : pairs) { + auto* protoAttribute = protoAttributes->add_attributes(); + protoAttribute->set_key(key); + protoAttribute->set_value(value.ToString()); + } } auto addAttribute = [&] (const TString& key, const auto& value) { @@ -487,9 +504,12 @@ void FromProto(TError* error, const NYT::NProto::TError& protoError) error->SetCode(TErrorCode(protoError.code())); error->SetMessage(FromProto<TString>(protoError.message())); if (protoError.has_attributes()) { - auto attributes = FromProto(protoError.attributes()); - - error->SetAttributes(std::move(attributes)); + for (const auto& protoAttribute : protoError.attributes().attributes()) { + auto key = FromProto<TString>(protoAttribute.key()); + auto value = FromProto<TString>(protoAttribute.value()); + (*error) <<= TErrorAttribute(key, TYsonString(value)); + } + error->UpdateOriginAttributes(); } *error->MutableInnerErrors() = FromProto<std::vector<TError>>(protoError.inner_errors()); } @@ -605,8 +625,12 @@ void TErrorSerializer::Load(TStreamLoadContext& context, TError& error) IAttributeDictionaryPtr attributes; if (Load<bool>(context)) { - attributes = CreateEphemeralAttributes(); - TAttributeDictionarySerializer::LoadNonNull(context, attributes); + size_t size = TSizeSerializer::Load(context); + for (size_t index = 0; index < size; ++index) { + auto key = Load<TString>(context); + auto value = Load<TYsonString>(context); + error <<= TErrorAttribute(key, value); + } } auto innerErrors = Load<std::vector<TError>>(context); @@ -617,8 +641,8 @@ void TErrorSerializer::Load(TStreamLoadContext& context, TError& error) } error.SetCode(code); + error.UpdateOriginAttributes(); error.SetMessage(std::move(message)); - error.SetAttributes(std::move(attributes)); *error.MutableInnerErrors() = std::move(innerErrors); } diff --git a/yt/yt/core/misc/stripped_error-inl.h b/yt/yt/core/misc/stripped_error-inl.h index 5c2effc9aa..7fbc8acd7e 100644 --- a/yt/yt/core/misc/stripped_error-inl.h +++ b/yt/yt/core/misc/stripped_error-inl.h @@ -4,6 +4,8 @@ #include "stripped_error.h" #endif +#include <library/cpp/yt/error/error_attributes.h> + #include <library/cpp/yt/string/format.h> namespace NYT { @@ -150,6 +152,17 @@ TError TError::Wrap(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... #undef IMPLEMENT_COPY_WRAP #undef IMPLEMENT_MOVE_WRAP +template <CMergeableDictionary TDictionary> +TError& TError::operator <<= (const TDictionary& attributes) & +{ + // This forces inclusion of error_attributes in the header file + // which is undesirable. + // One could (and probably should) implement type-erasure + // like AnyDictionaryRef to move this implementation in cpp file. + MutableAttributes()->MergeFrom(attributes); + return *this; +} + template <CErrorNestable TValue> TError&& TError::operator << (TValue&& rhs) && { @@ -236,7 +249,6 @@ inline void TError::ThrowOnError() && IMPLEMENT_THROW_ON_ERROR(); } - #undef IMPLEMENT_THROW_ON_ERROR //////////////////////////////////////////////////////////////////////////////// diff --git a/yt/yt/core/misc/stripped_error.cpp b/yt/yt/core/misc/stripped_error.cpp index fbcfb90ff0..1bb32bea82 100644 --- a/yt/yt/core/misc/stripped_error.cpp +++ b/yt/yt/core/misc/stripped_error.cpp @@ -13,11 +13,13 @@ // NB: We don't need printability since TError from // this file is not printable. #include <yt/yt/core/ytree/attributes.h> +#include <yt/yt/core/ytree/exception_helpers.h> #include <yt/yt/core/misc/origin_attributes.h> #include <library/cpp/yt/exception/exception.h> +#include <library/cpp/yt/error/error_attributes.h> #include <library/cpp/yt/error/origin_attributes.h> #include <library/cpp/yt/yson_string/string.h> @@ -56,7 +58,7 @@ public: : Code_(other.Code_) , Message_(other.Message_) , OriginAttributes_(other.OriginAttributes_) - , Attributes_(other.Attributes_ ? other.Attributes_->Clone() : nullptr) + , AttributesImpl_(other.AttributesImpl_ ? other.AttributesImpl_->Clone() : nullptr) , InnerErrors_(other.InnerErrors_) { } @@ -148,29 +150,26 @@ public: bool HasAttributes() const noexcept { - return Attributes_.operator bool(); + return AttributesImpl_.operator bool(); } - const IAttributeDictionary& Attributes() const + const TErrorAttributes& Attributes() const { - if (!Attributes_) { - return EmptyAttributes(); - } - return *Attributes_; + return Attributes_; } - void SetAttributes(IAttributeDictionaryPtr attributes) + void UpdateOriginAttributes() { - Attributes_ = std::move(attributes); - ExtractBultinAttributes(); + OriginAttributes_ = NYT::NDetail::ExtractFromDictionary(AttributesImpl_); } - IAttributeDictionary* MutableAttributes() noexcept + TErrorAttributes* MutableAttributes() noexcept { - if (!Attributes_) { - Attributes_ = CreateEphemeralAttributes(); + if (!HasAttributes()) { + AttributesImpl_ = CreateEphemeralAttributes(); + Attributes_ = TErrorAttributes(AttributesImpl_.Get()); } - return Attributes_.Get(); + return &Attributes_; } const std::vector<TError>& InnerErrors() const noexcept @@ -192,18 +191,78 @@ private: .Tid = NThreading::InvalidThreadId, }; - NYTree::IAttributeDictionaryPtr Attributes_; + NYTree::IAttributeDictionaryPtr AttributesImpl_; + TErrorAttributes Attributes_ = TErrorAttributes(AttributesImpl_.Get()); + std::vector<TError> InnerErrors_; - void ExtractBultinAttributes() - { - OriginAttributes_ = NYT::NDetail::ExtractFromDictionary(Attributes_); - } + friend class TErrorAttributes; }; //////////////////////////////////////////////////////////////////////////////// -namespace { +// NB(arkady-e1ppa): Currently TErrorAttributes is a facade +// which has minimal API of original dict with backend being the +// actual original dict. Once API-related issues are fixed we are +// free to implement a backend which doesn't depend on original dict. +std::vector<TString> TErrorAttributes::ListKeys() const +{ + auto* attributes = static_cast<IAttributeDictionary*>(Attributes_); + if (!attributes) { + return {}; + } + return attributes->ListKeys(); +} + +std::vector<TErrorAttributes::TKeyValuePair> TErrorAttributes::ListPairs() const +{ + auto* attributes = static_cast<IAttributeDictionary*>(Attributes_); + if (!attributes) { + return {}; + } + return attributes->ListPairs(); +} + +NYson::TYsonString TErrorAttributes::FindYson(TStringBuf key) const +{ + auto* attributes = static_cast<IAttributeDictionary*>(Attributes_); + if (!attributes) { + return {}; + } + return attributes->FindYson(key); +} + +void TErrorAttributes::SetYson(const TString& key, const NYson::TYsonString& value) +{ + auto* attributes = static_cast<IAttributeDictionary*>(Attributes_); + YT_VERIFY(attributes); + return attributes->SetYson(key, value); +} + +bool TErrorAttributes::Remove(const TString& key) +{ + auto* attributes = static_cast<IAttributeDictionary*>(Attributes_); + if (!attributes) { + return false; + } + return attributes->Remove(key); +} + +NYson::TYsonString TErrorAttributes::GetYson(TStringBuf key) const +{ + auto result = FindYson(key); + if (!result) { + // This method comes from "exception_helpers.h" + // and requires NYTree::EErrorCode::ResolveError enum value. + // TODO(arkady-e1ppa): eliminate this dependency somehow. + ThrowNoSuchAttribute(key); + } + return result; +} + +//////////////////////////////////////////////////////////////////////////////// + +namespace { bool IsWhitelisted(const TError& error, const THashSet<TStringBuf>& attributeWhitelist) { @@ -455,15 +514,16 @@ bool TError::HasAttributes() const noexcept return Impl_->HasAttributes(); } -const IAttributeDictionary& TError::Attributes() const +const TErrorAttributes& TError::Attributes() const { if (!Impl_) { - return EmptyAttributes(); + static TErrorAttributes empty{nullptr}; + return empty; } return Impl_->Attributes(); } -IAttributeDictionary* TError::MutableAttributes() +TErrorAttributes* TError::MutableAttributes() { MakeMutable(); return Impl_->MutableAttributes(); @@ -492,12 +552,12 @@ TOriginAttributes* TError::MutableOriginAttributes() const noexcept return Impl_->MutableOriginAttributes(); } -void TError::SetAttributes(NYTree::IAttributeDictionaryPtr attributes) +void TError::UpdateOriginAttributes() { if (!Impl_) { return; } - Impl_->SetAttributes(std::move(attributes)); + Impl_->UpdateOriginAttributes(); } const TString InnerErrorsTruncatedKey("inner_errors_truncated"); @@ -515,22 +575,20 @@ TError TError::Truncate( return innerError.Truncate(maxInnerErrorCount, stringLimit, attributeWhitelist); }; - auto truncateAttributes = [stringLimit, &attributeWhitelist] (const IAttributeDictionary& attributes) { - auto truncatedAttributes = CreateEphemeralAttributes(); + auto truncateAttributes = [stringLimit, &attributeWhitelist] (const TErrorAttributes& attributes, TErrorAttributes* mutableAttributes) { for (const auto& key : attributes.ListKeys()) { const auto& value = attributes.FindYson(key); if (std::ssize(value.AsStringBuf()) > stringLimit && !attributeWhitelist.contains(key)) { - truncatedAttributes->SetYson( + mutableAttributes->SetYson( key, NYson::ConvertToYsonString("...<attribute truncated>...")); } else { - truncatedAttributes->SetYson( + mutableAttributes->SetYson( key, value); } } - return truncatedAttributes; }; auto result = std::make_unique<TImpl>(); @@ -538,7 +596,7 @@ TError TError::Truncate( *result->MutableMessage() = std::move(TruncateString(GetMessage(), stringLimit, ErrorMessageTruncatedSuffix)); if (Impl_->HasAttributes()) { - result->SetAttributes(truncateAttributes(Impl_->Attributes())); + truncateAttributes(Impl_->Attributes(), result->MutableAttributes()); } *result->MutableOriginAttributes() = Impl_->OriginAttributes(); @@ -550,7 +608,7 @@ TError TError::Truncate( copiedInnerErrors.push_back(truncateInnerError(innerError)); } } else { - result->MutableAttributes()->Set(InnerErrorsTruncatedKey, true); + result->MutableAttributes()->SetYson(InnerErrorsTruncatedKey, ConvertToYsonString(true)); // NB(arkady-e1ppa): We want to always keep the last inner error, // so we make room for it and do not check if it is whitelisted. @@ -582,7 +640,7 @@ TError TError::Truncate( innerError = std::move(innerError).Truncate(maxInnerErrorCount, stringLimit, attributeWhitelist); }; - auto truncateAttributes = [stringLimit, &attributeWhitelist] (IAttributeDictionary* attributes) { + auto truncateAttributes = [stringLimit, &attributeWhitelist] (TErrorAttributes* attributes) { for (const auto& key : attributes->ListKeys()) { if (std::ssize(attributes->FindYson(key).AsStringBuf()) > stringLimit && !attributeWhitelist.contains(key)) { attributes->SetYson( @@ -602,7 +660,7 @@ TError TError::Truncate( } } else { auto& innerErrors = ApplyWhitelist(*MutableInnerErrors(), attributeWhitelist, maxInnerErrorCount); - MutableAttributes()->Set(InnerErrorsTruncatedKey, true); + MutableAttributes()->SetYson(InnerErrorsTruncatedKey, ConvertToYsonString(true)); for (auto& innerError : innerErrors) { truncateInnerError(innerError); @@ -712,12 +770,6 @@ TError& TError::operator <<= (std::vector<TError>&& innerErrors) & return *this; } -TError& TError::operator <<= (const NYTree::IAttributeDictionary& attributes) & -{ - MutableAttributes()->MergeFrom(attributes); - return *this; -} - //////////////////////////////////////////////////////////////////////////////// bool operator == (const TError& lhs, const TError& rhs) diff --git a/yt/yt/core/misc/stripped_error.h b/yt/yt/core/misc/stripped_error.h index fc4de80db6..ad985394b8 100644 --- a/yt/yt/core/misc/stripped_error.h +++ b/yt/yt/core/misc/stripped_error.h @@ -6,7 +6,7 @@ #include <library/cpp/yt/threading/public.h> -#include <library/cpp/yt/error/origin_attributes.h> +#include <library/cpp/yt/error/mergeable_dictionary.h> #include <library/cpp/yt/yson/public.h> @@ -141,15 +141,15 @@ public: bool HasAttributes() const noexcept; - const NYTree::IAttributeDictionary& Attributes() const; - NYTree::IAttributeDictionary* MutableAttributes(); + const TErrorAttributes& Attributes() const; + TErrorAttributes* MutableAttributes(); const std::vector<TError>& InnerErrors() const; std::vector<TError>* MutableInnerErrors(); // Used for deserialization only. TOriginAttributes* MutableOriginAttributes() const noexcept; - void SetAttributes(NYTree::IAttributeDictionaryPtr attributes); + void UpdateOriginAttributes(); TError Truncate( int maxInnerErrorCount = 2, @@ -223,7 +223,8 @@ public: TError& operator <<= (TError&& innerError) &; TError& operator <<= (const std::vector<TError>& innerErrors) &; TError& operator <<= (std::vector<TError>&& innerErrors) &; - TError& operator <<= (const NYTree::IAttributeDictionary& attributes) &; + template <CMergeableDictionary TDictionary> + TError& operator <<= (const TDictionary& attributes) &; template <CErrorNestable TValue> TError&& operator << (TValue&& operand) &&; @@ -244,6 +245,8 @@ private: explicit TErrorOr(std::unique_ptr<TImpl> impl); void MakeMutable(); + + friend class TErrorAttributes; }; //////////////////////////////////////////////////////////////////////////////// diff --git a/yt/yt/core/misc/unittests/error_ut.cpp b/yt/yt/core/misc/unittests/error_ut.cpp index 09e5a0fb2f..18c78a9cca 100644 --- a/yt/yt/core/misc/unittests/error_ut.cpp +++ b/yt/yt/core/misc/unittests/error_ut.cpp @@ -214,6 +214,12 @@ void IterateTestOverEveryRightOperand(TOverloadTest& tester) } } +template <class T> +void SetErrorAttribute(TError* error, TString key, const T& value) +{ + error->MutableAttributes()->SetYson(key, ConvertToYsonString(value)); +} + //////////////////////////////////////////////////////////////////////////////// TEST(TErrorTest, BitshiftOverloadsExplicitLeftOperand) @@ -315,18 +321,18 @@ TEST(TErrorTest, WrapRValue) { TError error("Error"); - // TError errorCopy = error; - // auto wrapped = std::move(errorCopy).Wrap("Wrapped error"); - // EXPECT_TRUE(errorCopy.IsOK()); - // EXPECT_EQ(wrapped.GetCode(), NYT::EErrorCode::Generic); - // EXPECT_EQ(wrapped.GetMessage(), "Wrapped error"); - // EXPECT_EQ(wrapped.InnerErrors().size(), 1u); - // EXPECT_EQ(wrapped.InnerErrors()[0], error); + TError errorCopy = error; + auto wrapped = std::move(errorCopy).Wrap("Wrapped error"); + EXPECT_TRUE(errorCopy.IsOK()); + EXPECT_EQ(wrapped.GetCode(), NYT::EErrorCode::Generic); + EXPECT_EQ(wrapped.GetMessage(), "Wrapped error"); + EXPECT_EQ(wrapped.InnerErrors().size(), 1u); + EXPECT_EQ(wrapped.InnerErrors()[0], error); - // TError anotherErrorCopy = error; - // auto trviallyWrapped = std::move(anotherErrorCopy).Wrap(); - // EXPECT_TRUE(anotherErrorCopy.IsOK()); - // EXPECT_EQ(trviallyWrapped, error); + TError anotherErrorCopy = error; + auto trviallyWrapped = std::move(anotherErrorCopy).Wrap(); + EXPECT_TRUE(anotherErrorCopy.IsOK()); + EXPECT_EQ(trviallyWrapped, error); } TEST(TErrorTest, ThrowErrorExceptionIfFailedMacroJustWorks) @@ -528,7 +534,7 @@ TEST(TErrorTest, TruncateLarge) << TError("Second inner error") << TError("Third inner error") << TError("Fourth inner error"); - error.MutableAttributes()->Set("my_attr", "Some long long attr"); + SetErrorAttribute(&error, "my_attr", "Some long long attr"); auto truncatedError = error.Truncate(/*maxInnerErrorCount*/ 3, /*stringLimit*/ 10); EXPECT_EQ(error.GetCode(), truncatedError.GetCode()); @@ -568,7 +574,7 @@ TEST(TErrorTest, TruncateLargeRValue) << TError("Second inner error") << TError("Third inner error") << TError("Fourth inner error"); - error.MutableAttributes()->Set("my_attr", "Some long long attr"); + SetErrorAttribute(&error, "my_attr", "Some long long attr"); auto errorCopy = error; auto truncatedError = std::move(errorCopy).Truncate(/*maxInnerErrorCount*/ 3, /*stringLimit*/ 10); @@ -591,7 +597,7 @@ TEST(TErrorTest, TruncateConsistentOverloads) << TError("Second inner error") << TError("Third inner error") << TError("Fourth inner error"); - error.MutableAttributes()->Set("my_attr", "Some long long attr"); + SetErrorAttribute(&error, "my_attr", "Some long long attr"); auto errorCopy = error; auto truncatedRValueError = std::move(errorCopy).Truncate(/*maxInnerErrorCount*/ 3, /*stringLimit*/ 10); @@ -604,8 +610,8 @@ TEST(TErrorTest, TruncateConsistentOverloads) TEST(TErrorTest, TruncateWhitelist) { auto error = TError("Some error"); - error.MutableAttributes()->Set("attr1", "Some long long attr"); - error.MutableAttributes()->Set("attr2", "Some long long attr"); + SetErrorAttribute(&error, "attr1", "Some long long attr"); + SetErrorAttribute(&error, "attr2", "Some long long attr"); THashSet<TStringBuf> myWhitelist = {"attr2"}; @@ -621,8 +627,8 @@ TEST(TErrorTest, TruncateWhitelist) TEST(TErrorTest, TruncateWhitelistRValue) { auto error = TError("Some error"); - error.MutableAttributes()->Set("attr1", "Some long long attr"); - error.MutableAttributes()->Set("attr2", "Some long long attr"); + SetErrorAttribute(&error, "attr1", "Some long long attr"); + SetErrorAttribute(&error, "attr2", "Some long long attr"); THashSet<TStringBuf> myWhitelist = {"attr2"}; @@ -640,8 +646,8 @@ TEST(TErrorTest, TruncateWhitelistRValue) TEST(TErrorTest, TruncateWhitelistInnerErrors) { auto innerError = TError("Inner error"); - innerError.MutableAttributes()->Set("attr1", "Some long long attr"); - innerError.MutableAttributes()->Set("attr2", "Some long long attr"); + SetErrorAttribute(&innerError, "attr1", "Some long long attr"); + SetErrorAttribute(&innerError, "attr2", "Some long long attr"); auto error = TError("Error") << innerError; @@ -660,8 +666,8 @@ TEST(TErrorTest, TruncateWhitelistInnerErrors) TEST(TErrorTest, TruncateWhitelistInnerErrorsRValue) { auto innerError = TError("Inner error"); - innerError.MutableAttributes()->Set("attr1", "Some long long attr"); - innerError.MutableAttributes()->Set("attr2", "Some long long attr"); + SetErrorAttribute(&innerError, "attr1", "Some long long attr"); + SetErrorAttribute(&innerError, "attr2", "Some long long attr"); auto error = TError("Error") << innerError; diff --git a/yt/yt/core/rpc/helpers.cpp b/yt/yt/core/rpc/helpers.cpp index 9341cbc51b..d9d2a93a89 100644 --- a/yt/yt/core/rpc/helpers.cpp +++ b/yt/yt/core/rpc/helpers.cpp @@ -74,7 +74,7 @@ bool IsChannelFailureErrorHandled(const TError& error) void LabelHandledChannelFailureError(TError* error) { - error->MutableAttributes()->Set("channel_failure_error_handled", true); + *error <<= TErrorAttribute("channel_failure_error_handled", true); } //////////////////////////////////////////////////////////////////////////////// @@ -620,7 +620,7 @@ void EnrichClientRequestError( { auto featureId = error->Attributes().Get<int>(FeatureIdAttributeKey); if (auto featureName = (*featureIdFormatter)(featureId)) { - error->MutableAttributes()->Set(FeatureNameAttributeKey, featureName); + *error <<= TErrorAttribute(FeatureNameAttributeKey, featureName); } } diff --git a/yt/yt/core/ytree/attributes-inl.h b/yt/yt/core/ytree/attributes-inl.h index dd367f5d24..d20ed98dc7 100644 --- a/yt/yt/core/ytree/attributes-inl.h +++ b/yt/yt/core/ytree/attributes-inl.h @@ -8,10 +8,14 @@ // #include "serialize.h" #include "convert.h" -namespace NYT::NYTree { +#include <library/cpp/yt/error/error_attributes.h> + +namespace NYT { //////////////////////////////////////////////////////////////////////////////// +namespace NYTree { + template <class T> T IAttributeDictionary::Get(TStringBuf key) const { @@ -86,4 +90,54 @@ void IAttributeDictionary::Set(const TString& key, const T& value) //////////////////////////////////////////////////////////////////////////////// -} // namespace NYT::NYTree +} // namespace NYTree + +//////////////////////////////////////////////////////////////////////////////// + +template <> +struct TMergeDictionariesTraits<NYTree::IAttributeDictionary> +{ + static auto MakeIterableView(const NYTree::IAttributeDictionary& dict) + { + return dict.ListPairs(); + } +}; + +//////////////////////////////////////////////////////////////////////////////// + +// TODO(arkady-e1ppa): Move this out eventually. +template <class T> +T TErrorAttributes::Get(TStringBuf key) const +{ + using NYTree::ConvertTo; + auto yson = GetYson(key); + try { + return ConvertTo<T>(yson); + } catch (const std::exception& ex) { + THROW_ERROR_EXCEPTION("Error parsing attribute %Qv", + key) + << ex; + } +} + +template <class T> +typename TOptionalTraits<T>::TOptional TErrorAttributes::Find(TStringBuf key) const +{ + using NYTree::ConvertTo; + + auto yson = FindYson(key); + if (!yson) { + return typename TOptionalTraits<T>::TOptional(); + } + try { + return ConvertTo<T>(yson); + } catch (const std::exception& ex) { + THROW_ERROR_EXCEPTION("Error parsing attribute %Qv", + key) + << ex; + } +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT diff --git a/yt/yt/core/ytree/exception_helpers.cpp b/yt/yt/core/ytree/exception_helpers.cpp index 5260ff91e5..630a4c162a 100644 --- a/yt/yt/core/ytree/exception_helpers.cpp +++ b/yt/yt/core/ytree/exception_helpers.cpp @@ -106,7 +106,7 @@ void ThrowMethodNotSupported(TStringBuf method, const std::optional<TString>& re "%Qv method is not supported", method); if (resolveType) { - error.MutableAttributes()->Set("resolve_type", *resolveType); + error <<= TErrorAttribute("resolve_type", *resolveType); } THROW_ERROR(error); } |