use serde::Serialize;
use std::collections::HashMap;
#[derive(Debug, Default, Clone)]
pub struct QueryBuilder {
params: HashMap<String, Vec<String>>,
}
impl Serialize for QueryBuilder {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let mut pairs: Vec<(&str, &str)> = Vec::new();
for (key, values) in &self.params {
for value in values {
pairs.push((key.as_str(), value.as_str()));
}
}
pairs.serialize(serializer)
}
}
impl QueryBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn filter(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
let key = key.into();
let value = value.into();
self.params.entry(key).or_default().push(value);
self
}
pub fn filters<I, K, V>(mut self, filters: I) -> Self
where
I: IntoIterator<Item = (K, V)>,
K: Into<String>,
V: Into<String>,
{
for (key, value) in filters {
self = self.filter(key, value);
}
self
}
pub fn limit(self, limit: usize) -> Self {
self.filter("limit", limit.to_string())
}
pub fn offset(self, offset: usize) -> Self {
self.filter("offset", offset.to_string())
}
pub fn order_by(self, field: impl Into<String>) -> Self {
self.filter("ordering", field)
}
pub fn search(self, query: impl Into<String>) -> Self {
self.filter("q", query)
}
pub fn id(self, id: impl Into<String>) -> Self {
self.filter("id", id)
}
pub fn ids<I, V>(mut self, ids: I) -> Self
where
I: IntoIterator<Item = V>,
V: Into<String>,
{
for id in ids {
self = self.filter("id", id);
}
self
}
pub fn tag(self, tag: impl Into<String>) -> Self {
self.filter("tag", tag)
}
pub fn param(self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.filter(key, value)
}
pub fn build(self) -> Self {
self
}
pub fn is_empty(&self) -> bool {
self.params.is_empty()
}
}
#[allow(dead_code)]
pub fn filters<I, K, V>(filters: I) -> QueryBuilder
where
I: IntoIterator<Item = (K, V)>,
K: Into<String>,
V: Into<String>,
{
QueryBuilder::new().filters(filters)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_query() {
let query = QueryBuilder::new();
assert!(query.is_empty());
}
#[test]
fn test_single_filter() {
let query = QueryBuilder::new().filter("site", "dc1");
assert!(!query.is_empty());
assert_eq!(query.params.get("site").unwrap(), &vec!["dc1"]);
}
#[test]
fn test_multiple_values_same_key() {
let query = QueryBuilder::new()
.filter("site", "dc1")
.filter("site", "dc2");
let sites = query.params.get("site").unwrap();
assert_eq!(sites.len(), 2);
assert!(sites.contains(&"dc1".to_string()));
assert!(sites.contains(&"dc2".to_string()));
}
#[test]
fn test_limit_and_offset() {
let query = QueryBuilder::new().limit(100).offset(50);
assert_eq!(query.params.get("limit").unwrap(), &vec!["100"]);
assert_eq!(query.params.get("offset").unwrap(), &vec!["50"]);
}
#[test]
fn test_order_by() {
let query = QueryBuilder::new().order_by("-name");
assert_eq!(query.params.get("ordering").unwrap(), &vec!["-name"]);
}
#[test]
fn test_search() {
let query = QueryBuilder::new().search("test device");
assert_eq!(query.params.get("q").unwrap(), &vec!["test device"]);
}
#[test]
fn test_multiple_ids() {
let query = QueryBuilder::new().ids(vec!["1", "2", "3"]);
let ids = query.params.get("id").unwrap();
assert_eq!(ids.len(), 3);
}
#[test]
fn test_serialization() {
let query = QueryBuilder::new()
.filter("site", "dc1")
.filter("status", "active")
.limit(50);
let json = serde_json::to_value(&query).unwrap();
let pairs = json
.as_array()
.expect("query serialization should be a list of pairs");
assert!(
pairs
.iter()
.any(|pair| pair == &serde_json::json!(["site", "dc1"]))
);
assert!(
pairs
.iter()
.any(|pair| pair == &serde_json::json!(["status", "active"]))
);
assert!(
pairs
.iter()
.any(|pair| pair == &serde_json::json!(["limit", "50"]))
);
}
#[test]
fn test_filters_helper() {
let query = filters([("site", "dc1"), ("status", "active")]);
assert_eq!(query.params.len(), 2);
}
}