diff options
author | arkady-e1ppa <arkady-e1ppa@yandex-team.com> | 2024-12-25 07:12:23 +0300 |
---|---|---|
committer | arkady-e1ppa <arkady-e1ppa@yandex-team.com> | 2024-12-25 07:31:55 +0300 |
commit | b5dd91799751f9924acb7c17ddad16ddb2086bba (patch) | |
tree | f430b4e9f752af7411d5503bfcf823e9718056c2 /library/cpp | |
parent | 75f1af270a6cf9a17b65fde6d12efbb94f235960 (diff) | |
download | ydb-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')
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 |