fix(search): surface failed Meilisearch tasks; make ensure_index idempotent

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-02 11:50:58 +02:00
parent dc903989f7
commit b8d198f150
2 changed files with 59 additions and 6 deletions
+41 -6
View File
@@ -2,6 +2,7 @@
use db::Db;
use domain::{CatalogueObject, ObjectId};
use meilisearch_sdk::tasks::Task;
use serde::{Deserialize, Serialize};
/// Errors from the search subsystem.
@@ -38,6 +39,16 @@ pub struct SearchClient {
index_uid: String,
}
/// Turn a completed task into an error if Meilisearch rejected it.
fn check_task(task: Task) -> Result<(), SearchError> {
match task {
Task::Failed { content } => Err(SearchError::Meili(
meilisearch_sdk::errors::Error::Meilisearch(content.error),
)),
_ => Ok(()),
}
}
impl SearchClient {
pub fn connect(url: &str, api_key: &str, index_uid: &str) -> Result<Self, SearchError> {
let client = meilisearch_sdk::client::Client::new(url, Some(api_key))?;
@@ -49,41 +60,61 @@ impl SearchClient {
}
pub async fn ensure_index(&self) -> Result<(), SearchError> {
self.client
let task = self
.client
.create_index(&self.index_uid, Some("id"))
.await?
.wait_for_completion(&self.client, None, None)
.await?;
self.client
// Tolerate "index already exists"; surface any other task failure.
if let Task::Failed { content } = &task {
if content.error.error_code != meilisearch_sdk::errors::ErrorCode::IndexAlreadyExists {
return Err(SearchError::Meili(
meilisearch_sdk::errors::Error::Meilisearch(content.error.clone()),
));
}
}
// set_filterable_attributes is idempotent on an existing index
let task = self
.client
.index(&self.index_uid)
.set_filterable_attributes(["visibility"])
.await?
.wait_for_completion(&self.client, None, None)
.await?;
check_task(task)?;
Ok(())
}
pub async fn index_object(&self, doc: &SearchDocument) -> Result<(), SearchError> {
self.client
let task = self
.client
.index(&self.index_uid)
.add_or_replace(std::slice::from_ref(doc), Some("id"))
.await?
.wait_for_completion(&self.client, None, None)
.await?;
check_task(task)?;
Ok(())
}
pub async fn remove_object(&self, id: ObjectId) -> Result<(), SearchError> {
self.client
let task = self
.client
.index(&self.index_uid)
.delete_document(id.to_string())
.await?
.wait_for_completion(&self.client, None, None)
.await?;
check_task(task)?;
Ok(())
}
@@ -113,12 +144,14 @@ impl SearchClient {
pub async fn reindex_all(&self, db: &Db) -> Result<(), SearchError> {
let index = self.client.index(&self.index_uid);
index
let task = index
.delete_all_documents()
.await?
.wait_for_completion(&self.client, None, None)
.await?;
check_task(task)?;
let objects = db::catalog::list_objects(db.pool()).await?;
let mut docs = Vec::with_capacity(objects.len());
@@ -128,11 +161,13 @@ impl SearchClient {
}
if !docs.is_empty() {
index
let task = index
.add_or_replace(&docs, Some("id"))
.await?
.wait_for_completion(&self.client, None, None)
.await?;
check_task(task)?;
}
Ok(())