Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Working with list operations

Some services return potentially large lists of items, such as rows or resource descriptions. To keep CPU and memory usage under control, services return these resources in pages: smaller subsets of the items with a continuation token to request the next subset.

Iterating over items by page can be tedious. The client libraries provide adapters to convert the pages into asynchronous iterators. This guide shows you how to work with these adapters.

Prerequisites

This guide uses the Secret Manager service to demonstrate list operations, but the concepts apply to other services as well.

You may want to follow the service quickstart, which shows you how to enable the service and ensure that you've logged in and that your account has the necessary permissions.

For complete setup instructions for the Rust libraries, see Setting up your development environment.

Dependencies

Add the Secret Manager library to your Cargo.toml file:

cargo add google-cloud-secretmanager-v1

Iterating list methods

To help iterate the items in a list method, the APIs return an implementation of the ItemPaginator trait. Introduce it into scope via a use declaration:

    use google_cloud_gax::paginator::ItemPaginator as _;

To iterate the items, use the by_item function.

    let mut list = client
        .list_secrets()
        .set_parent(format!("projects/{project_id}"))
        .by_item();
    while let Some(secret) = list.next().await {
        let secret = secret?;
        println!("  secret={}", secret.name)
    }

In rare cases, pages might contain extra information that you need access to. Or you may need to checkpoint your progress across processes. In these cases, you can iterate over full pages instead of individual items.

First introduce Paginator into scope via a use declaration:

    use google_cloud_gax::paginator::Paginator as _;

Then iterate over the pages using by_page:

    let mut list = client
        .list_secrets()
        .set_parent(format!("projects/{project_id}"))
        .by_page();
    while let Some(page) = list.next().await {
        let page = page?;
        println!("  next_page_token={}", page.next_page_token);
        page.secrets
            .into_iter()
            .for_each(|secret| println!("    secret={}", secret.name));
    }

Working with futures::Stream

You may want to use these APIs in the larger Rust ecosystem of asynchronous streams, such as tokio::Stream. This is readily done, but you must first enable the unstable-streams feature in the google_cloud_gax crate:

cargo add google-cloud-gax --features unstable-stream

The name of this feature is intended to convey that we consider these APIs unstable, because they are! You should only use them if you are prepared to deal with any breaks that result from incompatible changes to the futures::Stream trait.

The following examples also use the futures::stream::StreamExt trait, which you enable by adding the futures crate.

cargo add futures

Add the required use declarations:

    use futures::stream::StreamExt;
    use google_cloud_gax::paginator::ItemPaginator as _;

Then use the into_stream function to convert ItemPaginator into a futures::Stream of items.

    let list = client
        .list_secrets()
        .set_parent(format!("projects/{project_id}"))
        .by_item()
        .into_stream();
    list.map(|secret| -> gax::Result<()> {
        println!("  secret={}", secret?.name);
        Ok(())
    })
    .fold(Ok(()), async |acc, result| -> gax::Result<()> {
        acc.and(result)
    })
    .await?;

Similarly, you can use the into_stream function to convert Paginator into a futures::Stream of pages.

    let list = client
        .list_secrets()
        .set_parent(format!("projects/{project_id}"))
        .by_page()
        .into_stream();
    list.enumerate()
        .map(|(index, page)| -> gax::Result<()> {
            println!("page={}, next_page_token={}", index, page?.next_page_token);
            Ok(())
        })
        .fold(Ok(()), async |acc, result| -> gax::Result<()> {
            acc.and(result)
        })
        .await?;

Resuming list methods by setting next page token

In some cases, such as an interrupted list operation, you can set the next page token to resume paginating from a specific page.

    let page = client
        .list_secrets()
        .set_parent(format!("projects/{project_id}"))
        .send()
        .await;
    let page = page?;
    let mut next_page_token = page.next_page_token.clone();
    page.secrets
        .into_iter()
        .for_each(|secret| println!("    secret={}", secret.name));

    while !next_page_token.is_empty() {
        println!("  next_page_token={next_page_token}");

        let page = client
            .list_secrets()
            .set_parent(format!("projects/{project_id}"))
            .set_page_token(next_page_token)
            .send()
            .await;
        let page = page?;
        next_page_token = page.next_page_token.clone();

        page.secrets
            .into_iter()
            .for_each(|secret| println!("    secret={}", secret.name));
    }

Additional paginator technical details

The standard Google API List method follows the pagination guideline defined by AIP-158. Each call to a List method for a resource returns a page of resource items (e.g. secrets) along with a next-page token that can be passed to the List method to retrieve the next page.

The Google Cloud Client Libraries for Rust provide an adapter to convert the list RPCs as defined by AIP-4233 into a streams that can be iterated over in an async fashion.

What's next

Learn more about working with the Cloud Client Libraries for Rust: