blob: 314936b5db5600909ec1c6fc000f615d09d2efa6 [file] [log] [blame]
#ifndef AOS_UTIL_STATUS_H_
#define AOS_UTIL_STATUS_H_
#include <optional>
#include <source_location>
#include <string_view>
#include "absl/strings/str_format.h"
#include "glog/logging.h"
#include "tl/expected.hpp"
#include "aos/containers/inlined_vector.h"
namespace aos {
// The Status class provides a means by which errors can be readily returned
// from methods. It will typically be wrapped by an std::expected<> to
// accommodate a return value or the Status, although an "ok" status can also be
// used to indicate no-error.
//
// The Status class is similar to the absl::Status or std::error_code classes,
// in that it consists of an integer error code of some sort (where 0 indicates
// "ok") and a string error message of some sort. The main additions of this
// class are:
// 1. Adding a first-class exposure of an std::source_location to make exposure
// of the sources of errors easier.
// 2. Providing an interface that allows for Status implementations that expose
// messages without malloc'ing (not possible with absl::Status, although it
// is possible with std::error_code).
// 3. Making it relatively easy to quickly return a simple error & message
// (specifying a custom error with std::error_code is possible but requires
// jumping through hoops and managing some global state).
//
// The goal of this class is that it should be easy to convert from exiting
// error types (absl::Status, std::error_code) to this type.
class Status {
public:
// In order to allow simple error messages without memory allocation, we
// reserve a small amount of stack space for error messages. This constant
// specifies the length of these strings.
static constexpr size_t kStaticMessageLength = 128;
// Attaches human-readable status enums to integer codes---the specific
// numeric codes are used as exit codes when terminating execution of the
// program.
// Note: While 0 will always indicate success and non-zero values will always
// indicate failures we may attempt to further expand the set of non-zero exit
// codes in the future and may decide to reuse 1 for a more specific error
// code at the time (although it is reasonably likely that it will be kept as
// a catch-all general error).
enum class StatusCode : int {
kOk = 0,
kError = 1,
};
// Constructs a status that indicates success, with no associated error
// message our source location.
static Status Ok() { return Status(StatusCode::kOk, "", std::nullopt); }
// Constructs an Error, copying the provided message. If the message is
// shorter than kStaticMessageLength, then the message will be stored entirely
// on the stack; longer messages will require dynamic memory allocation.
// The default source_location will correspond to the call-site of the
// Status::Error() method. This should only be overridden by wrappers that
// want to present a fancier interface to users.
static Status Error(
std::string_view message,
std::source_location source_location = std::source_location::current()) {
return Status(StatusCode::kError, message, std::move(source_location));
}
static tl::unexpected<Status> UnexpectedError(
std::string_view message,
std::source_location source_location = std::source_location::current()) {
return tl::unexpected<Status>(Error(message, std::move(source_location)));
}
// Constructs an error, retaining the provided pointer to a null-terminated
// error message. It is assumed that the message pointer will stay valid
// ~indefinitely. This is generally only appropriate to use with string
// literals (e.g., Status::StringLiteralError("Hello, World!")).
// The default source_location will correspond to the call-site of the
// Status::Error() method. This should only be overridden by wrappers that
// want to present a fancier interface to users.
static Status StringLiteralError(
const char *message,
std::source_location source_location = std::source_location::current()) {
return Status(StatusCode::kError, message, std::move(source_location));
}
static tl::unexpected<Status> UnexpectedStringLiteralError(
const char *message,
std::source_location source_location = std::source_location::current()) {
return tl::unexpected<Status>(
StringLiteralError(message, std::move(source_location)));
}
Status(Status &&other);
Status &operator=(Status &&other);
Status(const Status &other);
// Returns true if the Status indicates success.
[[nodiscard]] bool ok() const { return code_ == StatusCode::kOk; }
// Returns a numeric value for the status code. Zero will always indicate
// success; non-zero values will always indicate an error.
[[nodiscard]] int code() const { return static_cast<int>(code_); }
// Returns a view of the error message.
[[nodiscard]] std::string_view message() const { return message_; }
// Returns the source_location attached to the current Status. If the
// source_location was never set, will return nullopt. The source_location
// will typically be left unset for successful ("ok") statuses.
[[nodiscard]] const std::optional<std::source_location> &source_location()
const {
return source_location_;
}
std::string ToString() const;
private:
Status(StatusCode code, std::string_view message,
std::optional<std::source_location> source_location);
Status(StatusCode code, const char *message,
std::optional<std::source_location> source_location);
// Constructs a string view from the provided buffer if it has data and
// otherwise uses the provided string view. Used in copy/move constructors to
// figure out whether we should use the buffer or keep the pointer to the
// existing std::string_view (as is the case for when we store a pointer to a
// string literal).
static std::string_view MakeStringViewFromBufferOrView(
const aos::InlinedVector<char, kStaticMessageLength> &buffer,
const std::string_view &view) {
return (buffer.size() > 0) ? std::string_view(buffer.begin(), buffer.end())
: view;
}
StatusCode code_;
aos::InlinedVector<char, kStaticMessageLength> owned_message_;
std::string_view message_;
std::optional<std::source_location> source_location_;
};
// Dies fatally if the provided expected does not include the value T, printing
// out an error message that includes the Status on the way out.
// Returns the stored value on success.
template <typename T>
T CheckExpected(const tl::expected<T, Status> &expected) {
if (expected.has_value()) {
return expected.value();
}
LOG(FATAL) << expected.error().ToString();
}
template <>
void CheckExpected<void>(const tl::expected<void, Status> &expected);
} // namespace aos
#endif // AOS_UTIL_STATUS_H_