diff --git a/src/ystdlib/error_handling/CMakeLists.txt b/src/ystdlib/error_handling/CMakeLists.txt index b3c41a5e..4d68d824 100644 --- a/src/ystdlib/error_handling/CMakeLists.txt +++ b/src/ystdlib/error_handling/CMakeLists.txt @@ -4,10 +4,14 @@ cpp_library( PUBLIC_HEADERS ErrorCode.hpp TraceableException.hpp + Result.hpp utils.hpp + PUBLIC_LINK_LIBRARIES + outcome::hl TESTS_SOURCES test/constants.hpp test/test_ErrorCode.cpp + test/test_Result.cpp test/test_TraceableException.cpp test/types.cpp test/types.hpp diff --git a/src/ystdlib/error_handling/Result.hpp b/src/ystdlib/error_handling/Result.hpp new file mode 100644 index 00000000..7b0a48f6 --- /dev/null +++ b/src/ystdlib/error_handling/Result.hpp @@ -0,0 +1,66 @@ +#ifndef YSTDLIB_ERROR_HANDLING_RESULT_HPP +#define YSTDLIB_ERROR_HANDLING_RESULT_HPP + +#include + +#include +#include +#include +#include + +namespace ystdlib::error_handling { +/** + * A Rust-style `Result` type for standardized, exception-free error handling. + * + * This alias standardizes error handling across the codebase by defaulting the error type to + * `std::error_code`, which interoperates with the `ystdlib::error_handling::ErrorCode`, making it + * easier to compose errors and propagate them across different modules and libraries. + * + * @tparam ReturnType The type returned on success. + * @tparam ErrorType The type used to represent errors. + */ +template +using Result = OUTCOME_V2_NAMESPACE::std_result; + +/** + * @return A value indicating successful completion of a function that returns a void result (i.e., + * `Result`). + */ +[[nodiscard]] inline auto success() -> OUTCOME_V2_NAMESPACE::success_type { + return OUTCOME_V2_NAMESPACE::success(); +} + +/** + * A function-style macro that emulates Rust’s try (`?`) operator for error propagation. + * + * @param expr An expression that evaluates to a `Result` object. + * + * Behavior: + * - If `expr` represents an error (i.e., `expr.has_error()` returns true), the macro performs an + * early return from the enclosing function with the contained error. + * - Otherwise, it unwraps and yields the successful value as an rvalue reference (`expr.value()`). + * + * NOTE: This macro is only supported on GCC and Clang due to reliance on compiler-specific + * extensions. + */ +#ifdef OUTCOME_TRYX + // NOLINTNEXTLINE(cppcoreguidelines-macro-usage) + #define YSTDLIB_ERROR_HANDLING_TRYX(expr) OUTCOME_TRYX(expr) +#endif + +/** + * A function-style macro for propagating errors from expressions that evaluate to a void result + * (`Result`). + * + * @param expr An expression that evaluates to a `Result` object. + * + * Behavior: + * - If `expr` represents an error (i.e., `expr.has_error()` returns true), the macro performs an + * early return from the enclosing function with the contained error. + * - Otherwise, execution continues normally. + */ +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define YSTDLIB_ERROR_HANDLING_TRYV(expr) OUTCOME_TRYV(expr) +} // namespace ystdlib::error_handling + +#endif // YSTDLIB_ERROR_HANDLING_RESULT_HPP diff --git a/src/ystdlib/error_handling/test/test_Result.cpp b/src/ystdlib/error_handling/test/test_Result.cpp new file mode 100644 index 00000000..176b84be --- /dev/null +++ b/src/ystdlib/error_handling/test/test_Result.cpp @@ -0,0 +1,139 @@ +#include +#include +#include + +#include + +#include + +#include "types.hpp" + +using ystdlib::error_handling::Result; +using ystdlib::error_handling::success; +using ystdlib::error_handling::test::AlwaysSuccessErrorCode; +using ystdlib::error_handling::test::AlwaysSuccessErrorCodeEnum; +using ystdlib::error_handling::test::BinaryErrorCode; +using ystdlib::error_handling::test::BinaryErrorCodeEnum; + +namespace { +constexpr int cTestInt{123}; +constexpr auto cVoidFunc = [](bool is_error) -> Result { + if (is_error) { + return BinaryErrorCode{BinaryErrorCodeEnum::Failure}; + } + return success(); +}; +constexpr auto cIntFunc = [](bool is_error) -> Result { + if (is_error) { + return std::errc::bad_message; + } + return cTestInt; +}; +constexpr auto cUniquePtrFunc = [](bool is_error) -> Result> { + if (is_error) { + return AlwaysSuccessErrorCode{AlwaysSuccessErrorCodeEnum::Success}; + } + return std::make_unique(cTestInt); +}; +} // namespace + +namespace ystdlib::error_handling::test { +TEST_CASE("test_result_void", "[error_handling][Result]") { + auto const result_no_error{cVoidFunc(false)}; + REQUIRE_FALSE(result_no_error.has_error()); + REQUIRE(std::is_void_v); + + auto const result_has_error{cVoidFunc(true)}; + REQUIRE(result_has_error.has_error()); + REQUIRE(BinaryErrorCode{BinaryErrorCodeEnum::Failure} == result_has_error.error()); +} + +TEST_CASE("test_result_void_in_main", "[error_handling][Result]") { + auto main_func = [&](bool is_error) -> Result { + YSTDLIB_ERROR_HANDLING_TRYV(cVoidFunc(is_error)); + return success(); + }; + auto const main_no_error{main_func(false)}; + REQUIRE_FALSE(main_no_error.has_error()); + REQUIRE(std::is_void_v); + + auto const main_has_error{main_func(true)}; + REQUIRE(main_has_error.has_error()); + REQUIRE(BinaryErrorCode{BinaryErrorCodeEnum::Failure} == main_has_error.error()); +} + +TEST_CASE("test_result_int", "[error_handling][Result]") { + auto const result_no_error{cIntFunc(false)}; + REQUIRE_FALSE(result_no_error.has_error()); + REQUIRE(cTestInt == result_no_error.value()); + + auto const result_has_error{cIntFunc(true)}; + REQUIRE(result_has_error.has_error()); + REQUIRE(std::errc::bad_message == result_has_error.error()); +} + +TEST_CASE("test_result_int_in_main", "[error_handling][Result]") { + auto main_func = [&](bool is_error) -> Result { + YSTDLIB_ERROR_HANDLING_TRYV(cIntFunc(is_error)); + return success(); + }; + auto const main_no_error{main_func(false)}; + REQUIRE_FALSE(main_no_error.has_error()); + REQUIRE(std::is_void_v); + + auto const main_has_error{main_func(true)}; + REQUIRE(main_has_error.has_error()); + REQUIRE(std::errc::bad_message == main_has_error.error()); +} + +TEST_CASE("test_result_int_propagate", "[error_handling][Result]") { + auto main_func = [&](bool is_error) -> Result { + return YSTDLIB_ERROR_HANDLING_TRYX(cIntFunc(is_error)); + }; + auto const main_no_error{main_func(false)}; + REQUIRE_FALSE(main_no_error.has_error()); + REQUIRE(cTestInt == main_no_error.value()); + + auto const main_has_error{main_func(true)}; + REQUIRE(main_has_error.has_error()); + REQUIRE(std::errc::bad_message == main_has_error.error()); +} + +TEST_CASE("test_result_unique_ptr", "[error_handling][Result]") { + auto const result_no_error{cUniquePtrFunc(false)}; + REQUIRE_FALSE(result_no_error.has_error()); + REQUIRE(cTestInt == *(result_no_error.value())); + + auto const result_has_error{cUniquePtrFunc(true)}; + REQUIRE(result_has_error.has_error()); + REQUIRE(AlwaysSuccessErrorCode{AlwaysSuccessErrorCodeEnum::Success} == result_has_error.error() + ); +} + +TEST_CASE("test_result_unique_ptr_in_main", "[error_handling][Result]") { + auto main_func = [&](bool is_error) -> Result { + YSTDLIB_ERROR_HANDLING_TRYV(cUniquePtrFunc(is_error)); + return success(); + }; + auto const main_no_error{main_func(false)}; + REQUIRE_FALSE(main_no_error.has_error()); + REQUIRE(std::is_void_v); + + auto const main_has_error{main_func(true)}; + REQUIRE(main_has_error.has_error()); + REQUIRE(AlwaysSuccessErrorCode{AlwaysSuccessErrorCodeEnum::Success} == main_has_error.error()); +} + +TEST_CASE("test_result_unique_ptr_propagate", "[error_handling][Result]") { + auto main_func = [&](bool is_error) -> Result> { + return YSTDLIB_ERROR_HANDLING_TRYX(cUniquePtrFunc(is_error)); + }; + auto const main_no_error{main_func(false)}; + REQUIRE_FALSE(main_no_error.has_error()); + REQUIRE(cTestInt == *(main_no_error.value())); + + auto const main_has_error{main_func(true)}; + REQUIRE(main_has_error.has_error()); + REQUIRE(AlwaysSuccessErrorCode{AlwaysSuccessErrorCodeEnum::Success} == main_has_error.error()); +} +} // namespace ystdlib::error_handling::test