diff --git a/src/ystdlib/error_handling/CMakeLists.txt b/src/ystdlib/error_handling/CMakeLists.txt index c5b70129..b3c41a5e 100644 --- a/src/ystdlib/error_handling/CMakeLists.txt +++ b/src/ystdlib/error_handling/CMakeLists.txt @@ -3,9 +3,12 @@ cpp_library( NAMESPACE ystdlib PUBLIC_HEADERS ErrorCode.hpp + TraceableException.hpp + utils.hpp TESTS_SOURCES test/constants.hpp test/test_ErrorCode.cpp + test/test_TraceableException.cpp test/types.cpp test/types.hpp ) diff --git a/src/ystdlib/error_handling/TraceableException.hpp b/src/ystdlib/error_handling/TraceableException.hpp new file mode 100644 index 00000000..43a34d70 --- /dev/null +++ b/src/ystdlib/error_handling/TraceableException.hpp @@ -0,0 +1,78 @@ +#ifndef YSTDLIB_ERROR_HANDLING_TRACEABLEEXCEPTION_HPP +#define YSTDLIB_ERROR_HANDLING_TRACEABLEEXCEPTION_HPP + +#include +#include +#include +#include +#include +#include + +#include "utils.hpp" + +namespace ystdlib::error_handling { +/** + * An exception class that is thrown with an `std::error_code`. + * + * This class extends `std::exception` and can be thrown with an `std::error_code` argument. It also + * provides additional information to aid in debugging by storing details in `std::source_location`, + * including the function name, file name, and line number of the throwing location. + * + * @see std::source_location::file_name() + * @see std::source_location::function_name() + * @see std::source_location::line() + */ +class TraceableException : public std::exception { +public: + // Constructors + explicit TraceableException( + std::error_code error_code, + std::source_location const& where = std::source_location::current() + ) + : m_error_code{error_code}, + m_where{where} { + std::ostringstream oss; + oss << where; + m_what = oss.str(); + } + + explicit TraceableException( + std::error_code error_code, + std::string message, + std::source_location const& where = std::source_location::current() + ) + : m_error_code{error_code}, + m_what{std::move(message)}, + m_where{where} {} + + // Methods implementing std::exception + [[nodiscard]] auto what() const noexcept -> char const* override { return m_what.c_str(); } + + // Methods + [[nodiscard]] auto error_code() const -> std::error_code { return m_error_code; } + + [[nodiscard]] auto what() -> std::string& { return m_what; } + + [[nodiscard]] auto where() const noexcept -> std::source_location const& { return m_where; } + +private: + // Variables + std::error_code m_error_code; + std::string m_what; + std::source_location m_where; +}; +} // namespace ystdlib::error_handling + +// NOLINTBEGIN(bugprone-macro-parentheses, cppcoreguidelines-macro-usage) +/** + * Defines a derived `TraceableException` class with the given class name. + * + * @param T The class' name. + */ +#define YSTDLIB_ERROR_HANDLING_DEFINE_TRACEABLE_EXCEPTION(T) \ + class T : public ystdlib::error_handling::TraceableException { \ + using ystdlib::error_handling::TraceableException::TraceableException; \ + } +// NOLINTEND(bugprone-macro-parentheses, cppcoreguidelines-macro-usage) + +#endif // YSTDLIB_ERROR_HANDLING_TRACEABLEEXCEPTION_HPP diff --git a/src/ystdlib/error_handling/test/test_TraceableException.cpp b/src/ystdlib/error_handling/test/test_TraceableException.cpp new file mode 100644 index 00000000..0fec7953 --- /dev/null +++ b/src/ystdlib/error_handling/test/test_TraceableException.cpp @@ -0,0 +1,67 @@ +#include +#include + +#include + +#include + +#include "types.hpp" + +using ystdlib::error_handling::TraceableException; + +namespace { +constexpr auto cCustomFailureDescription{"This operation failed due to invalid args."}; +constexpr auto cCurrentFileName{"src/ystdlib/error_handling/test/test_TraceableException.cpp"}; +constexpr auto cSuccessFuncName{ + "static void ystdlib::error_handling::test::Worker::execute_with_success()" +}; +constexpr auto cFailureFuncName{ + "static void ystdlib::error_handling::test::Worker::execute_with_failure()" +}; +} // namespace + +namespace ystdlib::error_handling::test { +class Worker { +public: + YSTDLIB_ERROR_HANDLING_DEFINE_TRACEABLE_EXCEPTION(OperationFailed); + + static auto execute_with_success() -> void { + throw OperationFailed(BinaryErrorCode{BinaryErrorCodeEnum::Success}); + } + + static auto execute_with_failure() -> void { + throw OperationFailed( + BinaryErrorCode{BinaryErrorCodeEnum::Failure}, + cCustomFailureDescription + ); + } +}; +} // namespace ystdlib::error_handling::test + +namespace { +template +[[nodiscard]] auto capture_exception(Callable&& f) -> TraceableException; + +template +auto capture_exception(Callable&& f) -> TraceableException { + try { + std::forward(f)(); + } catch (TraceableException& e) { + return e; + } + assert(false && "The function is expected to throw."); +} +} // namespace + +namespace ystdlib::error_handling::test { +TEST_CASE("test_traceable_exception", "[error_handling][TraceableException]") { + auto const ex_success{capture_exception(Worker::execute_with_success)}; + REQUIRE(std::string{ex_success.where().file_name()}.ends_with(cCurrentFileName)); + REQUIRE((0 == std::strcmp(ex_success.where().function_name(), cSuccessFuncName))); + + auto const ex_failure{capture_exception(Worker::execute_with_failure)}; + REQUIRE((0 == std::strcmp(ex_failure.what(), cCustomFailureDescription))); + REQUIRE(std::string{ex_failure.where().file_name()}.ends_with(cCurrentFileName)); + REQUIRE((0 == std::strcmp(ex_failure.where().function_name(), cFailureFuncName))); +} +} // namespace ystdlib::error_handling::test diff --git a/src/ystdlib/error_handling/utils.hpp b/src/ystdlib/error_handling/utils.hpp new file mode 100644 index 00000000..b20e6a1d --- /dev/null +++ b/src/ystdlib/error_handling/utils.hpp @@ -0,0 +1,20 @@ +#ifndef YSTDLIB_ERROR_HANDLING_UTILS_HPP +#define YSTDLIB_ERROR_HANDLING_UTILS_HPP + +#include +#include + +namespace ystdlib::error_handling { +/** + * Writes a formatted representation of `std::source_location` to the output stream. + * + * @param os + * @param where + */ +inline auto operator<<(std::ostream& os, std::source_location const& where) -> std::ostream& { + return os << where.file_name() << "(" << where.line() << ":" << where.column() + << "), function `" << where.function_name() << "`"; +} +} // namespace ystdlib::error_handling + +#endif // YSTDLIB_ERROR_HANDLING_UTILS_HPP