Como lidar com erros

Ofereça uma experiência do usuário mais consistente interpretando e respondendo a erros de maneira proativa. Se você estiver desenvolvendo fluxos de trabalho automatizados na nuvem ou interagindo com APIs remotas, as bibliotecas de cliente do Rust oferecem maneiras de lidar com erros normalmente. Este guia explica como:

  • Tratar erros:inspecione os tipos de erro e ramifique a lógica do aplicativo com base em códigos de status do serviço, como criar um recurso ausente ao encontrar um erro NotFound.
  • Examinar detalhes do erro: extraia e examine detalhes de erros avançados, como violações de campo de solicitação inválida ou falhas de cota, retornados por Google Cloud serviços para solucionar problemas de API e ajustar dinamicamente o comportamento de execução.
  • Resolver erros de vinculação:interprete e resolva erros de vinculação HTTP do lado do cliente causados por campos de solicitação inválidos ou ausentes para garantir que as solicitações cheguem ao serviço sem problemas.

Pré-requisitos

Este guia usa o serviço Secret Manager e a API Cloud Natural Language para demonstrar o tratamento de erros. Para executar os exemplos, primeiro:

  1. Ative o serviço Secret Manager.
  2. Ative a API Cloud Natural Language.
  3. Configure a autenticação.

Dependências

Use o comando a seguir para adicionar as dependências necessárias ao arquivo Cargo.toml:

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

Tratar erros

As bibliotecas de cliente do Rust permitem que você mostre e reaja a erros. Por exemplo, você pode usar a descoberta de erros para ramificar o comportamento: um padrão comum em serviços de nuvem é usar um recurso como se o contêiner dele existisse, criando o contêiner somente se você encontrar um erro. Se o contêiner geralmente existir, essa abordagem será mais eficiente do que verificar se o contêiner existe antes de fazer a solicitação.

O exemplo a seguir demonstra como tratar um recurso ausente capturando o erro ao tentar atualizar um secret do Secret Manager e criando-o se ele ainda não existir.

  1. Tente criar uma nova versão do secret:

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

  2. Se update_attempt for bem-sucedido, imprima o resultado e retorne:

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

  3. Se update_attempt falhar, você precisará desambiguar a causa da falha. A solicitação pode ter falhado por vários motivos, como uma conexão interrompida ou um erro com tokens de autenticação. As políticas de novas tentativas podem lidar com a maioria desses erros. Procure erros retornados pelo serviço:

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

  4. Procure um erro que corresponda a um secret ausente:

    if status.code == Code::NotFound {

  5. Se você encontrou um erro "não encontrado" (Code::NotFound), tente criar o secret:

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

  6. Tente adicionar a versão do secret novamente. Desta vez, retorne um erro se algo falhar:

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

Exemplo de código: função principal (sample)

O código completo deste exemplo é dividido em três partes: a função de orquestração principal (sample), seguida pelos dois métodos auxiliares (update_attempt e create_secret).

A função sample tenta adicionar uma nova versão a um secret. Ela captura o erro retornado pelo cliente e verifica se o erro é um Code::NotFound. Se o secret não for encontrado, a função criará o secret inicialmente ausente e tentará atualizar novamente.

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

Exemplo de código: método auxiliar (update_attempt)

O método auxiliar update_attempt tenta adicionar uma versão do secret, calculando a soma de verificação CRC32c dos dados de 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)
}

Exemplo de código: método auxiliar (create_secret)

O método auxiliar create_secret cria um secret ausente e configura uma política de novas tentativas personalizada:

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

Examinar detalhes do erro

Alguns Google Cloud serviços incluem mais detalhes de erros quando as solicitações falham. Para ajudar na solução de problemas, as bibliotecas de cliente do Rust incluem esses detalhes ao formatar erros usando std::fmt::Display. Você pode examinar esses detalhes e mudar o comportamento do aplicativo de acordo com eles.

Somente os erros retornados pelo serviço contêm informações detalhadas. As bibliotecas de cliente retornam uma StatusDetails enumeração com os diferentes tipos de detalhes de erros.

Extrair detalhes do erro

Este exemplo envia intencionalmente uma solicitação inválida para a API Cloud Natural Language e examina o erro resultante.

  1. Crie um cliente:

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

  2. Envie uma solicitação (neste exemplo, um campo de chave está ausente):

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

  3. Extraia o erro do resultado usando funções padrão do Rust. O tipo de erro imprime todos os detalhes do erro de forma legível:

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

O resultado será assim:

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

Examinar detalhes do erro de maneira programática

Às vezes, pode ser necessário examinar os detalhes do erro de maneira programática. Este exemplo percorre a estrutura de dados e imprime os campos mais relevantes.

Somente os erros retornados pelo serviço contêm informações detalhadas. Portanto, primeiro consulte o erro para verificar se ele contém o tipo de erro correto. Se for o caso, você poderá detalhar algumas informações de nível superior sobre o erro:

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

Itere os detalhes:

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

Como mencionado anteriormente, as bibliotecas de cliente retornam uma StatusDetails enumeração com os diferentes tipos de detalhes de erros. Este exemplo examina apenas erros BadRequest:

StatusDetails::BadRequest(bad) => {

Um BadRequest contém uma lista de campos que estão em violação. Você pode iterar e imprimir os detalhes de cada um deles:

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

Essas informações podem ser úteis durante o desenvolvimento. Outras ramificações de StatusDetails, como QuotaFailure, podem ser úteis no momento da execução para limitar um aplicativo.

Resposta esperada

A saída dos detalhes do erro é semelhante a esta:

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

Exemplo de código: examinar detalhes do erro

A função sample envia uma solicitação intencionalmente inválida para a API Cloud Natural Language para gerar um erro de serviço. Em seguida, ela captura o erro e extrai programaticamente o StatusDetails para inspecionar e imprimir violações de campo BadRequest específicas.

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

Resolver erros de vinculação

Ao usar HTTP para enviar solicitações aos Google Cloud serviços, a solicitação usa um identificador uniforme de recursos (URI, na sigla em inglês) para especificar um recurso. Algumas RPCs correspondem a vários URIs, e o conteúdo da solicitação determina qual URI é usado.

A biblioteca de cliente considera todos os URIs possíveis e só retorna um erro de vinculação se nenhum URI funcionar. Normalmente, isso acontece quando um campo está ausente ou em um formato inválido.

Se a solicitação não fornecer campos que contenham um formato válido para qualquer URI possível, você poderá encontrar um erro de vinculação:

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

O erro de exemplo anterior ocorreu porque o exemplo tenta recuperar os detalhes de um recurso sem fornecer o nome dele. Especificamente, o campo name field em um GetSecretRequest é obrigatório, mas não é definido pelo exemplo:

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

Como corrigir erros de vinculação

Para corrigir o erro, defina o campo obrigatório para que ele corresponda a um dos modelos mostrados na mensagem de erro:

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

Qualquer modelo permite que a biblioteca de cliente faça uma solicitação ao servidor. Por exemplo, o código a seguir corresponde ao primeiro modelo:

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

Como alternativa, o código a seguir corresponde ao segundo modelo:

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

Interpretação de modelos

A mensagem de erro de um erro de vinculação inclui strings de modelo que mostram valores possíveis para os campos de solicitação. A maioria das strings de modelo inclui * e ** como caracteres curinga para corresponder aos valores de campo.

Caractere curinga único

O caractere curinga * sozinho significa uma string não vazia sem um /. Ele pode ser considerado como a expressão regular [^/]+.

Veja alguns exemplos:

Modelo Entrada Corresponder?
* simple-string-123 true
projects/* projects/p true
projects/*/locations projects/p/locations true
projects/*/locations/* projects/p/locations/l true
* "" (vazio) false
* string/with/slashes false
projects/* projects/ (vazio) 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

Caractere curinga duplo

Menos comum é o caractere curinga **, que significa qualquer string. A string pode estar vazia ou conter qualquer número de barras (/). Ela pode ser considerada como a expressão regular .*.

Quando um modelo termina em /**, a barra inicial é opcional.

Modelo Entrada Corresponder?
** "" 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

Inspecionar erros de vinculação

Se você precisar inspecionar o erro de maneira programática, verifique se ele é um erro de vinculação e faça o downcast para um 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");

A seguir