Skip to content

Commit 2ed729f

Browse files
feat(error_handling): Port error_handling library and related unit tests from CLP core. (#28)
Co-authored-by: Lin Zhihao <[email protected]>
1 parent d53b826 commit 2ed729f

File tree

11 files changed

+335
-33
lines changed

11 files changed

+335
-33
lines changed

src/ystdlib/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
add_subdirectory(testlib)
1+
add_subdirectory(error_handling)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
cpp_library(
2+
NAME error_handling
3+
NAMESPACE ystdlib
4+
TESTS_SOURCES
5+
test/constants.hpp
6+
test/test_ErrorCode.cpp
7+
test/types.cpp
8+
test/types.hpp
9+
)
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#ifndef YSTDLIB_ERROR_HANDLING_ERRORCODE_HPP
2+
#define YSTDLIB_ERROR_HANDLING_ERRORCODE_HPP
3+
4+
#include <concepts>
5+
#include <string>
6+
#include <system_error>
7+
#include <type_traits>
8+
9+
namespace ystdlib::error_handling {
10+
/**
11+
* Concept that defines a template parameter of an integer-based error code enumeration.
12+
* @tparam Type
13+
*/
14+
template <typename Type>
15+
concept ErrorCodeEnumType = std::is_enum_v<Type> && requires(Type type) {
16+
{
17+
static_cast<std::underlying_type_t<Type>>(type)
18+
} -> std::convertible_to<int>;
19+
};
20+
21+
/**
22+
* Template that defines a `std::error_category` of the given set of error code enumeration.
23+
* @tparam ErrorCodeEnum
24+
*/
25+
template <ErrorCodeEnumType ErrorCodeEnum>
26+
class ErrorCategory : public std::error_category {
27+
public:
28+
// Methods implementing `std::error_category`
29+
/**
30+
* Gets the error category name.
31+
* Note: A specialization must be explicitly implemented for each valid `ErrorCodeEnum`.
32+
* @return The name of the error category.
33+
*/
34+
[[nodiscard]] auto name() const noexcept -> char const* override;
35+
36+
/**
37+
* Gets the descriptive message associated with the given error.
38+
* @param error_num
39+
* @return The descriptive message for the error.
40+
*/
41+
[[nodiscard]] auto message(int error_num) const -> std::string override {
42+
return message(static_cast<ErrorCodeEnum>(error_num));
43+
}
44+
45+
/**
46+
* @param error_num
47+
* @param condition
48+
* @return Whether the error condition of the given error matches the given condition.
49+
*/
50+
[[nodiscard]] auto
51+
equivalent(int error_num, std::error_condition const& condition) const noexcept
52+
-> bool override {
53+
return equivalent(static_cast<ErrorCodeEnum>(error_num), condition);
54+
}
55+
56+
// Methods
57+
/**
58+
* Gets the descriptive message associated with the given error.
59+
* Note: A specialization must be explicitly implemented for each valid `ErrorCodeEnum`.
60+
* @param error_enum.
61+
* @return The descriptive message for the error.
62+
*/
63+
[[nodiscard]] auto message(ErrorCodeEnum error_enum) const -> std::string;
64+
65+
/**
66+
* Note: A specialization can be implemented to create error enum to error condition mappings.
67+
* @param error_num
68+
* @param condition
69+
* @return Whether the error condition of the given error matches the given condition.
70+
*/
71+
[[nodiscard]] auto
72+
equivalent(ErrorCodeEnum error_enum, std::error_condition const& condition) const noexcept
73+
-> bool;
74+
};
75+
76+
/**
77+
* Template class that defines an error code. An error code is represented by a error enum value and
78+
* the associated error category. This template class is designed to be `std::error_code`
79+
* compatible, meaning that every instance of this class can be used to construct a corresponded
80+
* `std::error_code` instance, or compare with a `std::error_code` instance to inspect a specific
81+
* error.
82+
* @tparam ErrorCodeEnum
83+
*/
84+
template <ErrorCodeEnumType ErrorCodeEnum>
85+
class ErrorCode {
86+
public:
87+
/**
88+
* @return The reference to the singleton of the corresponded error category.
89+
*/
90+
[[nodiscard]] constexpr static auto get_category() -> ErrorCategory<ErrorCodeEnum> const& {
91+
return cCategory;
92+
}
93+
94+
// Constructor
95+
ErrorCode(ErrorCodeEnum error) : m_error{error} {}
96+
97+
/**
98+
* @return The underlying error code enum.
99+
*/
100+
[[nodiscard]] auto get_error() const -> ErrorCodeEnum { return m_error; }
101+
102+
/**
103+
* @return The error code as an error number.
104+
*/
105+
[[nodiscard]] auto get_error_num() const -> int { return static_cast<int>(m_error); }
106+
107+
private:
108+
static inline ErrorCategory<ErrorCodeEnum> const cCategory;
109+
110+
ErrorCodeEnum m_error;
111+
};
112+
113+
/**
114+
* @tparam ErrorCodeEnum
115+
* @param error
116+
* @return Constructed `std::error_code` from the given `ErrorCode` instance.
117+
*/
118+
template <typename ErrorCodeEnum>
119+
[[nodiscard]] auto make_error_code(ErrorCode<ErrorCodeEnum> error) -> std::error_code;
120+
121+
template <ErrorCodeEnumType ErrorCodeEnum>
122+
auto ErrorCategory<ErrorCodeEnum>::equivalent(
123+
ErrorCodeEnum error_enum,
124+
std::error_condition const& condition
125+
) const noexcept -> bool {
126+
return std::error_category::default_error_condition(static_cast<int>(error_enum)) == condition;
127+
}
128+
129+
template <typename ErrorCodeEnum>
130+
auto make_error_code(ErrorCode<ErrorCodeEnum> error) -> std::error_code {
131+
return {error.get_error_num(), ErrorCode<ErrorCodeEnum>::get_category()};
132+
}
133+
} // namespace ystdlib::error_handling
134+
135+
/**
136+
* The macro to create a specialization of `std::is_error_code_enum` for a given type T. Only types
137+
* that are marked with this macro will be considered as a valid YStdlib error code enum, and thus
138+
* used to specialize `ErrorCode` and `ErrorCategory` templates.
139+
*/
140+
// NOLINTBEGIN(bugprone-macro-parentheses, cppcoreguidelines-macro-usage)
141+
#define YSTDLIB_ERROR_HANDLING_MARK_AS_ERROR_CODE_ENUM(T) \
142+
template <> \
143+
struct std::is_error_code_enum<ystdlib::error_handling::ErrorCode<T>> : std::true_type { \
144+
static_assert(std::is_enum_v<T>); \
145+
};
146+
// NOLINTEND(bugprone-macro-parentheses, cppcoreguidelines-macro-usage)
147+
148+
#endif // YSTDLIB_ERROR_HANDLING_ERRORCODE_HPP
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#ifndef YSTDLIB_ERROR_HANDLING_TEST_CONSTANTS_HPP
2+
#define YSTDLIB_ERROR_HANDLING_TEST_CONSTANTS_HPP
3+
4+
#include <array>
5+
#include <string_view>
6+
#include <system_error>
7+
8+
namespace ystdlib::error_handling::test {
9+
constexpr std::string_view cAlwaysSuccessErrorCategoryName{"Always Success Error Code"};
10+
constexpr std::string_view cBinaryTestErrorCategoryName{"Binary Error Code"};
11+
constexpr std::string_view cSuccessErrorMsg{"Success"};
12+
constexpr std::string_view cFailureErrorMsg{"Failure"};
13+
constexpr std::string_view cUnrecognizedErrorCode{"Unrecognized Error Code"};
14+
constexpr std::array cFailureConditions{std::errc::not_connected, std::errc::timed_out};
15+
constexpr std::array cNoneFailureConditions{std::errc::broken_pipe, std::errc::address_in_use};
16+
} // namespace ystdlib::error_handling::test
17+
18+
#endif // YSTDLIB_ERROR_HANDLING_TEST_CONSTANTS_HPP
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#include <algorithm>
2+
#include <string_view>
3+
#include <system_error>
4+
5+
#include <ystdlib/error_handling/ErrorCode.hpp>
6+
7+
#include <catch2/catch_test_macros.hpp>
8+
9+
#include "constants.hpp"
10+
#include "types.hpp"
11+
12+
namespace ystdlib::error_handling::test {
13+
TEST_CASE("test_error_code", "[error_handling][ErrorCode]") {
14+
// Test error codes within the same error category
15+
BinaryErrorCode const success{BinaryErrorCodeEnum::Success};
16+
std::error_code const success_error_code{success};
17+
REQUIRE((success == success_error_code));
18+
REQUIRE((cSuccessErrorMsg == success_error_code.message()));
19+
REQUIRE((BinaryErrorCode::get_category() == success_error_code.category()));
20+
REQUIRE((cBinaryTestErrorCategoryName == success_error_code.category().name()));
21+
22+
BinaryErrorCode const failure{BinaryErrorCodeEnum::Failure};
23+
std::error_code const failure_error_code{failure};
24+
REQUIRE((failure == failure_error_code));
25+
REQUIRE((cFailureErrorMsg == failure_error_code.message()));
26+
REQUIRE((BinaryErrorCode::get_category() == failure_error_code.category()));
27+
REQUIRE((cBinaryTestErrorCategoryName == failure_error_code.category().name()));
28+
29+
REQUIRE((success_error_code != failure_error_code));
30+
REQUIRE((success_error_code.category() == failure_error_code.category()));
31+
32+
AlwaysSuccessErrorCode const always_success{AlwaysSuccessErrorCodeEnum::Success};
33+
std::error_code const always_success_error_code{always_success};
34+
REQUIRE((always_success_error_code == always_success));
35+
REQUIRE((cSuccessErrorMsg == always_success_error_code.message()));
36+
REQUIRE((AlwaysSuccessErrorCode::get_category() == always_success_error_code.category()));
37+
REQUIRE((cAlwaysSuccessErrorCategoryName == always_success_error_code.category().name()));
38+
39+
// Compare error codes from different error category
40+
// Error codes that have the same value or message won't be the same with each other if they are
41+
// from different error categories.
42+
REQUIRE((success_error_code.value() == always_success_error_code.value()));
43+
REQUIRE((success_error_code.message() == always_success_error_code.message()));
44+
REQUIRE((success_error_code.category() != always_success_error_code.category()));
45+
REQUIRE((success_error_code != always_success_error_code));
46+
REQUIRE((AlwaysSuccessErrorCode{AlwaysSuccessErrorCodeEnum::Success} != success_error_code));
47+
REQUIRE((BinaryErrorCode{BinaryErrorCodeEnum::Success} != always_success_error_code));
48+
}
49+
50+
TEST_CASE("test_error_code_failure_condition", "[error_handling][ErrorCode]") {
51+
std::error_code const failure_error_code{BinaryErrorCode{BinaryErrorCodeEnum::Failure}};
52+
std::ranges::for_each(cFailureConditions, [&](auto const& failure_condition) {
53+
REQUIRE((failure_error_code == failure_condition));
54+
});
55+
std::ranges::for_each(cNoneFailureConditions, [&](auto const& none_failure_condition) {
56+
REQUIRE((failure_error_code != none_failure_condition));
57+
});
58+
}
59+
} // namespace ystdlib::error_handling::test
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#include "types.hpp"
2+
3+
#include <algorithm>
4+
#include <string>
5+
#include <string_view>
6+
#include <system_error>
7+
8+
#include "constants.hpp"
9+
10+
using ystdlib::error_handling::test::AlwaysSuccessErrorCategory;
11+
using ystdlib::error_handling::test::AlwaysSuccessErrorCodeEnum;
12+
using ystdlib::error_handling::test::BinaryErrorCategory;
13+
using ystdlib::error_handling::test::BinaryErrorCodeEnum;
14+
using ystdlib::error_handling::test::cAlwaysSuccessErrorCategoryName;
15+
using ystdlib::error_handling::test::cBinaryTestErrorCategoryName;
16+
using ystdlib::error_handling::test::cFailureConditions;
17+
using ystdlib::error_handling::test::cFailureErrorMsg;
18+
using ystdlib::error_handling::test::cSuccessErrorMsg;
19+
using ystdlib::error_handling::test::cUnrecognizedErrorCode;
20+
21+
template <>
22+
auto AlwaysSuccessErrorCategory::name() const noexcept -> char const* {
23+
return cAlwaysSuccessErrorCategoryName.data();
24+
}
25+
26+
template <>
27+
auto AlwaysSuccessErrorCategory::message(AlwaysSuccessErrorCodeEnum error_enum) const
28+
-> std::string {
29+
switch (error_enum) {
30+
case AlwaysSuccessErrorCodeEnum::Success:
31+
return std::string{cSuccessErrorMsg};
32+
default:
33+
return std::string{cUnrecognizedErrorCode};
34+
}
35+
}
36+
37+
template <>
38+
auto BinaryErrorCategory::name() const noexcept -> char const* {
39+
return cBinaryTestErrorCategoryName.data();
40+
}
41+
42+
template <>
43+
auto BinaryErrorCategory::message(BinaryErrorCodeEnum error_enum) const -> std::string {
44+
switch (error_enum) {
45+
case BinaryErrorCodeEnum::Success:
46+
return std::string{cSuccessErrorMsg};
47+
case BinaryErrorCodeEnum::Failure:
48+
return std::string{cFailureErrorMsg};
49+
default:
50+
return std::string{cUnrecognizedErrorCode};
51+
}
52+
}
53+
54+
template <>
55+
auto BinaryErrorCategory::equivalent(
56+
BinaryErrorCodeEnum error_enum,
57+
std::error_condition const& condition
58+
) const noexcept -> bool {
59+
switch (error_enum) {
60+
case BinaryErrorCodeEnum::Failure:
61+
return std::ranges::any_of(
62+
cFailureConditions.cbegin(),
63+
cFailureConditions.cend(),
64+
[&](auto failure_condition) -> bool { return condition == failure_condition; }
65+
);
66+
default:
67+
return false;
68+
}
69+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#ifndef YSTDLIB_ERROR_HANDLING_TEST_TYPES_HPP
2+
#define YSTDLIB_ERROR_HANDLING_TEST_TYPES_HPP
3+
4+
#include <cstdint>
5+
6+
#include <ystdlib/error_handling/ErrorCode.hpp>
7+
8+
namespace ystdlib::error_handling::test {
9+
enum class AlwaysSuccessErrorCodeEnum : uint8_t {
10+
Success = 0
11+
};
12+
13+
enum class BinaryErrorCodeEnum : uint8_t {
14+
Success = 0,
15+
Failure
16+
};
17+
18+
using AlwaysSuccessErrorCode = ystdlib::error_handling::ErrorCode<AlwaysSuccessErrorCodeEnum>;
19+
using AlwaysSuccessErrorCategory
20+
= ystdlib::error_handling::ErrorCategory<AlwaysSuccessErrorCodeEnum>;
21+
using BinaryErrorCode = ystdlib::error_handling::ErrorCode<BinaryErrorCodeEnum>;
22+
using BinaryErrorCategory = ystdlib::error_handling::ErrorCategory<BinaryErrorCodeEnum>;
23+
} // namespace ystdlib::error_handling::test
24+
25+
YSTDLIB_ERROR_HANDLING_MARK_AS_ERROR_CODE_ENUM(
26+
ystdlib::error_handling::test::AlwaysSuccessErrorCodeEnum
27+
);
28+
YSTDLIB_ERROR_HANDLING_MARK_AS_ERROR_CODE_ENUM(ystdlib::error_handling::test::BinaryErrorCodeEnum);
29+
30+
#endif // YSTDLIB_ERROR_HANDLING_TEST_TYPES_HPP

src/ystdlib/testlib/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
This file was deleted.

src/ystdlib/testlib/hello.hpp

Lines changed: 0 additions & 12 deletions
This file was deleted.

src/ystdlib/testlib/test-hello.cpp

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)