-
Notifications
You must be signed in to change notification settings - Fork 4
feat(error_handling): Migrate TraceableException
from CLP with changes:
#43
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
Changes from all commits
a249239
1193425
74e4b45
424b310
369da3f
e390fcf
6e4e637
9b24006
51ef54b
193fe11
57b0937
a30d207
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
#ifndef YSTDLIB_ERROR_HANDLING_TRACEABLEEXCEPTION_HPP | ||
#define YSTDLIB_ERROR_HANDLING_TRACEABLEEXCEPTION_HPP | ||
|
||
#include <exception> | ||
#include <source_location> | ||
#include <sstream> | ||
#include <string> | ||
#include <system_error> | ||
#include <utility> | ||
|
||
#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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
#include <cassert> | ||
#include <cstring> | ||
|
||
#include <ystdlib/error_handling/TraceableException.hpp> | ||
|
||
#include <catch2/catch_test_macros.hpp> | ||
|
||
#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 <typename Callable> | ||
[[nodiscard]] auto capture_exception(Callable&& f) -> TraceableException; | ||
|
||
template <typename Callable> | ||
auto capture_exception(Callable&& f) -> TraceableException { | ||
try { | ||
std::forward<Callable>(f)(); | ||
} catch (TraceableException& e) { | ||
return e; | ||
} | ||
assert(false && "The function is expected to throw."); | ||
} | ||
Comment on lines
+41
to
+53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Enhance type safety with template constraints. |
||
} // 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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
#ifndef YSTDLIB_ERROR_HANDLING_UTILS_HPP | ||
#define YSTDLIB_ERROR_HANDLING_UTILS_HPP | ||
|
||
#include <ostream> | ||
#include <source_location> | ||
|
||
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Avoid overloading 'what' with differing return types.
Having both
what() const noexcept -> char const*
(line 49) andwhat() -> std::string&
(line 54) can cause confusion. Callers might expect the standard signature only. If read–write access tom_what
is needed, consider offering a different named method.