Skip to content

Commit f65e98b

Browse files
authored
feat(error_handling): Migrate TraceableException from CLP with changes: (#43)
* Replace `__FILENAME__` and `__LINE__` macros with `std::source_location`. * Add a new parameter `message` to allow users to specify custom error messages. * Add `YSTDLIB_ERROR_HANDLING_DEFINE_TRACEABLE_EXCEPTION(type_name)` macro for simplified definitions. * Use absolute paths instead of relative paths for file locations.
1 parent 6b5a88c commit f65e98b

File tree

4 files changed

+168
-0
lines changed

4 files changed

+168
-0
lines changed

src/ystdlib/error_handling/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,12 @@ cpp_library(
33
NAMESPACE ystdlib
44
PUBLIC_HEADERS
55
ErrorCode.hpp
6+
TraceableException.hpp
7+
utils.hpp
68
TESTS_SOURCES
79
test/constants.hpp
810
test/test_ErrorCode.cpp
11+
test/test_TraceableException.cpp
912
test/types.cpp
1013
test/types.hpp
1114
)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#ifndef YSTDLIB_ERROR_HANDLING_TRACEABLEEXCEPTION_HPP
2+
#define YSTDLIB_ERROR_HANDLING_TRACEABLEEXCEPTION_HPP
3+
4+
#include <exception>
5+
#include <source_location>
6+
#include <sstream>
7+
#include <string>
8+
#include <system_error>
9+
#include <utility>
10+
11+
#include "utils.hpp"
12+
13+
namespace ystdlib::error_handling {
14+
/**
15+
* An exception class that is thrown with an `std::error_code`.
16+
*
17+
* This class extends `std::exception` and can be thrown with an `std::error_code` argument. It also
18+
* provides additional information to aid in debugging by storing details in `std::source_location`,
19+
* including the function name, file name, and line number of the throwing location.
20+
*
21+
* @see std::source_location::file_name()
22+
* @see std::source_location::function_name()
23+
* @see std::source_location::line()
24+
*/
25+
class TraceableException : public std::exception {
26+
public:
27+
// Constructors
28+
explicit TraceableException(
29+
std::error_code error_code,
30+
std::source_location const& where = std::source_location::current()
31+
)
32+
: m_error_code{error_code},
33+
m_where{where} {
34+
std::ostringstream oss;
35+
oss << where;
36+
m_what = oss.str();
37+
}
38+
39+
explicit TraceableException(
40+
std::error_code error_code,
41+
std::string message,
42+
std::source_location const& where = std::source_location::current()
43+
)
44+
: m_error_code{error_code},
45+
m_what{std::move(message)},
46+
m_where{where} {}
47+
48+
// Methods implementing std::exception
49+
[[nodiscard]] auto what() const noexcept -> char const* override { return m_what.c_str(); }
50+
51+
// Methods
52+
[[nodiscard]] auto error_code() const -> std::error_code { return m_error_code; }
53+
54+
[[nodiscard]] auto what() -> std::string& { return m_what; }
55+
56+
[[nodiscard]] auto where() const noexcept -> std::source_location const& { return m_where; }
57+
58+
private:
59+
// Variables
60+
std::error_code m_error_code;
61+
std::string m_what;
62+
std::source_location m_where;
63+
};
64+
} // namespace ystdlib::error_handling
65+
66+
// NOLINTBEGIN(bugprone-macro-parentheses, cppcoreguidelines-macro-usage)
67+
/**
68+
* Defines a derived `TraceableException` class with the given class name.
69+
*
70+
* @param T The class' name.
71+
*/
72+
#define YSTDLIB_ERROR_HANDLING_DEFINE_TRACEABLE_EXCEPTION(T) \
73+
class T : public ystdlib::error_handling::TraceableException { \
74+
using ystdlib::error_handling::TraceableException::TraceableException; \
75+
}
76+
// NOLINTEND(bugprone-macro-parentheses, cppcoreguidelines-macro-usage)
77+
78+
#endif // YSTDLIB_ERROR_HANDLING_TRACEABLEEXCEPTION_HPP
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#include <cassert>
2+
#include <cstring>
3+
4+
#include <ystdlib/error_handling/TraceableException.hpp>
5+
6+
#include <catch2/catch_test_macros.hpp>
7+
8+
#include "types.hpp"
9+
10+
using ystdlib::error_handling::TraceableException;
11+
12+
namespace {
13+
constexpr auto cCustomFailureDescription{"This operation failed due to invalid args."};
14+
constexpr auto cCurrentFileName{"src/ystdlib/error_handling/test/test_TraceableException.cpp"};
15+
constexpr auto cSuccessFuncName{
16+
"static void ystdlib::error_handling::test::Worker::execute_with_success()"
17+
};
18+
constexpr auto cFailureFuncName{
19+
"static void ystdlib::error_handling::test::Worker::execute_with_failure()"
20+
};
21+
} // namespace
22+
23+
namespace ystdlib::error_handling::test {
24+
class Worker {
25+
public:
26+
YSTDLIB_ERROR_HANDLING_DEFINE_TRACEABLE_EXCEPTION(OperationFailed);
27+
28+
static auto execute_with_success() -> void {
29+
throw OperationFailed(BinaryErrorCode{BinaryErrorCodeEnum::Success});
30+
}
31+
32+
static auto execute_with_failure() -> void {
33+
throw OperationFailed(
34+
BinaryErrorCode{BinaryErrorCodeEnum::Failure},
35+
cCustomFailureDescription
36+
);
37+
}
38+
};
39+
} // namespace ystdlib::error_handling::test
40+
41+
namespace {
42+
template <typename Callable>
43+
[[nodiscard]] auto capture_exception(Callable&& f) -> TraceableException;
44+
45+
template <typename Callable>
46+
auto capture_exception(Callable&& f) -> TraceableException {
47+
try {
48+
std::forward<Callable>(f)();
49+
} catch (TraceableException& e) {
50+
return e;
51+
}
52+
assert(false && "The function is expected to throw.");
53+
}
54+
} // namespace
55+
56+
namespace ystdlib::error_handling::test {
57+
TEST_CASE("test_traceable_exception", "[error_handling][TraceableException]") {
58+
auto const ex_success{capture_exception(Worker::execute_with_success)};
59+
REQUIRE(std::string{ex_success.where().file_name()}.ends_with(cCurrentFileName));
60+
REQUIRE((0 == std::strcmp(ex_success.where().function_name(), cSuccessFuncName)));
61+
62+
auto const ex_failure{capture_exception(Worker::execute_with_failure)};
63+
REQUIRE((0 == std::strcmp(ex_failure.what(), cCustomFailureDescription)));
64+
REQUIRE(std::string{ex_failure.where().file_name()}.ends_with(cCurrentFileName));
65+
REQUIRE((0 == std::strcmp(ex_failure.where().function_name(), cFailureFuncName)));
66+
}
67+
} // namespace ystdlib::error_handling::test
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#ifndef YSTDLIB_ERROR_HANDLING_UTILS_HPP
2+
#define YSTDLIB_ERROR_HANDLING_UTILS_HPP
3+
4+
#include <ostream>
5+
#include <source_location>
6+
7+
namespace ystdlib::error_handling {
8+
/**
9+
* Writes a formatted representation of `std::source_location` to the output stream.
10+
*
11+
* @param os
12+
* @param where
13+
*/
14+
inline auto operator<<(std::ostream& os, std::source_location const& where) -> std::ostream& {
15+
return os << where.file_name() << "(" << where.line() << ":" << where.column()
16+
<< "), function `" << where.function_name() << "`";
17+
}
18+
} // namespace ystdlib::error_handling
19+
20+
#endif // YSTDLIB_ERROR_HANDLING_UTILS_HPP

0 commit comments

Comments
 (0)