aboutsummaryrefslogblamecommitdiffstats
path: root/library/cpp/yt/error/error.h
blob: 8b459d0beb8c8d26bc41226f9c7c58688043116a (plain) (tree)
1
2
3
4
5
6
7
8
            
                                            

                                            
                                                      
 
















































                                                                                

                       














                                                                         
                                                      




                                                       
                                                                   























                                                                 
                                            









                                         
                                               




                                                                
                                  































































                                                                                                
                                    





                                                                            
                                                                   


















                                                                  
                                  



















































                                                                                    
                                    






















































                                                                                                                       
                                                                                    




























                                                                                     
                                                      




































































                                                                                                                     
                      
                            
#pragma once

#include <library/cpp/yt/error/error_code.h>

#include <library/cpp/yt/threading/public.h>

#include <library/cpp/yt/error/mergeable_dictionary.h>

#include <library/cpp/yt/yson_string/convert.h>
#include <library/cpp/yt/yson_string/string.h>

#include <library/cpp/yt/misc/property.h>

#include <util/system/compiler.h>
#include <util/system/getpid.h>

#include <util/generic/size_literals.h>

#include <type_traits>

namespace NYT {

////////////////////////////////////////////////////////////////////////////////

//! An opaque wrapper around |int| value capable of conversions from |int|s and
//! arbitrary enum types.
class TErrorCode
{
public:
    using TUnderlying = int;

    constexpr TErrorCode();
    explicit constexpr TErrorCode(int value);
    template <class E>
        requires std::is_enum_v<E>
    constexpr TErrorCode(E value);

    constexpr operator int() const;

    template <class E>
        requires std::is_enum_v<E>
    constexpr operator E() const;

    template <class E>
        requires std::is_enum_v<E>
    constexpr bool operator == (E rhs) const;

    constexpr bool operator == (TErrorCode rhs) const;
private:
    int Value_;
};

////////////////////////////////////////////////////////////////////////////////

void FormatValue(TStringBuilderBase* builder, TErrorCode code, TStringBuf spec);

////////////////////////////////////////////////////////////////////////////////

// Forward declaration.
class TErrorException;

template <class TValue>
concept CErrorNestable = requires (TError& error, TValue&& operand)
{
    { error <<= std::forward<TValue>(operand) } -> std::same_as<TError&>;
};

template <>
class [[nodiscard]] TErrorOr<void>
{
public:
    TErrorOr();
    ~TErrorOr();

    TErrorOr(const TError& other);
    TErrorOr(TError&& other) noexcept;

    TErrorOr(const TErrorException& errorEx) noexcept;

    TErrorOr(const std::exception& ex);

    struct TDisableFormat
    { };
    static constexpr TDisableFormat DisableFormat = {};

    TErrorOr(std::string message, TDisableFormat);
    TErrorOr(TErrorCode code, std::string message, TDisableFormat);

    template <class... TArgs>
    explicit TErrorOr(
        TFormatString<TArgs...> format,
        TArgs&&... arg);

    template <class... TArgs>
    TErrorOr(
        TErrorCode code,
        TFormatString<TArgs...> format,
        TArgs&&... args);

    TError& operator = (const TError& other);
    TError& operator = (TError&& other) noexcept;

    static TError FromSystem();
    static TError FromSystem(int error);
    static TError FromSystem(const TSystemError& error);

    TErrorCode GetCode() const;
    TError& SetCode(TErrorCode code);

    TErrorCode GetNonTrivialCode() const;
    THashSet<TErrorCode> GetDistinctNonTrivialErrorCodes() const;

    const std::string& GetMessage() const;
    TError& SetMessage(std::string message);

    bool HasOriginAttributes() const;
    TProcessId GetPid() const;
    TStringBuf GetThreadName() const;
    NThreading::TThreadId GetTid() const;

    bool HasDatetime() const;
    TInstant GetDatetime() const;

    bool HasAttributes() const noexcept;

    const TErrorAttributes& Attributes() const;
    TErrorAttributes* MutableAttributes();

    const std::vector<TError>& InnerErrors() const;
    std::vector<TError>* MutableInnerErrors();

    // Used for deserialization only.
    TOriginAttributes* MutableOriginAttributes() const noexcept;
    void UpdateOriginAttributes();

    TError Truncate(
        int maxInnerErrorCount = 2,
        i64 stringLimit = 16_KB,
        const THashSet<TStringBuf>& attributeWhitelist = {}) const &;
    TError Truncate(
        int maxInnerErrorCount = 2,
        i64 stringLimit = 16_KB,
        const THashSet<TStringBuf>& attributeWhitelist = {}) &&;

    bool IsOK() const;

    template <class U>
        requires (!CStringLiteral<std::remove_cvref_t<U>>)
    void ThrowOnError(U&& u) const &;
    template <class... TArgs>
    void ThrowOnError(TFormatString<TArgs...> format, TArgs&&... args) const &;
    template <class... TArgs>
    void ThrowOnError(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) const &;
    inline void ThrowOnError() const &;

    template <class U>
        requires (!CStringLiteral<std::remove_cvref_t<U>>)
    void ThrowOnError(U&& u) &&;
    template <class... TArgs>
    void ThrowOnError(TFormatString<TArgs...> format, TArgs&&... args) &&;
    template <class... TArgs>
    void ThrowOnError(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) &&;
    inline void ThrowOnError() &&;

    template <CInvocable<bool(const TError&)> TFilter>
    std::optional<TError> FindMatching(const TFilter& filter) const;
    template <CInvocable<bool(TErrorCode)> TFilter>
    std::optional<TError> FindMatching(const TFilter& filter) const;
    std::optional<TError> FindMatching(TErrorCode code) const;
    std::optional<TError> FindMatching(const THashSet<TErrorCode>& codes) const;

    template <class U>
        requires (!CStringLiteral<std::remove_cvref_t<U>>)
    TError Wrap(U&& u) const &;
    template <class... TArgs>
    TError Wrap(TFormatString<TArgs...> format, TArgs&&... args) const &;
    template <class... TArgs>
    TError Wrap(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) const &;
    TError Wrap() const &;

    template <class U>
        requires (!CStringLiteral<std::remove_cvref_t<U>>)
    TError Wrap(U&& u) &&;
    template <class... TArgs>
    TError Wrap(TFormatString<TArgs...> format, TArgs&&... args) &&;
    template <class... TArgs>
    TError Wrap(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) &&;
    TError Wrap() &&;

    //! Perform recursive aggregation of error codes and messages over the error tree.
    //! Result of this aggregation is suitable for error clustering in groups of
    //! "similar" errors. Refer to yt/yt/library/error_skeleton/skeleton_ut.cpp for examples.
    //!
    //! This method builds skeleton from scratch by doing complete error tree traversal,
    //! so calling it in computationally hot code is discouraged.
    //!
    //! In order to prevent core -> re2 dependency, implementation belongs to a separate library
    //! yt/yt/library/error_skeleton. Calling this method without PEERDIR'ing implementation
    //! results in an exception.
    std::string GetSkeleton() const;

    TError& operator <<= (const TErrorAttribute& attribute) &;
    TError& operator <<= (const std::vector<TErrorAttribute>& attributes) &;
    TError& operator <<= (const TError& innerError) &;
    TError& operator <<= (TError&& innerError) &;
    TError& operator <<= (const std::vector<TError>& innerErrors) &;
    TError& operator <<= (std::vector<TError>&& innerErrors) &;
    TError& operator <<= (TAnyMergeableDictionaryRef attributes) &;

    template <CErrorNestable TValue>
    TError&& operator << (TValue&& operand) &&;

    template <CErrorNestable TValue>
    TError operator << (TValue&& operand) const &;

    template <CErrorNestable TValue>
    TError&& operator << (const std::optional<TValue>& rhs) &&;

    template <CErrorNestable TValue>
    TError operator << (const std::optional<TValue>& rhs) const &;

private:
    class TImpl;
    std::unique_ptr<TImpl> Impl_;

    explicit TErrorOr(std::unique_ptr<TImpl> impl);

    void MakeMutable();

    friend class TErrorAttributes;
};

////////////////////////////////////////////////////////////////////////////////

bool operator == (const TError& lhs, const TError& rhs);

////////////////////////////////////////////////////////////////////////////////

void FormatValue(TStringBuilderBase* builder, const TError& error, TStringBuf spec);

////////////////////////////////////////////////////////////////////////////////

using TErrorVisitor = std::function<void(const TError&, int depth)>;

//! Traverses the error tree in DFS order.
void TraverseError(
    const TError& error,
    const TErrorVisitor& visitor,
    int depth = 0);

////////////////////////////////////////////////////////////////////////////////

template <class T>
struct TErrorTraits
{
    using TWrapped = TErrorOr<T>;
    using TUnwrapped = T;
};

template <class T>
struct TErrorTraits<TErrorOr<T>>
{
    using TUnderlying = T;
    using TWrapped = TErrorOr<T>;
    using TUnwrapped = T;
};

////////////////////////////////////////////////////////////////////////////////

class TErrorException
    : public std::exception
{
public:
    DEFINE_BYREF_RW_PROPERTY(TError, Error);

public:
    TErrorException() = default;
    TErrorException(const TErrorException& other) = default;
    TErrorException(TErrorException&& other) = default;

    const char* what() const noexcept override;

private:
    mutable std::string CachedWhat_;
};

// Make these templates to avoid type erasure during throw.
template <class TException>
    requires std::derived_from<std::remove_cvref_t<TException>, TErrorException>
TException&& operator <<= (TException&& ex, const TError& error);
template <class TException>
    requires std::derived_from<std::remove_cvref_t<TException>, TErrorException>
TException&& operator <<= (TException&& ex, TError&& error);

////////////////////////////////////////////////////////////////////////////////

namespace NDetail {

struct TErrorAdaptor
{
    template <class TArg>
        requires std::constructible_from<TError, TArg>
    TError operator << (TArg&& rhs) const;

    template <class TArg>
        requires
            std::constructible_from<TError, TArg> &&
            std::derived_from<std::remove_cvref_t<TArg>, TError>
    TArg&& operator << (TArg&& rhs) const;
};

// Make these to correctly forward TError to Wrap call.
template <class TErrorLike, class U>
    requires
        std::derived_from<std::remove_cvref_t<TErrorLike>, TError> &&
        (!CStringLiteral<std::remove_cvref_t<U>>)
void ThrowErrorExceptionIfFailed(TErrorLike&& error, U&& u);

template <class TErrorLike, class... TArgs>
    requires std::derived_from<std::remove_cvref_t<TErrorLike>, TError>
void ThrowErrorExceptionIfFailed(TErrorLike&& error, TFormatString<TArgs...> format, TArgs&&... args);

template <class TErrorLike, class... TArgs>
    requires std::derived_from<std::remove_cvref_t<TErrorLike>, TError>
void ThrowErrorExceptionIfFailed(TErrorLike&& error, TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args);

template <class TErrorLike>
    requires std::derived_from<std::remove_cvref_t<TErrorLike>, TError>
void ThrowErrorExceptionIfFailed(TErrorLike&& error);

} // namespace NDetail

////////////////////////////////////////////////////////////////////////////////

#define THROW_ERROR \
    throw ::NYT::TErrorException() <<= ::NYT::NDetail::TErrorAdaptor() <<

#define THROW_ERROR_EXCEPTION(head, ...) \
    THROW_ERROR ::NYT::TError(head __VA_OPT__(,) __VA_ARGS__)

// NB: When given an error and a string as arguments, this macro automatically wraps
// new error around the initial one.
#define THROW_ERROR_EXCEPTION_IF_FAILED(error, ...) \
    ::NYT::NDetail::ThrowErrorExceptionIfFailed((error) __VA_OPT__(,) __VA_ARGS__); \

#define THROW_ERROR_EXCEPTION_UNLESS(condition, head, ...) \
    if ((condition)) {\
    } else { \
        THROW_ERROR ::NYT::TError(head __VA_OPT__(,) __VA_ARGS__); \
    }

#define THROW_ERROR_EXCEPTION_IF(condition, head, ...) \
    THROW_ERROR_EXCEPTION_UNLESS(!(condition), head, __VA_ARGS__)

////////////////////////////////////////////////////////////////////////////////

template <class T>
class [[nodiscard]] TErrorOr
    : public TError
{
public:
    TErrorOr();

    TErrorOr(const T& value);
    TErrorOr(T&& value) noexcept;

    TErrorOr(const TErrorOr<T>& other);
    TErrorOr(TErrorOr<T>&& other) noexcept;

    TErrorOr(const TError& other);
    TErrorOr(TError&& other) noexcept;

    TErrorOr(const TErrorException& errorEx) noexcept;

    TErrorOr(const std::exception& ex);

    template <class U>
    TErrorOr(const TErrorOr<U>& other);
    template <class U>
    TErrorOr(TErrorOr<U>&& other) noexcept;

    TErrorOr<T>& operator = (const TErrorOr<T>& other)
        requires std::is_copy_assignable_v<T>;
    TErrorOr<T>& operator = (TErrorOr<T>&& other) noexcept
        requires std::is_nothrow_move_assignable_v<T>;

    const T& Value() const & Y_LIFETIME_BOUND;
    T& Value() & Y_LIFETIME_BOUND;
    T&& Value() && Y_LIFETIME_BOUND;

    template <class U>
        requires (!CStringLiteral<std::remove_cvref_t<U>>)
    const T& ValueOrThrow(U&& u) const & Y_LIFETIME_BOUND;
    template <class... TArgs>
    const T& ValueOrThrow(TFormatString<TArgs...> format, TArgs&&... args) const & Y_LIFETIME_BOUND;
    template <class... TArgs>
    const T& ValueOrThrow(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) const & Y_LIFETIME_BOUND;
    const T& ValueOrThrow() const & Y_LIFETIME_BOUND;

    template <class U>
        requires (!CStringLiteral<std::remove_cvref_t<U>>)
    T& ValueOrThrow(U&& u) & Y_LIFETIME_BOUND;
    template <class... TArgs>
    T& ValueOrThrow(TFormatString<TArgs...> format, TArgs&&... args) & Y_LIFETIME_BOUND;
    template <class... TArgs>
    T& ValueOrThrow(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) & Y_LIFETIME_BOUND;
    T& ValueOrThrow() & Y_LIFETIME_BOUND;

    template <class U>
        requires (!CStringLiteral<std::remove_cvref_t<U>>)
    T&& ValueOrThrow(U&& u) && Y_LIFETIME_BOUND;
    template <class... TArgs>
    T&& ValueOrThrow(TFormatString<TArgs...> format, TArgs&&... args) && Y_LIFETIME_BOUND;
    template <class... TArgs>
    T&& ValueOrThrow(TErrorCode code, TFormatString<TArgs...> format, TArgs&&... args) && Y_LIFETIME_BOUND;
    T&& ValueOrThrow() && Y_LIFETIME_BOUND;

    const T& ValueOrDefault(const T& defaultValue Y_LIFETIME_BOUND) const & Y_LIFETIME_BOUND;
    T& ValueOrDefault(T& defaultValue Y_LIFETIME_BOUND) & Y_LIFETIME_BOUND;
    constexpr T ValueOrDefault(T&& defaultValue) const &;
    constexpr T ValueOrDefault(T&& defaultValue) &&;

private:
    std::optional<T> Value_;
};

////////////////////////////////////////////////////////////////////////////////

template <class T>
void FormatValue(TStringBuilderBase* builder, const TErrorOr<T>& error, TStringBuf spec);

////////////////////////////////////////////////////////////////////////////////

template <class F, class... As>
auto RunNoExcept(F&& functor, As&&... args) noexcept -> decltype(functor(std::forward<As>(args)...))
{
    return functor(std::forward<As>(args)...);
}

////////////////////////////////////////////////////////////////////////////////

} // namespace NYT

#define STRIPPED_ERROR_INL_H_
#include "error-inl.h"
#undef STRIPPED_ERROR_INL_H_