1use std::{collections::BTreeMap, fmt};
2
3use serde::{Serialize, Serializer};
4use serde_json::{Map, Value};
5
6use crate::file_extension::normalize_file_extension;
7
8pub type ExtraOptions = BTreeMap<String, Value>;
9
10#[derive(Clone, Debug, PartialEq, Eq, Serialize, serde::Deserialize)]
16#[serde(untagged)]
17#[non_exhaustive]
18pub enum Input {
19 Task(String),
20 Tasks(Vec<String>),
21}
22
23impl From<String> for Input {
24 fn from(value: String) -> Self {
25 Self::Task(value)
26 }
27}
28
29impl From<&str> for Input {
30 fn from(value: &str) -> Self {
31 Self::Task(value.to_string())
32 }
33}
34
35impl From<Vec<String>> for Input {
36 fn from(value: Vec<String>) -> Self {
37 Self::Tasks(value)
38 }
39}
40
41impl From<Vec<&str>> for Input {
42 fn from(value: Vec<&str>) -> Self {
43 Self::Tasks(value.into_iter().map(str::to_string).collect())
44 }
45}
46
47#[derive(Clone)]
67pub struct TaskRequest {
68 operation: String,
69 payload: Map<String, Value>,
70}
71
72mod sealed {
73 pub trait Sealed {}
74}
75
76pub trait TaskPayload: sealed::Sealed + Serialize {
81 const OPERATION: &'static str;
83}
84
85impl TaskRequest {
86 pub fn import_url(url: impl Into<String>) -> Self {
88 ImportUrlTask::new(url).into()
89 }
90
91 pub fn import_upload() -> Self {
93 ImportUploadTask::default().into()
94 }
95
96 pub fn import_base64(file: impl Into<String>, filename: impl Into<String>) -> Self {
98 Base64ImportTask::new(file, filename).into()
99 }
100
101 pub fn import_raw(file: impl Into<String>, filename: impl Into<String>) -> Self {
103 RawImportTask::new(file, filename).into()
104 }
105
106 pub fn import_s3(
108 bucket: impl Into<String>,
109 region: impl Into<String>,
110 access_key_id: impl Into<String>,
111 secret_access_key: impl Into<String>,
112 ) -> Self {
113 S3ImportTask::new(bucket, region, access_key_id, secret_access_key).into()
114 }
115
116 pub fn import_azure_blob(
118 storage_account: impl Into<String>,
119 container: impl Into<String>,
120 ) -> Self {
121 AzureBlobImportTask::new(storage_account, container).into()
122 }
123
124 pub fn import_google_cloud_storage(
126 project_id: impl Into<String>,
127 bucket: impl Into<String>,
128 client_email: impl Into<String>,
129 private_key: impl Into<String>,
130 ) -> Self {
131 GoogleCloudStorageImportTask::new(project_id, bucket, client_email, private_key).into()
132 }
133
134 pub fn import_openstack(
136 auth_url: impl Into<String>,
137 username: impl Into<String>,
138 password: impl Into<String>,
139 region: impl Into<String>,
140 container: impl Into<String>,
141 ) -> Self {
142 OpenStackImportTask::new(auth_url, username, password, region, container).into()
143 }
144
145 pub fn import_sftp(host: impl Into<String>, username: impl Into<String>) -> Self {
147 SftpImportTask::new(host, username).into()
148 }
149
150 pub fn convert(input: impl Into<Input>, output_format: impl Into<String>) -> Self {
152 ConvertTask::new(input, output_format).into()
153 }
154
155 pub fn optimize(input: impl Into<Input>) -> Self {
157 OptimizeTask::new(input).into()
158 }
159
160 pub fn watermark(task: WatermarkTask) -> Self {
162 task.into()
163 }
164
165 pub fn capture_website(url: impl Into<String>, output_format: impl Into<String>) -> Self {
167 CaptureWebsiteTask::new(url, output_format).into()
168 }
169
170 pub fn thumbnail(input: impl Into<Input>, output_format: impl Into<String>) -> Self {
172 ThumbnailTask::new(input, output_format).into()
173 }
174
175 pub fn metadata(input: impl Into<Input>) -> Self {
177 MetadataTask::new(input).into()
178 }
179
180 pub fn metadata_write(input: impl Into<Input>) -> Self {
182 MetadataWriteTask::new(input).into()
183 }
184
185 pub fn merge(input: impl Into<Input>, output_format: impl Into<String>) -> Self {
187 MergeTask::new(input, output_format).into()
188 }
189
190 pub fn archive(input: impl Into<Input>, output_format: impl Into<String>) -> Self {
192 ArchiveTask::new(input, output_format).into()
193 }
194
195 pub fn command(
197 input: impl Into<Input>,
198 engine: impl Into<String>,
199 command: impl Into<String>,
200 arguments: impl Into<String>,
201 ) -> Self {
202 CommandTask::new(input, engine, command, arguments).into()
203 }
204
205 pub fn pdf_a(input: impl Into<Input>) -> Self {
207 PdfATask::new(input).into()
208 }
209
210 pub fn pdf_x(input: impl Into<Input>) -> Self {
212 PdfXTask::new(input).into()
213 }
214
215 pub fn pdf_ocr(input: impl Into<Input>) -> Self {
217 PdfOcrTask::new(input).into()
218 }
219
220 pub fn pdf_encrypt(input: impl Into<Input>) -> Self {
222 PdfEncryptTask::new(input).into()
223 }
224
225 pub fn pdf_decrypt(input: impl Into<Input>) -> Self {
227 PdfDecryptTask::new(input).into()
228 }
229
230 pub fn pdf_split_pages(input: impl Into<Input>) -> Self {
232 PdfSplitPagesTask::new(input).into()
233 }
234
235 pub fn pdf_extract_pages(input: impl Into<Input>) -> Self {
237 PdfExtractPagesTask::new(input).into()
238 }
239
240 pub fn pdf_rotate_pages(input: impl Into<Input>) -> Self {
242 PdfRotatePagesTask::new(input).into()
243 }
244
245 pub fn export_url(input: impl Into<Input>) -> Self {
247 ExportUrlTask::new(input).into()
248 }
249
250 pub fn export_s3(
252 input: impl Into<Input>,
253 bucket: impl Into<String>,
254 region: impl Into<String>,
255 access_key_id: impl Into<String>,
256 secret_access_key: impl Into<String>,
257 ) -> Self {
258 S3ExportTask::new(input, bucket, region, access_key_id, secret_access_key).into()
259 }
260
261 pub fn export_azure_blob(
263 input: impl Into<Input>,
264 storage_account: impl Into<String>,
265 container: impl Into<String>,
266 ) -> Self {
267 AzureBlobExportTask::new(input, storage_account, container).into()
268 }
269
270 pub fn export_google_cloud_storage(
272 input: impl Into<Input>,
273 project_id: impl Into<String>,
274 bucket: impl Into<String>,
275 client_email: impl Into<String>,
276 private_key: impl Into<String>,
277 ) -> Self {
278 GoogleCloudStorageExportTask::new(input, project_id, bucket, client_email, private_key)
279 .into()
280 }
281
282 pub fn export_openstack(
284 input: impl Into<Input>,
285 auth_url: impl Into<String>,
286 username: impl Into<String>,
287 password: impl Into<String>,
288 region: impl Into<String>,
289 container: impl Into<String>,
290 ) -> Self {
291 OpenStackExportTask::new(input, auth_url, username, password, region, container).into()
292 }
293
294 pub fn export_sftp(
296 input: impl Into<Input>,
297 host: impl Into<String>,
298 username: impl Into<String>,
299 ) -> Self {
300 SftpExportTask::new(input, host, username).into()
301 }
302
303 pub fn export_upload(input: impl Into<Input>, url: impl Into<String>) -> Self {
305 ExportUploadTask::new(input, url).into()
306 }
307
308 pub fn custom(operation: impl Into<String>) -> GenericTask {
321 GenericTask::new(operation)
322 }
323
324 pub fn try_from_payload<T>(payload: T) -> crate::Result<Self>
326 where
327 T: TaskPayload,
328 {
329 Ok(Self {
330 operation: T::OPERATION.to_string(),
331 payload: serialize_payload(payload)?,
332 })
333 }
334
335 pub fn from_payload<T>(payload: T) -> Self
340 where
341 T: TaskPayload,
342 {
343 Self::try_from_payload(payload)
344 .expect("task payload serialization should produce a JSON object")
345 }
346
347 pub fn operation(&self) -> &str {
349 self.operation.as_str()
350 }
351
352 pub fn payload(&self) -> &Map<String, Value> {
354 &self.payload
355 }
356}
357
358impl Serialize for TaskRequest {
359 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
360 where
361 S: Serializer,
362 {
363 let mut object = self.payload.clone();
364 object.insert(
365 "operation".to_string(),
366 Value::String(self.operation.clone()),
367 );
368
369 object.serialize(serializer)
370 }
371}
372
373impl fmt::Debug for TaskRequest {
374 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375 f.debug_struct("TaskRequest")
376 .field("operation", &self.operation)
377 .field("payload", &"REDACTED")
378 .finish()
379 }
380}
381
382macro_rules! task_payload {
383 ($type:ty, $operation:literal) => {
384 impl sealed::Sealed for $type {}
385
386 impl TaskPayload for $type {
387 const OPERATION: &'static str = $operation;
388 }
389
390 impl From<$type> for TaskRequest {
391 fn from(value: $type) -> Self {
392 Self::from_payload(value)
393 }
394 }
395 };
396}
397
398fn serialize_payload<T>(payload: T) -> crate::Result<Map<String, Value>>
399where
400 T: Serialize,
401{
402 match serde_json::to_value(payload)? {
403 Value::Object(object) => Ok(object),
404 _ => Err(<serde_json::Error as serde::ser::Error>::custom(
405 "task payload serialization must produce a JSON object",
406 )
407 .into()),
408 }
409}
410
411fn insert_extra_object_field(
412 extra: &mut ExtraOptions,
413 object_key: &str,
414 field_key: impl Into<String>,
415 value: impl Into<Value>,
416) {
417 match extra
418 .entry(object_key.to_string())
419 .or_insert_with(|| Value::Object(Map::new()))
420 {
421 Value::Object(object) => {
422 object.insert(field_key.into(), value.into());
423 }
424 existing => {
425 let mut object = Map::new();
426 object.insert(field_key.into(), value.into());
427 *existing = Value::Object(object);
428 }
429 }
430}
431
432fn redacted_option<T>(value: &Option<T>) -> Option<&'static str> {
433 value.as_ref().map(|_| "REDACTED")
434}
435
436struct RedactedStringMap<'a>(&'a BTreeMap<String, String>);
437
438impl fmt::Debug for RedactedStringMap<'_> {
439 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
440 let mut debug = f.debug_map();
441 for key in self.0.keys() {
442 debug.entry(key, &"REDACTED");
443 }
444 debug.finish()
445 }
446}
447
448macro_rules! debug_struct_redacted {
449 (
450 $name:ident {
451 fields: [$($field:ident),* $(,)?],
452 redacted: [$($redacted:ident),* $(,)?],
453 redacted_options: [$($redacted_option:ident),* $(,)?],
454 redacted_maps: [$($redacted_map:ident),* $(,)?]
455 }
456 ) => {
457 impl fmt::Debug for $name {
458 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
459 let mut debug = f.debug_struct(stringify!($name));
460 $(debug.field(stringify!($field), &self.$field);)*
461 $(debug.field(stringify!($redacted), &"REDACTED");)*
462 $(debug.field(stringify!($redacted_option), &redacted_option(&self.$redacted_option));)*
463 $(debug.field(stringify!($redacted_map), &RedactedStringMap(&self.$redacted_map));)*
464 debug.finish()
465 }
466 }
467 };
468}
469
470#[derive(Clone, Serialize)]
471pub struct ImportUrlTask {
472 url: String,
473 #[serde(skip_serializing_if = "Option::is_none")]
474 filename: Option<String>,
475 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
476 headers: BTreeMap<String, String>,
477}
478
479impl ImportUrlTask {
480 pub fn new(url: impl Into<String>) -> Self {
481 Self {
482 url: url.into(),
483 filename: None,
484 headers: BTreeMap::new(),
485 }
486 }
487
488 pub fn filename(mut self, filename: impl Into<String>) -> Self {
489 self.filename = Some(filename.into());
490 self
491 }
492
493 pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
494 self.headers.insert(key.into(), value.into());
495 self
496 }
497}
498
499task_payload!(ImportUrlTask, "import/url");
500
501#[derive(Clone, Debug, Default, Serialize)]
502pub struct ImportUploadTask {
503 #[serde(skip_serializing_if = "Option::is_none")]
504 redirect: Option<String>,
505}
506
507impl ImportUploadTask {
508 pub fn redirect(mut self, redirect: impl Into<String>) -> Self {
509 self.redirect = Some(redirect.into());
510 self
511 }
512}
513
514task_payload!(ImportUploadTask, "import/upload");
515
516#[derive(Clone, Debug, Serialize)]
517pub struct Base64ImportTask {
518 file: String,
519 filename: String,
520}
521
522impl Base64ImportTask {
523 pub fn new(file: impl Into<String>, filename: impl Into<String>) -> Self {
524 Self {
525 file: file.into(),
526 filename: filename.into(),
527 }
528 }
529}
530
531task_payload!(Base64ImportTask, "import/base64");
532
533#[derive(Clone, Debug, Serialize)]
534pub struct RawImportTask {
535 file: String,
536 filename: String,
537}
538
539impl RawImportTask {
540 pub fn new(file: impl Into<String>, filename: impl Into<String>) -> Self {
541 Self {
542 file: file.into(),
543 filename: filename.into(),
544 }
545 }
546}
547
548task_payload!(RawImportTask, "import/raw");
549
550#[derive(Clone, Serialize)]
551pub struct S3ImportTask {
552 bucket: String,
553 region: String,
554 #[serde(skip_serializing_if = "Option::is_none")]
555 endpoint: Option<String>,
556 #[serde(skip_serializing_if = "Option::is_none")]
557 key: Option<String>,
558 #[serde(skip_serializing_if = "Option::is_none")]
559 key_prefix: Option<String>,
560 access_key_id: String,
561 secret_access_key: String,
562 #[serde(skip_serializing_if = "Option::is_none")]
563 session_token: Option<String>,
564 #[serde(skip_serializing_if = "Option::is_none")]
565 filename: Option<String>,
566}
567
568impl S3ImportTask {
569 pub fn new(
570 bucket: impl Into<String>,
571 region: impl Into<String>,
572 access_key_id: impl Into<String>,
573 secret_access_key: impl Into<String>,
574 ) -> Self {
575 Self {
576 bucket: bucket.into(),
577 region: region.into(),
578 endpoint: None,
579 key: None,
580 key_prefix: None,
581 access_key_id: access_key_id.into(),
582 secret_access_key: secret_access_key.into(),
583 session_token: None,
584 filename: None,
585 }
586 }
587
588 pub fn endpoint(mut self, endpoint: impl Into<String>) -> Self {
589 self.endpoint = Some(endpoint.into());
590 self
591 }
592
593 pub fn key(mut self, key: impl Into<String>) -> Self {
594 self.key = Some(key.into());
595 self
596 }
597
598 pub fn key_prefix(mut self, key_prefix: impl Into<String>) -> Self {
599 self.key_prefix = Some(key_prefix.into());
600 self
601 }
602
603 pub fn session_token(mut self, session_token: impl Into<String>) -> Self {
604 self.session_token = Some(session_token.into());
605 self
606 }
607
608 pub fn filename(mut self, filename: impl Into<String>) -> Self {
609 self.filename = Some(filename.into());
610 self
611 }
612}
613
614task_payload!(S3ImportTask, "import/s3");
615
616#[derive(Clone, Serialize)]
617pub struct AzureBlobImportTask {
618 storage_account: String,
619 #[serde(skip_serializing_if = "Option::is_none")]
620 storage_access_key: Option<String>,
621 #[serde(skip_serializing_if = "Option::is_none")]
622 sas_token: Option<String>,
623 container: String,
624 #[serde(skip_serializing_if = "Option::is_none")]
625 blob: Option<String>,
626 #[serde(skip_serializing_if = "Option::is_none")]
627 blob_prefix: Option<String>,
628 #[serde(skip_serializing_if = "Option::is_none")]
629 filename: Option<String>,
630}
631
632impl AzureBlobImportTask {
633 pub fn new(storage_account: impl Into<String>, container: impl Into<String>) -> Self {
634 Self {
635 storage_account: storage_account.into(),
636 storage_access_key: None,
637 sas_token: None,
638 container: container.into(),
639 blob: None,
640 blob_prefix: None,
641 filename: None,
642 }
643 }
644
645 pub fn storage_access_key(mut self, storage_access_key: impl Into<String>) -> Self {
646 self.storage_access_key = Some(storage_access_key.into());
647 self
648 }
649
650 pub fn sas_token(mut self, sas_token: impl Into<String>) -> Self {
651 self.sas_token = Some(sas_token.into());
652 self
653 }
654
655 pub fn blob(mut self, blob: impl Into<String>) -> Self {
656 self.blob = Some(blob.into());
657 self
658 }
659
660 pub fn blob_prefix(mut self, blob_prefix: impl Into<String>) -> Self {
661 self.blob_prefix = Some(blob_prefix.into());
662 self
663 }
664
665 pub fn filename(mut self, filename: impl Into<String>) -> Self {
666 self.filename = Some(filename.into());
667 self
668 }
669}
670
671task_payload!(AzureBlobImportTask, "import/azure/blob");
672
673#[derive(Clone, Serialize)]
674pub struct GoogleCloudStorageImportTask {
675 project_id: String,
676 bucket: String,
677 client_email: String,
678 private_key: String,
679 #[serde(skip_serializing_if = "Option::is_none")]
680 file: Option<String>,
681 #[serde(skip_serializing_if = "Option::is_none")]
682 file_prefix: Option<String>,
683 #[serde(skip_serializing_if = "Option::is_none")]
684 filename: Option<String>,
685}
686
687impl GoogleCloudStorageImportTask {
688 pub fn new(
689 project_id: impl Into<String>,
690 bucket: impl Into<String>,
691 client_email: impl Into<String>,
692 private_key: impl Into<String>,
693 ) -> Self {
694 Self {
695 project_id: project_id.into(),
696 bucket: bucket.into(),
697 client_email: client_email.into(),
698 private_key: private_key.into(),
699 file: None,
700 file_prefix: None,
701 filename: None,
702 }
703 }
704
705 pub fn file(mut self, file: impl Into<String>) -> Self {
706 self.file = Some(file.into());
707 self
708 }
709
710 pub fn file_prefix(mut self, file_prefix: impl Into<String>) -> Self {
711 self.file_prefix = Some(file_prefix.into());
712 self
713 }
714
715 pub fn filename(mut self, filename: impl Into<String>) -> Self {
716 self.filename = Some(filename.into());
717 self
718 }
719}
720
721task_payload!(GoogleCloudStorageImportTask, "import/google-cloud-storage");
722
723#[derive(Clone, Serialize)]
724pub struct OpenStackImportTask {
725 auth_url: String,
726 username: String,
727 password: String,
728 region: String,
729 container: String,
730 #[serde(skip_serializing_if = "Option::is_none")]
731 file: Option<String>,
732 #[serde(skip_serializing_if = "Option::is_none")]
733 file_prefix: Option<String>,
734 #[serde(skip_serializing_if = "Option::is_none")]
735 filename: Option<String>,
736}
737
738impl OpenStackImportTask {
739 pub fn new(
740 auth_url: impl Into<String>,
741 username: impl Into<String>,
742 password: impl Into<String>,
743 region: impl Into<String>,
744 container: impl Into<String>,
745 ) -> Self {
746 Self {
747 auth_url: auth_url.into(),
748 username: username.into(),
749 password: password.into(),
750 region: region.into(),
751 container: container.into(),
752 file: None,
753 file_prefix: None,
754 filename: None,
755 }
756 }
757
758 pub fn file(mut self, file: impl Into<String>) -> Self {
759 self.file = Some(file.into());
760 self
761 }
762
763 pub fn file_prefix(mut self, file_prefix: impl Into<String>) -> Self {
764 self.file_prefix = Some(file_prefix.into());
765 self
766 }
767
768 pub fn filename(mut self, filename: impl Into<String>) -> Self {
769 self.filename = Some(filename.into());
770 self
771 }
772}
773
774task_payload!(OpenStackImportTask, "import/openstack");
775
776#[derive(Clone, Serialize)]
777pub struct SftpImportTask {
778 host: String,
779 #[serde(skip_serializing_if = "Option::is_none")]
780 port: Option<u16>,
781 username: String,
782 #[serde(skip_serializing_if = "Option::is_none")]
783 password: Option<String>,
784 #[serde(skip_serializing_if = "Option::is_none")]
785 private_key: Option<String>,
786 #[serde(skip_serializing_if = "Option::is_none")]
787 file: Option<String>,
788 #[serde(skip_serializing_if = "Option::is_none")]
789 path: Option<String>,
790 #[serde(skip_serializing_if = "Option::is_none")]
791 filename: Option<String>,
792}
793
794impl SftpImportTask {
795 pub fn new(host: impl Into<String>, username: impl Into<String>) -> Self {
796 Self {
797 host: host.into(),
798 port: None,
799 username: username.into(),
800 password: None,
801 private_key: None,
802 file: None,
803 path: None,
804 filename: None,
805 }
806 }
807
808 pub fn port(mut self, port: u16) -> Self {
809 self.port = Some(port);
810 self
811 }
812
813 pub fn password(mut self, password: impl Into<String>) -> Self {
814 self.password = Some(password.into());
815 self
816 }
817
818 pub fn private_key(mut self, private_key: impl Into<String>) -> Self {
819 self.private_key = Some(private_key.into());
820 self
821 }
822
823 pub fn file(mut self, file: impl Into<String>) -> Self {
824 self.file = Some(file.into());
825 self
826 }
827
828 pub fn path(mut self, path: impl Into<String>) -> Self {
829 self.path = Some(path.into());
830 self
831 }
832
833 pub fn filename(mut self, filename: impl Into<String>) -> Self {
834 self.filename = Some(filename.into());
835 self
836 }
837}
838
839task_payload!(SftpImportTask, "import/sftp");
840
841#[derive(Clone, Debug, Serialize)]
842pub struct ConvertTask {
843 input: Input,
844 #[serde(skip_serializing_if = "Option::is_none")]
845 input_format: Option<String>,
846 output_format: String,
847 #[serde(skip_serializing_if = "Option::is_none")]
848 engine: Option<String>,
849 #[serde(skip_serializing_if = "Option::is_none")]
850 engine_version: Option<String>,
851 #[serde(skip_serializing_if = "Option::is_none")]
852 filename: Option<String>,
853 #[serde(skip_serializing_if = "Option::is_none")]
854 timeout: Option<u64>,
855 #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
856 extra: ExtraOptions,
857}
858
859impl ConvertTask {
860 pub fn new(input: impl Into<Input>, output_format: impl Into<String>) -> Self {
861 Self {
862 input: input.into(),
863 input_format: None,
864 output_format: normalize_file_extension(output_format),
865 engine: None,
866 engine_version: None,
867 filename: None,
868 timeout: None,
869 extra: BTreeMap::new(),
870 }
871 }
872
873 pub fn input_format(mut self, input_format: impl Into<String>) -> Self {
874 self.input_format = Some(normalize_file_extension(input_format));
875 self
876 }
877
878 pub fn engine(mut self, engine: impl Into<String>) -> Self {
879 self.engine = Some(engine.into());
880 self
881 }
882
883 pub fn engine_version(mut self, engine_version: impl Into<String>) -> Self {
884 self.engine_version = Some(engine_version.into());
885 self
886 }
887
888 pub fn filename(mut self, filename: impl Into<String>) -> Self {
889 self.filename = Some(filename.into());
890 self
891 }
892
893 pub fn timeout(mut self, timeout: u64) -> Self {
894 self.timeout = Some(timeout);
895 self
896 }
897
898 pub fn option(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
899 self.extra.insert(key.into(), value.into());
900 self
901 }
902}
903
904task_payload!(ConvertTask, "convert");
905
906#[derive(Clone, Debug, Serialize)]
907pub struct OptimizeTask {
908 input: Input,
909 #[serde(skip_serializing_if = "Option::is_none")]
910 input_format: Option<String>,
911 #[serde(skip_serializing_if = "Option::is_none")]
912 quality: Option<u8>,
913 #[serde(skip_serializing_if = "Option::is_none")]
914 profile: Option<String>,
915 #[serde(skip_serializing_if = "Option::is_none")]
916 engine: Option<String>,
917 #[serde(skip_serializing_if = "Option::is_none")]
918 engine_version: Option<String>,
919 #[serde(skip_serializing_if = "Option::is_none")]
920 filename: Option<String>,
921 #[serde(skip_serializing_if = "Option::is_none")]
922 timeout: Option<u64>,
923 #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
924 extra: ExtraOptions,
925}
926
927impl OptimizeTask {
928 pub fn new(input: impl Into<Input>) -> Self {
929 Self {
930 input: input.into(),
931 input_format: None,
932 quality: None,
933 profile: None,
934 engine: None,
935 engine_version: None,
936 filename: None,
937 timeout: None,
938 extra: BTreeMap::new(),
939 }
940 }
941
942 pub fn input_format(mut self, input_format: impl Into<String>) -> Self {
943 self.input_format = Some(normalize_file_extension(input_format));
944 self
945 }
946
947 pub fn quality(mut self, quality: u8) -> Self {
948 self.quality = Some(quality);
949 self
950 }
951
952 pub fn profile(mut self, profile: impl Into<String>) -> Self {
953 self.profile = Some(profile.into());
954 self
955 }
956
957 pub fn engine(mut self, engine: impl Into<String>) -> Self {
958 self.engine = Some(engine.into());
959 self
960 }
961
962 pub fn engine_version(mut self, engine_version: impl Into<String>) -> Self {
963 self.engine_version = Some(engine_version.into());
964 self
965 }
966
967 pub fn filename(mut self, filename: impl Into<String>) -> Self {
968 self.filename = Some(filename.into());
969 self
970 }
971
972 pub fn timeout(mut self, timeout: u64) -> Self {
973 self.timeout = Some(timeout);
974 self
975 }
976
977 pub fn option(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
978 self.extra.insert(key.into(), value.into());
979 self
980 }
981}
982
983task_payload!(OptimizeTask, "optimize");
984
985#[derive(Clone, Debug, Serialize)]
986#[serde(rename_all = "lowercase")]
987#[non_exhaustive]
988pub enum Layer {
989 Above,
990 Below,
991}
992
993#[derive(Clone, Debug, Serialize)]
994#[serde(rename_all = "lowercase")]
995#[non_exhaustive]
996pub enum PositionVertical {
997 Top,
998 Center,
999 Bottom,
1000}
1001
1002#[derive(Clone, Debug, Serialize)]
1003#[serde(rename_all = "lowercase")]
1004#[non_exhaustive]
1005pub enum PositionHorizontal {
1006 Left,
1007 Center,
1008 Right,
1009}
1010
1011#[derive(Clone, Debug, Serialize)]
1012#[serde(rename_all = "lowercase")]
1013#[non_exhaustive]
1014pub enum FontAlign {
1015 Left,
1016 Center,
1017 Right,
1018}
1019
1020#[derive(Clone, Debug, Serialize)]
1021pub struct WatermarkTask {
1022 input: Input,
1023 #[serde(skip_serializing_if = "Option::is_none")]
1024 input_format: Option<String>,
1025 #[serde(skip_serializing_if = "Option::is_none")]
1026 pages: Option<String>,
1027 #[serde(skip_serializing_if = "Option::is_none")]
1028 layer: Option<Layer>,
1029 #[serde(skip_serializing_if = "Option::is_none")]
1030 text: Option<String>,
1031 #[serde(skip_serializing_if = "Option::is_none")]
1032 font_size: Option<u32>,
1033 #[serde(skip_serializing_if = "Option::is_none")]
1034 font_width_percent: Option<u8>,
1035 #[serde(skip_serializing_if = "Option::is_none")]
1036 font_color: Option<String>,
1037 #[serde(skip_serializing_if = "Option::is_none")]
1038 font_name: Option<String>,
1039 #[serde(skip_serializing_if = "Option::is_none")]
1040 font_align: Option<FontAlign>,
1041 #[serde(skip_serializing_if = "Option::is_none")]
1042 image: Option<String>,
1043 #[serde(skip_serializing_if = "Option::is_none")]
1044 image_width: Option<u32>,
1045 #[serde(skip_serializing_if = "Option::is_none")]
1046 image_height: Option<u32>,
1047 #[serde(skip_serializing_if = "Option::is_none")]
1048 image_width_percent: Option<u8>,
1049 #[serde(skip_serializing_if = "Option::is_none")]
1050 position_vertical: Option<PositionVertical>,
1051 #[serde(skip_serializing_if = "Option::is_none")]
1052 position_horizontal: Option<PositionHorizontal>,
1053 #[serde(skip_serializing_if = "Option::is_none")]
1054 margin_vertical: Option<u32>,
1055 #[serde(skip_serializing_if = "Option::is_none")]
1056 margin_horizontal: Option<u32>,
1057 #[serde(skip_serializing_if = "Option::is_none")]
1058 opacity: Option<u8>,
1059 #[serde(skip_serializing_if = "Option::is_none")]
1060 rotation: Option<i16>,
1061 #[serde(skip_serializing_if = "Option::is_none")]
1062 filename: Option<String>,
1063 #[serde(skip_serializing_if = "Option::is_none")]
1064 engine: Option<String>,
1065 #[serde(skip_serializing_if = "Option::is_none")]
1066 engine_version: Option<String>,
1067 #[serde(skip_serializing_if = "Option::is_none")]
1068 timeout: Option<u64>,
1069 #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
1070 extra: ExtraOptions,
1071}
1072
1073impl WatermarkTask {
1074 pub fn text(input: impl Into<Input>, text: impl Into<String>) -> Self {
1075 Self::new(input).text_content(text)
1076 }
1077
1078 pub fn image(input: impl Into<Input>, image_task_name: impl Into<String>) -> Self {
1079 Self::new(input).image_task(image_task_name)
1080 }
1081
1082 fn new(input: impl Into<Input>) -> Self {
1083 Self {
1084 input: input.into(),
1085 input_format: None,
1086 pages: None,
1087 layer: None,
1088 text: None,
1089 font_size: None,
1090 font_width_percent: None,
1091 font_color: None,
1092 font_name: None,
1093 font_align: None,
1094 image: None,
1095 image_width: None,
1096 image_height: None,
1097 image_width_percent: None,
1098 position_vertical: None,
1099 position_horizontal: None,
1100 margin_vertical: None,
1101 margin_horizontal: None,
1102 opacity: None,
1103 rotation: None,
1104 filename: None,
1105 engine: None,
1106 engine_version: None,
1107 timeout: None,
1108 extra: BTreeMap::new(),
1109 }
1110 }
1111
1112 fn text_content(mut self, text: impl Into<String>) -> Self {
1113 self.text = Some(text.into());
1114 self
1115 }
1116
1117 fn image_task(mut self, image_task_name: impl Into<String>) -> Self {
1118 self.image = Some(image_task_name.into());
1119 self
1120 }
1121
1122 pub fn input_format(mut self, input_format: impl Into<String>) -> Self {
1123 self.input_format = Some(normalize_file_extension(input_format));
1124 self
1125 }
1126
1127 pub fn pages(mut self, pages: impl Into<String>) -> Self {
1128 self.pages = Some(pages.into());
1129 self
1130 }
1131
1132 pub fn layer(mut self, layer: Layer) -> Self {
1133 self.layer = Some(layer);
1134 self
1135 }
1136
1137 pub fn font_size(mut self, font_size: u32) -> Self {
1138 self.font_size = Some(font_size);
1139 self
1140 }
1141
1142 pub fn font_width_percent(mut self, font_width_percent: u8) -> Self {
1143 self.font_width_percent = Some(font_width_percent);
1144 self
1145 }
1146
1147 pub fn font_color(mut self, font_color: impl Into<String>) -> Self {
1148 self.font_color = Some(font_color.into());
1149 self
1150 }
1151
1152 pub fn font_name(mut self, font_name: impl Into<String>) -> Self {
1153 self.font_name = Some(font_name.into());
1154 self
1155 }
1156
1157 pub fn font_align_left(mut self) -> Self {
1158 self.font_align = Some(FontAlign::Left);
1159 self
1160 }
1161
1162 pub fn font_align_center(mut self) -> Self {
1163 self.font_align = Some(FontAlign::Center);
1164 self
1165 }
1166
1167 pub fn font_align_right(mut self) -> Self {
1168 self.font_align = Some(FontAlign::Right);
1169 self
1170 }
1171
1172 pub fn position(mut self, vertical: PositionVertical, horizontal: PositionHorizontal) -> Self {
1173 self.position_vertical = Some(vertical);
1174 self.position_horizontal = Some(horizontal);
1175 self
1176 }
1177
1178 pub fn margins(mut self, vertical: u32, horizontal: u32) -> Self {
1179 self.margin_vertical = Some(vertical);
1180 self.margin_horizontal = Some(horizontal);
1181 self
1182 }
1183
1184 pub fn opacity(mut self, opacity: u8) -> Self {
1185 self.opacity = Some(opacity);
1186 self
1187 }
1188
1189 pub fn rotation(mut self, rotation: i16) -> Self {
1190 self.rotation = Some(rotation);
1191 self
1192 }
1193
1194 pub fn image_width(mut self, image_width: u32) -> Self {
1195 self.image_width = Some(image_width);
1196 self
1197 }
1198
1199 pub fn image_height(mut self, image_height: u32) -> Self {
1200 self.image_height = Some(image_height);
1201 self
1202 }
1203
1204 pub fn image_width_percent(mut self, image_width_percent: u8) -> Self {
1205 self.image_width_percent = Some(image_width_percent);
1206 self
1207 }
1208
1209 pub fn filename(mut self, filename: impl Into<String>) -> Self {
1210 self.filename = Some(filename.into());
1211 self
1212 }
1213
1214 pub fn engine(mut self, engine: impl Into<String>) -> Self {
1215 self.engine = Some(engine.into());
1216 self
1217 }
1218
1219 pub fn engine_version(mut self, engine_version: impl Into<String>) -> Self {
1220 self.engine_version = Some(engine_version.into());
1221 self
1222 }
1223
1224 pub fn timeout(mut self, timeout: u64) -> Self {
1225 self.timeout = Some(timeout);
1226 self
1227 }
1228
1229 pub fn option(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
1230 self.extra.insert(key.into(), value.into());
1231 self
1232 }
1233}
1234
1235task_payload!(WatermarkTask, "watermark");
1236
1237#[derive(Clone, Debug, Serialize)]
1238pub struct CaptureWebsiteTask {
1239 url: String,
1240 output_format: String,
1241 #[serde(skip_serializing_if = "Option::is_none")]
1242 engine: Option<String>,
1243 #[serde(skip_serializing_if = "Option::is_none")]
1244 engine_version: Option<String>,
1245 #[serde(skip_serializing_if = "Option::is_none")]
1246 filename: Option<String>,
1247 #[serde(skip_serializing_if = "Option::is_none")]
1248 timeout: Option<u64>,
1249 #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
1250 extra: ExtraOptions,
1251}
1252
1253impl CaptureWebsiteTask {
1254 pub fn new(url: impl Into<String>, output_format: impl Into<String>) -> Self {
1255 Self {
1256 url: url.into(),
1257 output_format: normalize_file_extension(output_format),
1258 engine: None,
1259 engine_version: None,
1260 filename: None,
1261 timeout: None,
1262 extra: BTreeMap::new(),
1263 }
1264 }
1265
1266 pub fn engine(mut self, engine: impl Into<String>) -> Self {
1267 self.engine = Some(engine.into());
1268 self
1269 }
1270
1271 pub fn engine_version(mut self, engine_version: impl Into<String>) -> Self {
1272 self.engine_version = Some(engine_version.into());
1273 self
1274 }
1275
1276 pub fn filename(mut self, filename: impl Into<String>) -> Self {
1277 self.filename = Some(filename.into());
1278 self
1279 }
1280
1281 pub fn timeout(mut self, timeout: u64) -> Self {
1282 self.timeout = Some(timeout);
1283 self
1284 }
1285
1286 pub fn option(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
1287 self.extra.insert(key.into(), value.into());
1288 self
1289 }
1290}
1291
1292task_payload!(CaptureWebsiteTask, "capture-website");
1293
1294#[derive(Clone, Debug, Serialize)]
1295pub struct ThumbnailTask {
1296 input: Input,
1297 #[serde(skip_serializing_if = "Option::is_none")]
1298 input_format: Option<String>,
1299 output_format: String,
1300 #[serde(skip_serializing_if = "Option::is_none")]
1301 engine: Option<String>,
1302 #[serde(skip_serializing_if = "Option::is_none")]
1303 engine_version: Option<String>,
1304 #[serde(skip_serializing_if = "Option::is_none")]
1305 width: Option<u32>,
1306 #[serde(skip_serializing_if = "Option::is_none")]
1307 height: Option<u32>,
1308 #[serde(skip_serializing_if = "Option::is_none")]
1309 fit: Option<String>,
1310 #[serde(skip_serializing_if = "Option::is_none")]
1311 count: Option<u32>,
1312 #[serde(skip_serializing_if = "Option::is_none")]
1313 timestamp: Option<String>,
1314 #[serde(skip_serializing_if = "Option::is_none")]
1315 filename: Option<String>,
1316 #[serde(skip_serializing_if = "Option::is_none")]
1317 timeout: Option<u64>,
1318 #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
1319 extra: ExtraOptions,
1320}
1321
1322impl ThumbnailTask {
1323 pub fn new(input: impl Into<Input>, output_format: impl Into<String>) -> Self {
1324 Self {
1325 input: input.into(),
1326 input_format: None,
1327 output_format: normalize_file_extension(output_format),
1328 engine: None,
1329 engine_version: None,
1330 width: None,
1331 height: None,
1332 fit: None,
1333 count: None,
1334 timestamp: None,
1335 filename: None,
1336 timeout: None,
1337 extra: BTreeMap::new(),
1338 }
1339 }
1340
1341 pub fn input_format(mut self, input_format: impl Into<String>) -> Self {
1342 self.input_format = Some(normalize_file_extension(input_format));
1343 self
1344 }
1345
1346 pub fn engine(mut self, engine: impl Into<String>) -> Self {
1347 self.engine = Some(engine.into());
1348 self
1349 }
1350
1351 pub fn engine_version(mut self, engine_version: impl Into<String>) -> Self {
1352 self.engine_version = Some(engine_version.into());
1353 self
1354 }
1355
1356 pub fn width(mut self, width: u32) -> Self {
1357 self.width = Some(width);
1358 self
1359 }
1360
1361 pub fn height(mut self, height: u32) -> Self {
1362 self.height = Some(height);
1363 self
1364 }
1365
1366 pub fn dimensions(mut self, width: u32, height: u32) -> Self {
1367 self.width = Some(width);
1368 self.height = Some(height);
1369 self
1370 }
1371
1372 pub fn fit(mut self, fit: impl Into<String>) -> Self {
1373 self.fit = Some(fit.into());
1374 self
1375 }
1376
1377 pub fn count(mut self, count: u32) -> Self {
1378 self.count = Some(count);
1379 self
1380 }
1381
1382 pub fn timestamp(mut self, timestamp: impl Into<String>) -> Self {
1383 self.timestamp = Some(timestamp.into());
1384 self
1385 }
1386
1387 pub fn filename(mut self, filename: impl Into<String>) -> Self {
1388 self.filename = Some(filename.into());
1389 self
1390 }
1391
1392 pub fn timeout(mut self, timeout: u64) -> Self {
1393 self.timeout = Some(timeout);
1394 self
1395 }
1396
1397 pub fn option(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
1398 self.extra.insert(key.into(), value.into());
1399 self
1400 }
1401}
1402
1403task_payload!(ThumbnailTask, "thumbnail");
1404
1405#[derive(Clone, Debug, Serialize)]
1406pub struct MetadataTask {
1407 input: Input,
1408 #[serde(skip_serializing_if = "Option::is_none")]
1409 input_format: Option<String>,
1410 #[serde(skip_serializing_if = "Option::is_none")]
1411 engine: Option<String>,
1412 #[serde(skip_serializing_if = "Option::is_none")]
1413 engine_version: Option<String>,
1414 #[serde(skip_serializing_if = "Option::is_none")]
1415 timeout: Option<u64>,
1416 #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
1417 extra: ExtraOptions,
1418}
1419
1420impl MetadataTask {
1421 pub fn new(input: impl Into<Input>) -> Self {
1422 Self {
1423 input: input.into(),
1424 input_format: None,
1425 engine: None,
1426 engine_version: None,
1427 timeout: None,
1428 extra: BTreeMap::new(),
1429 }
1430 }
1431
1432 pub fn input_format(mut self, input_format: impl Into<String>) -> Self {
1433 self.input_format = Some(normalize_file_extension(input_format));
1434 self
1435 }
1436
1437 pub fn engine(mut self, engine: impl Into<String>) -> Self {
1438 self.engine = Some(engine.into());
1439 self
1440 }
1441
1442 pub fn engine_version(mut self, engine_version: impl Into<String>) -> Self {
1443 self.engine_version = Some(engine_version.into());
1444 self
1445 }
1446
1447 pub fn timeout(mut self, timeout: u64) -> Self {
1448 self.timeout = Some(timeout);
1449 self
1450 }
1451
1452 pub fn option(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
1453 self.extra.insert(key.into(), value.into());
1454 self
1455 }
1456}
1457
1458task_payload!(MetadataTask, "metadata");
1459
1460#[derive(Clone, Debug, Serialize)]
1461pub struct MetadataWriteTask {
1462 input: Input,
1463 #[serde(skip_serializing_if = "Option::is_none")]
1464 input_format: Option<String>,
1465 #[serde(skip_serializing_if = "Option::is_none")]
1466 engine: Option<String>,
1467 #[serde(skip_serializing_if = "Option::is_none")]
1468 engine_version: Option<String>,
1469 metadata: BTreeMap<String, Value>,
1470 #[serde(skip_serializing_if = "Option::is_none")]
1471 filename: Option<String>,
1472 #[serde(skip_serializing_if = "Option::is_none")]
1473 timeout: Option<u64>,
1474 #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
1475 extra: ExtraOptions,
1476}
1477
1478impl MetadataWriteTask {
1479 pub fn new(input: impl Into<Input>) -> Self {
1480 Self {
1481 input: input.into(),
1482 input_format: None,
1483 engine: None,
1484 engine_version: None,
1485 metadata: BTreeMap::new(),
1486 filename: None,
1487 timeout: None,
1488 extra: BTreeMap::new(),
1489 }
1490 }
1491
1492 pub fn input_format(mut self, input_format: impl Into<String>) -> Self {
1493 self.input_format = Some(normalize_file_extension(input_format));
1494 self
1495 }
1496
1497 pub fn engine(mut self, engine: impl Into<String>) -> Self {
1498 self.engine = Some(engine.into());
1499 self
1500 }
1501
1502 pub fn engine_version(mut self, engine_version: impl Into<String>) -> Self {
1503 self.engine_version = Some(engine_version.into());
1504 self
1505 }
1506
1507 pub fn metadata(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
1508 self.metadata.insert(key.into(), value.into());
1509 self
1510 }
1511
1512 pub fn metadata_map(mut self, metadata: BTreeMap<String, Value>) -> Self {
1513 self.metadata = metadata;
1514 self
1515 }
1516
1517 pub fn filename(mut self, filename: impl Into<String>) -> Self {
1518 self.filename = Some(filename.into());
1519 self
1520 }
1521
1522 pub fn timeout(mut self, timeout: u64) -> Self {
1523 self.timeout = Some(timeout);
1524 self
1525 }
1526
1527 pub fn option(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
1528 self.extra.insert(key.into(), value.into());
1529 self
1530 }
1531}
1532
1533task_payload!(MetadataWriteTask, "metadata/write");
1534
1535#[derive(Clone, Debug, Serialize)]
1536pub struct MergeTask {
1537 input: Input,
1538 output_format: String,
1539 #[serde(skip_serializing_if = "Option::is_none")]
1540 engine: Option<String>,
1541 #[serde(skip_serializing_if = "Option::is_none")]
1542 engine_version: Option<String>,
1543 #[serde(skip_serializing_if = "Option::is_none")]
1544 filename: Option<String>,
1545 #[serde(skip_serializing_if = "Option::is_none")]
1546 timeout: Option<u64>,
1547 #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
1548 extra: ExtraOptions,
1549}
1550
1551impl MergeTask {
1552 pub fn new(input: impl Into<Input>, output_format: impl Into<String>) -> Self {
1553 Self {
1554 input: input.into(),
1555 output_format: normalize_file_extension(output_format),
1556 engine: None,
1557 engine_version: None,
1558 filename: None,
1559 timeout: None,
1560 extra: BTreeMap::new(),
1561 }
1562 }
1563
1564 pub fn engine(mut self, engine: impl Into<String>) -> Self {
1565 self.engine = Some(engine.into());
1566 self
1567 }
1568
1569 pub fn engine_version(mut self, engine_version: impl Into<String>) -> Self {
1570 self.engine_version = Some(engine_version.into());
1571 self
1572 }
1573
1574 pub fn filename(mut self, filename: impl Into<String>) -> Self {
1575 self.filename = Some(filename.into());
1576 self
1577 }
1578
1579 pub fn timeout(mut self, timeout: u64) -> Self {
1580 self.timeout = Some(timeout);
1581 self
1582 }
1583
1584 pub fn option(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
1585 self.extra.insert(key.into(), value.into());
1586 self
1587 }
1588}
1589
1590task_payload!(MergeTask, "merge");
1591
1592#[derive(Clone, Debug, Serialize)]
1593pub struct ArchiveTask {
1594 input: Input,
1595 output_format: String,
1596 #[serde(skip_serializing_if = "Option::is_none")]
1597 engine: Option<String>,
1598 #[serde(skip_serializing_if = "Option::is_none")]
1599 engine_version: Option<String>,
1600 #[serde(skip_serializing_if = "Option::is_none")]
1601 filename: Option<String>,
1602 #[serde(skip_serializing_if = "Option::is_none")]
1603 timeout: Option<u64>,
1604 #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
1605 extra: ExtraOptions,
1606}
1607
1608impl ArchiveTask {
1609 pub fn new(input: impl Into<Input>, output_format: impl Into<String>) -> Self {
1610 Self {
1611 input: input.into(),
1612 output_format: normalize_file_extension(output_format),
1613 engine: None,
1614 engine_version: None,
1615 filename: None,
1616 timeout: None,
1617 extra: BTreeMap::new(),
1618 }
1619 }
1620
1621 pub fn engine(mut self, engine: impl Into<String>) -> Self {
1622 self.engine = Some(engine.into());
1623 self
1624 }
1625
1626 pub fn engine_version(mut self, engine_version: impl Into<String>) -> Self {
1627 self.engine_version = Some(engine_version.into());
1628 self
1629 }
1630
1631 pub fn filename(mut self, filename: impl Into<String>) -> Self {
1632 self.filename = Some(filename.into());
1633 self
1634 }
1635
1636 pub fn timeout(mut self, timeout: u64) -> Self {
1637 self.timeout = Some(timeout);
1638 self
1639 }
1640
1641 pub fn option(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
1642 self.extra.insert(key.into(), value.into());
1643 self
1644 }
1645}
1646
1647task_payload!(ArchiveTask, "archive");
1648
1649#[derive(Clone, Debug, Serialize)]
1650pub struct CommandTask {
1651 input: Input,
1652 engine: String,
1653 command: String,
1654 arguments: String,
1655 #[serde(skip_serializing_if = "Option::is_none")]
1656 engine_version: Option<String>,
1657 #[serde(skip_serializing_if = "Option::is_none")]
1658 capture_output: Option<bool>,
1659 #[serde(skip_serializing_if = "Option::is_none")]
1660 timeout: Option<u64>,
1661 #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
1662 extra: ExtraOptions,
1663}
1664
1665impl CommandTask {
1666 pub fn new(
1667 input: impl Into<Input>,
1668 engine: impl Into<String>,
1669 command: impl Into<String>,
1670 arguments: impl Into<String>,
1671 ) -> Self {
1672 Self {
1673 input: input.into(),
1674 engine: engine.into(),
1675 command: command.into(),
1676 arguments: arguments.into(),
1677 engine_version: None,
1678 capture_output: None,
1679 timeout: None,
1680 extra: BTreeMap::new(),
1681 }
1682 }
1683
1684 pub fn engine_version(mut self, engine_version: impl Into<String>) -> Self {
1685 self.engine_version = Some(engine_version.into());
1686 self
1687 }
1688
1689 pub fn capture_output(mut self, capture_output: bool) -> Self {
1690 self.capture_output = Some(capture_output);
1691 self
1692 }
1693
1694 pub fn timeout(mut self, timeout: u64) -> Self {
1695 self.timeout = Some(timeout);
1696 self
1697 }
1698
1699 pub fn option(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
1700 self.extra.insert(key.into(), value.into());
1701 self
1702 }
1703}
1704
1705task_payload!(CommandTask, "command");
1706
1707macro_rules! pdf_task {
1708 ($type:ident, $operation:literal) => {
1709 #[derive(Clone, Debug, Serialize)]
1710 pub struct $type {
1711 input: Input,
1712 #[serde(skip_serializing_if = "Option::is_none")]
1713 engine: Option<String>,
1714 #[serde(skip_serializing_if = "Option::is_none")]
1715 engine_version: Option<String>,
1716 #[serde(skip_serializing_if = "Option::is_none")]
1717 filename: Option<String>,
1718 #[serde(skip_serializing_if = "Option::is_none")]
1719 timeout: Option<u64>,
1720 #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
1721 extra: ExtraOptions,
1722 }
1723
1724 impl $type {
1725 pub fn new(input: impl Into<Input>) -> Self {
1726 Self {
1727 input: input.into(),
1728 engine: None,
1729 engine_version: None,
1730 filename: None,
1731 timeout: None,
1732 extra: BTreeMap::new(),
1733 }
1734 }
1735
1736 pub fn engine(mut self, engine: impl Into<String>) -> Self {
1737 self.engine = Some(engine.into());
1738 self
1739 }
1740
1741 pub fn engine_version(mut self, engine_version: impl Into<String>) -> Self {
1742 self.engine_version = Some(engine_version.into());
1743 self
1744 }
1745
1746 pub fn filename(mut self, filename: impl Into<String>) -> Self {
1747 self.filename = Some(filename.into());
1748 self
1749 }
1750
1751 pub fn timeout(mut self, timeout: u64) -> Self {
1752 self.timeout = Some(timeout);
1753 self
1754 }
1755
1756 pub fn option(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
1757 self.extra.insert(key.into(), value.into());
1758 self
1759 }
1760 }
1761
1762 task_payload!($type, $operation);
1763 };
1764}
1765
1766pdf_task!(PdfATask, "pdf/a");
1767pdf_task!(PdfXTask, "pdf/x");
1768pdf_task!(PdfOcrTask, "pdf/ocr");
1769pdf_task!(PdfEncryptTask, "pdf/encrypt");
1770pdf_task!(PdfDecryptTask, "pdf/decrypt");
1771pdf_task!(PdfSplitPagesTask, "pdf/split-pages");
1772pdf_task!(PdfExtractPagesTask, "pdf/extract-pages");
1773pdf_task!(PdfRotatePagesTask, "pdf/rotate-pages");
1774
1775#[derive(Clone, Debug, Serialize)]
1776pub struct ExportUrlTask {
1777 input: Input,
1778 #[serde(skip_serializing_if = "Option::is_none")]
1779 inline: Option<bool>,
1780 #[serde(skip_serializing_if = "Option::is_none")]
1781 archive_multiple_files: Option<bool>,
1782}
1783
1784impl ExportUrlTask {
1785 pub fn new(input: impl Into<Input>) -> Self {
1786 Self {
1787 input: input.into(),
1788 inline: None,
1789 archive_multiple_files: None,
1790 }
1791 }
1792
1793 pub fn inline(mut self, inline: bool) -> Self {
1794 self.inline = Some(inline);
1795 self
1796 }
1797
1798 pub fn archive_multiple_files(mut self, archive_multiple_files: bool) -> Self {
1799 self.archive_multiple_files = Some(archive_multiple_files);
1800 self
1801 }
1802}
1803
1804task_payload!(ExportUrlTask, "export/url");
1805
1806#[derive(Clone, Serialize)]
1807pub struct S3ExportTask {
1808 input: Input,
1809 bucket: String,
1810 region: String,
1811 #[serde(skip_serializing_if = "Option::is_none")]
1812 endpoint: Option<String>,
1813 #[serde(skip_serializing_if = "Option::is_none")]
1814 key: Option<String>,
1815 #[serde(skip_serializing_if = "Option::is_none")]
1816 key_prefix: Option<String>,
1817 access_key_id: String,
1818 secret_access_key: String,
1819 #[serde(skip_serializing_if = "Option::is_none")]
1820 session_token: Option<String>,
1821 #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
1822 extra: ExtraOptions,
1823}
1824
1825impl S3ExportTask {
1826 pub fn new(
1827 input: impl Into<Input>,
1828 bucket: impl Into<String>,
1829 region: impl Into<String>,
1830 access_key_id: impl Into<String>,
1831 secret_access_key: impl Into<String>,
1832 ) -> Self {
1833 Self {
1834 input: input.into(),
1835 bucket: bucket.into(),
1836 region: region.into(),
1837 endpoint: None,
1838 key: None,
1839 key_prefix: None,
1840 access_key_id: access_key_id.into(),
1841 secret_access_key: secret_access_key.into(),
1842 session_token: None,
1843 extra: BTreeMap::new(),
1844 }
1845 }
1846
1847 pub fn endpoint(mut self, endpoint: impl Into<String>) -> Self {
1848 self.endpoint = Some(endpoint.into());
1849 self
1850 }
1851
1852 pub fn key(mut self, key: impl Into<String>) -> Self {
1853 self.key = Some(key.into());
1854 self
1855 }
1856
1857 pub fn key_prefix(mut self, key_prefix: impl Into<String>) -> Self {
1858 self.key_prefix = Some(key_prefix.into());
1859 self
1860 }
1861
1862 pub fn session_token(mut self, session_token: impl Into<String>) -> Self {
1863 self.session_token = Some(session_token.into());
1864 self
1865 }
1866
1867 pub fn acl(self, acl: impl Into<String>) -> Self {
1868 self.option("acl", acl.into())
1869 }
1870
1871 pub fn cache_control(self, cache_control: impl Into<String>) -> Self {
1872 self.option("cache_control", cache_control.into())
1873 }
1874
1875 pub fn content_disposition(self, content_disposition: impl Into<String>) -> Self {
1876 self.option("content_disposition", content_disposition.into())
1877 }
1878
1879 pub fn content_type(self, content_type: impl Into<String>) -> Self {
1880 self.option("content_type", content_type.into())
1881 }
1882
1883 pub fn server_side_encryption(self, server_side_encryption: impl Into<String>) -> Self {
1884 self.option("server_side_encryption", server_side_encryption.into())
1885 }
1886
1887 pub fn metadata(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
1888 insert_extra_object_field(&mut self.extra, "metadata", key, value);
1889 self
1890 }
1891
1892 pub fn tag(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
1893 insert_extra_object_field(&mut self.extra, "tagging", key, value);
1894 self
1895 }
1896
1897 pub fn option(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
1898 self.extra.insert(key.into(), value.into());
1899 self
1900 }
1901}
1902
1903task_payload!(S3ExportTask, "export/s3");
1904
1905#[derive(Clone, Serialize)]
1906pub struct AzureBlobExportTask {
1907 input: Input,
1908 storage_account: String,
1909 #[serde(skip_serializing_if = "Option::is_none")]
1910 storage_access_key: Option<String>,
1911 #[serde(skip_serializing_if = "Option::is_none")]
1912 sas_token: Option<String>,
1913 container: String,
1914 #[serde(skip_serializing_if = "Option::is_none")]
1915 blob: Option<String>,
1916 #[serde(skip_serializing_if = "Option::is_none")]
1917 blob_prefix: Option<String>,
1918 #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
1919 extra: ExtraOptions,
1920}
1921
1922impl AzureBlobExportTask {
1923 pub fn new(
1924 input: impl Into<Input>,
1925 storage_account: impl Into<String>,
1926 container: impl Into<String>,
1927 ) -> Self {
1928 Self {
1929 input: input.into(),
1930 storage_account: storage_account.into(),
1931 storage_access_key: None,
1932 sas_token: None,
1933 container: container.into(),
1934 blob: None,
1935 blob_prefix: None,
1936 extra: BTreeMap::new(),
1937 }
1938 }
1939
1940 pub fn storage_access_key(mut self, storage_access_key: impl Into<String>) -> Self {
1941 self.storage_access_key = Some(storage_access_key.into());
1942 self
1943 }
1944
1945 pub fn sas_token(mut self, sas_token: impl Into<String>) -> Self {
1946 self.sas_token = Some(sas_token.into());
1947 self
1948 }
1949
1950 pub fn blob(mut self, blob: impl Into<String>) -> Self {
1951 self.blob = Some(blob.into());
1952 self
1953 }
1954
1955 pub fn blob_prefix(mut self, blob_prefix: impl Into<String>) -> Self {
1956 self.blob_prefix = Some(blob_prefix.into());
1957 self
1958 }
1959
1960 pub fn metadata(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
1961 insert_extra_object_field(&mut self.extra, "metadata", key, value);
1962 self
1963 }
1964
1965 pub fn option(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
1966 self.extra.insert(key.into(), value.into());
1967 self
1968 }
1969}
1970
1971task_payload!(AzureBlobExportTask, "export/azure/blob");
1972
1973#[derive(Clone, Serialize)]
1974pub struct GoogleCloudStorageExportTask {
1975 input: Input,
1976 project_id: String,
1977 bucket: String,
1978 client_email: String,
1979 private_key: String,
1980 #[serde(skip_serializing_if = "Option::is_none")]
1981 file: Option<String>,
1982 #[serde(skip_serializing_if = "Option::is_none")]
1983 file_prefix: Option<String>,
1984 #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
1985 extra: ExtraOptions,
1986}
1987
1988impl GoogleCloudStorageExportTask {
1989 pub fn new(
1990 input: impl Into<Input>,
1991 project_id: impl Into<String>,
1992 bucket: impl Into<String>,
1993 client_email: impl Into<String>,
1994 private_key: impl Into<String>,
1995 ) -> Self {
1996 Self {
1997 input: input.into(),
1998 project_id: project_id.into(),
1999 bucket: bucket.into(),
2000 client_email: client_email.into(),
2001 private_key: private_key.into(),
2002 file: None,
2003 file_prefix: None,
2004 extra: BTreeMap::new(),
2005 }
2006 }
2007
2008 pub fn file(mut self, file: impl Into<String>) -> Self {
2009 self.file = Some(file.into());
2010 self
2011 }
2012
2013 pub fn file_prefix(mut self, file_prefix: impl Into<String>) -> Self {
2014 self.file_prefix = Some(file_prefix.into());
2015 self
2016 }
2017
2018 pub fn option(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
2019 self.extra.insert(key.into(), value.into());
2020 self
2021 }
2022}
2023
2024task_payload!(GoogleCloudStorageExportTask, "export/google-cloud-storage");
2025
2026#[derive(Clone, Serialize)]
2027pub struct OpenStackExportTask {
2028 input: Input,
2029 auth_url: String,
2030 username: String,
2031 password: String,
2032 region: String,
2033 container: String,
2034 #[serde(skip_serializing_if = "Option::is_none")]
2035 file: Option<String>,
2036 #[serde(skip_serializing_if = "Option::is_none")]
2037 file_prefix: Option<String>,
2038 #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
2039 extra: ExtraOptions,
2040}
2041
2042impl OpenStackExportTask {
2043 pub fn new(
2044 input: impl Into<Input>,
2045 auth_url: impl Into<String>,
2046 username: impl Into<String>,
2047 password: impl Into<String>,
2048 region: impl Into<String>,
2049 container: impl Into<String>,
2050 ) -> Self {
2051 Self {
2052 input: input.into(),
2053 auth_url: auth_url.into(),
2054 username: username.into(),
2055 password: password.into(),
2056 region: region.into(),
2057 container: container.into(),
2058 file: None,
2059 file_prefix: None,
2060 extra: BTreeMap::new(),
2061 }
2062 }
2063
2064 pub fn file(mut self, file: impl Into<String>) -> Self {
2065 self.file = Some(file.into());
2066 self
2067 }
2068
2069 pub fn file_prefix(mut self, file_prefix: impl Into<String>) -> Self {
2070 self.file_prefix = Some(file_prefix.into());
2071 self
2072 }
2073
2074 pub fn option(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
2075 self.extra.insert(key.into(), value.into());
2076 self
2077 }
2078}
2079
2080task_payload!(OpenStackExportTask, "export/openstack");
2081
2082#[derive(Clone, Serialize)]
2083pub struct SftpExportTask {
2084 input: Input,
2085 host: String,
2086 #[serde(skip_serializing_if = "Option::is_none")]
2087 port: Option<u16>,
2088 username: String,
2089 #[serde(skip_serializing_if = "Option::is_none")]
2090 password: Option<String>,
2091 #[serde(skip_serializing_if = "Option::is_none")]
2092 private_key: Option<String>,
2093 #[serde(skip_serializing_if = "Option::is_none")]
2094 file: Option<String>,
2095 #[serde(skip_serializing_if = "Option::is_none")]
2096 path: Option<String>,
2097 #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
2098 extra: ExtraOptions,
2099}
2100
2101impl SftpExportTask {
2102 pub fn new(
2103 input: impl Into<Input>,
2104 host: impl Into<String>,
2105 username: impl Into<String>,
2106 ) -> Self {
2107 Self {
2108 input: input.into(),
2109 host: host.into(),
2110 port: None,
2111 username: username.into(),
2112 password: None,
2113 private_key: None,
2114 file: None,
2115 path: None,
2116 extra: BTreeMap::new(),
2117 }
2118 }
2119
2120 pub fn port(mut self, port: u16) -> Self {
2121 self.port = Some(port);
2122 self
2123 }
2124
2125 pub fn password(mut self, password: impl Into<String>) -> Self {
2126 self.password = Some(password.into());
2127 self
2128 }
2129
2130 pub fn private_key(mut self, private_key: impl Into<String>) -> Self {
2131 self.private_key = Some(private_key.into());
2132 self
2133 }
2134
2135 pub fn file(mut self, file: impl Into<String>) -> Self {
2136 self.file = Some(file.into());
2137 self
2138 }
2139
2140 pub fn path(mut self, path: impl Into<String>) -> Self {
2141 self.path = Some(path.into());
2142 self
2143 }
2144
2145 pub fn option(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
2146 self.extra.insert(key.into(), value.into());
2147 self
2148 }
2149}
2150
2151task_payload!(SftpExportTask, "export/sftp");
2152
2153#[derive(Clone, Serialize)]
2154pub struct ExportUploadTask {
2155 input: Input,
2156 url: String,
2157 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
2158 headers: BTreeMap<String, String>,
2159 #[serde(flatten, skip_serializing_if = "BTreeMap::is_empty")]
2160 extra: ExtraOptions,
2161}
2162
2163impl ExportUploadTask {
2164 pub fn new(input: impl Into<Input>, url: impl Into<String>) -> Self {
2165 Self {
2166 input: input.into(),
2167 url: url.into(),
2168 headers: BTreeMap::new(),
2169 extra: BTreeMap::new(),
2170 }
2171 }
2172
2173 pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
2174 self.headers.insert(key.into(), value.into());
2175 self
2176 }
2177
2178 pub fn option(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
2179 self.extra.insert(key.into(), value.into());
2180 self
2181 }
2182}
2183
2184task_payload!(ExportUploadTask, "export/upload");
2185
2186#[derive(Clone)]
2201pub struct GenericTask {
2202 operation: String,
2203 data: Map<String, Value>,
2204}
2205
2206impl GenericTask {
2207 pub fn new(operation: impl Into<String>) -> Self {
2209 Self {
2210 operation: operation.into(),
2211 data: Map::new(),
2212 }
2213 }
2214
2215 pub fn field(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
2217 self.data.insert(key.into(), value.into());
2218 self
2219 }
2220
2221 pub fn operation(&self) -> &str {
2223 self.operation.as_str()
2224 }
2225
2226 pub fn data(&self) -> &Map<String, Value> {
2228 &self.data
2229 }
2230}
2231
2232impl From<GenericTask> for TaskRequest {
2233 fn from(value: GenericTask) -> Self {
2234 Self {
2235 operation: value.operation,
2236 payload: value.data,
2237 }
2238 }
2239}
2240
2241impl fmt::Debug for GenericTask {
2242 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2243 f.debug_struct("GenericTask")
2244 .field("operation", &self.operation)
2245 .field("data", &"REDACTED")
2246 .finish()
2247 }
2248}
2249
2250debug_struct_redacted!(ImportUrlTask {
2251 fields: [url, filename],
2252 redacted: [],
2253 redacted_options: [],
2254 redacted_maps: [headers]
2255});
2256
2257debug_struct_redacted!(S3ImportTask {
2258 fields: [bucket, region, endpoint, key, key_prefix, filename],
2259 redacted: [access_key_id, secret_access_key],
2260 redacted_options: [session_token],
2261 redacted_maps: []
2262});
2263
2264debug_struct_redacted!(AzureBlobImportTask {
2265 fields: [storage_account, container, blob, blob_prefix, filename],
2266 redacted: [],
2267 redacted_options: [storage_access_key, sas_token],
2268 redacted_maps: []
2269});
2270
2271debug_struct_redacted!(GoogleCloudStorageImportTask {
2272 fields: [
2273 project_id,
2274 bucket,
2275 client_email,
2276 file,
2277 file_prefix,
2278 filename
2279 ],
2280 redacted: [private_key],
2281 redacted_options: [],
2282 redacted_maps: []
2283});
2284
2285debug_struct_redacted!(OpenStackImportTask {
2286 fields: [
2287 auth_url,
2288 username,
2289 region,
2290 container,
2291 file,
2292 file_prefix,
2293 filename
2294 ],
2295 redacted: [password],
2296 redacted_options: [],
2297 redacted_maps: []
2298});
2299
2300debug_struct_redacted!(SftpImportTask {
2301 fields: [host, port, username, file, path, filename],
2302 redacted: [],
2303 redacted_options: [password, private_key],
2304 redacted_maps: []
2305});
2306
2307debug_struct_redacted!(S3ExportTask {
2308 fields: [input, bucket, region, endpoint, key, key_prefix, extra],
2309 redacted: [access_key_id, secret_access_key],
2310 redacted_options: [session_token],
2311 redacted_maps: []
2312});
2313
2314debug_struct_redacted!(AzureBlobExportTask {
2315 fields: [input, storage_account, container, blob, blob_prefix, extra],
2316 redacted: [],
2317 redacted_options: [storage_access_key, sas_token],
2318 redacted_maps: []
2319});
2320
2321debug_struct_redacted!(GoogleCloudStorageExportTask {
2322 fields: [
2323 input,
2324 project_id,
2325 bucket,
2326 client_email,
2327 file,
2328 file_prefix,
2329 extra
2330 ],
2331 redacted: [private_key],
2332 redacted_options: [],
2333 redacted_maps: []
2334});
2335
2336debug_struct_redacted!(OpenStackExportTask {
2337 fields: [
2338 input,
2339 auth_url,
2340 username,
2341 region,
2342 container,
2343 file,
2344 file_prefix,
2345 extra
2346 ],
2347 redacted: [password],
2348 redacted_options: [],
2349 redacted_maps: []
2350});
2351
2352debug_struct_redacted!(SftpExportTask {
2353 fields: [input, host, port, username, file, path, extra],
2354 redacted: [],
2355 redacted_options: [password, private_key],
2356 redacted_maps: []
2357});
2358
2359debug_struct_redacted!(ExportUploadTask {
2360 fields: [input, url, extra],
2361 redacted: [],
2362 redacted_options: [],
2363 redacted_maps: [headers]
2364});