diff options
| author | Lexi Winter <lexi@le-fay.org> | 2025-07-02 03:43:39 +0100 |
|---|---|---|
| committer | Lexi Winter <lexi@le-fay.org> | 2025-07-02 03:43:39 +0100 |
| commit | 06fafff8e9e9c096cc39bde0306caa53ad3a2351 (patch) | |
| tree | edfd6a5b08b77bc082e53b7b53a2e5f8dbbc8566 /nihil.error | |
| parent | 712edacbc75e6dc98c11b3c5f6c52f823c747e2f (diff) | |
| download | nihil-06fafff8e9e9c096cc39bde0306caa53ad3a2351.tar.gz nihil-06fafff8e9e9c096cc39bde0306caa53ad3a2351.tar.bz2 | |
move error and match to util
Diffstat (limited to 'nihil.error')
| -rw-r--r-- | nihil.error/CMakeLists.txt | 27 | ||||
| -rw-r--r-- | nihil.error/error.ccm | 322 | ||||
| -rw-r--r-- | nihil.error/error.test.cc | 273 | ||||
| -rw-r--r-- | nihil.error/sys_error.ccm | 18 |
4 files changed, 0 insertions, 640 deletions
diff --git a/nihil.error/CMakeLists.txt b/nihil.error/CMakeLists.txt deleted file mode 100644 index 81c2cd8..0000000 --- a/nihil.error/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -# This source code is released into the public domain. - -add_library(nihil.error STATIC) -target_link_libraries(nihil.error PRIVATE nihil.std nihil.match) -target_sources(nihil.error - PUBLIC FILE_SET modules TYPE CXX_MODULES FILES - error.ccm - sys_error.ccm -) - -if(NIHIL_TESTS) - enable_testing() - - add_executable(nihil.error.test - error.test.cc) - - target_link_libraries(nihil.error.test PRIVATE - nihil.std - nihil.core - nihil.error - Catch2::Catch2WithMain - ) - - include(CTest) - include(Catch) - catch_discover_tests(nihil.error.test) -endif() diff --git a/nihil.error/error.ccm b/nihil.error/error.ccm deleted file mode 100644 index 833f7f1..0000000 --- a/nihil.error/error.ccm +++ /dev/null @@ -1,322 +0,0 @@ -// This source code is released into the public domain. -export module nihil.error; - -// error: a type representing an error. -// -// An error consists of an immediate cause, which may be a string or -// std:error_code, and an optional proximate cause, which is another error -// object. Any number of error objects may be stacked. -// -// For example, a failure to open a file might be a stack of two errors: -// -// - string, "failed to open /etc/somefile", -// - std::error_code, "No such file or directory". -// -// Calling .str() will format the entire stack starting at that error, -// for example: "failed to open /etc/somefile: No such file or directory". -// -// Errors may be moved and (relatively) cheaply copied, since the cause -// chain is refcounted. -// -// error derives from std::exception, so it may be thrown and caught and -// provides a useful what(). When throwing errors, creating a derived -// error will make it easier to distinguish errors when catching them. - -import nihil.std; -import nihil.match; - -export import :sys_error; - -namespace nihil { - -// Things which can be errors. -using error_t = std::variant<std::monostate, std::string, std::error_code, std::error_condition>; - -// A proxy class used when constructing errors. This has implicit constructors from various types, -// which means we don't have to handle every possible combination of types in error itself. -export struct error_proxy -{ - // Construct from... - - // ... a string_view - error_proxy(std::string_view const what) // NOLINT - : m_error(std::string(what)) - { - } - - // ... an std::string; so we can move the string into place if it's an rvalue. - error_proxy(auto &&what) // NOLINT - requires(std::same_as<std::remove_cvref_t<decltype(what)>, std::string>) - : m_error(std::forward<decltype(what)>(what)) - { - } - - // ... a C string - error_proxy(char const *what) // NOLINT - : m_error(std::string(what)) - { - } - - // ... an std::error_code - error_proxy(std::error_code const what) // NOLINT - : m_error(what) - { - } - - // ... an std::error_condition - error_proxy(std::error_condition const what) // NOLINT - : m_error(what) - { - } - - // ... an error code enum - template <typename T> - requires(std::is_error_code_enum<T>::value) - error_proxy(T what) // NOLINT - : m_error(make_error_code(what)) - { - } - - // ... an error condition enum - template <typename T> - requires(std::is_error_condition_enum<T>::value) - error_proxy(T what) // NOLINT - : m_error(make_error_condition(what)) - { - } - - // Not copyable. - error_proxy(error_proxy const &) = delete; - auto operator=(error_proxy const &) -> error_proxy & = delete; - - // Not movable. - error_proxy(error_proxy &&) = delete; - auto operator=(error_proxy &&) -> error_proxy & = delete; - - ~error_proxy() = default; - - // Let error extract the error_t. - [[nodiscard]] auto error() && -> error_t && - { - return std::move(m_error); - } - -private: - // The error. - error_t m_error; -}; - -// The error class. -export struct error : std::exception -{ - // Create an empty error, representing success. - error() = default; - - // Destroy an error. - ~error() override = default; - - // Create an error from an error proxy. - explicit error(error_proxy &&proxy) - : m_error(std::move(std::move(proxy).error())) - { - } - - // Create an error from an error proxy and an error cause. - error(error_proxy &&proxy, error cause) - : m_error(std::move(std::move(proxy).error())) - , m_cause(std::make_shared<error>(std::move(cause))) - { - } - - // Create an error from an error proxy and an error_proxy cause. - // For example, error("cannot open file", std::errc::permission_denied). - error(error_proxy &&proxy, error_proxy &&cause) - : m_error(std::move(std::move(proxy).error())) - , m_cause(std::make_shared<error>(std::move(cause))) - { - } - - // Copyable. - error(error const &) = default; - auto operator=(error const &) -> error & = default; - - // Movable. - error(error &&) noexcept = default; - auto operator=(error &&) noexcept -> error & = default; - - // Return the cause of this error. - [[nodiscard]] auto cause(this error const &self) -> std::shared_ptr<error> const & - { - return self.m_cause; - } - - // Return the root cause of this error, which may be this object. - // For errors caused by an OS error, this will typically be the - // error_code error. - [[nodiscard]] auto root_cause(this error const &self) -> error const & - { - auto const *cause = &self; - while (cause->m_cause) - cause = cause->m_cause.get(); - return *cause; - } - - // Format this error without its cause as a string. - [[nodiscard]] auto this_str(this error const &self) -> std::string - { - return self.m_error | match { - [] (std::monostate) -> std::string { - return "No error"; - }, - [] (std::error_code const &m) { - return m.message(); - }, - [] (std::error_condition const &m) { - return m.message(); - }, - [] (std::string const &m) { - return m; - } - }; - } - - // Format this error and its cause as a string. - [[nodiscard]] auto full_str(this error const &self) -> std::string - { - auto str = self.this_str(); - - auto cause = self.cause(); - while (cause) { - str += ": " + cause->this_str(); - cause = cause->cause(); - } - - return str; - } - - // Return this error's error_code, if any. - [[nodiscard]] auto code(this error const &self) -> std::optional<std::error_code> - { - auto const *code = std::get_if<std::error_code>(&self.m_error); - if (code != nullptr) - return {*code}; - return {}; - } - - // Return this error's error_condition, if any. - [[nodiscard]] auto condition(this error const &self) -> std::optional<std::error_condition> - { - auto const *condition = std::get_if<std::error_condition>(&self.m_error); - if (condition != nullptr) - return {*condition}; - return {}; - } - - // Format this error and its cause as a C string and return it. This is for - // compatibility with std::exception. The lifetime of the returned string - // is the same as the error object. - [[nodiscard]] auto what() const noexcept -> char const * final - { - if (!m_what) - m_what = this->full_str(); - return m_what->c_str(); - } - - // Allow error to be implicitly converted to std::expected and std::unexpected, to make - // using it with std::expected easier. - - template <typename T> - operator std::expected<T, error> () && // NOLINT - { - return std::unexpected{std::move(*this)}; - } - - template<typename T> - operator std::expected<T, error> () const // NOLINT - { - return std::unexpected{*this}; - } - - operator std::unexpected<error> () && // NOLINT - { - return std::unexpected{std::move(*this)}; - } - - operator std::unexpected<error> () const // NOLINT - { - return std::unexpected{*this}; - } - -private: - // This error. - error_t m_error = make_error_code(std::errc()); - - // The cause of this error, if any. - std::shared_ptr<error> m_cause; - - // For std::exception::what(), we need to keep the string valid - // until we're destroyed. - mutable std::optional<std::string> m_what; - - // Equality comparison. - [[nodiscard]] friend auto operator==(error const &lhs, error const &rhs) -> bool - { - return lhs.m_error == rhs.m_error; - } - - [[nodiscard]] friend auto operator<=>(error const &lhs, error const &rhs) -> std::strong_ordering - { - return lhs.m_error <=> rhs.m_error; - } - - // Compare an error with an std::error_code. - [[nodiscard]] friend auto operator==(error const &lhs, std::error_code const &rhs) -> bool - { - return lhs.code() == rhs; - } - - // Compare an error to an std::error_condition. - [[nodiscard]] friend auto operator==(error const &lhs, std::error_condition const &rhs) -> bool - { - return lhs.condition() == rhs; - } - - // Compare an error to an std::error_code enum. - [[nodiscard]] friend auto operator==(error const &lhs, auto rhs) -> bool - requires(std::is_error_code_enum<decltype(rhs)>::value) - { - return lhs.code() == rhs; - } - - // Compare an error to an std::error_condition enum. - [[nodiscard]] friend auto operator==(error const &lhs, auto rhs) -> bool - requires(std::is_error_condition_enum<decltype(rhs)>::value) - { - return lhs.condition() == rhs; - } - - // Print an error to a stream. - friend auto operator<<(std::ostream &strm, error const &e) -> std::ostream & - { - return strm << e.full_str(); - } -}; - -} // namespace nihil - -// Make error formattable. -export template <> -struct std::formatter<nihil::error, char> -{ - template <typename ParseContext> - constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator - { - return ctx.begin(); - } - - template <typename FormatContext> - auto format(nihil::error const &e, FormatContext &ctx) const -> FormatContext::iterator - { - return std::ranges::copy(e.full_str(), ctx.out()).out; - } -}; diff --git a/nihil.error/error.test.cc b/nihil.error/error.test.cc deleted file mode 100644 index db6c773..0000000 --- a/nihil.error/error.test.cc +++ /dev/null @@ -1,273 +0,0 @@ -// This source code is released into the public domain. - -#include <catch2/catch_test_macros.hpp> - -import nihil.std; -import nihil.core; -import nihil.error; - -namespace { -inline constexpr auto *test_tags = "[nihil][nihil.error]"; - -TEST_CASE("error: invariants", test_tags) -{ - static_assert(std::destructible<nihil::error>); - static_assert(std::default_initializable<nihil::error>); - static_assert(std::move_constructible<nihil::error>); - static_assert(std::copy_constructible<nihil::error>); - static_assert(std::equality_comparable<nihil::error>); - static_assert(std::totally_ordered<nihil::error>); - static_assert(std::swappable<nihil::error>); - static_assert(std::regular<nihil::error>); -} - -SCENARIO("A nihil::error can be constructed from a C string", test_tags) -{ - GIVEN ("An error object constructed from a string") { - auto e = nihil::error("an error"); - - THEN ("full_str() should return the string") { - REQUIRE(e.full_str() == "an error"); - } - } -} - -SCENARIO("A nihil::error can be constructed from an std::string lvalue", test_tags) -{ - GIVEN ("An error object constructed from an std::string lvalue") { - auto s = std::string("an error"); - auto e = nihil::error(s); - - THEN ("full_str() should return the string") { - REQUIRE(e.full_str() == "an error"); - } - } -} - -SCENARIO("A nihil::error can be constructed from an std::string rvalue", test_tags) -{ - GIVEN ("An error object constructed from an std::string rvalue") { - auto e = nihil::error(std::string("an error")); - - THEN ("full_str() should return the string") { - REQUIRE(e.full_str() == "an error"); - } - } -} - -SCENARIO("A nihil::error can be constructed from an std::string_view lvalue", test_tags) -{ - GIVEN ("An error object constructed from an std::string_view lvalue") { - auto s = std::string_view("an error"); - auto e = nihil::error(s); - - THEN ("full_str() should return the string") { - REQUIRE(e.full_str() == "an error"); - } - } -} - -SCENARIO("A nihil::error can be constructed from an std::string_view rvalue", test_tags) -{ - GIVEN ("An error object constructed from an std::string_view rvalue") { - auto e = nihil::error(std::string_view("an error")); - - THEN ("full_str() should return the string") { - REQUIRE(e.full_str() == "an error"); - } - } -} - -SCENARIO("A nihil::error can be constructed from an std::error_condition", test_tags) -{ - GIVEN ("An error object constructed from std::errc::invalid_argument") { - auto e = nihil::error(std::error_condition(std::errc::invalid_argument)); - - THEN ("full_str() should return the string") { - REQUIRE(e.full_str() == "Invalid argument"); - } - - AND_THEN ("condition() should return the error code") { - REQUIRE(e.condition().has_value()); - REQUIRE(*e.condition() == std::errc::invalid_argument); - } - - AND_THEN ("The error should be comparable to the error code") { - REQUIRE(e == std::errc::invalid_argument); - } - } -} - -SCENARIO("A nihil::error can be constructed from an std::errc", test_tags) -{ - GIVEN ("An error object constructed from std::errc::invalid_argument") { - auto e = nihil::error(std::errc::invalid_argument); - - THEN ("full_str() should return the string") { - REQUIRE(e.full_str() == "Invalid argument"); - } - - AND_THEN ("condition() should return the error code") { - REQUIRE(e.condition().has_value()); - REQUIRE(*e.condition() == std::errc::invalid_argument); - } - - AND_THEN ("The error should be comparable to the error code") { - REQUIRE(e == std::errc::invalid_argument); - } - } -} - -SCENARIO("A nihil::error can be constructed from a nihil::errc", test_tags) -{ - GIVEN ("An error object constructed from std::errc::invalid_argument") { - auto e = nihil::error(nihil::errc::incomplete_command); - - THEN ("full_str() should return the string") { - REQUIRE(e.full_str() == "Incomplete command"); - } - - AND_THEN ("condition() should return the error code") { - REQUIRE(e.condition().has_value()); - REQUIRE(*e.condition() == nihil::errc::incomplete_command); - } - - AND_THEN ("The error should be comparable to the error code") { - REQUIRE(e == nihil::errc::incomplete_command); - } - } -} - -SCENARIO("A nihil::error can be constructed with a cause", test_tags) -{ - GIVEN ("An error object constructed with a cause") { - auto e = nihil::error("an error", std::errc::invalid_argument); - - THEN ("full_str() should return the string") { - REQUIRE(e.full_str() == "an error: Invalid argument"); - } - - AND_THEN ("cause() should return the cause") { - REQUIRE(e.cause()); - REQUIRE(*e.cause() == std::errc::invalid_argument); - } - } -} - -SCENARIO("std::format with a nihil::error", test_tags) -{ - GIVEN ("A nihil::error with no cause") { - auto e = nihil::error("an error"); - - THEN ("std::format should return the string") { - REQUIRE(std::format("{}", e) == "an error"); - } - - AND_THEN ("std::format should return the same as full_str()") { - REQUIRE(std::format("{}", e) == e.full_str()); - } - } - - GIVEN ("A nihil::error with a cause") { - auto e = nihil::error("an error", std::errc::invalid_argument); - - THEN ("std::format should return the string") { - REQUIRE(std::format("{}", e) == "an error: Invalid argument"); - } - - AND_THEN ("std::format should return the same as full_str()") { - REQUIRE(std::format("{}", e) == e.full_str()); - } - } -} - -SCENARIO("Print a nihil::error to an std::ostream", test_tags) -{ - GIVEN ("A nihil::error with no cause") { - auto e = nihil::error("an error"); - - THEN ("The error should be printed to the stream") { - auto ss = std::stringstream(); - ss << e; - REQUIRE(ss.str() == "an error"); - } - } - - GIVEN ("A nihil::error with a cause") { - auto e = nihil::error("an error", std::errc::invalid_argument); - - THEN ("The error should be printed to the stream") { - auto ss = std::stringstream(); - ss << e; - REQUIRE(ss.str() == "an error: Invalid argument"); - } - } -} - -SCENARIO("Comparison of nihil::error with operator==", test_tags) -{ - GIVEN ("Two nihil::error objects constructed from the same string") { - auto e1 = nihil::error("an error"); - auto e2 = nihil::error("an error"); - - THEN ("The two objects should be equal") { - REQUIRE(e1 == e2); - } - } - - GIVEN ("Two nihil::error objects constructed from different strings") { - auto e1 = nihil::error("an error"); - auto e2 = nihil::error("another error"); - - THEN ("The two objects should not be equal") { - REQUIRE(e1 != e2); - } - } - - GIVEN ("Two nihil::error objects constructed from the same error code") { - auto e1 = nihil::error(std::errc::invalid_argument); - auto e2 = nihil::error(std::errc::invalid_argument); - - THEN ("The two objects should be equal") { - REQUIRE(e1 == e2); - } - } - - GIVEN ("Two nihil::error objects constructed from different error codes") { - auto e1 = nihil::error(std::errc::invalid_argument); - auto e2 = nihil::error(std::errc::permission_denied); - - THEN ("The two objects should not be equal") { - REQUIRE(e1 != e2); - } - } -} - -SCENARIO("Comparison of nihil::error with operator<", test_tags) -{ - GIVEN ("Two nihil::error objects constructed from the same string") { - auto e1 = nihil::error("aaa"); - auto e2 = nihil::error("zzz"); - - THEN ("aaa should be less than zzz") { - REQUIRE(e1 < e2); - } - } -} - -SCENARIO("Throwing and catching a nihil::error object", test_tags) -{ - GIVEN ("A nihil::error object") { - THEN ("We should be able to throw and catch the error") { - REQUIRE_THROWS_AS(throw nihil::error("an error"), nihil::error); - - try { - throw nihil::error("an error"); - } catch (nihil::error const &e) { - REQUIRE(e.full_str() == "an error"); - }; - } - } -} - -} // anonymous namespace diff --git a/nihil.error/sys_error.ccm b/nihil.error/sys_error.ccm deleted file mode 100644 index 102f4c5..0000000 --- a/nihil.error/sys_error.ccm +++ /dev/null @@ -1,18 +0,0 @@ -// This source code is released into the public domain. -module; - -#include <cerrno> - -export module nihil.error:sys_error; - -import nihil.std; - -namespace nihil { - -// Allow access to errno without having to include <cerrno>. -export [[nodiscard]] auto sys_error() -> std::errc -{ - return static_cast<std::errc>(errno); -} - -} // namespace nihil |
