perfetto: add payload APIs to base::Status
This CL allows adding "payloads" to base::Status, allowing attaching
arbitrary key, value arguments. This allows providing additional context about the error (e.g. source locations, human readable messages etc).
Inspiration for these APIs comes directly from absl::Status which has
methods with the same prototypes (but different impls).
Change-Id: Ifb5ab4ebbb2265e69eb7667602c19c7f8b49d4bb
diff --git a/Android.bp b/Android.bp
index b65d3fe..eab5301 100644
--- a/Android.bp
+++ b/Android.bp
@@ -8425,6 +8425,7 @@
"src/base/scoped_file_unittest.cc",
"src/base/small_vector_unittest.cc",
"src/base/status_or_unittest.cc",
+ "src/base/status_unittest.cc",
"src/base/string_splitter_unittest.cc",
"src/base/string_utils_unittest.cc",
"src/base/string_view_unittest.cc",
diff --git a/include/perfetto/base/status.h b/include/perfetto/base/status.h
index 2939357..f059184 100644
--- a/include/perfetto/base/status.h
+++ b/include/perfetto/base/status.h
@@ -17,7 +17,10 @@
#ifndef INCLUDE_PERFETTO_BASE_STATUS_H_
#define INCLUDE_PERFETTO_BASE_STATUS_H_
+#include <optional>
#include <string>
+#include <string_view>
+#include <vector>
#include "perfetto/base/compiler.h"
#include "perfetto/base/export.h"
@@ -30,6 +33,10 @@
// This can used as the return type of functions which would usually return an
// bool for success or int for errno but also wants to add some string context
// (ususally for logging).
+//
+// Similar to absl::Status, an optional "payload" can also be included with more
+// context about the error. This allows passing additional metadata about the
+// error (e.g. location of errors, potential mitigations etc).
class PERFETTO_EXPORT_COMPONENT Status {
public:
Status() : ok_(true) {}
@@ -52,9 +59,49 @@
const std::string& message() const { return message_; }
const char* c_message() const { return message_.c_str(); }
+ //////////////////////////////////////////////////////////////////////////////
+ // Payload Management APIs
+ //////////////////////////////////////////////////////////////////////////////
+
+ // Payloads can be attached to error statuses to provide additional context.
+ //
+ // Payloads are (key, value) pairs, where the key is a string acting as a
+ // unique "type URL" and the value is an opaque string. The "type URL" should
+ // be unique, follow the format of a URL and, ideally, documentation on how to
+ // interpret its associated data should be available.
+ //
+ // To attach a payload to a status object, call `Status::SetPayload()`.
+ // Similarly, to extract the payload from a status, call
+ // `Status::GetPayload()`.
+ //
+ // Note: the payload APIs are only meaningful to call when the status is an
+ // error. Otherwise, all methods are noops.
+
+ // Gets the payload for the given |type_url| if one exists.
+ //
+ // Will always return std::nullopt if |ok()|.
+ std::optional<std::string_view> GetPayload(std::string_view type_url);
+
+ // Sets the payload for the given key. The key should
+ //
+ // Will always do nothing if |ok()|.
+ void SetPayload(std::string_view type_url, std::string value);
+
+ // Erases the payload for the given string and returns true if the payload
+ // existed and was erased.
+ //
+ // Will always do nothing if |ok()|.
+ bool ErasePayload(std::string_view type_url);
+
private:
+ struct Payload {
+ std::string type_url;
+ std::string payload;
+ };
+
bool ok_ = false;
std::string message_;
+ std::vector<Payload> payloads_;
};
// Returns a status object which represents the Ok status.
diff --git a/src/base/BUILD.gn b/src/base/BUILD.gn
index 8d48bf0..7dfce6b 100644
--- a/src/base/BUILD.gn
+++ b/src/base/BUILD.gn
@@ -199,6 +199,7 @@
"scoped_file_unittest.cc",
"small_vector_unittest.cc",
"status_or_unittest.cc",
+ "status_unittest.cc",
"string_splitter_unittest.cc",
"string_utils_unittest.cc",
"string_view_unittest.cc",
diff --git a/src/base/status.cc b/src/base/status.cc
index 30ccc47..d3c13e8 100644
--- a/src/base/status.cc
+++ b/src/base/status.cc
@@ -17,6 +17,7 @@
#include "perfetto/base/status.h"
#include <stdarg.h>
+#include <algorithm>
namespace perfetto {
namespace base {
@@ -31,5 +32,42 @@
return status;
}
+std::optional<std::string_view> Status::GetPayload(std::string_view type_url) {
+ if (ok()) {
+ return std::nullopt;
+ }
+ for (const auto& kv : payloads_) {
+ if (kv.type_url == type_url) {
+ return kv.payload;
+ }
+ }
+ return std::nullopt;
+}
+
+void Status::SetPayload(std::string_view type_url, std::string value) {
+ if (ok()) {
+ return;
+ }
+ for (auto& kv : payloads_) {
+ if (kv.type_url == type_url) {
+ kv.payload = value;
+ return;
+ }
+ }
+ payloads_.push_back(Payload{std::string(type_url), std::move(value)});
+}
+
+bool Status::ErasePayload(std::string_view type_url) {
+ if (ok()) {
+ return false;
+ }
+ auto it = std::remove_if(
+ payloads_.begin(), payloads_.end(),
+ [type_url](const Payload& p) { return p.type_url == type_url; });
+ bool erased = it != payloads_.end();
+ payloads_.erase(it, payloads_.end());
+ return erased;
+}
+
} // namespace base
} // namespace perfetto
diff --git a/src/base/status_unittest.cc b/src/base/status_unittest.cc
new file mode 100644
index 0000000..df42b31
--- /dev/null
+++ b/src/base/status_unittest.cc
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/base/status.h"
+
+#include "test/gtest_and_gmock.h"
+
+namespace perfetto {
+namespace base {
+
+TEST(StatusTest, GetMissingPayload) {
+ base::Status status = base::ErrStatus("Error");
+ ASSERT_EQ(status.GetPayload("test.foo.com/bar"), std::nullopt);
+}
+
+TEST(StatusTest, SetThenGetPayload) {
+ base::Status status = base::ErrStatus("Error");
+ status.SetPayload("test.foo.com/bar", "payload_value");
+ ASSERT_EQ(status.GetPayload("test.foo.com/bar"), "payload_value");
+}
+
+TEST(StatusTest, SetEraseGetPayload) {
+ base::Status status = base::ErrStatus("Error");
+ status.SetPayload("test.foo.com/bar", "payload_value");
+ ASSERT_TRUE(status.ErasePayload("test.foo.com/bar"));
+ ASSERT_EQ(status.GetPayload("test.foo.com/bar"), std::nullopt);
+}
+
+TEST(StatusTest, SetOverride) {
+ base::Status status = base::ErrStatus("Error");
+ status.SetPayload("test.foo.com/bar", "payload_value");
+ status.SetPayload("test.foo.com/bar", "other_value");
+ ASSERT_EQ(status.GetPayload("test.foo.com/bar"), "other_value");
+}
+
+TEST(StatusTest, SetGetOk) {
+ base::Status status = base::OkStatus();
+ status.SetPayload("test.foo.com/bar", "payload_value");
+ ASSERT_EQ(status.GetPayload("test.foo.com/bar"), std::nullopt);
+}
+
+TEST(StatusTest, SetMultipleAndDuplicate) {
+ base::Status status = base::ErrStatus("Error");
+ status.SetPayload("test.foo.com/bar", "payload_value");
+ status.SetPayload("test.foo.com/bar1", "1");
+ status.SetPayload("test.foo.com/bar2", "2");
+ status.SetPayload("test.foo.com/bar", "other_value");
+ ASSERT_EQ(status.GetPayload("test.foo.com/bar"), "other_value");
+ ASSERT_EQ(status.GetPayload("test.foo.com/bar1"), "1");
+ ASSERT_EQ(status.GetPayload("test.foo.com/bar2"), "2");
+}
+
+} // namespace base
+} // namespace perfetto