Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): Add ErrorCode template to standardize conversion of user-defined error code enums to std::error_code. #486

Merged
merged 13 commits into from
Dec 2, 2024
2 changes: 2 additions & 0 deletions components/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ set(SOURCE_FILES_unitTest
src/clp/DictionaryEntry.hpp
src/clp/DictionaryReader.hpp
src/clp/DictionaryWriter.hpp
src/clp/error_handling/ErrorCode.hpp
src/clp/EncodedVariableInterpreter.cpp
src/clp/EncodedVariableInterpreter.hpp
src/clp/ErrorCode.hpp
Expand Down Expand Up @@ -459,6 +460,7 @@ set(SOURCE_FILES_unitTest
tests/test-BufferedFileReader.cpp
tests/test-EncodedVariableInterpreter.cpp
tests/test-encoding_methods.cpp
tests/test-error_handling.cpp
tests/test-ffi_SchemaTree.cpp
tests/test-Grep.cpp
tests/test-ir_encoding_methods.cpp
Expand Down
122 changes: 122 additions & 0 deletions components/core/src/clp/error_handling/ErrorCode.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
#ifndef CLP_ERROR_HANDLING_ERRORCODE_HPP
#define CLP_ERROR_HANDLING_ERRORCODE_HPP

#include <concepts>
#include <string>
#include <system_error>
#include <type_traits>

namespace clp::error_handling {
/**
* Concept that defines a template parameter of an integer-based error code enumeration.
* @tparam Type
*/
template <typename Type>
concept ErrorCodeEnumType = std::is_enum_v<Type> && requires(Type type) {
{
static_cast<std::underlying_type_t<Type>>(type)
} -> std::convertible_to<int>;
};

/**
* Template that defines a `std::error_category` of the given set of error code enumeration.
* @tparam ErrorCodeEnum
*/
template <ErrorCodeEnumType ErrorCodeEnum>
class ErrorCategory : public std::error_category {
public:
// Methods implementing `std::error_category`
/**
* Gets the error category name.
* Note: A specialization must be explicitly implemented for each valid `ErrorCodeEnum`.
* @return The name of the error category.
*/
[[nodiscard]] auto name() const noexcept -> char const* override;
LinZhihao-723 marked this conversation as resolved.
Show resolved Hide resolved

/**
* Gets the descriptive message associated with the given error.
* @param error_num
* @return The descriptive message for the error.
*/
[[nodiscard]] auto message(int error_num) const -> std::string override;

// Methods
/**
* Gets the descriptive message associated with the given error.
* Note: A specialization must be explicitly implemented for each valid `ErrorCodeEnum`.
* @param error_enum.
* @return The descriptive message for the error.
*/
[[nodiscard]] auto message(ErrorCodeEnum error_enum) const -> std::string;
};

/**
* Template class that defines an error code. An error code is represented by a error enum value and
* the associated error category. This template class is designed to be `std::error_code`
* compatible, meaning that every instance of this class can be used to construct a corresponded
* `std::error_code` instance, or compare with a `std::error_code` instance to inspect a specific
* error.
* @tparam ErrorCodeEnum
*/
template <ErrorCodeEnumType ErrorCodeEnum>
class ErrorCode {
public:
// Constructor
ErrorCode(ErrorCodeEnum error) : m_error{error} {}

/**
* @return The error code as an error number.
*/
[[nodiscard]] auto get_errno() const -> int;

/**
* @return The underlying error code enum.
*/
[[nodiscard]] auto get_err_enum() const -> ErrorCodeEnum;

/**
* @return The reference to the singleton of the corresponded error category.
*/
[[nodiscard]] static auto get_category() -> ErrorCategory<ErrorCodeEnum> const&;

private:
static inline ErrorCategory<ErrorCodeEnum> const cCategory;

ErrorCodeEnum m_error;
};

/**
* @tparam ErrorCodeEnum
* @param error
* @return Constructed `std::error_code` from the given `ErrorCode` instance.
*/
template <typename ErrorCodeEnum>
[[nodiscard]] auto make_error_code(ErrorCode<ErrorCodeEnum> error) -> std::error_code;

template <ErrorCodeEnumType ErrorCodeEnum>
auto ErrorCategory<ErrorCodeEnum>::message(int error_num) const -> std::string {
return message(static_cast<ErrorCodeEnum>(error_num));
}

template <ErrorCodeEnumType ErrorCodeEnum>
auto ErrorCode<ErrorCodeEnum>::get_errno() const -> int {
return static_cast<int>(m_error);
}

template <ErrorCodeEnumType ErrorCodeEnum>
auto ErrorCode<ErrorCodeEnum>::get_err_enum() const -> ErrorCodeEnum {
return m_error;
}

template <ErrorCodeEnumType ErrorCodeEnum>
auto ErrorCode<ErrorCodeEnum>::get_category() -> ErrorCategory<ErrorCodeEnum> const& {
return ErrorCode<ErrorCodeEnum>::cCategory;
}

template <typename ErrorCodeEnum>
auto make_error_code(ErrorCode<ErrorCodeEnum> error) -> std::error_code {
return {error.get_errno(), ErrorCode<ErrorCodeEnum>::get_category()};
}
} // namespace clp::error_handling

#endif // CLP_ERROR_HANDLING_ERRORCODE_HPP
113 changes: 113 additions & 0 deletions components/core/tests/test-error_handling.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#include <cstdint>
#include <string>
#include <string_view>
#include <system_error>
#include <type_traits>

#include <Catch2/single_include/catch2/catch.hpp>

#include "../src/clp/error_handling/ErrorCode.hpp"

using clp::error_handling::ErrorCategory;
using clp::error_handling::ErrorCode;
using std::string;
using std::string_view;

namespace {
enum class AlwaysSuccessErrorCodeEnum : uint8_t {
Success = 0
};

enum class BinaryErrorCodeEnum : uint8_t {
Success = 0,
Failure
};

using AlwaysSuccessErrorCode = ErrorCode<AlwaysSuccessErrorCodeEnum>;
using AlwaysSuccessErrorCategory = ErrorCategory<AlwaysSuccessErrorCodeEnum>;
using BinaryErrorCode = ErrorCode<BinaryErrorCodeEnum>;
using BinaryErrorCategory = ErrorCategory<BinaryErrorCodeEnum>;

constexpr string_view cAlwaysSuccessErrorCategoryName{"Always Success Error Code"};
constexpr string_view cBinaryTestErrorCategoryName{"Binary Error Code"};
constexpr string_view cSuccessErrMsg{"Success"};
constexpr string_view cFailureErrMsg{"Failure"};
constexpr string_view cUnrecognizedErrorCode{"Unrecognized Error Code"};
} // namespace

namespace std {
template <>
struct is_error_code_enum<BinaryErrorCode> : std::true_type {};

template <>
struct is_error_code_enum<AlwaysSuccessErrorCode> : std::true_type {};
} // namespace std

template <>
auto AlwaysSuccessErrorCategory::name() const noexcept -> char const* {
return cAlwaysSuccessErrorCategoryName.data();
}

template <>
auto AlwaysSuccessErrorCategory::message(AlwaysSuccessErrorCodeEnum error_enum) const -> string {
switch (error_enum) {
case AlwaysSuccessErrorCodeEnum::Success:
return string{cSuccessErrMsg};
default:
return string{cUnrecognizedErrorCode};
}
}

template <>
auto BinaryErrorCategory::name() const noexcept -> char const* {
return cBinaryTestErrorCategoryName.data();
}

template <>
auto BinaryErrorCategory::message(BinaryErrorCodeEnum error_enum) const -> string {
switch (error_enum) {
case BinaryErrorCodeEnum::Success:
return string{cSuccessErrMsg};
case BinaryErrorCodeEnum::Failure:
return string{cFailureErrMsg};
default:
return string{cUnrecognizedErrorCode};
}
}

TEST_CASE("test_error_code_implementation", "[error_handling][ErrorCode]") {
// Test error codes within the same error category
BinaryErrorCode const success{BinaryErrorCodeEnum::Success};
std::error_code const success_error_code{success};
REQUIRE((success == success_error_code));
REQUIRE((cSuccessErrMsg == success_error_code.message()));
REQUIRE((BinaryErrorCode::get_category() == success_error_code.category()));
REQUIRE((cBinaryTestErrorCategoryName == success_error_code.category().name()));

BinaryErrorCode const failure{BinaryErrorCodeEnum::Failure};
std::error_code const failure_error_code{failure};
REQUIRE((failure == failure_error_code));
REQUIRE((cFailureErrMsg == failure_error_code.message()));
REQUIRE((BinaryErrorCode::get_category() == failure_error_code.category()));
REQUIRE((cBinaryTestErrorCategoryName == failure_error_code.category().name()));

REQUIRE((success_error_code != failure_error_code));
REQUIRE((success_error_code.category() == failure_error_code.category()));

AlwaysSuccessErrorCode const always_success{AlwaysSuccessErrorCodeEnum::Success};
std::error_code const always_success_error_code{always_success};
REQUIRE((always_success_error_code == always_success));
REQUIRE((cSuccessErrMsg == always_success_error_code.message()));
REQUIRE((AlwaysSuccessErrorCode::get_category() == always_success_error_code.category()));
REQUIRE((cAlwaysSuccessErrorCategoryName == always_success_error_code.category().name()));

// Compare error codes from different error category
// Error codes that have the same value or message won't be the same with each other if they are
// from different error categories.
REQUIRE((success_error_code.value() == always_success_error_code.value()));
REQUIRE((success_error_code.message() == always_success_error_code.message()));
REQUIRE((success_error_code.category() != always_success_error_code.category()));
REQUIRE((success_error_code != always_success_error_code));
REQUIRE((AlwaysSuccessErrorCode{AlwaysSuccessErrorCodeEnum::Success} != success_error_code));
REQUIRE((BinaryErrorCode{BinaryErrorCodeEnum::Success} != always_success_error_code));
}
Loading