1use std::sync::Arc;
4use std::time::Duration;
5
6use crate::clients::actor::ActorClient;
7use crate::clients::actor_collection::ActorCollectionClient;
8use crate::clients::build::BuildClient;
9use crate::clients::build_collection::BuildCollectionClient;
10use crate::clients::dataset::DatasetClient;
11use crate::clients::dataset_collection::DatasetCollectionClient;
12use crate::clients::key_value_store::KeyValueStoreClient;
13use crate::clients::key_value_store_collection::KeyValueStoreCollectionClient;
14use crate::clients::log::LogClient;
15use crate::clients::request_queue::RequestQueueClient;
16use crate::clients::request_queue_collection::RequestQueueCollectionClient;
17use crate::clients::run::RunClient;
18use crate::clients::run_collection::RunCollectionClient;
19use crate::clients::schedule::ScheduleClient;
20use crate::clients::schedule_collection::ScheduleCollectionClient;
21use crate::clients::store_collection::StoreCollectionClient;
22use crate::clients::task::TaskClient;
23use crate::clients::task_collection::TaskCollectionClient;
24use crate::clients::user::UserClient;
25use crate::clients::webhook::WebhookClient;
26use crate::clients::webhook_collection::WebhookCollectionClient;
27use crate::clients::webhook_dispatch::WebhookDispatchClient;
28use crate::clients::webhook_dispatch_collection::WebhookDispatchCollectionClient;
29use crate::common::build_user_agent;
30use crate::http_client::{
31 HttpBackend, HttpClient, RequestCompression, ReqwestBackend, RetryConfig,
32};
33
34const DEFAULT_BASE_URL: &str = "https://api.apify.com";
36const DEFAULT_MAX_RETRIES: u32 = 8;
38const DEFAULT_MIN_DELAY_BETWEEN_RETRIES: Duration = Duration::from_millis(500);
40const DEFAULT_TIMEOUT: Duration = Duration::from_secs(360);
42pub(crate) const ME_USER_PLACEHOLDER: &str = "me";
44
45pub struct ApifyClientBuilder {
57 token: Option<String>,
58 base_url: String,
59 public_base_url: Option<String>,
60 max_retries: u32,
61 min_delay_between_retries: Duration,
62 timeout: Duration,
63 user_agent_suffix: Option<String>,
64 http_backend: Option<Arc<dyn HttpBackend>>,
65 request_compression: RequestCompression,
66}
67
68impl Default for ApifyClientBuilder {
69 fn default() -> Self {
70 Self {
71 token: None,
72 base_url: DEFAULT_BASE_URL.to_string(),
73 public_base_url: None,
74 max_retries: DEFAULT_MAX_RETRIES,
75 min_delay_between_retries: DEFAULT_MIN_DELAY_BETWEEN_RETRIES,
76 timeout: DEFAULT_TIMEOUT,
77 user_agent_suffix: None,
78 http_backend: None,
79 request_compression: RequestCompression::default(),
80 }
81 }
82}
83
84impl ApifyClientBuilder {
85 pub fn token(mut self, token: impl Into<String>) -> Self {
87 self.token = Some(token.into());
88 self
89 }
90
91 pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
95 self.base_url = base_url.into();
96 self
97 }
98
99 pub fn public_base_url(mut self, public_base_url: impl Into<String>) -> Self {
103 self.public_base_url = Some(public_base_url.into());
104 self
105 }
106
107 pub fn max_retries(mut self, max_retries: u32) -> Self {
109 self.max_retries = max_retries;
110 self
111 }
112
113 pub fn min_delay_between_retries(mut self, delay: Duration) -> Self {
115 self.min_delay_between_retries = delay;
116 self
117 }
118
119 pub fn timeout(mut self, timeout: Duration) -> Self {
121 self.timeout = timeout;
122 self
123 }
124
125 pub fn user_agent_suffix(mut self, suffix: impl Into<String>) -> Self {
127 self.user_agent_suffix = Some(suffix.into());
128 self
129 }
130
131 pub fn request_compression(mut self, compression: RequestCompression) -> Self {
139 self.request_compression = compression;
140 self
141 }
142
143 pub fn http_backend(mut self, backend: Arc<dyn HttpBackend>) -> Self {
147 self.http_backend = Some(backend);
148 self
149 }
150
151 pub fn build(self) -> ApifyClient {
153 let backend = self
154 .http_backend
155 .unwrap_or_else(|| Arc::new(ReqwestBackend::new()));
156
157 let user_agent = build_user_agent(self.user_agent_suffix.as_deref());
158
159 let retry = RetryConfig {
160 max_retries: self.max_retries,
161 min_delay_between_retries: self.min_delay_between_retries,
162 timeout: self.timeout,
163 };
164
165 let http = HttpClient::new(
166 backend,
167 self.token,
168 user_agent,
169 retry,
170 self.request_compression,
171 );
172
173 let trimmed = self.base_url.trim_end_matches('/');
174 let base_url = format!("{trimmed}/v2");
175
176 let public_trimmed = self
177 .public_base_url
178 .as_deref()
179 .unwrap_or(&self.base_url)
180 .trim_end_matches('/');
181 let public_base_url = format!("{public_trimmed}/v2");
182
183 ApifyClient {
184 http,
185 base_url,
186 public_base_url,
187 }
188 }
189}
190
191#[derive(Debug, Clone)]
199pub struct ApifyClient {
200 http: HttpClient,
201 base_url: String,
202 public_base_url: String,
203}
204
205impl ApifyClient {
206 pub fn builder() -> ApifyClientBuilder {
208 ApifyClientBuilder::default()
209 }
210
211 pub fn new(token: impl Into<String>) -> Self {
213 ApifyClientBuilder::default().token(token).build()
214 }
215
216 pub(crate) fn http(&self) -> HttpClient {
217 self.http.clone()
218 }
219
220 pub fn user_agent(&self) -> &str {
222 self.http.user_agent()
223 }
224
225 pub fn api_base_url(&self) -> &str {
228 &self.base_url
229 }
230
231 pub fn actors(&self) -> ActorCollectionClient {
235 ActorCollectionClient::new(self.http(), &self.base_url)
236 }
237
238 pub fn actor(&self, id: impl Into<String>) -> ActorClient {
240 ActorClient::new(self.clone(), self.http(), &self.base_url, &id.into())
241 }
242
243 pub fn builds(&self) -> BuildCollectionClient {
247 BuildCollectionClient::new(self.http(), &self.base_url)
248 }
249
250 pub fn build(&self, id: impl Into<String>) -> BuildClient {
252 BuildClient::new(self.http(), &self.base_url, &id.into())
253 }
254
255 pub fn runs(&self) -> RunCollectionClient {
259 RunCollectionClient::new(self.http(), &self.base_url, "actor-runs")
260 }
261
262 pub fn run(&self, id: impl Into<String>) -> RunClient {
264 RunClient::new(
265 self.clone(),
266 self.http(),
267 &self.base_url,
268 "actor-runs",
269 &id.into(),
270 )
271 }
272
273 pub fn datasets(&self) -> DatasetCollectionClient {
277 DatasetCollectionClient::new(self.http(), &self.base_url)
278 }
279
280 pub fn dataset(&self, id: impl Into<String>) -> DatasetClient {
282 DatasetClient::new(self.http(), &self.base_url, "datasets", &id.into())
283 .with_public_base(&self.public_base_url)
284 }
285
286 pub fn key_value_stores(&self) -> KeyValueStoreCollectionClient {
290 KeyValueStoreCollectionClient::new(self.http(), &self.base_url)
291 }
292
293 pub fn key_value_store(&self, id: impl Into<String>) -> KeyValueStoreClient {
295 KeyValueStoreClient::new(self.http(), &self.base_url, "key-value-stores", &id.into())
296 .with_public_base(&self.public_base_url)
297 }
298
299 pub fn request_queues(&self) -> RequestQueueCollectionClient {
303 RequestQueueCollectionClient::new(self.http(), &self.base_url)
304 }
305
306 pub fn request_queue(&self, id: impl Into<String>) -> RequestQueueClient {
308 RequestQueueClient::new(self.http(), &self.base_url, "request-queues", &id.into())
309 }
310
311 pub fn tasks(&self) -> TaskCollectionClient {
315 TaskCollectionClient::new(self.http(), &self.base_url)
316 }
317
318 pub fn task(&self, id: impl Into<String>) -> TaskClient {
320 TaskClient::new(self.clone(), self.http(), &self.base_url, &id.into())
321 }
322
323 pub fn schedules(&self) -> ScheduleCollectionClient {
327 ScheduleCollectionClient::new(self.http(), &self.base_url)
328 }
329
330 pub fn schedule(&self, id: impl Into<String>) -> ScheduleClient {
332 ScheduleClient::new(self.http(), &self.base_url, &id.into())
333 }
334
335 pub fn webhooks(&self) -> WebhookCollectionClient {
339 WebhookCollectionClient::new(self.http(), &self.base_url)
340 }
341
342 pub fn webhook(&self, id: impl Into<String>) -> WebhookClient {
344 WebhookClient::new(self.http(), &self.base_url, &id.into())
345 }
346
347 pub fn webhook_dispatches(&self) -> WebhookDispatchCollectionClient {
349 WebhookDispatchCollectionClient::new(self.http(), &self.base_url)
350 }
351
352 pub fn webhook_dispatch(&self, id: impl Into<String>) -> WebhookDispatchClient {
354 WebhookDispatchClient::new(self.http(), &self.base_url, &id.into())
355 }
356
357 pub fn store(&self) -> StoreCollectionClient {
361 StoreCollectionClient::new(self.http(), &self.base_url)
362 }
363
364 pub fn log(&self, build_or_run_id: impl Into<String>) -> LogClient {
366 LogClient::new(self.http(), &self.base_url, "logs", &build_or_run_id.into())
367 }
368
369 pub fn me(&self) -> UserClient {
371 UserClient::new(self.http(), &self.base_url, ME_USER_PLACEHOLDER)
372 }
373
374 pub fn user(&self, id: impl Into<String>) -> UserClient {
376 UserClient::new(self.http(), &self.base_url, &id.into())
377 }
378
379 pub async fn set_status_message(
388 &self,
389 message: &str,
390 is_terminal: bool,
391 ) -> crate::error::ApifyClientResult<crate::models::ActorRun> {
392 let run_id = std::env::var("ACTOR_RUN_ID").map_err(|_| {
393 crate::error::ApifyClientError::InvalidArgument(
394 "ACTOR_RUN_ID environment variable is not set".to_string(),
395 )
396 })?;
397 let body = serde_json::json!({
398 "statusMessage": message,
399 "isStatusMessageTerminal": is_terminal,
400 });
401 self.run(run_id).update(&body).await
402 }
403}