Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/ystdlib/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
add_subdirectory(testlib)
add_subdirectory(error_handling)
8 changes: 8 additions & 0 deletions src/ystdlib/error_handling/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
cpp_library(
NAME error_handling
NAMESPACE ystdlib
TESTS_SOURCES
test-Defs.hpp
test-Defs.cpp
test-ErrorCode.cpp
)
148 changes: 148 additions & 0 deletions src/ystdlib/error_handling/ErrorCode.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#ifndef YSTDLIB_ERROR_HANDLING_ERRORCODE_HPP
#define YSTDLIB_ERROR_HANDLING_ERRORCODE_HPP

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

namespace ystdlib::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;

/**
* 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 {
return message(static_cast<ErrorCodeEnum>(error_num));
}

/**
* @param error_num
* @param condition
* @return Whether the error condition of the given error matches the given condition.
*/
[[nodiscard]] auto
equivalent(int error_num, std::error_condition const& condition) const noexcept
-> bool override {
return equivalent(static_cast<ErrorCodeEnum>(error_num), condition);
}

// 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;

/**
* Note: A specialization can be implemented to create error enum to error condition mappings.
* @param error_num
* @param condition
* @return Whether the error condition of the given error matches the given condition.
*/
[[nodiscard]] auto
equivalent(ErrorCodeEnum error_enum, std::error_condition const& condition) const noexcept
-> bool;
};

/**
* 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 underlying error code enum.
*/
[[nodiscard]] auto get_error() const -> ErrorCodeEnum { return m_error; }

/**
* @return The error code as an error number.
*/
[[nodiscard]] auto get_error_num() const -> int { return static_cast<int>(m_error); }

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

Comment thread
Bill-hbrhbr marked this conversation as resolved.
Outdated
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>::equivalent(
ErrorCodeEnum error_enum,
std::error_condition const& condition
) const noexcept -> bool {
return std::error_category::default_error_condition(static_cast<int>(error_enum)) == condition;
}

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

/**
* The macro to create a specialization of `std::is_error_code_enum` for a given type T. Only types
* that are marked with this macro will be considered as a valid YStdlib error code enum, and thus
* used to specialize `ErrorCode` and `ErrorCategory` templates.
*/
// NOLINTBEGIN(bugprone-macro-parentheses, cppcoreguidelines-macro-usage)
#define YSTDLIB_ERROR_HANDLING_MARK_AS_ERROR_CODE_ENUM(T) \
template <> \
struct std::is_error_code_enum<ystdlib::error_handling::ErrorCode<T>> : std::true_type { \
static_assert(std::is_enum_v<T>); \
};
// NOLINTEND(bugprone-macro-parentheses, cppcoreguidelines-macro-usage)

#endif // YSTDLIB_ERROR_HANDLING_ERRORCODE_HPP
57 changes: 57 additions & 0 deletions src/ystdlib/error_handling/test-Defs.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#include "test-Defs.hpp"

#include <algorithm>
#include <array>
#include <string>
#include <string_view>
#include <system_error>

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

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

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

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

template <>
auto BinaryErrorCategory::equivalent(
BinaryErrorCodeEnum error_enum,
std::error_condition const& condition
) const noexcept -> bool {
switch (error_enum) {
case BinaryErrorCodeEnum::Failure:
return std::ranges::any_of(
cFailureConditions.cbegin(),
cFailureConditions.cend(),
[&](auto failure_condition) -> bool { return condition == failure_condition; }
);
default:
return false;
}
}
32 changes: 32 additions & 0 deletions src/ystdlib/error_handling/test-Defs.hpp
Comment thread
Bill-hbrhbr marked this conversation as resolved.
Outdated
Comment thread
Bill-hbrhbr marked this conversation as resolved.
Outdated
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#include <array>
#include <cstdint>
#include <string_view>
#include <system_error>

#include <ystdlib/error_handling/ErrorCode.hpp>

constexpr std::string_view cAlwaysSuccessErrorCategoryName{"Always Success Error Code"};
constexpr std::string_view cBinaryTestErrorCategoryName{"Binary Error Code"};
constexpr std::string_view cSuccessErrorMsg{"Success"};
constexpr std::string_view cFailureErrorMsg{"Failure"};
constexpr std::string_view cUnrecognizedErrorCode{"Unrecognized Error Code"};
constexpr std::array cFailureConditions{std::errc::not_connected, std::errc::timed_out};
constexpr std::array cNoneFailureConditions{std::errc::broken_pipe, std::errc::address_in_use};

enum class AlwaysSuccessErrorCodeEnum : uint8_t {
Success = 0
};

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

using AlwaysSuccessErrorCode = ystdlib::error_handling::ErrorCode<AlwaysSuccessErrorCodeEnum>;
using AlwaysSuccessErrorCategory
= ystdlib::error_handling::ErrorCategory<AlwaysSuccessErrorCodeEnum>;
using BinaryErrorCode = ystdlib::error_handling::ErrorCode<BinaryErrorCodeEnum>;
using BinaryErrorCategory = ystdlib::error_handling::ErrorCategory<BinaryErrorCodeEnum>;
Comment thread
Bill-hbrhbr marked this conversation as resolved.
Outdated

YSTDLIB_ERROR_HANDLING_MARK_AS_ERROR_CODE_ENUM(AlwaysSuccessErrorCodeEnum);
YSTDLIB_ERROR_HANDLING_MARK_AS_ERROR_CODE_ENUM(BinaryErrorCodeEnum);
63 changes: 63 additions & 0 deletions src/ystdlib/error_handling/test-ErrorCode.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#include <algorithm>
#include <array>
#include <string_view>
#include <system_error>

#include <ystdlib/error_handling/ErrorCode.hpp>

#include <catch2/catch_test_macros.hpp>

#include "test-Defs.hpp"

TEST_CASE("test_error_code", "[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((cSuccessErrorMsg == 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((cFailureErrorMsg == 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((cSuccessErrorMsg == 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));
}

TEST_CASE("test_error_code_failure_condition", "[error_handling][ErrorCode]") {
std::error_code const failure_error_code{BinaryErrorCode{BinaryErrorCodeEnum::Failure}};
std::ranges::for_each(
cFailureConditions.cbegin(),
cFailureConditions.cend(),
[&](auto failure_condition) { REQUIRE((failure_error_code == failure_condition)); }
);
std::ranges::for_each(
cNoneFailureConditions.cbegin(),
cNoneFailureConditions.cend(),
[&](auto none_failure_condition) {
REQUIRE((failure_error_code != none_failure_condition));
}
);
}
1 change: 0 additions & 1 deletion src/ystdlib/testlib/CMakeLists.txt

This file was deleted.

12 changes: 0 additions & 12 deletions src/ystdlib/testlib/hello.hpp

This file was deleted.

18 changes: 0 additions & 18 deletions src/ystdlib/testlib/test-hello.cpp

This file was deleted.

2 changes: 1 addition & 1 deletion taskfile.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ vars:
G_TEST_BIN_DIR: "{{.G_BUILD_DIR}}/testbin"
G_TEST_TARGET_SUFFIXES:
- "all"
- "testlib"
- "error_handling"

tasks:
clean:
Expand Down