Skip to content

Commit a249239

Browse files
committed
Add traceable exception
1 parent 5f43a3b commit a249239

File tree

4 files changed

+172
-0
lines changed

4 files changed

+172
-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+
SourceLocation.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: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#ifndef YSTDLIB_ERROR_HANDLING_SOURCELOCATION_HPP
2+
#define YSTDLIB_ERROR_HANDLING_SOURCELOCATION_HPP
3+
4+
#include <iostream>
5+
#include <source_location>
6+
7+
namespace ystdlib::error_handling {
8+
class SourceLocation : public std::source_location {
9+
public:
10+
// Constructor
11+
constexpr explicit SourceLocation(std::source_location where) : std::source_location{where} {}
12+
13+
#ifdef YSTDLIB_CPP_PROJECT_SOURCE_PATH_SIZE
14+
[[nodiscard]] auto file_name() const -> char const* {
15+
// NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic)
16+
return std::source_location::file_name() + YSTDLIB_CPP_PROJECT_SOURCE_PATH_SIZE;
17+
}
18+
#endif
19+
20+
friend std::ostream& operator<<(std::ostream& os, SourceLocation const& where) {
21+
os << where.file_name() << "(" << where.line() << ":" << where.column() << "), function `"
22+
<< where.function_name() << "`";
23+
return os;
24+
}
25+
};
26+
} // namespace ystdlib::error_handling
27+
28+
#endif // YSTDLIB_ERROR_HANDLING_SOURCELOCATION_HPP
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#ifndef YSTDLIB_ERROR_HANDLING_TRACEABLEEXCEPTION_HPP
2+
#define YSTDLIB_ERROR_HANDLING_TRACEABLEEXCEPTION_HPP
3+
4+
#include <concepts>
5+
#include <exception>
6+
#include <source_location>
7+
#include <stacktrace>
8+
#include <string>
9+
#include <system_error>
10+
11+
#include "SourceLocation.hpp"
12+
13+
namespace ystdlib::error_handling {
14+
/**
15+
* Concept that defines a template parameter of an integer-based error code enumeration.
16+
* @tparam Type
17+
*/
18+
template <typename Type>
19+
concept ErrorCodeType
20+
= std::same_as<Type, std::error_code> || std::convertible_to<Type, std::error_code>;
21+
22+
/**
23+
* An exception class that is thrown with an `std::error_code`.
24+
*
25+
* This class extends `std::exception` and can be thrown with an `std::error_code` argument. It also
26+
* provides additional information to aid in debugging by storing details in `std::source_location`,
27+
* including the function name, file name, and line number of the throwing location.
28+
*
29+
* @see std::source_location::file_name()
30+
* @see std::source_location::function_name()
31+
* @see std::source_location::line()
32+
*/
33+
template <typename ErrorCodeType>
34+
class TraceableException : public std::exception, public std::source_location {
35+
public:
36+
// Constructors
37+
explicit TraceableException(
38+
ErrorCodeType error_code,
39+
std::source_location const& where = std::source_location::current()
40+
)
41+
: TraceableException{std::move(error_code), where.function_name(), where} {}
42+
43+
explicit TraceableException(
44+
ErrorCodeType error_code,
45+
std::string what,
46+
std::source_location const& where = std::source_location::current()
47+
)
48+
: m_error_code{std::move(error_code)},
49+
m_what{std::move(what)},
50+
m_where{where} {}
51+
52+
// Methods implementing std::exception
53+
[[nodiscard]] auto what() const noexcept -> char const* override { return m_what.c_str(); }
54+
55+
// Methods
56+
[[nodiscard]] auto error_code() const -> ErrorCodeType { return m_error_code; }
57+
58+
[[nodiscard]] auto what() -> std::string& { return m_what; }
59+
60+
[[nodiscard]] auto where() const noexcept -> SourceLocation const& { return m_where; }
61+
62+
private:
63+
// Variables
64+
ErrorCodeType const m_error_code;
65+
std::string m_what;
66+
SourceLocation const m_where;
67+
};
68+
69+
} // namespace ystdlib::error_handling
70+
71+
/**
72+
* The macro to define a `TraceableException` class with the given class name T.
73+
*/
74+
// NOLINTBEGIN(bugprone-macro-parentheses, cppcoreguidelines-macro-usage)
75+
#define YSTDLIB_ERROR_HANDLING_DEFINE_TRACEABLE_EXCEPTION(T, E) \
76+
class T : public ystdlib::error_handling::TraceableException<E> { \
77+
using ystdlib::error_handling::TraceableException<E>::TraceableException; \
78+
}
79+
// NOLINTEND(bugprone-macro-parentheses, cppcoreguidelines-macro-usage)
80+
81+
#endif // YSTDLIB_ERROR_HANDLING_TRACEABLEEXCEPTION_HPP
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#include <cassert>
2+
#include <iostream>
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::ErrorCodeType;
11+
using ystdlib::error_handling::TraceableException;
12+
13+
namespace {
14+
constexpr auto cCustomFailureDescription{"This operation has failed."};
15+
} // namespace
16+
17+
namespace ystdlib::error_handling::test {
18+
class Worker {
19+
public:
20+
YSTDLIB_ERROR_HANDLING_DEFINE_TRACEABLE_EXCEPTION(OperationFailed, BinaryErrorCode);
21+
22+
static auto execute_with_success() -> void {
23+
throw OperationFailed(BinaryErrorCode{BinaryErrorCodeEnum::Success});
24+
}
25+
26+
static auto execute_with_failure() -> void {
27+
// clang-format off
28+
throw OperationFailed(BinaryErrorCode{BinaryErrorCodeEnum::Failure}, cCustomFailureDescription);
29+
// clang-format on
30+
}
31+
32+
// static auto execute_with_invalid_args() -> void {
33+
// throw OperationFailed(std::make_error_code(std::errc::invalid_argument));
34+
// }
35+
};
36+
} // namespace ystdlib::error_handling::test
37+
38+
namespace {
39+
template <ErrorCodeType E, typename Callable>
40+
[[nodiscard]] auto capture_exception(Callable&& f) -> TraceableException<E>;
41+
42+
template <ErrorCodeType E, typename Callable>
43+
auto capture_exception(Callable&& f) -> TraceableException<E> {
44+
try {
45+
std::forward<Callable>(f)();
46+
} catch (TraceableException<E>& e) {
47+
return e;
48+
}
49+
assert(false && "The function is expected to throw.");
50+
}
51+
} // namespace
52+
53+
namespace ystdlib::error_handling::test {
54+
TEST_CASE("test_traceable_exception", "[error_handling][TraceableException]") {
55+
REQUIRE(1 == 1);
56+
auto const ex{capture_exception<BinaryErrorCode>(Worker::execute_with_success)};
57+
std::cout << ex.where().file_name() << std::endl;
58+
std::cout << ex.where() << std::endl;
59+
}
60+
} // namespace ystdlib::error_handling::test

0 commit comments

Comments
 (0)