Gestion des exceptions

Offrez une expérience utilisateur plus cohérente en interprétant les erreurs de manière proactive et en y répondant. Que vous développiez des workflows cloud automatisés ou que vous interagissiez avec des API à distance, les bibliothèques clientes Rust vous permettent de gérer les erreurs de manière fluide. Ce guide explique comment :

  • Gérer les erreurs : inspectez les types d'erreurs et branchez la logique de votre application en fonction des codes d'état du service, par exemple en créant une ressource manquante lorsque vous rencontrez une erreur NotFound.
  • Examiner les détails des erreurs : extrayez et examinez les détails des erreurs enrichis (par exemple, les violations de champs de requêtes incorrectes ou les échecs de quotas) renvoyés par Google Cloud les services pour résoudre les problèmes d'API et ajuster dynamiquement le comportement d'exécution.
  • Résoudre les erreurs de liaison : interprétez et résolvez les erreurs de liaison HTTP côté client causées par des champs de requête non valides ou manquants pour vous assurer que vos requêtes atteignent le service sans problème.

Prérequis

Ce guide utilise le service Secret Manager et l' API Cloud Natural Language pour illustrer la gestion des exceptions. Pour exécuter les exemples, procédez comme suit :

  1. Activez le service Secret Manager.
  2. Activez l'API Cloud Natural Language.
  3. Configurez l'authentification.

Dépendances

Utilisez la commande suivante pour ajouter les dépendances requises à votre fichier Cargo.toml :

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

Gérer les erreurs

Les bibliothèques clientes Rust vous permettent de détecter les erreurs et d'y réagir. Vous pouvez, par exemple, utiliser la détection des erreurs pour brancher le comportement : un modèle courant dans les services cloud consiste à utiliser une ressource comme si son conteneur existait, et à ne créer le conteneur qu'en cas d'erreur. Si le conteneur existe généralement, cette approche est plus efficace que de vérifier s'il existe avant d'envoyer la requête.

L'exemple suivant montre comment gérer une ressource manquante en interceptant l'erreur lors d'une tentative de mise à jour d'un secret Secret Manager et en le créant s'il n'existe pas déjà.

  1. Tentez de créer une version secrète :

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

  2. Si update_attempt réussit, affichez le résultat et renvoyez-le :

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

  3. Si update_attempt échoue, vous devez identifier la cause de l'échec. La requête peut avoir échoué pour de nombreuses raisons, par exemple en raison d'une connexion interrompue ou d'une erreur avec les jetons d'authentification. Les règles de nouvelle tentative peuvent gérer la plupart de ces erreurs. Recherchez les erreurs renvoyées par le service :

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

  4. Recherchez une erreur correspondant à un secret manquant :

    if status.code == Code::NotFound {

  5. Si vous avez rencontré une erreur "not found" (Code::NotFound), essayez de créer le secret :

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

  6. Essayez d'ajouter à nouveau la version secrète. Cette fois, renvoyez une erreur en cas d'échec :

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

Exemple de code : fonction principale (sample)

Le code complet de cet exemple est divisé en trois parties : la fonction d'orchestration principale (sample), suivie de ses deux méthodes d'assistance (update_attempt et create_secret).

La fonction sample tente d'ajouter une nouvelle version à un secret. Elle intercepte l'erreur renvoyée par le client et vérifie s'il s'agit d'une erreur Code::NotFound. Si le secret n'est pas trouvé, la fonction crée le secret initialement manquant et retente la mise à jour.

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

Exemple de code : méthode d'assistance (update_attempt)

La méthode d'assistance update_attempt tente d'ajouter une version secrète en calculant la somme de contrôle CRC32c des données de la charge utile :

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

Exemple de code : méthode d'assistance (create_secret)

La méthode d'assistance create_secret crée un secret manquant et configure une règle de nouvelle tentative personnalisée :

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

Examiner les détails des erreurs

Certains Google Cloud services incluent des détails d'erreur supplémentaires lorsque les requêtes échouent. Pour faciliter la résolution des problèmes, les bibliothèques clientes Rust incluent ces détails lors de la mise en forme des erreurs à l'aide de std::fmt::Display. Vous pouvez examiner ces détails et modifier le comportement de votre application en conséquence.

Seules les erreurs renvoyées par le service contiennent des informations détaillées. Les bibliothèques clientes renvoient une StatusDetails énumération avec les différents types de détails d'erreur.

Extraire les détails des erreurs

Cet exemple envoie intentionnellement une requête incorrecte à l' API Cloud Natural Language et examine l'erreur résultante.

  1. Créez un client :

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

  2. Envoyez une requête (dans cet exemple, un champ de clé est manquant) :

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

  3. Extrayez l'erreur du résultat à l'aide des fonctions Rust standards. Le type d'erreur affiche tous les détails de l'erreur sous une forme lisible :

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

Le résultat ressemble à ce qui suit :

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

Examiner les détails des erreurs par programmation

Vous devrez peut-être parfois examiner les détails des erreurs par programmation. Cet exemple parcourt la structure de données et affiche les champs les plus pertinents.

Seules les erreurs renvoyées par le service contiennent des informations détaillées. Interrogez donc d'abord l'erreur pour voir si elle contient le type d'erreur correct. Si c'est le cas, vous pouvez décomposer certaines informations de premier niveau sur l'erreur :

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

Parcourez les détails :

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

Comme indiqué précédemment, les bibliothèques clientes renvoient une StatusDetails énumération avec les différents types de détails d'erreur. Cet exemple n'examine que les erreurs BadRequest :

StatusDetails::BadRequest(bad) => {

Une BadRequest contient une liste de champs qui ne sont pas conformes. Vous pouvez parcourir et afficher les détails de chacun d'eux :

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

Ces informations peuvent être utiles lors du développement. D'autres branches de StatusDetails, telles que QuotaFailure, peuvent être utiles au moment de l'exécution pour limiter une application.

Résultat attendu

Le résultat des détails de l'erreur est semblable à ce qui suit :

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

Exemple de code : examiner les détails des erreurs

La fonction sample envoie intentionnellement une requête non valide à l'API Cloud Natural Language pour générer une erreur de service. Elle intercepte ensuite l'erreur et extrait par programmation les StatusDetails pour inspecter et afficher des violations de champs BadRequest spécifiques.

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

Résoudre les erreurs de liaison

Lorsque vous utilisez HTTP pour envoyer des requêtes à des Google Cloud services, la requête utilise un URI (Uniform Resource Identifier) pour spécifier une ressource. Certains RPC correspondent à plusieurs URI, et le contenu de la requête détermine l'URI utilisé.

La bibliothèque cliente prend en compte tous les URI possibles et ne renvoie une erreur de liaison que si aucun URI ne fonctionne. En règle générale, cela se produit lorsqu'un champ est manquant ou dans un format non valide.

Si votre requête ne fournit pas de champs contenant un format valide pour un URI possible, vous pouvez rencontrer une erreur de liaison :

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'erreur de l'exemple précédent s'est produite, car l'exemple tente de récupérer les détails d'une ressource sans fournir son nom. Plus précisément, le name champ d'une GetSecretRequest est obligatoire, mais n'est pas défini par l'exemple :

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

Corriger les erreurs de liaison

Pour corriger l'erreur, définissez le champ obligatoire afin qu'il corresponde à l'un des modèles affichés dans le message d'erreur :

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

L'un ou l'autre modèle permet à la bibliothèque cliente d'envoyer une requête au serveur. Par exemple, le code suivant correspond au premier modèle :

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

Le code suivant correspond au deuxième modèle :

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

Interpréter les modèles

Le message d'erreur d'une erreur de liaison inclut des chaînes de modèle indiquant les valeurs possibles pour les champs de la requête. La plupart des chaînes de modèle incluent * et ** comme caractères génériques pour correspondre aux valeurs des champs.

Caractère générique unique

Le caractère générique * seul signifie une chaîne non vide sans /. Il peut être considéré comme l'expression régulière [^/]+.

Voici quelques exemples :

Modèle Entrée Détection d'une correspondance
* simple-string-123 true
projects/* projects/p true
projects/*/locations projects/p/locations true
projects/*/locations/* projects/p/locations/l true
* "" (vide) false
* string/with/slashes false
projects/* projects/ (vide) false
projects/* projects/p/ (barre oblique supplémentaire) false
projects/* projects/p/locations/l false
projects/*/locations projects/p false
projects/*/locations projects/p/locations/l false

Double caractère générique

Le caractère générique **, moins courant, signifie n'importe quelle chaîne. La chaîne peut être vide ou contenir un nombre quelconque de barres obliques (/). Elle peut être considérée comme l' expression régulière .*.

Lorsqu'un modèle se termine par /**, la barre oblique initiale est facultative.

Modèle Entrée Détection d'une correspondance
** "" 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

Inspecter les erreurs de liaison

Si vous devez inspecter l'erreur par programmation, vérifiez s'il s'agit d'une erreur de liaison et effectuez un downcast vers 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");

Étape suivante