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