From a6c38ab59ed25ff4a444c48f829109158095f9b6 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 19 Jun 2026 12:59:11 +0000 Subject: [PATCH] feat: add standalone "Test examples" CI step and complete docs coverage Align the client with the updated client requirements: - CI: add a standalone `Test examples` step that actually runs the documentation example code end-to-end (`cargo test --test examples`, each example program executed against the live API) plus the in-documentation doctests (`cargo test --doc`). `Run integration tests` now skips the example smoke tests so they run only in the dedicated step. - Docs: wire every docs/ page containing code into doctests via `#[doc = include_str!]` so all in-documentation snippets are compiled and checked (doctests 7 -> 21). - Docs: document response-model fields read by the README quick start and examples (ActorRun, Actor, Build, User, storage metadata, RequestQueueRequest, ActorStoreListItem, PaginationList and friends) and surface all copy-paste dependencies (serde_json, futures-util) in the install sections. - Bump version to 0.2.1 and record the change in CHANGELOG.md. --- .github/workflows/rust-integration-tests.yml | 24 +++-- CHANGELOG.md | 28 ++++++ Cargo.toml | 2 +- README.md | 11 ++- docs/README.md | 27 ++++-- docs/actors.md | 57 ++++++++++++ docs/builds.md | 3 + docs/misc.md | 75 +++++++++++++++- docs/runs.md | 49 ++++++++++ docs/storages.md | 94 +++++++++++++++++++- examples/iterate_store.rs | 3 +- src/lib.rs | 29 +++++- 12 files changed, 383 insertions(+), 19 deletions(-) diff --git a/.github/workflows/rust-integration-tests.yml b/.github/workflows/rust-integration-tests.yml index edeb2ef..85e0970 100644 --- a/.github/workflows/rust-integration-tests.yml +++ b/.github/workflows/rust-integration-tests.yml @@ -65,12 +65,24 @@ jobs: env: # The integration-test token is stored as a repository secret. APIFY_TOKEN: ${{ secrets.APIFY_TOKEN }} - # Limit parallelism to be gentle on the shared test account. - run: cargo test --verbose -- --test-threads=4 + # Limit parallelism to be gentle on the shared test account. The documentation example + # programs in `tests/examples.rs` (test names prefixed `example_`) are exercised by the + # standalone `Test examples` step below, so they are skipped here to keep the two + # concerns separate. + run: cargo test --lib --tests --verbose -- --skip example_ --test-threads=4 - - name: Run documentation example tests + # Standalone CI step that verifies the documentation examples actually work. It runs the + # example programs from `examples/` end-to-end against the live API (via the `example_*` + # smoke tests in `tests/examples.rs`, each of which executes `cargo run --example `) + # and runs the in-documentation code snippets as doctests (`cargo test --doc`, which + # compiles every fenced `rust` block in the README and the `docs/` pages and runs the + # runnable ones). Both are required by the documentation requirements: each documentation + # example has a CI test that actually runs the code. + - name: Test examples env: APIFY_TOKEN: ${{ secrets.APIFY_TOKEN }} - # Match the integration-test thread cap so doctests that hit the live API stay gentle - # on the shared account. - run: cargo test --doc -- --test-threads=4 + # Match the integration-test thread cap so the example programs and any doctests that hit + # the live API stay gentle on the shared account. + run: | + cargo test --test examples --verbose -- --test-threads=4 + cargo test --doc --verbose -- --test-threads=4 diff --git a/CHANGELOG.md b/CHANGELOG.md index b3cdfbc..4c02df5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,34 @@ All notable changes to the Rust Apify API client are documented here. The format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/). +## [0.2.1] - 2026-06-19 + +Compliance fix for the updated client/test requirements (apify-client-orchestration PR #4). +No changes to the public interface; CI and documentation-testing only. + +### Changed +- CI: added a standalone `Test examples` workflow step that verifies the documentation + examples actually work — it runs the `examples/` programs end-to-end against the live API + (the `example_*` smoke tests in `tests/examples.rs`, each invoking `cargo run --example`) and + runs the in-documentation code snippets as doctests (`cargo test --doc`). The example smoke + tests were previously executed as part of the `Run integration tests` step and the doctests in + a separate `Run documentation example tests` step; they are now consolidated under the + requirement-named `Test examples` step. `Run integration tests` now skips the `example_*` + tests (via `--skip example_`) so the two concerns stay separate. +- Documentation testing: the external `docs/` pages (`docs/README.md`, `docs/actors.md`, + `docs/misc.md`, `docs/storages.md`, `docs/runs.md`, `docs/builds.md`) are now compiled as + doctests via `#[doc = include_str!]` in `src/lib.rs`, so every in-documentation `rust` code + snippet is verified valid and runnable by `cargo test --doc`. Previously only the root + `README.md` snippets were doctest-checked. +- Documentation: added response-model field tables for the types the README Quick start and the + examples read but which were previously undocumented — `ActorRun` (incl. `id`, `status`, + `default_dataset_id`/`default_key_value_store_id`/`default_request_queue_id`) in + `docs/runs.md`; `Actor` (`id`, …) and `Build` (`id`, `status`, …) in `docs/actors.md` (with a + cross-reference from `docs/builds.md`); the shared storage-metadata fields of `Dataset` / + `KeyValueStore` / `RequestQueue` (incl. `id`) in `docs/storages.md`; and `User` (`id`, + `username`) in `docs/misc.md`. Each new section carries a runnable `no_run` doctest exercising + the documented fields. + ## [0.2.0] - 2026-06-19 Updated to Apify OpenAPI specification `v2-2026-06-18T095846Z` (previously diff --git a/Cargo.toml b/Cargo.toml index 5c4c410..65a33e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "apify-client" -version = "0.2.0" +version = "0.2.1" authors = ["Apify Technologies "] description = "The official Rust client for the Apify API (https://apify.com)." license = "Apache-2.0" diff --git a/README.md b/README.md index 8e73ae1..98fb285 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,8 @@ It provides a resource-oriented, async interface that mirrors the official - Transparent authentication, retries with exponential backoff, and timeouts. - Resource clients for Actors, runs, builds, tasks, datasets, key-value stores, request queues, schedules, webhooks, the Apify Store, users and logs. -- Convenience helpers: run/wait, log streaming (redirection), lazy Store iteration. +- Convenience helpers: run/wait, log streaming (redirection; needs the `futures-util` crate — + see [Installation](#installation)), lazy Store iteration. - A replaceable HTTP transport for testing or custom runtimes. ## Installation @@ -18,8 +19,16 @@ It provides a resource-oriented, async interface that mirrors the official [dependencies] apify-client = "0.2" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } +serde_json = "1" # for the `serde_json::Value` responses used in the Quick start ``` +The Quick start below reads dynamically-typed records with `serde_json::Value`, so a fresh +project needs `serde_json`. Two more dependencies are needed only for specific features: + +- `futures-util = "0.3"` — to consume `LogClient::stream()` (log streaming/redirection); it + provides the `StreamExt` trait used by the [`log_redirection`](examples/log_redirection.rs) + example. See [`docs/misc.md`](docs/misc.md#logs--clientlogbuild_or_run_id). + By default the client uses the system TLS (`native-tls`). To use rustls instead: ```toml diff --git a/docs/README.md b/docs/README.md index bb9a22f..9015f66 100644 --- a/docs/README.md +++ b/docs/README.md @@ -29,6 +29,11 @@ apify-client = "0.2" tokio = { version = "1", features = ["macros", "rt-multi-thread"] } ``` +Some snippets and examples need extra crates: add `serde_json = "1"` when you read +dynamically-typed responses as `serde_json::Value` (as the README Quick start does), and +`futures-util = "0.3"` to consume `LogClient::stream()` (log streaming — see +[Store, users and logs](misc.md#logs--clientlogbuild_or_run_id)). + Create a client and call a resource: ```rust,no_run @@ -45,10 +50,10 @@ async fn main() -> Result<(), Box> { ## Imports -All public types you need day-to-day — the client, the builder, and every option struct -(`ActorListOptions`, `ListOptions`, `StorageListOptions`, `StoreListOptions`, -`RunListOptions`, `ActorStartOptions`, `DatasetListItemsOptions`, `DownloadItemsFormat`, -`GetRecordOptions`, …) — are re-exported at the crate root, so you can import them directly +The client, the builder, and every option/parameter struct (`ActorListOptions`, `ListOptions`, +`StorageListOptions`, `StoreListOptions`, `RunListOptions`, `ActorStartOptions`, +`DatasetListItemsOptions`, `DownloadItemsFormat`, `GetRecordOptions`, …), plus the common +container `PaginationList`, are re-exported at the crate root, so you can import them directly from `apify_client`: ```rust,no_run @@ -56,7 +61,17 @@ use apify_client::{ApifyClient, ActorListOptions, StoreListOptions, DownloadItem ``` You do **not** need the longer `apify_client::clients::::` paths shown by -`cargo doc`'s module tree — the short crate-root path is the supported way to import them. +`cargo doc`'s module tree for these option types — the short crate-root path is the supported +way to import them. + +API resource/response **models** (`Actor`, `ActorRun`, `Build`, `Dataset`, `KeyValueStore`, +`RequestQueue`, `RequestQueueRequest`, `RequestQueueHead`, `RequestQueueOperationInfo`, +`KeyValueStoreKeysPage`, `ActorStoreListItem`, `User`, …) live in the [`apify_client::models`] +module and are imported from there: + +```rust,no_run +use apify_client::models::RequestQueueRequest; +``` ## `ApifyClient` and the builder @@ -111,7 +126,7 @@ Each example in [`../examples`](../examples) is runnable with |---|---| | `run_store_actor` | Run a Store Actor, wait, read its default dataset. | | `storages` | Create + write + read each storage type. | -| `get_account` | Fetch the current account. | +| `get_account` | Fetch the current account, plus its monthly usage (current cycle and for a specific date). | | `create_build_run_actor` | Create an Actor, build, run, fetch the run log. | | `run_and_last_run_storages` | Run an Actor, then read the last run's storages. | | `iterate_store` | Lazily iterate Store Actors. | diff --git a/docs/actors.md b/docs/actors.md index f33b95c..a2be47a 100644 --- a/docs/actors.md +++ b/docs/actors.md @@ -88,6 +88,63 @@ println!("validated against latest build: {result}"); The `build` argument accepts a build **tag** (e.g. `"latest"`, `"beta"`) or a build **number** (e.g. `"1.2.34"`); the referenced build must already exist for the API to resolve its schema. +## `Actor` fields + +`Actor` (from `apify_client::models`) is returned by `get`, `create`, `update`, and the Actor +`list`. The commonly-used fields — including the `actor.id` read in the +[README error-handling example](../README.md#error-handling) and the `create_build_run_actor` +example: + +| Field | Type | Description | +|---|---|---| +| `id` | `String` | Unique Actor ID (always present); used to build a `client.actor(&actor.id)` client. | +| `user_id` | `Option` | ID of the user who owns the Actor. | +| `name` | `Option` | Technical name used in API paths. | +| `username` | `Option` | Username of the Actor's owner. | +| `title` | `Option` | Human-readable title shown in the UI. | +| `description` | `Option` | Description of what the Actor does. | +| `is_public` | `Option` | Whether the Actor is published in Apify Store. | +| `created_at` | `Option>` | When the Actor was created. | +| `modified_at` | `Option>` | When the Actor was last modified. | +| `extra` | `Extra` | Any other fields returned by the API. | + +```rust,no_run +# use apify_client::ApifyClient; +# async fn run(client: ApifyClient) -> Result<(), Box> { +if let Some(actor) = client.actor("apify~hello-world").get().await? { + println!("actor {} ({:?})", actor.id, actor.title.or(actor.name)); +} +# Ok(()) +# } +``` + +## `Build` fields + +`Build` (from `apify_client::models`) is returned by `build`, `default_build` resolution, `get`, +`abort` and `wait_for_finish` (see also [builds](builds.md)). The fields the +`create_build_run_actor` example reads (`build.id`, `build.status`): + +| Field | Type | Description | +|---|---|---| +| `id` | `String` | Unique build ID (always present); used to build a `client.build(&build.id)` client. | +| `act_id` | `Option` | ID of the Actor that was built. | +| `status` | `Option` | Current build status; the terminal values match the run statuses. | +| `started_at` | `Option>` | When the build started. | +| `finished_at` | `Option>` | When the build finished. | +| `build_number` | `Option` | Build number, e.g. `0.1.2`. | +| `extra` | `Extra` | Any other fields returned by the API. | + +`Build::is_terminal()` reports whether `status` is a terminal value, mirroring `ActorRun`. + +```rust,no_run +# use apify_client::ApifyClient; +# async fn run(client: ApifyClient, build_id: &str) -> Result<(), Box> { +let build = client.build(build_id).wait_for_finish(Some(300)).await?; +println!("build {} status {:?}", build.id, build.status); +# Ok(()) +# } +``` + ## Actor versions and environment variables `ActorVersionClient`: `get`, `update`, `delete`, `env_var(name)`, `env_vars()`. diff --git a/docs/builds.md b/docs/builds.md index 3af907a..e18dacb 100644 --- a/docs/builds.md +++ b/docs/builds.md @@ -18,3 +18,6 @@ collections are available via `actor.builds()`. | `delete()` | — | `()` | Deletes the build. | | `wait_for_finish(wait_secs)` | `Option` | `Build` | Polls until the build is terminal. | | `log()` | — | `LogClient` | Access the build's log. | + +The returned `Build` model's fields (`id`, `status`, `build_number`, …) are documented in +[actors.md → `Build` fields](actors.md#build-fields). diff --git a/docs/misc.md b/docs/misc.md index 026f801..c8d6b73 100644 --- a/docs/misc.md +++ b/docs/misc.md @@ -12,8 +12,36 @@ `StoreListOptions`: `offset`, `limit`, `search`, `sort_by`, `category`, `username`, `pricing_model`. -`StoreActorIterator::next()` returns `Option`, fetching the next page -on demand until the listing is exhausted. +`StoreActorIterator::next()` is `async` and fallible — it returns +`ApifyClientResult>` (i.e. `Result, ApifyClientError>`), +fetching the next page on demand and yielding `Ok(None)` once the listing is exhausted. Drive it +with `.await?`: + +```rust,no_run +# use apify_client::{ApifyClient, StoreListOptions}; +# async fn run(client: ApifyClient) -> Result<(), Box> { +let mut iter = client.store().iterate(StoreListOptions::default()); +while let Some(actor) = iter.next().await? { + // `title` is the human-readable name; fall back to the technical `name`. + println!("{}: {:?}", actor.id, actor.title.or(actor.name)); +} +# Ok(()) +# } +``` + +`ActorStoreListItem` (from `apify_client::models`) is the element type yielded by both `list` +and the iterator. Its fields: + +| Field | Type | Description | +|---|---|---| +| `id` | `String` | Unique Actor ID (always present). | +| `name` | `Option` | Technical name of the Actor. | +| `username` | `Option` | Username of the Actor's owner. | +| `title` | `Option` | Human-readable title. | +| `extra` | `Extra` | Any other fields returned by the API. | + +`name`, `username` and `title` are optional, so a display routine typically prefers `title` +and falls back to `name` (e.g. `actor.title.or(actor.name)`). ## Users — `client.me()` / `client.user(id)` @@ -32,6 +60,25 @@ The methods marked **(`me` only)** operate on the authenticated account and are `Err(ApifyClientError::InvalidArgument(..))` without making a network request; `get()` is the only method that works for both `me` and other users. +`get()` returns a `User` (from `apify_client::models`). Its fields — including the `user.id` and +`user.username` the [`get_account`](../examples/get_account.rs) example reads: + +| Field | Type | Description | +|---|---|---| +| `id` | `String` | Unique user ID (always present). | +| `username` | `Option` | Username. | +| `extra` | `Extra` | Any other fields returned by the API (more fields are present for `me` than for a public `user(id)`). | + +```rust,no_run +# use apify_client::ApifyClient; +# async fn run(client: ApifyClient) -> Result<(), Box> { +if let Some(user) = client.me().get().await? { + println!("account id {}, username {:?}", user.id, user.username); +} +# Ok(()) +# } +``` + `monthly_usage()` is shorthand for `monthly_usage_for_date(None)` (current cycle). The client unwraps the API's `{ data: ... }` envelope, so the returned `Value` has the shape `{ usageCycle: { startAt, endAt }, monthlyServiceUsage, dailyServiceUsages, ... }`. Billing @@ -64,3 +111,27 @@ Also reachable via `run.log()` and `build.log()`. |---|---|---|---| | `get()` | — | `Option` | The entire log as text. | | `stream()` | — | `Stream>>` | Streams log chunks live (log redirection). | + +Consuming `stream()` requires the [`futures_util::StreamExt`] trait (from the `futures-util` +crate) in scope to call `.next()` on the returned stream. Add it to your `Cargo.toml`: + +```toml +[dependencies] +futures-util = "0.3" +``` + +Then redirect a run's log to stdout as it is produced: + +```rust,no_run +use apify_client::ApifyClient; +use futures_util::StreamExt; + +# async fn run(client: ApifyClient, run_id: &str) -> Result<(), Box> { +let mut stream = client.run(run_id).log().stream().await?; +while let Some(chunk) = stream.next().await { + let chunk = chunk?; + print!("{}", String::from_utf8_lossy(&chunk)); +} +# Ok(()) +# } +``` diff --git a/docs/runs.md b/docs/runs.md index b7593c8..09ca01e 100644 --- a/docs/runs.md +++ b/docs/runs.md @@ -34,3 +34,52 @@ collections are available via `actor.runs()` and `task.runs()`. values are `READY`, `RUNNING`, `SUCCEEDED`, `FAILED`, `ABORTING`, `ABORTED`, `TIMING-OUT`, and `TIMED-OUT`; the terminal ones (`SUCCEEDED`, `FAILED`, `ABORTED`, `TIMED-OUT`) are what `is_terminal()` reports and what `wait_for_finish` polls for. + +## `ActorRun` fields + +`ActorRun` (from `apify_client::models`) is returned by `start`, `call`, `get`, `abort`, +`wait_for_finish`, and the run `list`. The fields most callers read — including the +`run.status` / `run.default_dataset_id` accessed in the [README Quick start](../README.md#quick-start): + +| Field | Type | Description | +|---|---|---| +| `id` | `String` | Unique run ID (always present); used to build a `client.run(&run.id)` client. | +| `act_id` | `Option` | ID of the Actor that produced the run. | +| `actor_task_id` | `Option` | ID of the task that started the run, if any. | +| `user_id` | `Option` | ID of the user who owns the run. | +| `status` | `Option` | Current run status (see the status values above). | +| `status_message` | `Option` | Optional human-readable status message. | +| `started_at` | `Option>` | When the run started. | +| `finished_at` | `Option>` | When the run finished (absent while running). | +| `build_id` | `Option` | ID of the build used for the run. | +| `default_dataset_id` | `Option` | Default dataset ID — pass to `client.dataset(..)` to read results. | +| `default_key_value_store_id` | `Option` | Default key-value store ID for the run. | +| `default_request_queue_id` | `Option` | Default request queue ID for the run. | +| `container_url` | `Option` | URL of the run's container, while running. | +| `extra` | `Extra` | Any other fields returned by the API. | + +The three `default_*_id` fields are `Option` because they are only populated once the +run has its storages assigned; the storages are reachable directly via `run.dataset()`, +`run.key_value_store()` and `run.request_queue()` (see [storages](storages.md)). + +```rust,no_run +use apify_client::ApifyClient; + +# async fn run() -> Result<(), Box> { +let client = ApifyClient::new(std::env::var("APIFY_TOKEN")?); +let run = client + .actor("apify/hello-world") + .call::(None, Default::default(), None) + .await?; + +println!("run {} finished with status {:?}", run.id, run.status); +if let Some(dataset_id) = &run.default_dataset_id { + let items = client + .dataset(dataset_id) + .list_items::(Default::default()) + .await?; + println!("got {} item(s)", items.items.len()); +} +# Ok(()) +# } +``` diff --git a/docs/storages.md b/docs/storages.md index 53fe05b..75b698b 100644 --- a/docs/storages.md +++ b/docs/storages.md @@ -1,5 +1,35 @@ # Storages: datasets, key-value stores, request queues +## Storage metadata models (`Dataset`, `KeyValueStore`, `RequestQueue`) + +`get` and `get_or_create` on each storage collection/client return a metadata model from +`apify_client::models` (`Dataset`, `KeyValueStore`, `RequestQueue`). All three share a common +core; the `.id` field is what the examples read to build a per-storage client +(`client.dataset(&dataset.id)`, `client.key_value_store(&store.id)`, +`client.request_queue(&queue.id)`): + +| Field | Type | On | Description | +|---|---|---|---| +| `id` | `String` | all three | Unique storage ID (always present); pass to `client.dataset(..)` / `client.key_value_store(..)` / `client.request_queue(..)`. | +| `name` | `Option` | all three | Technical name, if the storage is named. | +| `user_id` | `Option` | all three | ID of the owner. | +| `created_at` | `Option>` | all three | When the storage was created. | +| `modified_at` | `Option>` | all three | When the storage was last modified. | +| `item_count` | `Option` | `Dataset` only | Total number of items in the dataset. | +| `total_request_count` | `Option` | `RequestQueue` only | Total number of requests ever added. | +| `extra` | `Extra` | all three | Any other fields returned by the API. | + +```rust,no_run +# use apify_client::ApifyClient; +# async fn run(client: ApifyClient) -> Result<(), Box> { +let dataset = client.datasets().get_or_create(None).await?; +// Use the metadata `id` to obtain a client for the storage itself. +let dataset_client = client.dataset(&dataset.id); +# let _ = dataset_client; +# Ok(()) +# } +``` + ## Datasets — `client.datasets()` / `client.dataset(id)` `DatasetCollectionClient`: `list(options: StorageListOptions)`, @@ -87,10 +117,72 @@ let scratch = client.datasets().get_or_create(None).await?; | `batch_delete_requests(requests)` | `&[impl Serialize]` | `Value` | Batch delete. | | `list_requests(options)` | `ListRequestsOptions { limit, exclusive_start_id, cursor, filter }` | `Value` | List requests (cursor/filter pagination). | | `paginate_requests(page_limit)` | `Option` | `RequestQueueRequestsIterator` | Lazy request iterator. | -| `list_and_lock_head(lock_secs, limit)` | `i64`, `Option` | `Value` | Lock head requests. | | `prolong_request_lock(id, lock_secs, forefront)` | `&str`, `i64`, `bool` | `Value` | Extend a lock. | | `delete_request_lock(id, forefront)` | `&str`, `bool` | `()` | Release a lock. | | `unlock_requests()` | — | `Value` | Release all this client's locks. | +### `RequestQueueRequest` and request-queue return types + +`RequestQueueRequest` (from `apify_client::models`) is the value passed to `add_request` / +`update_request` and returned by `get_request` / inside `RequestQueueHead`. Its fields: + +| Field | Type | Description | +|---|---|---| +| `id` | `Option` | Request ID assigned by the API; leave `None` when adding a new request. | +| `url` | `String` | The URL to process (required). | +| `unique_key` | `Option` | Dedup key (defaults to `url` server-side when omitted). | +| `method` | `Option` | HTTP method (defaults to `GET`). | +| `user_data` | `Option` | Arbitrary user data attached to the request. | +| `extra` | `Extra` | Any other fields returned by the API; use `Default::default()` when constructing. | + +Construct one and add it to a queue: + +```rust,no_run +use apify_client::models::RequestQueueRequest; +# use apify_client::ApifyClient; +# async fn run(client: ApifyClient) -> Result<(), Box> { +let queue = client.request_queues().get_or_create(None).await?; +let queue_client = client.request_queue(&queue.id); + +let request = RequestQueueRequest { + id: None, + url: "https://example.com/".to_string(), + unique_key: Some("example".to_string()), + method: Some("GET".to_string()), + user_data: None, + extra: Default::default(), +}; +let info = queue_client.add_request(&request, false).await?; +println!("added request {}", info.request_id); + +let head = queue_client.list_head(Some(10)).await?; +println!("{} request(s) at the head", head.items.len()); +# Ok(()) +# } +``` + +Relevant return-type fields: + +- `RequestQueueOperationInfo`: `request_id: String`, `was_already_present: bool`, + `was_already_handled: bool`. +- `RequestQueueHead`: `limit: i64`, `had_multiple_clients: bool`, + `items: Vec`, `extra: Extra` (any other fields returned by the API). +- `KeyValueStoreKeysPage`: `limit: i64`, `is_truncated: bool`, `exclusive_start_key`, + `next_exclusive_start_key` (both `Option`), `items: Vec`. + +## Common list container — `PaginationList` + +Offset/limit-paginated list methods (`list_items`, the various collection `list` methods, …) +return `PaginationList`. Re-exported at the crate root (`apify_client::PaginationList`). Fields: + +| Field | Type | Description | +|---|---|---| +| `total` | `i64` | Total items available across all pages. | +| `offset` | `i64` | Items skipped at the start. | +| `limit` | `i64` | Max items the API would return for this request. | +| `count` | `i64` | Items actually returned in this page. | +| `desc` | `bool` | Whether the items are in descending order. | +| `items` | `Vec` | The items of this page. | + The storage clients are also reachable from a run via `run.dataset()`, `run.key_value_store()` and `run.request_queue()`. diff --git a/examples/iterate_store.rs b/examples/iterate_store.rs index 9d51932..3e2cf0b 100644 --- a/examples/iterate_store.rs +++ b/examples/iterate_store.rs @@ -9,7 +9,8 @@ async fn main() -> Result<(), Box> { let token = std::env::var("APIFY_TOKEN").expect("set APIFY_TOKEN"); let client = ApifyClient::new(token); - // Iterate the store, fetching pages on demand. Stop after the first 10 actors. + // Iterate the store, fetching pages of 5 on demand (`limit` is the per-page size, not a + // total cap). The loop below stops after the first 10 actors regardless of page size. let mut iter = client.store().iterate(StoreListOptions { limit: Some(5), ..Default::default() diff --git a/src/lib.rs b/src/lib.rs index eb8e99a..d56df5c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,7 +67,34 @@ pub use clients::run::RunResurrectOptions; pub use clients::run_collection::RunListOptions; pub use clients::store_collection::StoreListOptions; -// Compile-test the code snippets in the README so the documentation stays valid. +// Compile-test the code snippets in the README and the external `docs/` pages so every +// in-documentation code snippet stays valid and runnable. Pulling each Markdown file in as +// a doctest source means `cargo test --doc` (the `Test examples` CI step) compiles every +// `rust` fenced block; `no_run` blocks are compiled but not executed, runnable blocks run. #[doc = include_str!("../README.md")] #[cfg(doctest)] struct ReadmeDoctests; + +#[doc = include_str!("../docs/README.md")] +#[cfg(doctest)] +struct DocsReadmeDoctests; + +#[doc = include_str!("../docs/actors.md")] +#[cfg(doctest)] +struct DocsActorsDoctests; + +#[doc = include_str!("../docs/misc.md")] +#[cfg(doctest)] +struct DocsMiscDoctests; + +#[doc = include_str!("../docs/storages.md")] +#[cfg(doctest)] +struct DocsStoragesDoctests; + +#[doc = include_str!("../docs/runs.md")] +#[cfg(doctest)] +struct DocsRunsDoctests; + +#[doc = include_str!("../docs/builds.md")] +#[cfg(doctest)] +struct DocsBuildsDoctests;