···
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
+
#[derive(Debug, Error)]
+
pub enum TurbopufferError {
+
#[error("query too long: {message}")]
+
QueryTooLong { message: String },
+
#[error("turbopuffer API error: {0}")]
+
#[error("request failed: {0}")]
+
RequestFailed(#[from] reqwest::Error),
+
Other(#[from] anyhow::Error),
+
#[derive(Debug, Deserialize)]
+
struct TurbopufferErrorResponse {
#[derive(Debug, Serialize)]
pub struct QueryRequest {
···
.context(format!("failed to parse query response: {}", body))
+
pub async fn bm25_query(&self, query_text: &str, top_k: usize) -> Result<QueryResponse, TurbopufferError> {
"https://api.turbopuffer.com/v1/vectors/{}/query",
···
"include_attributes": ["url", "name", "filename"],
+
if let Ok(pretty) = serde_json::to_string_pretty(&request) {
+
log::debug!("turbopuffer BM25 query request: {}", pretty);
···
.header("Authorization", format!("Bearer {}", self.api_key))
if !response.status().is_success() {
let status = response.status();
let body = response.text().await.unwrap_or_default();
+
// try to parse turbopuffer error response
+
if let Ok(error_resp) = serde_json::from_str::<TurbopufferErrorResponse>(&body) {
+
// check if it's a query length error
+
if error_resp.error.contains("too long") && error_resp.error.contains("max 1024") {
+
return Err(TurbopufferError::QueryTooLong {
+
message: error_resp.error,
+
return Err(TurbopufferError::ApiError(format!(
+
"turbopuffer BM25 query failed with status {}: {}",
+
let body = response.text().await
+
.map_err(|e| TurbopufferError::Other(anyhow::anyhow!("failed to read response body: {}", e)))?;
log::debug!("turbopuffer BM25 response: {}", body);
let parsed: QueryResponse = serde_json::from_str(&body)
+
.map_err(|e| TurbopufferError::Other(anyhow::anyhow!("failed to parse BM25 query response: {}", e)))?;
// DEBUG: log first result to see what BM25 returns
if let Some(first) = parsed.first() {