Skip to content

Commit 8847ffa

Browse files
authored
doc(common): in-depth guide for StatusOr (#10555)
1 parent 52747e4 commit 8847ffa

7 files changed

Lines changed: 228 additions & 1 deletion

File tree

google/cloud/BUILD.bazel

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,3 +357,12 @@ load(":google_cloud_cpp_rest_protobuf_internal_unit_tests.bzl", "google_cloud_cp
357357
"@com_google_googletest//:gtest_main",
358358
],
359359
) for test in google_cloud_cpp_rest_protobuf_internal_unit_tests]
360+
361+
[cc_test(
362+
name = sample.replace("/", "_").replace(".cc", ""),
363+
srcs = [sample],
364+
tags = ["integration-test"],
365+
deps = [
366+
"//google/cloud/testing_util:google_cloud_cpp_testing_private",
367+
],
368+
) for sample in glob(["samples/*.cc"])]

google/cloud/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
set(DOXYGEN_PROJECT_NAME "Google Cloud C++ Client")
1818
set(DOXYGEN_PROJECT_BRIEF "C++ Client Library for Google Cloud Platform")
1919
set(DOXYGEN_PROJECT_NUMBER "${PROJECT_VERSION}")
20+
set(DOXYGEN_EXAMPLE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/samples")
2021
set(DOXYGEN_EXCLUDE_SYMBOLS "internal" "testing_util" "examples")
2122

2223
# Creates the proto headers needed by doxygen.

google/cloud/doc/common-main.dox

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,8 @@ implementation details and subject to change and/or removal without notice.
2828
@warning The symbols in the `google::cloud::testing_util` namespace are
2929
implementation details and subject to change and/or removal without notice.
3030

31+
## More information
32+
33+
- @ref common-error-handling for more details about how the libraries report
34+
run-time errors and how you can handle them.
3135
*/
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/**
2+
@page common-error-handling Error Handling
3+
4+
[StatusOr<T>]: @ref google::cloud::StatusOr
5+
[StatusOr<T>::value()]: @ref google::cloud::StatusOr::value()
6+
[StreamRange<S>]: @ref google::cloud::StreamRange
7+
[AsyncStreamingReadWriteRpc<T,U>]: @ref google::cloud::AsyncStreamingReadWriteRpc
8+
[future<T>]: @ref google::cloud::future
9+
[RuntimeStatusError]: @ref google::cloud::RuntimeStatusError
10+
[error_info()]: @ref google::cloud::Status::error_info()
11+
[input iterators]: https://en.cppreference.com/w/cpp/named_req/InputIterator
12+
[iostream operator<<]: @ref google::cloud::operator<<(std::ostream&,google::cloud::Status const&)
13+
14+
@par Overview
15+
16+
In general, the `google-cloud-cpp` libraries return a [StatusOr<T>] if a
17+
function may fail and needs to signal an error. `StatusOr<T>` is an "outcome",
18+
it contains either the value returned on success, or a description of the
19+
error. Errors are represented by @ref google::cloud::Status, thus the name.
20+
If you are familiar with `std::expected` from C++23, `StatusOr<T>` plays a
21+
similar role, but does not attempt to be compatible with it.
22+
23+
If you are planning to log a `Status`, consider using the [iostream operator<<].
24+
A `Status` contains more than just the message, in particular, its
25+
[error_info()] member function may return additional information that is useful
26+
during troubleshooting.
27+
28+
@par Stream Ranges
29+
30+
Some functions return [StreamRange<S>], where `S` is a `StatusOr<T>`. These
31+
ranges provide [input iterators] that paginate or stream results from a service,
32+
offering a more idiomatic API. The value type in these iterators is
33+
`StatusOr<T>` because the stream may fail after it has successfully returned
34+
some values. For example, if the request to obtain the next page of results
35+
fails, or if the underlying stream is interrupted by the service.
36+
37+
@par Futures
38+
39+
Some functions return a "future" ([future<T>]). These objects represent a value
40+
that will be obtained asynchronously. By the very nature of asynchronous
41+
operations, the request may fail after the function is called. Therefore, we
42+
have chosen to return `future<StatusOr<T>>`. We think the alternatives are
43+
either incorrect (e.g. `StatusOr<future<T>>` can only handle errors detected
44+
before the function returns), or overly complex
45+
(`StatusOr<future<StatusOr<T>>>`).
46+
47+
@par Values with specific error handling
48+
49+
Some functions return a value that already has a mechanism to signal failures.
50+
For example, some functions return [AsyncStreamingReadWriteRpc<T,U>] (or
51+
technically `std::unique_ptr<AsyncStreamingReadWriteRpc<T,U>>`). A small number
52+
of functions return classes derived from `std::istream` or `std::ostream.
53+
In such cases, the library does not wrap the result in a `StatusOr<T>` because
54+
the returned type already has mechanisms to signal errors.
55+
56+
@par Example: Using StatusOr<T>
57+
58+
You can check that a `StatusOr<T>` contains a value by calling the `.ok()`
59+
method, or by using `operator bool()` (like with other smart pointers). If
60+
there is no value, you can access the contained `Status` object using the
61+
`.status()` member. If there is a value, you may access it by dereferencing
62+
with `operator*()` or `operator->()`. As with all smart pointers, callers must
63+
first check that the `StatusOr<T>` contains a value before dereferencing and
64+
accessing the contained value.
65+
66+
@snippet samples.cc status-or-usage
67+
68+
@par Example: Using StatusOr<T> with exceptions
69+
70+
Some applications prefer to throw exceptions on errors. In this case, consider
71+
using [StatusOr<T>::value()]. This function throws a [RuntimeStatusError] if
72+
there is no value, and returns the value otherwise.
73+
74+
@note If you're compiling with exceptions disabled, calling `.value()` on a
75+
`StatusOr<T>` that does not contain a value will terminate the program
76+
instead of throwing.
77+
78+
@snippet samples.cc status-or-exceptions
79+
80+
@par Error Handling in google-cloud-cpp code samples
81+
82+
The code samples for `google-cloud-cpp` try to emphasize how to use specific
83+
APIs and often have minimal error handling. A typical code sample may simply
84+
throw the status on error, like so:
85+
86+
@code {.cpp}
87+
namespace svc = ::google::cloud::some_service;
88+
[](svc::Client client, std::string const& key) {
89+
auto response = client.SomeRpc(key);
90+
if (!response) throw std::move(response).status();
91+
// ... example continues here ...
92+
}
93+
@endcode
94+
95+
This should not be interpreted as a best practice. If your application is
96+
designed to work with exceptions, then using [StatusOr<T>::value()] is a better
97+
alternative. We just want to show that some error handling is required for these
98+
functions, but at the same time we don't want to obscure the example with a lot
99+
of error handling code.
100+
101+
@see @ref google::cloud::StatusOr
102+
@see @ref google::cloud::Status the class used to describe errors.
103+
@see @ref google::cloud::future for more details on the type returned
104+
by asynchronous operations.
105+
@see https://en.cppreference.com/w/cpp/utility/expected for more information
106+
about `std::expected`
107+
108+
*/

google/cloud/google_cloud_cpp_common.cmake

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,3 +404,7 @@ if (BUILD_TESTING)
404404
google_cloud_cpp_add_common_options(${target})
405405
endforeach ()
406406
endif ()
407+
408+
if (BUILD_TESTING AND GOOGLE_CLOUD_CPP_ENABLE_CXX_EXCEPTIONS)
409+
google_cloud_cpp_add_samples_relative("common" "samples/")
410+
endif ()

google/cloud/samples/samples.cc

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright 2023 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+
// Generated by the Codegen C++ plugin.
16+
// If you make any local changes, they will be lost.
17+
// source: google/cloud/run/v2/revision.proto
18+
19+
#include "google/cloud/project.h"
20+
#include "google/cloud/testing_util/example_driver.h"
21+
#include <fstream>
22+
#include <iostream>
23+
#include <string>
24+
#include <vector>
25+
26+
namespace {
27+
28+
using ::google::cloud::testing_util::Usage;
29+
30+
void StatusOrUsage(std::vector<std::string> const& argv) {
31+
if (argv.size() != 1 || argv[0] == "--help") {
32+
throw Usage{"status-or-usage <project-name>"};
33+
}
34+
//! [status-or-usage]
35+
namespace gc = ::google::cloud;
36+
[](std::string const& project_name) {
37+
gc::StatusOr<gc::Project> project = gc::MakeProject(project_name);
38+
if (!project) {
39+
std::cerr << "Error parsing project <" << project_name
40+
<< ">: " << project.status() << "\n";
41+
return;
42+
}
43+
std::cout << "The project id is " << project->project_id() << "\n";
44+
}
45+
//! [status-or-usage]
46+
(argv.at(0));
47+
}
48+
49+
void StatusOrExceptions(std::vector<std::string> const& argv) {
50+
if (argv.size() != 1 || argv[0] == "--help") {
51+
throw Usage{"status-or-exceptions <project-name>"};
52+
}
53+
//! [status-or-exceptions]
54+
namespace gc = ::google::cloud;
55+
[](std::string const& project_name) {
56+
try {
57+
gc::Project project = gc::MakeProject(project_name).value();
58+
std::cout << "The project id is " << project.project_id() << "\n";
59+
} catch (gc::RuntimeStatusError const& ex) {
60+
std::cerr << "Error parsing project <" << project_name
61+
<< ">: " << ex.status() << "\n";
62+
}
63+
}
64+
//! [status-or-exceptions]
65+
(argv.at(0));
66+
}
67+
68+
void AutoRun(std::vector<std::string> const& argv) {
69+
namespace examples = ::google::cloud::testing_util;
70+
if (!argv.empty()) throw examples::Usage{"auto"};
71+
72+
std::cout << "\nRunning StatusOrUsage() example [1]" << std::endl;
73+
StatusOrUsage({"invalid-project-name"});
74+
75+
std::cout << "\nRunning StatusOrUsage() example [2]" << std::endl;
76+
StatusOrUsage({"projects/my-project-id"});
77+
78+
std::cout << "\nRunning StatusOrExceptions() example [1]" << std::endl;
79+
StatusOrExceptions({"invalid-project-name"});
80+
81+
std::cout << "\nRunning StatusOrExceptions() example [2]" << std::endl;
82+
StatusOrExceptions({"projects/my-project-id"});
83+
}
84+
85+
} // namespace
86+
87+
int main(int argc, char* argv[]) { // NOLINT(bugprone-exception-escape)
88+
google::cloud::testing_util::Example example({
89+
{"status-or-usage", StatusOrUsage},
90+
{"status-or-exceptions", StatusOrExceptions},
91+
{"auto", AutoRun},
92+
});
93+
return example.Run(argc, argv);
94+
}

google/cloud/status.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,6 @@ class Status {
135135
friend inline bool operator!=(Status const& a, Status const& b) {
136136
return !(a == b);
137137
}
138-
friend std::ostream& operator<<(std::ostream& os, Status const& s);
139138

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

150+
/**
151+
* Stream @p s to @p os.
152+
*
153+
* This in intended for logging and troubleshooting. Applications should not
154+
* depend on the format of this output.
155+
*/
156+
std::ostream& operator<<(std::ostream& os, Status const& s);
157+
151158
/**
152159
* A runtime error that wraps a `google::cloud::Status`.
153160
*/

0 commit comments

Comments
 (0)