Skip to content

Commit

Permalink
Add ostream<< and AbslStringify to absl::StatusOr.
Browse files Browse the repository at this point in the history
These methods will only be defined if they're defined for `T`.  Additionally,
we add jitter to the output to discourage people relying on the output format.

PiperOrigin-RevId: 590598988
Change-Id: I4e7173b5f0c66fd3a1cdd3392944e20b8a26641f
  • Loading branch information
Zie Weaver authored and copybara-github committed Dec 13, 2023
1 parent f16e457 commit 031d99a
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 0 deletions.
2 changes: 2 additions & 0 deletions absl/status/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ cc_library(
"//absl/base:raw_logging_internal",
"//absl/meta:type_traits",
"//absl/strings",
"//absl/strings:has_ostream_operator",
"//absl/strings:str_format",
"//absl/types:variant",
"//absl/utility",
],
Expand Down
3 changes: 3 additions & 0 deletions absl/status/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,11 @@ absl_cc_library(
absl::base
absl::config
absl::core_headers
absl::has_ostream_operator
absl::nullability
absl::raw_logging_internal
absl::status
absl::str_format
absl::strings
absl::type_traits
absl::utility
Expand All @@ -97,5 +99,6 @@ absl_cc_test(
DEPS
absl::status
absl::statusor
absl::strings
GTest::gmock_main
)
49 changes: 49 additions & 0 deletions absl/status/internal/statusor_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
#ifndef ABSL_STATUS_INTERNAL_STATUSOR_INTERNAL_H_
#define ABSL_STATUS_INTERNAL_STATUSOR_INTERNAL_H_

#include <cstdint>
#include <type_traits>
#include <utility>

#include "absl/base/attributes.h"
#include "absl/base/nullability.h"
#include "absl/meta/type_traits.h"
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "absl/utility/utility.h"

namespace absl {
Expand Down Expand Up @@ -379,6 +381,53 @@ struct MoveAssignBase<T, false> {

ABSL_ATTRIBUTE_NORETURN void ThrowBadStatusOrAccess(absl::Status status);

// Used to introduce jitter into the output of printing functions for
// `StatusOr` (i.e. `AbslStringify` and `operator<<`).
class StringifyRandom {
enum BracesType {
kBareParens = 0,
kSpaceParens,
kBareBrackets,
kSpaceBrackets,
};

// Returns a random `BracesType` determined once per binary load.
static BracesType RandomBraces() {
static const BracesType kRandomBraces = static_cast<BracesType>(
(reinterpret_cast<uintptr_t>(&kRandomBraces) >> 4) % 4);
return kRandomBraces;
}

public:
static inline absl::string_view OpenBrackets() {
switch (RandomBraces()) {
case kBareParens:
return "(";
case kSpaceParens:
return "( ";
case kBareBrackets:
return "[";
case kSpaceBrackets:
return "[ ";
}
return "(";
}

static inline absl::string_view CloseBrackets() {
switch (RandomBraces()) {
case kBareParens:
return ")";
case kSpaceParens:
return " )";
case kBareBrackets:
return "]";
case kSpaceBrackets:
return " ]";
}
return ")";
}
};

} // namespace internal_statusor
ABSL_NAMESPACE_END
} // namespace absl
Expand Down
39 changes: 39 additions & 0 deletions absl/status/statusor.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include <exception>
#include <initializer_list>
#include <new>
#include <ostream>
#include <string>
#include <type_traits>
#include <utility>
Expand All @@ -49,6 +50,9 @@
#include "absl/meta/type_traits.h"
#include "absl/status/internal/statusor_internal.h"
#include "absl/status/status.h"
#include "absl/strings/has_absl_stringify.h"
#include "absl/strings/has_ostream_operator.h"
#include "absl/strings/str_format.h"
#include "absl/types/variant.h"
#include "absl/utility/utility.h"

Expand Down Expand Up @@ -651,6 +655,41 @@ bool operator!=(const StatusOr<T>& lhs, const StatusOr<T>& rhs) {
return !(lhs == rhs);
}

// Prints the `value` or the status in brackets to `os`.
//
// Requires `T` supports `operator<<`. Do not rely on the output format which
// may change without notice.
template <typename T, typename std::enable_if<
absl::HasOstreamOperator<T>::value, int>::type = 0>
std::ostream& operator<<(std::ostream& os, const StatusOr<T>& status_or) {
if (status_or.ok()) {
os << status_or.value();
} else {
os << internal_statusor::StringifyRandom::OpenBrackets()
<< status_or.status()
<< internal_statusor::StringifyRandom::CloseBrackets();
}
return os;
}

// As above, but supports `StrCat`, `StrFormat`, etc.
//
// Requires `T` has `AbslStringify`. Do not rely on the output format which
// may change without notice.
template <
typename Sink, typename T,
typename std::enable_if<absl::HasAbslStringify<T>::value, int>::type = 0>
void AbslStringify(Sink& sink, const StatusOr<T>& status_or) {
if (status_or.ok()) {
absl::Format(&sink, "%v", status_or.value());
} else {
absl::Format(&sink, "%s%v%s",
internal_statusor::StringifyRandom::OpenBrackets(),
status_or.status(),
internal_statusor::StringifyRandom::CloseBrackets());
}
}

//------------------------------------------------------------------------------
// Implementation details for StatusOr<T>
//------------------------------------------------------------------------------
Expand Down
37 changes: 37 additions & 0 deletions absl/status/statusor_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
#include <initializer_list>
#include <map>
#include <memory>
#include <ostream>
#include <sstream>
#include <string>
#include <type_traits>
#include <utility>
Expand All @@ -29,6 +31,7 @@
#include "absl/base/casts.h"
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/string_view.h"
#include "absl/types/any.h"
#include "absl/types/variant.h"
Expand All @@ -37,13 +40,16 @@
namespace {

using ::testing::AllOf;
using ::testing::AnyOf;
using ::testing::AnyWith;
using ::testing::ElementsAre;
using ::testing::EndsWith;
using ::testing::Field;
using ::testing::HasSubstr;
using ::testing::Ne;
using ::testing::Not;
using ::testing::Pointee;
using ::testing::StartsWith;
using ::testing::VariantWith;

#ifdef GTEST_HAS_STATUS_MATCHERS
Expand Down Expand Up @@ -1881,4 +1887,35 @@ TEST(StatusOr, StatusAssignmentFromTypeConvertibleToStatus) {
EXPECT_EQ(statusor.status(), static_cast<absl::Status>(v));
}

struct PrintTestStruct {
friend std::ostream& operator<<(std::ostream& os, const PrintTestStruct&) {
return os << "ostream";
}

template <typename Sink>
friend void AbslStringify(Sink& sink, const PrintTestStruct&) {
sink.Append("stringify");
}
};

TEST(StatusOr, OkPrinting) {
absl::StatusOr<PrintTestStruct> print_me = PrintTestStruct{};
std::stringstream stream;
stream << print_me;
EXPECT_EQ(stream.str(), "ostream");
EXPECT_EQ(absl::StrCat(print_me), "stringify");
}

TEST(StatusOr, ErrorPrinting) {
absl::StatusOr<PrintTestStruct> print_me = absl::UnknownError("error");
std::stringstream stream;
stream << print_me;
const auto error_matcher =
AllOf(HasSubstr("UNKNOWN"), HasSubstr("error"),
AnyOf(AllOf(StartsWith("("), EndsWith(")")),
AllOf(StartsWith("["), EndsWith("]"))));
EXPECT_THAT(stream.str(), error_matcher);
EXPECT_THAT(absl::StrCat(print_me), error_matcher);
}

} // namespace

0 comments on commit 031d99a

Please sign in to comment.