1use serde::{Deserialize, Deserializer, Serialize};
2use serde_json::{Map, Value};
3use std::time::Duration;
4use time::OffsetDateTime;
5
6use crate::{
7 client::Client, client::SwapIndexes, errors::Error, errors::MeilisearchError, indexes::Index,
8 request::HttpClient, settings::Settings, task_info::TaskInfo,
9};
10
11#[derive(Debug, Clone, Deserialize)]
12#[serde(rename_all = "camelCase", tag = "type")]
13pub enum TaskType {
14 Customs,
15 DocumentAdditionOrUpdate {
16 details: Option<DocumentAdditionOrUpdate>,
17 },
18 DocumentDeletion {
19 details: Option<DocumentDeletion>,
20 },
21 IndexCreation {
22 details: Option<IndexCreation>,
23 },
24 IndexUpdate {
25 details: Option<IndexUpdate>,
26 },
27 IndexDeletion {
28 details: Option<IndexDeletion>,
29 },
30 SettingsUpdate {
31 details: Box<Option<Settings>>,
32 },
33 DumpCreation {
34 details: Option<DumpCreation>,
35 },
36 IndexSwap {
37 details: Option<IndexSwap>,
38 },
39 NetworkTopologyChange {
40 #[serde(skip_serializing_if = "Option::is_none")]
41 details: Option<NetworkTopologyChangeDetails>,
42 },
43 TaskCancelation {
44 details: Option<TaskCancelation>,
45 },
46 TaskDeletion {
47 details: Option<TaskDeletion>,
48 },
49 SnapshotCreation {
50 details: Option<SnapshotCreation>,
51 },
52 IndexCompaction {
53 details: Option<IndexCompaction>,
54 },
55}
56
57#[derive(Debug, Clone, Deserialize)]
58pub struct TasksResults {
59 pub results: Vec<Task>,
60 pub total: u64,
61 pub limit: u32,
62 pub from: Option<u32>,
63 pub next: Option<u32>,
64}
65
66#[derive(Debug, Clone, Deserialize)]
67#[serde(rename_all = "camelCase")]
68pub struct DocumentAdditionOrUpdate {
69 pub indexed_documents: Option<usize>,
70 pub received_documents: usize,
71}
72
73#[derive(Debug, Clone, Deserialize)]
74#[serde(rename_all = "camelCase")]
75pub struct DocumentDeletion {
76 pub provided_ids: Option<usize>,
77 pub deleted_documents: Option<usize>,
78 pub original_filter: Option<String>,
79}
80
81#[derive(Debug, Clone, Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub struct IndexCreation {
84 pub primary_key: Option<String>,
85}
86
87#[derive(Debug, Clone, Deserialize)]
88#[serde(rename_all = "camelCase")]
89pub struct IndexUpdate {
90 pub primary_key: Option<String>,
91}
92
93#[derive(Debug, Clone, Deserialize)]
94#[serde(rename_all = "camelCase")]
95pub struct IndexDeletion {
96 pub deleted_documents: Option<usize>,
97}
98
99#[derive(Debug, Clone, Deserialize)]
100#[serde(rename_all = "camelCase")]
101pub struct SnapshotCreation {}
102
103#[derive(Debug, Clone, Deserialize)]
104#[serde(rename_all = "camelCase")]
105pub struct IndexCompaction {}
106
107#[derive(Debug, Clone, Deserialize)]
108#[serde(rename_all = "camelCase")]
109pub struct NetworkTopologyChangeDetails {
110 #[serde(flatten)]
111 pub info: Map<String, Value>,
112}
113
114#[derive(Debug, Clone, Deserialize)]
115#[serde(rename_all = "camelCase")]
116pub struct DumpCreation {
117 pub dump_uid: Option<String>,
118}
119
120#[derive(Debug, Clone, Deserialize)]
121#[serde(rename_all = "camelCase")]
122pub struct IndexSwap {
123 pub swaps: Vec<SwapIndexes>,
124}
125
126#[derive(Debug, Clone, Deserialize)]
127#[serde(rename_all = "camelCase")]
128pub struct TaskCancelation {
129 pub matched_tasks: usize,
130 pub canceled_tasks: Option<usize>,
131 pub original_filter: String,
132}
133
134#[derive(Debug, Clone, Deserialize)]
135#[serde(rename_all = "camelCase")]
136pub struct TaskDeletion {
137 pub matched_tasks: usize,
138 pub deleted_tasks: Option<usize>,
139 pub original_filter: String,
140}
141
142#[derive(Deserialize, Debug, Clone)]
143#[serde(rename_all = "camelCase")]
144pub struct FailedTask {
145 pub error: MeilisearchError,
146 #[serde(flatten)]
147 pub task: SucceededTask,
148}
149
150impl AsRef<u32> for FailedTask {
151 fn as_ref(&self) -> &u32 {
152 &self.task.uid
153 }
154}
155
156fn deserialize_duration<'de, D>(deserializer: D) -> Result<Duration, D::Error>
157where
158 D: Deserializer<'de>,
159{
160 let s = String::deserialize(deserializer)?;
161 let iso_duration = iso8601::duration(&s).map_err(serde::de::Error::custom)?;
162 Ok(iso_duration.into())
163}
164
165#[derive(Deserialize, Debug, Clone)]
166#[serde(rename_all = "camelCase")]
167pub struct SucceededTask {
168 #[serde(deserialize_with = "deserialize_duration")]
169 pub duration: Duration,
170 #[serde(with = "time::serde::rfc3339")]
171 pub enqueued_at: OffsetDateTime,
172 #[serde(with = "time::serde::rfc3339")]
173 pub started_at: OffsetDateTime,
174 #[serde(with = "time::serde::rfc3339")]
175 pub finished_at: OffsetDateTime,
176 pub canceled_by: Option<usize>,
177 pub index_uid: Option<String>,
178 pub error: Option<MeilisearchError>,
179 #[serde(skip_serializing_if = "Option::is_none")]
181 pub remotes: Option<Map<String, Value>>,
182 #[serde(flatten)]
183 pub update_type: TaskType,
184 pub uid: u32,
185}
186
187impl AsRef<u32> for SucceededTask {
188 fn as_ref(&self) -> &u32 {
189 &self.uid
190 }
191}
192
193#[derive(Debug, Clone, Deserialize)]
194#[serde(rename_all = "camelCase")]
195pub struct EnqueuedTask {
196 #[serde(with = "time::serde::rfc3339")]
197 pub enqueued_at: OffsetDateTime,
198 pub index_uid: Option<String>,
199 #[serde(skip_serializing_if = "Option::is_none")]
201 pub remotes: Option<Map<String, Value>>,
202 #[serde(flatten)]
203 pub update_type: TaskType,
204 pub uid: u32,
205}
206
207impl AsRef<u32> for EnqueuedTask {
208 fn as_ref(&self) -> &u32 {
209 &self.uid
210 }
211}
212
213#[derive(Debug, Clone, Deserialize)]
214#[serde(rename_all = "camelCase")]
215pub struct ProcessingTask {
216 #[serde(with = "time::serde::rfc3339")]
217 pub enqueued_at: OffsetDateTime,
218 #[serde(with = "time::serde::rfc3339")]
219 pub started_at: OffsetDateTime,
220 pub index_uid: Option<String>,
221 #[serde(skip_serializing_if = "Option::is_none")]
223 pub remotes: Option<Map<String, Value>>,
224 #[serde(flatten)]
225 pub update_type: TaskType,
226 pub uid: u32,
227}
228
229impl AsRef<u32> for ProcessingTask {
230 fn as_ref(&self) -> &u32 {
231 &self.uid
232 }
233}
234
235#[derive(Debug, Clone, Deserialize)]
236#[serde(rename_all = "camelCase", tag = "status")]
237pub enum Task {
238 Enqueued {
239 #[serde(flatten)]
240 content: EnqueuedTask,
241 },
242 Processing {
243 #[serde(flatten)]
244 content: ProcessingTask,
245 },
246 Failed {
247 #[serde(flatten)]
248 content: FailedTask,
249 },
250 Succeeded {
251 #[serde(flatten)]
252 content: SucceededTask,
253 },
254}
255
256impl Task {
257 #[must_use]
258 pub fn get_uid(&self) -> u32 {
259 match self {
260 Self::Enqueued { content } => *content.as_ref(),
261 Self::Processing { content } => *content.as_ref(),
262 Self::Failed { content } => *content.as_ref(),
263 Self::Succeeded { content } => *content.as_ref(),
264 }
265 }
266
267 pub async fn wait_for_completion<Http: HttpClient>(
313 self,
314 client: &Client<Http>,
315 interval: Option<Duration>,
316 timeout: Option<Duration>,
317 ) -> Result<Self, Error> {
318 client.wait_for_task(self, interval, timeout).await
319 }
320
321 #[allow(clippy::result_large_err)] pub fn try_make_index<Http: HttpClient>(
346 self,
347 client: &Client<Http>,
348 ) -> Result<Index<Http>, Self> {
349 match self {
350 Self::Succeeded {
351 content:
352 SucceededTask {
353 index_uid,
354 update_type: TaskType::IndexCreation { .. },
355 ..
356 },
357 } => Ok(client.index(index_uid.unwrap())),
358 _ => Err(self),
359 }
360 }
361
362 #[must_use]
394 pub fn unwrap_failure(self) -> MeilisearchError {
395 match self {
396 Self::Failed {
397 content: FailedTask { error, .. },
398 } => error,
399 _ => panic!("Called `unwrap_failure` on a non `Failed` task."),
400 }
401 }
402
403 #[must_use]
430 pub fn is_failure(&self) -> bool {
431 matches!(self, Self::Failed { .. })
432 }
433
434 #[must_use]
459 pub fn is_success(&self) -> bool {
460 matches!(self, Self::Succeeded { .. })
461 }
462
463 #[must_use]
487 pub fn is_pending(&self) -> bool {
488 matches!(self, Self::Enqueued { .. } | Self::Processing { .. })
489 }
490}
491
492impl AsRef<u32> for Task {
493 fn as_ref(&self) -> &u32 {
494 match self {
495 Self::Enqueued { content } => content.as_ref(),
496 Self::Processing { content } => content.as_ref(),
497 Self::Succeeded { content } => content.as_ref(),
498 Self::Failed { content } => content.as_ref(),
499 }
500 }
501}
502
503#[derive(Debug, Serialize, Clone)]
504pub struct TasksPaginationFilters {
505 #[serde(skip_serializing_if = "Option::is_none")]
507 limit: Option<u32>,
508 #[serde(skip_serializing_if = "Option::is_none")]
510 from: Option<u32>,
511}
512
513#[derive(Debug, Serialize, Clone)]
514pub struct TasksCancelFilters {}
515
516#[derive(Debug, Serialize, Clone)]
517pub struct TasksDeleteFilters {}
518
519pub type TasksSearchQuery<'a, Http> = TasksQuery<'a, TasksPaginationFilters, Http>;
520pub type TasksCancelQuery<'a, Http> = TasksQuery<'a, TasksCancelFilters, Http>;
521pub type TasksDeleteQuery<'a, Http> = TasksQuery<'a, TasksDeleteFilters, Http>;
522
523#[derive(Debug, Serialize, Clone)]
524#[serde(rename_all = "camelCase")]
525pub struct TasksQuery<'a, T, Http: HttpClient> {
526 #[serde(skip_serializing)]
527 client: &'a Client<Http>,
528 #[serde(skip_serializing_if = "Option::is_none")]
530 index_uids: Option<Vec<&'a str>>,
531 #[serde(skip_serializing_if = "Option::is_none")]
533 statuses: Option<Vec<&'a str>>,
534 #[serde(skip_serializing_if = "Option::is_none", rename = "types")]
536 task_types: Option<Vec<&'a str>>,
537 #[serde(skip_serializing_if = "Option::is_none")]
539 uids: Option<Vec<&'a u32>>,
540 #[serde(skip_serializing_if = "Option::is_none")]
542 canceled_by: Option<Vec<&'a u32>>,
543 #[serde(
545 skip_serializing_if = "Option::is_none",
546 serialize_with = "time::serde::rfc3339::option::serialize"
547 )]
548 before_enqueued_at: Option<OffsetDateTime>,
549 #[serde(
551 skip_serializing_if = "Option::is_none",
552 serialize_with = "time::serde::rfc3339::option::serialize"
553 )]
554 after_enqueued_at: Option<OffsetDateTime>,
555 #[serde(
557 skip_serializing_if = "Option::is_none",
558 serialize_with = "time::serde::rfc3339::option::serialize"
559 )]
560 before_started_at: Option<OffsetDateTime>,
561 #[serde(
563 skip_serializing_if = "Option::is_none",
564 serialize_with = "time::serde::rfc3339::option::serialize"
565 )]
566 after_started_at: Option<OffsetDateTime>,
567 #[serde(
569 skip_serializing_if = "Option::is_none",
570 serialize_with = "time::serde::rfc3339::option::serialize"
571 )]
572 before_finished_at: Option<OffsetDateTime>,
573 #[serde(
575 skip_serializing_if = "Option::is_none",
576 serialize_with = "time::serde::rfc3339::option::serialize"
577 )]
578 after_finished_at: Option<OffsetDateTime>,
579
580 #[serde(flatten)]
581 pagination: T,
582
583 #[serde(skip_serializing_if = "Option::is_none")]
585 reverse: Option<bool>,
586}
587
588#[allow(missing_docs)]
589impl<'a, T, Http: HttpClient> TasksQuery<'a, T, Http> {
590 pub fn with_index_uids<'b>(
591 &'b mut self,
592 index_uids: impl IntoIterator<Item = &'a str>,
593 ) -> &'b mut TasksQuery<'a, T, Http> {
594 self.index_uids = Some(index_uids.into_iter().collect());
595 self
596 }
597 pub fn with_statuses<'b>(
598 &'b mut self,
599 statuses: impl IntoIterator<Item = &'a str>,
600 ) -> &'b mut TasksQuery<'a, T, Http> {
601 self.statuses = Some(statuses.into_iter().collect());
602 self
603 }
604 pub fn with_types<'b>(
605 &'b mut self,
606 task_types: impl IntoIterator<Item = &'a str>,
607 ) -> &'b mut TasksQuery<'a, T, Http> {
608 self.task_types = Some(task_types.into_iter().collect());
609 self
610 }
611 pub fn with_uids<'b>(
612 &'b mut self,
613 uids: impl IntoIterator<Item = &'a u32>,
614 ) -> &'b mut TasksQuery<'a, T, Http> {
615 self.uids = Some(uids.into_iter().collect());
616 self
617 }
618 pub fn with_before_enqueued_at<'b>(
619 &'b mut self,
620 before_enqueued_at: &'a OffsetDateTime,
621 ) -> &'b mut TasksQuery<'a, T, Http> {
622 self.before_enqueued_at = Some(*before_enqueued_at);
623 self
624 }
625 pub fn with_after_enqueued_at<'b>(
626 &'b mut self,
627 after_enqueued_at: &'a OffsetDateTime,
628 ) -> &'b mut TasksQuery<'a, T, Http> {
629 self.after_enqueued_at = Some(*after_enqueued_at);
630 self
631 }
632 pub fn with_before_started_at<'b>(
633 &'b mut self,
634 before_started_at: &'a OffsetDateTime,
635 ) -> &'b mut TasksQuery<'a, T, Http> {
636 self.before_started_at = Some(*before_started_at);
637 self
638 }
639 pub fn with_after_started_at<'b>(
640 &'b mut self,
641 after_started_at: &'a OffsetDateTime,
642 ) -> &'b mut TasksQuery<'a, T, Http> {
643 self.after_started_at = Some(*after_started_at);
644 self
645 }
646 pub fn with_before_finished_at<'b>(
647 &'b mut self,
648 before_finished_at: &'a OffsetDateTime,
649 ) -> &'b mut TasksQuery<'a, T, Http> {
650 self.before_finished_at = Some(*before_finished_at);
651 self
652 }
653 pub fn with_after_finished_at<'b>(
654 &'b mut self,
655 after_finished_at: &'a OffsetDateTime,
656 ) -> &'b mut TasksQuery<'a, T, Http> {
657 self.after_finished_at = Some(*after_finished_at);
658 self
659 }
660 pub fn with_canceled_by<'b>(
661 &'b mut self,
662 task_uids: impl IntoIterator<Item = &'a u32>,
663 ) -> &'b mut TasksQuery<'a, T, Http> {
664 self.canceled_by = Some(task_uids.into_iter().collect());
665 self
666 }
667 pub fn with_reverse<'b>(&'b mut self, reverse: bool) -> &'b mut TasksQuery<'a, T, Http> {
668 self.reverse = Some(reverse);
669 self
670 }
671}
672
673impl<'a, Http: HttpClient> TasksQuery<'a, TasksCancelFilters, Http> {
674 #[must_use]
675 pub fn new(client: &'a Client<Http>) -> TasksQuery<'a, TasksCancelFilters, Http> {
676 TasksQuery {
677 client,
678 index_uids: None,
679 statuses: None,
680 task_types: None,
681 uids: None,
682 canceled_by: None,
683 before_enqueued_at: None,
684 after_enqueued_at: None,
685 before_started_at: None,
686 after_started_at: None,
687 before_finished_at: None,
688 after_finished_at: None,
689 reverse: None,
690 pagination: TasksCancelFilters {},
691 }
692 }
693
694 pub async fn execute(&'a self) -> Result<TaskInfo, Error> {
695 self.client.cancel_tasks_with(self).await
696 }
697}
698
699impl<'a, Http: HttpClient> TasksQuery<'a, TasksDeleteFilters, Http> {
700 #[must_use]
701 pub fn new(client: &'a Client<Http>) -> TasksQuery<'a, TasksDeleteFilters, Http> {
702 TasksQuery {
703 client,
704 index_uids: None,
705 statuses: None,
706 task_types: None,
707 uids: None,
708 canceled_by: None,
709 before_enqueued_at: None,
710 after_enqueued_at: None,
711 before_started_at: None,
712 after_started_at: None,
713 before_finished_at: None,
714 after_finished_at: None,
715 pagination: TasksDeleteFilters {},
716 reverse: None,
717 }
718 }
719
720 pub async fn execute(&'a self) -> Result<TaskInfo, Error> {
721 self.client.delete_tasks_with(self).await
722 }
723}
724
725impl<'a, Http: HttpClient> TasksQuery<'a, TasksPaginationFilters, Http> {
726 #[must_use]
727 pub fn new(client: &'a Client<Http>) -> TasksQuery<'a, TasksPaginationFilters, Http> {
728 TasksQuery {
729 client,
730 index_uids: None,
731 statuses: None,
732 task_types: None,
733 uids: None,
734 canceled_by: None,
735 before_enqueued_at: None,
736 after_enqueued_at: None,
737 before_started_at: None,
738 after_started_at: None,
739 before_finished_at: None,
740 after_finished_at: None,
741 pagination: TasksPaginationFilters {
742 limit: None,
743 from: None,
744 },
745 reverse: None,
746 }
747 }
748 pub fn with_limit<'b>(
749 &'b mut self,
750 limit: u32,
751 ) -> &'b mut TasksQuery<'a, TasksPaginationFilters, Http> {
752 self.pagination.limit = Some(limit);
753 self
754 }
755 pub fn with_from<'b>(
756 &'b mut self,
757 from: u32,
758 ) -> &'b mut TasksQuery<'a, TasksPaginationFilters, Http> {
759 self.pagination.from = Some(from);
760 self
761 }
762 pub async fn execute(&'a self) -> Result<TasksResults, Error> {
763 self.client.get_tasks_with(self).await
764 }
765}
766
767#[cfg(test)]
768mod test {
769
770 #[test]
771 fn test_deserialize_enqueued_task_with_remotes() {
772 let json = r#"{
773 "enqueuedAt": "2022-02-03T13:02:38.369634Z",
774 "indexUid": "movies",
775 "status": "enqueued",
776 "type": "indexUpdate",
777 "uid": 12,
778 "remotes": { "ms-00": { "status": "ok" } }
779 }"#;
780 let task: Task = serde_json::from_str(json).unwrap();
781 match task {
782 Task::Enqueued { content } => {
783 let remotes = content.remotes.expect("remotes should be present");
784 assert!(remotes.contains_key("ms-00"));
785 }
786 _ => panic!("expected enqueued task"),
787 }
788 }
789
790 #[test]
791 fn test_deserialize_processing_task_with_remotes() {
792 let json = r#"{
793 "details": {
794 "indexedDocuments": null,
795 "receivedDocuments": 10
796 },
797 "duration": null,
798 "enqueuedAt": "2022-02-03T15:17:02.801341Z",
799 "finishedAt": null,
800 "indexUid": "movies",
801 "startedAt": "2022-02-03T15:17:02.812338Z",
802 "status": "processing",
803 "type": "documentAdditionOrUpdate",
804 "uid": 14,
805 "remotes": { "ms-00": { "status": "ok" } }
806 }"#;
807 let task: Task = serde_json::from_str(json).unwrap();
808 match task {
809 Task::Processing { content } => {
810 let remotes = content.remotes.expect("remotes should be present");
811 assert!(remotes.contains_key("ms-00"));
812 }
813 _ => panic!("expected processing task"),
814 }
815 }
816
817 use super::*;
818 use crate::{
819 client::*,
820 errors::{ErrorCode, ErrorType},
821 };
822 use big_s::S;
823 use meilisearch_test_macro::meilisearch_test;
824 use serde::{Deserialize, Serialize};
825 use std::time::Duration;
826
827 #[derive(Debug, Serialize, Deserialize, PartialEq)]
828 struct Document {
829 id: usize,
830 value: String,
831 kind: String,
832 }
833
834 #[test]
835 fn test_deserialize_task() {
836 let datetime = OffsetDateTime::parse(
837 "2022-02-03T13:02:38.369634Z",
838 &time::format_description::well_known::Rfc3339,
839 )
840 .unwrap();
841
842 let task: Task = serde_json::from_str(
843 r#"
844{
845 "enqueuedAt": "2022-02-03T13:02:38.369634Z",
846 "indexUid": "meili",
847 "status": "enqueued",
848 "type": "documentAdditionOrUpdate",
849 "uid": 12
850}"#,
851 )
852 .unwrap();
853
854 assert!(matches!(
855 task,
856 Task::Enqueued {
857 content: EnqueuedTask {
858 enqueued_at,
859 index_uid: Some(index_uid),
860 update_type: TaskType::DocumentAdditionOrUpdate { details: None },
861 uid: 12, .. }
862 }
863 if enqueued_at == datetime && index_uid == "meili"));
864
865 let task: Task = serde_json::from_str(
866 r#"
867{
868 "details": {
869 "indexedDocuments": null,
870 "receivedDocuments": 19547
871 },
872 "duration": null,
873 "enqueuedAt": "2022-02-03T15:17:02.801341Z",
874 "finishedAt": null,
875 "indexUid": "meili",
876 "startedAt": "2022-02-03T15:17:02.812338Z",
877 "status": "processing",
878 "type": "documentAdditionOrUpdate",
879 "uid": 14
880}"#,
881 )
882 .unwrap();
883
884 assert!(matches!(
885 task,
886 Task::Processing {
887 content: ProcessingTask {
888 started_at,
889 update_type: TaskType::DocumentAdditionOrUpdate {
890 details: Some(DocumentAdditionOrUpdate {
891 received_documents: 19547,
892 indexed_documents: None,
893 })
894 },
895 uid: 14,
896 ..
897 }
898 }
899 if started_at == OffsetDateTime::parse(
900 "2022-02-03T15:17:02.812338Z",
901 &time::format_description::well_known::Rfc3339
902 ).unwrap()
903 ));
904
905 let task: Task = serde_json::from_str(
906 r#"
907{
908 "details": {
909 "indexedDocuments": 19546,
910 "receivedDocuments": 19547
911 },
912 "duration": "PT10.848957S",
913 "enqueuedAt": "2022-02-03T15:17:02.801341Z",
914 "finishedAt": "2022-02-03T15:17:13.661295Z",
915 "indexUid": "meili",
916 "startedAt": "2022-02-03T15:17:02.812338Z",
917 "status": "succeeded",
918 "type": "documentAdditionOrUpdate",
919 "uid": 14
920}"#,
921 )
922 .unwrap();
923
924 assert!(matches!(
925 task,
926 Task::Succeeded {
927 content: SucceededTask {
928 update_type: TaskType::DocumentAdditionOrUpdate {
929 details: Some(DocumentAdditionOrUpdate {
930 received_documents: 19547,
931 indexed_documents: Some(19546),
932 })
933 },
934 uid: 14,
935 duration,
936 ..
937 }
938 }
939 if duration == Duration::from_millis(10_848)
940 ));
941 }
942
943 #[meilisearch_test]
944 async fn test_wait_for_task_with_args(client: Client, movies: Index) -> Result<(), Error> {
945 let task = movies
946 .add_documents(
947 &[
948 Document {
949 id: 0,
950 kind: "title".into(),
951 value: S("The Social Network"),
952 },
953 Document {
954 id: 1,
955 kind: "title".into(),
956 value: S("Harry Potter and the Sorcerer's Stone"),
957 },
958 ],
959 None,
960 )
961 .await?
962 .wait_for_completion(
963 &client,
964 Some(Duration::from_millis(1)),
965 Some(Duration::from_millis(6000)),
966 )
967 .await?;
968
969 assert!(matches!(task, Task::Succeeded { .. }));
970 Ok(())
971 }
972
973 #[meilisearch_test]
974 async fn test_get_tasks_no_params() -> Result<(), Error> {
975 let mut s = mockito::Server::new_async().await;
976 let mock_server_url = s.url();
977 let client = Client::new(mock_server_url, Some("masterKey")).unwrap();
978 let path = "/tasks";
979
980 let mock_res = s.mock("GET", path).with_status(200).create_async().await;
981 let _ = client.get_tasks().await;
982 mock_res.assert_async().await;
983
984 Ok(())
985 }
986
987 #[meilisearch_test]
988 async fn test_get_tasks_with_params() -> Result<(), Error> {
989 let mut s = mockito::Server::new_async().await;
990 let mock_server_url = s.url();
991 let client = Client::new(mock_server_url, Some("masterKey")).unwrap();
992 let path =
993 "/tasks?indexUids=movies,test&statuses=equeued&types=documentDeletion&uids=1&limit=0&from=1&reverse=true";
994
995 let mock_res = s.mock("GET", path).with_status(200).create_async().await;
996
997 let mut query = TasksSearchQuery::new(&client);
998 query
999 .with_index_uids(["movies", "test"])
1000 .with_statuses(["equeued"])
1001 .with_types(["documentDeletion"])
1002 .with_from(1)
1003 .with_limit(0)
1004 .with_uids([&1])
1005 .with_reverse(true);
1006
1007 let _ = client.get_tasks_with(&query).await;
1008
1009 mock_res.assert_async().await;
1010
1011 Ok(())
1012 }
1013
1014 #[meilisearch_test]
1015 async fn test_get_tasks_with_date_params() -> Result<(), Error> {
1016 let mut s = mockito::Server::new_async().await;
1017 let mock_server_url = s.url();
1018 let client = Client::new(mock_server_url, Some("masterKey")).unwrap();
1019 let path = "/tasks?\
1020 beforeEnqueuedAt=2022-02-03T13%3A02%3A38.369634Z\
1021 &afterEnqueuedAt=2023-02-03T13%3A02%3A38.369634Z\
1022 &beforeStartedAt=2024-02-03T13%3A02%3A38.369634Z\
1023 &afterStartedAt=2025-02-03T13%3A02%3A38.369634Z\
1024 &beforeFinishedAt=2026-02-03T13%3A02%3A38.369634Z\
1025 &afterFinishedAt=2027-02-03T13%3A02%3A38.369634Z";
1026
1027 let mock_res = s.mock("GET", path).with_status(200).create_async().await;
1028
1029 let before_enqueued_at = OffsetDateTime::parse(
1030 "2022-02-03T13:02:38.369634Z",
1031 &time::format_description::well_known::Rfc3339,
1032 )
1033 .unwrap();
1034 let after_enqueued_at = OffsetDateTime::parse(
1035 "2023-02-03T13:02:38.369634Z",
1036 &time::format_description::well_known::Rfc3339,
1037 )
1038 .unwrap();
1039 let before_started_at = OffsetDateTime::parse(
1040 "2024-02-03T13:02:38.369634Z",
1041 &time::format_description::well_known::Rfc3339,
1042 )
1043 .unwrap();
1044
1045 let after_started_at = OffsetDateTime::parse(
1046 "2025-02-03T13:02:38.369634Z",
1047 &time::format_description::well_known::Rfc3339,
1048 )
1049 .unwrap();
1050
1051 let before_finished_at = OffsetDateTime::parse(
1052 "2026-02-03T13:02:38.369634Z",
1053 &time::format_description::well_known::Rfc3339,
1054 )
1055 .unwrap();
1056
1057 let after_finished_at = OffsetDateTime::parse(
1058 "2027-02-03T13:02:38.369634Z",
1059 &time::format_description::well_known::Rfc3339,
1060 )
1061 .unwrap();
1062
1063 let mut query = TasksSearchQuery::new(&client);
1064 query
1065 .with_before_enqueued_at(&before_enqueued_at)
1066 .with_after_enqueued_at(&after_enqueued_at)
1067 .with_before_started_at(&before_started_at)
1068 .with_after_started_at(&after_started_at)
1069 .with_before_finished_at(&before_finished_at)
1070 .with_after_finished_at(&after_finished_at);
1071
1072 let _ = client.get_tasks_with(&query).await;
1073
1074 mock_res.assert_async().await;
1075
1076 Ok(())
1077 }
1078
1079 #[meilisearch_test]
1080 async fn test_get_tasks_on_struct_with_params() -> Result<(), Error> {
1081 let mut s = mockito::Server::new_async().await;
1082 let mock_server_url = s.url();
1083 let client = Client::new(mock_server_url, Some("masterKey")).unwrap();
1084 let path =
1085 "/tasks?indexUids=movies,test&statuses=equeued&types=documentDeletion&canceledBy=9";
1086
1087 let mock_res = s.mock("GET", path).with_status(200).create_async().await;
1088
1089 let mut query = TasksSearchQuery::new(&client);
1090 let _ = query
1091 .with_index_uids(["movies", "test"])
1092 .with_statuses(["equeued"])
1093 .with_types(["documentDeletion"])
1094 .with_canceled_by([&9])
1095 .execute()
1096 .await;
1097
1098 mock_res.assert_async().await;
1099
1100 Ok(())
1101 }
1102
1103 #[meilisearch_test]
1104 async fn test_get_tasks_with_none_existant_index_uids(client: Client) -> Result<(), Error> {
1105 let mut query = TasksSearchQuery::new(&client);
1106 query.with_index_uids(["no_name"]);
1107 let tasks = client.get_tasks_with(&query).await.unwrap();
1108
1109 assert_eq!(tasks.results.len(), 0);
1110 Ok(())
1111 }
1112
1113 #[meilisearch_test]
1114 async fn test_get_tasks_with_execute(client: Client) -> Result<(), Error> {
1115 let tasks = TasksSearchQuery::new(&client)
1116 .with_index_uids(["no_name"])
1117 .execute()
1118 .await
1119 .unwrap();
1120
1121 assert_eq!(tasks.results.len(), 0);
1122 Ok(())
1123 }
1124
1125 #[meilisearch_test]
1126 async fn test_failing_task(client: Client, index: Index) -> Result<(), Error> {
1127 let task_info = client.create_index(index.uid, None).await.unwrap();
1128 let task = client.get_task(task_info).await?;
1129 let task = client.wait_for_task(task, None, None).await?;
1130
1131 let error = task.unwrap_failure();
1132 assert_eq!(error.error_code, ErrorCode::IndexAlreadyExists);
1133 assert_eq!(error.error_type, ErrorType::InvalidRequest);
1134 Ok(())
1135 }
1136
1137 #[meilisearch_test]
1138 async fn test_cancel_tasks_with_params() -> Result<(), Error> {
1139 let mut s = mockito::Server::new_async().await;
1140 let mock_server_url = s.url();
1141 let client = Client::new(mock_server_url, Some("masterKey")).unwrap();
1142 let path =
1143 "/tasks/cancel?indexUids=movies,test&statuses=equeued&types=documentDeletion&uids=1";
1144
1145 let mock_res = s.mock("POST", path).with_status(200).create_async().await;
1146
1147 let mut query = TasksCancelQuery::new(&client);
1148 query
1149 .with_index_uids(["movies", "test"])
1150 .with_statuses(["equeued"])
1151 .with_types(["documentDeletion"])
1152 .with_uids([&1]);
1153
1154 let _ = client.cancel_tasks_with(&query).await;
1155
1156 mock_res.assert_async().await;
1157
1158 Ok(())
1159 }
1160
1161 #[meilisearch_test]
1162 async fn test_cancel_tasks_with_params_execute() -> Result<(), Error> {
1163 let mut s = mockito::Server::new_async().await;
1164 let mock_server_url = s.url();
1165 let client = Client::new(mock_server_url, Some("masterKey")).unwrap();
1166 let path =
1167 "/tasks/cancel?indexUids=movies,test&statuses=equeued&types=documentDeletion&uids=1";
1168
1169 let mock_res = s.mock("POST", path).with_status(200).create_async().await;
1170
1171 let mut query = TasksCancelQuery::new(&client);
1172 let _ = query
1173 .with_index_uids(["movies", "test"])
1174 .with_statuses(["equeued"])
1175 .with_types(["documentDeletion"])
1176 .with_uids([&1])
1177 .execute()
1178 .await;
1179
1180 mock_res.assert_async().await;
1181
1182 Ok(())
1183 }
1184
1185 #[meilisearch_test]
1186 async fn test_delete_tasks_with_params() -> Result<(), Error> {
1187 let mut s = mockito::Server::new_async().await;
1188 let mock_server_url = s.url();
1189 let client = Client::new(mock_server_url, Some("masterKey")).unwrap();
1190 let path = "/tasks?indexUids=movies,test&statuses=equeued&types=documentDeletion&uids=1";
1191
1192 let mock_res = s.mock("DELETE", path).with_status(200).create_async().await;
1193
1194 let mut query = TasksDeleteQuery::new(&client);
1195 query
1196 .with_index_uids(["movies", "test"])
1197 .with_statuses(["equeued"])
1198 .with_types(["documentDeletion"])
1199 .with_uids([&1]);
1200
1201 let _ = client.delete_tasks_with(&query).await;
1202
1203 mock_res.assert_async().await;
1204
1205 Ok(())
1206 }
1207
1208 #[meilisearch_test]
1209 async fn test_delete_tasks_with_params_execute() -> Result<(), Error> {
1210 let mut s = mockito::Server::new_async().await;
1211 let mock_server_url = s.url();
1212 let client = Client::new(mock_server_url, Some("masterKey")).unwrap();
1213 let path = "/tasks?indexUids=movies,test&statuses=equeued&types=documentDeletion&uids=1";
1214
1215 let mock_res = s.mock("DELETE", path).with_status(200).create_async().await;
1216
1217 let mut query = TasksDeleteQuery::new(&client);
1218 let _ = query
1219 .with_index_uids(["movies", "test"])
1220 .with_statuses(["equeued"])
1221 .with_types(["documentDeletion"])
1222 .with_uids([&1])
1223 .execute()
1224 .await;
1225
1226 mock_res.assert_async().await;
1227
1228 Ok(())
1229 }
1230}