aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorarkady-e1ppa <arkady-e1ppa@yandex-team.com>2024-10-22 18:15:25 +0300
committerarkady-e1ppa <arkady-e1ppa@yandex-team.com>2024-10-22 19:13:37 +0300
commit477bb1cd2d7d44f218454a8a4137a849eb7e64fa (patch)
treea9a2acf38b06ab47ecc6a377c82e5ec494ab588b
parent0bd28574f1df329ac2b07bf9081184f739bcb483 (diff)
downloadydb-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.h72
-rw-r--r--library/cpp/yt/error/error_attributes.cpp62
-rw-r--r--library/cpp/yt/error/error_attributes.h105
-rw-r--r--library/cpp/yt/error/mergeable_dictionary.h57
-rw-r--r--library/cpp/yt/error/public.h17
-rw-r--r--library/cpp/yt/error/ya.make2
-rw-r--r--yt/yt/client/api/security_client.cpp16
-rw-r--r--yt/yt/client/tablet_client/table_mount_cache_detail.cpp4
-rw-r--r--yt/yt/core/misc/error.cpp42
-rw-r--r--yt/yt/core/misc/stripped_error-inl.h14
-rw-r--r--yt/yt/core/misc/stripped_error.cpp132
-rw-r--r--yt/yt/core/misc/stripped_error.h13
-rw-r--r--yt/yt/core/misc/unittests/error_ut.cpp50
-rw-r--r--yt/yt/core/rpc/helpers.cpp4
-rw-r--r--yt/yt/core/ytree/attributes-inl.h58
-rw-r--r--yt/yt/core/ytree/exception_helpers.cpp2
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);
}