blob: e59e71252132285bc089b5079e5f76d74235cc93 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file
#ifndef COBALT_SRC_LIB_UTIL_STATUS_BUILDER_H_
#define COBALT_SRC_LIB_UTIL_STATUS_BUILDER_H_
#include <string>
#include "src/lib/util/source_location.h"
#include "src/pb/event.pb.h"
#include "src/public/lib/status.h"
#include "src/public/lib/status_codes.h"
#include "src/registry/metric_definition.pb.h"
#include "src/registry/report_definition.pb.h"
namespace cobalt::util {
// virtual base ContextFormatter, that will be specialized for various types.
// If there is no specialized implementation for a given type, a custom formatter isn't present, and
// can't be used as an argument to WithContexts.
template <typename T>
class ContextFormatter {
virtual ~ContextFormatter() = default;
// Return the default key to use if passed to WithContexts (should be inline and static if
// possible)
virtual std::string default_key() = 0;
// Write the value to the given ostream (should be inline)
virtual void write_value(std::ostream& stream) = 0;
};
class [[nodiscard]] StatusBuilder {
public:
explicit StatusBuilder() = delete;
~StatusBuilder() = default;
// Create a StatusBuilder based on |original_status|. Used to add extra contextual information to
// the status.
explicit StatusBuilder(const Status& original_status,
util::SourceLocation location = util::SourceLocation::current());
// Create a StatusBuilder for a given |status_code|.
explicit StatusBuilder(StatusCode status_code,
util::SourceLocation location = util::SourceLocation::current());
// Create a StatusBuilder for a given |status_code| with an |initial message|.
StatusBuilder(StatusCode status_code, const std::string& initial_message,
util::SourceLocation location = util::SourceLocation::current());
// Make StatusBuilder move-only.
StatusBuilder(const StatusBuilder& sb) = delete;
StatusBuilder& operator=(const StatusBuilder& sb) = delete;
StatusBuilder(StatusBuilder&&) = default;
StatusBuilder& operator=(StatusBuilder&&) = default;
// Converts the |StatusBuilder| into a |Status| object.
Status Build();
StatusBuilder& SetCode(StatusCode status_code) & {
code_ = status_code;
return *this;
}
// "Owned" (rvalue) version of SetCode that doesn}'t convert StatusBuilder into a reference
// (lvalue)
[[nodiscard]] StatusBuilder&& SetCode(StatusCode status_code) && {
return std::move(SetCode(status_code));
}
// LogLevel is used to denote where the status should be logged on construction.
enum class LogLevel {
INFO,
WARNING,
ERROR,
NO_LOGGING,
};
// These methods define where the status should be logged on construction. If
// none of these methods are called, or |LogLevel::NO_LOGGING| is provided to
// the |Log| method, nothing will be logged when the |StatusBuilder| is
// converted into a |Status|.
StatusBuilder& Log(LogLevel level) & {
log_level_ = level;
return *this;
}
// "Owned" (rvalue) version of Log that does not convert StatusBuilder to a reference (lvalue)
[[nodiscard]] StatusBuilder&& Log(LogLevel level) && { return std::move(Log(level)); }
// When the |Status| object is created, send it to LOG(ERROR).
StatusBuilder& LogError() & { return Log(LogLevel::ERROR); }
// "Owned" (rvalue) version of LogError that does not convert StatusBuilder to a reference
// (lvalue)
[[nodiscard]] StatusBuilder&& LogError() && { return std::move(LogError()); }
// When the |Status| object is created, send it to LOG(WARNING)
StatusBuilder& LogWarning() & { return Log(LogLevel::WARNING); }
// "Owned" (rvalue) version of LogWarning that does not convert StatusBuilder to a reference
// (lvalue)
[[nodiscard]] StatusBuilder&& LogWarning() && { return std::move(LogWarning()); }
// When the |Status| object is created, send it to LOG(INFO)
StatusBuilder& LogInfo() & { return Log(LogLevel::INFO); }
// "Owned" (rvalue) version of LogInfo that does not convert StatusBuilder to a reference (lvalue)
[[nodiscard]] StatusBuilder&& LogInfo() && { return std::move(LogInfo()); }
// WithContext can be used to easily add contextual information to a Status
// while maintaining the error_code, and error_message.
//
// Example:
// Status result = DoSomething();
// if (!status.ok()) {
// // This will add 'Metric=<metric_id>' to the error_details string.
// return util::StatusBuilder(result).WithContext("Metric", metric_id);
// }
template <typename V>
inline StatusBuilder& WithContext(const std::string& key, const V& value) & {
// Check if stream is empty.
if (error_details_.tellp() != std::streampos(0)) {
error_details_ << ", ";
}
error_details_ << key << "=";
if constexpr (std::is_constructible<ContextFormatter<V>, V>::value) {
// Use the custom ContextFormatter for the value.
ContextFormatter<V>(value).write_value(error_details_);
} else {
error_details_ << value;
}
return *this;
}
// "Owned" (rvalue) version of WithContext that doesn't convert the StatusBuilder into a reference
// (lvalue).
template <typename V>
inline StatusBuilder&& WithContext(const std::string& key, const V& value) && {
return std::move(WithContext(key, value));
}
// Single argument version of WithContext for types where there is a ContextFormatter defined.
template <typename V>
inline StatusBuilder& WithContexts(const V& v) & {
static_assert(std::is_constructible<ContextFormatter<V>, V>::value,
"All arguments to StatusBuilder::WithContext *must* have a ContextFormatter "
"implementation");
return WithContext(ContextFormatter<V>(v).default_key(), v);
}
// "Owned" (rvalue) version of the above method that doesn't convert StatusBuilder into reference
// (lvalue).
template <typename V>
inline StatusBuilder&& WithContexts(const V& v) && {
return std::move(WithContexts(v));
}
// Variadic version of WithContexts.
template <typename V, typename... ContextValues>
inline StatusBuilder& WithContexts(const V& v, ContextValues&&... args) & {
WithContexts(v);
return WithContexts(std::forward<ContextValues>(args)...);
}
// "Owned" (rvalue) version of the above method that doesn't convert StatusBuilder into reference
// (lvalue).
template <typename V, typename... ContextValues>
inline StatusBuilder&& WithContexts(const V& v, ContextValues&&... args) && {
return std::move(WithContexts(v, std::forward<ContextValues>(args)...));
}
// AppendMsg appends the value to the error_message;
template <typename V>
inline StatusBuilder& AppendMsg(const V& value) & {
if constexpr (std::is_constructible<ContextFormatter<V>, V>::value) {
// Use the custom ContextFormatter for the value.
ContextFormatter<V>(value).write_value(error_message_);
} else {
error_message_ << value;
}
return *this;
}
// "Owned" (rvalue) version of AppendMsg that doesn't convert the StatusBuilder into a reference
// (lvalue).
template <typename V>
[[nodiscard]] inline StatusBuilder&& AppendMsg(const V& value) && {
return std::move(AppendMsg(value));
}
private:
util::SourceLocation location_;
StatusCode code_ = StatusCode::OK;
LogLevel log_level_ = LogLevel::NO_LOGGING;
std::stringstream error_message_;
std::stringstream error_details_;
};
// Specialization of ContextFormatter for cobalt::ReportDefinition to give it default formatting and
// allow it to be passed to StatusBuilder::WithContexts.
//
// Will print Report=<report_name()> by default.
template <>
class ContextFormatter<ReportDefinition> {
public:
explicit ContextFormatter(const ReportDefinition& v) : v_(v) {}
inline static std::string default_key() { return "Report"; }
inline void write_value(std::ostream& stream) { stream << v_.report_name(); }
private:
const ReportDefinition& v_;
};
// Specialization of ContextFormatter for cobalt::MetricDefinition to give it default formatting and
// allow it to be passed to StatusBuilder::WithContexts.
//
// Will print Metric=<metric_name()> by default.
template <>
class ContextFormatter<MetricDefinition> {
public:
explicit ContextFormatter(const MetricDefinition& v) : v_(v) {}
inline static std::string default_key() { return "Metric"; }
inline void write_value(std::ostream& stream) { stream << v_.metric_name(); }
private:
const MetricDefinition& v_;
};
// Specialization of ContextFormatter for protobuf enums to give them default formatting and allow
// it to be passed to StatusBuilder::WithContexts.
//
// Will print <enum_type>=<enum_string_value> by default.
#define PB_ENUM_FORMATTER(ty) \
template <> \
class ContextFormatter<ty> { \
public: \
explicit ContextFormatter(const ty& v) : v_(v) {} \
inline static std::string default_key() { return #ty; } \
inline void write_value(std::ostream& stream) { stream << ty##_Name(v_); } \
\
private: \
const ty& v_; \
}
PB_ENUM_FORMATTER(MetricDefinition::MetricType);
PB_ENUM_FORMATTER(ReportDefinition::OnDeviceAggregationType);
PB_ENUM_FORMATTER(ReportDefinition::ReportType);
PB_ENUM_FORMATTER(SystemProfileSelectionPolicy);
#undef PB_ENUM_FORMATTER
// Specialization of ContextFormatter for Event::TypeCase to give it default formatting and allow it
// to be passed to StatusBuilder::WithContexts.
//
// Will print Event::TypeCase=<v()> by default. Where v() maps to a human-readable string.
template <>
class ContextFormatter<Event::TypeCase> {
public:
explicit ContextFormatter(const Event::TypeCase& v) : v_(v) {}
inline static std::string default_key() { return "Event::TypeCase"; }
std::string v() {
switch (v_) {
case Event::kEventOccurredEvent:
return "EVENT_OCCURRED";
case Event::kEventCountEvent:
return "EVENT_COUNT";
case Event::kElapsedTimeEvent:
return "ELAPSED_TIME";
case Event::kFrameRateEvent:
return "FRAME_RATE";
case Event::kMemoryUsageEvent:
return "MEMORY_USAGE";
case Event::kIntHistogramEvent:
return "INT_HISTOGRAM";
case Event::kOccurrenceEvent:
return "OCCURRENCE";
case Event::kIntegerEvent:
return "INTEGER";
case Event::kIntegerHistogramEvent:
return "INTEGER_HISTOGRAM";
case Event::kStringEvent:
return "STRING";
case Event::kCustomEvent:
return "CUSTOM";
default:
return "UNKNOWN_CASE";
}
}
inline void write_value(std::ostream& stream) { stream << v(); }
private:
const Event::TypeCase& v_;
};
} // namespace cobalt::util
#endif // COBALT_SRC_LIB_UTIL_STATUS_BUILDER_H_