HeroIndex Client

A Rust client library for HeroIndex, a high-performance full-text search server built on Tantivy.
Features
- Async/Await - Built on Tokio for async operations
- Type-Safe - Strongly typed responses
- Simple API - Intuitive method names matching RPC calls
- Connection Pooling Ready - Each client maintains its own connection state
Installation
Add to your Cargo.toml:
[dependencies]
heroindex_client = "0.1"
tokio = { version = "1", features = ["full"] }
serde_json = "1"
Quick Start
use heroindex_client::HeroIndexClient;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), heroindex_client::Error> {
let mut client = HeroIndexClient::connect("/tmp/heroindex.sock").await?;
let ping = client.ping().await?;
println!("Server status: {} (v{})", ping.status, ping.version);
Ok(())
}
Complete Example
use heroindex_client::HeroIndexClient;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), heroindex_client::Error> {
let mut client = HeroIndexClient::connect("/tmp/heroindex.sock").await?;
client.db_create("products", json!({
"fields": [
{"name": "id", "type": "str", "stored": true, "indexed": true},
{"name": "name", "type": "text", "stored": true, "indexed": true},
{"name": "description", "type": "text", "stored": true, "indexed": true},
{"name": "price", "type": "f64", "stored": true, "indexed": true, "fast": true},
{"name": "in_stock", "type": "bool", "stored": true, "indexed": true}
]
})).await?;
client.db_select("products").await?;
client.doc_add(json!({
"id": "prod-001",
"name": "Wireless Mouse",
"description": "Ergonomic wireless mouse with USB receiver",
"price": 29.99,
"in_stock": true
})).await?;
client.doc_add_batch(vec![
json!({"id": "prod-002", "name": "Mechanical Keyboard", "description": "RGB mechanical keyboard with Cherry MX switches", "price": 149.99, "in_stock": true}),
json!({"id": "prod-003", "name": "USB-C Hub", "description": "7-port USB-C hub with HDMI output", "price": 49.99, "in_stock": false}),
json!({"id": "prod-004", "name": "Webcam HD", "description": "1080p HD webcam with microphone", "price": 79.99, "in_stock": true}),
]).await?;
client.commit().await?;
client.reload().await?;
let results = client.search(
json!({"type": "match", "field": "description", "value": "wireless"}),
10, 0
).await?;
println!("Found {} products:", results.total_hits);
for hit in &results.hits {
println!(" - {} (score: {:.2})",
hit.doc["name"].as_str().unwrap_or("?"),
hit.score
);
}
Ok(())
}
API Reference
Connection
let mut client = HeroIndexClient::connect("/tmp/heroindex.sock").await?;
Server Operations
let ping = client.ping().await?;
println!("Status: {}, Version: {}", ping.status, ping.version);
let stats = client.stats().await?;
println!("Databases: {}, Total docs: {}", stats.databases, stats.total_docs);
let schema = client.discover().await?;
Database Operations
let list = client.db_list().await?;
for db in &list.databases {
println!("{}: {} docs", db.name, db.doc_count);
}
client.db_create("mydb", json!({
"fields": [
{"name": "title", "type": "text", "stored": true, "indexed": true}
]
})).await?;
client.db_select("mydb").await?;
let info = client.db_info().await?;
println!("Doc count: {}, Segments: {}", info.doc_count, info.segment_count);
client.db_delete("mydb").await?;
Schema Operations
let schema = client.schema().await?;
for field in &schema.fields {
println!("{}: {} (stored: {})", field.name, field.field_type, field.stored);
}
Document Operations
client.doc_add(json!({"title": "Hello World"})).await?;
client.doc_add_batch(vec![
json!({"title": "Doc 1"}),
json!({"title": "Doc 2"}),
json!({"title": "Doc 3"}),
]).await?;
client.doc_delete("id", json!("doc-to-delete")).await?;
client.commit().await?;
client.reload().await?;
Search Operations
let results = client.search(
json!({"type": "match", "field": "content", "value": "search terms"}),
10, 0 ).await?;
println!("Found {} results in {}ms", results.total_hits, results.took_ms);
for hit in &results.hits {
println!("Score: {:.2}, Doc: {:?}", hit.score, hit.doc);
}
let count = client.count(json!({"type": "all"})).await?;
println!("Total documents: {}", count.count);
Query Examples
Match Query (Full-Text Search)
client.search(json!({
"type": "match",
"field": "content",
"value": "rust programming"
}), 10, 0).await?;
Fuzzy Query (Typo-Tolerant)
client.search(json!({
"type": "fuzzy",
"field": "title",
"value": "helo",
"distance": 1 }), 10, 0).await?;
Phrase Query (Exact Phrase)
client.search(json!({
"type": "phrase",
"field": "content",
"value": "exact phrase match"
}), 10, 0).await?;
Range Query (Numeric)
client.search(json!({
"type": "range",
"field": "price",
"gte": 50.0,
"lt": 100.0
}), 10, 0).await?;
Boolean Query (Combined)
client.search(json!({
"type": "boolean",
"must": [
{"type": "match", "field": "category", "value": "electronics"}
],
"should": [
{"type": "match", "field": "title", "value": "premium"}
],
"must_not": [
{"type": "term", "field": "status", "value": "discontinued"}
]
}), 10, 0).await?;
Prefix Query
client.search(json!({
"type": "prefix",
"field": "title",
"value": "auto"
}), 10, 0).await?;
Term Query (Exact Match)
client.search(json!({
"type": "term",
"field": "id",
"value": "prod-001"
}), 10, 0).await?;
Error Handling
use heroindex_client::{Error, error_codes};
match client.db_select("nonexistent").await {
Ok(_) => println!("Selected"),
Err(Error::Rpc { code, message }) => {
if code == error_codes::DATABASE_NOT_FOUND {
println!("Database not found: {}", message);
}
}
Err(e) => println!("Other error: {}", e),
}
Response Types
All methods return strongly-typed responses:
PingResponse - status, version
ServerStats - uptime_secs, databases, total_docs
DatabaseList - databases: Vec<DatabaseInfo>
DatabaseInfo - name, doc_count, size_bytes, segment_count
SchemaInfo - fields: Vec<FieldInfo>
SearchResult - total_hits, hits: Vec<SearchHit>, took_ms
SearchHit - score, doc: serde_json::Value
CountResult - count
OpResult - success, opstamp
Related Crates
License
MIT License