Crates.io | query-x |
lib.rs | query-x |
version | 0.3.0 |
created_at | 2025-09-25 08:31:56.858627+00 |
updated_at | 2025-09-25 15:41:16.666444+00 |
description | Convert http query params to sql queries |
homepage | |
repository | https://github.com/0xC0DE666/query-x |
max_upload_size | |
id | 1854366 |
size | 144,459 |
A powerful Rust library for parsing HTTP query parameters into structured queries with support for both traditional and advanced similarity-based filtering, plus optional SQL generation.
?name=john
) and advanced (?name=contains:john
) query parametersAdd this to your Cargo.toml
:
[dependencies]
query-x = "0.3.0"
# Optional: Enable SQL generation (enabled by default)
# query-x = { version = "0.3.0", default-features = false }
use query_x::Query;
// Parse traditional HTTP query parameters
let query = Query::from_http("name=john&age=25&city=london".to_string())?;
// Access parameters
let name_param = query.parameters.0.get("name").unwrap();
assert_eq!(name_param.0, query_x::Similarity::Equals);
assert_eq!(name_param.1, vec!["john"]);
// Convert back to HTTP
let http_string = query.to_http();
// Result: "name=equals:john&age=equals:25&city=equals:london&limit=50&offset=0"
use query_x::Query;
// Parse advanced query parameters
let query = Query::from_http("name=contains:john&age=between:20,30&price=greater:100".to_string())?;
// Access parameters with different similarity types
let name_param = query.parameters.0.get("name").unwrap();
assert_eq!(name_param.0, query_x::Similarity::Contains);
assert_eq!(name_param.1, vec!["john"]);
let age_param = query.parameters.0.get("age").unwrap();
assert_eq!(age_param.0, query_x::Similarity::Between);
assert_eq!(age_param.1, vec!["20", "30"]);
use query_x::Query;
// Mix traditional and advanced parameters
let query = Query::from_http("name=john&name=jane&age=contains:25&status=active".to_string())?;
// Traditional parameters (repeated values)
let name_param = query.parameters.0.get("name").unwrap();
assert_eq!(name_param.0, query_x::Similarity::Equals);
assert_eq!(name_param.1, vec!["john", "jane"]);
// Advanced parameters
let age_param = query.parameters.0.get("age").unwrap();
assert_eq!(age_param.0, query_x::Similarity::Contains);
assert_eq!(age_param.1, vec!["25"]);
You can also build queries programmatically using the builder pattern:
use query_x::{Query, Parameters, SortFields};
// Build parameters using the builder pattern
let mut parameters = Parameters::new();
parameters
.equals("name".to_string(), vec!["john".to_string(), "jane".to_string()])
.contains("description".to_string(), vec!["rust".to_string()])
.between("age".to_string(), vec!["18".to_string(), "65".to_string()])
.greater("price".to_string(), vec!["100".to_string()]);
// Build sort fields using the builder pattern
let mut sort_fields = SortFields::new();
sort_fields
.descending("date_created".to_string())
.ascending("name".to_string());
// Create the query
let query = Query::init(parameters, sort_fields, 25, 0);
// Convert to HTTP string
let http_string = query.to_http();
// Result: "name=equals:john,jane&description=contains:rust&age=between:18,65&price=greater:100&order=date_created:desc,name:asc&limit=25&offset=0"
The library provides multiple ways to access parameter data for different use cases:
use query_x::Query;
let query = Query::from_http("name=contains:john&age=between:20,30".to_string())?;
// Access parameters using semantic methods
let name_param = query.parameters.inner().get("name").unwrap();
assert_eq!(*name_param.similarity(), Similarity::Contains);
assert_eq!(name_param.values(), &vec!["john".to_string()]);
let age_param = query.parameters.inner().get("age").unwrap();
assert_eq!(*age_param.similarity(), Similarity::Between);
assert_eq!(age_param.values(), &vec!["20".to_string(), "30".to_string()]);
For advanced operations, you can access the underlying collections directly:
use query_x::Query;
let mut query = Query::new();
query.parameters.equals("name".to_string(), vec!["john".to_string()]);
query.sort_fields.ascending("date_created".to_string());
// Access the underlying IndexMap for complex operations
let param_map = query.parameters.inner();
let sort_map = query.sort_fields.inner();
// Iterate over all parameters
for (key, param) in param_map {
println!("{}: {:?} = {:?}", key, param.similarity(), param.values());
}
// Perform bulk operations
let param_map_mut = query.parameters.inner_mut();
param_map_mut.insert("new_param".to_string(), (Similarity::Greater, vec!["100".to_string()]));
The library maintains full backward compatibility with tuple access:
use query_x::Query;
let query = Query::from_http("name=contains:john".to_string())?;
let param = query.parameters.inner().get("name").unwrap();
// Old tuple access still works
assert_eq!(param.0, Similarity::Contains);
assert_eq!(param.1, vec!["john".to_string()]);
// New semantic access also works
assert_eq!(*param.similarity(), Similarity::Contains);
assert_eq!(param.values(), &vec!["john".to_string()]);
// Both return the same data
assert_eq!(param.0, *param.similarity());
assert_eq!(param.1, *param.values());
The library supports various similarity types for advanced filtering:
Similarity | Description | Example | SQL Equivalent |
---|---|---|---|
equals |
Exact match | name=equals:john |
name = ? |
contains |
Substring match | name=contains:john |
name LIKE ? |
starts-with |
Prefix match | name=starts-with:john |
name LIKE ? |
ends-with |
Suffix match | name=ends-with:john |
name LIKE ? |
between |
Range match | age=between:20,30 |
age BETWEEN ? AND ? |
greater |
Greater than | price=greater:100 |
price > ? |
lesser |
Less than | price=lesser:100 |
price < ? |
greater-or-equal |
Greater or equal | price=greater-or-equal:100 |
price >= ? |
lesser-or-equal |
Less or equal | price=lesser-or-equal:100 |
price <= ? |
// Multiple values for equals (IN clause)
"?name=equals:john,jane,bob"
// โ name IN ('john', 'jane', 'bob')
// Multiple ranges for between
"?age=between:18,25,30,40,50,65"
// โ (age BETWEEN 18 AND 25) OR (age BETWEEN 30 AND 40) OR (age BETWEEN 50 AND 65)
// Note: Odd values (65) are ignored
use query_x::Query;
let query = Query::from_http("name=john&order=date_created:desc,name:asc&limit=25&offset=10".to_string())?;
// Access sorting
assert_eq!(query.sort_fields.0.len(), 2);
assert_eq!(query.sort_fields.0[0].name, "date_created");
assert_eq!(query.sort_fields.0[0].order, query_x::SortOrder::Descending);
// Access pagination
assert_eq!(query.limit, 25);
assert_eq!(query.offset, 10);
Enable the sql
feature (enabled by default) to generate SQL queries:
use query_x::Query;
let query = Query::from_http("name=contains:john&age=between:20,30&order=date_created:desc&limit=10".to_string())?;
// Generate SQL with parameter placeholders
let sql = query.to_sql();
// Result: "WHERE name LIKE ? AND age BETWEEN ? AND ? ORDER BY date_created DESC LIMIT ? OFFSET ?"
// Use with your database driver
// let stmt = conn.prepare(&format!("SELECT * FROM users {}", sql))?;
// let rows = stmt.query(["%john%", "20", "30", "10", "0"])?;
// Traditional parameters
"?name=john&name=jane&age=25"
// โ "WHERE name IN (?, ?) AND age = ? LIMIT ? OFFSET ?"
// Advanced parameters
"?name=contains:john&age=between:20,30&price=greater:100"
// โ "WHERE name LIKE ? AND age BETWEEN ? AND ? AND price > ? LIMIT ? OFFSET ?"
// Complex mixed query
"?name=john&name=jane&age=contains:25&price=greater:100&order=date_created:desc&limit=20"
// โ "WHERE name IN (?, ?) AND age LIKE ? AND price > ? ORDER BY date_created DESC LIMIT ? OFFSET ?"
The library automatically handles URL encoding and decoding:
use query_x::Query;
// URL encoded parameters
let query = Query::from_http("name=john%20doe&email=test%40example.com".to_string())?;
let name_param = query.parameters.0.get("name").unwrap();
assert_eq!(name_param.1, vec!["john doe"]); // Automatically decoded
let email_param = query.parameters.0.get("email").unwrap();
assert_eq!(email_param.1, vec!["test@example.com"]); // Automatically decoded
use query_x::Query;
let query = Query::from_http("name=john&age=25&email=john@example.com".to_string())?;
// Keep only specific parameters
let filtered_params = query.parameters.keep(vec!["name".to_string(), "age".to_string()]);
let filtered_query = Query::init(filtered_params, query.sort_fields, query.limit, query.offset);
// Result: Only name and age parameters remain
// Remove specific parameters
let filtered_params = query.parameters.remove(vec!["email".to_string()]);
let filtered_query = Query::init(filtered_params, query.sort_fields, query.limit, query.offset);
// Result: email parameter is removed, name and age remain
use query_x::{Query, error::Error};
match Query::from_http("invalid=query".to_string()) {
Ok(query) => {
// Handle successful parsing
println!("Query parsed successfully: {:?}", query);
}
Err(Error::InvalidParameter(msg)) => {
// Handle invalid parameter format
eprintln!("Invalid parameter: {}", msg);
}
Err(Error::InvalidSortField(msg)) => {
// Handle invalid sort field
eprintln!("Invalid sort field: {}", msg);
}
Err(e) => {
// Handle other errors
eprintln!("Error: {}", e);
}
}
use query_x::Query;
// Complex product search with multiple filters
let query = Query::from_http(
"category=electronics&brand=apple&brand=samsung&price=between:100,500&rating=greater-or-equal:4&order=price:asc&limit=20"
)?;
// Generate SQL for product search
let sql = query.to_sql();
// "WHERE category = ? AND brand IN (?, ?) AND price BETWEEN ? AND ? AND rating >= ? ORDER BY price ASC LIMIT ? OFFSET ?"
use query_x::Query;
// User filtering and management
let query = Query::from_http(
"name=contains:john&age=greater:18&status=active&role=admin&role=user&order=created_at:desc&limit=50"
)?;
// Generate SQL for user query
let sql = query.to_sql();
// "WHERE name LIKE ? AND age > ? AND status = ? AND role IN (?, ?) ORDER BY created_at DESC LIMIT ? OFFSET ?"
use query_x::Query;
// Content filtering with date ranges
let query = Query::from_http(
"title=contains:rust&tags=programming&tags=web&date=between:2023-01-01,2023-12-31&published=true&order=date:desc&limit=25"
)?;
// Generate SQL for content query
let sql = query.to_sql();
// "WHERE title LIKE ? AND tags IN (?, ?) AND date BETWEEN ? AND ? AND published = ? ORDER BY date DESC LIMIT ? OFFSET ?"
The library supports feature flags for optional functionality:
[dependencies]
# Default: includes SQL generation
query-x = "0.3.0"
# Without SQL generation (smaller binary)
query-x = { version = "0.3.0", default-features = false }
# With specific features
query-x = { version = "0.3.0", features = ["sql"] }
Query
: Main query structure containing parameters, sorting, and paginationParameters
: Collection of query parameters with builder methodsSortFields
: Collection of sort fields with builder methodsParameter
: Type alias for (Similarity, Vec<String>)
with trait methodsParameterGet
: Trait providing semantic access to parameter dataSimilarity
: Enum defining comparison types (equals, contains, between, etc.)SortOrder
: Sort direction (ascending, descending)Query::from_http()
: Parse HTTP query string into Query structQuery::to_http()
: Convert Query struct back to HTTP query stringQuery::to_sql()
: Generate SQL query with parameter placeholders (feature-gated)Query::init()
: Create Query with custom parameters, sort fields, limit, and offsetParameters::new()
: Create new Parameters collectionParameters::equals()
, Parameters::contains()
, etc.: Builder methods for adding parametersParameters::inner()
: Get immutable reference to underlying IndexMapParameters::inner_mut()
: Get mutable reference to underlying IndexMapParameters::keep()
: Filter parameters to keep only specified keysParameters::remove()
: Remove specified parametersSortFields::new()
: Create new SortFields collectionSortFields::ascending()
, SortFields::descending()
: Builder methods for adding sort fieldsSortFields::inner()
: Get immutable reference to underlying IndexMapSortFields::inner_mut()
: Get mutable reference to underlying IndexMapSortFields::keep()
: Filter sort fields to keep only specified keysSortFields::remove()
: Remove specified sort fieldsParameter::similarity()
: Get reference to similarity typeParameter::values()
: Get reference to parameter valuesContributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
This project is licensed under either of
at your option.
See CHANGELOG.md for a detailed list of changes.
Made with โค๏ธ in Rust