refactor(search): document+guard visibility filter precondition; drop redundant dev-dep

- Remove serde_json from [dev-dependencies] (already in [dependencies])
- Add debug_assert! in search_objects visibility filter as defense-in-depth
- Extend search_objects doc-comment with visibility precondition
- Clarify estimated_total_hits.unwrap_or(0) is safe under offset/limit pagination
- Add brief comment on with_crop_length(20) explaining ~20-word context window

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-04 11:19:46 +02:00
parent 84c4c2807b
commit 9b1771d584
2 changed files with 17 additions and 2 deletions
-1
View File
@@ -16,6 +16,5 @@ serde_json.workspace = true
[dev-dependencies] [dev-dependencies]
tokio.workspace = true tokio.workspace = true
uuid.workspace = true uuid.workspace = true
serde_json.workspace = true
sqlx.workspace = true sqlx.workspace = true
domain = { path = "../domain" } domain = { path = "../domain" }
+17 -1
View File
@@ -176,6 +176,12 @@ impl SearchClient {
/// Full-text query returning display-ready hits with highlighted snippets and the /// Full-text query returning display-ready hits with highlighted snippets and the
/// estimated total match count. `visibility`, when set, filters on the indexed /// estimated total match count. `visibility`, when set, filters on the indexed
/// `visibility` attribute. Pagination is offset/limit. /// `visibility` attribute. Pagination is offset/limit.
///
/// # Preconditions
///
/// When `visibility` is `Some`, the value must be one of `"draft"`, `"internal"`, or
/// `"public"`. The caller owns this validation (the API layer enforces it); this
/// method `debug_assert!`s the constraint as defense-in-depth.
pub async fn search_objects( pub async fn search_objects(
&self, &self,
query: &str, query: &str,
@@ -185,7 +191,14 @@ impl SearchClient {
) -> Result<SearchResults, SearchError> { ) -> Result<SearchResults, SearchError> {
let index = self.client.index(&self.index_uid); let index = self.client.index(&self.index_uid);
let filter = visibility.map(|v| format!("visibility = \"{v}\"")); let filter = visibility.map(|v| {
debug_assert!(
matches!(v, "draft" | "internal" | "public"),
"visibility filter must be a known value; got {v:?}"
);
format!("visibility = \"{v}\"")
});
let highlight: &[&str] = &["object_name", "brief_description", "fields_text"]; let highlight: &[&str] = &["object_name", "brief_description", "fields_text"];
let crop: &[(&str, Option<usize>)] = &[("brief_description", None), ("fields_text", None)]; let crop: &[(&str, Option<usize>)] = &[("brief_description", None), ("fields_text", None)];
@@ -196,6 +209,7 @@ impl SearchClient {
.with_limit(limit) .with_limit(limit)
.with_attributes_to_highlight(Selectors::Some(highlight)) .with_attributes_to_highlight(Selectors::Some(highlight))
.with_attributes_to_crop(Selectors::Some(crop)) .with_attributes_to_crop(Selectors::Some(crop))
// ~20 words gives enough catalogue-description context around a match.
.with_crop_length(20) .with_crop_length(20)
.with_highlight_pre_tag(HL_PRE) .with_highlight_pre_tag(HL_PRE)
.with_highlight_post_tag(HL_POST); .with_highlight_post_tag(HL_POST);
@@ -226,6 +240,8 @@ impl SearchClient {
Ok(SearchResults { Ok(SearchResults {
hits, hits,
// estimated_total_hits is always present for offset/limit pagination;
// None only under page-based mode, which we don't use.
estimated_total: results.estimated_total_hits.unwrap_or(0), estimated_total: results.estimated_total_hits.unwrap_or(0),
}) })
} }