diff options
author | arkady-e1ppa <[email protected]> | 2024-12-25 07:12:23 +0300 |
---|---|---|
committer | arkady-e1ppa <[email protected]> | 2024-12-25 07:31:55 +0300 |
commit | b5dd91799751f9924acb7c17ddad16ddb2086bba (patch) | |
tree | f430b4e9f752af7411d5503bfcf823e9718056c2 | |
parent | 75f1af270a6cf9a17b65fde6d12efbb94f235960 (diff) |
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
44 files changed, 1403 insertions, 1344 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 dcfc8b0677d..00000000000 --- 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/yt/yt/core/misc/stripped_error-inl.h b/library/cpp/yt/error/error-inl.h index 7fbc8acd7e2..f9a7355d1a9 100644 --- a/yt/yt/core/misc/stripped_error-inl.h +++ b/library/cpp/yt/error/error-inl.h @@ -1,7 +1,7 @@ #ifndef STRIPPED_ERROR_INL_H_ -#error "Direct inclusion of this file is not allowed, include stripped_error.h" +#error "Direct inclusion of this file is not allowed, include error.h" // For the sake of sane code completion. -#include "stripped_error.h" +#include "error.h" #endif #include <library/cpp/yt/error/error_attributes.h> @@ -67,6 +67,8 @@ inline TString FormatErrorMessage(TStringBuf 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) diff --git a/yt/yt/core/misc/stripped_error.cpp b/library/cpp/yt/error/error.cpp index 5be93260f93..bcc265ab2ed 100644 --- a/yt/yt/core/misc/stripped_error.cpp +++ b/library/cpp/yt/error/error.cpp @@ -1,27 +1,14 @@ -#include "stripped_error.h" - -// TODO(arkady-e1ppa): core/misc deps can easily be moved -// to relevant library/cpp/yt sections. -#include <yt/yt/core/misc/string_helpers.h> -#include <yt/yt/core/misc/proc.h> - -// TODO(arkady-e1ppa): core/ytree deps removal requires making -// a separate class that has some features of -// IAttributeDictionary. Namely, it needs to -// be created, have TYsonString added to it -// merged with other dictionary and deep copied. -// NB: We don't need printability since TError from -// this file is not printable. -#include <yt/yt/core/ytree/attributes.h> -#include <yt/yt/core/ytree/exception_helpers.h> - -#include <yt/yt/core/misc/origin_attributes.h> +#include "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> @@ -31,7 +18,6 @@ namespace NYT { -using namespace NYTree; using namespace NYson; //////////////////////////////////////////////////////////////////////////////// @@ -58,7 +44,7 @@ public: : Code_(other.Code_) , Message_(other.Message_) , OriginAttributes_(other.OriginAttributes_) - , AttributesImpl_(other.AttributesImpl_ ? other.AttributesImpl_->Clone() : nullptr) + , Attributes_(other.Attributes_) , InnerErrors_(other.InnerErrors_) { } @@ -150,7 +136,7 @@ public: bool HasAttributes() const noexcept { - return AttributesImpl_.operator bool(); + return true; } const TErrorAttributes& Attributes() const @@ -160,15 +146,11 @@ public: void UpdateOriginAttributes() { - OriginAttributes_ = NYT::NDetail::ExtractFromDictionary(AttributesImpl_); + OriginAttributes_ = NYT::NDetail::ExtractFromDictionary(&Attributes_); } TErrorAttributes* MutableAttributes() noexcept { - if (!HasAttributes()) { - AttributesImpl_ = CreateEphemeralAttributes(); - Attributes_ = TErrorAttributes(AttributesImpl_.Get()); - } return &Attributes_; } @@ -191,77 +173,13 @@ private: .Tid = NThreading::InvalidThreadId, }; - NYTree::IAttributeDictionaryPtr AttributesImpl_; - TErrorAttributes Attributes_ = TErrorAttributes(AttributesImpl_.Get()); + TErrorAttributes Attributes_; std::vector<TError> InnerErrors_; - - friend class TErrorAttributes; }; //////////////////////////////////////////////////////////////////////////////// -// NB(arkady-e1ppa): Currently TErrorAttributes is a facade -// which has minimal API of original dict with backend being the -// actual original dict. Once API-related issues are fixed we are -// free to implement a backend which doesn't depend on original dict. -std::vector<TErrorAttributes::TKey> TErrorAttributes::ListKeys() const -{ - auto* attributes = static_cast<IAttributeDictionary*>(Attributes_); - if (!attributes) { - return {}; - } - return attributes->ListKeys(); -} - -std::vector<TErrorAttributes::TKeyValuePair> TErrorAttributes::ListPairs() const -{ - auto* attributes = static_cast<IAttributeDictionary*>(Attributes_); - if (!attributes) { - return {}; - } - return attributes->ListPairs(); -} - -TErrorAttributes::TValue TErrorAttributes::FindYson(TStringBuf key) const -{ - auto* attributes = static_cast<IAttributeDictionary*>(Attributes_); - if (!attributes) { - return {}; - } - return attributes->FindYson(key); -} - -void TErrorAttributes::SetYson(const TKey& key, const TValue& value) -{ - auto* attributes = static_cast<IAttributeDictionary*>(Attributes_); - YT_VERIFY(attributes); - return attributes->SetYson(key, value); -} - -bool TErrorAttributes::Remove(const TKey& key) -{ - auto* attributes = static_cast<IAttributeDictionary*>(Attributes_); - if (!attributes) { - return false; - } - return attributes->Remove(key); -} - -TErrorAttributes::TValue TErrorAttributes::GetYson(TStringBuf key) const -{ - auto result = FindYson(key); - if (!result) { - // This method comes from "exception_helpers.h" - // and requires NYTree::EErrorCode::ResolveError enum value. - // TODO(arkady-e1ppa): eliminate this dependency somehow. - ThrowNoSuchAttribute(key); - } - return result; -} - -//////////////////////////////////////////////////////////////////////////////// - namespace { bool IsWhitelisted(const TError& error, const THashSet<TStringBuf>& attributeWhitelist) @@ -517,7 +435,7 @@ bool TError::HasAttributes() const noexcept const TErrorAttributes& TError::Attributes() const { if (!Impl_) { - static TErrorAttributes empty{nullptr}; + static TErrorAttributes empty = {}; return empty; } return Impl_->Attributes(); @@ -577,14 +495,14 @@ TError TError::Truncate( auto truncateAttributes = [stringLimit, &attributeWhitelist] (const TErrorAttributes& attributes, TErrorAttributes* mutableAttributes) { for (const auto& key : attributes.ListKeys()) { - const auto& value = attributes.FindYson(key); + const auto& value = attributes.FindValue(key); if (std::ssize(value.AsStringBuf()) > stringLimit && !attributeWhitelist.contains(key)) { - mutableAttributes->SetYson( + mutableAttributes->SetValue( key, - NYson::ConvertToYsonString("...<attribute truncated>...")); + NYT::ToErrorAttributeValue("...<attribute truncated>...")); } else { - mutableAttributes->SetYson( + mutableAttributes->SetValue( key, value); } @@ -608,7 +526,7 @@ TError TError::Truncate( copiedInnerErrors.push_back(truncateInnerError(innerError)); } } else { - result->MutableAttributes()->SetYson(InnerErrorsTruncatedKey, ConvertToYsonString(true)); + 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. @@ -642,10 +560,10 @@ TError TError::Truncate( auto truncateAttributes = [stringLimit, &attributeWhitelist] (TErrorAttributes* attributes) { for (const auto& key : attributes->ListKeys()) { - if (std::ssize(attributes->FindYson(key).AsStringBuf()) > stringLimit && !attributeWhitelist.contains(key)) { - attributes->SetYson( + if (std::ssize(attributes->FindValue(key).AsStringBuf()) > stringLimit && !attributeWhitelist.contains(key)) { + attributes->SetValue( key, - NYson::ConvertToYsonString("...<attribute truncated>...")); + NYT::ToErrorAttributeValue("...<attribute truncated>...")); } } }; @@ -660,7 +578,7 @@ TError TError::Truncate( } } else { auto& innerErrors = ApplyWhitelist(*MutableInnerErrors(), attributeWhitelist, maxInnerErrorCount); - MutableAttributes()->SetYson(InnerErrorsTruncatedKey, ConvertToYsonString(true)); + MutableAttributes()->SetValue(InnerErrorsTruncatedKey, NYT::ToErrorAttributeValue(true)); for (auto& innerError : innerErrors) { truncateInnerError(innerError); @@ -728,14 +646,14 @@ void TError::MakeMutable() TError& TError::operator <<= (const TErrorAttribute& attribute) & { - MutableAttributes()->SetYson(attribute.Key, attribute.Value); + MutableAttributes()->SetAttribute(attribute); return *this; } TError& TError::operator <<= (const std::vector<TErrorAttribute>& attributes) & { for (const auto& attribute : attributes) { - MutableAttributes()->SetYson(attribute.Key, attribute.Value); + MutableAttributes()->SetAttribute(attribute); } return *this; } @@ -811,6 +729,18 @@ void AppendAttribute(TStringBuilderBase* builder, const TString& key, const TStr 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; @@ -821,21 +751,23 @@ void AppendError(TStringBuilderBase* builder, const TError& error, int indent) builder->AppendChar('\n'); if (error.GetCode() != NYT::EErrorCode::Generic) { - AppendAttribute(builder, "code", ToString(static_cast<int>(error.GetCode())), indent); + 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(*error.MutableOriginAttributes()), + NYT::NDetail::FormatOrigin(*originAttributes), indent); - } else if (IsErrorSanitizerEnabled() && HasHost(error)) { + } else if (IsErrorSanitizerEnabled() && originAttributes->Host.operator bool()) { AppendAttribute( builder, "host", - ToString(GetHost(error)), + TString{originAttributes->Host}, indent); } @@ -848,41 +780,12 @@ void AppendError(TStringBuilderBase* builder, const TError& error, int indent) } for (const auto& [key, value] : error.Attributes().ListPairs()) { - TTokenizer tokenizer(value.AsStringBuf()); - // TODO(arkady-e1ppa): Remove this once failed verifies have been dealt with. - [[unlikely]] if (!tokenizer.ParseNext()) { - Cerr << - NYT::Format( - "%v *** Empty token encountered while formatting TError attribute (Key: %v, Value: %v)" - "(BuilderAccumulatedData: %v)", - TInstant::Now(), - key, - value.AsStringBuf(), - builder->GetBuffer()) - << '\n'; - Flush(Cerr); - YT_ABORT(); - } - // YT_VERIFY(tokenizer.ParseNext()); - switch (tokenizer.GetCurrentType()) { - case ETokenType::String: - AppendAttribute(builder, key, TString(tokenizer.CurrentToken().GetStringValue()), indent); - break; - case ETokenType::Int64: - AppendAttribute(builder, key, ToString(tokenizer.CurrentToken().GetInt64Value()), indent); - break; - case ETokenType::Uint64: - AppendAttribute(builder, key, ToString(tokenizer.CurrentToken().GetUint64Value()), indent); - break; - case ETokenType::Double: - AppendAttribute(builder, key, ToString(tokenizer.CurrentToken().GetDoubleValue()), indent); - break; - case ETokenType::Boolean: - AppendAttribute(builder, key, TString(FormatBool(tokenizer.CurrentToken().GetBooleanValue())), indent); - break; - default: - AppendAttribute(builder, key, ConvertToYsonString(value, EYsonFormat::Text).ToString(), indent); - break; + 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); } } @@ -921,15 +824,4 @@ const char* TErrorException::what() const noexcept //////////////////////////////////////////////////////////////////////////////// -// TODO(arkady-e1ppa): Move this out eventually. -[[noreturn]] void TErrorAttributes::ThrowCannotParseAttributeException(TStringBuf key, const std::exception& ex) -{ - THROW_ERROR_EXCEPTION( - "Error parsing attribute %Qv", - key) - << ex; -} - -//////////////////////////////////////////////////////////////////////////////// - } // namespace NYT diff --git a/yt/yt/core/misc/stripped_error.h b/library/cpp/yt/error/error.h index ffec3b0db75..6412ad029b8 100644 --- a/yt/yt/core/misc/stripped_error.h +++ b/library/cpp/yt/error/error.h @@ -1,8 +1,6 @@ #pragma once -#include "public.h" - -#include <yt/yt/core/ytree/public.h> +#include <library/cpp/yt/error/error_code.h> #include <library/cpp/yt/threading/public.h> @@ -440,5 +438,5 @@ auto RunNoExcept(F&& functor, As&&... args) noexcept -> decltype(functor(std::fo } // namespace NYT #define STRIPPED_ERROR_INL_H_ -#include "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 00000000000..df7696b85be --- /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 dec4a4dd9b3..8b7a79004a0 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 6574665865b..0a77d9afb0c 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 09aa48eebb9..06db3b211e6 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 80dd80de484..da71af63cc4 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/yt/yt/core/misc/error_code.cpp b/library/cpp/yt/error/error_code.cpp index 666db885f05..9cb0d57dc2c 100644 --- a/yt/yt/core/misc/error_code.cpp +++ b/library/cpp/yt/error/error_code.cpp @@ -1,6 +1,6 @@ #include "error_code.h" -#include <yt/yt/core/logging/log.h> +#include <library/cpp/yt/logging/logger.h> #include <library/cpp/yt/misc/global.h> diff --git a/library/cpp/yt/error/error_code.h b/library/cpp/yt/error/error_code.h new file mode 100644 index 00000000000..1c2c08fbb4a --- /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/yt/yt/core/misc/error_helpers-inl.h b/library/cpp/yt/error/error_helpers-inl.h index d15c5f6f93e..d15c5f6f93e 100644 --- a/yt/yt/core/misc/error_helpers-inl.h +++ b/library/cpp/yt/error/error_helpers-inl.h diff --git a/yt/yt/core/misc/error_helpers.h b/library/cpp/yt/error/error_helpers.h index 5eda19e4bf7..b68385be357 100644 --- a/yt/yt/core/misc/error_helpers.h +++ b/library/cpp/yt/error/error_helpers.h @@ -1,8 +1,8 @@ #pragma once -#include "error.h" +#include <library/cpp/yt/error/error.h> -#include <yt/yt/core/ytree/attributes.h> +#include <library/cpp/yt/error/error_attributes.h> #include <library/cpp/yt/misc/optional.h> diff --git a/library/cpp/yt/error/origin_attributes.cpp b/library/cpp/yt/error/origin_attributes.cpp index 5ff0b039339..6f86e31ae8f 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 d98782469b8..e25eb37ea7d 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 04201128aa3..77ce70ee07f 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/yt/yt/core/misc/unittests/error_code_ut.cpp b/library/cpp/yt/error/unittests/error_code_ut.cpp index 0fddbbee79d..4bdb17f5d9c 100644 --- a/yt/yt/core/misc/unittests/error_code_ut.cpp +++ b/library/cpp/yt/error/unittests/error_code_ut.cpp @@ -1,7 +1,7 @@ #include <yt/yt/core/test_framework/framework.h> -#include <yt/yt/core/misc/error.h> -#include <yt/yt/core/misc/error_code.h> +#include <library/cpp/yt/error/error.h> +#include <library/cpp/yt/error/error_code.h> #include <library/cpp/yt/string/format.h> 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 00000000000..a4248b3956a --- /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 00000000000..9082a25535c --- /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 60d3e9a92fb..ca4370ef9da 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 c4bd98b66c0..3cf5651e230 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 9794cfd69f0..ac5aa5d4e4b 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 00000000000..b1b65608e3f --- /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 diff --git a/yt/yt/client/api/operation_client.cpp b/yt/yt/client/api/operation_client.cpp index 07253e5ee52..fccdbbf60ab 100644 --- a/yt/yt/client/api/operation_client.cpp +++ b/yt/yt/client/api/operation_client.cpp @@ -267,15 +267,11 @@ static std::optional<NScheduler::EAbortReason> TryGetJobAbortReasonFromError(con return std::nullopt; } - if (auto yson = error.Attributes().FindYson("abort_reason")) { - try { - return ConvertTo<NScheduler::EAbortReason>(yson); - } catch (const std::exception& exception) { - return std::nullopt; - } + try { + return error.Attributes().Find<NScheduler::EAbortReason>("abort_reason"); + } catch (const std::exception&) { + return std::nullopt; } - - return std::nullopt; } void Serialize(const TJob& job, NYson::IYsonConsumer* consumer, TStringBuf idKey) diff --git a/yt/yt/client/tablet_client/table_mount_cache_detail.cpp b/yt/yt/client/tablet_client/table_mount_cache_detail.cpp index ae1d41c975f..59cf0de4d83 100644 --- a/yt/yt/client/tablet_client/table_mount_cache_detail.cpp +++ b/yt/yt/client/tablet_client/table_mount_cache_detail.cpp @@ -256,8 +256,8 @@ auto TTableMountCacheBase::TryHandleServantNotActiveError(const TError& error) return {}; } - if (auto siblingCellDescriptor = attributes.FindYson("sibling_servant_cell_descriptor")) { - RegisterCell(ConvertToNode(siblingCellDescriptor)); + if (auto siblingCellDescriptor = attributes.Find<INodePtr>("sibling_servant_cell_descriptor")) { + RegisterCell(std::move(siblingCellDescriptor)); } else { return {}; } diff --git a/yt/yt/core/actions/unittests/future_ut.cpp b/yt/yt/core/actions/unittests/future_ut.cpp index 68e3c554a4d..8410a08c858 100644 --- a/yt/yt/core/actions/unittests/future_ut.cpp +++ b/yt/yt/core/actions/unittests/future_ut.cpp @@ -1474,7 +1474,7 @@ TEST_F(TFutureTest, WithDeadlineFail) auto deadline = TInstant::Now() + SleepQuantum; auto f2 = f1.WithDeadline(deadline); EXPECT_EQ(NYT::EErrorCode::Timeout, f2.Get().GetCode()); - EXPECT_EQ(NYson::ConvertToYsonString(deadline), f2.Get().Attributes().FindYson("deadline")); + EXPECT_EQ(deadline, f2.Get().Attributes().Get<TInstant>("deadline")); } TEST_F(TFutureTest, WithTimeoutSuccess) @@ -1503,7 +1503,7 @@ TEST_F(TFutureTest, WithTimeoutFail) auto f1 = p.ToFuture(); auto f2 = f1.WithTimeout(SleepQuantum); EXPECT_EQ(NYT::EErrorCode::Timeout, f2.Get().GetCode()); - EXPECT_EQ(NYson::ConvertToYsonString(SleepQuantum), f2.Get().Attributes().FindYson("timeout")); + EXPECT_EQ(SleepQuantum, f2.Get().Attributes().Get<TDuration>("timeout")); } TEST_W(TFutureTest, Holder) diff --git a/yt/yt/core/misc/error-inl.h b/yt/yt/core/misc/error-inl.h index 3185de11b2e..f215fcd9072 100644 --- a/yt/yt/core/misc/error-inl.h +++ b/yt/yt/core/misc/error-inl.h @@ -4,21 +4,17 @@ #include "error.h" #endif -namespace NYT::NToAttributeValueImpl { +namespace NYT::NAttributeValueConversionImpl { //////////////////////////////////////////////////////////////////////////////// template <class T> -NYson::TYsonString TagInvoke(TTagInvokeTag<ToAttributeValue>, const T& value) + requires (!CPrimitiveConvertible<T>) +NYson::TYsonString TagInvoke(TTagInvokeTag<ToErrorAttributeValue>, const T& value) { - return NYson::ConvertToYsonString(value); -} - -inline NYson::TYsonString TagInvoke(TTagInvokeTag<ToAttributeValue>, const NYson::TYsonString& value) -{ - return value; + return NYson::ConvertToYsonString(value, NYson::EYsonFormat::Text); } //////////////////////////////////////////////////////////////////////////////// -} // namespace NYT::NToAttributeValueImpl +} // namespace NYT::NAttributeValueConversionImpl diff --git a/yt/yt/core/misc/error.cpp b/yt/yt/core/misc/error.cpp index 3aed00942b1..18394636d5e 100644 --- a/yt/yt/core/misc/error.cpp +++ b/yt/yt/core/misc/error.cpp @@ -1,6 +1,5 @@ #include "error.h" #include "serialize.h" -#include "origin_attributes.h" #include <yt/yt/core/concurrency/public.h> @@ -169,7 +168,7 @@ TString FormatOriginOverride(const TOriginAttributes& attributes) GetFid(attributes)); } -TOriginAttributes ExtractFromDictionaryOverride(const NYTree::IAttributeDictionaryPtr& attributes) +TOriginAttributes ExtractFromDictionaryOverride(TErrorAttributes* attributes) { auto result = NYT::NDetail::ExtractFromDictionaryDefault(attributes); @@ -627,7 +626,6 @@ void TErrorSerializer::Load(TStreamLoadContext& context, TError& error) auto code = Load<TErrorCode>(context); auto message = Load<TString>(context); - IAttributeDictionaryPtr attributes; if (Load<bool>(context)) { size_t size = TSizeSerializer::Load(context); for (size_t index = 0; index < size; ++index) { diff --git a/yt/yt/core/misc/error.h b/yt/yt/core/misc/error.h index 8399b49301d..fd47419ee9b 100644 --- a/yt/yt/core/misc/error.h +++ b/yt/yt/core/misc/error.h @@ -1,7 +1,6 @@ #pragma once #include "public.h" -#include "stripped_error.h" #include <yt/yt/core/yson/public.h> @@ -11,6 +10,7 @@ #include <yt/yt/core/concurrency/public.h> +#include <library/cpp/yt/error/error.h> #include <library/cpp/yt/error/origin_attributes.h> namespace NYT { diff --git a/yt/yt/core/misc/error_code.h b/yt/yt/core/misc/error_code.h index 1c2c08fbb4a..bbc1ecb433d 100644 --- a/yt/yt/core/misc/error_code.h +++ b/yt/yt/core/misc/error_code.h @@ -1,101 +1 @@ -#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 +#include <library/cpp/yt/error/error_code.h> diff --git a/yt/yt/core/misc/origin_attributes.cpp b/yt/yt/core/misc/origin_attributes.cpp deleted file mode 100644 index 332d1f58b22..00000000000 --- a/yt/yt/core/misc/origin_attributes.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "origin_attributes.h" - -#include <yt/yt/core/ytree/attributes.h> - -namespace NYT::NDetail { - -//////////////////////////////////////////////////////////////////////////////// - -TOriginAttributes ExtractFromDictionary(const NYTree::IAttributeDictionaryPtr& attributes) -{ - using TFunctor = TOriginAttributes(*)(const NYTree::IAttributeDictionaryPtr&); - - if (auto strong = NGlobal::GetErasedVariable(ExtractFromDictionaryTag)) { - return strong->AsConcrete<TFunctor>()(attributes); - } - - return ExtractFromDictionaryDefault(attributes); -} - -//////////////////////////////////////////////////////////////////////////////// - -TOriginAttributes ExtractFromDictionaryDefault(const NYTree::IAttributeDictionaryPtr& attributes) -{ - TOriginAttributes result; - if (!attributes) { - return result; - } - - static const TString HostKey("host"); - result.HostHolder = TSharedRef::FromString(attributes->GetAndRemove<TString>(HostKey, TString())); - result.Host = result.HostHolder.empty() ? TStringBuf() : TStringBuf(result.HostHolder.Begin(), result.HostHolder.End()); - - static const TString DatetimeKey("datetime"); - result.Datetime = attributes->GetAndRemove<TInstant>(DatetimeKey, TInstant()); - - static const TString PidKey("pid"); - result.Pid = attributes->GetAndRemove<TProcessId>(PidKey, 0); - - static const TString TidKey("tid"); - result.Tid = attributes->GetAndRemove<NThreading::TThreadId>(TidKey, NThreading::InvalidThreadId); - - static const TString ThreadNameKey("thread"); - result.ThreadName = attributes->GetAndRemove<TString>(ThreadNameKey, TString()); - - return result; -} - -//////////////////////////////////////////////////////////////////////////////// - -} // namespace NYT::NDetail diff --git a/yt/yt/core/misc/origin_attributes.h b/yt/yt/core/misc/origin_attributes.h deleted file mode 100644 index 5848431fc86..00000000000 --- a/yt/yt/core/misc/origin_attributes.h +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include <yt/yt/core/ytree/public.h> - -#include <library/cpp/yt/error/origin_attributes.h> - -// TODO(arkady-e1ppa): Move this to library/cpp/yt/error when deps on ytree are gone. - -namespace NYT::NDetail { - -//////////////////////////////////////////////////////////////////////////////// - -// Weak symbol. -TOriginAttributes ExtractFromDictionary(const NYTree::IAttributeDictionaryPtr& attributes); - -//////////////////////////////////////////////////////////////////////////////// - -// Default impl of weak symbol. -TOriginAttributes ExtractFromDictionaryDefault(const NYTree::IAttributeDictionaryPtr& attributes); - -//////////////////////////////////////////////////////////////////////////////// - -} // namespace NYT::NDetail diff --git a/yt/yt/core/misc/proc.h b/yt/yt/core/misc/proc.h index 86d8ad42232..76ef3e18403 100644 --- a/yt/yt/core/misc/proc.h +++ b/yt/yt/core/misc/proc.h @@ -2,37 +2,16 @@ #include "common.h" -#include <yt/yt/core/misc/stripped_error.h> +#include <library/cpp/yt/error/error.h> -#include <util/system/file.h> +#include <library/cpp/yt/system/proc.h> -#include <errno.h> +#include <util/system/file.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))) -); - -//////////////////////////////////////////////////////////////////////////////// - bool IsSystemErrorCode(TErrorCode errorCode); bool IsSystemError(const TError& error); diff --git a/yt/yt/core/misc/process_exit_profiler.h b/yt/yt/core/misc/process_exit_profiler.h index 0306e44ed22..08cc1aa22d1 100644 --- a/yt/yt/core/misc/process_exit_profiler.h +++ b/yt/yt/core/misc/process_exit_profiler.h @@ -1,10 +1,11 @@ #pragma once #include "error.h" -#include "error_helpers.h" #include <yt/yt/library/profiling/sensor.h> +#include <library/cpp/yt/error/error_helpers.h> + #ifdef _unix_ #include <string.h> #endif diff --git a/yt/yt/core/misc/public.h b/yt/yt/core/misc/public.h index 2ce8dd5a2d0..5fb6839f12d 100644 --- a/yt/yt/core/misc/public.h +++ b/yt/yt/core/misc/public.h @@ -1,7 +1,8 @@ #pragma once #include "common.h" -#include "error_code.h" + +#include <library/cpp/yt/error/public.h> // Google Protobuf forward declarations. namespace google::protobuf { @@ -163,15 +164,6 @@ DECLARE_REFCOUNTED_STRUCT(IHedgingManager) //////////////////////////////////////////////////////////////////////////////// -YT_DEFINE_ERROR_ENUM( - ((OK) (0)) - ((Generic) (1)) - ((Canceled) (2)) - ((Timeout) (3)) - ((FutureCombinerFailure) (4)) - ((FutureCombinerShortcut)(5)) -); - DEFINE_ENUM(EProcessErrorCode, ((NonZeroExitCode) (10000)) ((Signal) (10001)) diff --git a/yt/yt/core/misc/string_helpers.cpp b/yt/yt/core/misc/string_helpers.cpp deleted file mode 100644 index 3a75b91d3a1..00000000000 --- a/yt/yt/core/misc/string_helpers.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include "string_helpers.h" - -#include <library/cpp/yt/string/format.h> - -namespace NYT { - -//////////////////////////////////////////////////////////////////////////////// - -const TStringBuf DefaultTruncatedMessage("...<truncated>"); - -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/yt/yt/core/misc/string_helpers.h b/yt/yt/core/misc/string_helpers.h deleted file mode 100644 index c52b03009c2..00000000000 --- a/yt/yt/core/misc/string_helpers.h +++ /dev/null @@ -1,17 +0,0 @@ -#pragma once - -#include "public.h" - -namespace NYT { - -//////////////////////////////////////////////////////////////////////////////// - -extern const TStringBuf DefaultTruncatedMessage; - -void TruncateStringInplace(TString* string, int lengthLimit, TStringBuf truncatedSuffix = DefaultTruncatedMessage); - -TString TruncateString(TString string, int lengthLimit, TStringBuf truncatedSuffix = DefaultTruncatedMessage); - -//////////////////////////////////////////////////////////////////////////////// - -} // namespace NYT diff --git a/yt/yt/core/misc/unittests/error_ut.cpp b/yt/yt/core/misc/unittests/error_ut.cpp index 18c78a9cca3..de4e4fba123 100644 --- a/yt/yt/core/misc/unittests/error_ut.cpp +++ b/yt/yt/core/misc/unittests/error_ut.cpp @@ -4,7 +4,6 @@ #include <yt/yt/core/concurrency/scheduler.h> #include <yt/yt/core/misc/error.h> -#include <yt/yt/core/misc/error_helpers.h> #include <yt/yt/core/net/local_address.h> @@ -27,377 +26,6 @@ using namespace NYTree; //////////////////////////////////////////////////////////////////////////////// -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->MutableAttributes()->SetYson(key, ConvertToYsonString(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, SerializationDepthLimit) { constexpr int Depth = 1000; @@ -479,296 +107,6 @@ TEST(TErrorTest, DoNotDuplicateOriginalErrorDepth) } } -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(GetSpanId(error), GetSpanId(truncatedError)); - 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(GetSpanId(error), GetSpanId(truncatedError)); - 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, 20, 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, 20, 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); - - 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, ErrorSanitizer) { auto checkSantizied = [&] (const TError& error) { @@ -842,29 +180,6 @@ TEST(TErrorTest, SimpleLoadAfterSave) EXPECT_EQ(ToString(savedError), ToString(loadedError)); } -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, TraceContext) { TError error; @@ -913,82 +228,6 @@ TEST(TErrorTest, NativeFiberId) .ThrowOnError(); } -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 diff --git a/yt/yt/core/misc/unittests/ya.make b/yt/yt/core/misc/unittests/ya.make index 79d62c52c28..f8ab343a69d 100644 --- a/yt/yt/core/misc/unittests/ya.make +++ b/yt/yt/core/misc/unittests/ya.make @@ -24,7 +24,6 @@ SRCS( digest_ut.cpp ema_counter_ut.cpp enum_ut.cpp - error_code_ut.cpp error_ut.cpp fair_scheduler_ut.cpp fenwick_tree_ut.cpp diff --git a/yt/yt/core/ya.make b/yt/yt/core/ya.make index 9794fce0a28..04c85cab3a7 100644 --- a/yt/yt/core/ya.make +++ b/yt/yt/core/ya.make @@ -129,7 +129,6 @@ SRCS( misc/crash_handler.cpp misc/digest.cpp misc/error.cpp - misc/error_code.cpp misc/fs.cpp # NB: it is necessary to prevent linker optimization of # REGISTER_INTERMEDIATE_PROTO_INTEROP_REPRESENTATION macros for TGuid. @@ -142,7 +141,6 @@ SRCS( misc/linear_probe.cpp misc/memory_usage_tracker.cpp misc/relaxed_mpsc_queue.cpp - misc/origin_attributes.cpp misc/parser_helpers.cpp misc/pattern_formatter.cpp misc/phoenix.cpp @@ -162,8 +160,6 @@ SRCS( misc/slab_allocator.cpp misc/statistic_path.cpp misc/statistics.cpp - misc/string_helpers.cpp - misc/stripped_error.cpp misc/cache_config.cpp misc/utf8_decoder.cpp misc/zerocopy_output_writer.cpp diff --git a/yt/yt/core/ytree/attributes-inl.h b/yt/yt/core/ytree/attributes-inl.h index cfb18c40cfa..1783c33044f 100644 --- a/yt/yt/core/ytree/attributes-inl.h +++ b/yt/yt/core/ytree/attributes-inl.h @@ -99,7 +99,17 @@ struct TMergeDictionariesTraits<NYTree::IAttributeDictionary> { static auto MakeIterableView(const NYTree::IAttributeDictionary& dict) { - return dict.ListPairs(); + auto pairs = dict.ListPairs(); + + std::vector<TErrorAttributes::TKeyValuePair> ret = {}; + ret.reserve(std::ssize(pairs)); + + for (const auto& [key, value] : pairs) { + ret.emplace_back( + key, + NYT::ToErrorAttributeValue(value)); + } + return ret; } }; diff --git a/yt/yt/core/ytree/convert-inl.h b/yt/yt/core/ytree/convert-inl.h index 0e3385ed5bf..60bf5979411 100644 --- a/yt/yt/core/ytree/convert-inl.h +++ b/yt/yt/core/ytree/convert-inl.h @@ -182,14 +182,6 @@ T ConstructYTreeConvertibleObject() //////////////////////////////////////////////////////////////////////////////// -} // namespace NYT::NYTree - -//////////////////////////////////////////////////////////////////////////////// - -namespace NYT::NConvertToImpl { - -//////////////////////////////////////////////////////////////////////////////// - namespace { double ConvertYsonStringBaseToDouble(const NYson::TYsonStringBuf& yson) @@ -234,15 +226,9 @@ TString ConvertYsonStringBaseToString(const NYson::TYsonStringBuf& yson) //////////////////////////////////////////////////////////////////////////////// -// NB(arkady-e1ppa): TTagInvokeTag uses decltype under the hood -// meaning the resulting expression is not viable for template argument deduction -// thus we have to write the type by hand in order to have TTo deducible -// automatically. template <class TTo> -TTo TagInvoke(NConvertToImpl::TFn<TTo>, const NYTree::INodePtr& node) +TTo ConvertTo(const NYTree::INodePtr& node) { - using namespace NYTree; - auto result = ConstructYTreeConvertibleObject<TTo>(); Deserialize(result, node); return result; @@ -251,10 +237,8 @@ TTo TagInvoke(NConvertToImpl::TFn<TTo>, const NYTree::INodePtr& node) //////////////////////////////////////////////////////////////////////////////// template <class TTo, class TFrom> -TTo TagInvoke(NConvertToImpl::TFn<TTo>, const TFrom& value) +TTo ConvertTo(const TFrom& value) { - using namespace NYTree; - auto type = GetYsonType(value); if constexpr ( NYson::ArePullParserDeserializable<TTo>() && @@ -285,9 +269,8 @@ TTo TagInvoke(NConvertToImpl::TFn<TTo>, const TFrom& value) #define IMPLEMENT_CHECKED_INTEGRAL_CONVERT_TO(type) \ template <> \ - inline type TagInvoke(TTagInvokeTag<ConvertTo<type>>, const NYson::TYsonString& str) \ + inline type ConvertTo(const NYson::TYsonString& str) \ { \ - using namespace NYTree; \ NYson::TTokenizer tokenizer(str.AsStringBuf()); \ const auto& token = SkipAttributes(&tokenizer); \ switch (token.GetType()) { \ @@ -316,29 +299,50 @@ IMPLEMENT_CHECKED_INTEGRAL_CONVERT_TO(ui8) //////////////////////////////////////////////////////////////////////////////// template <> -inline double TagInvoke(TTagInvokeTag<ConvertTo<double>>, const NYson::TYsonString& str) +inline double ConvertTo(const NYson::TYsonString& str) { return ConvertYsonStringBaseToDouble(str); } template <> -inline double TagInvoke(TTagInvokeTag<ConvertTo<double>>, const NYson::TYsonStringBuf& str) +inline double ConvertTo(const NYson::TYsonStringBuf& str) { return ConvertYsonStringBaseToDouble(str); } template <> -inline TString TagInvoke(TTagInvokeTag<ConvertTo<TString>>, const NYson::TYsonString& str) +inline TString ConvertTo(const NYson::TYsonString& str) { return ConvertYsonStringBaseToString(str); } template <> -inline TString TagInvoke(TTagInvokeTag<ConvertTo<TString>>, const NYson::TYsonStringBuf& str) +inline TString ConvertTo(const NYson::TYsonStringBuf& str) { return ConvertYsonStringBaseToString(str); } //////////////////////////////////////////////////////////////////////////////// -} // namespace NYT::NConvertToImpl +} // namespace NYT::NYTree + +namespace NYT::NAttributeValueConversionImpl { + +//////////////////////////////////////////////////////////////////////////////// + +template <class T> + requires (!CPrimitiveConvertible<T>) +T TagInvoke(TFrom<T>, const NYson::TYsonString& value) +{ + return NYTree::ConvertTo<T>(value); +} + +//////////////////////////////////////////////////////////////////////////////// + +} // namespace NYT::NAttributeValueConversionImpl + +namespace NYT { + +using NYT::NYTree::ConvertTo; + +} // namespace NYT diff --git a/yt/yt/core/ytree/convert.h b/yt/yt/core/ytree/convert.h index d80e4448f2f..e07b10c0bbd 100644 --- a/yt/yt/core/ytree/convert.h +++ b/yt/yt/core/ytree/convert.h @@ -5,8 +5,6 @@ #include <yt/yt/core/yson/consumer.h> -#include <library/cpp/yt/error/convert_to_cpo.h> - namespace NYT::NYson { //////////////////////////////////////////////////////////////////////////////// @@ -55,7 +53,11 @@ T ConstructYTreeConvertibleObject(); //////////////////////////////////////////////////////////////////////////////// -using NYT::ConvertTo; +template <class TTo> +TTo ConvertTo(const INodePtr& node); + +template <class TTo, class TFrom> +TTo ConvertTo(const TFrom& value); //////////////////////////////////////////////////////////////////////////////// diff --git a/yt/yt/core/ytree/node-inl.h b/yt/yt/core/ytree/node-inl.h index a766ad7f020..49e3c0acd40 100644 --- a/yt/yt/core/ytree/node-inl.h +++ b/yt/yt/core/ytree/node-inl.h @@ -4,12 +4,19 @@ #include "node.h" #endif -#include <library/cpp/yt/error/convert_to_cpo.h> - namespace NYT::NYTree { //////////////////////////////////////////////////////////////////////////////// +// Forward declarations +template <class TTo> +TTo ConvertTo(const INodePtr& node); + +template <class TTo, class TFrom> +TTo ConvertTo(const TFrom& value); + +//////////////////////////////////////////////////////////////////////////////// + template <class T> T INode::GetValue() const { |