blob: 9880fc7bc00b7982a6dac070d47a4d2fc90abcbc [file] [log] [blame]
James Kuszmaul847927d2024-05-23 15:33:18 -07001#ifndef AOS_UTIL_STATUS_H_
2#define AOS_UTIL_STATUS_H_
3#include <optional>
4#include <source_location>
5#include <string_view>
6
Austin Schuh99f7c6a2024-06-25 22:07:44 -07007#include "absl/log/log.h"
James Kuszmaul847927d2024-05-23 15:33:18 -07008#include "absl/strings/str_format.h"
James Kuszmaul847927d2024-05-23 15:33:18 -07009#include "tl/expected.hpp"
10
11#include "aos/containers/inlined_vector.h"
12
13namespace aos {
James Kuszmaul053d42a2024-05-30 16:46:08 -070014// The Error class provides a means by which errors can be readily returned
James Kuszmaul847927d2024-05-23 15:33:18 -070015// from methods. It will typically be wrapped by an std::expected<> to
James Kuszmaul053d42a2024-05-30 16:46:08 -070016// accommodate a return value or the Error.
James Kuszmaul847927d2024-05-23 15:33:18 -070017//
James Kuszmaul053d42a2024-05-30 16:46:08 -070018// 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 Kuszmaul847927d2024-05-23 15:33:18 -070023// 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 Kuszmaul053d42a2024-05-30 16:46:08 -070026// 2. Providing an interface that allows for Error implementations that expose
James Kuszmaul847927d2024-05-23 15:33:18 -070027// 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 Kuszmaul053d42a2024-05-30 16:46:08 -070032// 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 Kuszmaul847927d2024-05-23 15:33:18 -070034//
James Kuszmaul053d42a2024-05-30 16:46:08 -070035// The goal of this class is that it should be easy to convert from existing
James Kuszmaul847927d2024-05-23 15:33:18 -070036// error types (absl::Status, std::error_code) to this type.
James Kuszmaul053d42a2024-05-30 16:46:08 -070037//
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.
44class Error {
James Kuszmaul847927d2024-05-23 15:33:18 -070045 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 Kuszmaul053d42a2024-05-30 16:46:08 -070050
James Kuszmaul847927d2024-05-23 15:33:18 -070051 // 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 Kuszmaul053d42a2024-05-30 16:46:08 -070063
James Kuszmaul847927d2024-05-23 15:33:18 -070064 // 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 Kuszmaul053d42a2024-05-30 16:46:08 -070068 // Error::Error() method. This should only be overridden by wrappers that
James Kuszmaul847927d2024-05-23 15:33:18 -070069 // want to present a fancier interface to users.
James Kuszmaul053d42a2024-05-30 16:46:08 -070070 static Error MakeError(
James Kuszmaul847927d2024-05-23 15:33:18 -070071 std::string_view message,
72 std::source_location source_location = std::source_location::current()) {
James Kuszmaul053d42a2024-05-30 16:46:08 -070073 return Error(StatusCode::kError, message, std::move(source_location));
James Kuszmaul847927d2024-05-23 15:33:18 -070074 }
James Kuszmaul053d42a2024-05-30 16:46:08 -070075 static tl::unexpected<Error> MakeUnexpectedError(
James Kuszmaul847927d2024-05-23 15:33:18 -070076 std::string_view message,
77 std::source_location source_location = std::source_location::current()) {
James Kuszmaul053d42a2024-05-30 16:46:08 -070078 return tl::unexpected<Error>(
79 MakeError(message, std::move(source_location)));
James Kuszmaul847927d2024-05-23 15:33:18 -070080 }
James Kuszmaul053d42a2024-05-30 16:46:08 -070081
James Kuszmaul847927d2024-05-23 15:33:18 -070082 // 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 Kuszmaul053d42a2024-05-30 16:46:08 -070085 // literals (e.g., Error::StringLiteralError("Hello, World!")).
James Kuszmaul847927d2024-05-23 15:33:18 -070086 // The default source_location will correspond to the call-site of the
James Kuszmaul053d42a2024-05-30 16:46:08 -070087 // Error::Error() method. This should only be overridden by wrappers that
James Kuszmaul847927d2024-05-23 15:33:18 -070088 // want to present a fancier interface to users.
James Kuszmaul053d42a2024-05-30 16:46:08 -070089 static Error MakeStringLiteralError(
James Kuszmaul847927d2024-05-23 15:33:18 -070090 const char *message,
91 std::source_location source_location = std::source_location::current()) {
James Kuszmaul053d42a2024-05-30 16:46:08 -070092 return Error(StatusCode::kError, message, std::move(source_location));
James Kuszmaul847927d2024-05-23 15:33:18 -070093 }
James Kuszmaul053d42a2024-05-30 16:46:08 -070094 static tl::unexpected<Error> MakeUnexpectedStringLiteralError(
James Kuszmaul847927d2024-05-23 15:33:18 -070095 const char *message,
96 std::source_location source_location = std::source_location::current()) {
James Kuszmaul053d42a2024-05-30 16:46:08 -070097 return tl::unexpected<Error>(
98 MakeStringLiteralError(message, std::move(source_location)));
James Kuszmaul847927d2024-05-23 15:33:18 -070099 }
100
James Kuszmaul053d42a2024-05-30 16:46:08 -0700101 Error(Error &&other);
102 Error &operator=(Error &&other);
103 Error(const Error &other);
James Kuszmaul847927d2024-05-23 15:33:18 -0700104
James Kuszmaul847927d2024-05-23 15:33:18 -0700105 // 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 Kuszmaul053d42a2024-05-30 16:46:08 -0700110 // Returns the source_location attached to the current Error. If the
James Kuszmaul847927d2024-05-23 15:33:18 -0700111 // 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 Kuszmaul053d42a2024-05-30 16:46:08 -0700121 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 Kuszmaul847927d2024-05-23 15:33:18 -0700125
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 Kuszmaul053d42a2024-05-30 16:46:08 -0700132template <typename T>
133using Result = tl::expected<T, Error>;
134
James Kuszmaul847927d2024-05-23 15:33:18 -0700135// Dies fatally if the provided expected does not include the value T, printing
James Kuszmaul053d42a2024-05-30 16:46:08 -0700136// out an error message that includes the Error on the way out.
James Kuszmaul847927d2024-05-23 15:33:18 -0700137// Returns the stored value on success.
138template <typename T>
James Kuszmaul053d42a2024-05-30 16:46:08 -0700139T CheckExpected(const Result<T> &expected) {
James Kuszmaul847927d2024-05-23 15:33:18 -0700140 if (expected.has_value()) {
141 return expected.value();
142 }
143 LOG(FATAL) << expected.error().ToString();
144}
145
146template <>
James Kuszmaul053d42a2024-05-30 16:46:08 -0700147void CheckExpected<void>(const Result<void> &expected);
148
149int ResultExitCode(const Result<void> &expected);
James Kuszmaul847927d2024-05-23 15:33:18 -0700150} // namespace aos
151#endif // AOS_UTIL_STATUS_H_