Skip to main content

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                &params,
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}