Skip to content
Merged
4 changes: 4 additions & 0 deletions src/ystdlib/error_handling/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ cpp_library(
NAMESPACE ystdlib
PUBLIC_HEADERS
ErrorCode.hpp
Result.hpp
PUBLIC_LINK_LIBRARIES
outcome::hl
TESTS_SOURCES
test/constants.hpp
test/test_ErrorCode.cpp
test/test_Result.cpp
test/types.cpp
test/types.hpp
)
65 changes: 65 additions & 0 deletions src/ystdlib/error_handling/Result.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#ifndef YSTDLIB_ERROR_HANDLING_RESULT_HPP
#define YSTDLIB_ERROR_HANDLING_RESULT_HPP

#include <system_error>

#include <outcome/config.hpp>
#include <outcome/std_result.hpp>
#include <outcome/success_failure.hpp>
#include <outcome/try.hpp>

namespace ystdlib::error_handling {
/**
* A convenience alias for Outcome's std_result.
*
* 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 framework,
* making it easier to compose errors and propagate them across different modules and libraries.
*
* @tparam ReturnType The type returned upon success.
* @tparam ErrorType The type returned upon failure.
*/
template <typename ReturnType, typename ErrorType = std::error_code>
using Result = OUTCOME_V2_NAMESPACE::std_result<ReturnType, ErrorType>;

/**
* Default return value for ystdlib::error_handling::Result<void> when function succeeds.
*
* Example:
* auto my_func() -> Result<void> {
* // ...
* return success();
* }
*/
[[nodiscard]] inline auto success() -> OUTCOME_V2_NAMESPACE::success_type<void> {
return OUTCOME_V2_NAMESPACE::success();
}

/**
* Propagates errors like Rust's `?` operator.
*
* Evaluates `expr`, and if it contains an error, returns the error from the calling function.
* Otherwise, extracts and returns the value.
*
* Only supported on AppleClang, Clang, and GCC due to reliance on Outcome's TRY macros.
*/
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define YSTDLIB_ERROR_HANDLING_TRYX(expr) (OUTCOME_TRYX(expr))

/**
* Error propagation macro for expressions that return void on success.
*
* Evaluates `expr`, and if it contains an error, returns the error from the calling function.
* Intended for use with expressions that return `Result<void>`.
*
* Only supported on AppleClang, Clang, and GCC due to reliance on Outcome's TRY macros.
*/
// NOLINTBEGIN(cppcoreguidelines-avoid-do-while, cppcoreguidelines-macro-usage)
#define YSTDLIB_ERROR_HANDLING_TRYV(expr) \
do { \
OUTCOME_TRYV(expr); \
} while (false)
// NOLINTEND(cppcoreguidelines-avoid-do-while, cppcoreguidelines-macro-usage)
} // namespace ystdlib::error_handling

#endif // YSTDLIB_ERROR_HANDLING_RESULT_HPP
92 changes: 92 additions & 0 deletions src/ystdlib/error_handling/test/test_Result.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#include <system_error>
#include <type_traits>

#include <ystdlib/error_handling/Result.hpp>

#include <catch2/catch_test_macros.hpp>

#include "types.hpp"

using ystdlib::error_handling::Result;
using ystdlib::error_handling::success;
using ystdlib::error_handling::test::BinaryErrorCode;
using ystdlib::error_handling::test::BinaryErrorCodeEnum;

namespace {
constexpr int cTestInt{123};
constexpr auto cVoidFunc = [](bool is_error) -> Result<void> {
if (is_error) {
return BinaryErrorCode{BinaryErrorCodeEnum::Failure};
}
return success();
};
constexpr auto cIntFunc = [](bool is_error) -> Result<int> {
if (is_error) {
return std::errc::bad_message;
}
return 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<decltype(result_no_error.value())>);

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<void> {
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<decltype(main_no_error.value())>);

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<void> {
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<decltype(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_int_propagate", "[error_handling][Result]") {
auto main_func = [&](bool is_error) -> Result<int> {
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()));
}
} // namespace ystdlib::error_handling::test