Features
- Fully typed, async API built on
reqwest.
- Composable, immutable query builder.
- Support for all Searchcraft query kinds (fuzzy, exact, dynamic, term, more-like-this).
- Full index, document, federation, synonym, and stopword management.
- Streaming AI search summaries over Server-Sent Events.
- Analytics via the Measure API, including the dashboard reports.
rustls by default, with an optional native-tls backend.
- API keys held in
secrecy wrappers so they are not
logged by accident.
Installation
Add the crate to your project:
cargo add searchcraft
Or add it manually to your Cargo.toml:
[dependencies]
searchcraft = "0.1"
Quick start
use searchcraft::SearchcraftClient;
use searchcraft::search::query::QueryBuilder;
#[tokio::main]
async fn main() -> Result<(), searchcraft::Error> {
let client = SearchcraftClient::new(
"https://my-instance.searchcraft.io",
Some("sc-read-key"),
None::<String>,
)?;
let request = QueryBuilder::fuzzy()
.term("laptop")
.limit(10)
.build_request();
let response = client
.search_index::<serde_json::Value>("products", &request)
.await?;
for hit in &response.data.hits {
match hit.score {
Some(score) => println!("{} (score: {score:.2})", hit.document_id),
None => println!("{}", hit.document_id),
}
}
Ok(())
}
Feature flags
| Feature |
Default |
Description |
rustls |
✅ |
Use rustls for TLS |
native-tls |
❌ |
Use the platform's native TLS |
To use native TLS instead of rustls:
[dependencies]
searchcraft = { version = "0.1", default-features = false, features = ["native-tls"] }
API overview
Search
Search a single index or a federation of indices:
# async fn example() -> Result<(), searchcraft::Error> {
# let client = searchcraft::SearchcraftClient::new("https://x.io", Some("k"), None::<String>)?;
use searchcraft::search::query::QueryBuilder;
let request = QueryBuilder::fuzzy()
.term("laptop")
.and("gaming")
.not("refurbished")
.limit(20)
.build_request();
let results = client.search_index::<serde_json::Value>("products", &request).await?;
let results = client.search_federation::<serde_json::Value>("global", &request).await?;
# Ok(())
# }
The QueryBuilder supports fuzzy, exact, and dynamic query modes, field queries,
range queries, comparisons, boolean composition (and/or/not), grouping,
pagination (limit/offset), and sorting (order_by). limit is capped at 200
and is checked client-side before the request is sent.
AI search summaries
Engine 0.10.0+ can stream an LLM-generated summary of a query's results over
Server-Sent Events. Check get_index_capabilities first — the endpoint
requires AI features to be enabled on the index and a key with summary
permissions:
# async fn example() -> Result<(), searchcraft::Error> {
# let client = searchcraft::SearchcraftClient::new("https://x.io", Some("k"), None::<String>)?;
use searchcraft::search::query::QueryBuilder;
use searchcraft::search::types::SummaryStreamEvent;
use searchcraft::search::StreamExt;
let capabilities = client.get_index_capabilities("products").await?;
if capabilities.ai.enabled {
let request = QueryBuilder::fuzzy().term("laptop").build_request();
let mut stream = client.search_summary("products", &request).await?;
while let Some(event) = stream.next().await {
match event {
SummaryStreamEvent::Delta(d) => print!("{}", d.content),
SummaryStreamEvent::Done(d) => println!("\n({} results)", d.results_count),
SummaryStreamEvent::Error(e) => eprintln!("summary failed: {}", e.message),
SummaryStreamEvent::Metadata(_) => {}
}
}
}
# Ok(())
# }
Malformed frames and mid-stream transport failures arrive as Error events
rather than ending the stream. The configured timeout bounds only connection
setup for this endpoint, so a slow generation is never cut short.
Index management
# async fn example() -> Result<(), searchcraft::Error> {
# let client = searchcraft::SearchcraftClient::new("https://x.io", Some("k"), Some("w"))?;
use std::collections::HashMap;
use searchcraft::admin::types::{FieldConfig, FieldType, IndexConfig};
let indices = client.list_indices().await?;
let config = IndexConfig {
language: Some("en".into()),
search_fields: Some(vec!["title".into()]),
fields: Some(HashMap::from([(
"title".to_string(),
FieldConfig {
stored: Some(true),
required: Some(true),
..FieldConfig::new(FieldType::Text)
},
)])),
..Default::default()
};
client.create_index("my-index", &config).await?;
let patch = IndexConfig { auto_commit_delay: Some(5000), ..Default::default() };
client.update_index("my-index", &patch).await?;
let stats = client.get_index_stats("my-index").await?;
println!("Documents: {}", stats.document_count);
# Ok(())
# }
Document ingestion
# async fn example() -> Result<(), searchcraft::Error> {
# let client = searchcraft::SearchcraftClient::new("https://x.io", Some("k"), Some("w"))?;
let doc = serde_json::json!({
"id": "doc-1",
"title": "Gaming Laptop",
"price": 999.99
});
client.insert_document("products", &doc).await?;
let docs = vec![doc];
client.batch_insert_documents("products", &docs).await?;
# Ok(())
# }
Additional APIs
| Module |
Endpoints |
| Federations |
List (all, by organization), get, create, update, delete, indices, stats |
| Auth keys |
List (all, by application/organization/federation/index), get, create, update, delete, permission check (self-hosted) |
| Capabilities |
Per-index AI capability reporting |
| Health |
Health check |
| Stopwords |
Get, get defaults, add, remove, clear |
| Synonyms |
Get, add, remove, clear |
| Transactions |
Commit, rollback index transactions |
| Measure |
Status, send events, dashboard summary/conversion/usage |
This crate targets Searchcraft engine 0.11.0. Every endpoint the engine
exposes at that version is covered, except POST /index/:index/hint, which is
marked work-in-progress server-side.
Configuration
use std::time::Duration;
use searchcraft::Config;
let config = Config::new(
"https://my-instance.searchcraft.io",
Some("read-key"),
Some("ingest-key"),
)?
.with_admin_key("admin-key")
.with_timeout(Duration::from_secs(10))
.with_header("X-Custom", "value");
let client = searchcraft::SearchcraftClient::from_config(config)?;
# Ok::<(), searchcraft::Error>(())
Error handling
All fallible operations return searchcraft::error::Result<T>. The Error
enum covers configuration mistakes, authentication failures (401/403),
not-found (404), validation errors (400), other API errors, and network
problems:
# async fn example() -> Result<(), searchcraft::Error> {
# let client = searchcraft::SearchcraftClient::new("https://x.io", Some("k"), None::<String>)?;
# let request = searchcraft::search::query::QueryBuilder::fuzzy().term("x").build_request();
match client.search_index::<serde_json::Value>("products", &request).await {
Ok(response) => println!("Found {} results", response.data.count),
Err(searchcraft::Error::NotFound(msg)) => eprintln!("Index not found: {msg}"),
Err(searchcraft::Error::Authentication { status, .. }) => eprintln!("Auth failed ({status})"),
Err(e) if e.is_retryable() => eprintln!("Transient error, retry: {e}"),
Err(e) => eprintln!("Error: {e}"),
}
# Ok(())
# }
Minimum supported Rust version
The MSRV is 1.75 (Rust 2021 edition).
Contributing
Please file issues in the
Searchcraft Issues
repository.
Maintainers: see PUBLISHING.md for the release process.
License
Licensed under the Apache License, Version 2.0. See LICENSE for the
full text.
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this crate by you shall be licensed as above, without any
additional terms or conditions.