meilisearch_sdk/
task_info.rs

1use serde::Deserialize;
2use std::time::Duration;
3use time::OffsetDateTime;
4
5use crate::{client::Client, errors::Error, request::HttpClient, tasks::*};
6
7#[derive(Debug, Clone, Deserialize)]
8#[serde(rename_all = "camelCase")]
9pub struct TaskInfo {
10    #[serde(with = "time::serde::rfc3339")]
11    pub enqueued_at: OffsetDateTime,
12    pub index_uid: Option<String>,
13    pub status: String,
14    #[serde(flatten)]
15    pub update_type: TaskType,
16    pub task_uid: u32,
17}
18
19impl AsRef<u32> for TaskInfo {
20    fn as_ref(&self) -> &u32 {
21        &self.task_uid
22    }
23}
24
25impl TaskInfo {
26    #[must_use]
27    pub fn get_task_uid(&self) -> u32 {
28        self.task_uid
29    }
30
31    /// Wait until Meilisearch processes a task provided by [`TaskInfo`], and get its status.
32    ///
33    /// `interval` = The frequency at which the server should be polled. **Default = 50ms**
34    ///
35    /// `timeout` = The maximum time to wait for processing to complete. **Default = 5000ms**
36    ///
37    /// If the waited time exceeds `timeout` then an [`Error::Timeout`] will be returned.
38    ///
39    /// See also [`Client::wait_for_task`, `Index::wait_for_task`].
40    ///
41    /// # Example
42    ///
43    /// ```
44    /// # use meilisearch_sdk::{client::*, indexes::*, tasks::*};
45    /// # use serde::{Serialize, Deserialize};
46    /// #
47    /// # #[derive(Debug, Serialize, Deserialize, PartialEq)]
48    /// # struct Document {
49    /// #    id: usize,
50    /// #    value: String,
51    /// #    kind: String,
52    /// # }
53    /// #
54    /// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
55    /// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
56    /// #
57    /// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
58    /// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
59    /// let movies = client.index("movies_wait_for_completion");
60    ///
61    /// let status = movies.add_documents(&[
62    ///     Document { id: 0, kind: "title".into(), value: "The Social Network".to_string() },
63    ///     Document { id: 1, kind: "title".into(), value: "Harry Potter and the Sorcerer's Stone".to_string() },
64    /// ], None)
65    ///     .await
66    ///     .unwrap()
67    ///     .wait_for_completion(&client, None, None)
68    ///     .await
69    ///     .unwrap();
70    ///
71    /// assert!(matches!(status, Task::Succeeded { .. }));
72    /// # movies.delete().await.unwrap().wait_for_completion(&client, None, None).await.unwrap();
73    /// # });
74    /// ```
75    pub async fn wait_for_completion<Http: HttpClient>(
76        self,
77        client: &Client<Http>,
78        interval: Option<Duration>,
79        timeout: Option<Duration>,
80    ) -> Result<Task, Error> {
81        client.wait_for_task(self, interval, timeout).await
82    }
83}
84
85#[cfg(test)]
86mod test {
87    use super::*;
88    use crate::{
89        client::*,
90        errors::{ErrorCode, ErrorType},
91        indexes::Index,
92    };
93    use big_s::S;
94    use meilisearch_test_macro::meilisearch_test;
95    use serde::{Deserialize, Serialize};
96    use std::time::Duration;
97
98    #[derive(Debug, Serialize, Deserialize, PartialEq)]
99    struct Document {
100        id: usize,
101        value: String,
102        kind: String,
103    }
104
105    #[test]
106    fn test_deserialize_task_info() {
107        let datetime = OffsetDateTime::parse(
108            "2022-02-03T13:02:38.369634Z",
109            &time::format_description::well_known::Rfc3339,
110        )
111        .unwrap();
112
113        let task_info: TaskInfo = serde_json::from_str(
114            r#"
115{
116  "enqueuedAt": "2022-02-03T13:02:38.369634Z",
117  "indexUid": "meili",
118  "status": "enqueued",
119  "type": "documentAdditionOrUpdate",
120  "taskUid": 12
121}"#,
122        )
123        .unwrap();
124
125        assert!(matches!(
126            task_info,
127            TaskInfo {
128                enqueued_at,
129                index_uid: Some(index_uid),
130                task_uid: 12,
131                update_type: TaskType::DocumentAdditionOrUpdate { details: None },
132                status,
133            }
134        if enqueued_at == datetime && index_uid == "meili" && status == "enqueued"));
135    }
136
137    #[meilisearch_test]
138    async fn test_wait_for_task_with_args(client: Client, movies: Index) -> Result<(), Error> {
139        let task_info = movies
140            .add_documents(
141                &[
142                    Document {
143                        id: 0,
144                        kind: "title".into(),
145                        value: S("The Social Network"),
146                    },
147                    Document {
148                        id: 1,
149                        kind: "title".into(),
150                        value: S("Harry Potter and the Sorcerer's Stone"),
151                    },
152                ],
153                None,
154            )
155            .await?;
156
157        let task = client
158            .get_task(task_info)
159            .await?
160            .wait_for_completion(
161                &client,
162                Some(Duration::from_millis(1)),
163                Some(Duration::from_millis(6000)),
164            )
165            .await?;
166
167        assert!(matches!(task, Task::Succeeded { .. }));
168        Ok(())
169    }
170
171    #[meilisearch_test]
172    async fn test_failing_task(client: Client, index: Index) -> Result<(), Error> {
173        let task_info = client.create_index(index.uid, None).await.unwrap();
174        let task = client.wait_for_task(task_info, None, None).await?;
175
176        let error = task.unwrap_failure();
177        assert_eq!(error.error_code, ErrorCode::IndexAlreadyExists);
178        assert_eq!(error.error_type, ErrorType::InvalidRequest);
179        Ok(())
180    }
181}