Skip to content

Commit ba56822

Browse files
authored
impl(common): helpers to create ErrorInfo (#10271)
1 parent 98e6839 commit ba56822

File tree

5 files changed

+298
-2
lines changed

5 files changed

+298
-2
lines changed

google/cloud/internal/make_status.cc

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// limitations under the License.
1414

1515
#include "google/cloud/internal/make_status.h"
16+
#include "google/cloud/version.h"
1617

1718
namespace google {
1819
namespace cloud {
@@ -85,6 +86,99 @@ Status DataLossError(std::string msg, ErrorInfo info) {
8586
return Status(StatusCode::kDataLoss, std::move(msg), std::move(info));
8687
}
8788

89+
ErrorInfoBuilder::ErrorInfoBuilder(std::string file, int line,
90+
std::string function) {
91+
metadata_.emplace("gcloud-cpp.version", version_string());
92+
metadata_.emplace("gcloud-cpp.source.filename", std::move(file));
93+
metadata_.emplace("gcloud-cpp.source.line", std::to_string(line));
94+
metadata_.emplace("gcloud-cpp.source.function", std::move(function));
95+
}
96+
97+
ErrorInfo ErrorInfoBuilder::Build(StatusCode code) && {
98+
return ErrorInfo(reason_.value_or(StatusCodeToString(code)), "gcloud-cpp",
99+
std::move(metadata_));
100+
}
101+
102+
Status CancelledError(std::string msg, ErrorInfoBuilder b) {
103+
return Status(StatusCode::kCancelled, std::move(msg),
104+
std::move(b).Build(StatusCode::kCancelled));
105+
}
106+
107+
Status UnknownError(std::string msg, ErrorInfoBuilder b) {
108+
return Status(StatusCode::kUnknown, std::move(msg),
109+
std::move(b).Build(StatusCode::kUnknown));
110+
}
111+
112+
Status InvalidArgumentError(std::string msg, ErrorInfoBuilder b) {
113+
return Status(StatusCode::kInvalidArgument, std::move(msg),
114+
std::move(b).Build(StatusCode::kInvalidArgument));
115+
}
116+
117+
Status DeadlineExceededError(std::string msg, ErrorInfoBuilder b) {
118+
return Status(StatusCode::kDeadlineExceeded, std::move(msg),
119+
std::move(b).Build(StatusCode::kDeadlineExceeded));
120+
}
121+
122+
Status NotFoundError(std::string msg, ErrorInfoBuilder b) {
123+
return Status(StatusCode::kNotFound, std::move(msg),
124+
std::move(b).Build(StatusCode::kNotFound));
125+
}
126+
127+
Status AlreadyExistsError(std::string msg, ErrorInfoBuilder b) {
128+
return Status(StatusCode::kAlreadyExists, std::move(msg),
129+
std::move(b).Build(StatusCode::kAlreadyExists));
130+
}
131+
132+
Status PermissionDeniedError(std::string msg, ErrorInfoBuilder b) {
133+
return Status(StatusCode::kPermissionDenied, std::move(msg),
134+
std::move(b).Build(StatusCode::kPermissionDenied));
135+
}
136+
137+
Status UnauthenticatedError(std::string msg, ErrorInfoBuilder b) {
138+
return Status(StatusCode::kUnauthenticated, std::move(msg),
139+
std::move(b).Build(StatusCode::kUnauthenticated));
140+
}
141+
142+
Status ResourceExhaustedError(std::string msg, ErrorInfoBuilder b) {
143+
return Status(StatusCode::kResourceExhausted, std::move(msg),
144+
std::move(b).Build(StatusCode::kResourceExhausted));
145+
}
146+
147+
Status FailedPreconditionError(std::string msg, ErrorInfoBuilder b) {
148+
return Status(StatusCode::kFailedPrecondition, std::move(msg),
149+
std::move(b).Build(StatusCode::kFailedPrecondition));
150+
}
151+
152+
Status AbortedError(std::string msg, ErrorInfoBuilder b) {
153+
return Status(StatusCode::kAborted, std::move(msg),
154+
std::move(b).Build(StatusCode::kAborted));
155+
}
156+
157+
Status OutOfRangeError(std::string msg, ErrorInfoBuilder b) {
158+
return Status(StatusCode::kOutOfRange, std::move(msg),
159+
std::move(b).Build(StatusCode::kOutOfRange));
160+
}
161+
162+
Status UnimplementedError(std::string msg, ErrorInfoBuilder b) {
163+
return Status(StatusCode::kUnimplemented, std::move(msg),
164+
std::move(b).Build(StatusCode::kUnimplemented));
165+
}
166+
167+
Status InternalError(std::string msg, ErrorInfoBuilder b) {
168+
return Status(StatusCode::kInternal, std::move(msg),
169+
std::move(b).Build(StatusCode::kInternal));
170+
}
171+
172+
Status UnavailableError(std::string msg, ErrorInfoBuilder b) {
173+
return Status(StatusCode::kUnavailable, std::move(msg),
174+
std::move(b).Build(StatusCode::kUnavailable));
175+
}
176+
177+
Status DataLossError(std::string msg, ErrorInfoBuilder b) {
178+
return Status(StatusCode::kDataLoss, std::move(msg),
179+
std::move(b).Build(StatusCode::kDataLoss));
180+
}
181+
88182
} // namespace internal
89183
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
90184
} // namespace cloud

google/cloud/internal/make_status.h

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_MAKE_STATUS_H
1616
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_MAKE_STATUS_H
1717

18+
#include "google/cloud/internal/error_metadata.h"
1819
#include "google/cloud/status.h"
1920
#include "google/cloud/version.h"
2021
#include <string>
@@ -41,6 +42,87 @@ Status InternalError(std::string msg, ErrorInfo info = {});
4142
Status UnavailableError(std::string msg, ErrorInfo info = {});
4243
Status DataLossError(std::string msg, ErrorInfo info = {});
4344

45+
/**
46+
* Build `ErrorInfo` instances from parts.
47+
*
48+
* @par Example
49+
*
50+
* This is typically used in conjunction with the `GCP_ERROR_INFO()` macro. To
51+
* return an error with minimal annotations use:
52+
*
53+
* @code
54+
* StatusOr<double> SquareRoot(double x) {
55+
* if (x < 0) return OutOfRangeError("negative input", GCP_ERROR_INFO());
56+
* return std::sqrt(x);
57+
* }
58+
* @endcode
59+
*
60+
* To include more annotations you could use:
61+
*
62+
* @code
63+
* StatusOr<std::string> GetString(
64+
* nlohmann::json const& json, absl::string_view key,
65+
* ErrorContext const& ec) {
66+
* auto i = json.find(key);
67+
* if (i = json.end()) {
68+
* return InvalidArgument(
69+
* "missing key",
70+
* GCP_ERROR_INFO().WithContext(ec).WithMetadata("key", key));
71+
* }
72+
* return i->get<std::string>(); // no type checking in this example
73+
* }
74+
* @endcode
75+
*
76+
*/
77+
class ErrorInfoBuilder {
78+
public:
79+
ErrorInfoBuilder(std::string file, int line, std::string function);
80+
81+
/// Adds the metadata from an error context, existing values are not replaced.
82+
ErrorInfoBuilder&& WithContext(ErrorContext const& ec) && {
83+
metadata_.insert(ec.begin(), ec.end());
84+
return std::move(*this);
85+
}
86+
87+
/// Add a metadata pair, existing values are not replaced.
88+
ErrorInfoBuilder&& WithMetadata(absl::string_view key,
89+
absl::string_view value) && {
90+
metadata_.emplace(key, value);
91+
return std::move(*this);
92+
}
93+
94+
ErrorInfoBuilder&& WithReason(std::string reason) && {
95+
reason_ = std::move(reason);
96+
return std::move(*this);
97+
}
98+
99+
ErrorInfo Build(StatusCode code) &&;
100+
101+
private:
102+
absl::optional<std::string> reason_;
103+
std::unordered_map<std::string, std::string> metadata_;
104+
};
105+
106+
#define GCP_ERROR_INFO() \
107+
::google::cloud::internal::ErrorInfoBuilder(__FILE__, __LINE__, __func__)
108+
109+
Status CancelledError(std::string msg, ErrorInfoBuilder b);
110+
Status UnknownError(std::string msg, ErrorInfoBuilder b);
111+
Status InvalidArgumentError(std::string msg, ErrorInfoBuilder b);
112+
Status DeadlineExceededError(std::string msg, ErrorInfoBuilder b);
113+
Status NotFoundError(std::string msg, ErrorInfoBuilder b);
114+
Status AlreadyExistsError(std::string msg, ErrorInfoBuilder b);
115+
Status PermissionDeniedError(std::string msg, ErrorInfoBuilder b);
116+
Status UnauthenticatedError(std::string msg, ErrorInfoBuilder b);
117+
Status ResourceExhaustedError(std::string msg, ErrorInfoBuilder b);
118+
Status FailedPreconditionError(std::string msg, ErrorInfoBuilder b);
119+
Status AbortedError(std::string msg, ErrorInfoBuilder b);
120+
Status OutOfRangeError(std::string msg, ErrorInfoBuilder b);
121+
Status UnimplementedError(std::string msg, ErrorInfoBuilder b);
122+
Status InternalError(std::string msg, ErrorInfoBuilder b);
123+
Status UnavailableError(std::string msg, ErrorInfoBuilder b);
124+
Status DataLossError(std::string msg, ErrorInfoBuilder b);
125+
44126
} // namespace internal
45127
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
46128
} // namespace cloud

google/cloud/internal/make_status_test.cc

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
2222
namespace internal {
2323

2424
using ::google::cloud::testing_util::StatusIs;
25+
using ::testing::_;
26+
using ::testing::Contains;
27+
using ::testing::Pair;
2528

2629
TEST(MakeStatus, Basic) {
2730
auto error_info = ErrorInfo("REASON", "domain", {{"key", "value"}});
@@ -60,6 +63,123 @@ TEST(MakeStatus, Basic) {
6063
}
6164
}
6265

66+
TEST(MakeStatus, ErrorInfoBuilderDefault) {
67+
auto const code = StatusCode::kInvalidArgument;
68+
auto const actual = GCP_ERROR_INFO().Build(code);
69+
EXPECT_EQ(actual.reason(), StatusCodeToString(code));
70+
EXPECT_EQ(actual.domain(), "gcloud-cpp");
71+
EXPECT_THAT(actual.metadata(),
72+
Contains(Pair("gcloud-cpp.version", version_string())));
73+
EXPECT_THAT(actual.metadata(),
74+
Contains(Pair("gcloud-cpp.source.filename", __FILE__)));
75+
EXPECT_THAT(actual.metadata(), Contains(Pair("gcloud-cpp.source.line", _)));
76+
EXPECT_THAT(actual.metadata(),
77+
Contains(Pair("gcloud-cpp.source.function", _)));
78+
}
79+
80+
TEST(MakeStatus, ErrorInfoBuilderWithReason) {
81+
auto const code = StatusCode::kInvalidArgument;
82+
auto const actual = GCP_ERROR_INFO().WithReason("TEST_REASON").Build(code);
83+
EXPECT_EQ(actual.reason(), "TEST_REASON");
84+
EXPECT_EQ(actual.domain(), "gcloud-cpp");
85+
EXPECT_THAT(actual.metadata(),
86+
Contains(Pair("gcloud-cpp.version", version_string())));
87+
EXPECT_THAT(actual.metadata(),
88+
Contains(Pair("gcloud-cpp.source.filename", __FILE__)));
89+
EXPECT_THAT(actual.metadata(), Contains(Pair("gcloud-cpp.source.line", _)));
90+
EXPECT_THAT(actual.metadata(),
91+
Contains(Pair("gcloud-cpp.source.function", _)));
92+
}
93+
94+
TEST(MakeStatus, ErrorInfoBuilderWithErrorContext) {
95+
auto ec1 = ErrorContext({{"k0", "v0"}, {"k1", "v1"}});
96+
auto ec2 = ErrorContext({{"k0", "not-used"}, {"k2", "v2"}});
97+
98+
auto const code = StatusCode::kInvalidArgument;
99+
auto const actual =
100+
GCP_ERROR_INFO().WithContext(ec1).WithContext(ec2).Build(code);
101+
EXPECT_THAT(actual.metadata(), Contains(Pair("k0", "v0")));
102+
EXPECT_THAT(actual.metadata(), Contains(Pair("k1", "v1")));
103+
EXPECT_THAT(actual.metadata(), Contains(Pair("k2", "v2")));
104+
EXPECT_EQ(actual.reason(), StatusCodeToString(code));
105+
EXPECT_EQ(actual.domain(), "gcloud-cpp");
106+
EXPECT_THAT(actual.metadata(),
107+
Contains(Pair("gcloud-cpp.version", version_string())));
108+
EXPECT_THAT(actual.metadata(),
109+
Contains(Pair("gcloud-cpp.source.filename", __FILE__)));
110+
EXPECT_THAT(actual.metadata(), Contains(Pair("gcloud-cpp.source.line", _)));
111+
EXPECT_THAT(actual.metadata(),
112+
Contains(Pair("gcloud-cpp.source.function", _)));
113+
}
114+
115+
TEST(MakeStatus, ErrorInfoBuilderWithMetadata) {
116+
auto const code = StatusCode::kInvalidArgument;
117+
auto const actual = GCP_ERROR_INFO()
118+
.WithMetadata("k0", "v0")
119+
.WithMetadata("k1", "v1")
120+
.WithMetadata("k0", "not-used")
121+
.Build(code);
122+
EXPECT_THAT(actual.metadata(), Contains(Pair("k0", "v0")));
123+
EXPECT_THAT(actual.metadata(), Contains(Pair("k1", "v1")));
124+
EXPECT_EQ(actual.reason(), StatusCodeToString(code));
125+
EXPECT_EQ(actual.domain(), "gcloud-cpp");
126+
EXPECT_THAT(actual.metadata(),
127+
Contains(Pair("gcloud-cpp.version", version_string())));
128+
EXPECT_THAT(actual.metadata(),
129+
Contains(Pair("gcloud-cpp.source.filename", __FILE__)));
130+
EXPECT_THAT(actual.metadata(), Contains(Pair("gcloud-cpp.source.line", _)));
131+
EXPECT_THAT(actual.metadata(),
132+
Contains(Pair("gcloud-cpp.source.function", _)));
133+
}
134+
135+
TEST(MakeStatus, WithErrorInfo) {
136+
struct TestCase {
137+
StatusCode code;
138+
Status status;
139+
} cases[] = {
140+
{StatusCode::kCancelled, CancelledError("test", GCP_ERROR_INFO())},
141+
{StatusCode::kUnknown, UnknownError("test", GCP_ERROR_INFO())},
142+
{StatusCode::kInvalidArgument,
143+
InvalidArgumentError("test", GCP_ERROR_INFO())},
144+
{StatusCode::kDeadlineExceeded,
145+
DeadlineExceededError("test", GCP_ERROR_INFO())},
146+
{StatusCode::kNotFound, NotFoundError("test", GCP_ERROR_INFO())},
147+
{StatusCode::kAlreadyExists,
148+
AlreadyExistsError("test", GCP_ERROR_INFO())},
149+
{StatusCode::kPermissionDenied,
150+
PermissionDeniedError("test", GCP_ERROR_INFO())},
151+
{StatusCode::kUnauthenticated,
152+
UnauthenticatedError("test", GCP_ERROR_INFO())},
153+
{StatusCode::kResourceExhausted,
154+
ResourceExhaustedError("test", GCP_ERROR_INFO())},
155+
{StatusCode::kFailedPrecondition,
156+
FailedPreconditionError("test", GCP_ERROR_INFO())},
157+
{StatusCode::kAborted, AbortedError("test", GCP_ERROR_INFO())},
158+
{StatusCode::kOutOfRange, OutOfRangeError("test", GCP_ERROR_INFO())},
159+
{StatusCode::kUnimplemented,
160+
UnimplementedError("test", GCP_ERROR_INFO())},
161+
{StatusCode::kInternal, InternalError("test", GCP_ERROR_INFO())},
162+
{StatusCode::kUnavailable, UnavailableError("test", GCP_ERROR_INFO())},
163+
{StatusCode::kDataLoss, DataLossError("test", GCP_ERROR_INFO())},
164+
};
165+
166+
for (auto const& test : cases) {
167+
SCOPED_TRACE("Testing for " + StatusCodeToString(test.code));
168+
EXPECT_THAT(test.status, StatusIs(test.code));
169+
EXPECT_EQ(test.status.message(), "test");
170+
EXPECT_EQ(test.status.error_info().reason(), StatusCodeToString(test.code));
171+
EXPECT_EQ(test.status.error_info().domain(), "gcloud-cpp");
172+
EXPECT_THAT(test.status.error_info().metadata(),
173+
Contains(Pair("gcloud-cpp.version", _)));
174+
EXPECT_THAT(test.status.error_info().metadata(),
175+
Contains(Pair("gcloud-cpp.source.filename", __FILE__)));
176+
EXPECT_THAT(test.status.error_info().metadata(),
177+
Contains(Pair("gcloud-cpp.source.line", _)));
178+
EXPECT_THAT(test.status.error_info().metadata(),
179+
Contains(Pair("gcloud-cpp.source.function", _)));
180+
}
181+
}
182+
63183
} // namespace internal
64184
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
65185
} // namespace cloud

google/cloud/storage/internal/curl_request_builder.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ namespace storage {
3030
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
3131
namespace internal {
3232
/**
33-
* Implements the Builder pattern for CurlRequest, and CurlUploadRequest.
33+
* Implements the Build pattern for CurlRequest, and CurlUploadRequest.
3434
*/
3535
class CurlRequestBuilder {
3636
public:

google/cloud/storage/internal/rest_request_builder.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ namespace storage {
2828
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
2929
namespace internal {
3030
/**
31-
* Implements a storage request Option aware Builder pattern wrapper around
31+
* Implements a storage request Option aware Build pattern wrapper around
3232
* google::cloud::rest_internal::RestRequest.
3333
*/
3434
class RestRequestBuilder {

0 commit comments

Comments
 (0)