Expand description
§searchez — a searchable-model layer for Rust
The Rust ecosystem has excellent search engines (tantivy, meilisearch,
opensearch) and nothing above them: no way to say “this model is
searchable”, keep its index in sync as records change, and turn results back
into records. Every project rewrites the same to_document(), the same
post-save index call, and the same bespoke backfill binary. searchez is that
missing layer.
Three pieces:
Searchable— a type declares its index, its id, and its document.SearchEngine— index / remove / reindex / search, over a pluggableBackend. The lifecycle spine.MemoryBackend— a real BM25-ranked in-memory engine, so it all runs and tests with no external service. Swap in a server backend later; nothing above theBackendtrait changes.
use searchez::{Searchable, SearchEngine, MemoryBackend, Document, Query};
struct Product { id: u64, name: String, in_stock: bool }
impl Searchable for Product {
fn index_name() -> &'static str { "products" }
fn search_id(&self) -> String { self.id.to_string() }
fn to_document(&self) -> Document {
Document::new().field("name", self.name.clone()).field("in_stock", self.in_stock)
}
}
let engine = SearchEngine::new(MemoryBackend::new());
engine.index(&Product { id: 1, name: "Dark Roast Coffee".into(), in_stock: true }).await?;
let hits = engine.search::<Product>(Query::text("coffee").filter("in_stock", true)).await?;
assert_eq!(hits[0].id, "1");§Keeping the index in sync
Rust has no universal ORM callback, so sync points are explicit — call
SearchEngine::index after a create/update and SearchEngine::remove
after a delete. In exchange the boilerplate (document mapping, backend call,
bulk backfill) is done for you. SearchEngine::reindex rebuilds an index
from a full set of records (the backfill job).
§Hydration
A Hit carries the stored Document, which covers displaying results
directly. When you need the full database record (columns or associations
you didn’t index), take hit.id, load the rows from your database, and
preserve the hit order — see the README for the pattern.
Structs§
- Document
- A flat bag of fields describing one record. Build it fluently, or straight
from a
serde_json::json!({...})object. - Filter
- An exact-match constraint on a single field, ANDed with the others.
- Hit
- One matching record: its id, relevance score, and the stored document (so the common case needs no second lookup; see the crate docs on hydrating full DB records when you need more than was indexed).
- Index
Doc - A document ready to index: its id plus the fields.
- Meilisearch
Backend - A
Backendtalking to a Meilisearch server. - Memory
Backend - In-memory, BM25-ranked search backend. Construct with
MemoryBackend::new. - Query
- A search request: optional full-text, zero or more exact filters, and a page.
- Search
Engine - The handle you index and search through. Holds a
Backend; clone-free, pass it by reference (wrap inArcto share across tasks).
Enums§
- Search
Error - What can go wrong talking to a search backend.
Constants§
- DEFAULT_
LIMIT - The default page size when a query doesn’t set one — enough for a first page, bounded so an unqualified search can’t return an entire index.
Traits§
- Backend
- A storage-and-retrieval engine for search documents. Methods are keyed by
index(Searchkick’s per-model index) so one backend serves many models. - Searchable
- A type that can be indexed and searched.