aboutsummaryrefslogtreecommitdiffstats
path: root/library/cpp
diff options
context:
space:
mode:
authorarkady-e1ppa <arkady-e1ppa@yandex-team.com>2024-12-25 07:12:23 +0300
committerarkady-e1ppa <arkady-e1ppa@yandex-team.com>2024-12-25 07:31:55 +0300
commitb5dd91799751f9924acb7c17ddad16ddb2086bba (patch)
treef430b4e9f752af7411d5503bfcf823e9718056c2 /library/cpp
parent75f1af270a6cf9a17b65fde6d12efbb94f235960 (diff)
downloadydb-b5dd91799751f9924acb7c17ddad16ddb2086bba.tar.gz
YT-21233: Adjust (To/From)ErrorAttributeValue CPOs, revert ConvertTo becoming CPO, move TErrorCode and TError to library/cpp/yt/error
\[nodiff:caesar\] List of changes: 1) Make `ConvertTo` a normal function again (basically, a revert of previous change). It turned out to be very inconvenient entry point 2) Introduce concept for "Primitive types" which are handled without yt/core module. Please note that inclusion of core **does not** affect work with primitive types (otherwise we would be facing and ODR violation). These are numerics, duration, instant, string-like objects and guid 3) Introduce `FromErrorAttributeValue` which is a substitute to `ConvertTo` with special behavior (see below) 4) Scheme for (de-)serialization has changed: 1) Primitive types are handled via `ConvertToTextYsonString` and `ConvertFromTextYsonString` 2) Other types are not supported without yt/core. Otherwise `ConvertToYsonString(value, EYsonFormat::Text)` used for serialization and `ConvertTo<T>(value)` for deserialization. 5) New format of attribute value storage allows to not care about concrete type of the attribute (text yson strings can be detected since they start with `\"` and deserialized so are text yson bools, everything else is already human readable). This drops dependency on `TTokenizer` 6) Some small parts (e.g. enums or constants or aliases) of yt/core/misc files were moved to library/cpp/yt/\* locations and re-included to drop dependencies of stripped\_error on them 7) `TErrorAttributes` is now a simple hash map instead of a handle to `IAttributeDictionary` 8) `ExtractFromAttributes` weak symbol is finally moved to library/cpp/yt/error due to point 9 allowing to fully drop dependency on `IAttributeDictionary`. Combined with point 7 this drops dep on yt/core/ytree altogether 9) Moved `TErrorCode` to library/cpp/yt/error. There is a logger there which forces dep on library/cpp/yt/logging. It is not required really, and can be later removed 10) Moved `TError` with format and macroes to library/cpp/yt/error. It still (and probably forever will) depend on yson. What can be done next: Switch TYsonString to TString and move ConvertTo/FromTextYsonString to library/cpp/yt/error. This would remove dep on library/cpp/yt/yson\_string commit_hash:6f11dc478ab782a1f98a5aedcd45a4800d3f4e7b
Diffstat (limited to 'library/cpp')
-rw-r--r--library/cpp/yt/error/convert_to_cpo.h41
-rw-r--r--library/cpp/yt/error/error-inl.h581
-rw-r--r--library/cpp/yt/error/error.cpp827
-rw-r--r--library/cpp/yt/error/error.h442
-rw-r--r--library/cpp/yt/error/error_attribute-inl.h57
-rw-r--r--library/cpp/yt/error/error_attribute.h62
-rw-r--r--library/cpp/yt/error/error_attributes-inl.h24
-rw-r--r--library/cpp/yt/error/error_attributes.cpp115
-rw-r--r--library/cpp/yt/error/error_attributes.h48
-rw-r--r--library/cpp/yt/error/error_code.cpp164
-rw-r--r--library/cpp/yt/error/error_code.h101
-rw-r--r--library/cpp/yt/error/error_helpers-inl.h37
-rw-r--r--library/cpp/yt/error/error_helpers.h27
-rw-r--r--library/cpp/yt/error/origin_attributes.cpp43
-rw-r--r--library/cpp/yt/error/origin_attributes.h10
-rw-r--r--library/cpp/yt/error/public.h14
-rw-r--r--library/cpp/yt/error/unittests/error_code_ut.cpp145
-rw-r--r--library/cpp/yt/error/unittests/error_ut.cpp779
-rw-r--r--library/cpp/yt/error/unittests/ya.make18
-rw-r--r--library/cpp/yt/error/ya.make9
-rw-r--r--library/cpp/yt/string/string.cpp15
-rw-r--r--library/cpp/yt/string/string.h8
-rw-r--r--library/cpp/yt/system/proc.h32
23 files changed, 3493 insertions, 106 deletions
diff --git a/library/cpp/yt/error/convert_to_cpo.h b/library/cpp/yt/error/convert_to_cpo.h
deleted file mode 100644
index dcfc8b0677..0000000000
--- a/library/cpp/yt/error/convert_to_cpo.h
+++ /dev/null
@@ -1,41 +0,0 @@
-#pragma once
-
-#include <library/cpp/yt/misc/tag_invoke_cpo.h>
-
-namespace NYT {
-
-////////////////////////////////////////////////////////////////////////////////
-
-// NB(arkady-e1ppa): This file intentionally is unaware of possible "yson-implementations"
-// e.g. INode and TYsonString(Buf). This is done for two reasons:
-// 1) We can't have dep on INodePtr here anyway.
-// 2) We would like to eventually remove dep on yson of this module.
-
-////////////////////////////////////////////////////////////////////////////////
-
-// NB(arkady-e1ppa): We intentionally generate a separate namespace
-// where ConvertToImpl functions would reside
-// without polluting general-use namespaces.
-namespace NConvertToImpl {
-
-template <class T>
-struct TFn : public NYT::TTagInvokeCpoBase<TFn<T>>
-{ };
-
-} // NConvertToImpl
-
-////////////////////////////////////////////////////////////////////////////////
-
-template <class T>
-inline constexpr NConvertToImpl::TFn<T> ConvertTo = {};
-
-////////////////////////////////////////////////////////////////////////////////
-
-template <class TTo, class TFrom>
-concept CConvertsTo = requires (const TFrom& from) {
- { NYT::ConvertTo<TTo>(from) } -> std::same_as<TTo>;
-};
-
-////////////////////////////////////////////////////////////////////////////////
-
-} // namespace NYT
diff --git a/library/cpp/yt/error/error-inl.h b/library/cpp/yt/error/error-inl.h
new file mode 100644
index 0000000000..f9a7355d1a
--- /dev/null
+++ b/library/cpp/yt/error/error-inl.h
@@ -0,0 +1,581 @@
+#ifndef STRIPPED_ERROR_INL_H_
+#error "Direct inclusion of this file is not allowed, include error.h"
+// For the sake of sane code completion.
+#include "error.h"
+#endif
+
+#include <library/cpp/yt/error/error_attributes.h>
+
+#include <library/cpp/yt/string/format.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline constexpr TErrorCode::TErrorCode()
+ : Value_(static_cast<int>(NYT::EErrorCode::OK))
+{ }
+
+inline constexpr TErrorCode::TErrorCode(int value)
+ : Value_(value)
+{ }
+
+template <class E>
+ requires std::is_enum_v<E>
+constexpr TErrorCode::TErrorCode(E value)
+ : Value_(static_cast<int>(value))
+{ }
+
+inline constexpr TErrorCode::operator int() const
+{
+ return Value_;
+}
+
+template <class E>
+ requires std::is_enum_v<E>
+constexpr TErrorCode::operator E() const
+{
+ return static_cast<E>(Value_);
+}
+
+template <class E>
+ requires std::is_enum_v<E>
+constexpr bool TErrorCode::operator == (E rhs) const
+{
+ return Value_ == static_cast<int>(rhs);
+}
+
+constexpr bool TErrorCode::operator == (TErrorCode rhs) const
+{
+ return Value_ == static_cast<int>(rhs);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+template <class... TArgs>
+TString FormatErrorMessage(TStringBuf format, TArgs&&... args)
+{
+ return Format(TRuntimeFormat{format}, std::forward<TArgs>(args)...);
+}
+
+inline TString FormatErrorMessage(TStringBuf format)
+{
+ return TString(format);
+}
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class... TArgs>
+TError::TErrorOr(TFormatString<TArgs...> format, TArgs&&... args)
+ : TErrorOr(NYT::EErrorCode::Generic, NYT::NDetail::FormatErrorMessage(format.Get(), std::forward<TArgs>(args)...), DisableFormat)
+{ }
+
+template <class... TArgs>
+TError::TErrorOr(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args)
+ : TErrorOr(code, NYT::NDetail::FormatErrorMessage(format.Get(), std::forward<TArgs>(args)...), DisableFormat)
+{ }
+
+template <CInvocable<bool(const TError&)> TFilter>
+std::optional<TError> TError::FindMatching(const TFilter& filter) const
+{
+ if (!Impl_) {
+ return {};
+ }
+
+ if (filter(*this)) {
+ return *this;
+ }
+
+ for (const auto& innerError : InnerErrors()) {
+ if (auto innerResult = innerError.FindMatching(filter)) {
+ return innerResult;
+ }
+ }
+
+ return {};
+}
+
+template <CInvocable<bool(TErrorCode)> TFilter>
+std::optional<TError> TError::FindMatching(const TFilter& filter) const
+{
+ return FindMatching([&] (const TError& error) { return filter(error.GetCode()); });
+}
+
+#define IMPLEMENT_COPY_WRAP(...) \
+ return TError(__VA_ARGS__) << *this; \
+ static_assert(true)
+
+#define IMPLEMENT_MOVE_WRAP(...) \
+ return TError(__VA_ARGS__) << std::move(*this); \
+ static_assert(true)
+
+template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+TError TError::Wrap(U&& u) const &
+{
+ IMPLEMENT_COPY_WRAP(std::forward<U>(u));
+}
+
+template <class... TArgs>
+TError TError::Wrap(TFormatString<TArgs...> format, TArgs&&... args) const &
+{
+ IMPLEMENT_COPY_WRAP(format, std::forward<TArgs>(args)...);
+}
+
+template <class... TArgs>
+TError TError::Wrap(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) const &
+{
+ IMPLEMENT_COPY_WRAP(code, format, std::forward<TArgs>(args)...);
+}
+
+template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+TError TError::Wrap(U&& u) &&
+{
+ IMPLEMENT_MOVE_WRAP(std::forward<U>(u));
+}
+
+template <class... TArgs>
+TError TError::Wrap(TFormatString<TArgs...> format, TArgs&&... args) &&
+{
+ IMPLEMENT_MOVE_WRAP(format, std::forward<TArgs>(args)...);
+}
+
+template <class... TArgs>
+TError TError::Wrap(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) &&
+{
+ IMPLEMENT_MOVE_WRAP(code, format, std::forward<TArgs>(args)...);
+}
+
+#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) &&
+{
+ return std::move(*this <<= std::forward<TValue>(rhs));
+}
+
+template <CErrorNestable TValue>
+TError TError::operator << (TValue&& rhs) const &
+{
+ return TError(*this) << std::forward<TValue>(rhs);
+}
+
+template <CErrorNestable TValue>
+TError&& TError::operator << (const std::optional<TValue>& rhs) &&
+{
+ if (rhs) {
+ return std::move(*this <<= *rhs);
+ } else {
+ return std::move(*this);
+ }
+}
+
+template <CErrorNestable TValue>
+TError TError::operator << (const std::optional<TValue>& rhs) const &
+{
+ if (rhs) {
+ return TError(*this) << *rhs;
+ } else {
+ return *this;
+ }
+}
+
+#define IMPLEMENT_THROW_ON_ERROR(...) \
+ if (!IsOK()) { \
+ THROW_ERROR std::move(*this).Wrap(__VA_ARGS__); \
+ } \
+ static_assert(true)
+
+template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+void TError::ThrowOnError(U&& u) const &
+{
+ IMPLEMENT_THROW_ON_ERROR(std::forward<U>(u));
+}
+
+template <class... TArgs>
+void TError::ThrowOnError(TFormatString<TArgs...> format, TArgs&&... args) const &
+{
+ IMPLEMENT_THROW_ON_ERROR(format, std::forward<TArgs>(args)...);
+}
+
+template <class... TArgs>
+void TError::ThrowOnError(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) const &
+{
+ IMPLEMENT_THROW_ON_ERROR(code, format, std::forward<TArgs>(args)...);
+}
+
+inline void TError::ThrowOnError() const &
+{
+ IMPLEMENT_THROW_ON_ERROR();
+}
+
+template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+void TError::ThrowOnError(U&& u) &&
+{
+ IMPLEMENT_THROW_ON_ERROR(std::forward<U>(u));
+}
+
+template <class... TArgs>
+void TError::ThrowOnError(TFormatString<TArgs...> format, TArgs&&... args) &&
+{
+ IMPLEMENT_THROW_ON_ERROR(format, std::forward<TArgs>(args)...);
+}
+
+template <class... TArgs>
+void TError::ThrowOnError(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) &&
+{
+ IMPLEMENT_THROW_ON_ERROR(code, format, std::forward<TArgs>(args)...);
+}
+
+inline void TError::ThrowOnError() &&
+{
+ IMPLEMENT_THROW_ON_ERROR();
+}
+
+#undef IMPLEMENT_THROW_ON_ERROR
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+TErrorOr<T>::TErrorOr()
+{
+ Value_.emplace();
+}
+
+template <class T>
+TErrorOr<T>::TErrorOr(T&& value) noexcept
+ : Value_(std::move(value))
+{ }
+
+template <class T>
+TErrorOr<T>::TErrorOr(const T& value)
+ : Value_(value)
+{ }
+
+template <class T>
+TErrorOr<T>::TErrorOr(const TError& other)
+ : TError(other)
+{
+ YT_VERIFY(!IsOK());
+}
+
+template <class T>
+TErrorOr<T>::TErrorOr(TError&& other) noexcept
+ : TError(std::move(other))
+{
+ YT_VERIFY(!IsOK());
+}
+
+template <class T>
+TErrorOr<T>::TErrorOr(const TErrorOr<T>& other)
+ : TError(other)
+{
+ if (IsOK()) {
+ Value_.emplace(other.Value());
+ }
+}
+
+template <class T>
+TErrorOr<T>::TErrorOr(TErrorOr<T>&& other) noexcept
+ : TError(std::move(other))
+{
+ if (IsOK()) {
+ Value_.emplace(std::move(other.Value()));
+ }
+}
+
+template <class T>
+template <class U>
+TErrorOr<T>::TErrorOr(const TErrorOr<U>& other)
+ : TError(other)
+{
+ if (IsOK()) {
+ Value_.emplace(other.Value());
+ }
+}
+
+template <class T>
+template <class U>
+TErrorOr<T>::TErrorOr(TErrorOr<U>&& other) noexcept
+ : TError(other)
+{
+ if (IsOK()) {
+ Value_.emplace(std::move(other.Value()));
+ }
+}
+
+template <class T>
+TErrorOr<T>::TErrorOr(const std::exception& ex)
+ : TError(ex)
+{ }
+
+template <class T>
+TErrorOr<T>& TErrorOr<T>::operator = (const TErrorOr<T>& other)
+ requires std::is_copy_assignable_v<T>
+{
+ static_cast<TError&>(*this) = static_cast<const TError&>(other);
+ Value_ = other.Value_;
+ return *this;
+}
+
+template <class T>
+TErrorOr<T>& TErrorOr<T>::operator = (TErrorOr<T>&& other) noexcept
+ requires std::is_nothrow_move_assignable_v<T>
+{
+ static_cast<TError&>(*this) = std::move(other);
+ Value_ = std::move(other.Value_);
+ return *this;
+}
+
+#define IMPLEMENT_VALUE_OR_THROW_REF(...) \
+ if (!IsOK()) { \
+ THROW_ERROR Wrap(__VA_ARGS__); \
+ } \
+ return *Value_; \
+ static_assert(true)
+
+#define IMPLEMENT_VALUE_OR_THROW_MOVE(...) \
+ if (!IsOK()) { \
+ THROW_ERROR std::move(*this).Wrap(__VA_ARGS__); \
+ } \
+ return std::move(*Value_); \
+ static_assert(true)
+
+template <class T>
+template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+const T& TErrorOr<T>::ValueOrThrow(U&& u) const & Y_LIFETIME_BOUND
+{
+ IMPLEMENT_VALUE_OR_THROW_REF(std::forward<U>(u));
+}
+
+template <class T>
+template <class... TArgs>
+const T& TErrorOr<T>::ValueOrThrow(TFormatString<TArgs...> format, TArgs&&... args) const & Y_LIFETIME_BOUND
+{
+ IMPLEMENT_VALUE_OR_THROW_REF(format, std::forward<TArgs>(args)...);
+}
+
+template <class T>
+template <class... TArgs>
+const T& TErrorOr<T>::ValueOrThrow(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) const & Y_LIFETIME_BOUND
+{
+ IMPLEMENT_VALUE_OR_THROW_REF(code, format, std::forward<TArgs>(args)...);
+}
+
+template <class T>
+const T& TErrorOr<T>::ValueOrThrow() const & Y_LIFETIME_BOUND
+{
+ IMPLEMENT_VALUE_OR_THROW_REF();
+}
+
+template <class T>
+template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+T& TErrorOr<T>::ValueOrThrow(U&& u) & Y_LIFETIME_BOUND
+{
+ IMPLEMENT_VALUE_OR_THROW_REF(std::forward<U>(u));
+}
+
+template <class T>
+template <class... TArgs>
+T& TErrorOr<T>::ValueOrThrow(TFormatString<TArgs...> format, TArgs&&... args) & Y_LIFETIME_BOUND
+{
+ IMPLEMENT_VALUE_OR_THROW_REF(format, std::forward<TArgs>(args)...);
+}
+
+template <class T>
+template <class... TArgs>
+T& TErrorOr<T>::ValueOrThrow(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) & Y_LIFETIME_BOUND
+{
+ IMPLEMENT_VALUE_OR_THROW_REF(code, format, std::forward<TArgs>(args)...);
+}
+
+template <class T>
+T& TErrorOr<T>::ValueOrThrow() & Y_LIFETIME_BOUND
+{
+ IMPLEMENT_VALUE_OR_THROW_REF();
+}
+
+template <class T>
+template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+T&& TErrorOr<T>::ValueOrThrow(U&& u) && Y_LIFETIME_BOUND
+{
+ IMPLEMENT_VALUE_OR_THROW_MOVE(std::forward<U>(u));
+}
+
+template <class T>
+template <class... TArgs>
+T&& TErrorOr<T>::ValueOrThrow(TFormatString<TArgs...> format, TArgs&&... args) && Y_LIFETIME_BOUND
+{
+ IMPLEMENT_VALUE_OR_THROW_MOVE(format, std::forward<TArgs>(args)...);
+}
+
+template <class T>
+template <class... TArgs>
+T&& TErrorOr<T>::ValueOrThrow(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) && Y_LIFETIME_BOUND
+{
+ IMPLEMENT_VALUE_OR_THROW_MOVE(code, format, std::forward<TArgs>(args)...);
+}
+
+template <class T>
+T&& TErrorOr<T>::ValueOrThrow() && Y_LIFETIME_BOUND
+{
+ IMPLEMENT_VALUE_OR_THROW_MOVE();
+}
+
+#undef IMPLEMENT_VALUE_OR_THROW_REF
+#undef IMPLEMENT_VALUE_OR_THROW_MOVE
+
+template <class T>
+T&& TErrorOr<T>::Value() && Y_LIFETIME_BOUND
+{
+ YT_ASSERT(IsOK());
+ return std::move(*Value_);
+}
+
+template <class T>
+T& TErrorOr<T>::Value() & Y_LIFETIME_BOUND
+{
+ YT_ASSERT(IsOK());
+ return *Value_;
+}
+
+template <class T>
+const T& TErrorOr<T>::Value() const & Y_LIFETIME_BOUND
+{
+ YT_ASSERT(IsOK());
+ return *Value_;
+}
+
+template <class T>
+const T& TErrorOr<T>::ValueOrDefault(const T& defaultValue Y_LIFETIME_BOUND) const & Y_LIFETIME_BOUND
+{
+ return IsOK() ? *Value_ : defaultValue;
+}
+
+template <class T>
+T& TErrorOr<T>::ValueOrDefault(T& defaultValue Y_LIFETIME_BOUND) & Y_LIFETIME_BOUND
+{
+ return IsOK() ? *Value_ : defaultValue;
+}
+
+template <class T>
+constexpr T TErrorOr<T>::ValueOrDefault(T&& defaultValue) const &
+{
+ return IsOK()
+ ? *Value_
+ : std::forward<T>(defaultValue);
+}
+
+template <class T>
+constexpr T TErrorOr<T>::ValueOrDefault(T&& defaultValue) &&
+{
+ return IsOK()
+ ? std::move(*Value_)
+ : std::forward<T>(defaultValue);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TException>
+ requires std::derived_from<std::remove_cvref_t<TException>, TErrorException>
+TException&& operator <<= (TException&& ex, const TError& error)
+{
+ YT_VERIFY(!error.IsOK());
+ ex.Error() = error;
+ return std::move(ex);
+}
+
+template <class TException>
+ requires std::derived_from<std::remove_cvref_t<TException>, TErrorException>
+TException&& operator <<= (TException&& ex, TError&& error)
+{
+ YT_VERIFY(!error.IsOK());
+ ex.Error() = std::move(error);
+ return std::move(ex);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+template <class TArg>
+ requires std::constructible_from<TError, TArg>
+TError TErrorAdaptor::operator << (TArg&& rhs) const
+{
+ return TError(std::forward<TArg>(rhs));
+}
+
+template <class TArg>
+ requires
+ std::constructible_from<TError, TArg> &&
+ std::derived_from<std::remove_cvref_t<TArg>, TError>
+TArg&& TErrorAdaptor::operator << (TArg&& rhs) const
+{
+ return std::forward<TArg>(rhs);
+}
+
+template <class TErrorLike, class U>
+ requires
+ std::derived_from<std::remove_cvref_t<TErrorLike>, TError> &&
+ (!CStringLiteral<std::remove_cvref_t<U>>)
+void ThrowErrorExceptionIfFailed(TErrorLike&& error, U&& u)
+{
+ std::move(error).ThrowOnError(std::forward<U>(u));
+}
+
+template <class TErrorLike, class... TArgs>
+ requires std::derived_from<std::remove_cvref_t<TErrorLike>, TError>
+void ThrowErrorExceptionIfFailed(TErrorLike&& error, TFormatString<TArgs...> format, TArgs&&... args)
+{
+ std::move(error).ThrowOnError(format, std::forward<TArgs>(args)...);
+}
+
+template <class TErrorLike, class... TArgs>
+ requires std::derived_from<std::remove_cvref_t<TErrorLike>, TError>
+void ThrowErrorExceptionIfFailed(TErrorLike&& error, TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args)
+{
+ std::move(error).ThrowOnError(code, format, std::forward<TArgs>(args)...);
+}
+
+template <class TErrorLike>
+ requires std::derived_from<std::remove_cvref_t<TErrorLike>, TError>
+void ThrowErrorExceptionIfFailed(TErrorLike&& error)
+{
+ std::move(error).ThrowOnError();
+}
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+void FormatValue(TStringBuilderBase* builder, const TErrorOr<T>& error, TStringBuf spec)
+{
+ FormatValue(builder, static_cast<const TError&>(error), spec);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/error/error.cpp b/library/cpp/yt/error/error.cpp
new file mode 100644
index 0000000000..bcc265ab2e
--- /dev/null
+++ b/library/cpp/yt/error/error.cpp
@@ -0,0 +1,827 @@
+#include "error.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/string/string.h>
+
+#include <library/cpp/yt/system/proc.h>
+
+#include <library/cpp/yt/yson_string/string.h>
+
+#include <util/string/subst.h>
+
+#include <util/system/error.h>
+#include <util/system/thread.h>
+
+namespace NYT {
+
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, TErrorCode code, TStringBuf spec)
+{
+ FormatValue(builder, static_cast<int>(code), spec);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+constexpr TStringBuf ErrorMessageTruncatedSuffix = "...<message truncated>";
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TError::TImpl
+{
+public:
+ TImpl()
+ : Code_(NYT::EErrorCode::OK)
+ { }
+
+ TImpl(const TError::TImpl& other)
+ : Code_(other.Code_)
+ , Message_(other.Message_)
+ , OriginAttributes_(other.OriginAttributes_)
+ , Attributes_(other.Attributes_)
+ , InnerErrors_(other.InnerErrors_)
+ { }
+
+ explicit TImpl(TString message)
+ : Code_(NYT::EErrorCode::Generic)
+ , Message_(std::move(message))
+ {
+ OriginAttributes_.Capture();
+ }
+
+ TImpl(TErrorCode code, TString message)
+ : Code_(code)
+ , Message_(std::move(message))
+ {
+ if (!IsOK()) {
+ OriginAttributes_.Capture();
+ }
+ }
+
+ bool IsOK() const noexcept
+ {
+ return Code_ == NYT::EErrorCode::OK;
+ }
+
+ TErrorCode GetCode() const noexcept
+ {
+ return Code_;
+ }
+
+ void SetCode(TErrorCode code) noexcept
+ {
+ Code_ = code;
+ }
+
+ const TString& GetMessage() const noexcept
+ {
+ return Message_;
+ }
+
+ TString* MutableMessage() noexcept
+ {
+ return &Message_;
+ }
+
+ bool HasOriginAttributes() const noexcept
+ {
+ return OriginAttributes_.ThreadName.Length > 0;
+ }
+
+ const TOriginAttributes& OriginAttributes() const noexcept
+ {
+ return OriginAttributes_;
+ }
+
+ TOriginAttributes* MutableOriginAttributes() noexcept
+ {
+ return &OriginAttributes_;
+ }
+
+ TProcessId GetPid() const noexcept
+ {
+ return OriginAttributes_.Pid;
+ }
+
+ NThreading::TThreadId GetTid() const noexcept
+ {
+ return OriginAttributes_.Tid;
+ }
+
+ TStringBuf GetThreadName() const noexcept
+ {
+ return OriginAttributes_.ThreadName.ToStringBuf();
+ }
+
+ bool HasDatetime() const noexcept
+ {
+ return OriginAttributes_.Datetime != TInstant();
+ }
+
+ TInstant GetDatetime() const noexcept
+ {
+ return OriginAttributes_.Datetime;
+ }
+
+ void SetDatetime(TInstant datetime) noexcept
+ {
+ OriginAttributes_.Datetime = datetime;
+ }
+
+ bool HasAttributes() const noexcept
+ {
+ return true;
+ }
+
+ const TErrorAttributes& Attributes() const
+ {
+ return Attributes_;
+ }
+
+ void UpdateOriginAttributes()
+ {
+ OriginAttributes_ = NYT::NDetail::ExtractFromDictionary(&Attributes_);
+ }
+
+ TErrorAttributes* MutableAttributes() noexcept
+ {
+ return &Attributes_;
+ }
+
+ const std::vector<TError>& InnerErrors() const noexcept
+ {
+ return InnerErrors_;
+ }
+
+ std::vector<TError>* MutableInnerErrors() noexcept
+ {
+ return &InnerErrors_;
+ }
+
+private:
+ TErrorCode Code_;
+ TString Message_;
+
+ TOriginAttributes OriginAttributes_{
+ .Pid = 0,
+ .Tid = NThreading::InvalidThreadId,
+ };
+
+ TErrorAttributes Attributes_;
+
+ std::vector<TError> InnerErrors_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+bool IsWhitelisted(const TError& error, const THashSet<TStringBuf>& attributeWhitelist)
+{
+ for (const auto& key : error.Attributes().ListKeys()) {
+ if (attributeWhitelist.contains(key)) {
+ return true;
+ }
+ }
+
+ for (const auto& innerError : error.InnerErrors()) {
+ if (IsWhitelisted(innerError, attributeWhitelist)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+//! Returns vector which consists of objects from errors such that:
+//! if N is the number of objects in errors s.t. IsWhitelisted is true
+//! then first N objects of returned vector are the ones for which IsWhitelisted is true
+//! followed by std::max(0, maxInnerErrorCount - N - 1) remaining objects
+//! finally followed by errors.back().
+std::vector<TError>& ApplyWhitelist(std::vector<TError>& errors, const THashSet<TStringBuf>& attributeWhitelist, int maxInnerErrorCount)
+{
+ if (std::ssize(errors) < std::max(2, maxInnerErrorCount)) {
+ return errors;
+ }
+
+ auto firstNotWhitelisted = std::partition(
+ errors.begin(),
+ std::prev(errors.end()),
+ [&attributeWhitelist] (const TError& error) {
+ return IsWhitelisted(error, attributeWhitelist);
+ });
+
+ int lastErrorOffset = std::max<int>(firstNotWhitelisted - errors.begin(), maxInnerErrorCount - 1);
+
+ *(errors.begin() + lastErrorOffset) = std::move(errors.back());
+ errors.resize(lastErrorOffset + 1);
+
+ return errors;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+TError::TErrorOr() = default;
+
+TError::~TErrorOr() = default;
+
+TError::TErrorOr(const TError& other)
+{
+ if (!other.IsOK()) {
+ Impl_ = std::make_unique<TImpl>(*other.Impl_);
+ }
+}
+
+TError::TErrorOr(TError&& other) noexcept
+ : Impl_(std::move(other.Impl_))
+{ }
+
+TError::TErrorOr(const std::exception& ex)
+{
+ if (auto simpleException = dynamic_cast<const TSimpleException*>(&ex)) {
+ *this = TError(NYT::EErrorCode::Generic, TRuntimeFormat{simpleException->GetMessage()});
+ // NB: clang-14 is incapable of capturing structured binding variables
+ // so we force materialize them via this function call.
+ auto addAttribute = [this] (const auto& key, const auto& value) {
+ std::visit([&] (const auto& actual) {
+ *this <<= TErrorAttribute(key, actual);
+ }, value);
+ };
+ for (const auto& [key, value] : simpleException->GetAttributes()) {
+ addAttribute(key, value);
+ }
+ try {
+ if (simpleException->GetInnerException()) {
+ std::rethrow_exception(simpleException->GetInnerException());
+ }
+ } catch (const std::exception& innerEx) {
+ *this <<= TError(innerEx);
+ }
+ } else if (const auto* errorEx = dynamic_cast<const TErrorException*>(&ex)) {
+ *this = errorEx->Error();
+ } else {
+ *this = TError(NYT::EErrorCode::Generic, TRuntimeFormat{ex.what()});
+ }
+ YT_VERIFY(!IsOK());
+}
+
+TError::TErrorOr(TString message, TDisableFormat)
+ : Impl_(std::make_unique<TImpl>(std::move(message)))
+{ }
+
+TError::TErrorOr(TErrorCode code, TString message, TDisableFormat)
+ : Impl_(std::make_unique<TImpl>(code, std::move(message)))
+{ }
+
+TError& TError::operator = (const TError& other)
+{
+ if (other.IsOK()) {
+ Impl_.reset();
+ } else {
+ Impl_ = std::make_unique<TImpl>(*other.Impl_);
+ }
+ return *this;
+}
+
+TError& TError::operator = (TError&& other) noexcept
+{
+ Impl_ = std::move(other.Impl_);
+ return *this;
+}
+
+TError TError::FromSystem()
+{
+ return FromSystem(LastSystemError());
+}
+
+TError TError::FromSystem(int error)
+{
+ return TError(TErrorCode(LinuxErrorCodeBase + error), TRuntimeFormat{LastSystemErrorText(error)}) <<
+ TErrorAttribute("errno", error);
+}
+
+TError TError::FromSystem(const TSystemError& error)
+{
+ return FromSystem(error.Status());
+}
+
+TErrorCode TError::GetCode() const
+{
+ if (!Impl_) {
+ return NYT::EErrorCode::OK;
+ }
+ return Impl_->GetCode();
+}
+
+TError& TError::SetCode(TErrorCode code)
+{
+ MakeMutable();
+ Impl_->SetCode(code);
+ return *this;
+}
+
+TErrorCode TError::GetNonTrivialCode() const
+{
+ if (!Impl_) {
+ return NYT::EErrorCode::OK;
+ }
+
+ if (GetCode() != NYT::EErrorCode::Generic) {
+ return GetCode();
+ }
+
+ for (const auto& innerError : InnerErrors()) {
+ auto innerCode = innerError.GetNonTrivialCode();
+ if (innerCode != NYT::EErrorCode::Generic) {
+ return innerCode;
+ }
+ }
+
+ return GetCode();
+}
+
+THashSet<TErrorCode> TError::GetDistinctNonTrivialErrorCodes() const
+{
+ THashSet<TErrorCode> result;
+ TraverseError(*this, [&result] (const TError& error, int /*depth*/) {
+ if (auto errorCode = error.GetCode(); errorCode != NYT::EErrorCode::OK) {
+ result.insert(errorCode);
+ }
+ });
+ return result;
+}
+
+const TString& TError::GetMessage() const
+{
+ if (!Impl_) {
+ static const TString Result;
+ return Result;
+ }
+ return Impl_->GetMessage();
+}
+
+TError& TError::SetMessage(TString message)
+{
+ MakeMutable();
+ *Impl_->MutableMessage() = std::move(message);
+ return *this;
+}
+
+bool TError::HasOriginAttributes() const
+{
+ if (!Impl_) {
+ return false;
+ }
+ return Impl_->HasOriginAttributes();
+}
+
+bool TError::HasDatetime() const
+{
+ if (!Impl_) {
+ return false;
+ }
+ return Impl_->HasDatetime();
+}
+
+TInstant TError::GetDatetime() const
+{
+ if (!Impl_) {
+ return {};
+ }
+ return Impl_->GetDatetime();
+}
+
+TProcessId TError::GetPid() const
+{
+ if (!Impl_) {
+ return 0;
+ }
+ return Impl_->GetPid();
+}
+
+NThreading::TThreadId TError::GetTid() const
+{
+ if (!Impl_) {
+ return NThreading::InvalidThreadId;
+ }
+ return Impl_->GetTid();
+}
+
+TStringBuf TError::GetThreadName() const
+{
+ if (!Impl_) {
+ static TString empty;
+ return empty;
+ }
+ return Impl_->GetThreadName();
+}
+
+bool TError::HasAttributes() const noexcept
+{
+ if (!Impl_) {
+ return false;
+ }
+ return Impl_->HasAttributes();
+}
+
+const TErrorAttributes& TError::Attributes() const
+{
+ if (!Impl_) {
+ static TErrorAttributes empty = {};
+ return empty;
+ }
+ return Impl_->Attributes();
+}
+
+TErrorAttributes* TError::MutableAttributes()
+{
+ MakeMutable();
+ return Impl_->MutableAttributes();
+}
+
+const std::vector<TError>& TError::InnerErrors() const
+{
+ if (!Impl_) {
+ static const std::vector<TError> Result;
+ return Result;
+ }
+ return Impl_->InnerErrors();
+}
+
+std::vector<TError>* TError::MutableInnerErrors()
+{
+ MakeMutable();
+ return Impl_->MutableInnerErrors();
+}
+
+TOriginAttributes* TError::MutableOriginAttributes() const noexcept
+{
+ if (!Impl_) {
+ return nullptr;
+ }
+ return Impl_->MutableOriginAttributes();
+}
+
+void TError::UpdateOriginAttributes()
+{
+ if (!Impl_) {
+ return;
+ }
+ Impl_->UpdateOriginAttributes();
+}
+
+const TString InnerErrorsTruncatedKey("inner_errors_truncated");
+
+TError TError::Truncate(
+ int maxInnerErrorCount,
+ i64 stringLimit,
+ const THashSet<TStringBuf>& attributeWhitelist) const &
+{
+ if (!Impl_) {
+ return TError();
+ }
+
+ auto truncateInnerError = [=, &attributeWhitelist] (const TError& innerError) {
+ return innerError.Truncate(maxInnerErrorCount, stringLimit, attributeWhitelist);
+ };
+
+ auto truncateAttributes = [stringLimit, &attributeWhitelist] (const TErrorAttributes& attributes, TErrorAttributes* mutableAttributes) {
+ for (const auto& key : attributes.ListKeys()) {
+ const auto& value = attributes.FindValue(key);
+
+ if (std::ssize(value.AsStringBuf()) > stringLimit && !attributeWhitelist.contains(key)) {
+ mutableAttributes->SetValue(
+ key,
+ NYT::ToErrorAttributeValue("...<attribute truncated>..."));
+ } else {
+ mutableAttributes->SetValue(
+ key,
+ value);
+ }
+ }
+ };
+
+ auto result = std::make_unique<TImpl>();
+ result->SetCode(GetCode());
+ *result->MutableMessage()
+ = std::move(TruncateString(GetMessage(), stringLimit, ErrorMessageTruncatedSuffix));
+ if (Impl_->HasAttributes()) {
+ truncateAttributes(Impl_->Attributes(), result->MutableAttributes());
+ }
+ *result->MutableOriginAttributes() = Impl_->OriginAttributes();
+
+ const auto& innerErrors = InnerErrors();
+ auto& copiedInnerErrors = *result->MutableInnerErrors();
+
+ if (std::ssize(innerErrors) <= maxInnerErrorCount) {
+ for (const auto& innerError : innerErrors) {
+ copiedInnerErrors.push_back(truncateInnerError(innerError));
+ }
+ } else {
+ result->MutableAttributes()->SetValue(InnerErrorsTruncatedKey, NYT::ToErrorAttributeValue(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.
+ for (int idx = 0; idx < std::ssize(innerErrors) - 1; ++idx) {
+ const auto& innerError = innerErrors[idx];
+ if (
+ IsWhitelisted(innerError, attributeWhitelist) ||
+ std::ssize(copiedInnerErrors) < maxInnerErrorCount - 1)
+ {
+ copiedInnerErrors.push_back(truncateInnerError(innerError));
+ }
+ }
+ copiedInnerErrors.push_back(truncateInnerError(innerErrors.back()));
+ }
+
+ return TError(std::move(result));
+}
+
+TError TError::Truncate(
+ int maxInnerErrorCount,
+ i64 stringLimit,
+ const THashSet<TStringBuf>& attributeWhitelist) &&
+{
+ if (!Impl_) {
+ return TError();
+ }
+
+ auto truncateInnerError = [=, &attributeWhitelist] (TError& innerError) {
+ innerError = std::move(innerError).Truncate(maxInnerErrorCount, stringLimit, attributeWhitelist);
+ };
+
+ auto truncateAttributes = [stringLimit, &attributeWhitelist] (TErrorAttributes* attributes) {
+ for (const auto& key : attributes->ListKeys()) {
+ if (std::ssize(attributes->FindValue(key).AsStringBuf()) > stringLimit && !attributeWhitelist.contains(key)) {
+ attributes->SetValue(
+ key,
+ NYT::ToErrorAttributeValue("...<attribute truncated>..."));
+ }
+ }
+ };
+
+ TruncateStringInplace(Impl_->MutableMessage(), stringLimit, ErrorMessageTruncatedSuffix);
+ if (Impl_->HasAttributes()) {
+ truncateAttributes(Impl_->MutableAttributes());
+ }
+ if (std::ssize(InnerErrors()) <= maxInnerErrorCount) {
+ for (auto& innerError : *MutableInnerErrors()) {
+ truncateInnerError(innerError);
+ }
+ } else {
+ auto& innerErrors = ApplyWhitelist(*MutableInnerErrors(), attributeWhitelist, maxInnerErrorCount);
+ MutableAttributes()->SetValue(InnerErrorsTruncatedKey, NYT::ToErrorAttributeValue(true));
+
+ for (auto& innerError : innerErrors) {
+ truncateInnerError(innerError);
+ }
+ }
+
+ return std::move(*this);
+}
+
+bool TError::IsOK() const
+{
+ if (!Impl_) {
+ return true;
+ }
+ return Impl_->IsOK();
+}
+
+TError TError::Wrap() const &
+{
+ return *this;
+}
+
+TError TError::Wrap() &&
+{
+ return std::move(*this);
+}
+
+Y_WEAK TString GetErrorSkeleton(const TError& /*error*/)
+{
+ // Proper implementation resides in yt/yt/library/error_skeleton/skeleton.cpp.
+ THROW_ERROR_EXCEPTION("Error skeleton implementation library is not linked; consider PEERDIR'ing yt/yt/library/error_skeleton");
+}
+
+TString TError::GetSkeleton() const
+{
+ return GetErrorSkeleton(*this);
+}
+
+std::optional<TError> TError::FindMatching(TErrorCode code) const
+{
+ return FindMatching([&] (TErrorCode errorCode) {
+ return code == errorCode;
+ });
+}
+
+std::optional<TError> TError::FindMatching(const THashSet<TErrorCode>& codes) const
+{
+ return FindMatching([&] (TErrorCode code) {
+ return codes.contains(code);
+ });
+}
+
+TError::TErrorOr(std::unique_ptr<TImpl> impl)
+ : Impl_(std::move(impl))
+{ }
+
+void TError::MakeMutable()
+{
+ if (!Impl_) {
+ Impl_ = std::make_unique<TImpl>();
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TError& TError::operator <<= (const TErrorAttribute& attribute) &
+{
+ MutableAttributes()->SetAttribute(attribute);
+ return *this;
+}
+
+TError& TError::operator <<= (const std::vector<TErrorAttribute>& attributes) &
+{
+ for (const auto& attribute : attributes) {
+ MutableAttributes()->SetAttribute(attribute);
+ }
+ return *this;
+}
+
+TError& TError::operator <<= (const TError& innerError) &
+{
+ MutableInnerErrors()->push_back(innerError);
+ return *this;
+}
+
+TError& TError::operator <<= (TError&& innerError) &
+{
+ MutableInnerErrors()->push_back(std::move(innerError));
+ return *this;
+}
+
+TError& TError::operator <<= (const std::vector<TError>& innerErrors) &
+{
+ MutableInnerErrors()->insert(
+ MutableInnerErrors()->end(),
+ innerErrors.begin(),
+ innerErrors.end());
+ return *this;
+}
+
+TError& TError::operator <<= (std::vector<TError>&& innerErrors) &
+{
+ MutableInnerErrors()->insert(
+ MutableInnerErrors()->end(),
+ std::make_move_iterator(innerErrors.begin()),
+ std::make_move_iterator(innerErrors.end()));
+ return *this;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool operator == (const TError& lhs, const TError& rhs)
+{
+ if (!lhs.MutableOriginAttributes() && !rhs.MutableOriginAttributes()) {
+ return true;
+ }
+ // NB(arkady-e1ppa): Origin attributes equality comparison is
+ // bit-wise but garbage bits are zeroed so it shouldn't matter.
+ return
+ lhs.GetCode() == rhs.GetCode() &&
+ lhs.GetMessage() == rhs.GetMessage() &&
+ *lhs.MutableOriginAttributes() == *rhs.MutableOriginAttributes() &&
+ lhs.Attributes() == rhs.Attributes() &&
+ lhs.InnerErrors() == rhs.InnerErrors();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void AppendIndent(TStringBuilderBase* builer, int indent)
+{
+ builer->AppendChar(' ', indent);
+}
+
+void AppendAttribute(TStringBuilderBase* builder, const TString& key, const TString& value, int indent)
+{
+ AppendIndent(builder, indent + 4);
+ if (!value.Contains('\n')) {
+ builder->AppendFormat("%-15s %s", key, value);
+ } else {
+ builder->AppendString(key);
+ TString indentedValue = "\n" + value;
+ SubstGlobal(indentedValue, "\n", "\n" + TString{static_cast<size_t>(indent + 8), ' '});
+ // Now first line in indentedValue is empty and every other line is indented by 8 spaces.
+ builder->AppendString(indentedValue);
+ }
+ builder->AppendChar('\n');
+}
+
+void AppendError(TStringBuilderBase* builder, const TError& error, int indent)
+{
+ auto isStringTextYson = [] (const NYson::TYsonString& str) {
+ return
+ str &&
+ std::ssize(str.AsStringBuf()) != 0 &&
+ str.AsStringBuf().front() == '\"';
+ };
+ auto isBoolTextYson = [] (const NYson::TYsonString& str) {
+ return
+ str.AsStringBuf() == "%false" ||
+ str.AsStringBuf() == "%true";
+ };
+
+ if (error.IsOK()) {
+ builder->AppendString("OK");
+ return;
+ }
+
+ AppendIndent(builder, indent);
+ builder->AppendString(error.GetMessage());
+ builder->AppendChar('\n');
+
+ if (error.GetCode() != NYT::EErrorCode::Generic) {
+ AppendAttribute(builder, "code", NYT::ToString(static_cast<int>(error.GetCode())), indent);
+ }
+
+ // Pretty-print origin.
+ const auto* originAttributes = error.MutableOriginAttributes();
+ YT_ASSERT(originAttributes);
+ if (error.HasOriginAttributes()) {
+ AppendAttribute(
+ builder,
+ "origin",
+ NYT::NDetail::FormatOrigin(*originAttributes),
+ indent);
+ } else if (IsErrorSanitizerEnabled() && originAttributes->Host.operator bool()) {
+ AppendAttribute(
+ builder,
+ "host",
+ TString{originAttributes->Host},
+ indent);
+ }
+
+ if (error.HasDatetime()) {
+ AppendAttribute(
+ builder,
+ "datetime",
+ Format("%v", error.GetDatetime()),
+ indent);
+ }
+
+ for (const auto& [key, value] : error.Attributes().ListPairs()) {
+ if (isStringTextYson(value)) {
+ AppendAttribute(builder, key, ConvertFromTextYsonString<TString>(value), indent);
+ } else if (isBoolTextYson(value)) {
+ AppendAttribute(builder, key, TString{FormatBool(ConvertFromTextYsonString<bool>(value))}, indent);
+ } else {
+ AppendAttribute(builder, key, value.ToString(), indent);
+ }
+ }
+
+ for (const auto& innerError : error.InnerErrors()) {
+ builder->AppendChar('\n');
+ AppendError(builder, innerError, indent + 2);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, const TError& error, TStringBuf /*spec*/)
+{
+ AppendError(builder, error, 0);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+void TraverseError(const TError& error, const TErrorVisitor& visitor, int depth)
+{
+ visitor(error, depth);
+ for (const auto& inner : error.InnerErrors()) {
+ TraverseError(inner, visitor, depth + 1);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+const char* TErrorException::what() const noexcept
+{
+ if (CachedWhat_.empty()) {
+ CachedWhat_ = ToString(Error_);
+ }
+ return CachedWhat_.data();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/error/error.h b/library/cpp/yt/error/error.h
new file mode 100644
index 0000000000..6412ad029b
--- /dev/null
+++ b/library/cpp/yt/error/error.h
@@ -0,0 +1,442 @@
+#pragma once
+
+#include <library/cpp/yt/error/error_code.h>
+
+#include <library/cpp/yt/threading/public.h>
+
+#include <library/cpp/yt/error/mergeable_dictionary.h>
+
+#include <library/cpp/yt/yson/public.h>
+
+#include <library/cpp/yt/yson_string/convert.h>
+#include <library/cpp/yt/yson_string/string.h>
+
+#include <library/cpp/yt/misc/property.h>
+
+#include <util/system/compiler.h>
+#include <util/system/getpid.h>
+
+#include <util/generic/size_literals.h>
+
+#include <type_traits>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! An opaque wrapper around |int| value capable of conversions from |int|s and
+//! arbitrary enum types.
+class TErrorCode
+{
+public:
+ using TUnderlying = int;
+
+ constexpr TErrorCode();
+ explicit constexpr TErrorCode(int value);
+ template <class E>
+ requires std::is_enum_v<E>
+ constexpr TErrorCode(E value);
+
+ constexpr operator int() const;
+
+ template <class E>
+ requires std::is_enum_v<E>
+ constexpr operator E() const;
+
+ template <class E>
+ requires std::is_enum_v<E>
+ constexpr bool operator == (E rhs) const;
+
+ constexpr bool operator == (TErrorCode rhs) const;
+private:
+ int Value_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, TErrorCode code, TStringBuf spec);
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TValue>
+concept CErrorNestable = requires (TError& error, TValue&& operand)
+{
+ { error <<= std::forward<TValue>(operand) } -> std::same_as<TError&>;
+};
+
+template <>
+class [[nodiscard]] TErrorOr<void>
+{
+public:
+ TErrorOr();
+ ~TErrorOr();
+
+ TErrorOr(const TError& other);
+ TErrorOr(TError&& other) noexcept;
+
+ TErrorOr(const std::exception& ex);
+
+ struct TDisableFormat
+ { };
+ static constexpr TDisableFormat DisableFormat = {};
+
+ TErrorOr(TString message, TDisableFormat);
+ TErrorOr(TErrorCode code, TString message, TDisableFormat);
+
+ template <class... TArgs>
+ explicit TErrorOr(
+ TFormatString<TArgs...> format,
+ TArgs&&... arg);
+
+ template <class... TArgs>
+ TErrorOr(
+ TErrorCode code,
+ TFormatString<TArgs...> format,
+ TArgs&&... args);
+
+ TError& operator = (const TError& other);
+ TError& operator = (TError&& other) noexcept;
+
+ static TError FromSystem();
+ static TError FromSystem(int error);
+ static TError FromSystem(const TSystemError& error);
+
+ TErrorCode GetCode() const;
+ TError& SetCode(TErrorCode code);
+
+ TErrorCode GetNonTrivialCode() const;
+ THashSet<TErrorCode> GetDistinctNonTrivialErrorCodes() const;
+
+ const TString& GetMessage() const;
+ TError& SetMessage(TString message);
+
+ bool HasOriginAttributes() const;
+ TProcessId GetPid() const;
+ TStringBuf GetThreadName() const;
+ NThreading::TThreadId GetTid() const;
+
+ bool HasDatetime() const;
+ TInstant GetDatetime() const;
+
+ bool HasAttributes() const noexcept;
+
+ 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 UpdateOriginAttributes();
+
+ TError Truncate(
+ int maxInnerErrorCount = 2,
+ i64 stringLimit = 16_KB,
+ const THashSet<TStringBuf>& attributeWhitelist = {}) const &;
+ TError Truncate(
+ int maxInnerErrorCount = 2,
+ i64 stringLimit = 16_KB,
+ const THashSet<TStringBuf>& attributeWhitelist = {}) &&;
+
+ bool IsOK() const;
+
+ template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+ void ThrowOnError(U&& u) const &;
+ template <class... TArgs>
+ void ThrowOnError(TFormatString<TArgs...> format, TArgs&&... args) const &;
+ template <class... TArgs>
+ void ThrowOnError(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) const &;
+ inline void ThrowOnError() const &;
+
+ template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+ void ThrowOnError(U&& u) &&;
+ template <class... TArgs>
+ void ThrowOnError(TFormatString<TArgs...> format, TArgs&&... args) &&;
+ template <class... TArgs>
+ void ThrowOnError(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) &&;
+ inline void ThrowOnError() &&;
+
+ template <CInvocable<bool(const TError&)> TFilter>
+ std::optional<TError> FindMatching(const TFilter& filter) const;
+ template <CInvocable<bool(TErrorCode)> TFilter>
+ std::optional<TError> FindMatching(const TFilter& filter) const;
+ std::optional<TError> FindMatching(TErrorCode code) const;
+ std::optional<TError> FindMatching(const THashSet<TErrorCode>& codes) const;
+
+ template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+ TError Wrap(U&& u) const &;
+ template <class... TArgs>
+ TError Wrap(TFormatString<TArgs...> format, TArgs&&... args) const &;
+ template <class... TArgs>
+ TError Wrap(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) const &;
+ TError Wrap() const &;
+
+ template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+ TError Wrap(U&& u) &&;
+ template <class... TArgs>
+ TError Wrap(TFormatString<TArgs...> format, TArgs&&... args) &&;
+ template <class... TArgs>
+ TError Wrap(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) &&;
+ TError Wrap() &&;
+
+ //! Perform recursive aggregation of error codes and messages over the error tree.
+ //! Result of this aggregation is suitable for error clustering in groups of
+ //! "similar" errors. Refer to yt/yt/library/error_skeleton/skeleton_ut.cpp for examples.
+ //!
+ //! This method builds skeleton from scratch by doing complete error tree traversal,
+ //! so calling it in computationally hot code is discouraged.
+ //!
+ //! In order to prevent core -> re2 dependency, implementation belongs to a separate library
+ //! yt/yt/library/error_skeleton. Calling this method without PEERDIR'ing implementation
+ //! results in an exception.
+ TString GetSkeleton() const;
+
+ TError& operator <<= (const TErrorAttribute& attribute) &;
+ TError& operator <<= (const std::vector<TErrorAttribute>& attributes) &;
+ TError& operator <<= (const TError& innerError) &;
+ TError& operator <<= (TError&& innerError) &;
+ TError& operator <<= (const std::vector<TError>& innerErrors) &;
+ TError& operator <<= (std::vector<TError>&& innerErrors) &;
+ template <CMergeableDictionary TDictionary>
+ TError& operator <<= (const TDictionary& attributes) &;
+
+ template <CErrorNestable TValue>
+ TError&& operator << (TValue&& operand) &&;
+
+ template <CErrorNestable TValue>
+ TError operator << (TValue&& operand) const &;
+
+ template <CErrorNestable TValue>
+ TError&& operator << (const std::optional<TValue>& rhs) &&;
+
+ template <CErrorNestable TValue>
+ TError operator << (const std::optional<TValue>& rhs) const &;
+
+private:
+ class TImpl;
+ std::unique_ptr<TImpl> Impl_;
+
+ explicit TErrorOr(std::unique_ptr<TImpl> impl);
+
+ void MakeMutable();
+
+ friend class TErrorAttributes;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool operator == (const TError& lhs, const TError& rhs);
+
+////////////////////////////////////////////////////////////////////////////////
+
+void FormatValue(TStringBuilderBase* builder, const TError& error, TStringBuf spec);
+
+////////////////////////////////////////////////////////////////////////////////
+
+using TErrorVisitor = std::function<void(const TError&, int depth)>;
+
+//! Traverses the error tree in DFS order.
+void TraverseError(
+ const TError& error,
+ const TErrorVisitor& visitor,
+ int depth = 0);
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+struct TErrorTraits
+{
+ using TWrapped = TErrorOr<T>;
+ using TUnwrapped = T;
+};
+
+template <class T>
+struct TErrorTraits<TErrorOr<T>>
+{
+ using TUnderlying = T;
+ using TWrapped = TErrorOr<T>;
+ using TUnwrapped = T;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TErrorException
+ : public std::exception
+{
+public:
+ DEFINE_BYREF_RW_PROPERTY(TError, Error);
+
+public:
+ TErrorException() = default;
+ TErrorException(const TErrorException& other) = default;
+ TErrorException(TErrorException&& other) = default;
+
+ const char* what() const noexcept override;
+
+private:
+ mutable TString CachedWhat_;
+};
+
+// Make these templates to avoid type erasure during throw.
+template <class TException>
+ requires std::derived_from<std::remove_cvref_t<TException>, TErrorException>
+TException&& operator <<= (TException&& ex, const TError& error);
+template <class TException>
+ requires std::derived_from<std::remove_cvref_t<TException>, TErrorException>
+TException&& operator <<= (TException&& ex, TError&& error);
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NDetail {
+
+struct TErrorAdaptor
+{
+ template <class TArg>
+ requires std::constructible_from<TError, TArg>
+ TError operator << (TArg&& rhs) const;
+
+ template <class TArg>
+ requires
+ std::constructible_from<TError, TArg> &&
+ std::derived_from<std::remove_cvref_t<TArg>, TError>
+ TArg&& operator << (TArg&& rhs) const;
+};
+
+// Make these to correctly forward TError to Wrap call.
+template <class TErrorLike, class U>
+ requires
+ std::derived_from<std::remove_cvref_t<TErrorLike>, TError> &&
+ (!CStringLiteral<std::remove_cvref_t<U>>)
+void ThrowErrorExceptionIfFailed(TErrorLike&& error, U&& u);
+
+template <class TErrorLike, class... TArgs>
+ requires std::derived_from<std::remove_cvref_t<TErrorLike>, TError>
+void ThrowErrorExceptionIfFailed(TErrorLike&& error, TFormatString<TArgs...> format, TArgs&&... args);
+
+template <class TErrorLike, class... TArgs>
+ requires std::derived_from<std::remove_cvref_t<TErrorLike>, TError>
+void ThrowErrorExceptionIfFailed(TErrorLike&& error, TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args);
+
+template <class TErrorLike>
+ requires std::derived_from<std::remove_cvref_t<TErrorLike>, TError>
+void ThrowErrorExceptionIfFailed(TErrorLike&& error);
+
+} // namespace NDetail
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define THROW_ERROR \
+ throw ::NYT::TErrorException() <<= ::NYT::NDetail::TErrorAdaptor() <<
+
+#define THROW_ERROR_EXCEPTION(head, ...) \
+ THROW_ERROR ::NYT::TError(head __VA_OPT__(,) __VA_ARGS__)
+
+// NB: When given an error and a string as arguments, this macro automatically wraps
+// new error around the initial one.
+#define THROW_ERROR_EXCEPTION_IF_FAILED(error, ...) \
+ ::NYT::NDetail::ThrowErrorExceptionIfFailed((error) __VA_OPT__(,) __VA_ARGS__); \
+
+#define THROW_ERROR_EXCEPTION_UNLESS(condition, head, ...) \
+ if ((condition)) {\
+ } else { \
+ THROW_ERROR ::NYT::TError(head __VA_OPT__(,) __VA_ARGS__); \
+ }
+
+#define THROW_ERROR_EXCEPTION_IF(condition, head, ...) \
+ THROW_ERROR_EXCEPTION_UNLESS(!(condition), head, __VA_ARGS__)
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+class [[nodiscard]] TErrorOr
+ : public TError
+{
+public:
+ TErrorOr();
+
+ TErrorOr(const T& value);
+ TErrorOr(T&& value) noexcept;
+
+ TErrorOr(const TErrorOr<T>& other);
+ TErrorOr(TErrorOr<T>&& other) noexcept;
+
+ TErrorOr(const TError& other);
+ TErrorOr(TError&& other) noexcept;
+
+ TErrorOr(const std::exception& ex);
+
+ template <class U>
+ TErrorOr(const TErrorOr<U>& other);
+ template <class U>
+ TErrorOr(TErrorOr<U>&& other) noexcept;
+
+ TErrorOr<T>& operator = (const TErrorOr<T>& other)
+ requires std::is_copy_assignable_v<T>;
+ TErrorOr<T>& operator = (TErrorOr<T>&& other) noexcept
+ requires std::is_nothrow_move_assignable_v<T>;
+
+ const T& Value() const & Y_LIFETIME_BOUND;
+ T& Value() & Y_LIFETIME_BOUND;
+ T&& Value() && Y_LIFETIME_BOUND;
+
+ template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+ const T& ValueOrThrow(U&& u) const & Y_LIFETIME_BOUND;
+ template <class... TArgs>
+ const T& ValueOrThrow(TFormatString<TArgs...> format, TArgs&&... args) const & Y_LIFETIME_BOUND;
+ template <class... TArgs>
+ const T& ValueOrThrow(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) const & Y_LIFETIME_BOUND;
+ const T& ValueOrThrow() const & Y_LIFETIME_BOUND;
+
+ template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+ T& ValueOrThrow(U&& u) & Y_LIFETIME_BOUND;
+ template <class... TArgs>
+ T& ValueOrThrow(TFormatString<TArgs...> format, TArgs&&... args) & Y_LIFETIME_BOUND;
+ template <class... TArgs>
+ T& ValueOrThrow(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) & Y_LIFETIME_BOUND;
+ T& ValueOrThrow() & Y_LIFETIME_BOUND;
+
+ template <class U>
+ requires (!CStringLiteral<std::remove_cvref_t<U>>)
+ T&& ValueOrThrow(U&& u) && Y_LIFETIME_BOUND;
+ template <class... TArgs>
+ T&& ValueOrThrow(TFormatString<TArgs...> format, TArgs&&... args) && Y_LIFETIME_BOUND;
+ template <class... TArgs>
+ T&& ValueOrThrow(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) && Y_LIFETIME_BOUND;
+ T&& ValueOrThrow() && Y_LIFETIME_BOUND;
+
+ const T& ValueOrDefault(const T& defaultValue Y_LIFETIME_BOUND) const & Y_LIFETIME_BOUND;
+ T& ValueOrDefault(T& defaultValue Y_LIFETIME_BOUND) & Y_LIFETIME_BOUND;
+ constexpr T ValueOrDefault(T&& defaultValue) const &;
+ constexpr T ValueOrDefault(T&& defaultValue) &&;
+
+private:
+ std::optional<T> Value_;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+void FormatValue(TStringBuilderBase* builder, const TErrorOr<T>& error, TStringBuf spec);
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class F, class... As>
+auto RunNoExcept(F&& functor, As&&... args) noexcept -> decltype(functor(std::forward<As>(args)...))
+{
+ return functor(std::forward<As>(args)...);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
+
+#define STRIPPED_ERROR_INL_H_
+#include "error-inl.h"
+#undef STRIPPED_ERROR_INL_H_
diff --git a/library/cpp/yt/error/error_attribute-inl.h b/library/cpp/yt/error/error_attribute-inl.h
new file mode 100644
index 0000000000..df7696b85b
--- /dev/null
+++ b/library/cpp/yt/error/error_attribute-inl.h
@@ -0,0 +1,57 @@
+#ifndef ERROR_ATTRIBUTE_INL_H_
+#error "Direct inclusion of this file is not allowed, include error_attribute.h"
+// For the sake of sane code completion.
+#include "error_attribute.h"
+#endif
+
+#include <library/cpp/yt/yson_string/convert.h>
+#include <library/cpp/yt/yson_string/format.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NAttributeValueConversionImpl {
+
+template <CPrimitiveConvertible T>
+NYson::TYsonString TagInvoke(TTagInvokeTag<ToErrorAttributeValue>, const T& value)
+{
+ if constexpr (std::constructible_from<TStringBuf, const T&>) {
+ return NYson::ConvertToTextYsonString(TStringBuf(value));
+ } else {
+ return NYson::ConvertToTextYsonString(value);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+inline bool IsBinaryYson(const NYson::TYsonString& yson)
+{
+ using namespace NYson::NDetail;
+
+ auto view = yson.AsStringBuf();
+ return
+ std::ssize(view) != 0 &&
+ (view.front() == EntitySymbol ||
+ view.front() == StringMarker ||
+ view.front() == Int64Marker ||
+ view.front() == DoubleMarker ||
+ view.front() == FalseMarker ||
+ view.front() == TrueMarker ||
+ view.front() == Uint64Marker);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <CPrimitiveConvertible T>
+T TagInvoke(TFrom<T>, const NYson::TYsonString& value)
+{
+ YT_VERIFY(!IsBinaryYson(value));
+ return NYson::ConvertFromTextYsonString<T>(value);
+}
+
+} // namespace NAttributeValueConversionImpl
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/error/error_attribute.h b/library/cpp/yt/error/error_attribute.h
index dec4a4dd9b..8b7a79004a 100644
--- a/library/cpp/yt/error/error_attribute.h
+++ b/library/cpp/yt/error/error_attribute.h
@@ -1,5 +1,8 @@
#pragma once
+#include "public.h"
+
+#include <library/cpp/yt/misc/guid.h>
#include <library/cpp/yt/misc/tag_invoke_cpo.h>
// TODO(arkady-e1ppa): Eliminate.
@@ -9,43 +12,72 @@ namespace NYT {
////////////////////////////////////////////////////////////////////////////////
-namespace NToAttributeValueImpl {
+template <class T>
+concept CPrimitiveConvertible =
+ std::same_as<T, i8> ||
+ std::same_as<T, i32> ||
+ std::same_as<T, i64> ||
+ std::same_as<T, ui8> ||
+ std::same_as<T, ui32> ||
+ std::same_as<T, ui64> ||
+ std::same_as<T, float> ||
+ std::same_as<T, double> ||
+ std::constructible_from<TStringBuf, const T&> ||
+ std::same_as<T, TDuration> ||
+ std::same_as<T, TInstant> ||
+ std::same_as<T, bool> ||
+ std::same_as<T, TGuid>;
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace NAttributeValueConversionImpl {
+
+struct TTo
+ : public TTagInvokeCpoBase<TTo>
+{ };
+
+////////////////////////////////////////////////////////////////////////////////
-struct TFn
- : public TTagInvokeCpoBase<TFn>
+template <class U>
+struct TFrom
+ : public TTagInvokeCpoBase<TFrom<U>>
{ };
-} // namespace NToAttributeValueImpl
+} // namespace NAttributeValueConversionImpl
////////////////////////////////////////////////////////////////////////////////
-inline constexpr NToAttributeValueImpl::TFn ToAttributeValue = {};
+inline constexpr NAttributeValueConversionImpl::TTo ToErrorAttributeValue = {};
+template <class U>
+inline constexpr NAttributeValueConversionImpl::TFrom<U> FromErrorAttributeValue = {};
////////////////////////////////////////////////////////////////////////////////
template <class T>
-concept CConvertibleToAttributeValue = CTagInvocableS<
- TTagInvokeTag<ToAttributeValue>,
- NYson::TYsonString(const T&)>;
+concept CConvertibleToAttributeValue = requires (const T& value) {
+ { NYT::ToErrorAttributeValue(value) } -> std::same_as<NYson::TYsonString>;
+};
+
+template <class T>
+concept CConvertibleFromAttributeValue = requires (const NYson::TYsonString& value) {
+ { NYT::FromErrorAttributeValue<T>(value) } -> std::same_as<T>;
+};
////////////////////////////////////////////////////////////////////////////////
struct TErrorAttribute
{
- // NB(arkady-e1ppa): Switch to std::string is quite possible
+ // TODO(arkady-e1ppa): Switch to std::string is quite possible
// however it requires patching IAttributeDictionary or
// switching it to the std::string first for interop reasons.
// Do that later.
using TKey = TString;
- // TODO(arkady-e1ppa): Use ConvertToYsonString(value, Format::Text)
- // here for complex values. Write manual implementations as ToString
- // for primitive types (e.g. integral types, guid, string, time).
using TValue = NYson::TYsonString;
template <CConvertibleToAttributeValue T>
TErrorAttribute(const TKey& key, const T& value)
: Key(key)
- , Value(NYT::ToAttributeValue(value))
+ , Value(NYT::ToErrorAttributeValue(value))
{ }
TKey Key;
@@ -55,3 +87,7 @@ struct TErrorAttribute
////////////////////////////////////////////////////////////////////////////////
} // namespace NYT
+
+#define ERROR_ATTRIBUTE_INL_H_
+#include "error_attribute-inl.h"
+#undef ERROR_ATTRIBUTE_INL_H_
diff --git a/library/cpp/yt/error/error_attributes-inl.h b/library/cpp/yt/error/error_attributes-inl.h
index 6574665865..0a77d9afb0 100644
--- a/library/cpp/yt/error/error_attributes-inl.h
+++ b/library/cpp/yt/error/error_attributes-inl.h
@@ -9,34 +9,34 @@ namespace NYT {
////////////////////////////////////////////////////////////////////////////////
template <class T>
- requires CConvertsTo<T, TErrorAttributes::TValue>
+ requires CConvertibleFromAttributeValue<T>
T TErrorAttributes::Get(TStringBuf key) const
{
- auto yson = GetYson(key);
+ auto yson = GetValue(key);
try {
- return NYT::ConvertTo<T>(yson);
+ return NYT::FromErrorAttributeValue<T>(yson);
} catch (const std::exception& ex) {
ThrowCannotParseAttributeException(key, ex);
}
}
template <class T>
- requires CConvertsTo<T, TErrorAttributes::TValue>
+ requires CConvertibleFromAttributeValue<T>
typename TOptionalTraits<T>::TOptional TErrorAttributes::Find(TStringBuf key) const
{
- auto yson = FindYson(key);
- if (!yson) {
+ auto value = FindValue(key);
+ if (!value) {
return typename TOptionalTraits<T>::TOptional();
}
try {
- return NYT::ConvertTo<T>(yson);
+ return NYT::FromErrorAttributeValue<T>(value);
} catch (const std::exception& ex) {
ThrowCannotParseAttributeException(key, ex);
}
}
template <class T>
- requires CConvertsTo<T, TErrorAttributes::TValue>
+ requires CConvertibleFromAttributeValue<T>
T TErrorAttributes::GetAndRemove(const TKey& key)
{
auto result = Get<T>(key);
@@ -45,14 +45,14 @@ T TErrorAttributes::GetAndRemove(const TKey& key)
}
template <class T>
- requires CConvertsTo<T, TErrorAttributes::TValue>
+ requires CConvertibleFromAttributeValue<T>
T TErrorAttributes::Get(TStringBuf key, const T& defaultValue) const
{
return Find<T>(key).value_or(defaultValue);
}
template <class T>
- requires CConvertsTo<T, TErrorAttributes::TValue>
+ requires CConvertibleFromAttributeValue<T>
T TErrorAttributes::GetAndRemove(const TKey& key, const T& defaultValue)
{
auto result = Find<T>(key);
@@ -65,7 +65,7 @@ T TErrorAttributes::GetAndRemove(const TKey& key, const T& defaultValue)
}
template <class T>
- requires CConvertsTo<T, TErrorAttributes::TValue>
+ requires CConvertibleFromAttributeValue<T>
typename TOptionalTraits<T>::TOptional TErrorAttributes::FindAndRemove(const TKey& key)
{
auto result = Find<T>(key);
@@ -81,7 +81,7 @@ void TErrorAttributes::MergeFrom(const TDictionary& dict)
using TTraits = TMergeDictionariesTraits<TDictionary>;
for (const auto& [key, value] : TTraits::MakeIterableView(dict)) {
- SetYson(key, value);
+ SetValue(key, value);
}
}
diff --git a/library/cpp/yt/error/error_attributes.cpp b/library/cpp/yt/error/error_attributes.cpp
index 09aa48eebb..06db3b211e 100644
--- a/library/cpp/yt/error/error_attributes.cpp
+++ b/library/cpp/yt/error/error_attributes.cpp
@@ -1,30 +1,70 @@
#include "error_attributes.h"
+#include "error.h"
+#include "error_code.h"
+
namespace NYT {
////////////////////////////////////////////////////////////////////////////////
-TErrorAttributes::TErrorAttributes(void* attributes)
- : Attributes_(attributes)
-{ }
+std::vector<TErrorAttributes::TKey> TErrorAttributes::ListKeys() const
+{
+ std::vector<TString> keys;
+ keys.reserve(Map_.size());
+ for (const auto& [key, value] : Map_) {
+ keys.push_back(key);
+ }
+ return keys;
+}
-void TErrorAttributes::Clear()
+std::vector<TErrorAttributes::TKeyValuePair> TErrorAttributes::ListPairs() const
{
- for (const auto& key : ListKeys()) {
- Remove(key);
+ std::vector<TKeyValuePair> pairs;
+ pairs.reserve(Map_.size());
+ for (const auto& pair : Map_) {
+ pairs.push_back(pair);
}
+ return pairs;
+}
+
+TErrorAttributes::TValue TErrorAttributes::FindValue(TStringBuf key) const
+{
+ auto it = Map_.find(key);
+ return it == Map_.end() ? TValue{} : it->second;
+}
+
+void TErrorAttributes::SetValue(const TKey& key, const TValue& value)
+{
+ Map_[key] = value;
}
-TErrorAttributes::TValue TErrorAttributes::GetYsonAndRemove(const TKey& key)
+void TErrorAttributes::SetAttribute(const TErrorAttribute& attribute)
{
- auto result = GetYson(key);
- Remove(key);
+ SetValue(attribute.Key, attribute.Value);
+}
+
+bool TErrorAttributes::Remove(const TKey& key)
+{
+ return Map_.erase(key) > 0;
+}
+
+TErrorAttributes::TValue TErrorAttributes::GetValue(TStringBuf key) const
+{
+ auto result = FindValue(key);
+ if (!result) {
+ ThrowNoSuchAttributeException(key);
+ }
return result;
}
+void TErrorAttributes::Clear()
+{
+ Map_.clear();
+}
+
bool TErrorAttributes::Contains(TStringBuf key) const
{
- return FindYson(key).operator bool();
+ return Map_.contains(key);
}
bool operator == (const TErrorAttributes& lhs, const TErrorAttributes& rhs)
@@ -59,4 +99,59 @@ bool operator == (const TErrorAttributes& lhs, const TErrorAttributes& rhs)
////////////////////////////////////////////////////////////////////////////////
+namespace {
+
+bool IsSpecialCharacter(char ch)
+{
+ return ch == '\\' || ch == '/' || ch == '@' || ch == '*' || ch == '&' || ch == '[' || ch == '{';
+}
+
+// AppendYPathLiteral.
+void DoFormatAttributeKey(TStringBuilderBase* builder, TStringBuf value)
+{
+ constexpr char asciiBegin = 32;
+ constexpr char asciiEnd = 127;
+ builder->Preallocate(value.length() + 16);
+ for (unsigned char ch : value) {
+ if (IsSpecialCharacter(ch)) {
+ builder->AppendChar('\\');
+ builder->AppendChar(ch);
+ } else if (ch < asciiBegin || ch > asciiEnd) {
+ builder->AppendString(TStringBuf("\\x"));
+ builder->AppendChar(IntToHexLowercase[ch >> 4]);
+ builder->AppendChar(IntToHexLowercase[ch & 0xf]);
+ } else {
+ builder->AppendChar(ch);
+ }
+ }
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+[[noreturn]] void TErrorAttributes::ThrowCannotParseAttributeException(TStringBuf key, const std::exception& ex)
+{
+ THROW_ERROR_EXCEPTION(
+ "Error parsing attribute %Qv",
+ key)
+ << ex;
+}
+
+[[noreturn]] void TErrorAttributes::ThrowNoSuchAttributeException(TStringBuf key)
+{
+ auto formatAttributeKey = [] (auto key) {
+ TStringBuilder builder;
+ DoFormatAttributeKey(&builder, key);
+ return builder.Flush();
+ };
+
+ THROW_ERROR_EXCEPTION(
+ /*NYTree::EErrorCode::ResolveError*/ TErrorCode{500},
+ "Attribute %Qv is not found",
+ formatAttributeKey(key));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
} // namespace NYT
diff --git a/library/cpp/yt/error/error_attributes.h b/library/cpp/yt/error/error_attributes.h
index 80dd80de48..da71af63cc 100644
--- a/library/cpp/yt/error/error_attributes.h
+++ b/library/cpp/yt/error/error_attributes.h
@@ -1,6 +1,5 @@
#pragma once
-#include "convert_to_cpo.h"
#include "error_attribute.h"
#include "mergeable_dictionary.h"
@@ -10,12 +9,6 @@ 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.
-// NB(arkady-e1ppa): For now we use TYsonString as value.
// TODO(arkady-e1ppa): Try switching to TString/std::string eventually.
// representing text-encoded yson string eventually (maybe).
class TErrorAttributes
@@ -31,11 +24,8 @@ public:
//! 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).
- TValue FindYson(TStringBuf key) const;
-
//! Sets the value of the attribute.
- void SetYson(const TKey& key, const TValue& value);
+ void SetAttribute(const TErrorAttribute& attribute);
//! Removes the attribute.
//! Returns |true| if the attribute was removed or |false| if there is no attribute with this key.
@@ -44,59 +34,50 @@ public:
//! Removes all attributes.
void Clear();
- //! Returns the value of the attribute (throws an exception if the attribute is not found).
- TValue GetYson(TStringBuf key) const;
-
- //! Same as #GetYson but removes the value.
- TValue GetYsonAndRemove(const TKey& 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>
- requires CConvertsTo<T, TValue>
+ requires CConvertibleFromAttributeValue<T>
T Get(TStringBuf key) const;
//! Same as #Get but removes the value.
template <class T>
- requires CConvertsTo<T, TValue>
+ requires CConvertibleFromAttributeValue<T>
T GetAndRemove(const TKey& key);
//! Finds the attribute and deserializes its value.
//! Uses default value if no such attribute is found.
template <class T>
- requires CConvertsTo<T, TValue>
+ requires CConvertibleFromAttributeValue<T>
T Get(TStringBuf key, const T& defaultValue) const;
//! Same as #Get but removes the value if it exists.
template <class T>
- requires CConvertsTo<T, TValue>
+ requires CConvertibleFromAttributeValue<T>
T GetAndRemove(const TKey& key, const T& defaultValue);
//! Finds the attribute and deserializes its value.
//! Returns null if no such attribute is found.
template <class T>
- requires CConvertsTo<T, TValue>
+ requires CConvertibleFromAttributeValue<T>
typename TOptionalTraits<T>::TOptional Find(TStringBuf key) const;
//! Same as #Find but removes the value if it exists.
template <class T>
- requires CConvertsTo<T, TValue>
+ requires CConvertibleFromAttributeValue<T>
typename TOptionalTraits<T>::TOptional FindAndRemove(const TKey& key);
template <CMergeableDictionary TDictionary>
void MergeFrom(const TDictionary& dict);
private:
- void* Attributes_; // IAttributesDictionary*
+ THashMap<TKey, TValue> Map_;
friend class TErrorOr<void>;
- explicit TErrorAttributes(void* attributes);
+ TErrorAttributes() = default;
TErrorAttributes(const TErrorAttributes& other) = default;
TErrorAttributes& operator= (const TErrorAttributes& other) = default;
@@ -104,8 +85,17 @@ private:
TErrorAttributes(TErrorAttributes&& other) = default;
TErrorAttributes& operator= (TErrorAttributes&& other) = default;
- // defined in yt/yt/core/misc/stripped_error.cpp right now.
+ //! Returns the value of the attribute (null indicates that the attribute is not found).
+ TValue FindValue(TStringBuf key) const;
+
+ //! Returns the value of the attribute (throws an exception if the attribute is not found).
+ TValue GetValue(TStringBuf key) const;
+
+ //! Sets the value of the attribute.
+ void SetValue(const TKey& key, const TValue& value);
+
[[noreturn]] static void ThrowCannotParseAttributeException(TStringBuf key, const std::exception& ex);
+ [[noreturn]] static void ThrowNoSuchAttributeException(TStringBuf key);
};
bool operator == (const TErrorAttributes& lhs, const TErrorAttributes& rhs);
diff --git a/library/cpp/yt/error/error_code.cpp b/library/cpp/yt/error/error_code.cpp
new file mode 100644
index 0000000000..9cb0d57dc2
--- /dev/null
+++ b/library/cpp/yt/error/error_code.cpp
@@ -0,0 +1,164 @@
+#include "error_code.h"
+
+#include <library/cpp/yt/logging/logger.h>
+
+#include <library/cpp/yt/misc/global.h>
+
+#include <util/string/split.h>
+
+#include <util/system/type_name.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+// TODO(achulkov2): Remove this once we find all duplicate error codes.
+static YT_DEFINE_GLOBAL(NLogging::TLogger, Logger, "ErrorCode")
+
+////////////////////////////////////////////////////////////////////////////////
+
+bool TErrorCodeRegistry::TErrorCodeInfo::operator==(const TErrorCodeInfo& rhs) const
+{
+ return Namespace == rhs.Namespace && Name == rhs.Name;
+}
+
+TErrorCodeRegistry* TErrorCodeRegistry::Get()
+{
+ return LeakySingleton<TErrorCodeRegistry>();
+}
+
+TErrorCodeRegistry::TErrorCodeInfo TErrorCodeRegistry::Get(int code) const
+{
+ auto it = CodeToInfo_.find(code);
+ if (it != CodeToInfo_.end()) {
+ return it->second;
+ }
+ for (const auto& range : ErrorCodeRanges_) {
+ if (range.Contains(code)) {
+ return range.Get(code);
+ }
+ }
+ return {"NUnknown", Format("ErrorCode%v", code)};
+}
+
+THashMap<int, TErrorCodeRegistry::TErrorCodeInfo> TErrorCodeRegistry::GetAllErrorCodes() const
+{
+ return CodeToInfo_;
+}
+
+std::vector<TErrorCodeRegistry::TErrorCodeRangeInfo> TErrorCodeRegistry::GetAllErrorCodeRanges() const
+{
+ return ErrorCodeRanges_;
+}
+
+void TErrorCodeRegistry::RegisterErrorCode(int code, const TErrorCodeInfo& errorCodeInfo)
+{
+ if (!CodeToInfo_.insert({code, errorCodeInfo}).second) {
+ // TODO(achulkov2): Deal with duplicate TransportError in NRpc and NBus.
+ if (code == 100) {
+ return;
+ }
+ // TODO(yuryalekseev): Deal with duplicate SslError in NRpc and NBus.
+ if (code == 119) {
+ return;
+ }
+ YT_LOG_FATAL(
+ "Duplicate error code (Code: %v, StoredCodeInfo: %v, NewCodeInfo: %v)",
+ code,
+ CodeToInfo_[code],
+ errorCodeInfo);
+ }
+}
+
+TErrorCodeRegistry::TErrorCodeInfo TErrorCodeRegistry::TErrorCodeRangeInfo::Get(int code) const
+{
+ return {Namespace, Formatter(code)};
+}
+
+bool TErrorCodeRegistry::TErrorCodeRangeInfo::Intersects(const TErrorCodeRangeInfo& other) const
+{
+ return std::max(From, other.From) <= std::min(To, other.To);
+}
+
+bool TErrorCodeRegistry::TErrorCodeRangeInfo::Contains(int value) const
+{
+ return From <= value && value <= To;
+}
+
+void TErrorCodeRegistry::RegisterErrorCodeRange(int from, int to, TString namespaceName, std::function<TString(int)> formatter)
+{
+ YT_VERIFY(from <= to);
+
+ TErrorCodeRangeInfo newRange{from, to, std::move(namespaceName), std::move(formatter)};
+ for (const auto& range : ErrorCodeRanges_) {
+ YT_LOG_FATAL_IF(
+ range.Intersects(newRange),
+ "Intersecting error code ranges registered (FirstRange: %v, SecondRange: %v)",
+ range,
+ newRange);
+ }
+ ErrorCodeRanges_.push_back(std::move(newRange));
+ CheckCodesAgainstRanges();
+}
+
+void TErrorCodeRegistry::CheckCodesAgainstRanges() const
+{
+ for (const auto& [code, info] : CodeToInfo_) {
+ for (const auto& range : ErrorCodeRanges_) {
+ YT_LOG_FATAL_IF(
+ range.Contains(code),
+ "Error code range contains another registered code "
+ "(Range: %v, Code: %v, RangeCodeInfo: %v, StandaloneCodeInfo: %v)",
+ range,
+ code,
+ range.Get(code),
+ info);
+ }
+ }
+}
+
+TString TErrorCodeRegistry::ParseNamespace(const std::type_info& errorCodeEnumTypeInfo)
+{
+ TString name;
+ // Ensures that "EErrorCode" is found as a substring in the type name and stores the prefix before
+ // the first occurrence into #name.
+ YT_VERIFY(StringSplitter(
+ TypeName(errorCodeEnumTypeInfo)).SplitByString("EErrorCode").Limit(2).TryCollectInto(&name, &std::ignore));
+
+ // TypeName returns name in form "enum ErrorCode" on Windows
+ if (name.StartsWith("enum ")) {
+ name.remove(0, 5);
+ }
+
+ // If the enum was declared directly in the global namespace, #name should be empty.
+ // Otherwise, #name should end with "::".
+ if (!name.empty()) {
+ YT_VERIFY(name.EndsWith("::"));
+ name.resize(name.size() - 2);
+ }
+ return name;
+}
+
+void FormatValue(
+ TStringBuilderBase* builder,
+ const TErrorCodeRegistry::TErrorCodeInfo& errorCodeInfo,
+ TStringBuf /*spec*/)
+{
+ if (errorCodeInfo.Namespace.empty()) {
+ Format(builder, "EErrorCode::%v", errorCodeInfo.Name);
+ return;
+ }
+ Format(builder, "%v::EErrorCode::%v", errorCodeInfo.Namespace, errorCodeInfo.Name);
+}
+
+void FormatValue(
+ TStringBuilderBase* builder,
+ const TErrorCodeRegistry::TErrorCodeRangeInfo& errorCodeRangeInfo,
+ TStringBuf /*spec*/)
+{
+ Format(builder, "%v-%v", errorCodeRangeInfo.From, errorCodeRangeInfo.To);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/error/error_code.h b/library/cpp/yt/error/error_code.h
new file mode 100644
index 0000000000..1c2c08fbb4
--- /dev/null
+++ b/library/cpp/yt/error/error_code.h
@@ -0,0 +1,101 @@
+#pragma once
+
+#include <library/cpp/yt/misc/enum.h>
+#include <library/cpp/yt/misc/port.h>
+#include <library/cpp/yt/misc/static_initializer.h>
+
+#include <library/cpp/yt/string/format.h>
+
+#include <util/generic/hash_set.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TErrorCodeRegistry
+{
+public:
+ static TErrorCodeRegistry* Get();
+
+ struct TErrorCodeInfo
+ {
+ TString Namespace;
+ //! Human-readable error code name.
+ TString Name;
+
+ bool operator==(const TErrorCodeInfo& rhs) const;
+ };
+
+ struct TErrorCodeRangeInfo
+ {
+ int From;
+ int To;
+ TString Namespace;
+ std::function<TString(int code)> Formatter;
+
+ TErrorCodeInfo Get(int code) const;
+ bool Intersects(const TErrorCodeRangeInfo& other) const;
+ bool Contains(int value) const;
+ };
+
+ //! Retrieves info from registered codes and code ranges.
+ TErrorCodeInfo Get(int code) const;
+
+ //! Retrieves information about registered codes.
+ THashMap<int, TErrorCodeInfo> GetAllErrorCodes() const;
+
+ //! Retrieves information about registered code ranges.
+ std::vector<TErrorCodeRangeInfo> GetAllErrorCodeRanges() const;
+
+ //! Registers a single error code.
+ void RegisterErrorCode(int code, const TErrorCodeInfo& errorCodeInfo);
+
+ //! Registers a range of error codes given a human-readable code to name formatter.
+ void RegisterErrorCodeRange(int from, int to, TString namespaceName, std::function<TString(int code)> formatter);
+
+ static TString ParseNamespace(const std::type_info& errorCodeEnumTypeInfo);
+
+private:
+ THashMap<int, TErrorCodeInfo> CodeToInfo_;
+ std::vector<TErrorCodeRangeInfo> ErrorCodeRanges_;
+
+ void CheckCodesAgainstRanges() const;
+};
+
+void FormatValue(
+ TStringBuilderBase* builder,
+ const TErrorCodeRegistry::TErrorCodeInfo& errorCodeInfo,
+ TStringBuf spec);
+
+void FormatValue(
+ TStringBuilderBase* builder,
+ const TErrorCodeRegistry::TErrorCodeRangeInfo& errorCodeInfo,
+ TStringBuf spec);
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define YT_DEFINE_ERROR_ENUM(seq) \
+ DEFINE_ENUM(EErrorCode, seq); \
+ YT_ATTRIBUTE_USED inline const void* ErrorEnum_EErrorCode = [] { \
+ for (auto errorCode : ::NYT::TEnumTraits<EErrorCode>::GetDomainValues()) { \
+ ::NYT::TErrorCodeRegistry::Get()->RegisterErrorCode( \
+ static_cast<int>(errorCode), \
+ {::NYT::TErrorCodeRegistry::ParseNamespace(typeid(EErrorCode)), ToString(errorCode)}); \
+ } \
+ return nullptr; \
+ } ()
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! NB: This macro should only by used in cpp files.
+#define YT_DEFINE_ERROR_CODE_RANGE(from, to, namespaceName, formatter) \
+ YT_STATIC_INITIALIZER( \
+ ::NYT::TErrorCodeRegistry::Get()->RegisterErrorCodeRange( \
+ from, \
+ to, \
+ namespaceName, \
+ formatter));
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/error/error_helpers-inl.h b/library/cpp/yt/error/error_helpers-inl.h
new file mode 100644
index 0000000000..d15c5f6f93
--- /dev/null
+++ b/library/cpp/yt/error/error_helpers-inl.h
@@ -0,0 +1,37 @@
+#ifndef ERROR_HELPERS_INL_H_
+#error "Direct inclusion of this file is not allowed, include error_helpers.h"
+// For the sake of sane code completion.
+#include "error_helpers.h"
+#endif
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+typename TOptionalTraits<T>::TOptional FindAttribute(const TError& error, TStringBuf key)
+{
+ return error.Attributes().Find<T>(key);
+}
+
+template <class T>
+typename TOptionalTraits<T>::TOptional FindAttributeRecursive(const TError& error, TStringBuf key)
+{
+ auto attr = FindAttribute<T>(error, key);
+ if (TOptionalTraits<T>::HasValue(attr)) {
+ return attr;
+ }
+
+ for (const auto& inner : error.InnerErrors()) {
+ attr = FindAttribute<T>(inner, key);
+ if (TOptionalTraits<T>::HasValue(attr)) {
+ return attr;
+ }
+ }
+
+ return TOptionalTraits<T>::Empty();
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
diff --git a/library/cpp/yt/error/error_helpers.h b/library/cpp/yt/error/error_helpers.h
new file mode 100644
index 0000000000..b68385be35
--- /dev/null
+++ b/library/cpp/yt/error/error_helpers.h
@@ -0,0 +1,27 @@
+#pragma once
+
+#include <library/cpp/yt/error/error.h>
+
+#include <library/cpp/yt/error/error_attributes.h>
+
+#include <library/cpp/yt/misc/optional.h>
+
+namespace NYT {
+
+// NB: Methods below are listed in a separate file and not in error.h to prevent
+// circular includes cause by the fact that attributes include error.
+////////////////////////////////////////////////////////////////////////////////
+
+template <class T>
+typename TOptionalTraits<T>::TOptional FindAttribute(const TError& error, TStringBuf key);
+
+template <class T>
+typename TOptionalTraits<T>::TOptional FindAttributeRecursive(const TError& error, TStringBuf key);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT
+
+#define ERROR_HELPERS_INL_H_
+#include "error_helpers-inl.h"
+#undef ERROR_HELPERS_INL_H_
diff --git a/library/cpp/yt/error/origin_attributes.cpp b/library/cpp/yt/error/origin_attributes.cpp
index 5ff0b03933..6f86e31ae8 100644
--- a/library/cpp/yt/error/origin_attributes.cpp
+++ b/library/cpp/yt/error/origin_attributes.cpp
@@ -1,4 +1,5 @@
#include "origin_attributes.h"
+#include "error_attributes.h"
#include <library/cpp/yt/assert/assert.h>
@@ -105,6 +106,48 @@ TString FormatOrigin(const TOriginAttributes& attributes)
}));
}
+////////////////////////////////////////////////////////////////////////////////
+
+TOriginAttributes ExtractFromDictionary(TErrorAttributes* attributes)
+{
+ using TFunctor = TOriginAttributes(*)(TErrorAttributes*);
+
+ if (auto strong = NGlobal::GetErasedVariable(ExtractFromDictionaryTag)) {
+ return strong->AsConcrete<TFunctor>()(attributes);
+ }
+
+ return ExtractFromDictionaryDefault(attributes);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TOriginAttributes ExtractFromDictionaryDefault(TErrorAttributes* attributes)
+{
+ TOriginAttributes result;
+ if (attributes == nullptr) {
+ return result;
+ }
+
+ // TODO(arkady-e1ppa): Try using std::string here.
+ static const TString HostKey("host");
+ result.HostHolder = TSharedRef::FromString(attributes->GetAndRemove(HostKey, TString()));
+ result.Host = result.HostHolder.empty() ? TStringBuf() : TStringBuf(result.HostHolder.Begin(), result.HostHolder.End());
+
+ static const TString DatetimeKey("datetime");
+ result.Datetime = attributes->GetAndRemove(DatetimeKey, TInstant());
+
+ static const TString PidKey("pid");
+ result.Pid = attributes->GetAndRemove(PidKey, TProcessId{});
+
+ static const TString TidKey("tid");
+ result.Tid = attributes->GetAndRemove(TidKey, NThreading::InvalidThreadId);
+
+ static const TString ThreadNameKey("thread");
+ result.ThreadName = attributes->GetAndRemove<TString>(ThreadNameKey, TString());
+
+ return result;
+}
+
} // namespace NDetail
////////////////////////////////////////////////////////////////////////////////
diff --git a/library/cpp/yt/error/origin_attributes.h b/library/cpp/yt/error/origin_attributes.h
index d98782469b..e25eb37ea7 100644
--- a/library/cpp/yt/error/origin_attributes.h
+++ b/library/cpp/yt/error/origin_attributes.h
@@ -1,5 +1,7 @@
#pragma once
+#include "public.h"
+
#include <library/cpp/yt/global/access.h>
#include <library/cpp/yt/memory/ref.h>
@@ -76,6 +78,14 @@ inline constexpr NGlobal::TVariableTag ExtractFromDictionaryTag = {};
std::optional<TOriginAttributes::TErasedExtensionData> GetExtensionData();
TString FormatOrigin(const TOriginAttributes& attributes);
+////////////////////////////////////////////////////////////////////////////////
+
+// Weak symbol.
+TOriginAttributes ExtractFromDictionary(TErrorAttributes* attributes);
+
+// Default impl of weak symbol.
+TOriginAttributes ExtractFromDictionaryDefault(TErrorAttributes* attributes);
+
} // namespace NDetail
////////////////////////////////////////////////////////////////////////////////
diff --git a/library/cpp/yt/error/public.h b/library/cpp/yt/error/public.h
index 04201128aa..77ce70ee07 100644
--- a/library/cpp/yt/error/public.h
+++ b/library/cpp/yt/error/public.h
@@ -1,6 +1,6 @@
#pragma once
-#include <library/cpp/yt/yson_string/string.h>
+#include "error_code.h"
namespace NYT {
@@ -9,6 +9,7 @@ namespace NYT {
template <class T>
class TErrorOr;
+using TError = TErrorOr<void>;
struct TErrorAttribute;
class TErrorAttributes;
@@ -16,4 +17,15 @@ struct TOriginAttributes;
////////////////////////////////////////////////////////////////////////////////
+YT_DEFINE_ERROR_ENUM(
+ ((OK) (0))
+ ((Generic) (1))
+ ((Canceled) (2))
+ ((Timeout) (3))
+ ((FutureCombinerFailure) (4))
+ ((FutureCombinerShortcut)(5))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
} // namespace NYT
diff --git a/library/cpp/yt/error/unittests/error_code_ut.cpp b/library/cpp/yt/error/unittests/error_code_ut.cpp
new file mode 100644
index 0000000000..4bdb17f5d9
--- /dev/null
+++ b/library/cpp/yt/error/unittests/error_code_ut.cpp
@@ -0,0 +1,145 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <library/cpp/yt/error/error.h>
+#include <library/cpp/yt/error/error_code.h>
+
+#include <library/cpp/yt/string/format.h>
+
+#include <ostream>
+
+////////////////////////////////////////////////////////////////////////////////
+
+YT_DEFINE_ERROR_ENUM(
+ ((Global1) (-5))
+ ((Global2) (-6))
+);
+
+namespace NExternalWorld {
+
+////////////////////////////////////////////////////////////////////////////////
+
+YT_DEFINE_ERROR_ENUM(
+ ((X) (-11))
+ ((Y) (-22))
+ ((Z) (-33))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NExternalWorld
+
+namespace NYT {
+
+void PrintTo(const TErrorCodeRegistry::TErrorCodeInfo& errorCodeInfo, std::ostream* os)
+{
+ *os << ToString(errorCodeInfo);
+}
+
+namespace NInternalLittleWorld {
+
+////////////////////////////////////////////////////////////////////////////////
+
+YT_DEFINE_ERROR_ENUM(
+ ((A) (-1))
+ ((B) (-2))
+ ((C) (-3))
+ ((D) (-4))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NMyOwnLittleWorld
+
+namespace {
+
+////////////////////////////////////////////////////////////////////////////////
+
+YT_DEFINE_ERROR_ENUM(
+ ((Kek) (-57))
+ ((Haha) (-179))
+ ((Muahaha) (-1543))
+ ((Kukarek) (-2007))
+);
+
+TString TestErrorCodeFormatter(int code)
+{
+ return Format("formatted%v", code);
+}
+
+YT_DEFINE_ERROR_CODE_RANGE(-4399, -4200, "NYT::Test", TestErrorCodeFormatter);
+
+DEFINE_ENUM(EDifferentTestErrorCode,
+ ((ErrorNumberOne) (-10000))
+ ((ErrorNumberTwo) (-10001))
+ ((ErrorNumberThree) (-10002))
+);
+
+TString DifferentTestErrorCodeFormatter(int code)
+{
+ return TEnumTraits<EDifferentTestErrorCode>::ToString(static_cast<EDifferentTestErrorCode>(code));
+}
+
+YT_DEFINE_ERROR_CODE_RANGE(-10005, -10000, "NYT::DifferentTest", DifferentTestErrorCodeFormatter);
+
+TEST(TErrorCodeRegistryTest, Basic)
+{
+#ifdef _unix_
+ EXPECT_EQ(
+ TErrorCodeRegistry::Get()->Get(-1543),
+ (TErrorCodeRegistry::TErrorCodeInfo{"NYT::(anonymous namespace)", "Muahaha"}));
+#else
+ EXPECT_EQ(
+ TErrorCodeRegistry::Get()->Get(-1543),
+ (TErrorCodeRegistry::TErrorCodeInfo{"NYT::`anonymous namespace'", "Muahaha"}));
+#endif
+ EXPECT_EQ(
+ TErrorCodeRegistry::Get()->Get(-3),
+ (TErrorCodeRegistry::TErrorCodeInfo{"NYT::NInternalLittleWorld", "C"}));
+ EXPECT_EQ(
+ TErrorCodeRegistry::Get()->Get(-33),
+ (TErrorCodeRegistry::TErrorCodeInfo{"NExternalWorld", "Z"}));
+ EXPECT_EQ(
+ TErrorCodeRegistry::Get()->Get(-5),
+ (TErrorCodeRegistry::TErrorCodeInfo{"", "Global1"}));
+ EXPECT_EQ(
+ TErrorCodeRegistry::Get()->Get(-4300),
+ (TErrorCodeRegistry::TErrorCodeInfo{"NYT::Test", "formatted-4300"}));
+ EXPECT_EQ(
+ TErrorCodeRegistry::Get()->Get(-10002),
+ (TErrorCodeRegistry::TErrorCodeInfo{"NYT::DifferentTest", "ErrorNumberThree"}));
+ EXPECT_EQ(
+ TErrorCodeRegistry::Get()->Get(-10005),
+ (TErrorCodeRegistry::TErrorCodeInfo{"NYT::DifferentTest", "EDifferentTestErrorCode(-10005)"}));
+ EXPECT_EQ(
+ TErrorCodeRegistry::Get()->Get(-111),
+ (TErrorCodeRegistry::TErrorCodeInfo{"NUnknown", "ErrorCode-111"}));
+}
+
+DEFINE_ENUM(ETestEnumOne,
+ ((VariantOne) (0))
+ ((VariantTwo) (1))
+);
+
+DEFINE_ENUM(ETestEnumTwo,
+ ((DifferentVariantOne) (0))
+ ((DifferentVariantTwo) (1))
+);
+
+template <class T, class K>
+concept EquallyComparable = requires(T a, K b)
+{
+ { static_cast<T>(0) == static_cast<K>(0) };
+};
+
+TEST(TErrorCodeTest, ImplicitCastTest)
+{
+ // assert TErrorCode is in scope
+ using NYT::TErrorCode;
+ bool equallyComparable = EquallyComparable<ETestEnumOne, ETestEnumTwo>;
+ EXPECT_FALSE(equallyComparable);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/library/cpp/yt/error/unittests/error_ut.cpp b/library/cpp/yt/error/unittests/error_ut.cpp
new file mode 100644
index 0000000000..a4248b3956
--- /dev/null
+++ b/library/cpp/yt/error/unittests/error_ut.cpp
@@ -0,0 +1,779 @@
+#include <yt/yt/core/test_framework/framework.h>
+
+#include <library/cpp/yt/error/error.h>
+#include <library/cpp/yt/error/error_helpers.h>
+
+#include <util/stream/str.h>
+#include <util/string/join.h>
+#include <util/string/split.h>
+
+namespace NYT {
+namespace {
+
+using namespace NYson;
+
+////////////////////////////////////////////////////////////////////////////////
+
+class TAdlException
+ : public std::exception
+{
+public:
+ static int ResetCallCount()
+ {
+ return std::exchange(OverloadCallCount, 0);
+ }
+
+ const char* what() const noexcept override
+ {
+ return "Adl exception";
+ }
+
+ // Simulate overload from TAdlException::operator <<
+ template <class TLikeThis, class TArg>
+ requires std::derived_from<std::decay_t<TLikeThis>, TAdlException>
+ friend TLikeThis&& operator << (TLikeThis&& ex, const TArg& /*other*/)
+ {
+ ++OverloadCallCount;
+ return std::forward<TLikeThis>(ex);
+ }
+
+private:
+ static inline int OverloadCallCount = 0;
+};
+
+class TAdlArgument
+{
+public:
+ static int ResetCallCount()
+ {
+ return std::exchange(OverloadCallCount, 0);
+ }
+
+ // Simulate overload TAdlArgument::operator <<
+ friend TError operator << (TError&& error, const TAdlArgument& /*other*/)
+ {
+ static const TErrorAttribute Attr("attr", "attr_value");
+ ++OverloadCallCount;
+ return std::move(error) << Attr;
+ }
+
+ friend TError operator << (const TError& error, const TAdlArgument& /*other*/)
+ {
+ static const TErrorAttribute Attr("attr", "attr_value");
+ ++OverloadCallCount;
+ return error << Attr;
+ }
+
+private:
+ static inline int OverloadCallCount = 0;
+};
+
+class TWidget
+{
+public:
+ TWidget()
+ {
+ DefaultConstructorCalls++;
+ };
+
+ TWidget(const TWidget&)
+ {
+ CopyConstructorCalls++;
+ }
+ TWidget& operator = (const TWidget&) = delete;
+
+ TWidget(TWidget&&)
+ {
+ MoveConstructorCalls++;
+ }
+ TWidget& operator = (TWidget&&) = delete;
+
+ static int ResetDefaultCount()
+ {
+ return std::exchange(DefaultConstructorCalls, 0);
+ }
+
+ static int ResetCopyCount()
+ {
+ return std::exchange(CopyConstructorCalls, 0);
+ }
+
+ static int ResetMoveCount()
+ {
+ return std::exchange(MoveConstructorCalls, 0);
+ }
+
+private:
+ static inline int DefaultConstructorCalls = 0;
+ static inline int CopyConstructorCalls = 0;
+ static inline int MoveConstructorCalls = 0;
+};
+
+////////////////////////////////////////////////////////////////////////////////
+
+template <class TOverloadTest, bool LeftOperandHasUserDefinedOverload = false>
+void IterateTestOverEveryRightOperand(TOverloadTest& tester)
+{
+ {
+ TErrorAttribute attribute("attr", "attr_value");
+ const auto& attributeRef = attribute;
+ tester(attributeRef);
+ }
+
+ {
+ std::vector<TErrorAttribute> attributeVector{{"attr1", "attr_value"}, {"attr2", "attr_value"}};
+ const auto& attributeVectorRef = attributeVector;
+ tester(attributeVectorRef);
+ }
+
+ {
+ TError error("Error");
+
+ const auto& errorRef = error;
+ tester(errorRef);
+
+ auto errorCopy = error;
+ tester(std::move(errorCopy));
+
+ if constexpr (!LeftOperandHasUserDefinedOverload) {
+ EXPECT_TRUE(errorCopy.IsOK());
+ }
+ }
+
+ {
+ std::vector<TError> vectorError{TError("Error"), TError("Error")};
+
+ const auto& vectorErrorRef = vectorError;
+ tester(vectorErrorRef);
+
+ auto vectorErrorCopy = vectorError;
+ tester(std::move(vectorErrorCopy));
+
+ if constexpr (!LeftOperandHasUserDefinedOverload) {
+ for (const auto& errorCopy : vectorErrorCopy) {
+ EXPECT_TRUE(errorCopy.IsOK());
+ }
+ }
+ }
+
+ {
+ TError error("Error");
+
+ const auto& attributeDictionaryRef = error.Attributes();
+ tester(attributeDictionaryRef);
+ }
+
+ {
+ try {
+ THROW_ERROR TError("Test error");
+ } catch(const NYT::TErrorException& ex) {
+ const auto& exRef = ex;
+ tester(exRef);
+
+ auto exCopy = ex;
+ tester(std::move(exCopy));
+ }
+ }
+
+ {
+ TErrorOr<int> err(std::exception{});
+
+ const auto& errRef = err;
+ tester(errRef);
+
+ auto errCopy = err;
+ tester(std::move(errCopy));
+
+ if constexpr (!LeftOperandHasUserDefinedOverload) {
+ EXPECT_TRUE(errCopy.IsOK());
+ }
+ }
+
+ {
+ TAdlArgument adlArg;
+
+ const TAdlArgument& adlArgRef = adlArg;
+ tester(adlArgRef);
+
+ if constexpr (!LeftOperandHasUserDefinedOverload) {
+ EXPECT_EQ(TAdlArgument::ResetCallCount(), 1);
+ }
+ }
+}
+
+template <class T>
+void SetErrorAttribute(TError* error, TString key, const T& value)
+{
+ *error <<= TErrorAttribute(key, value);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+TEST(TErrorTest, BitshiftOverloadsExplicitLeftOperand)
+{
+ // TError&& overload.
+ auto moveTester = [] (auto&& arg) {
+ TError error = TError("Test error");
+ TError moved = std::move(error) << std::forward<decltype(arg)>(arg);
+ EXPECT_TRUE(error.IsOK());
+ EXPECT_EQ(moved.GetMessage(), "Test error");
+ };
+ IterateTestOverEveryRightOperand(moveTester);
+
+ // const TError& overloads.
+ auto copyTester = [] (auto&& arg) {
+ TError error = TError("Test error");
+ TError copy = error << std::forward<decltype(arg)>(arg);
+ EXPECT_EQ(error.GetMessage(), copy.GetMessage());
+ };
+ IterateTestOverEveryRightOperand(copyTester);
+
+ // Test that TError pr value binds correctly and the call itself is unambiguous.
+ auto prvalueTester = [] (auto&& arg) {
+ TError error = TError("Test error") << std::forward<decltype(arg)>(arg);
+ EXPECT_EQ(error.GetMessage(), "Test error");
+ };
+ IterateTestOverEveryRightOperand(prvalueTester);
+}
+
+TEST(TErrorTest, BitshiftOverloadsImplicitLeftOperand)
+{
+ // We want to be able to write THROW_ERROR ex
+ auto throwErrorTester1 = [] (auto&& arg) {
+ try {
+ try {
+ THROW_ERROR TError("Test error");
+ } catch(const NYT::TErrorException& ex) {
+ THROW_ERROR ex << std::forward<decltype(arg)>(arg);
+ }
+ } catch(const NYT::TErrorException& ex) {
+ TError error = ex;
+ EXPECT_EQ(error.GetMessage(), "Test error");
+ }
+ };
+ IterateTestOverEveryRightOperand(throwErrorTester1);
+
+ // We also want to be able to write THROW_ERROR TError(smth) without compiler errors
+ auto throwErrorTester2 = [] (auto&& arg) {
+ try {
+ try {
+ THROW_ERROR TError("Test error");
+ } catch(const NYT::TErrorException& ex) {
+ THROW_ERROR TError(ex) << std::forward<decltype(arg)>(arg);
+ }
+ } catch(const NYT::TErrorException& ex) {
+ TError error = ex;
+ EXPECT_EQ(error.GetMessage(), "Test error");
+ }
+ };
+ IterateTestOverEveryRightOperand(throwErrorTester2);
+
+ // Left operand ADL finds the user-defined overload over NYT one.
+ // In this case AdlException should find templated function
+ // specialization with perfect match for args over conversions.
+ auto adlResolutionTester = [] (auto&& arg) {
+ TAdlException ex;
+ auto result = ex << std::forward<decltype(arg)>(arg);
+ static_assert(std::same_as<TAdlException, std::decay_t<decltype(result)>>);
+ EXPECT_EQ(TAdlException::ResetCallCount(), 1);
+ };
+ IterateTestOverEveryRightOperand<
+ decltype(adlResolutionTester),
+ /*LeftOperandHasUserDefinedOverload*/ true>(adlResolutionTester);
+
+ // Make sure no ambiguous calls.
+ auto genericErrorOrTester = [] (auto&& arg) {
+ TErrorOr<int> err(std::exception{});
+ TError error = err << std::forward<decltype(arg)>(arg);
+ EXPECT_EQ(error.GetCode(), NYT::EErrorCode::Generic);
+ };
+ IterateTestOverEveryRightOperand(genericErrorOrTester);
+}
+
+TEST(TErrorTest, Wrap)
+{
+ TError error("Error");
+
+ auto wrapped = error.Wrap("Wrapped error");
+ 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);
+
+ auto triviallyWrapped = error.Wrap();
+ EXPECT_EQ(triviallyWrapped, error);
+}
+
+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 anotherErrorCopy = error;
+ auto trviallyWrapped = std::move(anotherErrorCopy).Wrap();
+ EXPECT_TRUE(anotherErrorCopy.IsOK());
+ EXPECT_EQ(trviallyWrapped, error);
+}
+
+TEST(TErrorTest, ThrowErrorExceptionIfFailedMacroJustWorks)
+{
+ TError error;
+
+ EXPECT_NO_THROW(THROW_ERROR_EXCEPTION_IF_FAILED(error, "Outer error"));
+
+ error = TError("Real error");
+
+ TError errorCopy = error;
+
+ try {
+ THROW_ERROR_EXCEPTION_IF_FAILED(errorCopy, "Outer error");
+ } catch (const std::exception& ex) {
+ TError outerError(ex);
+
+ EXPECT_TRUE(errorCopy.IsOK());
+ EXPECT_EQ(outerError.GetMessage(), "Outer error");
+ EXPECT_EQ(outerError.InnerErrors().size(), 1u);
+ EXPECT_EQ(outerError.InnerErrors()[0], error);
+ }
+}
+
+TEST(TErrorTest, ThrowErrorExceptionIfFailedMacroExpression)
+{
+ try {
+ THROW_ERROR_EXCEPTION_IF_FAILED(
+ TError("Inner error")
+ << TErrorAttribute("attr", "attr_value"),
+ "Outer error");
+ } catch (const std::exception& ex) {
+ TError outerError(ex);
+
+ EXPECT_EQ(outerError.GetMessage(), "Outer error");
+ EXPECT_EQ(outerError.InnerErrors().size(), 1u);
+ EXPECT_EQ(outerError.InnerErrors()[0].GetMessage(), "Inner error");
+ EXPECT_EQ(outerError.InnerErrors()[0].Attributes().Get<TString>("attr"), "attr_value");
+ }
+}
+
+TEST(TErrorTest, ThrowErrorExceptionIfFailedMacroDontStealValue)
+{
+ TErrorOr<TWidget> widget = TWidget();
+ EXPECT_TRUE(widget.IsOK());
+ EXPECT_EQ(TWidget::ResetDefaultCount(), 1);
+ EXPECT_EQ(TWidget::ResetCopyCount(), 0);
+ EXPECT_EQ(TWidget::ResetMoveCount(), 1);
+
+ EXPECT_NO_THROW(THROW_ERROR_EXCEPTION_IF_FAILED(widget));
+ EXPECT_TRUE(widget.IsOK());
+ EXPECT_NO_THROW(widget.ValueOrThrow());
+ EXPECT_EQ(TWidget::ResetDefaultCount(), 0);
+ EXPECT_EQ(TWidget::ResetCopyCount(), 0);
+ EXPECT_EQ(TWidget::ResetMoveCount(), 0);
+}
+
+TEST(TErrorTest, ThrowErrorExceptionIfFailedMacroDontDupeCalls)
+{
+ EXPECT_NO_THROW(THROW_ERROR_EXCEPTION_IF_FAILED(TErrorOr<TWidget>(TWidget())));
+ EXPECT_EQ(TWidget::ResetDefaultCount(), 1);
+ EXPECT_EQ(TWidget::ResetCopyCount(), 0);
+ EXPECT_EQ(TWidget::ResetMoveCount(), 1);
+}
+
+TEST(TErrorTest, ErrorSkeletonStubImplementation)
+{
+ TError error("foo");
+ EXPECT_THROW(error.GetSkeleton(), std::exception);
+}
+
+TEST(TErrorTest, FormatCtor)
+{
+ // EXPECT_EQ("Some error %v", TError("Some error %v").GetMessage()); // No longer compiles due to static analysis.
+ EXPECT_EQ("Some error hello", TError("Some error %v", "hello").GetMessage());
+}
+
+TEST(TErrorTest, FindRecursive)
+{
+ auto inner = TError("Inner")
+ << TErrorAttribute("inner_attr", 42);
+ auto error = TError("Error")
+ << inner
+ << TErrorAttribute("attr", 8);
+
+ auto attr = FindAttribute<int>(error, "attr");
+ EXPECT_TRUE(attr);
+ EXPECT_EQ(*attr, 8);
+
+ EXPECT_FALSE(FindAttribute<int>(error, "inner_attr"));
+
+ auto innerAttr = FindAttributeRecursive<int>(error, "inner_attr");
+ EXPECT_TRUE(innerAttr);
+ EXPECT_EQ(*innerAttr, 42);
+}
+
+TEST(TErrorTest, TruncateSimple)
+{
+ auto error = TError("Some error")
+ << TErrorAttribute("my_attr", "Attr value")
+ << TError("Inner error");
+ auto truncatedError = error.Truncate();
+ EXPECT_EQ(error.GetCode(), truncatedError.GetCode());
+ EXPECT_EQ(error.GetMessage(), truncatedError.GetMessage());
+ EXPECT_EQ(error.GetPid(), truncatedError.GetPid());
+ EXPECT_EQ(error.GetTid(), truncatedError.GetTid());
+ EXPECT_EQ(error.GetDatetime(), truncatedError.GetDatetime());
+ EXPECT_EQ(error.Attributes().Get<TString>("my_attr"), truncatedError.Attributes().Get<TString>("my_attr"));
+ EXPECT_EQ(error.InnerErrors().size(), truncatedError.InnerErrors().size());
+ EXPECT_EQ(error.InnerErrors()[0].GetMessage(), truncatedError.InnerErrors()[0].GetMessage());
+}
+
+TEST(TErrorTest, TruncateLarge)
+{
+ auto error = TError("Some long long error")
+ << TError("First inner error")
+ << TError("Second inner error")
+ << TError("Third inner error")
+ << TError("Fourth inner error");
+ SetErrorAttribute(&error, "my_attr", "Some long long attr");
+
+ auto truncatedError = error.Truncate(/*maxInnerErrorCount*/ 3, /*stringLimit*/ 10);
+ EXPECT_EQ(error.GetCode(), truncatedError.GetCode());
+ EXPECT_EQ("Some long ...<message truncated>", truncatedError.GetMessage());
+ EXPECT_EQ("...<attribute truncated>...", truncatedError.Attributes().Get<TString>("my_attr"));
+ EXPECT_EQ(truncatedError.InnerErrors().size(), 3u);
+
+ EXPECT_EQ("First inne...<message truncated>", truncatedError.InnerErrors()[0].GetMessage());
+ EXPECT_EQ("Second inn...<message truncated>", truncatedError.InnerErrors()[1].GetMessage());
+ EXPECT_EQ("Fourth inn...<message truncated>", truncatedError.InnerErrors()[2].GetMessage());
+}
+
+TEST(TErrorTest, TruncateSimpleRValue)
+{
+ auto error = TError("Some error")
+ << TErrorAttribute("my_attr", "Attr value")
+ << TError("Inner error");
+ auto errorCopy = error;
+ auto truncatedError = std::move(errorCopy).Truncate();
+ EXPECT_TRUE(errorCopy.IsOK());
+
+ EXPECT_EQ(error.GetCode(), truncatedError.GetCode());
+ EXPECT_EQ(error.GetMessage(), truncatedError.GetMessage());
+ EXPECT_EQ(error.GetPid(), truncatedError.GetPid());
+ EXPECT_EQ(error.GetTid(), truncatedError.GetTid());
+ EXPECT_EQ(error.GetDatetime(), truncatedError.GetDatetime());
+ EXPECT_EQ(error.Attributes().Get<TString>("my_attr"), truncatedError.Attributes().Get<TString>("my_attr"));
+ EXPECT_EQ(error.InnerErrors().size(), truncatedError.InnerErrors().size());
+ EXPECT_EQ(error.InnerErrors()[0].GetMessage(), truncatedError.InnerErrors()[0].GetMessage());
+}
+
+TEST(TErrorTest, TruncateLargeRValue)
+{
+ auto error = TError("Some long long error")
+ << TError("First inner error")
+ << TError("Second inner error")
+ << TError("Third inner error")
+ << TError("Fourth inner error");
+ SetErrorAttribute(&error, "my_attr", "Some long long attr");
+
+ auto errorCopy = error;
+ auto truncatedError = std::move(errorCopy).Truncate(/*maxInnerErrorCount*/ 3, /*stringLimit*/ 10);
+ EXPECT_TRUE(errorCopy.IsOK());
+
+ EXPECT_EQ(error.GetCode(), truncatedError.GetCode());
+ EXPECT_EQ("Some long ...<message truncated>", truncatedError.GetMessage());
+ EXPECT_EQ("...<attribute truncated>...", truncatedError.Attributes().Get<TString>("my_attr"));
+ EXPECT_EQ(truncatedError.InnerErrors().size(), 3u);
+
+ EXPECT_EQ("First inne...<message truncated>", truncatedError.InnerErrors()[0].GetMessage());
+ EXPECT_EQ("Second inn...<message truncated>", truncatedError.InnerErrors()[1].GetMessage());
+ EXPECT_EQ("Fourth inn...<message truncated>", truncatedError.InnerErrors()[2].GetMessage());
+}
+
+TEST(TErrorTest, TruncateConsistentOverloads)
+{
+ auto error = TError("Some long long error")
+ << TError("First inner error")
+ << TError("Second inner error")
+ << TError("Third inner error")
+ << TError("Fourth inner error");
+ SetErrorAttribute(&error, "my_attr", "Some long long attr");
+
+ auto errorCopy = error;
+ auto truncatedRValueError = std::move(errorCopy).Truncate(/*maxInnerErrorCount*/ 3, /*stringLimit*/ 10);
+
+ auto trunactedLValueError = error.Truncate(/*maxInnerErrorCount*/ 3, /*stringLimit*/ 10);
+
+ EXPECT_EQ(truncatedRValueError, trunactedLValueError);
+}
+
+TEST(TErrorTest, TruncateWhitelist)
+{
+ auto error = TError("Some error");
+ SetErrorAttribute(&error, "attr1", "Some long long attr");
+ SetErrorAttribute(&error, "attr2", "Some long long attr");
+
+ THashSet<TStringBuf> myWhitelist = {"attr2"};
+
+ auto truncatedError = error.Truncate(2, 10, myWhitelist);
+
+ EXPECT_EQ(error.GetCode(), truncatedError.GetCode());
+ EXPECT_EQ(error.GetMessage(), truncatedError.GetMessage());
+
+ EXPECT_EQ("...<attribute truncated>...", truncatedError.Attributes().Get<TString>("attr1"));
+ EXPECT_EQ("Some long long attr", truncatedError.Attributes().Get<TString>("attr2"));
+}
+
+TEST(TErrorTest, TruncateWhitelistRValue)
+{
+ auto error = TError("Some error");
+ SetErrorAttribute(&error, "attr1", "Some long long attr");
+ SetErrorAttribute(&error, "attr2", "Some long long attr");
+
+ THashSet<TStringBuf> myWhitelist = {"attr2"};
+
+ auto errorCopy = error;
+ auto truncatedError = std::move(errorCopy).Truncate(2, 10, myWhitelist);
+ EXPECT_TRUE(errorCopy.IsOK());
+
+ EXPECT_EQ(error.GetCode(), truncatedError.GetCode());
+ EXPECT_EQ(error.GetMessage(), truncatedError.GetMessage());
+
+ EXPECT_EQ("...<attribute truncated>...", truncatedError.Attributes().Get<TString>("attr1"));
+ EXPECT_EQ("Some long long attr", truncatedError.Attributes().Get<TString>("attr2"));
+}
+
+TEST(TErrorTest, TruncateWhitelistInnerErrors)
+{
+ auto innerError = TError("Inner error");
+ SetErrorAttribute(&innerError, "attr1", "Some long long attr");
+ SetErrorAttribute(&innerError, "attr2", "Some long long attr");
+
+ auto error = TError("Error") << innerError;
+
+ THashSet<TStringBuf> myWhitelist = {"attr2"};
+
+ auto truncatedError = error.Truncate(2, 15, myWhitelist);
+ EXPECT_EQ(truncatedError.InnerErrors().size(), 1u);
+
+ auto truncatedInnerError = truncatedError.InnerErrors()[0];
+ EXPECT_EQ(truncatedInnerError.GetCode(), innerError.GetCode());
+ EXPECT_EQ(truncatedInnerError.GetMessage(), innerError.GetMessage());
+ EXPECT_EQ("...<attribute truncated>...", truncatedInnerError.Attributes().Get<TString>("attr1"));
+ EXPECT_EQ("Some long long attr", truncatedInnerError.Attributes().Get<TString>("attr2"));
+}
+
+TEST(TErrorTest, TruncateWhitelistInnerErrorsRValue)
+{
+ auto innerError = TError("Inner error");
+ SetErrorAttribute(&innerError, "attr1", "Some long long attr");
+ SetErrorAttribute(&innerError, "attr2", "Some long long attr");
+
+ auto error = TError("Error") << innerError;
+
+ THashSet<TStringBuf> myWhitelist = {"attr2"};
+
+ auto errorCopy = error;
+ auto truncatedError = std::move(errorCopy).Truncate(2, 15, myWhitelist);
+ EXPECT_TRUE(errorCopy.IsOK());
+ EXPECT_EQ(truncatedError.InnerErrors().size(), 1u);
+
+ auto truncatedInnerError = truncatedError.InnerErrors()[0];
+ EXPECT_EQ(truncatedInnerError.GetCode(), innerError.GetCode());
+ EXPECT_EQ(truncatedInnerError.GetMessage(), innerError.GetMessage());
+ EXPECT_EQ("...<attribute truncated>...", truncatedInnerError.Attributes().Get<TString>("attr1"));
+ EXPECT_EQ("Some long long attr", truncatedInnerError.Attributes().Get<TString>("attr2"));
+}
+
+TEST(TErrorTest, TruncateWhitelistSaveInnerError)
+{
+ auto genericInner = TError("GenericInner");
+ auto whitelistedInner = TError("Inner")
+ << TErrorAttribute("whitelisted_key", 42);
+
+ auto error = TError("Error")
+ << (genericInner << TErrorAttribute("foo", "bar"))
+ << whitelistedInner
+ << genericInner;
+
+ error = std::move(error).Truncate(1, 20, {
+ "whitelisted_key"
+ });
+ EXPECT_TRUE(!error.IsOK());
+ EXPECT_EQ(error.InnerErrors().size(), 2u);
+ EXPECT_EQ(error.InnerErrors()[0], whitelistedInner);
+ EXPECT_EQ(error.InnerErrors()[1], genericInner);
+
+ // TODO: error_helpers???
+ EXPECT_TRUE(FindAttributeRecursive<int>(error, "whitelisted_key"));
+ EXPECT_FALSE(FindAttributeRecursive<int>(error, "foo"));
+}
+
+TEST(TErrorTest, YTExceptionToError)
+{
+ try {
+ throw TSimpleException("message");
+ } catch (const std::exception& ex) {
+ TError error(ex);
+ EXPECT_EQ(NYT::EErrorCode::Generic, error.GetCode());
+ EXPECT_EQ("message", error.GetMessage());
+ }
+}
+
+TEST(TErrorTest, CompositeYTExceptionToError)
+{
+ try {
+ try {
+ throw TSimpleException("inner message");
+ } catch (const std::exception& ex) {
+ throw TSimpleException(ex, "outer message");
+ }
+ } catch (const std::exception& ex) {
+ TError outerError(ex);
+ EXPECT_EQ(NYT::EErrorCode::Generic, outerError.GetCode());
+ EXPECT_EQ("outer message", outerError.GetMessage());
+ EXPECT_EQ(1, std::ssize(outerError.InnerErrors()));
+ const auto& innerError = outerError.InnerErrors()[0];
+ EXPECT_EQ(NYT::EErrorCode::Generic, innerError.GetCode());
+ EXPECT_EQ("inner message", innerError.GetMessage());
+ }
+}
+
+TEST(TErrorTest, YTExceptionWithAttributesToError)
+{
+ try {
+ throw TSimpleException("message")
+ << TExceptionAttribute{"Int64 value", static_cast<i64>(42)}
+ << TExceptionAttribute{"double value", 7.77}
+ << TExceptionAttribute{"bool value", false}
+ << TExceptionAttribute{"String value", "FooBar"};
+ } catch (const std::exception& ex) {
+ TError error(ex);
+ EXPECT_EQ(NYT::EErrorCode::Generic, error.GetCode());
+ EXPECT_EQ("message", error.GetMessage());
+
+ auto i64value = error.Attributes().Find<i64>("Int64 value");
+ EXPECT_TRUE(i64value);
+ EXPECT_EQ(*i64value, static_cast<i64>(42));
+
+ auto doubleValue = error.Attributes().Find<double>("double value");
+ EXPECT_TRUE(doubleValue);
+ EXPECT_EQ(*doubleValue, 7.77);
+
+ auto boolValue = error.Attributes().Find<bool>("bool value");
+ EXPECT_TRUE(boolValue);
+ EXPECT_EQ(*boolValue, false);
+
+ auto stringValue = error.Attributes().Find<TString>("String value");
+ EXPECT_TRUE(stringValue);
+ EXPECT_EQ(*stringValue, "FooBar");
+ }
+}
+
+TEST(TErrorTest, AttributeSerialization)
+{
+ auto getWeededText = [] (const TError& err) {
+ std::vector<TString> lines;
+ for (const auto& line : StringSplitter(ToString(err)).Split('\n')) {
+ if (!line.Contains("origin") && !line.Contains("datetime")) {
+ lines.push_back(TString{line});
+ }
+ }
+ return JoinSeq("\n", lines);
+ };
+
+ EXPECT_EQ(getWeededText(TError("E1") << TErrorAttribute("A1", "V1")), TString(
+ "E1\n"
+ " A1 V1\n"));
+ EXPECT_EQ(getWeededText(TError("E1") << TErrorAttribute("A1", "L1\nL2\nL3")), TString(
+ "E1\n"
+ " A1\n"
+ " L1\n"
+ " L2\n"
+ " L3\n"));
+}
+
+TEST(TErrorTest, MacroStaticAnalysis)
+{
+ auto swallow = [] (auto expr) {
+ try {
+ expr();
+ } catch (...) {
+ }
+ };
+
+ swallow([] {
+ THROW_ERROR_EXCEPTION("Foo");
+ });
+ swallow([] {
+ THROW_ERROR_EXCEPTION("Hello, %v", "World");
+ });
+ swallow([] {
+ THROW_ERROR_EXCEPTION(NYT::EErrorCode::Generic, "Foo");
+ });
+ swallow([] {
+ THROW_ERROR_EXCEPTION(NYT::EErrorCode::Generic, "Foo%v", "Bar");
+ });
+ swallow([] {
+ THROW_ERROR_EXCEPTION(NYT::EErrorCode::Generic, "Foo%v%v", "Bar", "Baz");
+ });
+ swallow([] {
+ THROW_ERROR_EXCEPTION_IF_FAILED(TError{}, "Foo");
+ });
+ swallow([] {
+ THROW_ERROR_EXCEPTION_IF_FAILED(TError{}, "Foo%v", "Bar");
+ });
+ swallow([] {
+ THROW_ERROR_EXCEPTION_IF_FAILED(TError{}, "Foo%v%v", "Bar", "Baz");
+ });
+ swallow([] {
+ THROW_ERROR_EXCEPTION_IF_FAILED(TError{}, NYT::EErrorCode::Generic, "Foo%v", "Bar");
+ });
+ swallow([] {
+ THROW_ERROR_EXCEPTION_IF_FAILED(TError{}, NYT::EErrorCode::Generic, "Foo%v%v", "Bar", "Baz");
+ });
+}
+
+TEST(TErrorTest, WrapStaticAnalysis)
+{
+ TError error;
+ Y_UNUSED(error.Wrap());
+ Y_UNUSED(error.Wrap(std::exception{}));
+ Y_UNUSED(error.Wrap("Hello"));
+ Y_UNUSED(error.Wrap("Hello, %v", "World"));
+ Y_UNUSED(error.Wrap(TRuntimeFormat{"Hello, %v"}));
+}
+
+// NB(arkady-e1ppa): Uncomment these occasionally to see
+// that static analysis is still working.
+TEST(TErrorTest, MacroStaticAnalysisBrokenFormat)
+{
+ // auto swallow = [] (auto expr) {
+ // try {
+ // expr();
+ // } catch (...) {
+ // }
+ // };
+
+ // swallow([] {
+ // THROW_ERROR_EXCEPTION("Hello, %v");
+ // });
+ // swallow([] {
+ // THROW_ERROR_EXCEPTION(TErrorCode{}, "Foo%v");
+ // });
+ // swallow([] {
+ // THROW_ERROR_EXCEPTION_IF_FAILED(TError{}, "Foo%v");
+ // });
+ // swallow([] {
+ // THROW_ERROR_EXCEPTION_IF_FAILED(TError{}, TErrorCode{}, "Foo%v");
+ // });
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace
+} // namespace NYT
diff --git a/library/cpp/yt/error/unittests/ya.make b/library/cpp/yt/error/unittests/ya.make
new file mode 100644
index 0000000000..9082a25535
--- /dev/null
+++ b/library/cpp/yt/error/unittests/ya.make
@@ -0,0 +1,18 @@
+GTEST()
+
+INCLUDE(${ARCADIA_ROOT}/library/cpp/yt/ya_cpp.make.inc)
+
+SIZE(MEDIUM)
+
+SRCS(
+ error_ut.cpp
+ error_code_ut.cpp
+)
+
+PEERDIR(
+ library/cpp/yt/error
+
+ library/cpp/testing/gtest
+)
+
+END()
diff --git a/library/cpp/yt/error/ya.make b/library/cpp/yt/error/ya.make
index 60d3e9a92f..ca4370ef9d 100644
--- a/library/cpp/yt/error/ya.make
+++ b/library/cpp/yt/error/ya.make
@@ -10,11 +10,20 @@ PEERDIR(
library/cpp/yt/threading
library/cpp/yt/string
library/cpp/yt/yson_string # TODO(arkady-e1ppa): eliminate
+ library/cpp/yt/logging # TODO(arkady-e1ppa): Consider logging error_code crashes to stderr and drop this dep.
+
+ util
)
SRCS(
+ error.cpp
error_attributes.cpp
+ error_code.cpp
origin_attributes.cpp
)
END()
+
+RECURSE_FOR_TESTS(
+ unittests
+)
diff --git a/library/cpp/yt/string/string.cpp b/library/cpp/yt/string/string.cpp
index c4bd98b66c..3cf5651e23 100644
--- a/library/cpp/yt/string/string.cpp
+++ b/library/cpp/yt/string/string.cpp
@@ -359,4 +359,19 @@ TStringBuf FormatBool(bool value)
////////////////////////////////////////////////////////////////////////////////
+void TruncateStringInplace(TString* string, int lengthLimit, TStringBuf truncatedSuffix)
+{
+ if (std::ssize(*string) > lengthLimit) {
+ *string = Format("%v%v", string->substr(0, lengthLimit), truncatedSuffix);
+ }
+}
+
+TString TruncateString(TString string, int lengthLimit, TStringBuf truncatedSuffix)
+{
+ TruncateStringInplace(&string, lengthLimit, truncatedSuffix);
+ return string;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
} // namespace NYT
diff --git a/library/cpp/yt/string/string.h b/library/cpp/yt/string/string.h
index 9794cfd69f..ac5aa5d4e4 100644
--- a/library/cpp/yt/string/string.h
+++ b/library/cpp/yt/string/string.h
@@ -181,6 +181,14 @@ TStringBuf FormatBool(bool value);
////////////////////////////////////////////////////////////////////////////////
+inline constexpr TStringBuf DefaultTruncatedMessage = "...<truncated>";
+
+void TruncateStringInplace(TString* string, int lengthLimit, TStringBuf truncatedSuffix = DefaultTruncatedMessage);
+
+TString TruncateString(TString string, int lengthLimit, TStringBuf truncatedSuffix = DefaultTruncatedMessage);
+
+////////////////////////////////////////////////////////////////////////////////
+
} // namespace NYT
#define STRING_INL_H_
diff --git a/library/cpp/yt/system/proc.h b/library/cpp/yt/system/proc.h
new file mode 100644
index 0000000000..b1b65608e3
--- /dev/null
+++ b/library/cpp/yt/system/proc.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <library/cpp/yt/misc/enum.h>
+
+#include <errno.h>
+
+namespace NYT {
+
+////////////////////////////////////////////////////////////////////////////////
+
+//! NYT::TError::FromSystem adds this value to a system errno. The enum
+//! below lists several errno's that are used in our code.
+constexpr int LinuxErrorCodeBase = 4200;
+constexpr int LinuxErrorCodeCount = 2000;
+
+DEFINE_ENUM(ELinuxErrorCode,
+ ((NOENT) ((LinuxErrorCodeBase + ENOENT)))
+ ((IO) ((LinuxErrorCodeBase + EIO)))
+ ((ACCESS) ((LinuxErrorCodeBase + EACCES)))
+ ((NFILE) ((LinuxErrorCodeBase + ENFILE)))
+ ((MFILE) ((LinuxErrorCodeBase + EMFILE)))
+ ((NOSPC) ((LinuxErrorCodeBase + ENOSPC)))
+ ((PIPE) ((LinuxErrorCodeBase + EPIPE)))
+ ((CONNRESET) ((LinuxErrorCodeBase + ECONNRESET)))
+ ((TIMEDOUT) ((LinuxErrorCodeBase + ETIMEDOUT)))
+ ((CONNREFUSED) ((LinuxErrorCodeBase + ECONNREFUSED)))
+ ((DQUOT) ((LinuxErrorCodeBase + EDQUOT)))
+);
+
+////////////////////////////////////////////////////////////////////////////////
+
+} // namespace NYT