Skip to content

Commit 3bfaf67

Browse files
committed
docs: user guide on error handling
1 parent 6aa4f59 commit 3bfaf67

File tree

8 files changed

+280
-20
lines changed

8 files changed

+280
-20
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

guide/samples/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,8 @@ categories.workspace = true
2828
name = "getting_started"
2929

3030
[dependencies]
31-
tokio = { version = "1", features = ["full", "macros"] }
31+
crc32c = "0.6"
32+
tokio = { version = "1", features = ["full", "macros"] }
3233
# ANCHOR: longrunning
3334
google-cloud-longrunning = { version = "0.22", path = "../../src/generated/longrunning" }
3435
# ANCHOR_END: longrunning

guide/samples/src/error_handling.rs

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Copyright 2025 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+
//! Examples showing how to handle errors.
16+
17+
use google_cloud_gax as gax;
18+
use google_cloud_secretmanager_v1 as sm;
19+
20+
// ANCHOR: update-secret
21+
pub async fn update_secret(
22+
project_id: &str,
23+
secret_id: &str,
24+
data: Vec<u8>,
25+
) -> crate::Result<sm::model::SecretVersion> {
26+
// ANCHOR: update-secret-client
27+
let client = sm::client::SecretManagerService::builder().build().await?;
28+
// ANCHOR_END: update-secret-client
29+
30+
// ANCHOR: update-secret-initial-attempt
31+
match update_attempt(&client, project_id, secret_id, data.clone()).await {
32+
// ANCHOR_END: update-secret-initial-attempt
33+
// ANCHOR: update-secret-success
34+
Ok(version) => {
35+
println!("new version is {}", version.name);
36+
Ok(version)
37+
}
38+
// ANCHOR_END: update-secret-success
39+
// ANCHOR: update-secret-svc-error
40+
Err(e) => {
41+
if let Some(svc) = e.as_inner::<gax::error::ServiceError>() {
42+
// ANCHOR_END: update-secret-svc-error
43+
// ANCHOR: update-secret-not-found
44+
if is_not_found(svc) {
45+
// ANCHOR_END: update-secret-not-found
46+
// ANCHOR: update-secret-create
47+
let _ = create_secret(&client, project_id, secret_id).await?;
48+
// ANCHOR_END: update-secret-create
49+
// ANCHOR: update-secret-try-again
50+
let version = update_attempt(&client, project_id, secret_id, data).await?;
51+
println!("new version is {}", version.name);
52+
return Ok(version);
53+
// ANCHOR: update-secret-try-again
54+
}
55+
}
56+
Err(e.into())
57+
}
58+
}
59+
}
60+
// ANCHOR_END: client-retry
61+
62+
// ANCHOR: examine-error
63+
pub fn is_not_found(error: &gax::error::ServiceError) -> bool {
64+
let status = error.status();
65+
status.code == 404 || status.code == gax::error::rpc::Code::NotFound as i32
66+
}
67+
// ANCHOR_END: examine-error
68+
69+
// ANCHOR: update-attempt
70+
async fn update_attempt(
71+
client: &sm::client::SecretManagerService,
72+
project_id: &str,
73+
secret_id: &str,
74+
data: Vec<u8>,
75+
) -> gax::Result<sm::model::SecretVersion> {
76+
let checksum = crc32c::crc32c(&data) as i64;
77+
client
78+
.add_secret_version(format!("projects/{project_id}/secrets/{secret_id}"))
79+
.set_payload(
80+
sm::model::SecretPayload::new()
81+
.set_data(data)
82+
.set_data_crc32c(checksum),
83+
)
84+
.send()
85+
.await
86+
}
87+
// ANCHOR_END: update-attempt
88+
89+
// ANCHOR: create-secret
90+
pub async fn create_secret(
91+
client: &sm::client::SecretManagerService,
92+
project_id: &str,
93+
secret_id: &str,
94+
) -> gax::Result<sm::model::Secret> {
95+
use google_cloud_gax::options::RequestOptionsBuilder;
96+
use google_cloud_gax::retry_policy::AlwaysRetry;
97+
use google_cloud_gax::retry_policy::RetryPolicyExt;
98+
use std::time::Duration;
99+
100+
client
101+
.create_secret(format!("projects/{project_id}"))
102+
.with_retry_policy(
103+
AlwaysRetry
104+
.with_attempt_limit(5)
105+
.with_time_limit(Duration::from_secs(15)),
106+
)
107+
.set_secret_id(secret_id)
108+
.set_secret(
109+
sm::model::Secret::new()
110+
.set_replication(sm::model::Replication::new().set_replication(
111+
sm::model::replication::Replication::Automatic(
112+
sm::model::replication::Automatic::new().into(),
113+
),
114+
))
115+
.set_labels([("integration-test", "true")]),
116+
)
117+
.send()
118+
.await
119+
}
120+
// ANCHOR_END: create-secret

guide/samples/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
1818
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
1919

20+
pub mod error_handling;
2021
pub mod lro;
2122
pub mod polling_policies;
2223
pub mod retry_policies;

guide/samples/tests/driver.rs

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,7 @@ mod driver {
7474

7575
#[tokio::test(flavor = "multi_thread")]
7676
async fn retry_policies_request() -> user_guide_samples::Result<()> {
77-
use google_cloud_gax::options::RequestOptionsBuilder;
78-
use google_cloud_gax::retry_policy::AlwaysRetry;
79-
use google_cloud_gax::retry_policy::RetryPolicyExt;
8077
use google_cloud_secretmanager_v1 as sm;
81-
use std::time::Duration;
8278

8379
let project_id = std::env::var("GOOGLE_CLOUD_PROJECT").unwrap();
8480
let secret_id: String = rand::rng()
@@ -91,26 +87,49 @@ mod driver {
9187
// The sample will delete this secret. If that fails, the cleanup step
9288
// for the integration tests will garbage collect it in a couple of
9389
// days.
94-
let _ = client
95-
.create_secret(format!("projects/{project_id}"))
90+
let _ = user_guide_samples::error_handling::create_secret(&client, &project_id, &secret_id)
91+
.await?;
92+
user_guide_samples::retry_policies::request_retry(&client, &project_id, &secret_id).await
93+
}
94+
95+
#[tokio::test]
96+
async fn error_handling() -> user_guide_samples::Result<()> {
97+
use google_cloud_gax::retry_policy::AlwaysRetry;
98+
use google_cloud_gax::retry_policy::RetryPolicyExt;
99+
use google_cloud_secretmanager_v1 as sm;
100+
use std::time::Duration;
101+
102+
let project_id = std::env::var("GOOGLE_CLOUD_PROJECT").unwrap();
103+
let secret_id: String = rand::rng()
104+
.sample_iter(&Alphanumeric)
105+
.take(SECRET_ID_LENGTH)
106+
.map(char::from)
107+
.collect();
108+
109+
let client = sm::client::SecretManagerService::builder()
96110
.with_retry_policy(
97111
AlwaysRetry
98112
.with_attempt_limit(5)
99113
.with_time_limit(Duration::from_secs(15)),
100114
)
101-
.set_secret_id(&secret_id)
102-
.set_secret(
103-
sm::model::Secret::new()
104-
.set_replication(sm::model::Replication::new().set_replication(
105-
sm::model::replication::Replication::Automatic(
106-
sm::model::replication::Automatic::new().into(),
107-
),
108-
))
109-
.set_labels([("integration-test", "true")]),
110-
)
115+
.build()
116+
.await?;
117+
// The secret is immediately deleted. If that fails, the cleanup step
118+
// for the integration tests will garbage collect it in a couple of
119+
// days.
120+
let _ = user_guide_samples::error_handling::create_secret(&client, &project_id, &secret_id)
121+
.await?;
122+
let version = user_guide_samples::error_handling::update_secret(
123+
&project_id,
124+
&secret_id,
125+
"The quick brown fox jumps over the lazy dog".into(),
126+
)
127+
.await?;
128+
let _ = client.destroy_secret_version(&version.name).send().await?;
129+
let _ = client
130+
.delete_secret(format!("projects/{project_id}/secrets/{secret_id}"))
111131
.send()
112132
.await?;
113-
114-
user_guide_samples::retry_policies::request_retry(&client, &project_id, &secret_id).await
133+
Ok(())
115134
}
116135
}

guide/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ limitations under the License.
2121
- [Setting up Rust on Cloud Shell](setting_up_rust_on_cloud_shell.md)
2222
- [How to initialize a client](initialize_a_client.md)
2323
- [Configuring retry policies](configuring_retry_policies.md)
24+
- [Error Handling](error_handling.md)
2425
- [Working with long-running operations](working_with_long_running_operations.md)
2526
- [Configuring polling policies](configuring_polling_policies.md)
2627
- [How to write tests using a client](mock_a_client.md)

guide/src/error_handling.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<!--
2+
Copyright 2025 Google LLC
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
https://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
-->
16+
17+
# Error Handling
18+
19+
Sometimes applications need to branch based on the type and details of the
20+
error returned by the client library. This guide will show you how to write
21+
code to handle such errors.
22+
23+
> **Retryable errors:** one of the most common reasons to handle
24+
> errors in distributed systems is to retry requests that fail due to transient
25+
> errors. The Google Cloud client libraries for Rust implement a policy-based
26+
> retry loop. You only need to configure the policies to enable the retry loop,
27+
> and the libraries implement common retry policies. Consult the
28+
> [Configuring Retry Policies] section before implementing your own retry loop.
29+
30+
## Prerequisites
31+
32+
The guide uses the [Secret Manager] service, that makes the examples more
33+
concrete and therefore easier to follow. With that said, the same ideas work for
34+
any other service.
35+
36+
You may want to follow the service [quickstart]. This guide will walk you
37+
through the steps necessary to enable the service, ensure you have logged in,
38+
and that your account has the necessary permissions.
39+
40+
### Dependencies
41+
42+
As it is usual with Rust, you must declare the dependency in your
43+
`Cargo.toml` file. We use:
44+
45+
```toml
46+
{{#include ../samples/Cargo.toml:secretmanager}}
47+
```
48+
49+
## Motivation
50+
51+
In this guide we will create a new *secret version*. Secret versions are
52+
contained in *secrets*. One must create the secret before creating secret
53+
versions. A common pattern in cloud services is to use a resource as-if the
54+
container for it existed, and only create the container if there is an error.
55+
If the container exists most of the time, such an approach is more efficient
56+
than checking if the container exists before making the request. Checking if
57+
the container exists consumes more quota, results in more RPC charges, and is
58+
slower when the container already exists.
59+
60+
## Handling the error
61+
62+
First make an attempt to create a new secret version:
63+
64+
```rust,ignore
65+
{{#include ../samples/src/error_handling.rs:update-secret-initial-attempt}}
66+
```
67+
68+
If this succeeds, we can just print the successful result and return:
69+
70+
```rust,ignore
71+
{{#include ../samples/src/error_handling.rs:update-secret-success}}
72+
```
73+
74+
The request may have failed for many reasons: because the connection dropped
75+
before the request was fully sent, or the connection dropped before the response
76+
was received, or because it was impossible to create the authentication tokens.
77+
78+
The retry policies can deal with most of these errors, here we are interested
79+
only in errors returned by the service:
80+
81+
```rust,ignore
82+
{{#include ../samples/src/error_handling.rs:update-secret-svc-error}}
83+
```
84+
85+
and then only in errors that correspond to a missing secret:
86+
87+
```rust,ignore
88+
{{#include ../samples/src/error_handling.rs:update-secret-not-found}}
89+
```
90+
91+
```rust,ignore
92+
{{#include ../samples/src/error_handling.rs:examine-error}}
93+
```
94+
95+
If this is a "not found" error, we try to create the secret. This will simply
96+
return on failures:
97+
98+
```rust,ignore
99+
{{#include ../samples/src/error_handling.rs:update-secret-create}}
100+
```
101+
102+
Assuming the creation of the secret is successful, we can try to create the
103+
secret version again, this time just returning an error if anything fails:
104+
105+
```rust,ignore
106+
{{#include ../samples/src/error_handling.rs:update-secret-try-again}}
107+
```
108+
109+
## Error handling: complete code
110+
111+
```rust,ignore
112+
{{#include ../samples/src/error_handling.rs:update-secret}}
113+
```
114+
115+
[configuring retry policies]: /configuring_retry_policies.md
116+
[quickstart]: https://cloud.google.com/secret-manager/docs/quickstart
117+
[secret manager]: https://cloud.google.com/secret-manager

src/integration-tests/src/secret_manager/protobuf.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ async fn run_secret_versions(
246246
.await?;
247247
println!("DISABLE_SECRET_VERSION = {disable:?}");
248248

249-
println!("\nTesting disable_secret_version()");
249+
println!("\nTesting enable_secret_version()");
250250
let enable = client
251251
.enable_secret_version(&create_secret_version.name)
252252
.send()

0 commit comments

Comments
 (0)