aboutsummaryrefslogtreecommitdiffstats
path: root/nihil.error
diff options
context:
space:
mode:
Diffstat (limited to 'nihil.error')
-rw-r--r--nihil.error/CMakeLists.txt27
-rw-r--r--nihil.error/error.ccm322
-rw-r--r--nihil.error/error.test.cc273
-rw-r--r--nihil.error/sys_error.ccm18
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