meilisearch_sdk/
batches.rs

1use serde::{Deserialize, Serialize};
2use time::OffsetDateTime;
3
4use crate::{client::Client, errors::Error, request::HttpClient};
5
6/// Types and queries for the Meilisearch Batches API.
7///
8/// See: https://www.meilisearch.com/docs/reference/api/batches
9#[derive(Debug, Clone, Deserialize)]
10#[serde(rename_all = "camelCase")]
11pub struct Batch {
12    /// Unique identifier of the batch.
13    pub uid: u32,
14    /// When the batch was enqueued.
15    #[serde(default, with = "time::serde::rfc3339::option")]
16    pub enqueued_at: Option<OffsetDateTime>,
17    /// When the batch started processing.
18    #[serde(default, with = "time::serde::rfc3339::option")]
19    pub started_at: Option<OffsetDateTime>,
20    /// When the batch finished processing.
21    #[serde(default, with = "time::serde::rfc3339::option")]
22    pub finished_at: Option<OffsetDateTime>,
23    /// Index uid related to this batch (if applicable).
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub index_uid: Option<String>,
26    /// The task uids that are part of this batch.
27    #[serde(skip_serializing_if = "Option::is_none")]
28    pub task_uids: Option<Vec<u32>>,
29    /// The strategy that caused the autobatcher to stop batching tasks.
30    ///
31    /// Introduced in Meilisearch v1.15.
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub batch_strategy: Option<BatchStrategy>,
34}
35
36/// Reason why the autobatcher stopped batching tasks.
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
38#[serde(rename_all = "snake_case")]
39#[non_exhaustive]
40pub enum BatchStrategy {
41    /// The batch reached its configured size threshold.
42    SizeLimitReached,
43    /// The batch reached its configured time window threshold.
44    TimeLimitReached,
45    /// Unknown strategy (forward-compatibility).
46    #[serde(other)]
47    Unknown,
48}
49
50#[derive(Debug, Clone, Deserialize)]
51#[serde(rename_all = "camelCase")]
52pub struct BatchesResults {
53    pub results: Vec<Batch>,
54    pub total: u32,
55    pub limit: u32,
56    #[serde(skip_serializing_if = "Option::is_none")]
57    pub from: Option<u32>,
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub next: Option<u32>,
60}
61
62/// Query builder for listing batches.
63#[derive(Debug, Serialize, Clone)]
64#[serde(rename_all = "camelCase")]
65pub struct BatchesQuery<'a, Http: HttpClient> {
66    #[serde(skip_serializing)]
67    client: &'a Client<Http>,
68    /// Maximum number of batches to return.
69    #[serde(skip_serializing_if = "Option::is_none")]
70    limit: Option<u32>,
71    /// The first batch uid that should be returned.
72    #[serde(skip_serializing_if = "Option::is_none")]
73    from: Option<u32>,
74}
75
76impl<'a, Http: HttpClient> BatchesQuery<'a, Http> {
77    #[must_use]
78    pub fn new(client: &'a Client<Http>) -> BatchesQuery<'a, Http> {
79        BatchesQuery {
80            client,
81            limit: None,
82            from: None,
83        }
84    }
85
86    #[must_use]
87    pub fn with_limit(&mut self, limit: u32) -> &mut Self {
88        self.limit = Some(limit);
89        self
90    }
91
92    #[must_use]
93    pub fn with_from(&mut self, from: u32) -> &mut Self {
94        self.from = Some(from);
95        self
96    }
97
98    /// Execute the query and list batches.
99    pub async fn execute(&self) -> Result<BatchesResults, Error> {
100        self.client.get_batches_with(self).await
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use crate::batches::BatchStrategy;
107    use crate::client::Client;
108
109    #[tokio::test]
110    async fn test_get_batches_parses_batch_strategy() {
111        let mut s = mockito::Server::new_async().await;
112        let base = s.url();
113
114        let response_body = serde_json::json!({
115            "results": [
116                {
117                    "uid": 42,
118                    "enqueuedAt": "2024-10-11T11:49:53.000Z",
119                    "startedAt": "2024-10-11T11:49:54.000Z",
120                    "finishedAt": "2024-10-11T11:49:55.000Z",
121                    "indexUid": "movies",
122                    "taskUids": [1, 2, 3],
123                    "batchStrategy": "time_limit_reached"
124                }
125            ],
126            "limit": 20,
127            "from": null,
128            "next": null,
129            "total": 1
130        })
131        .to_string();
132
133        let _m = s
134            .mock("GET", "/batches")
135            .with_status(200)
136            .with_header("content-type", "application/json")
137            .with_body(response_body)
138            .create_async()
139            .await;
140
141        let client = Client::new(base, None::<String>).unwrap();
142        let batches = client.get_batches().await.expect("list batches failed");
143        assert_eq!(batches.results.len(), 1);
144        let b = &batches.results[0];
145        assert_eq!(b.uid, 42);
146        assert_eq!(b.batch_strategy, Some(BatchStrategy::TimeLimitReached));
147    }
148
149    #[tokio::test]
150    async fn test_get_batch_by_uid_parses_batch_strategy() {
151        let mut s = mockito::Server::new_async().await;
152        let base = s.url();
153
154        let response_body = serde_json::json!({
155            "uid": 99,
156            "batchStrategy": "size_limit_reached",
157            "taskUids": [10, 11]
158        })
159        .to_string();
160
161        let _m = s
162            .mock("GET", "/batches/99")
163            .with_status(200)
164            .with_header("content-type", "application/json")
165            .with_body(response_body)
166            .create_async()
167            .await;
168
169        let client = Client::new(base, None::<String>).unwrap();
170        let batch = client.get_batch(99).await.expect("get batch failed");
171        assert_eq!(batch.uid, 99);
172        assert_eq!(batch.batch_strategy, Some(BatchStrategy::SizeLimitReached));
173    }
174
175    #[tokio::test]
176    async fn test_query_serialization_for_batches() {
177        use mockito::Matcher;
178        let mut s = mockito::Server::new_async().await;
179        let base = s.url();
180
181        let _m = s
182            .mock("GET", "/batches")
183            .match_query(Matcher::AllOf(vec![
184                Matcher::UrlEncoded("limit".into(), "2".into()),
185                Matcher::UrlEncoded("from".into(), "40".into()),
186            ]))
187            .with_status(200)
188            .with_header("content-type", "application/json")
189            .with_body(r#"{"results":[],"limit":2,"total":0}"#)
190            .create_async()
191            .await;
192
193        let client = Client::new(base, None::<String>).unwrap();
194        let mut q = crate::batches::BatchesQuery::new(&client);
195        let _ = q.with_limit(2).with_from(40);
196        let res = client.get_batches_with(&q).await.expect("request failed");
197        assert_eq!(res.limit, 2);
198    }
199}