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

doc(common): in-depth guide for StatusOr #10555

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
9 changes: 9 additions & 0 deletions google/cloud/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -357,3 +357,12 @@ load(":google_cloud_cpp_rest_protobuf_internal_unit_tests.bzl", "google_cloud_cp
"@com_google_googletest//:gtest_main",
],
) for test in google_cloud_cpp_rest_protobuf_internal_unit_tests]

[cc_test(
name = sample.replace("/", "_").replace(".cc", ""),
srcs = [sample],
tags = ["integration-test"],
deps = [
"//google/cloud/testing_util:google_cloud_cpp_testing_private",
],
) for sample in glob(["samples/*.cc"])]
1 change: 1 addition & 0 deletions google/cloud/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
set(DOXYGEN_PROJECT_NAME "Google Cloud C++ Client")
set(DOXYGEN_PROJECT_BRIEF "C++ Client Library for Google Cloud Platform")
set(DOXYGEN_PROJECT_NUMBER "${PROJECT_VERSION}")
set(DOXYGEN_EXAMPLE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/samples")
set(DOXYGEN_EXCLUDE_SYMBOLS "internal" "testing_util" "examples")

# Creates the proto headers needed by doxygen.
Expand Down
4 changes: 4 additions & 0 deletions google/cloud/doc/common-main.dox
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,8 @@ implementation details and subject to change and/or removal without notice.
@warning The symbols in the `google::cloud::testing_util` namespace are
implementation details and subject to change and/or removal without notice.

## More information

- @ref common-error-handling for more details about how the libraries report
run-time errors and how you can handle them.
*/
108 changes: 108 additions & 0 deletions google/cloud/doc/error-handling.dox
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
@page common-error-handling Error Handling

[StatusOr<T>]: @ref google::cloud::StatusOr
[StatusOr<T>::value()]: @ref google::cloud::StatusOr::value()
[StreamRange<S>]: @ref google::cloud::StreamRange
[AsyncStreamingReadWriteRpc<T,U>]: @ref google::cloud::AsyncStreamingReadWriteRpc
[future<T>]: @ref google::cloud::future
[RuntimeStatusError]: @ref google::cloud::RuntimeStatusError
[error_info()]: @ref google::cloud::Status::error_info()
[input iterators]: https://en.cppreference.com/w/cpp/named_req/InputIterator
[iostream operator<<]: @ref google::cloud::operator<<(std::ostream&,google::cloud::Status const&)

@par Overview

In general, the `google-cloud-cpp` libraries return a [StatusOr<T>] if a
function may fail and needs to signal an error. `StatusOr<T>` is an "outcome",
it contains either the value returned on success, or a description of the
error. Errors are represented by @ref google::cloud::Status, thus the name.
If you are familiar with `std::expected` from C++23, `StatusOr<T>` plays a
similar role, but does not attempt to be compatible with it.

If you are planning to log a `Status`, consider using the [iostream operator<<].
A `Status` contains more than just the message, in particular, its
[error_info()] member function may return additional information that is useful
during troubleshooting.

@par Stream Ranges

Some functions return [StreamRange<S>], where `S` is a `StatusOr<T>`. These
ranges provide [input iterators] that paginate or stream results from a service,
offering a more idiomatic API. The value type in these iterators is
`StatusOr<T>` because the stream may fail after it has successfully returned
some values. For example, if the request to obtain the next page of results
fails, or if the underlying stream is interrupted by the service.

@par Futures

Some functions return a "future" ([future<T>]). These objects represent a value
that will be obtained asynchronously. By the very nature of asynchronous
operations, the request may fail after the function is called. Therefore, we
have chosen to return `future<StatusOr<T>>`. We think the alternatives are
either incorrect (e.g. `StatusOr<future<T>>` can only handle errors detected
before the function returns), or overly complex
(`StatusOr<future<StatusOr<T>>>`).

@par Values with specific error handling

Some functions return a value that already has a mechanism to signal failures.
For example, some functions return [AsyncStreamingReadWriteRpc<T,U>] (or
technically `std::unique_ptr<AsyncStreamingReadWriteRpc<T,U>>`). A small number
of functions return classes derived from `std::istream` or `std::ostream.
In such cases, the library does not wrap the result in a `StatusOr<T>` because
the returned type already has mechanisms to signal errors.

@par Example: Using StatusOr<T>

You can check that a `StatusOr<T>` contains a value by calling the `.ok()`
method, or by using `operator bool()` (like with other smart pointers). If
there is no value, you can access the contained `Status` object using the
`.status()` member. If there is a value, you may access it by dereferencing
with `operator*()` or `operator->()`. As with all smart pointers, callers must
first check that the `StatusOr<T>` contains a value before dereferencing and
accessing the contained value.

@snippet samples.cc status-or-usage

@par Example: Using StatusOr<T> with exceptions

Some applications prefer to throw exceptions on errors. In this case, consider
using [StatusOr<T>::value()]. This function throws a [RuntimeStatusError] if
there is no value, and returns the value otherwise.

@note If you're compiling with exceptions disabled, calling `.value()` on a
`StatusOr<T>` that does not contain a value will terminate the program
instead of throwing.

@snippet samples.cc status-or-exceptions

@par Error Handling in google-cloud-cpp code samples

The code samples for `google-cloud-cpp` try to emphasize how to use specific
APIs and often have minimal error handling. A typical code sample may simply
throw the status on error, like so:

@code {.cpp}
namespace svc = ::google::cloud::some_service;
[](svc::Client client, std::string const& key) {
auto response = client.SomeRpc(key);
if (!response) throw std::move(response).status();
// ... example continues here ...
}
@endcode

This should not be interpreted as a best practice. If your application is
designed to work with exceptions, then using [StatusOr<T>::value()] is a better
alternative. We just want to show that some error handling is required for these
functions, but at the same time we don't want to obscure the example with a lot
of error handling code.

@see @ref google::cloud::StatusOr
@see @ref google::cloud::Status the class used to describe errors.
@see @ref google::cloud::future for more details on the type returned
by asynchronous operations.
@see https://en.cppreference.com/w/cpp/utility/expected for more information
about `std::expected`

*/
4 changes: 4 additions & 0 deletions google/cloud/google_cloud_cpp_common.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -404,3 +404,7 @@ if (BUILD_TESTING)
google_cloud_cpp_add_common_options(${target})
endforeach ()
endif ()

if (BUILD_TESTING AND GOOGLE_CLOUD_CPP_ENABLE_CXX_EXCEPTIONS)
google_cloud_cpp_add_samples_relative("common" "samples/")
endif ()
94 changes: 94 additions & 0 deletions google/cloud/samples/samples.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Copyright 2023 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.

// Generated by the Codegen C++ plugin.
// If you make any local changes, they will be lost.
// source: google/cloud/run/v2/revision.proto

#include "google/cloud/project.h"
#include "google/cloud/testing_util/example_driver.h"
#include <fstream>
#include <iostream>
#include <string>
#include <vector>

namespace {

using ::google::cloud::testing_util::Usage;

void StatusOrUsage(std::vector<std::string> const& argv) {
if (argv.size() != 1 || argv[0] == "--help") {
throw Usage{"status-or-usage <project-name>"};
}
//! [status-or-usage]
namespace gc = ::google::cloud;
[](std::string const& project_name) {
gc::StatusOr<gc::Project> project = gc::MakeProject(project_name);
if (!project) {
std::cerr << "Error parsing project <" << project_name
<< ">: " << project.status() << "\n";
return;
}
std::cout << "The project id is " << project->project_id() << "\n";
}
//! [status-or-usage]
(argv.at(0));
}

void StatusOrExceptions(std::vector<std::string> const& argv) {
if (argv.size() != 1 || argv[0] == "--help") {
throw Usage{"status-or-exceptions <project-name>"};
}
//! [status-or-exceptions]
namespace gc = ::google::cloud;
[](std::string const& project_name) {
try {
gc::Project project = gc::MakeProject(project_name).value();
std::cout << "The project id is " << project.project_id() << "\n";
} catch (gc::RuntimeStatusError const& ex) {
std::cerr << "Error parsing project <" << project_name
<< ">: " << ex.status() << "\n";
}
}
//! [status-or-exceptions]
(argv.at(0));
}

void AutoRun(std::vector<std::string> const& argv) {
namespace examples = ::google::cloud::testing_util;
if (!argv.empty()) throw examples::Usage{"auto"};

std::cout << "\nRunning StatusOrUsage() example [1]" << std::endl;
StatusOrUsage({"invalid-project-name"});

std::cout << "\nRunning StatusOrUsage() example [2]" << std::endl;
StatusOrUsage({"projects/my-project-id"});

std::cout << "\nRunning StatusOrExceptions() example [1]" << std::endl;
StatusOrExceptions({"invalid-project-name"});

std::cout << "\nRunning StatusOrExceptions() example [2]" << std::endl;
StatusOrExceptions({"projects/my-project-id"});
}

} // namespace

int main(int argc, char* argv[]) { // NOLINT(bugprone-exception-escape)
google::cloud::testing_util::Example example({
{"status-or-usage", StatusOrUsage},
{"status-or-exceptions", StatusOrExceptions},
devbww marked this conversation as resolved.
Show resolved Hide resolved
{"auto", AutoRun},
});
return example.Run(argc, argv);
}
9 changes: 8 additions & 1 deletion google/cloud/status.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ class Status {
friend inline bool operator!=(Status const& a, Status const& b) {
return !(a == b);
}
friend std::ostream& operator<<(std::ostream& os, Status const& s);

private:
static bool Equals(Status const& a, Status const& b);
Expand All @@ -148,6 +147,14 @@ class Status {
std::unique_ptr<Impl> impl_;
};

/**
* Stream @p s to @p os.
*
* This in intended for logging and troubleshooting. Applications should not
* depend on the format of this output.
*/
std::ostream& operator<<(std::ostream& os, Status const& s);

/**
* A runtime error that wraps a `google::cloud::Status`.
*/
Expand Down