Skip to content

Commit 5757ec4

Browse files
Merge #722
722: Add batchStrategy field to batches r=curquiza a=kumarUjjawal # Pull Request ## Related issue Fixes #671 ## What does this PR do? - I also added examples in the `.code-samples.meilisearch.yaml` ## PR checklist Please check if your PR fulfills the following requirements: - [x] Does this PR fix an existing issue, or have you listed the changes applied in the PR description (and why they are needed)? - [x] Have you read the contributing guidelines? - [x] Have you made sure that the title is accurate and descriptive of the changes? Thank you so much for contributing to Meilisearch! <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added support for the Batches API — list batches with pagination and fetch batch details. * Included asynchronous code samples demonstrating listing batches, paginating results, and fetching a single batch. * **Tests** * Added unit tests covering batch parsing and retrieval behaviors. <!-- end of auto-generated comment: release notes by coderabbit.ai --> Co-authored-by: Kumar Ujjawal <[email protected]>
2 parents e782c48 + 5912be2 commit 5757ec4

File tree

4 files changed

+304
-0
lines changed

4 files changed

+304
-0
lines changed

.code-samples.meilisearch.yaml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,23 @@ reset_typo_tolerance_1: |-
449449
.reset_typo_tolerance()
450450
.await
451451
.unwrap();
452+
get_all_batches_1: |-
453+
let mut query = meilisearch_sdk::batches::BatchesQuery::new(&client);
454+
query.with_limit(20);
455+
let batches: meilisearch_sdk::batches::BatchesResults =
456+
client.get_batches_with(&query).await.unwrap();
457+
get_batch_1: |-
458+
let uid: u32 = 42;
459+
let batch: meilisearch_sdk::batches::Batch = client
460+
.get_batch(uid)
461+
.await
462+
.unwrap();
463+
get_all_batches_paginating_1: |-
464+
let mut query = meilisearch_sdk::batches::BatchesQuery::new(&client);
465+
query.with_limit(2);
466+
query.with_from(40);
467+
let batches: meilisearch_sdk::batches::BatchesResults =
468+
client.get_batches_with(&query).await.unwrap();
452469
get_stop_words_1: |-
453470
let stop_words: Vec<String> = client
454471
.index("movies")

src/batches.rs

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
use serde::{Deserialize, Serialize};
2+
use time::OffsetDateTime;
3+
4+
use 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")]
11+
pub 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]
40+
pub 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")]
52+
pub 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")]
65+
pub 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+
76+
impl<'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)]
105+
mod 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+
}

src/client.rs

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1112,6 +1112,92 @@ impl<Http: HttpClient> Client<Http> {
11121112
Ok(tasks)
11131113
}
11141114

1115+
/// List batches using the Batches API.
1116+
///
1117+
/// See: https://www.meilisearch.com/docs/reference/api/batches
1118+
///
1119+
/// # Example
1120+
///
1121+
/// ```
1122+
/// # use meilisearch_sdk::client::Client;
1123+
/// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1124+
/// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1125+
/// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1126+
/// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1127+
/// let batches = client.get_batches().await.unwrap();
1128+
/// # let _ = batches;
1129+
/// # });
1130+
/// ```
1131+
pub async fn get_batches(&self) -> Result<crate::batches::BatchesResults, Error> {
1132+
let res = self
1133+
.http_client
1134+
.request::<(), (), crate::batches::BatchesResults>(
1135+
&format!("{}/batches", self.host),
1136+
Method::Get { query: () },
1137+
200,
1138+
)
1139+
.await?;
1140+
Ok(res)
1141+
}
1142+
1143+
/// List batches with pagination filters.
1144+
///
1145+
/// # Example
1146+
///
1147+
/// ```
1148+
/// # use meilisearch_sdk::{client::Client, batches::BatchesQuery};
1149+
/// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1150+
/// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1151+
/// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1152+
/// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1153+
/// let mut query = BatchesQuery::new(&client);
1154+
/// query.with_limit(1);
1155+
/// let batches = client.get_batches_with(&query).await.unwrap();
1156+
/// # let _ = batches;
1157+
/// # });
1158+
/// ```
1159+
pub async fn get_batches_with(
1160+
&self,
1161+
query: &crate::batches::BatchesQuery<'_, Http>,
1162+
) -> Result<crate::batches::BatchesResults, Error> {
1163+
let res = self
1164+
.http_client
1165+
.request::<&crate::batches::BatchesQuery<'_, Http>, (), crate::batches::BatchesResults>(
1166+
&format!("{}/batches", self.host),
1167+
Method::Get { query },
1168+
200,
1169+
)
1170+
.await?;
1171+
Ok(res)
1172+
}
1173+
1174+
/// Get a single batch by its uid.
1175+
///
1176+
/// # Example
1177+
///
1178+
/// ```
1179+
/// # use meilisearch_sdk::client::Client;
1180+
/// # let MEILISEARCH_URL = option_env!("MEILISEARCH_URL").unwrap_or("http://localhost:7700");
1181+
/// # let MEILISEARCH_API_KEY = option_env!("MEILISEARCH_API_KEY").unwrap_or("masterKey");
1182+
/// # tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async {
1183+
/// # let client = Client::new(MEILISEARCH_URL, Some(MEILISEARCH_API_KEY)).unwrap();
1184+
/// let uid: u32 = 42;
1185+
/// let batch = client.get_batch(uid).await.unwrap();
1186+
/// # let _ = batch;
1187+
/// # });
1188+
/// ```
1189+
pub async fn get_batch(&self, uid: u32) -> Result<crate::batches::Batch, Error> {
1190+
let res = self
1191+
.http_client
1192+
.request::<(), (), crate::batches::Batch>(
1193+
&format!("{}/batches/{}", self.host, uid),
1194+
Method::Get { query: () },
1195+
200,
1196+
)
1197+
.await?;
1198+
Ok(res)
1199+
}
1200+
11151201
/// Generates a new tenant token.
11161202
///
11171203
/// # Example

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,8 @@
230230
#![warn(clippy::all)]
231231
#![allow(clippy::needless_doctest_main)]
232232

233+
/// Module to interact with the Batches API.
234+
pub mod batches;
233235
/// Module containing the [`Client`](client::Client) struct.
234236
pub mod client;
235237
/// Module representing the [documents] structures.

0 commit comments

Comments
 (0)