1use std::{collections::BTreeMap, convert::identity, fmt};
8
9use serde::{Deserialize, Serialize};
10use serde_json::Value;
11
12use crate::error::InvalidBuilderState;
13use crate::tasks::{
14 ArchiveTask, AzureBlobExportTask, AzureBlobImportTask, Base64ImportTask, CaptureWebsiteTask,
15 CommandTask, ConvertTask, ExportUploadTask, ExportUrlTask, GoogleCloudStorageExportTask,
16 GoogleCloudStorageImportTask, ImportUploadTask, ImportUrlTask, Input, MergeTask, MetadataTask,
17 MetadataWriteTask, OpenStackExportTask, OpenStackImportTask, OptimizeTask, PdfATask,
18 PdfDecryptTask, PdfEncryptTask, PdfExtractPagesTask, PdfOcrTask, PdfRotatePagesTask,
19 PdfSplitPagesTask, PdfXTask, RawImportTask, S3ExportTask, S3ImportTask, SftpExportTask,
20 SftpImportTask, TaskRequest, ThumbnailTask, WatermarkTask,
21};
22
23fn redacted_option<T>(value: &Option<T>) -> Option<&'static str> {
24 value.as_ref().map(|_| "REDACTED")
25}
26
27struct RedactedValueMap<'a>(&'a BTreeMap<String, Value>);
28
29impl fmt::Debug for RedactedValueMap<'_> {
30 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31 let mut debug = f.debug_map();
32 for key in self.0.keys() {
33 debug.entry(key, &"REDACTED");
34 }
35 debug.finish()
36 }
37}
38
39#[derive(Clone, Debug, Default, Deserialize, Serialize)]
41#[non_exhaustive]
42pub struct RateLimit {
43 #[serde(default)]
44 pub limit: Option<u64>,
45 #[serde(default)]
46 pub remaining: Option<u64>,
47 #[serde(default)]
48 pub reset: Option<u64>,
49 #[serde(default)]
50 pub retry_after: Option<u64>,
51}
52
53#[derive(Clone, Debug, Default, Deserialize, Serialize)]
55#[non_exhaustive]
56pub struct PaginationLinks {
57 #[serde(default)]
58 pub first: Option<String>,
59 #[serde(default)]
60 pub last: Option<String>,
61 #[serde(default)]
62 pub prev: Option<String>,
63 #[serde(default)]
64 pub next: Option<String>,
65 #[serde(flatten)]
66 pub extra: BTreeMap<String, Value>,
67}
68
69#[derive(Clone, Debug, Default, Deserialize, Serialize)]
71#[non_exhaustive]
72pub struct PaginationMeta {
73 #[serde(default)]
74 pub current_page: Option<u32>,
75 #[serde(default)]
76 pub from: Option<u32>,
77 #[serde(default)]
78 pub path: Option<String>,
79 #[serde(default)]
80 pub per_page: Option<u32>,
81 #[serde(default)]
82 pub to: Option<u32>,
83 #[serde(default)]
84 pub total: Option<u32>,
85 #[serde(default)]
86 pub last_page: Option<u32>,
87 #[serde(flatten)]
88 pub extra: BTreeMap<String, Value>,
89}
90
91#[derive(Clone, Debug, Default, Serialize)]
93#[non_exhaustive]
94pub struct Page<T> {
95 pub data: Vec<T>,
96 pub links: PaginationLinks,
97 pub meta: PaginationMeta,
98 #[serde(skip)]
99 pub rate_limit: Option<RateLimit>,
100}
101
102#[derive(Clone, Debug, Serialize)]
104#[non_exhaustive]
105pub struct ApiResponse<T> {
106 pub data: T,
107 pub links: PaginationLinks,
108 pub meta: PaginationMeta,
109 #[serde(skip)]
110 pub rate_limit: Option<RateLimit>,
111}
112
113#[derive(Clone, Default, Serialize)]
135pub struct JobCreateRequest {
136 tasks: BTreeMap<String, TaskRequest>,
137 #[serde(skip_serializing_if = "Option::is_none")]
138 tag: Option<String>,
139 #[serde(skip_serializing_if = "Option::is_none")]
140 webhook_url: Option<String>,
141 #[serde(skip_serializing_if = "Option::is_none")]
142 pub(crate) redirect: Option<bool>,
143 #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
144 extra: BTreeMap<String, Value>,
145}
146
147const RESERVED_JOB_FIELDS: [&str; 4] = ["tasks", "tag", "webhook_url", "redirect"];
148
149impl JobCreateRequest {
150 fn insert_option(&mut self, key: String, value: Value) {
151 if RESERVED_JOB_FIELDS.contains(&key.as_str()) {
152 return;
153 }
154 self.extra.insert(key, value);
155 }
156}
157
158#[derive(Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
163pub struct TaskName(String);
164
165impl TaskName {
166 fn new(value: impl Into<String>) -> Self {
167 Self(value.into())
168 }
169
170 pub fn as_str(&self) -> &str {
172 &self.0
173 }
174}
175
176impl AsRef<str> for TaskName {
177 fn as_ref(&self) -> &str {
178 self.as_str()
179 }
180}
181
182impl fmt::Display for TaskName {
183 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
184 formatter.write_str(self.as_str())
185 }
186}
187
188impl From<TaskName> for String {
189 fn from(value: TaskName) -> Self {
190 value.0
191 }
192}
193
194impl From<&TaskName> for String {
195 fn from(value: &TaskName) -> Self {
196 value.as_str().to_string()
197 }
198}
199
200impl From<TaskName> for Input {
201 fn from(value: TaskName) -> Self {
202 Self::from(value.0)
203 }
204}
205
206impl From<&TaskName> for Input {
207 fn from(value: &TaskName) -> Self {
208 Self::from(value.as_str())
209 }
210}
211
212impl From<Vec<TaskName>> for Input {
213 fn from(value: Vec<TaskName>) -> Self {
214 Self::Tasks(value.into_iter().map(String::from).collect())
215 }
216}
217
218impl From<Vec<&TaskName>> for Input {
219 fn from(value: Vec<&TaskName>) -> Self {
220 Self::Tasks(value.into_iter().map(String::from).collect())
221 }
222}
223
224impl<const N: usize> From<[TaskName; N]> for Input {
225 fn from(value: [TaskName; N]) -> Self {
226 Self::Tasks(value.into_iter().map(String::from).collect())
227 }
228}
229
230impl<const N: usize> From<[&TaskName; N]> for Input {
231 fn from(value: [&TaskName; N]) -> Self {
232 Self::Tasks(value.into_iter().map(String::from).collect())
233 }
234}
235
236impl JobCreateRequest {
237 pub fn builder() -> JobBuilder {
242 JobBuilder::default()
243 }
244
245 pub fn linear() -> JobBuilder {
247 Self::builder()
248 }
249
250 pub fn graph(configure: impl FnOnce(&mut JobGraphBuilder)) -> JobBuilder {
273 let mut graph = JobGraphBuilder::default();
274 configure(&mut graph);
275 graph.into_builder()
276 }
277
278 pub fn tasks(&self) -> &BTreeMap<String, TaskRequest> {
280 &self.tasks
281 }
282
283 pub fn tag(&self) -> Option<&str> {
285 self.tag.as_deref()
286 }
287
288 pub fn webhook_url(&self) -> Option<&str> {
290 self.webhook_url.as_deref()
291 }
292
293 pub fn redirect(&self) -> Option<bool> {
295 self.redirect
296 }
297
298 pub fn extra(&self) -> &BTreeMap<String, Value> {
300 &self.extra
301 }
302}
303
304impl fmt::Debug for JobCreateRequest {
305 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306 f.debug_struct("JobCreateRequest")
307 .field("tasks", &self.tasks)
308 .field("tag", &self.tag)
309 .field("webhook_url", &self.webhook_url)
310 .field("redirect", &self.redirect)
311 .field("extra", &RedactedValueMap(&self.extra))
312 .finish()
313 }
314}
315
316#[derive(Clone, Debug, Default)]
317pub struct JobBuilder {
342 request: JobCreateRequest,
343 last_task: Option<TaskName>,
344}
345
346macro_rules! linear_pdf_task_methods {
347 ($method:ident, $with_method:ident, $task_type:ident) => {
348 pub fn $method(self) -> crate::Result<Self> {
350 let input = self.previous_input(stringify!($method))?;
351 Ok(self.append_task(TaskRequest::$method(input)))
352 }
353
354 pub fn $with_method<F>(self, configure: F) -> crate::Result<Self>
356 where
357 F: FnOnce($task_type) -> $task_type,
358 {
359 let input = self.previous_input(stringify!($with_method))?;
360 Ok(self.append_configured_task($task_type::new(input), configure))
361 }
362 };
363}
364
365macro_rules! graph_pdf_task_methods {
366 ($method:ident, $with_method:ident, $task_type:ident) => {
367 pub fn $method(&mut self, input: impl Into<Input>) -> TaskName {
369 self.$with_method(input, identity)
370 }
371
372 pub fn $with_method<F>(&mut self, input: impl Into<Input>, configure: F) -> TaskName
374 where
375 F: FnOnce($task_type) -> $task_type,
376 {
377 self.add_configured_task($task_type::new(input), configure)
378 }
379 };
380}
381
382impl JobBuilder {
383 pub fn new() -> Self {
385 Self::default()
386 }
387
388 pub fn tag(mut self, tag: impl Into<String>) -> Self {
390 self.request.tag = Some(tag.into());
391 self
392 }
393
394 pub fn webhook_url(mut self, webhook_url: impl Into<String>) -> Self {
396 self.request.webhook_url = Some(webhook_url.into());
397 self
398 }
399
400 pub fn redirect(mut self, redirect: bool) -> Self {
402 self.request.redirect = Some(redirect);
403 self
404 }
405
406 pub fn option(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
408 self.request.insert_option(key.into(), value.into());
409 self
410 }
411
412 pub fn task(mut self, name: impl Into<String>, task: impl Into<TaskRequest>) -> Self {
417 let name = deduplicated_task_name(&name.into(), &self.request.tasks);
418 self.request
419 .tasks
420 .insert(name.as_str().to_string(), task.into());
421 self.last_task = Some(name);
422 self
423 }
424
425 pub fn add_task(&mut self, task: impl Into<TaskRequest>) -> TaskName {
442 let task = task.into();
443 let name = generated_task_name(task.operation(), &self.request.tasks);
444 self.request.tasks.insert(name.as_str().to_string(), task);
445 self.last_task = Some(name.clone());
446 name
447 }
448
449 pub fn add_named_task(
451 &mut self,
452 name: impl Into<String>,
453 task: impl Into<TaskRequest>,
454 ) -> TaskName {
455 let name = deduplicated_task_name(&name.into(), &self.request.tasks);
456 self.request
457 .tasks
458 .insert(name.as_str().to_string(), task.into());
459 self.last_task = Some(name.clone());
460 name
461 }
462
463 pub fn import_url(self, url: impl Into<String>) -> Self {
465 self.append_task(TaskRequest::import_url(url))
466 }
467
468 pub fn import_upload(self) -> Self {
470 self.append_task(TaskRequest::import_upload())
471 }
472
473 pub fn import_base64(self, file: impl Into<String>, filename: impl Into<String>) -> Self {
475 self.append_task(TaskRequest::import_base64(file, filename))
476 }
477
478 pub fn import_raw(self, file: impl Into<String>, filename: impl Into<String>) -> Self {
480 self.append_task(TaskRequest::import_raw(file, filename))
481 }
482
483 pub fn import_s3(
485 self,
486 bucket: impl Into<String>,
487 region: impl Into<String>,
488 access_key_id: impl Into<String>,
489 secret_access_key: impl Into<String>,
490 ) -> Self {
491 self.append_task(TaskRequest::import_s3(
492 bucket,
493 region,
494 access_key_id,
495 secret_access_key,
496 ))
497 }
498
499 pub fn import_azure_blob(
501 self,
502 storage_account: impl Into<String>,
503 container: impl Into<String>,
504 ) -> Self {
505 self.append_task(TaskRequest::import_azure_blob(storage_account, container))
506 }
507
508 pub fn import_google_cloud_storage(
510 self,
511 project_id: impl Into<String>,
512 bucket: impl Into<String>,
513 client_email: impl Into<String>,
514 private_key: impl Into<String>,
515 ) -> Self {
516 self.append_task(TaskRequest::import_google_cloud_storage(
517 project_id,
518 bucket,
519 client_email,
520 private_key,
521 ))
522 }
523
524 pub fn import_openstack(
526 self,
527 auth_url: impl Into<String>,
528 username: impl Into<String>,
529 password: impl Into<String>,
530 region: impl Into<String>,
531 container: impl Into<String>,
532 ) -> Self {
533 self.append_task(TaskRequest::import_openstack(
534 auth_url, username, password, region, container,
535 ))
536 }
537
538 pub fn import_sftp(self, host: impl Into<String>, username: impl Into<String>) -> Self {
540 self.append_task(TaskRequest::import_sftp(host, username))
541 }
542
543 pub fn convert(self, output_format: impl Into<String>) -> crate::Result<Self> {
545 let input = self.previous_input("convert")?;
546 Ok(self.append_task(TaskRequest::convert(input, output_format)))
547 }
548
549 pub fn convert_with_input_format(
551 self,
552 input_format: impl Into<String>,
553 output_format: impl Into<String>,
554 ) -> crate::Result<Self> {
555 let input = self.previous_input("convert_with_input_format")?;
556 Ok(self.append_task(ConvertTask::new(input, output_format).input_format(input_format)))
557 }
558
559 pub fn optimize(self) -> crate::Result<Self> {
561 let input = self.previous_input("optimize")?;
562 Ok(self.append_task(TaskRequest::optimize(input)))
563 }
564
565 pub fn watermark_text(self, text: impl Into<String>) -> crate::Result<Self> {
567 let input = self.previous_input("watermark_text")?;
568 Ok(
569 self.append_task(TaskRequest::watermark(crate::tasks::WatermarkTask::text(
570 input, text,
571 ))),
572 )
573 }
574
575 pub fn watermark_image(self, image_task_name: impl Into<String>) -> crate::Result<Self> {
577 let input = self.previous_input("watermark_image")?;
578 Ok(
579 self.append_task(TaskRequest::watermark(crate::tasks::WatermarkTask::image(
580 input,
581 image_task_name,
582 ))),
583 )
584 }
585
586 pub fn capture_website(self, url: impl Into<String>, output_format: impl Into<String>) -> Self {
588 self.append_task(TaskRequest::capture_website(url, output_format))
589 }
590
591 pub fn thumbnail(self, output_format: impl Into<String>) -> crate::Result<Self> {
593 let input = self.previous_input("thumbnail")?;
594 Ok(self.append_task(TaskRequest::thumbnail(input, output_format)))
595 }
596
597 pub fn metadata(self) -> crate::Result<Self> {
599 let input = self.previous_input("metadata")?;
600 Ok(self.append_task(TaskRequest::metadata(input)))
601 }
602
603 pub fn metadata_write(self) -> crate::Result<Self> {
605 let input = self.previous_input("metadata_write")?;
606 Ok(self.append_task(TaskRequest::metadata_write(input)))
607 }
608
609 pub fn merge(self, output_format: impl Into<String>) -> crate::Result<Self> {
611 let input = self.previous_input("merge")?;
612 Ok(self.append_task(TaskRequest::merge(input, output_format)))
613 }
614
615 pub fn archive(self, output_format: impl Into<String>) -> crate::Result<Self> {
617 let input = self.previous_input("archive")?;
618 Ok(self.append_task(TaskRequest::archive(input, output_format)))
619 }
620
621 pub fn command(
623 self,
624 engine: impl Into<String>,
625 command: impl Into<String>,
626 arguments: impl Into<String>,
627 ) -> crate::Result<Self> {
628 let input = self.previous_input("command")?;
629 Ok(self.append_task(TaskRequest::command(input, engine, command, arguments)))
630 }
631
632 linear_pdf_task_methods!(pdf_a, pdf_a_with, PdfATask);
633 linear_pdf_task_methods!(pdf_x, pdf_x_with, PdfXTask);
634 linear_pdf_task_methods!(pdf_ocr, pdf_ocr_with, PdfOcrTask);
635 linear_pdf_task_methods!(pdf_encrypt, pdf_encrypt_with, PdfEncryptTask);
636 linear_pdf_task_methods!(pdf_decrypt, pdf_decrypt_with, PdfDecryptTask);
637 linear_pdf_task_methods!(pdf_split_pages, pdf_split_pages_with, PdfSplitPagesTask);
638 linear_pdf_task_methods!(
639 pdf_extract_pages,
640 pdf_extract_pages_with,
641 PdfExtractPagesTask
642 );
643 linear_pdf_task_methods!(pdf_rotate_pages, pdf_rotate_pages_with, PdfRotatePagesTask);
644
645 pub fn export_url(self) -> crate::Result<Self> {
647 let input = self.previous_input("export_url")?;
648 Ok(self.append_task(TaskRequest::export_url(input)))
649 }
650
651 pub fn export_s3(
653 self,
654 bucket: impl Into<String>,
655 region: impl Into<String>,
656 access_key_id: impl Into<String>,
657 secret_access_key: impl Into<String>,
658 ) -> crate::Result<Self> {
659 let input = self.previous_input("export_s3")?;
660 Ok(self.append_task(TaskRequest::export_s3(
661 input,
662 bucket,
663 region,
664 access_key_id,
665 secret_access_key,
666 )))
667 }
668
669 pub fn export_azure_blob(
671 self,
672 storage_account: impl Into<String>,
673 container: impl Into<String>,
674 ) -> crate::Result<Self> {
675 let input = self.previous_input("export_azure_blob")?;
676 Ok(self.append_task(TaskRequest::export_azure_blob(
677 input,
678 storage_account,
679 container,
680 )))
681 }
682
683 pub fn export_google_cloud_storage(
685 self,
686 project_id: impl Into<String>,
687 bucket: impl Into<String>,
688 client_email: impl Into<String>,
689 private_key: impl Into<String>,
690 ) -> crate::Result<Self> {
691 let input = self.previous_input("export_google_cloud_storage")?;
692 Ok(self.append_task(TaskRequest::export_google_cloud_storage(
693 input,
694 project_id,
695 bucket,
696 client_email,
697 private_key,
698 )))
699 }
700
701 pub fn export_openstack(
703 self,
704 auth_url: impl Into<String>,
705 username: impl Into<String>,
706 password: impl Into<String>,
707 region: impl Into<String>,
708 container: impl Into<String>,
709 ) -> crate::Result<Self> {
710 let input = self.previous_input("export_openstack")?;
711 Ok(self.append_task(TaskRequest::export_openstack(
712 input, auth_url, username, password, region, container,
713 )))
714 }
715
716 pub fn export_sftp(
718 self,
719 host: impl Into<String>,
720 username: impl Into<String>,
721 ) -> crate::Result<Self> {
722 let input = self.previous_input("export_sftp")?;
723 Ok(self.append_task(TaskRequest::export_sftp(input, host, username)))
724 }
725
726 pub fn export_upload(self, url: impl Into<String>) -> crate::Result<Self> {
728 let input = self.previous_input("export_upload")?;
729 Ok(self.append_task(TaskRequest::export_upload(input, url)))
730 }
731
732 pub fn import_url_with<F>(self, url: impl Into<String>, configure: F) -> Self
734 where
735 F: FnOnce(ImportUrlTask) -> ImportUrlTask,
736 {
737 self.append_configured_task(ImportUrlTask::new(url), configure)
738 }
739
740 pub fn import_upload_with<F>(self, configure: F) -> Self
742 where
743 F: FnOnce(ImportUploadTask) -> ImportUploadTask,
744 {
745 self.append_configured_task(ImportUploadTask::default(), configure)
746 }
747
748 pub fn import_s3_with<F>(
750 self,
751 bucket: impl Into<String>,
752 region: impl Into<String>,
753 access_key_id: impl Into<String>,
754 secret_access_key: impl Into<String>,
755 configure: F,
756 ) -> Self
757 where
758 F: FnOnce(S3ImportTask) -> S3ImportTask,
759 {
760 self.append_configured_task(
761 S3ImportTask::new(bucket, region, access_key_id, secret_access_key),
762 configure,
763 )
764 }
765
766 pub fn import_azure_blob_with<F>(
768 self,
769 storage_account: impl Into<String>,
770 container: impl Into<String>,
771 configure: F,
772 ) -> Self
773 where
774 F: FnOnce(AzureBlobImportTask) -> AzureBlobImportTask,
775 {
776 self.append_configured_task(
777 AzureBlobImportTask::new(storage_account, container),
778 configure,
779 )
780 }
781
782 pub fn import_google_cloud_storage_with<F>(
784 self,
785 project_id: impl Into<String>,
786 bucket: impl Into<String>,
787 client_email: impl Into<String>,
788 private_key: impl Into<String>,
789 configure: F,
790 ) -> Self
791 where
792 F: FnOnce(GoogleCloudStorageImportTask) -> GoogleCloudStorageImportTask,
793 {
794 self.append_configured_task(
795 GoogleCloudStorageImportTask::new(project_id, bucket, client_email, private_key),
796 configure,
797 )
798 }
799
800 pub fn import_openstack_with<F>(
802 self,
803 auth_url: impl Into<String>,
804 username: impl Into<String>,
805 password: impl Into<String>,
806 region: impl Into<String>,
807 container: impl Into<String>,
808 configure: F,
809 ) -> Self
810 where
811 F: FnOnce(OpenStackImportTask) -> OpenStackImportTask,
812 {
813 self.append_configured_task(
814 OpenStackImportTask::new(auth_url, username, password, region, container),
815 configure,
816 )
817 }
818
819 pub fn import_sftp_with<F>(
821 self,
822 host: impl Into<String>,
823 username: impl Into<String>,
824 configure: F,
825 ) -> Self
826 where
827 F: FnOnce(SftpImportTask) -> SftpImportTask,
828 {
829 self.append_configured_task(SftpImportTask::new(host, username), configure)
830 }
831
832 pub fn convert_with<F>(
834 self,
835 output_format: impl Into<String>,
836 configure: F,
837 ) -> crate::Result<Self>
838 where
839 F: FnOnce(ConvertTask) -> ConvertTask,
840 {
841 let input = self.previous_input("convert_with")?;
842 Ok(self.append_configured_task(ConvertTask::new(input, output_format), configure))
843 }
844
845 pub fn optimize_with<F>(self, configure: F) -> crate::Result<Self>
847 where
848 F: FnOnce(OptimizeTask) -> OptimizeTask,
849 {
850 let input = self.previous_input("optimize_with")?;
851 Ok(self.append_configured_task(OptimizeTask::new(input), configure))
852 }
853
854 pub fn watermark_text_with<F>(
856 self,
857 text: impl Into<String>,
858 configure: F,
859 ) -> crate::Result<Self>
860 where
861 F: FnOnce(WatermarkTask) -> WatermarkTask,
862 {
863 let input = self.previous_input("watermark_text_with")?;
864 Ok(self.append_configured_task(WatermarkTask::text(input, text), configure))
865 }
866
867 pub fn watermark_image_with<F>(
869 self,
870 image_task_name: impl Into<String>,
871 configure: F,
872 ) -> crate::Result<Self>
873 where
874 F: FnOnce(WatermarkTask) -> WatermarkTask,
875 {
876 let input = self.previous_input("watermark_image_with")?;
877 Ok(self.append_configured_task(WatermarkTask::image(input, image_task_name), configure))
878 }
879
880 pub fn capture_website_with<F>(
882 self,
883 url: impl Into<String>,
884 output_format: impl Into<String>,
885 configure: F,
886 ) -> Self
887 where
888 F: FnOnce(CaptureWebsiteTask) -> CaptureWebsiteTask,
889 {
890 self.append_configured_task(CaptureWebsiteTask::new(url, output_format), configure)
891 }
892
893 pub fn thumbnail_with<F>(
895 self,
896 output_format: impl Into<String>,
897 configure: F,
898 ) -> crate::Result<Self>
899 where
900 F: FnOnce(ThumbnailTask) -> ThumbnailTask,
901 {
902 let input = self.previous_input("thumbnail_with")?;
903 Ok(self.append_configured_task(ThumbnailTask::new(input, output_format), configure))
904 }
905
906 pub fn metadata_with<F>(self, configure: F) -> crate::Result<Self>
908 where
909 F: FnOnce(MetadataTask) -> MetadataTask,
910 {
911 let input = self.previous_input("metadata_with")?;
912 Ok(self.append_configured_task(MetadataTask::new(input), configure))
913 }
914
915 pub fn metadata_write_with<F>(self, configure: F) -> crate::Result<Self>
917 where
918 F: FnOnce(MetadataWriteTask) -> MetadataWriteTask,
919 {
920 let input = self.previous_input("metadata_write_with")?;
921 Ok(self.append_configured_task(MetadataWriteTask::new(input), configure))
922 }
923
924 pub fn merge_with<F>(
926 self,
927 output_format: impl Into<String>,
928 configure: F,
929 ) -> crate::Result<Self>
930 where
931 F: FnOnce(MergeTask) -> MergeTask,
932 {
933 let input = self.previous_input("merge_with")?;
934 Ok(self.append_configured_task(MergeTask::new(input, output_format), configure))
935 }
936
937 pub fn archive_with<F>(
939 self,
940 output_format: impl Into<String>,
941 configure: F,
942 ) -> crate::Result<Self>
943 where
944 F: FnOnce(ArchiveTask) -> ArchiveTask,
945 {
946 let input = self.previous_input("archive_with")?;
947 Ok(self.append_configured_task(ArchiveTask::new(input, output_format), configure))
948 }
949
950 pub fn command_with<F>(
952 self,
953 engine: impl Into<String>,
954 command: impl Into<String>,
955 arguments: impl Into<String>,
956 configure: F,
957 ) -> crate::Result<Self>
958 where
959 F: FnOnce(CommandTask) -> CommandTask,
960 {
961 let input = self.previous_input("command_with")?;
962 Ok(self.append_configured_task(
963 CommandTask::new(input, engine, command, arguments),
964 configure,
965 ))
966 }
967
968 pub fn export_url_with<F>(self, configure: F) -> crate::Result<Self>
970 where
971 F: FnOnce(ExportUrlTask) -> ExportUrlTask,
972 {
973 let input = self.previous_input("export_url_with")?;
974 Ok(self.append_configured_task(ExportUrlTask::new(input), configure))
975 }
976
977 pub fn export_s3_with<F>(
979 self,
980 bucket: impl Into<String>,
981 region: impl Into<String>,
982 access_key_id: impl Into<String>,
983 secret_access_key: impl Into<String>,
984 configure: F,
985 ) -> crate::Result<Self>
986 where
987 F: FnOnce(S3ExportTask) -> S3ExportTask,
988 {
989 let input = self.previous_input("export_s3_with")?;
990 Ok(self.append_configured_task(
991 S3ExportTask::new(input, bucket, region, access_key_id, secret_access_key),
992 configure,
993 ))
994 }
995
996 pub fn export_azure_blob_with<F>(
998 self,
999 storage_account: impl Into<String>,
1000 container: impl Into<String>,
1001 configure: F,
1002 ) -> crate::Result<Self>
1003 where
1004 F: FnOnce(AzureBlobExportTask) -> AzureBlobExportTask,
1005 {
1006 let input = self.previous_input("export_azure_blob_with")?;
1007 Ok(self.append_configured_task(
1008 AzureBlobExportTask::new(input, storage_account, container),
1009 configure,
1010 ))
1011 }
1012
1013 pub fn export_google_cloud_storage_with<F>(
1015 self,
1016 project_id: impl Into<String>,
1017 bucket: impl Into<String>,
1018 client_email: impl Into<String>,
1019 private_key: impl Into<String>,
1020 configure: F,
1021 ) -> crate::Result<Self>
1022 where
1023 F: FnOnce(GoogleCloudStorageExportTask) -> GoogleCloudStorageExportTask,
1024 {
1025 let input = self.previous_input("export_google_cloud_storage_with")?;
1026 Ok(self.append_configured_task(
1027 GoogleCloudStorageExportTask::new(input, project_id, bucket, client_email, private_key),
1028 configure,
1029 ))
1030 }
1031
1032 pub fn export_openstack_with<F>(
1034 self,
1035 auth_url: impl Into<String>,
1036 username: impl Into<String>,
1037 password: impl Into<String>,
1038 region: impl Into<String>,
1039 container: impl Into<String>,
1040 configure: F,
1041 ) -> crate::Result<Self>
1042 where
1043 F: FnOnce(OpenStackExportTask) -> OpenStackExportTask,
1044 {
1045 let input = self.previous_input("export_openstack_with")?;
1046 Ok(self.append_configured_task(
1047 OpenStackExportTask::new(input, auth_url, username, password, region, container),
1048 configure,
1049 ))
1050 }
1051
1052 pub fn export_sftp_with<F>(
1054 self,
1055 host: impl Into<String>,
1056 username: impl Into<String>,
1057 configure: F,
1058 ) -> crate::Result<Self>
1059 where
1060 F: FnOnce(SftpExportTask) -> SftpExportTask,
1061 {
1062 let input = self.previous_input("export_sftp_with")?;
1063 Ok(self.append_configured_task(SftpExportTask::new(input, host, username), configure))
1064 }
1065
1066 pub fn export_upload_with<F>(self, url: impl Into<String>, configure: F) -> crate::Result<Self>
1068 where
1069 F: FnOnce(ExportUploadTask) -> ExportUploadTask,
1070 {
1071 let input = self.previous_input("export_upload_with")?;
1072 Ok(self.append_configured_task(ExportUploadTask::new(input, url), configure))
1073 }
1074
1075 pub fn build(self) -> JobCreateRequest {
1077 self.request
1078 }
1079
1080 fn append_task(mut self, task: impl Into<TaskRequest>) -> Self {
1081 self.add_task(task);
1082 self
1083 }
1084
1085 fn append_configured_task<T, F>(self, task: T, configure: F) -> Self
1086 where
1087 T: Into<TaskRequest>,
1088 F: FnOnce(T) -> T,
1089 {
1090 self.append_task(configure(task))
1091 }
1092
1093 fn previous_input(&self, method: &'static str) -> crate::Result<Input> {
1094 self.last_task
1095 .as_ref()
1096 .map(Input::from)
1097 .ok_or_else(|| InvalidBuilderState::missing_previous_task(method).into())
1098 }
1099}
1100
1101fn generated_task_name(operation: &str, existing: &BTreeMap<String, TaskRequest>) -> TaskName {
1102 deduplicated_task_name(&task_name_base(operation), existing)
1103}
1104
1105fn deduplicated_task_name(base: &str, existing: &BTreeMap<String, TaskRequest>) -> TaskName {
1106 if !existing.contains_key(base) {
1107 return TaskName::new(base);
1108 }
1109
1110 let mut counter = 2;
1111 loop {
1112 let candidate = format!("{base}-{counter}");
1113 if !existing.contains_key(&candidate) {
1114 return TaskName::new(candidate);
1115 }
1116 counter += 1;
1117 }
1118}
1119
1120fn task_name_base(operation: &str) -> String {
1121 let mut name = String::new();
1122 let mut previous_was_separator = false;
1123
1124 for byte in operation.bytes() {
1125 if byte.is_ascii_alphanumeric() {
1126 name.push(byte.to_ascii_lowercase() as char);
1127 previous_was_separator = false;
1128 } else if !previous_was_separator && !name.is_empty() {
1129 name.push('-');
1130 previous_was_separator = true;
1131 }
1132 }
1133
1134 while name.ends_with('-') {
1135 name.pop();
1136 }
1137
1138 if name.is_empty() {
1139 "task".to_string()
1140 } else {
1141 name
1142 }
1143}
1144
1145impl From<JobBuilder> for JobCreateRequest {
1146 fn from(builder: JobBuilder) -> Self {
1147 builder.build()
1148 }
1149}
1150
1151#[derive(Clone, Debug, Default)]
1172pub struct JobGraphBuilder {
1173 builder: JobBuilder,
1174}
1175
1176impl JobGraphBuilder {
1177 pub fn new() -> Self {
1182 Self::default()
1183 }
1184
1185 pub fn tag(&mut self, tag: impl Into<String>) -> &mut Self {
1187 self.builder.request.tag = Some(tag.into());
1188 self
1189 }
1190
1191 pub fn webhook_url(&mut self, webhook_url: impl Into<String>) -> &mut Self {
1193 self.builder.request.webhook_url = Some(webhook_url.into());
1194 self
1195 }
1196
1197 pub fn redirect(&mut self, redirect: bool) -> &mut Self {
1199 self.builder.request.redirect = Some(redirect);
1200 self
1201 }
1202
1203 pub fn option(&mut self, key: impl Into<String>, value: impl Into<Value>) -> &mut Self {
1205 self.builder.request.insert_option(key.into(), value.into());
1206 self
1207 }
1208
1209 pub fn add_task(&mut self, task: impl Into<TaskRequest>) -> TaskName {
1211 self.builder.add_task(task)
1212 }
1213
1214 pub fn add_named_task(
1216 &mut self,
1217 name: impl Into<String>,
1218 task: impl Into<TaskRequest>,
1219 ) -> TaskName {
1220 self.builder.add_named_task(name, task)
1221 }
1222
1223 pub fn import_url(&mut self, url: impl Into<String>) -> TaskName {
1225 self.import_url_with(url, identity)
1226 }
1227
1228 pub fn import_url_with<F>(&mut self, url: impl Into<String>, configure: F) -> TaskName
1230 where
1231 F: FnOnce(ImportUrlTask) -> ImportUrlTask,
1232 {
1233 self.add_configured_task(ImportUrlTask::new(url), configure)
1234 }
1235
1236 pub fn import_upload(&mut self) -> TaskName {
1238 self.import_upload_with(identity)
1239 }
1240
1241 pub fn import_upload_with<F>(&mut self, configure: F) -> TaskName
1243 where
1244 F: FnOnce(ImportUploadTask) -> ImportUploadTask,
1245 {
1246 self.add_configured_task(ImportUploadTask::default(), configure)
1247 }
1248
1249 pub fn import_base64(
1251 &mut self,
1252 file: impl Into<String>,
1253 filename: impl Into<String>,
1254 ) -> TaskName {
1255 self.add_task(Base64ImportTask::new(file, filename))
1256 }
1257
1258 pub fn import_raw(&mut self, file: impl Into<String>, filename: impl Into<String>) -> TaskName {
1260 self.add_task(RawImportTask::new(file, filename))
1261 }
1262
1263 pub fn import_s3(
1265 &mut self,
1266 bucket: impl Into<String>,
1267 region: impl Into<String>,
1268 access_key_id: impl Into<String>,
1269 secret_access_key: impl Into<String>,
1270 ) -> TaskName {
1271 self.import_s3_with(bucket, region, access_key_id, secret_access_key, identity)
1272 }
1273
1274 pub fn import_s3_with<F>(
1276 &mut self,
1277 bucket: impl Into<String>,
1278 region: impl Into<String>,
1279 access_key_id: impl Into<String>,
1280 secret_access_key: impl Into<String>,
1281 configure: F,
1282 ) -> TaskName
1283 where
1284 F: FnOnce(S3ImportTask) -> S3ImportTask,
1285 {
1286 self.add_configured_task(
1287 S3ImportTask::new(bucket, region, access_key_id, secret_access_key),
1288 configure,
1289 )
1290 }
1291
1292 pub fn import_azure_blob(
1294 &mut self,
1295 storage_account: impl Into<String>,
1296 container: impl Into<String>,
1297 ) -> TaskName {
1298 self.import_azure_blob_with(storage_account, container, identity)
1299 }
1300
1301 pub fn import_azure_blob_with<F>(
1303 &mut self,
1304 storage_account: impl Into<String>,
1305 container: impl Into<String>,
1306 configure: F,
1307 ) -> TaskName
1308 where
1309 F: FnOnce(AzureBlobImportTask) -> AzureBlobImportTask,
1310 {
1311 self.add_configured_task(
1312 AzureBlobImportTask::new(storage_account, container),
1313 configure,
1314 )
1315 }
1316
1317 pub fn import_google_cloud_storage(
1319 &mut self,
1320 project_id: impl Into<String>,
1321 bucket: impl Into<String>,
1322 client_email: impl Into<String>,
1323 private_key: impl Into<String>,
1324 ) -> TaskName {
1325 self.import_google_cloud_storage_with(
1326 project_id,
1327 bucket,
1328 client_email,
1329 private_key,
1330 identity,
1331 )
1332 }
1333
1334 pub fn import_google_cloud_storage_with<F>(
1336 &mut self,
1337 project_id: impl Into<String>,
1338 bucket: impl Into<String>,
1339 client_email: impl Into<String>,
1340 private_key: impl Into<String>,
1341 configure: F,
1342 ) -> TaskName
1343 where
1344 F: FnOnce(GoogleCloudStorageImportTask) -> GoogleCloudStorageImportTask,
1345 {
1346 self.add_configured_task(
1347 GoogleCloudStorageImportTask::new(project_id, bucket, client_email, private_key),
1348 configure,
1349 )
1350 }
1351
1352 pub fn import_openstack(
1354 &mut self,
1355 auth_url: impl Into<String>,
1356 username: impl Into<String>,
1357 password: impl Into<String>,
1358 region: impl Into<String>,
1359 container: impl Into<String>,
1360 ) -> TaskName {
1361 self.import_openstack_with(auth_url, username, password, region, container, identity)
1362 }
1363
1364 pub fn import_openstack_with<F>(
1366 &mut self,
1367 auth_url: impl Into<String>,
1368 username: impl Into<String>,
1369 password: impl Into<String>,
1370 region: impl Into<String>,
1371 container: impl Into<String>,
1372 configure: F,
1373 ) -> TaskName
1374 where
1375 F: FnOnce(OpenStackImportTask) -> OpenStackImportTask,
1376 {
1377 self.add_configured_task(
1378 OpenStackImportTask::new(auth_url, username, password, region, container),
1379 configure,
1380 )
1381 }
1382
1383 pub fn import_sftp(
1385 &mut self,
1386 host: impl Into<String>,
1387 username: impl Into<String>,
1388 ) -> TaskName {
1389 self.import_sftp_with(host, username, identity)
1390 }
1391
1392 pub fn import_sftp_with<F>(
1394 &mut self,
1395 host: impl Into<String>,
1396 username: impl Into<String>,
1397 configure: F,
1398 ) -> TaskName
1399 where
1400 F: FnOnce(SftpImportTask) -> SftpImportTask,
1401 {
1402 self.add_configured_task(SftpImportTask::new(host, username), configure)
1403 }
1404
1405 pub fn convert(
1407 &mut self,
1408 input: impl Into<Input>,
1409 output_format: impl Into<String>,
1410 ) -> TaskName {
1411 self.convert_with(input, output_format, identity)
1412 }
1413
1414 pub fn convert_with<F>(
1416 &mut self,
1417 input: impl Into<Input>,
1418 output_format: impl Into<String>,
1419 configure: F,
1420 ) -> TaskName
1421 where
1422 F: FnOnce(ConvertTask) -> ConvertTask,
1423 {
1424 self.add_configured_task(ConvertTask::new(input, output_format), configure)
1425 }
1426
1427 pub fn optimize(&mut self, input: impl Into<Input>) -> TaskName {
1429 self.optimize_with(input, identity)
1430 }
1431
1432 pub fn optimize_with<F>(&mut self, input: impl Into<Input>, configure: F) -> TaskName
1434 where
1435 F: FnOnce(OptimizeTask) -> OptimizeTask,
1436 {
1437 self.add_configured_task(OptimizeTask::new(input), configure)
1438 }
1439
1440 pub fn watermark_text(&mut self, input: impl Into<Input>, text: impl Into<String>) -> TaskName {
1442 self.watermark_text_with(input, text, identity)
1443 }
1444
1445 pub fn watermark_text_with<F>(
1447 &mut self,
1448 input: impl Into<Input>,
1449 text: impl Into<String>,
1450 configure: F,
1451 ) -> TaskName
1452 where
1453 F: FnOnce(WatermarkTask) -> WatermarkTask,
1454 {
1455 self.add_configured_task(WatermarkTask::text(input, text), configure)
1456 }
1457
1458 pub fn watermark_image(
1460 &mut self,
1461 input: impl Into<Input>,
1462 image_task_name: impl Into<String>,
1463 ) -> TaskName {
1464 self.watermark_image_with(input, image_task_name, identity)
1465 }
1466
1467 pub fn watermark_image_with<F>(
1469 &mut self,
1470 input: impl Into<Input>,
1471 image_task_name: impl Into<String>,
1472 configure: F,
1473 ) -> TaskName
1474 where
1475 F: FnOnce(WatermarkTask) -> WatermarkTask,
1476 {
1477 self.add_configured_task(WatermarkTask::image(input, image_task_name), configure)
1478 }
1479
1480 pub fn capture_website(
1482 &mut self,
1483 url: impl Into<String>,
1484 output_format: impl Into<String>,
1485 ) -> TaskName {
1486 self.capture_website_with(url, output_format, identity)
1487 }
1488
1489 pub fn capture_website_with<F>(
1491 &mut self,
1492 url: impl Into<String>,
1493 output_format: impl Into<String>,
1494 configure: F,
1495 ) -> TaskName
1496 where
1497 F: FnOnce(CaptureWebsiteTask) -> CaptureWebsiteTask,
1498 {
1499 self.add_configured_task(CaptureWebsiteTask::new(url, output_format), configure)
1500 }
1501
1502 pub fn thumbnail(
1504 &mut self,
1505 input: impl Into<Input>,
1506 output_format: impl Into<String>,
1507 ) -> TaskName {
1508 self.thumbnail_with(input, output_format, identity)
1509 }
1510
1511 pub fn thumbnail_with<F>(
1513 &mut self,
1514 input: impl Into<Input>,
1515 output_format: impl Into<String>,
1516 configure: F,
1517 ) -> TaskName
1518 where
1519 F: FnOnce(ThumbnailTask) -> ThumbnailTask,
1520 {
1521 self.add_configured_task(ThumbnailTask::new(input, output_format), configure)
1522 }
1523
1524 pub fn metadata(&mut self, input: impl Into<Input>) -> TaskName {
1526 self.metadata_with(input, identity)
1527 }
1528
1529 pub fn metadata_with<F>(&mut self, input: impl Into<Input>, configure: F) -> TaskName
1531 where
1532 F: FnOnce(MetadataTask) -> MetadataTask,
1533 {
1534 self.add_configured_task(MetadataTask::new(input), configure)
1535 }
1536
1537 pub fn metadata_write(&mut self, input: impl Into<Input>) -> TaskName {
1539 self.metadata_write_with(input, identity)
1540 }
1541
1542 pub fn metadata_write_with<F>(&mut self, input: impl Into<Input>, configure: F) -> TaskName
1544 where
1545 F: FnOnce(MetadataWriteTask) -> MetadataWriteTask,
1546 {
1547 self.add_configured_task(MetadataWriteTask::new(input), configure)
1548 }
1549
1550 pub fn merge(&mut self, input: impl Into<Input>, output_format: impl Into<String>) -> TaskName {
1552 self.merge_with(input, output_format, identity)
1553 }
1554
1555 pub fn merge_with<F>(
1557 &mut self,
1558 input: impl Into<Input>,
1559 output_format: impl Into<String>,
1560 configure: F,
1561 ) -> TaskName
1562 where
1563 F: FnOnce(MergeTask) -> MergeTask,
1564 {
1565 self.add_configured_task(MergeTask::new(input, output_format), configure)
1566 }
1567
1568 pub fn archive(
1570 &mut self,
1571 input: impl Into<Input>,
1572 output_format: impl Into<String>,
1573 ) -> TaskName {
1574 self.archive_with(input, output_format, identity)
1575 }
1576
1577 pub fn archive_with<F>(
1579 &mut self,
1580 input: impl Into<Input>,
1581 output_format: impl Into<String>,
1582 configure: F,
1583 ) -> TaskName
1584 where
1585 F: FnOnce(ArchiveTask) -> ArchiveTask,
1586 {
1587 self.add_configured_task(ArchiveTask::new(input, output_format), configure)
1588 }
1589
1590 pub fn command(
1592 &mut self,
1593 input: impl Into<Input>,
1594 engine: impl Into<String>,
1595 command: impl Into<String>,
1596 arguments: impl Into<String>,
1597 ) -> TaskName {
1598 self.command_with(input, engine, command, arguments, identity)
1599 }
1600
1601 pub fn command_with<F>(
1603 &mut self,
1604 input: impl Into<Input>,
1605 engine: impl Into<String>,
1606 command: impl Into<String>,
1607 arguments: impl Into<String>,
1608 configure: F,
1609 ) -> TaskName
1610 where
1611 F: FnOnce(CommandTask) -> CommandTask,
1612 {
1613 self.add_configured_task(
1614 CommandTask::new(input, engine, command, arguments),
1615 configure,
1616 )
1617 }
1618
1619 graph_pdf_task_methods!(pdf_a, pdf_a_with, PdfATask);
1620 graph_pdf_task_methods!(pdf_x, pdf_x_with, PdfXTask);
1621 graph_pdf_task_methods!(pdf_ocr, pdf_ocr_with, PdfOcrTask);
1622 graph_pdf_task_methods!(pdf_encrypt, pdf_encrypt_with, PdfEncryptTask);
1623 graph_pdf_task_methods!(pdf_decrypt, pdf_decrypt_with, PdfDecryptTask);
1624 graph_pdf_task_methods!(pdf_split_pages, pdf_split_pages_with, PdfSplitPagesTask);
1625 graph_pdf_task_methods!(
1626 pdf_extract_pages,
1627 pdf_extract_pages_with,
1628 PdfExtractPagesTask
1629 );
1630 graph_pdf_task_methods!(pdf_rotate_pages, pdf_rotate_pages_with, PdfRotatePagesTask);
1631
1632 pub fn export_url(&mut self, input: impl Into<Input>) -> TaskName {
1634 self.export_url_with(input, identity)
1635 }
1636
1637 pub fn export_url_with<F>(&mut self, input: impl Into<Input>, configure: F) -> TaskName
1639 where
1640 F: FnOnce(ExportUrlTask) -> ExportUrlTask,
1641 {
1642 self.add_configured_task(ExportUrlTask::new(input), configure)
1643 }
1644
1645 pub fn export_s3(
1647 &mut self,
1648 input: impl Into<Input>,
1649 bucket: impl Into<String>,
1650 region: impl Into<String>,
1651 access_key_id: impl Into<String>,
1652 secret_access_key: impl Into<String>,
1653 ) -> TaskName {
1654 self.export_s3_with(
1655 input,
1656 bucket,
1657 region,
1658 access_key_id,
1659 secret_access_key,
1660 identity,
1661 )
1662 }
1663
1664 pub fn export_s3_with<F>(
1666 &mut self,
1667 input: impl Into<Input>,
1668 bucket: impl Into<String>,
1669 region: impl Into<String>,
1670 access_key_id: impl Into<String>,
1671 secret_access_key: impl Into<String>,
1672 configure: F,
1673 ) -> TaskName
1674 where
1675 F: FnOnce(S3ExportTask) -> S3ExportTask,
1676 {
1677 self.add_configured_task(
1678 S3ExportTask::new(input, bucket, region, access_key_id, secret_access_key),
1679 configure,
1680 )
1681 }
1682
1683 pub fn export_azure_blob(
1685 &mut self,
1686 input: impl Into<Input>,
1687 storage_account: impl Into<String>,
1688 container: impl Into<String>,
1689 ) -> TaskName {
1690 self.export_azure_blob_with(input, storage_account, container, identity)
1691 }
1692
1693 pub fn export_azure_blob_with<F>(
1695 &mut self,
1696 input: impl Into<Input>,
1697 storage_account: impl Into<String>,
1698 container: impl Into<String>,
1699 configure: F,
1700 ) -> TaskName
1701 where
1702 F: FnOnce(AzureBlobExportTask) -> AzureBlobExportTask,
1703 {
1704 self.add_configured_task(
1705 AzureBlobExportTask::new(input, storage_account, container),
1706 configure,
1707 )
1708 }
1709
1710 pub fn export_google_cloud_storage(
1712 &mut self,
1713 input: impl Into<Input>,
1714 project_id: impl Into<String>,
1715 bucket: impl Into<String>,
1716 client_email: impl Into<String>,
1717 private_key: impl Into<String>,
1718 ) -> TaskName {
1719 self.export_google_cloud_storage_with(
1720 input,
1721 project_id,
1722 bucket,
1723 client_email,
1724 private_key,
1725 identity,
1726 )
1727 }
1728
1729 pub fn export_google_cloud_storage_with<F>(
1731 &mut self,
1732 input: impl Into<Input>,
1733 project_id: impl Into<String>,
1734 bucket: impl Into<String>,
1735 client_email: impl Into<String>,
1736 private_key: impl Into<String>,
1737 configure: F,
1738 ) -> TaskName
1739 where
1740 F: FnOnce(GoogleCloudStorageExportTask) -> GoogleCloudStorageExportTask,
1741 {
1742 self.add_configured_task(
1743 GoogleCloudStorageExportTask::new(input, project_id, bucket, client_email, private_key),
1744 configure,
1745 )
1746 }
1747
1748 pub fn export_openstack(
1750 &mut self,
1751 input: impl Into<Input>,
1752 auth_url: impl Into<String>,
1753 username: impl Into<String>,
1754 password: impl Into<String>,
1755 region: impl Into<String>,
1756 container: impl Into<String>,
1757 ) -> TaskName {
1758 self.export_openstack_with(
1759 input, auth_url, username, password, region, container, identity,
1760 )
1761 }
1762
1763 #[allow(clippy::too_many_arguments)]
1765 pub fn export_openstack_with<F>(
1766 &mut self,
1767 input: impl Into<Input>,
1768 auth_url: impl Into<String>,
1769 username: impl Into<String>,
1770 password: impl Into<String>,
1771 region: impl Into<String>,
1772 container: impl Into<String>,
1773 configure: F,
1774 ) -> TaskName
1775 where
1776 F: FnOnce(OpenStackExportTask) -> OpenStackExportTask,
1777 {
1778 self.add_configured_task(
1779 OpenStackExportTask::new(input, auth_url, username, password, region, container),
1780 configure,
1781 )
1782 }
1783
1784 pub fn export_sftp(
1786 &mut self,
1787 input: impl Into<Input>,
1788 host: impl Into<String>,
1789 username: impl Into<String>,
1790 ) -> TaskName {
1791 self.export_sftp_with(input, host, username, identity)
1792 }
1793
1794 pub fn export_sftp_with<F>(
1796 &mut self,
1797 input: impl Into<Input>,
1798 host: impl Into<String>,
1799 username: impl Into<String>,
1800 configure: F,
1801 ) -> TaskName
1802 where
1803 F: FnOnce(SftpExportTask) -> SftpExportTask,
1804 {
1805 self.add_configured_task(SftpExportTask::new(input, host, username), configure)
1806 }
1807
1808 pub fn export_upload(&mut self, input: impl Into<Input>, url: impl Into<String>) -> TaskName {
1810 self.export_upload_with(input, url, identity)
1811 }
1812
1813 pub fn export_upload_with<F>(
1815 &mut self,
1816 input: impl Into<Input>,
1817 url: impl Into<String>,
1818 configure: F,
1819 ) -> TaskName
1820 where
1821 F: FnOnce(ExportUploadTask) -> ExportUploadTask,
1822 {
1823 self.add_configured_task(ExportUploadTask::new(input, url), configure)
1824 }
1825
1826 pub fn into_builder(self) -> JobBuilder {
1828 self.builder
1829 }
1830
1831 pub fn build(self) -> JobCreateRequest {
1833 self.into_builder().build()
1834 }
1835
1836 fn add_configured_task<T, F>(&mut self, task: T, configure: F) -> TaskName
1837 where
1838 T: Into<TaskRequest>,
1839 F: FnOnce(T) -> T,
1840 {
1841 self.add_task(configure(task))
1842 }
1843}
1844
1845impl From<JobGraphBuilder> for JobBuilder {
1846 fn from(builder: JobGraphBuilder) -> Self {
1847 builder.into_builder()
1848 }
1849}
1850
1851impl From<JobGraphBuilder> for JobCreateRequest {
1852 fn from(builder: JobGraphBuilder) -> Self {
1853 builder.build()
1854 }
1855}
1856
1857#[derive(Clone, Debug, Default, Serialize)]
1859pub struct JobListQuery {
1860 #[serde(rename = "filter[status]", skip_serializing_if = "Option::is_none")]
1861 filter_status: Option<JobStatus>,
1862 #[serde(rename = "filter[tag]", skip_serializing_if = "Option::is_none")]
1863 filter_tag: Option<String>,
1864 #[serde(skip_serializing_if = "Option::is_none")]
1865 include: Option<String>,
1866 #[serde(skip_serializing_if = "Option::is_none")]
1867 per_page: Option<u32>,
1868 #[serde(skip_serializing_if = "Option::is_none")]
1869 page: Option<u32>,
1870}
1871
1872impl JobListQuery {
1873 pub fn status(mut self, status: JobStatus) -> Self {
1874 self.filter_status = Some(status);
1875 self
1876 }
1877
1878 pub fn tag(mut self, tag: impl Into<String>) -> Self {
1879 self.filter_tag = Some(tag.into());
1880 self
1881 }
1882
1883 pub fn include(mut self, include: impl Into<String>) -> Self {
1884 self.include = Some(include.into());
1885 self
1886 }
1887
1888 pub fn per_page(mut self, per_page: u32) -> Self {
1889 self.per_page = Some(per_page);
1890 self
1891 }
1892
1893 pub fn page(mut self, page: u32) -> Self {
1894 self.page = Some(page);
1895 self
1896 }
1897}
1898
1899#[derive(Clone, Debug, Default, Serialize)]
1901pub struct JobGetQuery {
1902 #[serde(skip_serializing_if = "Option::is_none")]
1903 include: Option<String>,
1904 #[serde(skip_serializing_if = "Option::is_none")]
1905 redirect: Option<bool>,
1906}
1907
1908impl JobGetQuery {
1909 pub fn include(mut self, include: impl Into<String>) -> Self {
1910 self.include = Some(include.into());
1911 self
1912 }
1913
1914 pub fn redirect(mut self, redirect: bool) -> Self {
1915 self.redirect = Some(redirect);
1916 self
1917 }
1918}
1919
1920#[derive(Clone, Debug, Default, Serialize)]
1922pub struct TaskListQuery {
1923 #[serde(rename = "filter[job_id]", skip_serializing_if = "Option::is_none")]
1924 filter_job_id: Option<String>,
1925 #[serde(rename = "filter[status]", skip_serializing_if = "Option::is_none")]
1926 filter_status: Option<TaskStatus>,
1927 #[serde(rename = "filter[operation]", skip_serializing_if = "Option::is_none")]
1928 filter_operation: Option<String>,
1929 #[serde(skip_serializing_if = "Option::is_none")]
1930 include: Option<String>,
1931 #[serde(skip_serializing_if = "Option::is_none")]
1932 per_page: Option<u32>,
1933 #[serde(skip_serializing_if = "Option::is_none")]
1934 page: Option<u32>,
1935}
1936
1937impl TaskListQuery {
1938 pub fn job_id(mut self, job_id: impl Into<String>) -> Self {
1939 self.filter_job_id = Some(job_id.into());
1940 self
1941 }
1942
1943 pub fn status(mut self, status: TaskStatus) -> Self {
1944 self.filter_status = Some(status);
1945 self
1946 }
1947
1948 pub fn operation(mut self, operation: impl Into<String>) -> Self {
1949 self.filter_operation = Some(operation.into());
1950 self
1951 }
1952
1953 pub fn include(mut self, include: impl Into<String>) -> Self {
1954 self.include = Some(include.into());
1955 self
1956 }
1957
1958 pub fn per_page(mut self, per_page: u32) -> Self {
1959 self.per_page = Some(per_page);
1960 self
1961 }
1962
1963 pub fn page(mut self, page: u32) -> Self {
1964 self.page = Some(page);
1965 self
1966 }
1967}
1968
1969#[derive(Clone, Debug, Default, Serialize)]
1971pub struct TaskGetQuery {
1972 #[serde(skip_serializing_if = "Option::is_none")]
1973 include: Option<String>,
1974}
1975
1976impl TaskGetQuery {
1977 pub fn include(mut self, include: impl Into<String>) -> Self {
1978 self.include = Some(include.into());
1979 self
1980 }
1981}
1982
1983#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
1985#[serde(rename_all = "lowercase")]
1986#[non_exhaustive]
1987pub enum JobStatus {
1988 Waiting,
1989 Processing,
1990 Finished,
1991 Error,
1992 #[serde(other)]
1993 Unknown,
1994}
1995
1996impl JobStatus {
1997 pub fn is_waiting(&self) -> bool {
1998 matches!(self, Self::Waiting)
1999 }
2000
2001 pub fn is_processing(&self) -> bool {
2002 matches!(self, Self::Processing)
2003 }
2004
2005 pub fn is_finished(&self) -> bool {
2006 matches!(self, Self::Finished)
2007 }
2008
2009 pub fn is_error(&self) -> bool {
2010 matches!(self, Self::Error)
2011 }
2012
2013 pub fn is_terminal(&self) -> bool {
2014 matches!(self, Self::Finished | Self::Error)
2015 }
2016}
2017
2018#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
2020#[serde(rename_all = "lowercase")]
2021#[non_exhaustive]
2022pub enum TaskStatus {
2023 Waiting,
2024 Queued,
2025 Processing,
2026 Finished,
2027 Error,
2028 #[serde(other)]
2029 Unknown,
2030}
2031
2032impl TaskStatus {
2033 pub fn is_waiting(&self) -> bool {
2034 matches!(self, Self::Waiting)
2035 }
2036
2037 pub fn is_queued(&self) -> bool {
2038 matches!(self, Self::Queued)
2039 }
2040
2041 pub fn is_processing(&self) -> bool {
2042 matches!(self, Self::Processing)
2043 }
2044
2045 pub fn is_finished(&self) -> bool {
2046 matches!(self, Self::Finished)
2047 }
2048
2049 pub fn is_error(&self) -> bool {
2050 matches!(self, Self::Error)
2051 }
2052
2053 pub fn is_terminal(&self) -> bool {
2054 matches!(self, Self::Finished | Self::Error)
2055 }
2056}
2057
2058#[derive(Clone, Deserialize, Serialize)]
2060#[non_exhaustive]
2061pub struct Job {
2062 pub id: String,
2063 #[serde(default)]
2064 pub tag: Option<String>,
2065 pub status: JobStatus,
2066 #[serde(default)]
2067 pub created_at: Option<String>,
2068 #[serde(default)]
2069 pub started_at: Option<String>,
2070 #[serde(default)]
2071 pub ended_at: Option<String>,
2072 #[serde(default)]
2073 pub tasks: Vec<JobTask>,
2074 #[serde(default)]
2075 pub links: BTreeMap<String, Value>,
2076 #[serde(flatten)]
2077 pub extra: BTreeMap<String, Value>,
2078}
2079
2080impl Job {
2081 pub fn is_finished(&self) -> bool {
2082 self.status.is_finished()
2083 }
2084
2085 pub fn is_error(&self) -> bool {
2086 self.status.is_error()
2087 }
2088
2089 pub fn is_terminal(&self) -> bool {
2090 self.status.is_terminal()
2091 }
2092
2093 pub fn export_tasks(&self) -> impl Iterator<Item = &JobTask> {
2094 self.tasks.iter().filter(|task| task.is_export_url())
2095 }
2096
2097 pub fn finished_export_tasks(&self) -> impl Iterator<Item = &JobTask> {
2098 self.export_tasks().filter(|task| task.is_finished())
2099 }
2100
2101 pub fn export_urls(&self) -> Vec<&FileResult> {
2103 self.finished_export_tasks()
2104 .flat_map(JobTask::files)
2105 .collect()
2106 }
2107}
2108
2109impl fmt::Debug for Job {
2110 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2111 f.debug_struct("Job")
2112 .field("id", &self.id)
2113 .field("tag", &self.tag)
2114 .field("status", &self.status)
2115 .field("created_at", &self.created_at)
2116 .field("started_at", &self.started_at)
2117 .field("ended_at", &self.ended_at)
2118 .field("tasks", &self.tasks)
2119 .field("links", &self.links)
2120 .field("extra", &RedactedValueMap(&self.extra))
2121 .finish()
2122 }
2123}
2124
2125#[derive(Clone, Deserialize, Serialize)]
2127#[non_exhaustive]
2128pub struct JobTask {
2129 #[serde(default)]
2130 pub id: Option<String>,
2131 pub name: String,
2132 pub operation: String,
2133 pub status: TaskStatus,
2134 #[serde(default)]
2135 pub message: Option<String>,
2136 #[serde(default)]
2137 pub code: Option<String>,
2138 #[serde(default)]
2139 pub credits: Option<f64>,
2140 #[serde(default)]
2141 pub created_at: Option<String>,
2142 #[serde(default)]
2143 pub started_at: Option<String>,
2144 #[serde(default)]
2145 pub ended_at: Option<String>,
2146 #[serde(default)]
2147 pub result: Option<TaskResult>,
2148 #[serde(default)]
2149 pub payload: Option<Value>,
2150 #[serde(flatten)]
2151 pub extra: BTreeMap<String, Value>,
2152}
2153
2154impl JobTask {
2155 pub fn is_finished(&self) -> bool {
2156 self.status.is_finished()
2157 }
2158
2159 pub fn is_error(&self) -> bool {
2160 self.status.is_error()
2161 }
2162
2163 pub fn is_terminal(&self) -> bool {
2164 self.status.is_terminal()
2165 }
2166
2167 pub fn is_export_url(&self) -> bool {
2168 self.operation == "export/url"
2169 }
2170
2171 pub fn files(&self) -> impl Iterator<Item = &FileResult> {
2172 self.result
2173 .as_ref()
2174 .into_iter()
2175 .flat_map(|result| result.files.iter())
2176 }
2177}
2178
2179impl fmt::Debug for JobTask {
2180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2181 f.debug_struct("JobTask")
2182 .field("id", &self.id)
2183 .field("name", &self.name)
2184 .field("operation", &self.operation)
2185 .field("status", &self.status)
2186 .field("message", &self.message)
2187 .field("code", &self.code)
2188 .field("credits", &self.credits)
2189 .field("created_at", &self.created_at)
2190 .field("started_at", &self.started_at)
2191 .field("ended_at", &self.ended_at)
2192 .field("result", &self.result)
2193 .field("payload", &redacted_option(&self.payload))
2194 .field("extra", &RedactedValueMap(&self.extra))
2195 .finish()
2196 }
2197}
2198
2199#[derive(Clone, Deserialize, Serialize)]
2201#[non_exhaustive]
2202pub struct Task {
2203 pub id: String,
2204 #[serde(default)]
2205 pub job_id: Option<String>,
2206 pub operation: String,
2207 pub status: TaskStatus,
2208 #[serde(default)]
2209 pub message: Option<String>,
2210 #[serde(default)]
2211 pub code: Option<String>,
2212 #[serde(default)]
2213 pub credits: Option<f64>,
2214 #[serde(default)]
2215 pub created_at: Option<String>,
2216 #[serde(default)]
2217 pub started_at: Option<String>,
2218 #[serde(default)]
2219 pub ended_at: Option<String>,
2220 #[serde(default)]
2221 pub depends_on_tasks: BTreeMap<String, String>,
2222 #[serde(default)]
2223 pub result: Option<TaskResult>,
2224 #[serde(default)]
2225 pub payload: Option<Value>,
2226 #[serde(flatten)]
2227 pub extra: BTreeMap<String, Value>,
2228}
2229
2230impl Task {
2231 pub fn is_finished(&self) -> bool {
2232 self.status.is_finished()
2233 }
2234
2235 pub fn is_error(&self) -> bool {
2236 self.status.is_error()
2237 }
2238
2239 pub fn is_terminal(&self) -> bool {
2240 self.status.is_terminal()
2241 }
2242
2243 pub fn is_import_upload(&self) -> bool {
2244 self.operation == "import/upload"
2245 }
2246
2247 pub fn upload_form(&self) -> Option<&UploadForm> {
2249 self.result
2250 .as_ref()
2251 .and_then(|result| result.form.as_ref())
2252 .filter(|_| self.is_import_upload())
2253 }
2254
2255 pub fn is_upload_ready(&self) -> bool {
2256 self.upload_form().is_some()
2257 }
2258
2259 pub fn files(&self) -> impl Iterator<Item = &FileResult> {
2260 self.result
2261 .as_ref()
2262 .into_iter()
2263 .flat_map(|result| result.files.iter())
2264 }
2265}
2266
2267impl fmt::Debug for Task {
2268 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2269 f.debug_struct("Task")
2270 .field("id", &self.id)
2271 .field("job_id", &self.job_id)
2272 .field("operation", &self.operation)
2273 .field("status", &self.status)
2274 .field("message", &self.message)
2275 .field("code", &self.code)
2276 .field("credits", &self.credits)
2277 .field("created_at", &self.created_at)
2278 .field("started_at", &self.started_at)
2279 .field("ended_at", &self.ended_at)
2280 .field("depends_on_tasks", &self.depends_on_tasks)
2281 .field("result", &self.result)
2282 .field("payload", &redacted_option(&self.payload))
2283 .field("extra", &RedactedValueMap(&self.extra))
2284 .finish()
2285 }
2286}
2287
2288#[derive(Clone, Default, Deserialize, Serialize)]
2290#[non_exhaustive]
2291pub struct TaskResult {
2292 #[serde(default)]
2293 pub files: Vec<FileResult>,
2294 #[serde(default)]
2295 pub form: Option<UploadForm>,
2296 #[serde(flatten)]
2297 pub extra: BTreeMap<String, Value>,
2298}
2299
2300impl fmt::Debug for TaskResult {
2301 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2302 f.debug_struct("TaskResult")
2303 .field("files", &self.files)
2304 .field("form", &self.form)
2305 .field("extra", &RedactedValueMap(&self.extra))
2306 .finish()
2307 }
2308}
2309
2310#[derive(Clone, Debug, Deserialize, Serialize)]
2312#[non_exhaustive]
2313pub struct FileResult {
2314 #[serde(default)]
2315 pub dir: Option<String>,
2316 pub filename: String,
2317 #[serde(default)]
2318 pub url: Option<String>,
2319 #[serde(default)]
2320 pub size: Option<u64>,
2321 #[serde(flatten)]
2322 pub extra: BTreeMap<String, Value>,
2323}
2324
2325#[derive(Clone, Deserialize, Serialize)]
2327#[non_exhaustive]
2328pub struct UploadForm {
2329 pub url: String,
2330 #[serde(default)]
2331 pub parameters: BTreeMap<String, Value>,
2332}
2333
2334impl fmt::Debug for UploadForm {
2335 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2336 f.debug_struct("UploadForm")
2337 .field("url", &self.url)
2338 .field("parameters", &RedactedValueMap(&self.parameters))
2339 .finish()
2340 }
2341}
2342
2343#[derive(Debug, Deserialize)]
2344pub(crate) struct DataEnvelope<T> {
2345 pub data: T,
2346 #[serde(default)]
2347 pub links: PaginationLinks,
2348 #[serde(default)]
2349 pub meta: PaginationMeta,
2350}