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