Fehlerbehandlung

Sorgen Sie für eine konsistentere Nutzererfahrung, indem Sie Fehler proaktiv interpretieren und darauf reagieren. Ob Sie automatisierte Cloud-Workflows entwickeln oder mit Remote-APIs interagieren: Die Rust-Clientbibliotheken bieten Möglichkeiten, Fehler auf elegante Weise zu verarbeiten. In dieser Anleitung erfahren Sie, wie Sie Folgendes tun:

  • Fehler verarbeiten:Untersuchen Sie Fehlertypen und verzweigen Sie Ihre Anwendungslogik basierend auf Dienststatuscodes, z. B. indem Sie eine fehlende Ressource erstellen, wenn ein NotFound-Fehler auftritt.
  • Fehlerdetails untersuchen: Extrahieren und untersuchen Sie detaillierte Fehlerdetails, z. B. Feldverletzungen bei ungültigen Anfragen oder Kontingentfehler, die von Google Cloud Diensten zurückgegeben werden, um API-Probleme zu beheben und das Laufzeitverhalten dynamisch anzupassen.
  • Bindungsfehler beheben:Interpretieren und beheben Sie clientseitige HTTP-Bindungsfehler, die durch ungültige oder fehlende Anfragenfelder verursacht werden, damit Ihre Anfragen den Dienst reibungslos erreichen.

Vorbereitung

In dieser Anleitung werden der Secret Manager-Dienst und die Cloud Natural Language API verwendet, um die Fehlerbehandlung zu veranschaulichen. So führen Sie die Beispiele aus:

  1. Aktivieren Sie den Secret Manager-D1ienst.
  2. Aktivieren Sie die Cloud Natural Language API.
  3. Richten Sie die Authentifizierung ein.

Abhängigkeiten

Verwenden Sie den folgenden Befehl, um die erforderlichen Abhängigkeiten zu Ihrer Cargo.toml-Datei hinzuzufügen:

cargo add google-cloud-secretmanager-v1 google-cloud-gax crc32c google-cloud-language-v2

Fehler verarbeiten

Mit den Rust-Clientbibliotheken können Sie Fehler erkennen und darauf reagieren. Sie können beispielsweise die Fehlererkennung verwenden, um das Verhalten zu verzweigen: Ein häufiges Muster in Clouddiensten besteht darin, eine Ressource so zu verwenden, als ob der Container dafür vorhanden wäre, und den Container nur zu erstellen, wenn ein Fehler auftritt. Wenn der Container normalerweise vorhanden ist, ist dieser Ansatz effizienter als zu prüfen, ob der Container vorhanden ist, bevor die Anfrage gesendet wird.

Im folgenden Beispiel wird gezeigt, wie Sie einen Fehler abfangen, wenn Sie versuchen, ein Secret Manager-Secret zu aktualisieren, und es erstellen, wenn es noch nicht vorhanden ist.

  1. Versuchen Sie, eine neue Secret-Version zu erstellen:

    match update_attempt(&client, project_id, secret_id, data.clone()).await {

  2. Wenn update_attempt erfolgreich ist, geben Sie das Ergebnis aus und kehren Sie zurück:

    Ok(version) => {
        println!("new version is {}", version.name);
        Ok(version)
    }

  3. Wenn update_attempt fehlschlägt, müssen Sie die Ursache des Fehlers ermitteln. Die Anfrage kann aus vielen Gründen fehlgeschlagen sein, z. B. aufgrund einer unterbrochenen Verbindung oder eines Fehlers mit Authentifizierungstokens. Wiederholungsrichtlinien können die meisten dieser Fehler beheben. Suchen Sie nach Fehlern, die vom Dienst zurückgegeben werden:

    Err(e) => {
        if let Some(status) = e.downcast_ref::<Error>().and_then(|e| e.status()) {

  4. Suchen Sie nach einem Fehler, der einem fehlenden Secret entspricht:

    if status.code == Code::NotFound {

  5. Wenn ein Fehler vom Typ „Nicht gefunden“ (Code::NotFound) aufgetreten ist, versuchen Sie, das Secret zu erstellen:

    let _ = create_secret(&client, project_id, secret_id).await?;

  6. Versuchen Sie noch einmal, die Secret-Version hinzuzufügen. Geben Sie dieses Mal einen Fehler zurück, wenn etwas fehlschlägt:

    let version = update_attempt(&client, project_id, secret_id, data).await?;
    println!("new version is {}", version.name);
    return Ok(version);

Codebeispiel: Hauptfunktion (sample)

Der vollständige Code für dieses Beispiel ist in drei Teile unterteilt: die Hauptorchestrierungsfunktion (sample) und die beiden Hilfsmethoden (update_attempt und create_secret).

Die Funktion sample versucht, einem Secret eine neue Version hinzuzufügen. Sie fängt den vom Client zurückgegebenen Fehler ab und prüft, ob es sich um einen Code::NotFound-Fehler handelt. Wenn das Secret nicht gefunden wird, erstellt die Funktion das ursprünglich fehlende Secret und wiederholt die Aktualisierung.

use google_cloud_gax::error::Error;
use google_cloud_gax::error::rpc::Code;
use google_cloud_secretmanager_v1::client::SecretManagerService;
use google_cloud_secretmanager_v1::model::SecretVersion;

pub async fn sample(
    project_id: &str,
    secret_id: &str,
    data: Vec<u8>,
) -> anyhow::Result<SecretVersion> {
    let client = SecretManagerService::builder().build().await?;

    match update_attempt(&client, project_id, secret_id, data.clone()).await {
        Ok(version) => {
            println!("new version is {}", version.name);
            Ok(version)
        }
        Err(e) => {
            if let Some(status) = e.downcast_ref::<Error>().and_then(|e| e.status()) {
                if status.code == Code::NotFound {
                    let _ = create_secret(&client, project_id, secret_id).await?;
                    let version = update_attempt(&client, project_id, secret_id, data).await?;
                    println!("new version is {}", version.name);
                    return Ok(version);
                }
            }
            Err(e)
        }
    }
}

Codebeispiel: Hilfsmethode (update_attempt)

Die Hilfsmethode update_attempt versucht, eine Secret-Version hinzuzufügen und berechnet die CRC32c-Prüfsumme der Nutzlastdaten:

use google_cloud_secretmanager_v1::client::SecretManagerService;
use google_cloud_secretmanager_v1::model::{SecretPayload, SecretVersion};

pub(crate) async fn update_attempt(
    client: &SecretManagerService,
    project_id: &str,
    secret_id: &str,
    data: Vec<u8>,
) -> anyhow::Result<SecretVersion> {
    let checksum = crc32c::crc32c(&data) as i64;
    let version = client
        .add_secret_version()
        .set_parent(format!("projects/{project_id}/secrets/{secret_id}"))
        .set_payload(
            SecretPayload::new()
                .set_data(data)
                .set_data_crc32c(checksum),
        )
        .send()
        .await?;
    Ok(version)
}

Codebeispiel: Hilfsmethode (create_secret)

Die Hilfsmethode create_secret erstellt ein fehlendes Secret und konfiguriert eine benutzerdefinierte Wiederholungsrichtlinie:

use google_cloud_gax::options::RequestOptionsBuilder;
use google_cloud_gax::retry_policy::AlwaysRetry;
use google_cloud_gax::retry_policy::RetryPolicyExt;
use google_cloud_secretmanager_v1::client::SecretManagerService;
use google_cloud_secretmanager_v1::model::{Replication, Secret, replication};
use std::time::Duration;

pub async fn create_secret(
    client: &SecretManagerService,
    project_id: &str,
    secret_id: &str,
) -> anyhow::Result<Secret> {
    let secret = client
        .create_secret()
        .set_parent(format!("projects/{project_id}"))
        .with_retry_policy(
            AlwaysRetry
                .with_attempt_limit(5)
                .with_time_limit(Duration::from_secs(60)),
        )
        .set_secret_id(secret_id)
        .set_secret(
            Secret::new()
                .set_replication(Replication::new().set_replication(
                    replication::Replication::Automatic(replication::Automatic::new().into()),
                ))
                .set_labels([("integration-test", "true")]),
        )
        .send()
        .await?;
    Ok(secret)
}

Fehlerdetails untersuchen

Einige Google Cloud Dienste enthalten zusätzliche Fehlerdetails, wenn Anfragen fehlschlagen. Zur Unterstützung bei der Fehlerbehebung enthalten die Rust-Clientbibliotheken diese Details, wenn Fehler mit std::fmt::Display formatiert werden. Sie können diese Details untersuchen und das Verhalten Ihrer Anwendung entsprechend ändern.

Nur Fehler, die vom Dienst zurückgegeben werden, enthalten detaillierte Informationen. Die Client bibliotheken geben eine StatusDetails Enum mit den verschiedenen Arten von Fehlerdetails zurück.

Fehlerdetails extrahieren

In diesem Beispiel wird absichtlich eine ungültige Anfrage an die Cloud Natural Language API gesendet und der resultierende Fehler untersucht.

  1. Erstellen Sie einen Client:

    let client = LanguageService::builder().build().await?;

  2. Senden Sie eine Anfrage. In diesem Beispiel fehlt ein Schlüsselfeld:

    let result = client
        .analyze_sentiment()
        .set_document(
            Document::new()
                // Missing document contents
                // .set_content("Hello World!")
                .set_type(Type::PlainText),
        )
        .send()
        .await;

  3. Extrahieren Sie den Fehler aus dem Ergebnis mit Standard-Rust-Funktionen. Der Fehlertyp gibt alle Fehlerdetails in für Menschen lesbarer Form aus:

    let err = result.expect_err("the request should have failed");
    println!("\nrequest failed with error {err:#?}");

Die Ausgabe sieht etwa so aus:

request failed with error Error {
    kind: Service {
        status_code: Some(
            400,
        ),
        headers: Some(
            {
                "vary": "X-Origin",
                "vary": "Referer",
                "vary": "Origin,Accept-Encoding",
                "content-type": "application/json; charset=UTF-8",
                "date": "Sat, 24 May 2025 17:19:49 GMT",
                "server": "scaffolding on HTTPServer2",
                "x-xss-protection": "0",
                "x-frame-options": "SAMEORIGIN",
                "x-content-type-options": "nosniff",
                "alt-svc": "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000",
                "accept-ranges": "none",
                "transfer-encoding": "chunked",
            },
        ),
        status: Status {
            code: InvalidArgument,
            message: "One of content, or gcs_content_uri must be set.",
            details: [
                BadRequest(
                    BadRequest {
                        field_violations: [
                            FieldViolation {
                                field: "document.content",
                                description: "Must have some text content to annotate.",
                                reason: "",
                                localized_message: None,
                                _unknown_fields: {},
                            },
                        ],
                        _unknown_fields: {},
                    },
                ),
            ],
        },
    },
}

Fehlerdetails programmatisch untersuchen

Manchmal müssen Sie die Fehlerdetails programmatisch untersuchen. In diesem Beispiel wird die Datenstruktur durchlaufen und die relevantesten Felder ausgegeben.

Nur Fehler, die vom Dienst zurückgegeben werden, enthalten detaillierte Informationen. Fragen Sie daher zuerst den Fehler ab, um zu prüfen, ob er den richtigen Fehlertyp enthält. Wenn dies der Fall ist, können Sie einige Informationen auf oberster Ebene zum Fehler aufschlüsseln:

if let Some(status) = err.status() {
    println!(
        "  status.code={}, status.message={}",
        status.code, status.message,
    );

Iterieren Sie über die Details:

for detail in status.details.iter() {
    match detail {

Wie bereits erwähnt, geben die Clientbibliotheken eine StatusDetails Enum mit den verschiedenen Arten von Fehlerdetails zurück. In diesem Beispiel werden nur BadRequest-Fehler untersucht:

StatusDetails::BadRequest(bad) => {

Ein BadRequest enthält eine Liste von Feldern, die gegen die Richtlinien verstoßen. Sie können die Details für jedes Feld durchlaufen und ausgeben:

for f in bad.field_violations.iter() {
    println!(
        "  the request field {} has a problem: \"{}\"",
        f.field, f.description
    );
}

Solche Informationen können bei der Entwicklung nützlich sein. Andere Zweige von StatusDetails, z. B. QuotaFailure, können zur Laufzeit nützlich sein, um eine Anwendung zu drosseln.

Erwartete Ausgabe

Die Ausgabe der Fehlerdetails sieht etwa so aus:

  status.code=400, status.message=One of content, or gcs_content_uri must be set., status.status=Some("INVALID_ARGUMENT")
  the request field document.content has a problem: "Must have some text content to annotate."

Codebeispiel: Fehlerdetails untersuchen

Die Funktion sample sendet eine absichtlich ungültige Anfrage an die Cloud Natural Language API, um einen Dienstfehler zu generieren. Anschließend fängt sie den Fehler ab und extrahiert programmatisch die StatusDetails, um bestimmte BadRequest-Feldverletzungen zu untersuchen und auszugeben.

use google_cloud_gax::error::rpc::StatusDetails;
use google_cloud_language_v2::client::LanguageService;
use google_cloud_language_v2::model::Document;
use google_cloud_language_v2::model::document::Type;

pub async fn sample() -> anyhow::Result<()> {
    let client = LanguageService::builder().build().await?;

    let result = client
        .analyze_sentiment()
        .set_document(
            Document::new()
                // Missing document contents
                // .set_content("Hello World!")
                .set_type(Type::PlainText),
        )
        .send()
        .await;

    let err = result.expect_err("the request should have failed");
    println!("\nrequest failed with error {err:#?}");

    if let Some(status) = err.status() {
        println!(
            "  status.code={}, status.message={}",
            status.code, status.message,
        );
        for detail in status.details.iter() {
            match detail {
                StatusDetails::BadRequest(bad) => {
                    for f in bad.field_violations.iter() {
                        println!(
                            "  the request field {} has a problem: \"{}\"",
                            f.field, f.description
                        );
                    }
                }
                _ => {
                    println!("  additional error details: {detail:?}");
                }
            }
        }
    }

    Ok(())
}

Bindungsfehler beheben

Wenn Sie HTTP verwenden, um Anfragen an Google Cloud Dienste zu senden, wird in der Anfrage ein URI (Uniform Resource Identifier) angegeben, um eine Ressource zu identifizieren. Einige RPCs entsprechen mehreren URIs und der Inhalt der Anfrage bestimmt, welcher URI verwendet wird.

Die Clientbibliothek berücksichtigt alle möglichen URIs und gibt nur einen Bindungsfehler zurück, wenn keine URIs funktionieren. Normalerweise tritt dies auf, wenn ein Feld fehlt oder ein ungültiges Format hat.

Wenn Ihre Anfrage keine Felder mit einem gültigen Format für einen möglichen URI enthält, kann ein Bindungsfehler auftreten:

Error: cannot find a matching binding to send the request: at least one of the
conditions must be met: (1) field `name` needs to be set and match the template:
'projects/*/secrets/*' OR (2) field `name` needs to be set and match the
template: 'projects/*/locations/*/secrets/*'

Der Fehler im vorherigen Beispiel ist aufgetreten, weil im Beispiel versucht wird, die Details einer Ressource abzurufen, ohne ihren Namen anzugeben. Insbesondere ist das name Feld in einer GetSecretRequest erforderlich, wird aber im Beispiel nicht festgelegt:

let secret = client
    .get_secret()
    //.set_name("projects/my-project/secrets/my-secret")
    .send()
    .await;

Bindungsfehler beheben

Legen Sie das erforderliche Feld so fest, dass es mit einer der Vorlagen in der Fehlermeldung übereinstimmt, um den Fehler zu beheben:

  • 'projects/*/secrets/*'
  • 'projects/*/locations/*/secrets/*'

Mit beiden Vorlagen kann die Clientbibliothek eine Anfrage an den Server senden. Der folgende Code entspricht beispielsweise der ersten Vorlage:

let secret = client
    .get_secret()
    .set_name("projects/my-project/secrets/my-secret")
    .send()
    .await;

Alternativ entspricht der folgende Code der zweiten Vorlage:

let secret = client
    .get_secret()
    .set_name("projects/my-project/locations/us-central1/secrets/my-secret")
    .send()
    .await;

Vorlagen interpretieren

Die Fehlermeldung für einen Bindungsfehler enthält Vorlagenstrings mit möglichen Werten für die Anfragenfelder. Die meisten Vorlagenstrings enthalten * und ** als Platzhalter, um die Feldwerte abzugleichen.

Einzelner Platzhalter

Der * Platzhalter allein steht für einen nicht leeren String ohne /. Er kann als regulärer Ausdruck [^/]+ betrachtet werden.

Hier sind einige Beispiele:

Vorlage Eingabe Übereinstimmung?
* simple-string-123 true
projects/* projects/p true
projects/*/locations projects/p/locations true
projects/*/locations/* projects/p/locations/l true
* "" (leer) false
* string/with/slashes false
projects/* projects/ (leer) false
projects/* projects/p/ (zusätzlicher Schrägstrich) false
projects/* projects/p/locations/l false
projects/*/locations projects/p false
projects/*/locations projects/p/locations/l false

Doppelter Platzhalter

Weniger häufig ist der Platzhalter **, der für einen beliebigen String steht. Der String kann leer sein oder eine beliebige Anzahl von Schrägstrichen (/) enthalten. Er kann als der reguläre Ausdruck .* betrachtet werden.

Wenn eine Vorlage mit /** endet, ist der anfängliche Schrägstrich optional.

Vorlage Eingabe Übereinstimmung?
** "" true
** simple-string-123 true
** string/with/slashes true
projects/*/** projects/p true
projects/*/** projects/p/locations true
projects/*/** projects/p/locations/l true
projects/*/** locations/l false
projects/*/** projects//locations/l false

Bindungsfehler untersuchen

Wenn Sie den Fehler programmatisch untersuchen müssen, prüfen Sie, ob es sich um einen Bindungsfehler handelt, und wandeln Sie ihn in einen BindingError um:

let secret = client
    .get_secret()
    //.set_name("projects/my-project/secrets/my-secret")
    .send()
    .await;

let e = secret.unwrap_err();
assert!(e.is_binding(), "{e:?}");
assert!(e.source().is_some(), "{e:?}");
let _ = e
    .source()
    .and_then(|e| e.downcast_ref::<BindingError>())
    .expect("should be a BindingError");

Nächste Schritte