Skip to content

Commit ec0821d

Browse files
feat(core): Add ErrorCode template to standardize conversion of user-defined error code enums to std::error_code. (#486)
1 parent 9439d30 commit ec0821d

File tree

3 files changed

+293
-0
lines changed

3 files changed

+293
-0
lines changed

components/core/CMakeLists.txt

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

0 commit comments

Comments
 (0)