carb/Error.h

File members: carb/Error.h

// Copyright (c) 2023, NVIDIA CORPORATION. All rights reserved.
//
// NVIDIA CORPORATION and its licensors retain all intellectual property
// and proprietary rights in and to this software, related documentation
// and any modifications thereto. Any use, reproduction, disclosure or
// distribution of this software and related documentation without an express
// license agreement from NVIDIA CORPORATION is strictly prohibited.
//
#pragma once

#include "Defines.h"
#include "Interface.h"
#include "Types.h"
#include "Version.h"
#include "cpp/StringView.h"
#include "detail/DeferredLoad.h"
#include "extras/Errors.h"

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

#include <cinttypes>

namespace carb
{

using omni::core::Result;

// bring all the kResult___ 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)

class Error;

struct ErrorApi
{
    CARB_PLUGIN_INTERFACE("carb::ErrorApi", 1, 0);

    static ErrorApi const& instance() noexcept;

    Error const*(CARB_ABI* viewCurrentError)(Result* code);

    Error*(CARB_ABI* takeCurrentError)(Result* code);

    Result(CARB_ABI* internalSetError)(Result code, char const* message, std::size_t message_size);

    Result(CARB_ABI* setErrorTo)(Error* error);

    void(CARB_ABI* setErrorOom)();

    Result(CARB_ABI* errorRelease)(Error* error);

    Error*(CARB_ABI* errorClone)(Error const* source);

    Error*(CARB_ABI* errorCreate)(Result code, const char* message, std::size_t message_size);

    Result(CARB_ABI* getErrorInfo)(Error const* error, Result* code, char const** message, std::size_t* message_size);

    Result(CARB_ABI* getCodeDescription)(
        Result code, char const** name, std::size_t* name_size, char const** message, std::size_t* message_size);

    //
    // Inline helper functions
    //

    Result setError(Result code) const noexcept;

    Result setError(Result code, const std::string& message) const noexcept;

    Result setError(Result code, const omni::string& message) const noexcept;

    Result setError(Result code, cpp::string_view message) const noexcept;

    Result setError(Result code, const char* message) const noexcept;

    Result setError(Result code, const char* message, std::size_t message_size) const noexcept;

    //
    // Static Inline helper functions
    //

    static void clearError() noexcept;

    static Result getError() noexcept;

    static void setFromErrno();

#if CARB_PLATFORM_WINDOWS || defined(DOXYGEN_BUILD)
    static void setFromWinApiErrorCode();
#endif
};

} // namespace carb

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

namespace carb
{
namespace detail
{

CARB_DETAIL_DEFINE_DEFERRED_LOAD(getCarbErrorApiFunc, carbGetErrorApi, (carb::ErrorApi const* (*)(carb::Version*)));

} // namespace detail

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 p;
    }();
    return *papi;
}

inline void ErrorApi::clearError() noexcept
{
    auto r = instance().setErrorTo(nullptr);
    CARB_UNUSED(r);
    CARB_ASSERT(r == omni::core::kResultSuccess);
}

inline Result ErrorApi::getError() noexcept
{
    Result r;
    instance().viewCurrentError(&r);
    return r;
}

inline Result ErrorApi::setError(Result code) const noexcept
{
    return internalSetError(code, nullptr, 0);
}

inline Result ErrorApi::setError(Result code, const std::string& message) const noexcept
{
    return internalSetError(code, message.c_str(), message.length());
}

inline Result ErrorApi::setError(Result code, const omni::string& message) const noexcept
{
    return internalSetError(code, message.c_str(), message.length());
}

inline Result ErrorApi::setError(Result code, cpp::string_view message) const noexcept
{
    return internalSetError(code, message.data(), message.length());
}

inline Result ErrorApi::setError(Result code, const char* message) const noexcept
{
    return internalSetError(code, message, message ? std::strlen(message) : 0);
}

inline Result ErrorApi::setError(Result code, const char* message, std::size_t message_size) const noexcept
{
    CARB_ASSERT(message_size != size_t(-1));
    return internalSetError(code, message, message_size);
}

inline void ErrorApi::setFromErrno()
{
    auto e = errno;
    switch (e)
    {
        case 0:
            instance().setError(kResultSuccess);
            break;
        case ENOSYS:
            instance().setError(kResultNotImplemented);
            break;
        case EACCES:
            instance().setError(kResultAccessDenied);
            break;
        case ENOMEM:
            instance().setError(kResultOutOfMemory);
            break;
        case EINVAL:
            instance().setError(kResultInvalidArgument);
            break;
        case EAGAIN:
#if !CARB_POSIX
        // This is different on Windows but the same for POSIX
        case EWOULDBLOCK:
#endif
            instance().setError(kResultTryAgain);
            break;
        case EINTR:
            instance().setError(kResultInterrupted);
            break;
        case EEXIST:
            instance().setError(kResultAlreadyExists);
            break;
        case EPERM:
            instance().setError(kResultInvalidOperation);
            break;
        case ENOENT:
            instance().setError(kResultNotFound);
            break;
        default:
            instance().setError(kResultFail, extras::convertErrnoToMessage(e));
            break;
    }
    errno = e;
}

#if CARB_PLATFORM_WINDOWS
inline void ErrorApi::setFromWinApiErrorCode()
{
    auto e = GetLastError();
    switch (e)
    {
        case CARBWIN_ERROR_SUCCESS:
            instance().setError(kResultSuccess);
            break;
        case CARBWIN_ERROR_FILE_NOT_FOUND:
        case CARBWIN_ERROR_PATH_NOT_FOUND:
            instance().setError(kResultNotFound);
            break;
        case CARBWIN_ERROR_ACCESS_DENIED:
            instance().setError(kResultAccessDenied);
            break;
        case CARBWIN_ERROR_ALREADY_EXISTS:
        case CARBWIN_ERROR_FILE_EXISTS:
            instance().setError(kResultAlreadyExists);
            break;
        case CARBWIN_ERROR_OUTOFMEMORY:
            instance().setError(kResultOutOfMemory);
            break;
        case CARBWIN_ERROR_NO_MORE_FILES:
        case CARBWIN_ERROR_NO_MORE_ITEMS:
            instance().setError(kResultNoMoreItems);
            break;
        case CARBWIN_ERROR_CALL_NOT_IMPLEMENTED:
            instance().setError(kResultNotImplemented);
            break;
        case CARBWIN_WAIT_TIMEOUT:
        case CARBWIN_ERROR_TIMEOUT:
            instance().setError(kResultTryAgain);
            break;
        default:
            instance().setError(kResultFail, extras::convertWinApiErrorCodeToMessage(e));
            break;
    }
    SetLastError(e);
}
#endif

} // namespace carb