Production-ready async Rust client for the EdgeQuake RAG API.
- Async/await — built on
reqwest+tokio - Builder pattern — fluent client configuration
- Thread-safe —
Clone + Send + SyncviaArcinternals - Automatic retry — exponential backoff on 429/5xx
- Multi-tenant — first-class tenant + workspace headers
- 22 resources — full API coverage
- Strong types — typed request/response structs with
serde - Rich errors —
thiserrorvariants withstatus_code()+is_retryable()
Add to your Cargo.toml:
[dependencies]
edgequake-sdk = { git = "https://github.com/raphaelmansuy/edgequake", path = "sdks/rust" }
tokio = { version = "1", features = ["full"] }use edgequake_sdk::EdgeQuakeClient;
#[tokio::main]
async fn main() -> edgequake_sdk::Result<()> {
let client = EdgeQuakeClient::builder()
.base_url("http://localhost:8080")
.api_key("my-api-key")
.build()?;
let health = client.health().check().await?;
println!("Status: {}", health.status);
Ok(())
}| Method | Description | Default |
|---|---|---|
.base_url(url) |
API server URL | http://localhost:8080 |
.api_key(key) |
API key (X-API-Key header) | None |
.bearer_token(token) |
JWT token (Authorization: Bearer) | None |
.tenant_id(id) |
Tenant ID header | None |
.workspace_id(id) |
Workspace ID header | None |
.timeout(duration) |
Request timeout | 30s |
.connect_timeout(duration) |
Connection timeout | 5s |
.max_retries(n) |
Max retry attempts on 429/5xx | 3 |
.user_agent(ua) |
Custom User-Agent string | edgequake-rust/{version} |
use std::time::Duration;
let client = EdgeQuakeClient::builder()
.base_url("https://api.example.com")
.api_key("sk-...")
.tenant_id("tenant-123")
.workspace_id("ws-456")
.timeout(Duration::from_secs(60))
.max_retries(5)
.build()?;let health = client.health().check().await?;
println!("{} v{}", health.status, health.version.unwrap_or_default());use edgequake_sdk::types::documents::UploadDocumentRequest;
// List documents
let docs = client.documents().list().await?;
// Get document by ID
let doc = client.documents().get("doc-id").await?;
// Upload text content (`POST /api/v1/documents`)
let mut up = UploadDocumentRequest::text("hello");
up.title = Some("test".into());
let result = client.documents().upload(&up).await?;
// Track processing status
let status = client.documents().track("track-id").await?;
println!("progress: {:?}", status.progress);
// Delete document
client.documents().delete("doc-id").await?;// Get full graph
let graph = client.graph().get().await?;
println!("nodes: {}, edges: {}", graph.nodes.len(), graph.edges.len());
// Search nodes
let results = client.graph().search("Alice").await?;use edgequake_sdk::types::graph::CreateEntityRequest;
// List entities
let entities = client.entities().list().await?;
// Get entity detail
let entity = client.entities().get("ALICE").await?;
// Create entity
let req = CreateEntityRequest {
entity_name: "BOB".into(),
entity_type: "person".into(),
description: "A person".into(),
source_id: "manual".into(),
metadata: None,
};
let result = client.entities().create(&req).await?;
// Merge entities
let merged = client.entities().merge("Alice", "ALICE").await?;
// Delete entity
client.entities().delete("ALICE").await?;use edgequake_sdk::types::graph::CreateRelationshipRequest;
// List relationships
let rels = client.relationships().list().await?;
// Create relationship
let req = CreateRelationshipRequest {
source: "Alice".into(),
target: "Bob".into(),
relationship_type: "knows".into(),
weight: Some(0.9),
description: Some("friends".into()),
};
let rel = client.relationships().create(&req).await?;
// Delete relationship
client.relationships().delete("rel-id").await?;use edgequake_sdk::types::query::{QueryRequest, QueryMode};
let req = QueryRequest {
query: "What is EdgeQuake?".into(),
mode: Some(QueryMode::Hybrid),
top_k: Some(5),
stream: None,
only_need_context: None,
};
let response = client.query().execute(&req).await?;
println!("Answer: {}", response.answer.unwrap_or_default());use edgequake_sdk::types::chat::{ChatCompletionRequest, ChatMessage};
let req = ChatCompletionRequest {
messages: vec![ChatMessage {
role: "user".into(),
content: "Hello!".into(),
}],
model: Some("gpt-4".into()),
temperature: Some(0.7),
max_tokens: None,
stream: None,
};
let response = client.chat().completions(&req).await?;use edgequake_sdk::types::auth::{LoginRequest, RefreshRequest};
// Login
let token = client.auth().login(&LoginRequest {
username: "admin".into(),
password: "secret".into(),
}).await?;
// Get current user
let me = client.auth().me().await?;
// Refresh token
let new_token = client.auth().refresh(&RefreshRequest {
refresh_token: token.refresh_token.unwrap(),
}).await?;use edgequake_sdk::types::auth::CreateUserRequest;
let users = client.users().list().await?;
let user = client.users().create(&CreateUserRequest {
username: "bob".into(),
email: "bob@example.com".into(),
password: "secret".into(),
role: None,
}).await?;
let user = client.users().get("user-id").await?;
client.users().delete("user-id").await?;let keys = client.api_keys().list().await?;
let key = client.api_keys().create("my-key").await?;
client.api_keys().revoke("key-id").await?;use edgequake_sdk::types::auth::CreateTenantRequest;
let tenants = client.tenants().list().await?;
let tenant = client.tenants().create(&CreateTenantRequest {
name: "Acme Corp".into(),
slug: Some("acme".into()),
}).await?;
let tenant = client.tenants().get("tenant-id").await?;
client.tenants().delete("tenant-id").await?;use edgequake_sdk::types::conversations::*;
let page = client.conversations().list().await?;
println!("{} conversations", page.items.len());
let convo = client.conversations().create(&CreateConversationRequest {
title: Some("Discussion".into()),
folder_id: None,
}).await?;
let detail = client.conversations().get("conv-id").await?;
println!("title: {:?}", detail.conversation.title);
// Send a message
let msg = client.conversations().create_message("conv-id", &CreateMessageRequest {
role: "user".into(),
content: "Hello!".into(),
parent_id: None,
stream: None,
}).await?;
// Share conversation
let share = client.conversations().share("conv-id").await?;
// Bulk delete
let result = client.conversations().bulk_delete(&["c1".into(), "c2".into()]).await?;
println!("affected: {}", result.affected);
// Pin/unpin
client.conversations().pin("conv-id").await?;
client.conversations().unpin("conv-id").await?;
// Delete
client.conversations().delete("conv-id").await?;use edgequake_sdk::types::conversations::CreateFolderRequest;
let folders = client.folders().list().await?;
let folder = client.folders().create(&CreateFolderRequest {
name: "Work".into(),
parent_id: None,
}).await?;
client.folders().delete("folder-id").await?;let tasks = client.tasks().list().await?;
let task = client.tasks().get("track-id").await?;
client.tasks().cancel("track-id").await?;let status = client.pipeline().status().await?;
println!("busy: {}, pending: {}", status.is_busy, status.pending_tasks);
let metrics = client.pipeline().metrics().await?;
println!("queue: {}", metrics.queue_depth);let summary = client.costs().summary().await?;
println!("total: ${:.2}", summary.total_cost_usd);
let history = client.costs().history().await?;
let budget = client.costs().budget().await?;// Chunk detail and chunk lineage only (no document chunk listing in API)
let chunk = client.chunks().get("chunk-id").await?;
let chunk_lineage = client.chunks().get_lineage("chunk-id").await?;let prov = client.provenance().for_entity("ALICE").await?;
let lineage = client.provenance().lineage("ALICE").await?;let catalog = client.models().list().await?;
let provider = client.models().current_provider().await?;
let health = client.models().providers_health().await?;
let llm = client.models().list_llm().await?;use edgequake_sdk::types::workspaces::CreateWorkspaceRequest;
let workspaces = client.workspaces().list("tenant-id").await?;
let ws = client.workspaces().create("tenant-id", &CreateWorkspaceRequest {
name: "production".into(),
slug: None,
description: None,
}).await?;
let stats = client.workspaces().stats("ws-id").await?;let updated = client.admin().patch_tenant_quota("tenant-id", 10).await?;
let defaults = client.admin().get_server_defaults().await?;let cfg = client.effective_config().get().await?;let progress = client.pdf().progress("track-id").await?;
let content = client.pdf().content("pdf-id").await?;
let batch = client.pdf().upload_batch(
vec![
(std::fs::read("a.pdf")?, "a.pdf".to_string()),
(std::fs::read("b.pdf")?, "b.pdf".to_string()),
],
Default::default(),
).await?;All methods return edgequake_sdk::Result<T>. Errors are strongly typed:
use edgequake_sdk::Error;
match client.documents().get("doc-id").await {
Ok(doc) => println!("Found: {}", doc.id),
Err(Error::NotFound { message }) => println!("Not found: {message}"),
Err(Error::Unauthorized { message }) => println!("Auth failed: {message}"),
Err(Error::RateLimited { retry_after, .. }) => {
println!("Rate limited, retry after {:?}", retry_after);
}
Err(e) => {
// status_code() returns Option<u16>
println!("Error ({}): {e}", e.status_code().unwrap_or(0));
// is_retryable() checks if 429/5xx/network
println!("Retryable: {}", e.is_retryable());
}
}| Variant | HTTP Status | Description |
|---|---|---|
BadRequest |
400 | Invalid request parameters |
Unauthorized |
401 | Authentication failed |
Forbidden |
403 | Permission denied |
NotFound |
404 | Resource not found |
Conflict |
409 | Resource conflict |
Validation |
422 | Validation error |
RateLimited |
429 | Rate limit exceeded |
Server |
5xx | Server error |
Network |
— | Transport error |
Json |
— | Serialization error |
Url |
— | URL parsing error |
Config |
— | Configuration error |
Timeout |
— | Operation timeout |
The client automatically retries on:
- 429 Too Many Requests
- 500, 502, 503, 504 Server Errors
- Network errors (connection timeouts, etc.)
Default: 3 retries with 500ms base exponential backoff (500ms → 1s → 2s).
# Run unit + integration tests (uses wiremock, no server needed)
cargo test
# Run E2E tests against a live server
EDGEQUAKE_BASE_URL=http://localhost:8080 cargo test --features e2eApache-2.0 — see LICENSE.