Skip to main content

apify_rs/
lib.rs

1//! Rust client for the [Apify](https://apify.com) platform API.
2//!
3//! Apify is a web scraping and automation cloud platform.  Users publish
4//! **Actors** (serverless programs) to the Apify Store, run them with a JSON
5//! input, and retrieve structured results from **Datasets** or **Key-Value
6//! Stores**.
7//!
8//! This crate mirrors the [Apify REST API v2](https://docs.apify.com/api/v2).
9//! It is organised around three primary resources:
10//!
11//! | Resource | What it represents | Typical workflow |
12//! |----------|-------------------|------------------|
13//! | [`actors`](resources::actors::ActorClient) | Scraping / automation programs | Pick an Actor and run it |
14//! | [`tasks`](resources::tasks::TaskClient)   | Saved Actor configurations     | Create once, run many times |
15//! | [`runs`](resources::runs::RunClient)      | Single executions              | Poll status, fetch output |
16//!
17//! # Disclaimer
18//!
19//! This project was written with the help of large language models (LLMs).
20//! It is not well tested in production, so please review and test thoroughly
21//! before using it for critical workloads.
22//!
23//! # Quick start
24//!
25//! ```ignore
26//! use apify_rs::ApifyClient;
27//!
28//! let client = ApifyClient::new(std::env::var("APIFY_TOKEN")?);
29//!
30//! // Run a task asynchronously
31//! let run = client.tasks().run("my-task-id", None).await?;
32//! println!("Started run {}", run.id);
33//!
34//! // Poll until the run finishes
35//! let finished = client.runs().wait_for_finish(&run.id, 5, 600).await?;
36//! println!("Run finished with status {:?}", finished.status);
37//! ```
38
39pub mod client;
40pub mod error;
41pub mod instagram;
42pub mod models;
43pub mod resources;
44
45pub use client::HttpClient;
46pub use error::ApifyError;
47pub use resources::{actors::ActorClient, runs::RunClient, tasks::TaskClient};
48
49use reqwest::Method;
50
51/// Root client for the Apify API.
52///
53/// Holds an authenticated [`HttpClient`] and exposes resource-specific
54/// sub-clients ([`TaskClient`], [`RunClient`], [`ActorClient`]).
55///
56/// You normally construct this via [`ApifyClient::new`] or the
57/// [`ApifyClientBuilder`] for more control.
58///
59/// # Example
60/// ```ignore
61/// let apify = ApifyClient::new("your-api-token");
62/// let task = apify.tasks().get("my-task-id").await?;
63/// let run = apify.runs().get("run-id").await?;
64/// ```
65#[derive(Debug, Clone)]
66pub struct ApifyClient {
67    http: client::HttpClient,
68}
69
70impl ApifyClient {
71    /// Create a new client authenticated with an API token.
72    ///
73    /// The token is sent as a `Bearer` header on every request.
74    pub fn new<T: Into<String>>(token: T) -> Self {
75        Self {
76            http: client::HttpClient::new(Some(token.into())),
77        }
78    }
79
80    /// Create an unauthenticated client.
81    ///
82    /// Only a subset of endpoints (public Actors, public datasets) work
83    /// without a token.  Most write operations will fail.
84    pub fn new_anonymous() -> Self {
85        Self {
86            http: client::HttpClient::new(None),
87        }
88    }
89
90    /// Override the base URL (default is `https://api.apify.com/v2`).
91    ///
92    /// Useful for integration tests against a mock server or enterprise
93    /// deployments with a custom gateway.
94    pub fn with_base_url(mut self, url: impl Into<String>) -> Self {
95        self.http = self.http.with_base_url(url);
96        self
97    }
98
99    /// Override the default HTTP timeout (60 s).
100    pub fn with_timeout(mut self, timeout: std::time::Duration) -> Self {
101        self.http = self.http.with_timeout(timeout);
102        self
103    }
104
105    /// Return a [`TaskClient`] tied to this client's HTTP transport.
106    pub fn tasks(&self) -> TaskClient<'_> {
107        TaskClient::new(&self.http)
108    }
109
110    /// Return a [`RunClient`] tied to this client's HTTP transport.
111    pub fn runs(&self) -> RunClient<'_> {
112        RunClient::new(&self.http)
113    }
114
115    /// Return an [`ActorClient`] tied to this client's HTTP transport.
116    pub fn actors(&self) -> ActorClient<'_> {
117        ActorClient::new(&self.http)
118    }
119
120    /// Send an arbitrary authenticated request.
121    ///
122    /// Handy when you need an endpoint that is not yet wrapped by a
123    /// resource client (e.g. webhooks, schedules, or new beta endpoints).
124    pub async fn raw_request<T: serde::de::DeserializeOwned>(
125        &self,
126        method: Method,
127        path: &str,
128        body: Option<impl serde::Serialize>,
129    ) -> Result<T, ApifyError> {
130        self.http.request(method, path, body).await
131    }
132}
133
134/// Fluent builder for [`ApifyClient`].
135///
136/// ```ignore
137/// let client = ApifyClientBuilder::new()
138///     .token("xxx")
139///     .timeout(Duration::from_secs(30))
140///     .build();
141/// ```
142pub struct ApifyClientBuilder {
143    token: Option<String>,
144    base_url: Option<String>,
145    timeout: Option<std::time::Duration>,
146}
147
148impl ApifyClientBuilder {
149    /// Start building with no configuration.
150    pub fn new() -> Self {
151        Self {
152            token: None,
153            base_url: None,
154            timeout: None,
155        }
156    }
157
158    /// Set the API token.
159    pub fn token(mut self, token: impl Into<String>) -> Self {
160        self.token = Some(token.into());
161        self
162    }
163
164    /// Set a custom base URL.
165    pub fn base_url(mut self, url: impl Into<String>) -> Self {
166        self.base_url = Some(url.into());
167        self
168    }
169
170    /// Set a request timeout.
171    pub fn timeout(mut self, timeout: std::time::Duration) -> Self {
172        self.timeout = Some(timeout);
173        self
174    }
175
176    /// Consume the builder and return an [`ApifyClient`].
177    pub fn build(self) -> ApifyClient {
178        let mut client = ApifyClient::new(self.token.unwrap_or_default());
179        if let Some(url) = self.base_url {
180            client = client.with_base_url(url);
181        }
182        if let Some(timeout) = self.timeout {
183            client = client.with_timeout(timeout);
184        }
185        client
186    }
187}
188
189impl Default for ApifyClientBuilder {
190    fn default() -> Self {
191        Self::new()
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    #[test]
200    fn test_builder() {
201        let _client = ApifyClientBuilder::new()
202            .token("test-token")
203            .timeout(std::time::Duration::from_secs(30))
204            .build();
205        // Just verify it builds without panic
206    }
207
208    #[test]
209    fn test_new_anonymous() {
210        let _client = ApifyClient::new_anonymous();
211        // Just verify it builds without panic
212    }
213}