apify_client/clients/task.rs
1//! Client for a single Actor task (`/v2/actor-tasks/{actorTaskId}`).
2
3use serde::Serialize;
4use serde_json::Value;
5
6use crate::client::ApifyClient;
7use crate::clients::actor::ActorStartOptions;
8use crate::clients::base::{
9 delete_resource, get_resource, post_with_body, update_resource, ResourceContext,
10};
11use crate::clients::run::{LastRunOptions, RunClient};
12use crate::clients::run_collection::RunCollectionClient;
13use crate::clients::webhook_collection::WebhookCollectionClient;
14use crate::common::QueryParams;
15use crate::error::ApifyClientResult;
16use crate::http_client::HttpClient;
17use crate::models::{ActorRun, Task};
18
19/// Client for a specific Actor task.
20#[derive(Debug, Clone)]
21pub struct TaskClient {
22 root: ApifyClient,
23 ctx: ResourceContext,
24}
25
26impl TaskClient {
27 pub(crate) fn new(root: ApifyClient, http: HttpClient, base_url: &str, id: &str) -> Self {
28 Self {
29 root,
30 ctx: ResourceContext::single(http, base_url, "actor-tasks", id),
31 }
32 }
33
34 /// Fetches the task object, or `None` if it does not exist.
35 pub async fn get(&self) -> ApifyClientResult<Option<Task>> {
36 get_resource(&self.ctx, None, &QueryParams::new()).await
37 }
38
39 /// Updates the task with the given fields.
40 pub async fn update<T: Serialize>(&self, new_fields: &T) -> ApifyClientResult<Task> {
41 update_resource(&self.ctx, None, new_fields).await
42 }
43
44 /// Deletes the task.
45 pub async fn delete(&self) -> ApifyClientResult<()> {
46 delete_resource(&self.ctx, None).await
47 }
48
49 /// Publishes the task on its public landing page, by setting `isPublic: true` through
50 /// [`TaskClient::update`].
51 ///
52 /// The task's Actor must be public and the task must already have its public display
53 /// configuration ([`Task::public_config`]) set up. An Actor can have at most 10 published
54 /// tasks, and an account at most 100 across all its Actors; contact Apify support to raise
55 /// either limit. Publishing an already-published task does nothing. The returned
56 /// [`Task::is_public`] reflects the new publication state.
57 pub async fn publish(&self) -> ApifyClientResult<Task> {
58 self.update(&serde_json::json!({ "isPublic": true })).await
59 }
60
61 /// Unpublishes the task from its public landing page, by setting `isPublic: false` through
62 /// [`TaskClient::update`].
63 ///
64 /// The public display configuration ([`Task::public_config`]) is preserved, so the task can
65 /// be published again later without re-entering it. Unpublishing a task that is not
66 /// published does nothing.
67 pub async fn unpublish(&self) -> ApifyClientResult<Task> {
68 self.update(&serde_json::json!({ "isPublic": false })).await
69 }
70
71 /// Starts the task and returns immediately with the created run.
72 ///
73 /// `input` overrides the task's saved input (or `None` to use the saved input). Set
74 /// [`ActorStartOptions::wait_for_resources`] to retry the start while the account lacks the
75 /// memory or a concurrent-run slot for it.
76 pub async fn start<T: Serialize>(
77 &self,
78 input: Option<&T>,
79 options: ActorStartOptions,
80 ) -> ApifyClientResult<ActorRun> {
81 let mut params = QueryParams::new();
82 options.apply(&mut params);
83 let body = match input {
84 Some(value) => Some(serde_json::to_vec(value)?),
85 None => None,
86 };
87 crate::clients::base::retry_while_resource_limited(options.wait_for_resources, || {
88 post_with_body(
89 &self.ctx,
90 Some("runs"),
91 ¶ms,
92 body.clone(),
93 "application/json",
94 )
95 })
96 .await
97 }
98
99 /// Starts the task and waits (client-side polling) for it to finish.
100 ///
101 /// `wait_secs` controls the wait budget:
102 /// - `None` polls indefinitely until the run reaches a terminal state.
103 /// - `Some(n)` bounds the wait to roughly `n` seconds; if the run has not finished by
104 /// then, the **last fetched (still non-terminal) run is returned** rather than an
105 /// error. Check `status` / `is_terminal()` on the result when using `Some`.
106 ///
107 /// Time spent retrying under [`ActorStartOptions::wait_for_resources`] does not count
108 /// toward `wait_secs`, since that budget only starts once the run has actually started.
109 pub async fn call<T: Serialize>(
110 &self,
111 input: Option<&T>,
112 options: ActorStartOptions,
113 wait_secs: Option<i64>,
114 ) -> ApifyClientResult<ActorRun> {
115 let run = self.start(input, options).await?;
116 self.root.run(run.id).wait_for_finish(wait_secs).await
117 }
118
119 /// Fetches the task's saved input, or `None` if not set.
120 pub async fn get_input(&self) -> ApifyClientResult<Option<Value>> {
121 let response =
122 crate::clients::base::get_raw(&self.ctx, Some("input"), &QueryParams::new()).await?;
123 match response {
124 Some(r) => Ok(Some(serde_json::from_slice(&r.body)?)),
125 None => Ok(None),
126 }
127 }
128
129 /// Updates the task's saved input.
130 pub async fn update_input<T: Serialize>(&self, input: &T) -> ApifyClientResult<Value> {
131 let body = serde_json::to_vec(input)?;
132 let url = self.ctx.url(Some("input"));
133 let mut headers = std::collections::HashMap::new();
134 headers.insert("Content-Type".to_string(), "application/json".to_string());
135 let response = self
136 .ctx
137 .http
138 .call(crate::http_client::HttpRequest {
139 method: crate::http_client::HttpMethod::Put,
140 url,
141 headers,
142 body: Some(body),
143 timeout: crate::clients::base::DEFAULT_REQUEST_TIMEOUT,
144 })
145 .await?;
146 Ok(serde_json::from_slice(&response.body)?)
147 }
148
149 /// Returns a client for the last run of this task, optionally filtered by run status.
150 ///
151 /// `status` filters by run status (e.g. `"SUCCEEDED"`, `"FAILED"`, `"RUNNING"`); pass `None`
152 /// to leave it unfiltered. This maps to the `status` query parameter on
153 /// `GET /v2/actor-tasks/{actorTaskId}/runs/last` and mirrors the reference client's
154 /// `lastRun({ status })`. To also filter by `origin`, use [`TaskClient::last_run_with_options`].
155 pub fn last_run(&self, status: Option<&str>) -> RunClient {
156 self.last_run_with_options(LastRunOptions {
157 status: status.map(str::to_owned),
158 origin: None,
159 })
160 }
161
162 /// Returns a client for the last run of this task, applying the given [`LastRunOptions`]
163 /// (e.g. [`LastRunOptions::status`] and/or [`LastRunOptions::origin`]).
164 ///
165 /// `status` filters by run status (e.g. `"SUCCEEDED"`, `"FAILED"`, `"RUNNING"`); `origin` filters
166 /// by how the run was started, with accepted values being the platform's run origins (e.g.
167 /// `"DEVELOPMENT"`, `"WEB"`, `"API"`, `"SCHEDULER"`). Both are documented optional query
168 /// parameters on `GET /v2/actor-tasks/{actorTaskId}/runs/last` and match the reference client's
169 /// `lastRun({ status, origin })`; leave a field as `None` to omit it.
170 pub fn last_run_with_options(&self, options: LastRunOptions) -> RunClient {
171 let mut client = RunClient::new(
172 self.root.clone(),
173 self.ctx.http.clone(),
174 &self.ctx.url(None),
175 "runs",
176 "last",
177 );
178 if let Some(status) = options.status.as_deref() {
179 client.set_base_param("status", status);
180 }
181 if let Some(origin) = options.origin.as_deref() {
182 client.set_base_param("origin", origin);
183 }
184 client
185 }
186
187 /// Returns a client for this task's run collection.
188 pub fn runs(&self) -> RunCollectionClient {
189 RunCollectionClient::new(self.ctx.http.clone(), &self.ctx.url(None), "runs")
190 }
191
192 /// Returns a client for this task's webhook collection.
193 pub fn webhooks(&self) -> WebhookCollectionClient {
194 WebhookCollectionClient::with_base(self.ctx.http.clone(), &self.ctx.url(None))
195 }
196}