Gestione degli errori

Offri un'esperienza utente più coerente interpretando e rispondendo in modo proattivo agli errori. Che tu stia sviluppando flussi di lavoro cloud automatizzati o interagendo con le API remote, le librerie client Rust forniscono modi per gestire gli errori in modo corretto. Questa guida spiega come:

  • Gestire gli errori: esaminare i tipi di errore e ramificare la logica dell'applicazione in base ai codici di stato del servizio, ad esempio creando una risorsa mancante quando si verifica un errore NotFound.
  • Esaminare i dettagli dell'errore: estrarre ed esaminare i dettagli degli errori, ad esempio le violazioni dei campi delle richieste errate o gli errori di quota, restituiti dai Google Cloud servizi per risolvere i problemi delle API e modificare dinamicamente il comportamento di runtime.
  • Risolvere gli errori di binding: interpretare e risolvere gli errori di binding HTTP lato client causati da campi di richiesta non validi o mancanti per garantire che le richieste raggiungano il servizio senza problemi.

Prerequisiti

Questa guida utilizza il servizio Secret Manager e l' API Cloud Natural Language per dimostrare la gestione degli errori. Per eseguire gli esempi, procedi nel seguente modo:

  1. Abilita il servizio Secret Manager.
  2. Attiva l'API Cloud Natural Language.
  3. Configura l'autenticazione.

Dipendenze

Utilizza il comando seguente per aggiungere le dipendenze richieste al file Cargo.toml:

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

Gestisci gli errori

Le librerie client Rust ti consentono di visualizzare gli errori e reagire ad essi. Ad esempio, puoi utilizzare il rilevamento degli errori per ramificare il comportamento: un pattern comune nei servizi cloud è utilizzare una risorsa come se il container esistesse, creando il container solo se si verifica un errore. Se il container esiste in genere, questo approccio è più efficiente rispetto al controllo dell'esistenza del container prima di effettuare la richiesta.

L'esempio seguente mostra come gestire una risorsa mancante rilevando l'errore durante il tentativo di aggiornare un secret di Secret Manager e creandolo se non esiste già.

  1. Prova a creare una nuova versione del secret:

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

  2. Se update_attempt ha esito positivo, stampa il risultato positivo e restituisci:

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

  3. Se update_attempt non riesce, devi disambiguare la causa dell'errore. La richiesta potrebbe non essere riuscita per molti motivi, ad esempio una connessione interrotta o un errore con i token di autenticazione. I criteri di ripetizione possono gestire la maggior parte di questi errori. Cerca gli errori restituiti dal servizio:

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

  4. Cerca un errore che corrisponda a un secret mancante:

    if status.code == Code::NotFound {

  5. Se hai riscontrato un errore "non trovato" (Code::NotFound), prova a creare il secret:

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

  6. Prova di nuovo ad aggiungere la versione del secret. Questa volta, restituisci un errore se si verifica un problema:

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

Esempio di codice: funzione principale (sample)

Il codice completo di questo esempio è suddiviso in tre parti: la funzione di orchestrazione principale (sample), seguita dai due metodi helper (update_attempt e create_secret).

La funzione sample tenta di aggiungere una nuova versione a un secret. Rileva l'errore restituito dal client e verifica se si tratta di un errore Code::NotFound. Se il secret non viene trovato, la funzione crea il secret inizialmente mancante e riprova l'aggiornamento.

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)
        }
    }
}

Esempio di codice: metodo helper (update_attempt)

Il metodo helper update_attempt tenta di aggiungere una versione del secret, calcolando il checksum CRC32c dei dati del payload:

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)
}

Esempio di codice: metodo helper (create_secret)

Il metodo helper create_secret crea un secret mancante e configura un criterio di ripetizione personalizzato:

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)
}

Esamina i dettagli dell'errore

Alcuni Google Cloud servizi includono dettagli aggiuntivi sugli errori quando le richieste non vanno a buon fine. Per facilitare la risoluzione dei problemi, le librerie client Rust includono questi dettagli durante la formattazione degli errori utilizzando std::fmt::Display. Puoi esaminare questi dettagli e modificare il comportamento dell'applicazione di conseguenza.

Solo gli errori restituiti dal servizio contengono informazioni dettagliate. Le librerie client restituiscono un' StatusDetails enumerazione con i diversi tipi di dettagli degli errori.

Estrai i dettagli dell'errore

Questo esempio invia intenzionalmente una richiesta errata all' API Cloud Natural Language ed esamina l'errore risultante.

  1. Crea un client:

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

  2. Invia una richiesta (in questo esempio manca un campo chiave):

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

  3. Estrai l'errore dal risultato utilizzando le funzioni Rust standard. Il tipo di errore stampa tutti i dettagli dell'errore in formato leggibile:

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

L'output è simile al seguente:

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: {},
                    },
                ),
            ],
        },
    },
}

Esamina i dettagli dell'errore a livello di programmazione

A volte potrebbe essere necessario esaminare i dettagli dell'errore a livello di programmazione. Questo esempio attraversa la struttura dei dati e stampa i campi più pertinenti.

Solo gli errori restituiti dal servizio contengono informazioni dettagliate, quindi esegui una query sull'errore per verificare se contiene il tipo di errore corretto. In caso affermativo, puoi suddividere alcune informazioni di primo livello sull'errore:

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

Itera sui dettagli:

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

Come accennato in precedenza, le librerie client restituiscono un' StatusDetails enumerazione con i diversi tipi di dettagli degli errori. Questo esempio esamina solo gli errori BadRequest:

StatusDetails::BadRequest(bad) => {

Un BadRequest contiene un elenco di campi in violazione. Puoi iterare e stampare i dettagli di ciascuno:

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

Queste informazioni possono essere utili durante lo sviluppo. Altri rami di StatusDetails, come QuotaFailure, potrebbero essere utili in fase di runtime per limitare un'applicazione.

Output previsto:

L'output dei dettagli dell'errore è simile al seguente:

  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."

Esempio di codice: esamina i dettagli dell'errore

La funzione sample invia intenzionalmente una richiesta non valida all'API Cloud Natural Language per generare un errore del servizio. Poi rileva l'errore ed estrae a livello di programmazione StatusDetails per esaminare e stampare violazioni specifiche del campo BadRequest.

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(())
}

Risolvi gli errori di binding

Quando utilizzi HTTP per inviare richieste ai Google Cloud servizi, la richiesta utilizza un URI (Uniform Resource Identifier) per specificare una risorsa. Alcuni RPC corrispondono a più URI e il contenuto della richiesta determina l'URI utilizzato.

La libreria client considera tutti gli URI possibili e restituisce un errore di binding solo se nessun URI funziona. In genere, questo accade quando un campo è mancante o in un formato non valido.

Se la richiesta non fornisce campi contenenti un formato valido per qualsiasi URI possibile, potresti riscontrare un errore di binding:

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/*'

L'errore dell'esempio precedente si è verificato perché l'esempio tenta di recuperare i dettagli di una risorsa senza fornire il relativo nome. In particolare, il campo name field in un GetSecretRequest è obbligatorio, ma non è impostato dall'esempio:

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

Come correggere gli errori di binding

Per correggere l'errore, imposta il campo obbligatorio in modo che corrisponda a uno dei modelli mostrati nel messaggio di errore:

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

Entrambi i modelli consentono alla libreria client di effettuare una richiesta al server. Ad esempio, il seguente codice corrisponde al primo modello:

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

In alternativa, il seguente codice corrisponde al secondo modello:

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

Interpretazione dei modelli

Il messaggio di errore per un errore di binding include stringhe di modelli che mostrano i valori possibili per i campi della richiesta. La maggior parte delle stringhe di modelli include * e ** come caratteri jolly per trovare la corrispondenza con i valori dei campi.

Carattere jolly singolo

Il carattere jolly * da solo indica una stringa non vuota senza /. Può essere considerato come l'espressione regolare [^/]+.

Ecco alcuni esempi:

Modello Input Corrispondenza?
* simple-string-123 true
projects/* projects/p true
projects/*/locations projects/p/locations true
projects/*/locations/* projects/p/locations/l true
* "" (vuoto) false
* string/with/slashes false
projects/* projects/ (vuoto) false
projects/* projects/p/ (barra extra) false
projects/* projects/p/locations/l false
projects/*/locations projects/p false
projects/*/locations projects/p/locations/l false

Carattere jolly doppio

Meno comune è il carattere jolly **, che indica qualsiasi stringa. La stringa può essere vuota o contenere un numero qualsiasi di barre (/). Può essere considerata come l' espressione regolare .*.

Quando un modello termina con /**, la barra iniziale è facoltativa.

Modello Input Corrispondenza?
** "" 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

Esamina gli errori di binding

Se devi esaminare l'errore a livello di programmazione, verifica se si tratta di un errore di binding ed esegui il downcast a un BindingError:

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");

Passaggi successivi