James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 1 | #ifndef AOS_UTIL_STATUS_H_ |
| 2 | #define AOS_UTIL_STATUS_H_ |
| 3 | #include <optional> |
| 4 | #include <source_location> |
| 5 | #include <string_view> |
| 6 | |
Austin Schuh | 99f7c6a | 2024-06-25 22:07:44 -0700 | [diff] [blame] | 7 | #include "absl/log/log.h" |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 8 | #include "absl/strings/str_format.h" |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 9 | #include "tl/expected.hpp" |
| 10 | |
| 11 | #include "aos/containers/inlined_vector.h" |
| 12 | |
| 13 | namespace aos { |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 14 | // The Error class provides a means by which errors can be readily returned |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 15 | // from methods. It will typically be wrapped by an std::expected<> to |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 16 | // accommodate a return value or the Error. |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 17 | // |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 18 | // The Error class is similar to the absl::Status or std::error_code classes, |
| 19 | // in that it consists of an integer error code of some sort (where 0 implicitly |
| 20 | // would indicate "ok", although we assume that if there is no error then |
| 21 | // you will be using an expected<> to return void or your actual return type) |
| 22 | // and a string error message of some sort. The main additions of this |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 23 | // class are: |
| 24 | // 1. Adding a first-class exposure of an std::source_location to make exposure |
| 25 | // of the sources of errors easier. |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 26 | // 2. Providing an interface that allows for Error implementations that expose |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 27 | // messages without malloc'ing (not possible with absl::Status, although it |
| 28 | // is possible with std::error_code). |
| 29 | // 3. Making it relatively easy to quickly return a simple error & message |
| 30 | // (specifying a custom error with std::error_code is possible but requires |
| 31 | // jumping through hoops and managing some global state). |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 32 | // 4. Does not support an "okay" state, to make it clear that the user is |
| 33 | // supposed to use a wrapper that will itself indicate okay. |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 34 | // |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 35 | // The goal of this class is that it should be easy to convert from existing |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 36 | // error types (absl::Status, std::error_code) to this type. |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 37 | // |
| 38 | // Users should typically use the Result<T> convenience method when returning |
| 39 | // Errors from methods. In the case where the method would normally return void, |
| 40 | // use Result<void>. Result<> is just a wrapper for tl::expected; when our |
| 41 | // compilers upgrade to support std::expected this should ease the transition, |
| 42 | // in addition to just providing a convenience wrapper to encourage a standard |
| 43 | // pattern of use. |
| 44 | class Error { |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 45 | public: |
| 46 | // In order to allow simple error messages without memory allocation, we |
| 47 | // reserve a small amount of stack space for error messages. This constant |
| 48 | // specifies the length of these strings. |
| 49 | static constexpr size_t kStaticMessageLength = 128; |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 50 | |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 51 | // Attaches human-readable status enums to integer codes---the specific |
| 52 | // numeric codes are used as exit codes when terminating execution of the |
| 53 | // program. |
| 54 | // Note: While 0 will always indicate success and non-zero values will always |
| 55 | // indicate failures we may attempt to further expand the set of non-zero exit |
| 56 | // codes in the future and may decide to reuse 1 for a more specific error |
| 57 | // code at the time (although it is reasonably likely that it will be kept as |
| 58 | // a catch-all general error). |
| 59 | enum class StatusCode : int { |
| 60 | kOk = 0, |
| 61 | kError = 1, |
| 62 | }; |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 63 | |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 64 | // Constructs an Error, copying the provided message. If the message is |
| 65 | // shorter than kStaticMessageLength, then the message will be stored entirely |
| 66 | // on the stack; longer messages will require dynamic memory allocation. |
| 67 | // The default source_location will correspond to the call-site of the |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 68 | // Error::Error() method. This should only be overridden by wrappers that |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 69 | // want to present a fancier interface to users. |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 70 | static Error MakeError( |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 71 | std::string_view message, |
| 72 | std::source_location source_location = std::source_location::current()) { |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 73 | return Error(StatusCode::kError, message, std::move(source_location)); |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 74 | } |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 75 | static tl::unexpected<Error> MakeUnexpectedError( |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 76 | std::string_view message, |
| 77 | std::source_location source_location = std::source_location::current()) { |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 78 | return tl::unexpected<Error>( |
| 79 | MakeError(message, std::move(source_location))); |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 80 | } |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 81 | |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 82 | // Constructs an error, retaining the provided pointer to a null-terminated |
| 83 | // error message. It is assumed that the message pointer will stay valid |
| 84 | // ~indefinitely. This is generally only appropriate to use with string |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 85 | // literals (e.g., Error::StringLiteralError("Hello, World!")). |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 86 | // The default source_location will correspond to the call-site of the |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 87 | // Error::Error() method. This should only be overridden by wrappers that |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 88 | // want to present a fancier interface to users. |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 89 | static Error MakeStringLiteralError( |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 90 | const char *message, |
| 91 | std::source_location source_location = std::source_location::current()) { |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 92 | return Error(StatusCode::kError, message, std::move(source_location)); |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 93 | } |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 94 | static tl::unexpected<Error> MakeUnexpectedStringLiteralError( |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 95 | const char *message, |
| 96 | std::source_location source_location = std::source_location::current()) { |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 97 | return tl::unexpected<Error>( |
| 98 | MakeStringLiteralError(message, std::move(source_location))); |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 99 | } |
| 100 | |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 101 | Error(Error &&other); |
| 102 | Error &operator=(Error &&other); |
| 103 | Error(const Error &other); |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 104 | |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 105 | // Returns a numeric value for the status code. Zero will always indicate |
| 106 | // success; non-zero values will always indicate an error. |
| 107 | [[nodiscard]] int code() const { return static_cast<int>(code_); } |
| 108 | // Returns a view of the error message. |
| 109 | [[nodiscard]] std::string_view message() const { return message_; } |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 110 | // Returns the source_location attached to the current Error. If the |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 111 | // source_location was never set, will return nullopt. The source_location |
| 112 | // will typically be left unset for successful ("ok") statuses. |
| 113 | [[nodiscard]] const std::optional<std::source_location> &source_location() |
| 114 | const { |
| 115 | return source_location_; |
| 116 | } |
| 117 | |
| 118 | std::string ToString() const; |
| 119 | |
| 120 | private: |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 121 | Error(StatusCode code, std::string_view message, |
| 122 | std::optional<std::source_location> source_location); |
| 123 | Error(StatusCode code, const char *message, |
| 124 | std::optional<std::source_location> source_location); |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 125 | |
| 126 | StatusCode code_; |
| 127 | aos::InlinedVector<char, kStaticMessageLength> owned_message_; |
| 128 | std::string_view message_; |
| 129 | std::optional<std::source_location> source_location_; |
| 130 | }; |
| 131 | |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 132 | template <typename T> |
| 133 | using Result = tl::expected<T, Error>; |
| 134 | |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 135 | // Dies fatally if the provided expected does not include the value T, printing |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 136 | // out an error message that includes the Error on the way out. |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 137 | // Returns the stored value on success. |
| 138 | template <typename T> |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 139 | T CheckExpected(const Result<T> &expected) { |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 140 | if (expected.has_value()) { |
| 141 | return expected.value(); |
| 142 | } |
| 143 | LOG(FATAL) << expected.error().ToString(); |
| 144 | } |
| 145 | |
| 146 | template <> |
James Kuszmaul | 053d42a | 2024-05-30 16:46:08 -0700 | [diff] [blame] | 147 | void CheckExpected<void>(const Result<void> &expected); |
| 148 | |
| 149 | int ResultExitCode(const Result<void> &expected); |
James Kuszmaul | 847927d | 2024-05-23 15:33:18 -0700 | [diff] [blame] | 150 | } // namespace aos |
| 151 | #endif // AOS_UTIL_STATUS_H_ |