Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

impl(common): classes to propagate error information #10259

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions google/cloud/google_cloud_cpp_common.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ google_cloud_cpp_common_hdrs = [
"internal/diagnostics_push.inc",
"internal/disable_deprecation_warnings.inc",
"internal/disable_msvc_crt_secure_warnings.inc",
"internal/error_metadata.h",
"internal/filesystem.h",
"internal/format_time_point.h",
"internal/future_base.h",
Expand Down Expand Up @@ -101,6 +102,7 @@ google_cloud_cpp_common_srcs = [
"internal/compiler_info.cc",
"internal/compute_engine_util.cc",
"internal/credentials_impl.cc",
"internal/error_metadata.cc",
"internal/filesystem.cc",
"internal/format_time_point.cc",
"internal/future_impl.cc",
Expand Down
3 changes: 3 additions & 0 deletions google/cloud/google_cloud_cpp_common.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ add_library(
internal/diagnostics_push.inc
internal/disable_deprecation_warnings.inc
internal/disable_msvc_crt_secure_warnings.inc
internal/error_metadata.cc
internal/error_metadata.h
internal/filesystem.cc
internal/filesystem.h
internal/format_time_point.cc
Expand Down Expand Up @@ -297,6 +299,7 @@ if (BUILD_TESTING)
internal/compiler_info_test.cc
internal/compute_engine_util_test.cc
internal/credentials_impl_test.cc
internal/error_metadata_test.cc
internal/filesystem_test.cc
internal/format_time_point_test.cc
internal/future_impl_test.cc
Expand Down
1 change: 1 addition & 0 deletions google/cloud/google_cloud_cpp_common_unit_tests.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ google_cloud_cpp_common_unit_tests = [
"internal/compiler_info_test.cc",
"internal/compute_engine_util_test.cc",
"internal/credentials_impl_test.cc",
"internal/error_metadata_test.cc",
"internal/filesystem_test.cc",
"internal/format_time_point_test.cc",
"internal/future_impl_test.cc",
Expand Down
33 changes: 33 additions & 0 deletions google/cloud/internal/error_metadata.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2022 Google LLC
//
// 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
//
// https://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 "google/cloud/internal/error_metadata.h"
#include "google/cloud/internal/absl_str_cat_quiet.h"
#include "google/cloud/internal/absl_str_join_quiet.h"

namespace google {
namespace cloud {
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
namespace internal {

std::string Format(absl::string_view message, ErrorContext const& context) {
if (context.empty()) return std::string{message};
return absl::StrCat(message, ", ",
absl::StrJoin(context, ", ", absl::PairFormatter("=")));
}

} // namespace internal
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
} // namespace cloud
} // namespace google
112 changes: 112 additions & 0 deletions google/cloud/internal/error_metadata.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright 2022 Google LLC
//
// 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
//
// https://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.

#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_ERROR_METADATA_H
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_ERROR_METADATA_H

#include "google/cloud/version.h"
#include "absl/strings/string_view.h"
#include <string>
#include <utility>
#include <vector>

namespace google {
namespace cloud {
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
namespace internal {

/**
* A (relatively) lightweight data structure to pass error metadata across
* implementation functions.
*
* Sometimes we want to provide additional context about errors. The original
* motivation is credential file parsing. These files can be fairly complex,
* and parsing requires many functions that only need the *contents* of the
* file to parse it, but may want to show the filename, the start of the
* parsing call tree, and maybe some key intermediate callers.
*
* This class can be used to pass these additional parameters as needed, maybe
* growing as parsing partially succeeds, and if there is an error the data
* can be included as part of the message (or `google::cloud::ErrorInfo`).
*
* @par Example
* @code
* namespace google::cloud::internal {
* StatusOr<Foo> ParseFooFile(std::string filename) {
* ErrorContext ec{
* {"filename", filename},
* {"origin", __func__},
* };
* std::ifstream is(filename);
* auto contents = std::string{std::istreambuf_iterator<char>{is.buf()},{ }};
* if (is.bad()) {
* return Status(
* StatusCode::kInvalidArgument,
* Format("cannot read file", error_context));
* }
* return ParseFooFileContents(std::move(contents), std::move(ec));
* }
*
* StatusOr<Foo> ParseFooFileContents(std::string contents, ErrorContext ec) {
* // Do some stuff
* if (has_bar()) return ParseFooFileContentsWithBar(
* .., std::move(error_context));
* // do more stuff
* if (bad()) return Status(
* StatusCode::kInvalidArgument,
* Format("badness parsing thingamajig", ec));
* // all good
* return Foo{...};
* }
* @endcode
*/
class ErrorContext {
public:
using Container = std::vector<std::pair<std::string, std::string>>;

ErrorContext() = default;
explicit ErrorContext(Container m) : metadata_(std::move(m)) {}

ErrorContext(ErrorContext const&) = default;
ErrorContext& operator=(ErrorContext const&) = default;
ErrorContext(ErrorContext&&) = default;
ErrorContext& operator=(ErrorContext&&) = default;

template <typename... A>
Container::reference emplace_back(A&&... a) {
return metadata_.emplace_back(std::forward<A>(a)...);
}

void push_back(Container::value_type p) {
return metadata_.push_back(std::move(p));
}

bool empty() const { return metadata_.empty(); }

Container::const_iterator begin() const { return metadata_.begin(); }

Container::const_iterator end() const { return metadata_.end(); }

private:
Container metadata_;
};

std::string Format(absl::string_view message, ErrorContext const& context);

} // namespace internal
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
} // namespace cloud
} // namespace google

#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_INTERNAL_ERROR_METADATA_H
44 changes: 44 additions & 0 deletions google/cloud/internal/error_metadata_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2022 Google LLC
//
// 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
//
// https://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 "google/cloud/internal/error_metadata.h"
#include <gmock/gmock.h>

namespace google {
namespace cloud {
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
namespace internal {
namespace {

using ::testing::HasSubstr;
using ::testing::StartsWith;

TEST(ErrorContext, FormatEmpty) {
EXPECT_EQ("error message", Format("error message", ErrorContext{}));
}

TEST(ErrorContext, Format) {
auto const actual =
Format("error message",
ErrorContext{{{"key", "value"}, {"filename", "the-filename"}}});
EXPECT_THAT(actual, StartsWith("error message"));
EXPECT_THAT(actual, HasSubstr("key=value"));
EXPECT_THAT(actual, HasSubstr("filename=the-filename"));
}

} // namespace
} // namespace internal
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
} // namespace cloud
} // namespace google