/* * 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 #include #include #include #include #include #include #include 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. ~error() override; // Create an error from a freeform string. error(std::string_view what, error cause); explicit error(std::string_view what); template requires(std::is_error_code_enum::value || std::is_error_condition_enum::value) error(std::string_view what, Cause &&cause) : error(what, error(std::forward(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::value) : error(make_error_code(errc), std::move(cause)) {} explicit error(auto errc) requires(std::is_error_code_enum::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::value) : error(make_error_condition(errc), std::move(cause)) {} explicit error(auto errc) requires(std::is_error_condition_enum::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; // 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; // Return this error's error_condition, if any. [[nodiscard]] auto condition(this error const &) -> std::optional; [[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 m_cause; // For std::exception::what(), we need to keep the string valid // until we're destroyed. mutable std::optional 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::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::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 { template constexpr auto parse(ParseContext &ctx) -> ParseContext::iterator { return ctx.begin(); } template auto format(nihil::error const &e, FormatContext &ctx) const -> FormatContext::iterator { return std::ranges::copy(to_string(e), ctx.out()).out; } };