Skip to main content

Rust Client

A modern, async Rust client library for hlquery, designed with a consistent API structure for familiarity and ease of use.

Features

  • Async/Await Support: Built on Tokio for modern async Rust
  • Type-safe: Strong typing throughout with serde for JSON handling
  • Consistent API Design: Familiar structure for developers
  • Authentication Support: Bearer token and X-API-Key authentication
  • Comprehensive Validation: Input validation for all operations
  • Error Handling: Rich error types with thiserror
  • Minimal Dependencies: Optimized for performance

Installation

Add this to your Cargo.toml:

[dependencies]
hlquery-rust-client = { path = "../api/rust" }
tokio = { version = "1", features = ["full"] }

Or if published to crates.io:

[dependencies]
hlquery-rust-client = "1.0"
tokio = { version = "1", features = ["full"] }

Quick Start

Basic Usage

use hlquery_rust_client::Client;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize client
let client = Client::new("http://localhost:9200", None)?;

// Health check
let health = client.health().await?;
println!("Status: {}", health.get_status_code());

// List collections
let collections = client.list_collections(0, 10).await?;
if collections.is_success() {
let body = collections.get_body();
if let Some(collections_array) = body.get("collections").and_then(|c| c.as_array()) {
println!("Found {} collections", collections_array.len());
}
}

Ok(())
}

With Authentication

use hlquery_rust_client::Client;
use std::collections::HashMap;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Method 1: Set token in constructor
let mut options = HashMap::new();
options.insert("token".to_string(), "your_token_here".to_string());
options.insert("auth_method".to_string(), "bearer".to_string());
let client = Client::new("http://localhost:9200", Some(options))?;

// Method 2: Set token dynamically
let client = Client::new("http://localhost:9200", None)?;
client.set_auth_token("your_token_here".to_string(), "bearer".to_string());

// Method 3: Use X-API-Key
client.set_auth_token("your_key_here".to_string(), "api-key".to_string());

Ok(())
}

API Reference

Client Initialization

let client = Client::new(base_url, options)?;

Parameters:

  • base_url (string, required): Base URL of hlquery server (e.g., "http://localhost:9200")
  • options (Option<HashMap<String, String>>, optional): Client options
    • "token": Authentication token
    • "auth_method": Authentication method ("bearer" or "api-key")
    • "timeout": Request timeout in milliseconds (default: 30000)

Health & Status

// Health check
let health = client.health().await?;

// Server statistics
let stats = client.stats().await?;

// Server information
let info = client.info().await?;

Collections API

// List collections
let collections = client.list_collections(0, 10).await?;

// Get collection details
let collection = client.get_collection("my_collection").await?;

// Compatibility alias: fields are returned in collection metadata
let fields = client.get_collection_fields("my_collection").await?;

// Get collection language information
let language = client.get_collection_language("my_collection").await?;

// Using the collections API handler
let collections_api = client.collections();

// Create collection
let schema = serde_json::json!({
"fields": [
{ "name": "title", "type": "string" },
{ "name": "content", "type": "string" }
]
});
let result = collections_api.create("new_collection", schema).await?;

// Update collection schema
let result = collections_api.update("collection_name", schema).await?;

// Delete collection
let result = collections_api.delete("collection_name").await?;

Documents API

use std::collections::HashMap;

// List documents
let mut params = HashMap::new();
params.insert("offset".to_string(), "0".to_string());
params.insert("limit".to_string(), "10".to_string());
let docs = client.list_documents("collection", Some(params)).await?;

// Get document by ID
let doc = client.get_document("collection", "doc_id").await?;

// Using the documents API handler
let documents_api = client.documents();

// Add document
let new_doc = serde_json::json!({
"id": "doc_1",
"title": "New Document",
"content": "Document content"
});
let result = documents_api.add("collection", new_doc).await?;

// Update document
let updated_doc = serde_json::json!({
"title": "Updated Document"
});
let result = documents_api.update("collection", "doc_id", updated_doc).await?;

// Delete document
let result = documents_api.delete("collection", "doc_id").await?;

// Bulk import
let bulk_docs = vec![
serde_json::json!({ "id": "doc1", "title": "Doc 1" }),
serde_json::json!({ "id": "doc2", "title": "Doc 2" })
];
let result = documents_api.import("collection", bulk_docs).await?;

Search API

use std::collections::HashMap;

// Simple search
let mut params = HashMap::new();
params.insert("q".to_string(), "search query".to_string());
params.insert("query_by".to_string(), "title,content".to_string());
params.insert("limit".to_string(), "10".to_string());
let results = client.search("collection", params).await?;

// Search with filters
let mut params = HashMap::new();
params.insert("q".to_string(), "query".to_string());
params.insert("query_by".to_string(), "title".to_string());
params.insert("filter_by".to_string(), "category:electronics".to_string());
params.insert("sort_by".to_string(), "price:asc".to_string());
params.insert("limit".to_string(), "20".to_string());
let results = client.search("collection", params).await?;

// Vector search
let mut params = HashMap::new();
params.insert("vector_query".to_string(), "[0.1,0.2,0.3,0.4,0.5]".to_string());
params.insert("limit".to_string(), "10".to_string());
params.insert("threshold".to_string(), "0.5".to_string());
let results = client.vector_search("collection", params).await?;

// Multi-search
let searches = vec![
serde_json::json!({
"collection": "col1",
"q": "query1",
"query_by": "title"
}),
serde_json::json!({
"collection": "col2",
"q": "query2",
"query_by": "content"
})
];
let results = client.search_api().multi_search(searches).await?;
// GET is also supported when explicitly required:
let results = client.search_api().multi_search_with_method(searches, "GET").await?;

Response Handling

All API methods return a Result<Response>:

let response = client.health().await?;

// Check success
if response.is_success() {
let body = response.get_body();
// Process response...
}

// Check status code
let status_code = response.get_status_code();

// Get error message
if response.is_error() {
if let Some(error) = response.get_error() {
println!("Error: {}", error);
}
}

Error Handling

The client uses Rust's Result type for error handling:

use hlquery_rust_client::{Client, HlqueryError};

match client.search("collection", params).await {
Ok(response) => {
// Handle success
}
Err(HlqueryError::RequestError(e)) => {
println!("Request failed: {}", e);
}
Err(HlqueryError::ValidationError(msg)) => {
println!("Validation failed: {}", msg);
}
Err(HlqueryError::AuthenticationError(msg)) => {
println!("Auth failed: {}", msg);
}
Err(e) => {
println!("Error: {}", e);
}
}

Input Validation

The Rust client includes comprehensive input validation for all operations:

use hlquery_rust_client::utils::validator;

// Validate collection name
if let Err(e) = validator::validate_collection_name("my_collection") {
println!("Invalid collection name: {}", e);
}

// Validate document ID
if let Err(e) = validator::validate_document_id("doc_123") {
println!("Invalid document ID: {}", e);
}

// Validate search parameters
let mut params = HashMap::new();
params.insert("q".to_string(), "query".to_string());
if let Err(e) = validator::validate_search_params(&params) {
println!("Invalid search params: {}", e);
}

Examples

Run Examples

# Comprehensive example
cargo run --example example

# Basic usage
cargo run --example basic_usage

# Collections examples
cargo run --example collections

# Documents examples
cargo run --example documents

# Search examples
cargo run --example search

With Authentication Token

cargo run --example example your_token_here

Requirements

  • Rust 1.70+ (Edition 2021)
  • Tokio runtime (for async support)

Dependencies

  • reqwest: HTTP client
  • serde / serde_json: JSON serialization
  • url: URL parsing
  • thiserror: Error handling
  • md5: Token generation
  • urlencoding: URL encoding
  • tokio: Async runtime

Architecture

The client follows a modular architecture:

  • Client: Main entry point, coordinates all operations
  • Request: Handles HTTP requests and authentication
  • Response: Wraps HTTP responses with helper methods
  • Collections: Collection management operations
  • Documents: Document CRUD operations
  • Search: Search operations
  • Utils: Configuration, validation, and authentication utilities

Directory Structure

rust/
├── Cargo.toml # Rust package definition
├── README.md # Documentation
├── LICENSE # BSD-3-Clause license

├── src/ # Source code
│ ├── lib.rs # Main entry point
│ ├── client.rs # Main client class
│ ├── request.rs # HTTP request handler
│ ├── response.rs # Response wrapper
│ ├── error.rs # Error types
│ ├── collections.rs # Collections API
│ ├── documents.rs # Documents API
│ ├── search.rs # Search API
│ └── utils/ # Utility modules
│ ├── mod.rs # Module exports
│ ├── config.rs # Configuration utilities
│ ├── validator.rs # Input validation
│ └── auth.rs # Authentication utilities

└── examples/ # Example code
├── example.rs # Comprehensive example
├── basic_usage.rs # Basic usage examples
├── collections.rs # Collection management
├── documents.rs # Document CRUD
└── search.rs # Search examples

License

BSD 3-Clause License

Copyright (C) 2021-2026, Carlos F. Ferry <carlos.ferry@gmail.com>

Support

  • Rust API Guide: See comprehensive Rust API documentation with detailed examples
  • API Documentation: See main hlquery API docs
  • Examples: Check the examples/ directory in api/rust/
  • Issues: Report issues in the main hlquery repository

Contributing

Contributions are welcome! The client follows Rust best practices:

  • Use async/await for all I/O operations
  • Return Result<T> for error handling
  • Use serde for JSON serialization
  • Follow Rust naming conventions (snake_case)
  • Include comprehensive input validation