diff --git a/CHANGELOG.md b/CHANGELOG.md index 49f6e83..b567e3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ 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.7.0] - 2026-08-10 + +### Added +- `TaskClient::publish()` and `TaskClient::unpublish()`, setting `isPublic` via `update` to + publish/unpublish a task on its public landing page, matching the reference client. + +### Changed +- Bumped `API_SPEC_VERSION` to `v2-2026-08-05T133145Z`. The spec delta (relaxed field + nullability, e.g. `Webhook.requestUrl`/`TaggedBuildInfo.buildNumber`, and additional + documented `402`/`404`/`408`/`409` error responses) needs no other code change: response + models are forward-compatible and error responses are handled generically. +- Removed the duplicated "official, but experimental, AI-generated and AI-maintained" notice + from the crate-level rustdoc and `docs/README.md`; it now appears only once, in the + top-level `README.md`. +- Bumped crate version to `0.7.0`. + ## [0.6.1] - 2026-07-14 ### Changed diff --git a/Cargo.toml b/Cargo.toml index 39c456a..0799dd6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "apify-client" -version = "0.6.1" +version = "0.7.0" authors = ["Apify Technologies "] description = "An official, but experimental, AI-generated and AI-maintained Rust client for the Apify API (https://apify.com)." license = "Apache-2.0" diff --git a/docs/README.md b/docs/README.md index ff992b9..f1273d6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,9 +1,5 @@ # Apify Rust client — documentation -> **Official, but experimental — AI-generated and AI-maintained.** This is an official Apify -> client, but it is experimental: it is generated and maintained by AI. Review the code before -> relying on it in production and report issues on the repository. - This directory documents the public API of the Apify Rust client. The same descriptions are available as rustdoc comments and can be browsed with `cargo doc --open`. diff --git a/docs/tasks.md b/docs/tasks.md index 8c450f0..6cbfb26 100644 --- a/docs/tasks.md +++ b/docs/tasks.md @@ -16,6 +16,8 @@ Obtained via `client.tasks()` (collection) and `client.task(id)` (single). |---|---|---|---| | `get()` | — | `Option` | Fetches the task. | | `update(fields)` | `&impl Serialize` | `Task` | Updates the task. | +| `publish()` | — | `Task` | Publishes the task on its public landing page (sets `isPublic: true`). Requires the task's Actor to be public and the task to have `publicConfig` set up. | +| `unpublish()` | — | `Task` | Unpublishes the task from its public landing page (sets `isPublic: false`), preserving `publicConfig`. | | `delete()` | — | `()` | Deletes the task. | | `start(input, options)` | `Option<&impl Serialize>`, `ActorStartOptions` | `ActorRun` | Starts a run. See [`ActorStartOptions`](actors.md#actorstartoptions) for the full field list. | | `call(input, options, wait_secs)` | `Option<&impl Serialize>`, `ActorStartOptions`, `Option` | `ActorRun` | Starts a run and waits. Same [`ActorStartOptions`](actors.md#actorstartoptions) as `start`. | diff --git a/src/clients/task.rs b/src/clients/task.rs index 7996d8c..9041d4a 100644 --- a/src/clients/task.rs +++ b/src/clients/task.rs @@ -46,6 +46,25 @@ impl TaskClient { delete_resource(&self.ctx, None).await } + /// Publishes the task on its public landing page, by setting `isPublic: true` through + /// [`TaskClient::update`]. + /// + /// The task's Actor must be public and the task must already have its public display + /// configuration (`publicConfig`) set up. Publishing an already-published task does nothing. + pub async fn publish(&self) -> ApifyClientResult { + self.update(&serde_json::json!({ "isPublic": true })).await + } + + /// Unpublishes the task from its public landing page, by setting `isPublic: false` through + /// [`TaskClient::update`]. + /// + /// The public display configuration (`publicConfig`) is preserved, so the task can be + /// published again later without re-entering it. Unpublishing a task that is not published + /// does nothing. + pub async fn unpublish(&self) -> ApifyClientResult { + self.update(&serde_json::json!({ "isPublic": false })).await + } + /// Starts the task and returns immediately with the created run. /// /// `input` overrides the task's saved input (or `None` to use the saved input). diff --git a/src/lib.rs b/src/lib.rs index c53ff11..fe9649e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,5 @@ //! # apify-client //! -//! **Official, but experimental — AI-generated and AI-maintained.** This is an official Apify -//! client, but it is experimental: it is generated and maintained by AI. Review the code before -//! relying on it in production and report issues on the repository. -//! //! An idiomatic Rust client for the [Apify API](https://docs.apify.com/api/v2). //! //! It provides a resource-oriented interface that mirrors the official diff --git a/src/version.rs b/src/version.rs index cf33eed..5e368b2 100644 --- a/src/version.rs +++ b/src/version.rs @@ -10,4 +10,4 @@ pub const CLIENT_VERSION: &str = env!("CARGO_PKG_VERSION"); /// and verified against. /// /// This corresponds to the `info.version` field of the Apify OpenAPI document. -pub const API_SPEC_VERSION: &str = "v2-2026-07-13T092445Z"; +pub const API_SPEC_VERSION: &str = "v2-2026-08-05T133145Z"; diff --git a/tests/task.rs b/tests/task.rs index a56971c..3a45b6e 100644 --- a/tests/task.rs +++ b/tests/task.rs @@ -87,6 +87,49 @@ async fn iterate_tasks() { ); } +/// `publish`/`unpublish` are thin wrappers around `update` that flip `isPublic`. Unpublishing an +/// already-unpublished task is a documented no-op, so it round-trips cleanly; publishing a task +/// requires write permission over its Actor (here the shared, Apify-owned `apify/hello-world`, +/// per [`task_definition`]), so the API is expected to reject it with `insufficient-permissions` - +/// this exercises the same request path without needing a private Actor the test account can +/// actually publish (which would additionally require an SEO description, an input field +/// selection, and a dataset view set up on the task's public display configuration). +#[tokio::test(flavor = "multi_thread")] +async fn task_publish_unpublish() { + let client = require_client!(); + let name = common::unique_name("task-publish"); + let task = client + .tasks() + .create(&task_definition(&name)) + .await + .expect("create task"); + + let cleanup_client = client.clone(); + let id = task.id.clone(); + let _guard = common::Cleanup::new(move || async move { + let _ = cleanup_client.task(&id).delete().await; + }); + + let task_client = client.task(&task.id); + + let unpublished = task_client + .unpublish() + .await + .expect("unpublish an already-unpublished task should be a no-op"); + assert_eq!(unpublished.extra.get("isPublic"), Some(&json!(false))); + + match task_client.publish().await { + Err(apify_client::ApifyClientError::Api(err)) => { + assert_eq!(err.status_code, 403); + assert_eq!(err.error_type.as_deref(), Some("insufficient-permissions")); + } + other => panic!( + "expected publish() on a task for an Actor we don't own to fail with \ + `insufficient-permissions`, got {other:?}" + ), + } +} + /// Complex flow: create a task for the public hello-world Actor, get it, update its input, /// list its runs, and delete it. #[tokio::test(flavor = "multi_thread")]