Penanganan error

Memberikan pengalaman pengguna yang lebih konsisten dengan menafsirkan dan merespons error secara proaktif. Baik Anda mengembangkan alur kerja cloud otomatis atau berinteraksi dengan API jarak jauh, library klien Rust menyediakan cara untuk menangani error dengan baik. Panduan ini menjelaskan cara:

  • Tangani error: Periksa jenis error dan buat logika aplikasi Anda berdasarkan kode status layanan, seperti membuat resource yang tidak ada saat terjadi error NotFound.
  • Periksa detail error: Ekstrak dan periksa detail error lengkap—seperti pelanggaran kolom permintaan yang salah atau kegagalan kuota—yang ditampilkan oleh Google Cloud layanan untuk memecahkan masalah API dan menyesuaikan perilaku runtime secara dinamis.
  • Atasi error pengikatan: Tafsirkan dan atasi error pengikatan HTTP sisi klien yang disebabkan oleh kolom permintaan yang tidak valid atau tidak ada untuk memastikan permintaan Anda mencapai layanan dengan lancar.

Prasyarat

Panduan ini menggunakan layanan Secret Manager dan Cloud Natural Language API untuk mendemonstrasikan penanganan error. Untuk menjalankan contoh, pertama:

  1. Aktifkan layanan Secret Manager.
  2. Aktifkan Cloud Natural Language API.
  3. Siapkan autentikasi.

Dependensi

Gunakan perintah berikut untuk menambahkan dependensi yang diperlukan ke file Cargo.toml Anda:

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

Menangani error

Library klien Rust memungkinkan Anda menampilkan dan bereaksi terhadap error. Misalnya, Anda dapat menggunakan penemuan error untuk membuat perilaku bercabang: pola umum dalam layanan cloud adalah menggunakan resource seolah-olah container untuk resource tersebut ada, dan hanya membuat container jika Anda mengalami error. Jika container biasanya ada, pendekatan ini lebih efisien daripada memeriksa apakah container ada sebelum membuat permintaan.

Contoh berikut menunjukkan cara menangani resource yang tidak ada dengan menangkap error saat mencoba memperbarui secret Secret Manager—dan membuatnya jika belum ada.

  1. Lakukan upaya untuk membuat versi secret baru:

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

  2. Jika update_attempt berhasil, cetak hasil yang berhasil dan tampilkan:

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

  3. Jika update_attempt gagal, Anda harus membedakan penyebab kegagalan tersebut. Permintaan mungkin gagal karena banyak alasan, seperti koneksi terputus atau error pada token autentikasi. Kebijakan percobaan ulang dapat menangani sebagian besar error ini. Cari error yang ditampilkan oleh layanan:

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

  4. Cari error yang sesuai dengan rahasia yang tidak ada:

    if status.code == Code::NotFound {

  5. Jika Anda mengalami error "not found" (Code::NotFound), coba buat secret:

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

  6. Coba tambahkan versi rahasia lagi. Kali ini, tampilkan error jika ada yang gagal:

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

Contoh kode: fungsi utama (sample)

Kode lengkap untuk contoh ini dibagi menjadi tiga bagian: fungsi pengaturan utama (sample), diikuti dengan dua metode pembantunya (update_attempt dan create_secret).

Fungsi sample mencoba menambahkan versi baru ke secret. Fungsi ini menangkap error yang ditampilkan oleh klien dan memeriksa apakah error tersebut adalah error Code::NotFound. Jika rahasia tidak ditemukan, fungsi akan membuat rahasia yang awalnya tidak ada dan mencoba lagi pembaruan.

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

Contoh kode: metode helper (update_attempt)

Metode helper update_attempt mencoba menambahkan versi secret, menghitung checksum CRC32c data 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)
}

Contoh kode: metode helper (create_secret)

Metode helper create_secret membuat secret yang tidak ada dan mengonfigurasi kebijakan percobaan ulang yang disesuaikan:

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

Periksa detail error

Beberapa layanan Google Cloud menyertakan detail error tambahan saat permintaan gagal. Untuk membantu pemecahan masalah, library klien Rust menyertakan detail ini saat memformat error menggunakan std::fmt::Display. Anda dapat memeriksa detail ini dan mengubah perilaku aplikasi Anda sesuai dengan itu.

Hanya error yang ditampilkan oleh layanan yang berisi informasi mendetail. Library klien menampilkan enum StatusDetails dengan berbagai jenis detail error.

Mengekstrak detail error

Contoh ini sengaja mengirim permintaan yang salah ke Cloud Natural Language API dan memeriksa error yang dihasilkan.

  1. Buat klien:

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

  2. Kirim permintaan (dalam contoh ini, kolom kunci tidak ada):

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

  3. Ekstrak error dari hasil menggunakan fungsi Rust standar. Jenis error mencetak semua detail error dalam bentuk yang dapat dibaca manusia:

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

Outputnya mirip dengan hal berikut ini:

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

Memeriksa detail error secara terprogram

Terkadang Anda mungkin perlu memeriksa detail error secara terprogram. Contoh ini melintasi struktur data dan mencetak kolom yang paling relevan.

Hanya error yang ditampilkan oleh layanan yang berisi informasi mendetail, jadi kueri error terlebih dahulu untuk melihat apakah error tersebut berisi jenis error yang benar. Jika ya, Anda dapat mengelompokkan beberapa informasi tingkat teratas tentang error:

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

Ulangi detailnya:

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

Seperti yang disebutkan sebelumnya, library klien menampilkan enum StatusDetails dengan berbagai jenis detail error. Contoh ini hanya memeriksa error BadRequest:

StatusDetails::BadRequest(bad) => {

BadRequest berisi daftar kolom yang melanggar. Anda dapat melakukan iterasi dan mencetak detail untuk setiap item:

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

Informasi tersebut dapat berguna selama pengembangan. Cabang lain dari StatusDetails, seperti QuotaFailure, mungkin berguna saat runtime untuk membatasi aplikasi.

Output yang diharapkan

Output dari detail error mirip dengan yang berikut ini:

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

Contoh kode: Periksa detail error

Fungsi sample mengirimkan permintaan yang sengaja tidak valid ke Cloud Natural Language API untuk menghasilkan error layanan. Kemudian, error akan ditangkap dan StatusDetails diekstrak secara terprogram untuk memeriksa dan mencetak pelanggaran kolom BadRequest tertentu.

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

Mengatasi error pengikatan

Saat menggunakan HTTP untuk mengirim permintaan ke layanan Google Cloud , permintaan menggunakan Uniform Resource Identifier (URI) untuk menentukan resource. Beberapa RPC sesuai dengan beberapa URI, dan konten permintaan menentukan URI mana yang digunakan.

Library klien mempertimbangkan semua kemungkinan URI dan hanya menampilkan error pengikatan jika tidak ada URI yang berfungsi. Biasanya, hal ini terjadi saat kolom tidak ada atau dalam format yang tidak valid.

Jika permintaan Anda gagal menyediakan kolom yang berisi format valid untuk kemungkinan URI apa pun, Anda mungkin mengalami error pengikatan:

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

Error contoh di atas terjadi karena contoh mencoba mengambil detail resource tanpa memberikan namanya. Secara khusus, kolom name pada GetSecretRequest wajib diisi, tetapi tidak ditetapkan oleh contoh:

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

Cara memperbaiki error pengikatan

Untuk memperbaiki error, tetapkan kolom wajib diisi agar cocok dengan salah satu template yang ditampilkan dalam pesan error:

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

Salah satu template memungkinkan library klien membuat permintaan ke server. Misalnya, kode berikut cocok dengan template pertama:

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

Atau, kode berikut cocok dengan template kedua:

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

Template interpretasi

Pesan error untuk error pengikatan mencakup string template yang menunjukkan kemungkinan nilai untuk kolom permintaan. Sebagian besar string template menyertakan * dan ** sebagai karakter pengganti untuk mencocokkan nilai kolom.

Karakter pengganti tunggal

Karakter pengganti * saja berarti string yang tidak kosong tanpa /. Dapat dianggap sebagai ekspresi reguler [^/]+.

Berikut beberapa contohnya:

Template Input Cocok?
* simple-string-123 true
projects/* projects/p true
projects/*/locations projects/p/locations true
projects/*/locations/* projects/p/locations/l true
* "" (kosong) false
* string/with/slashes false
projects/* projects/ (kosong) false
projects/* projects/p/ (garis miring tambahan) false
projects/* projects/p/locations/l false
projects/*/locations projects/p false
projects/*/locations projects/p/locations/l false

Karakter pengganti ganda

Yang kurang umum adalah karakter pengganti **, yang berarti string apa pun. String dapat kosong atau berisi sejumlah garis miring (/). String ini dapat dianggap sebagai ekspresi reguler .*.

Jika template diakhiri dengan /**, garis miring awal bersifat opsional.

Template Input Cocok?
** "" 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

Memeriksa error pengikatan

Jika Anda perlu memeriksa error secara terprogram, periksa apakah error tersebut adalah error pengikatan dan lakukan downcast ke 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");

Langkah berikutnya