aboutsummaryrefslogtreecommitdiffstats
path: root/nihil.error/error.ccm
diff options
context:
space:
mode:
authorLexi Winter <lexi@le-fay.org>2025-06-28 19:25:55 +0100
committerLexi Winter <lexi@le-fay.org>2025-06-28 19:25:55 +0100
commita2d7181700ac64b8e7a4472ec26dfa253b38f188 (patch)
tree23c5a9c8ec4089ac346e2e0f9391909c3089b66b /nihil.error/error.ccm
parentf226d46ee02b57dd76a4793593aa8d66e1c58353 (diff)
downloadnihil-a2d7181700ac64b8e7a4472ec26dfa253b38f188.tar.gz
nihil-a2d7181700ac64b8e7a4472ec26dfa253b38f188.tar.bz2
split nihil into separate modules
Diffstat (limited to 'nihil.error/error.ccm')
-rw-r--r--nihil.error/error.ccm199
1 files changed, 199 insertions, 0 deletions
diff --git a/nihil.error/error.ccm b/nihil.error/error.ccm
new file mode 100644
index 0000000..b624184
--- /dev/null
+++ b/nihil.error/error.ccm
@@ -0,0 +1,199 @@
+/*
+ * This source code is released into the public domain.
+ */
+
+module;
+
+/*
+ * 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.
+ */
+
+#include <iosfwd>
+#include <format>
+#include <memory>
+#include <optional>
+#include <string>
+#include <system_error>
+#include <utility>
+#include <variant>
+
+export module nihil.error;
+
+namespace nihil {
+
+// Things which can be errors.
+using error_t = std::variant<
+ std::monostate,
+ std::string,
+ std::error_code,
+ std::error_condition
+ >;
+
+export struct error : std::exception {
+ // Create an empty error, representing success.
+ error();
+
+ // Destroy an error.
+ virtual ~error();
+
+ // Create an error from a freeform string.
+ error(std::string_view what, error cause);
+ explicit error(std::string_view what);
+
+ template<typename Cause>
+ requires(std::is_error_code_enum<Cause>::value ||
+ std::is_error_condition_enum<Cause>::value)
+ error(std::string_view what, Cause &&cause)
+ : error(what, error(std::forward<Cause>(cause)))
+ {}
+
+ // Create an error from an std::error_code.
+ error(std::error_condition what, error cause);
+ explicit error(std::error_condition what);
+
+ // Create an error from an std::error_condition.
+ error(std::error_code what, error cause);
+ explicit error(std::error_code what);
+
+ // Create an error from an std::error_code enum.
+ error(auto errc, error cause)
+ requires(std::is_error_code_enum<decltype(errc)>::value)
+ : error(make_error_code(errc), std::move(cause))
+ {}
+
+ explicit error(auto errc)
+ requires(std::is_error_code_enum<decltype(errc)>::value)
+ : error(make_error_code(errc))
+ {}
+
+ // Create an error from an std::error_condition enum.
+ error(auto errc, error cause)
+ requires(std::is_error_condition_enum<decltype(errc)>::value)
+ : error(make_error_condition(errc), std::move(cause))
+ {}
+
+ explicit error(auto errc)
+ requires(std::is_error_condition_enum<decltype(errc)>::value)
+ : error(make_error_condition(errc))
+ {}
+
+ error(error const &);
+ error(error &&) noexcept;
+
+ auto operator=(this error &, error const &) -> error &;
+ auto operator=(this error &, error &&) noexcept -> error &;
+
+ // Return the cause of this error.
+ [[nodiscard]] auto cause(this error const &) -> std::shared_ptr<error>;
+
+ // 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 &) -> error const &;
+
+ // Format this error as a string.
+ [[nodiscard]] auto str(this error const &) -> std::string;
+
+ // Return this error's error_code, if any.
+ [[nodiscard]] auto code(this error const &)
+ -> std::optional<std::error_code>;
+
+ // Return this error's error_condition, if any.
+ [[nodiscard]] auto condition(this error const &)
+ -> std::optional<std::error_condition>;
+
+ [[nodiscard]] auto what() const noexcept -> char const * final;
+
+private:
+ friend auto operator==(error const &, error const &) -> bool;
+ friend auto operator<=>(error const &, error const &)
+ -> std::strong_ordering;
+
+ // 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;
+};
+
+/*
+ * Format an error and its cause(s) as a string.
+ */
+export [[nodiscard]] auto to_string(error const &) -> std::string;
+
+// Compare an error to another error. This only compares the error itself,
+// not any nested causes.
+export [[nodiscard]] auto operator==(error const &, error const &)
+ -> bool;
+export [[nodiscard]] auto operator<=>(error const &, error const &)
+ -> std::strong_ordering;
+
+// Compare an error to an std::error_code.
+export [[nodiscard]] auto operator==(error const &, std::error_code const &)
+ -> bool;
+
+// Compare an error to an std::error_condition.
+export [[nodiscard]] auto operator==(error const &,
+ std::error_condition const &)
+ -> bool;
+
+// Compare an error to an std::error_code enum.
+export [[nodiscard]] 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.
+export [[nodiscard]] 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 an ostream.
+export [[nodiscard]] auto operator<<(std::ostream &, error const &)
+ -> std::ostream &;
+
+} // 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(to_string(e), ctx.out()).out;
+ }
+};