Skip to main content

Crate searchez

Crate searchez 

Source
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:

  1. Searchable — a type declares its index, its id, and its document.
  2. SearchEngine — index / remove / reindex / search, over a pluggable Backend. The lifecycle spine.
  3. 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 the Backend trait 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).
IndexDoc
A document ready to index: its id plus the fields.
MeilisearchBackend
A Backend talking to a Meilisearch server.
MemoryBackend
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.
SearchEngine
The handle you index and search through. Holds a Backend; clone-free, pass it by reference (wrap in Arc to share across tasks).

Enums§

SearchError
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.

Type Aliases§

Result