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
)
39 changes: 39 additions & 0 deletions src/ystdlib/error_handling/Result.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#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 {
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.
*/
[[nodiscard]] inline auto success() -> OUTCOME_V2_NAMESPACE::success_type<void> {
return OUTCOME_V2_NAMESPACE::success();
}

/**
* This macro works the same way as Rust's `?` operator for error propagation.
*/
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define YSTDLIB_TRYX(expr) (OUTCOME_TRYX(expr))

/**
* This macro works the same way as Rust's `?` operator for error propagation, without returning
* any value.
*/
// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
#define YSTDLIB_TRYV(expr) \
do { \
OUTCOME_TRYV(expr); \
} while (false)
} // namespace ystdlib::error_handling

#endif // YSTDLIB_ERROR_HANDLING_RESULT_HPP
90 changes: 90 additions & 0 deletions src/ystdlib/error_handling/test/test_Result.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#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_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_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_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
Loading