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