Error.h#

Fully qualified name: carb/Error.h

File members: carb/Error.h

// SPDX-FileCopyrightText: Copyright (c) 2023-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
// SPDX-License-Identifier: LicenseRef-NvidiaProprietary
//
// NVIDIA CORPORATION, its affiliates and licensors retain all intellectual
// property and proprietary rights in and to this material, related
// documentation and any modifications thereto. Any use, reproduction,
// disclosure or distribution of this material and related documentation
// without an express license agreement from NVIDIA CORPORATION or
// its affiliates is strictly prohibited.

#pragma once

#include "Deprecation.h"
#include "Interface.h"
#include "Types.h"
#include "Version.h"
#include "cpp/ZStringView.h"
#include "detail/DeferredLoad.h"
#include "extras/Errors.h"
#include "thread/Util.h"

#include "../omni/core/Result.h"
#include "../omni/Expected.h"
#include "../omni/String.h"

#include <cinttypes>

#define carb_ErrorApi_latest CARB_HEXVERSION(2, 0)
#ifndef carb_ErrorApi
#    define carb_ErrorApi CARB_HEXVERSION(1, 1)
#endif

#ifndef CARB_ERRORAPI_DEPRECATIONS
#    define CARB_ERRORAPI_DEPRECATIONS 1
#endif

#if CARB_ERRORAPI_DEPRECATIONS && CARB_VERSION_ATLEAST(carb_ErrorApi, 1, 1)
#    define CARB_ERRORAPI_DEPRECATED(msg) CARB_DEPRECATED(msg)
#else
#    define CARB_ERRORAPI_DEPRECATED(msg)
#endif

namespace carb
{

#define CARBLOCAL_GEN(symbol, ...)                                                                                     \
                                                                        \
    k##symbol = omni::core::kResult##symbol,

enum class ErrorCode : std::int32_t
{
    OMNI_RESULT_ERROR_LIST(CARBLOCAL_GEN)
};

#undef CARBLOCAL_GEN

using omni::core::Result;

// bring all the kResultXXX values into carb namespace
#define CARB_RESULT_USE_OMNI_RESULT_GEN(symbol_, ...)                                                                  \
                                                                                                                \
    using omni::core::kResult##symbol_;
OMNI_RESULT_CODE_LIST(CARB_RESULT_USE_OMNI_RESULT_GEN)
#undef CARB_RESULT_USE_OMNI_RESULT_GEN

} // namespace carb

#if CARB_REQUIRE_LINKED
CARB_DYNAMICLINK const void* carbGetErrorApi(carb::Version* version);
#else
CARB_DYNAMICLINK const void* carbGetErrorApi(carb::Version* version) CARB_ATTRIBUTE(weak);
#endif

namespace carb::detail
{

CARB_DETAIL_DEFINE_DEFERRED_LOAD(getCarbErrorApiFunc, carbGetErrorApi, (const void* (*)(carb::Version*)));
} // namespace carb::detail

// Include the appropriate version of the interface
#if CARB_VERSION_ATLEAST(carb_ErrorApi, 2, 0)
#    include "detail/Error2.h"
#else
#    include "detail/Error1.h"
#endif

namespace carb
{

struct ErrorApi : public IError
{
    static ErrorApi const& instance() noexcept;

    //
    // Static inline helper functions
    //

#if CARB_VERSION_ATLEAST(carb_ErrorApi, 2, 0)
    static void clearError() noexcept
    {
        instance().setError(ErrorPtr{});
    }

    static ErrorCode getError(cpp::zstring_view* outMessage = nullptr) noexcept
    {
        return static_cast<const IError&>(instance()).currentError(outMessage);
    }
#else
    static void clearError() noexcept
    {
        [[maybe_unused]] auto r = instance().setErrorTo(nullptr);
        CARB_ASSERT(r == omni::core::kResultSuccess);
    }
    static Result getError() noexcept
    {
        Result r;
        (void)instance().viewCurrentError(&r);
        return r;
    }
#endif

    static ErrorCode convertFromErrno(extras::ErrnoType err = errno, std::string* message = nullptr);
    static void setFromErrno();

#if CARB_PLATFORM_WINDOWS || defined(DOXYGEN_BUILD)
    static ErrorCode convertFromWinApiErrorCode(DWORD err = ::GetLastError(), std::string* message = nullptr);
    static void setFromWinApiErrorCode();
#endif
};
static_assert(sizeof(ErrorApi) == sizeof(IError) && alignof(ErrorApi) == alignof(IError), "Broken assumption");

class ScopedError
{
#if CARB_VERSION_ATLEAST(carb_ErrorApi, 2, 0)
    ErrorPtr m_error{};

public:
    ScopedError() : m_error(ErrorApi::instance().viewCurrentError())
    {
    }
    ~ScopedError()
    {
        ErrorApi::instance().setError(std::move(m_error));
    }
    explicit operator bool() const noexcept
    {
        return bool(m_error);
    }

    ErrorCode get() const
    {
        return m_error ? m_error->getCode() : ErrorCode{};
    }

    explicit operator ErrorCode() const
    {
        return get();
    }

    cpp::zstring_view getMessage() const
    {
        return m_error ? m_error->getMessage() : cpp::zstring_view{};
    }

    operator cpp::zstring_view() const
    {
        return getMessage();
    }
#else
    Error* m_error{ nullptr };
    Result m_result{ kResultSuccess };

public:
    ScopedError()
    {
        if (auto err = ErrorApi::instance().viewCurrentError(&m_result))
        {
            m_error = ErrorApi::instance().errorClone(err);
        }
    }
    ~ScopedError()
    {
        ErrorApi::instance().setErrorTo(m_error);
    }

    explicit operator bool() const noexcept
    {
        return m_error != nullptr;
    }

    Result get() const
    {
        return m_result;
    }
    explicit operator Result() const
    {
        return get();
    }

#    if CARB_VERSION_ATLEAST(carb_ErrorApi, 1, 1)
    cpp::string_view getMessage() const
    {
        cpp::string_view out;
        if (m_error)
            ErrorApi::instance().getErrorInfoS(m_error, nullptr, &out);
        return out;
    }
    operator cpp::string_view() const
    {
        return getMessage();
    }
#    endif
#endif
};

inline ErrorApi const& ErrorApi::instance() noexcept
{
    static ErrorApi const* const papi = []() -> ErrorApi const* {
        const Version expected_version = ErrorApi::getInterfaceDesc().version;
        Version found_version = expected_version;

        auto p = detail::getCarbErrorApiFunc()(&found_version);
        CARB_FATAL_UNLESS(p != nullptr,
                          "Failed to load Error API for version this module was compiled against. This module was "
                          "compiled with Error API %" PRIu32 ".%" PRIu32
                          ", but the maximum-supported version of the "
                          "API in the linked %s is %" PRIu32 ".%" PRIu32,
                          expected_version.major, expected_version.minor,
                          CARB_PLATFORM_WINDOWS ? "carb.dll" : "libcarb.so", found_version.major, found_version.minor);
        return static_cast<const ErrorApi*>(p);
    }();
    return *papi;
}

// Common implementations for both v1 and v2
inline ErrorCode ErrorApi::convertFromErrno(extras::ErrnoType err, std::string* message)
{
    if (err == 0)
    {
        if (message)
            message->clear();
        return {};
    }
    if (message)
        *message = extras::convertErrnoToMessage(err);
    switch (err)
    {
        case ENOSYS:
            return ErrorCode::kNotImplemented;
        case EACCES:
            return ErrorCode::kAccessDenied;
        case ENOMEM:
            return ErrorCode::kOutOfMemory;
        case EINVAL:
            return ErrorCode::kInvalidArgument;
        case EAGAIN:
#if !CARB_POSIX
        // This is different on Windows but the same for POSIX
        case EWOULDBLOCK:
#endif
            return ErrorCode::kTryAgain;
        case EINTR:
            return ErrorCode::kInterrupted;
        case EEXIST:
            return ErrorCode::kAlreadyExists;
        case EPERM:
            return ErrorCode::kInvalidOperation;
        case ENOENT:
            return ErrorCode::kNotFound;
        case ENOTDIR:
            return ErrorCode::kNotDirectory;
        default:
            return ErrorCode::kFail;
    }
}
inline void ErrorApi::setFromErrno()
{
    extras::detail::ScopedErrno e; // just maintain errno over this call
    std::string message;
    auto result = convertFromErrno(e.get(), &message);

#if CARB_VERSION_ATLEAST(carb_ErrorApi, 2, 0)
    [[maybe_unused]] auto res = instance().setError(result, message);
    CARB_ASSERT(res);
#else
    [[maybe_unused]] auto res = instance().setError(cpp::to_underlying(result), message);
    CARB_ASSERT(res == kResultSuccess);
#endif
}

#if CARB_PLATFORM_WINDOWS
inline ErrorCode ErrorApi::convertFromWinApiErrorCode(DWORD err, std::string* output)
{
    if (err == CARBWIN_ERROR_SUCCESS)
    {
        if (output)
            output->clear();
        return {};
    }
    if (output)
        *output = std::system_category().message(err);
    switch (err)
    {
        case CARBWIN_ERROR_FILE_NOT_FOUND:
        case CARBWIN_ERROR_PATH_NOT_FOUND:
            return ErrorCode::kNotFound;
        case CARBWIN_ERROR_ACCESS_DENIED:
            return ErrorCode::kAccessDenied;
        case CARBWIN_ERROR_ALREADY_EXISTS:
        case CARBWIN_ERROR_FILE_EXISTS:
            return ErrorCode::kAlreadyExists;
        case CARBWIN_ERROR_OUTOFMEMORY:
            return ErrorCode::kOutOfMemory;
        case CARBWIN_ERROR_NO_MORE_FILES:
        case CARBWIN_ERROR_NO_MORE_ITEMS:
            return ErrorCode::kNoMoreItems;
        case CARBWIN_ERROR_CALL_NOT_IMPLEMENTED:
            return ErrorCode::kNotImplemented;
        case CARBWIN_WAIT_TIMEOUT:
        case CARBWIN_ERROR_TIMEOUT:
            return ErrorCode::kTryAgain;
        case CARBWIN_ERROR_DIRECTORY:
            return ErrorCode::kNotDirectory;
        default:
            return ErrorCode::kFail;
    }
}

inline void ErrorApi::setFromWinApiErrorCode()
{
    extras::detail::ScopedGetLastError gle; // just maintain GetLastError over this call
    std::string message;
    auto result = convertFromWinApiErrorCode(gle.get(), &message);
#    if CARB_VERSION_ATLEAST(carb_ErrorApi, 2, 0)
    [[maybe_unused]] auto res = instance().setError(result, message);
    CARB_ASSERT(res);
#    else
    [[maybe_unused]] auto res = instance().setError(cpp::to_underlying(result), message);
    CARB_ASSERT(res == kResultSuccess);
#    endif
}
#endif

// shims
namespace detail
{

#if CARB_VERSION_ATLEAST(carb_ErrorApi, 2, 0)
inline omni::expected<void, ErrorCode> setError(ErrorCode code, cpp::string_view message = {})
{
    return ErrorApi::instance().setError(code, message);
}
inline ErrorCode getError(cpp::zstring_view* outMessage = nullptr)
{
    return ErrorApi::instance().getError(outMessage);
}
#else
inline omni::expected<void, ErrorCode> setError(ErrorCode code, cpp::string_view message = {})
{
    if (auto res = ErrorApi::instance().setError(cpp::to_underlying(code), message); res != kResultSuccess)
        return omni::unexpected((ErrorCode)res);
    return {};
}
inline ErrorCode getError(cpp::zstring_view* outMessage = nullptr)
{
    Result code;
    auto err = ErrorApi::instance().viewCurrentError(&code);
    if (outMessage)
    {
        if (err)
        {
            const char* p;
            size_t s;
            CARB_IGNORE_DEPRECATION_BEGIN(void) ErrorApi::instance().getErrorInfo(err, nullptr, &p, &s);
            CARB_IGNORE_DEPRECATION_END
            *outMessage = cpp::zstring_view{ p, s };
        }
        else
            *outMessage = {};
    }
    return (ErrorCode)code;
}
#endif

} // namespace detail

} // namespace carb