Skip to content

Commit 98e6839

Browse files
authored
impl(common): classes to propagate error information (#10259)
1 parent 57823e0 commit 98e6839

File tree

6 files changed

+195
-0
lines changed

6 files changed

+195
-0
lines changed

google/cloud/google_cloud_cpp_common.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ google_cloud_cpp_common_hdrs = [
4444
"internal/diagnostics_push.inc",
4545
"internal/disable_deprecation_warnings.inc",
4646
"internal/disable_msvc_crt_secure_warnings.inc",
47+
"internal/error_metadata.h",
4748
"internal/filesystem.h",
4849
"internal/format_time_point.h",
4950
"internal/future_base.h",
@@ -101,6 +102,7 @@ google_cloud_cpp_common_srcs = [
101102
"internal/compiler_info.cc",
102103
"internal/compute_engine_util.cc",
103104
"internal/credentials_impl.cc",
105+
"internal/error_metadata.cc",
104106
"internal/filesystem.cc",
105107
"internal/format_time_point.cc",
106108
"internal/future_impl.cc",

google/cloud/google_cloud_cpp_common.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ add_library(
6565
internal/diagnostics_push.inc
6666
internal/disable_deprecation_warnings.inc
6767
internal/disable_msvc_crt_secure_warnings.inc
68+
internal/error_metadata.cc
69+
internal/error_metadata.h
6870
internal/filesystem.cc
6971
internal/filesystem.h
7072
internal/format_time_point.cc
@@ -297,6 +299,7 @@ if (BUILD_TESTING)
297299
internal/compiler_info_test.cc
298300
internal/compute_engine_util_test.cc
299301
internal/credentials_impl_test.cc
302+
internal/error_metadata_test.cc
300303
internal/filesystem_test.cc
301304
internal/format_time_point_test.cc
302305
internal/future_impl_test.cc

google/cloud/google_cloud_cpp_common_unit_tests.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ google_cloud_cpp_common_unit_tests = [
3232
"internal/compiler_info_test.cc",
3333
"internal/compute_engine_util_test.cc",
3434
"internal/credentials_impl_test.cc",
35+
"internal/error_metadata_test.cc",
3536
"internal/filesystem_test.cc",
3637
"internal/format_time_point_test.cc",
3738
"internal/future_impl_test.cc",
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "google/cloud/internal/error_metadata.h"
16+
#include "google/cloud/internal/absl_str_cat_quiet.h"
17+
#include "google/cloud/internal/absl_str_join_quiet.h"
18+
19+
namespace google {
20+
namespace cloud {
21+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
22+
namespace internal {
23+
24+
std::string Format(absl::string_view message, ErrorContext const& context) {
25+
if (context.empty()) return std::string{message};
26+
return absl::StrCat(message, ", ",
27+
absl::StrJoin(context, ", ", absl::PairFormatter("=")));
28+
}
29+
30+
} // namespace internal
31+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
32+
} // namespace cloud
33+
} // namespace google
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_ERROR_METADATA_H
16+
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_ERROR_METADATA_H
17+
18+
#include "google/cloud/version.h"
19+
#include "absl/strings/string_view.h"
20+
#include <string>
21+
#include <utility>
22+
#include <vector>
23+
24+
namespace google {
25+
namespace cloud {
26+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
27+
namespace internal {
28+
29+
/**
30+
* A (relatively) lightweight data structure to pass error metadata across
31+
* implementation functions.
32+
*
33+
* Sometimes we want to provide additional context about errors. The original
34+
* motivation is credential file parsing. These files can be fairly complex,
35+
* and parsing requires many functions that only need the *contents* of the
36+
* file to parse it, but may want to show the filename, the start of the
37+
* parsing call tree, and maybe some key intermediate callers.
38+
*
39+
* This class can be used to pass these additional parameters as needed, maybe
40+
* growing as parsing partially succeeds, and if there is an error the data
41+
* can be included as part of the message (or `google::cloud::ErrorInfo`).
42+
*
43+
* @par Example
44+
* @code
45+
* namespace google::cloud::internal {
46+
* StatusOr<Foo> ParseFooFile(std::string filename) {
47+
* ErrorContext ec{
48+
* {"filename", filename},
49+
* {"origin", __func__},
50+
* };
51+
* std::ifstream is(filename);
52+
* auto contents = std::string{std::istreambuf_iterator<char>{is.buf()},{ }};
53+
* if (is.bad()) {
54+
* return Status(
55+
* StatusCode::kInvalidArgument,
56+
* Format("cannot read file", error_context));
57+
* }
58+
* return ParseFooFileContents(std::move(contents), std::move(ec));
59+
* }
60+
*
61+
* StatusOr<Foo> ParseFooFileContents(std::string contents, ErrorContext ec) {
62+
* // Do some stuff
63+
* if (has_bar()) return ParseFooFileContentsWithBar(
64+
* .., std::move(error_context));
65+
* // do more stuff
66+
* if (bad()) return Status(
67+
* StatusCode::kInvalidArgument,
68+
* Format("badness parsing thingamajig", ec));
69+
* // all good
70+
* return Foo{...};
71+
* }
72+
* @endcode
73+
*/
74+
class ErrorContext {
75+
public:
76+
using Container = std::vector<std::pair<std::string, std::string>>;
77+
78+
ErrorContext() = default;
79+
explicit ErrorContext(Container m) : metadata_(std::move(m)) {}
80+
81+
ErrorContext(ErrorContext const&) = default;
82+
ErrorContext& operator=(ErrorContext const&) = default;
83+
ErrorContext(ErrorContext&&) = default;
84+
ErrorContext& operator=(ErrorContext&&) = default;
85+
86+
template <typename... A>
87+
Container::reference emplace_back(A&&... a) {
88+
return metadata_.emplace_back(std::forward<A>(a)...);
89+
}
90+
91+
void push_back(Container::value_type p) {
92+
return metadata_.push_back(std::move(p));
93+
}
94+
95+
bool empty() const { return metadata_.empty(); }
96+
97+
Container::const_iterator begin() const { return metadata_.begin(); }
98+
99+
Container::const_iterator end() const { return metadata_.end(); }
100+
101+
private:
102+
Container metadata_;
103+
};
104+
105+
std::string Format(absl::string_view message, ErrorContext const& context);
106+
107+
} // namespace internal
108+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
109+
} // namespace cloud
110+
} // namespace google
111+
112+
#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_ERROR_METADATA_H
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "google/cloud/internal/error_metadata.h"
16+
#include <gmock/gmock.h>
17+
18+
namespace google {
19+
namespace cloud {
20+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
21+
namespace internal {
22+
namespace {
23+
24+
using ::testing::HasSubstr;
25+
using ::testing::StartsWith;
26+
27+
TEST(ErrorContext, FormatEmpty) {
28+
EXPECT_EQ("error message", Format("error message", ErrorContext{}));
29+
}
30+
31+
TEST(ErrorContext, Format) {
32+
auto const actual =
33+
Format("error message",
34+
ErrorContext{{{"key", "value"}, {"filename", "the-filename"}}});
35+
EXPECT_THAT(actual, StartsWith("error message"));
36+
EXPECT_THAT(actual, HasSubstr("key=value"));
37+
EXPECT_THAT(actual, HasSubstr("filename=the-filename"));
38+
}
39+
40+
} // namespace
41+
} // namespace internal
42+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
43+
} // namespace cloud
44+
} // namespace google

0 commit comments

Comments
 (0)